Skip to content

Commit 7bae4b6

Browse files
committed
feat: implement RubyGit::Worktree#status
1 parent f75e0ec commit 7bae4b6

24 files changed

+3415
-3
lines changed

.rubocop.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
inherit_gem:
22
main_branch_shared_rubocop_config: config/rubocop.yml
33

4+
Metrics/MethodLength:
5+
Exclude:
6+
- "spec/spec_helper.rb"
7+
- "spec/**/*_spec.rb"
8+
9+
Metrics/AbcSize:
10+
Exclude:
11+
- "spec/spec_helper.rb"
12+
- "spec/**/*_spec.rb"
13+
414
AllCops:
515
# Pin this project to Ruby 3.1 in case the shared config above is upgraded to 3.2
616
# or later.

bin/console

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ require 'ruby_git'
1212
# Pry.start
1313

1414
require 'irb'
15+
require_relative '../spec/spec_helper'
16+
1517
IRB.start(__FILE__)

lib/ruby_git.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require_relative 'ruby_git/command_line'
44
require_relative 'ruby_git/encoding_normalizer'
55
require_relative 'ruby_git/errors'
6+
require_relative 'ruby_git/status'
67
require_relative 'ruby_git/version'
78
require_relative 'ruby_git/worktree'
89

lib/ruby_git/encoding_normalizer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'rchardet'
44

55
module RubyGit
6-
# Utility to normalize string encoding to the {#normalized_encoding}
6+
# Utility to normalize string encoding
77
# @api public
88
module EncodingNormalizer
99
# Detects the character encoding used to create a string or binary data

lib/ruby_git/status.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'status/branch'
4+
require_relative 'status/entry'
5+
require_relative 'status/ignored_entry'
6+
require_relative 'status/ordinary_entry'
7+
require_relative 'status/parser'
8+
require_relative 'status/renamed_entry'
9+
require_relative 'status/report'
10+
require_relative 'status/stash'
11+
require_relative 'status/submodule_status'
12+
require_relative 'status/unmerged_entry'
13+
require_relative 'status/untracked_entry'
14+
15+
module RubyGit
16+
# The working tree status
17+
module Status
18+
# Parse output of `git status` and return a structured report
19+
#
20+
# @example
21+
# output = `git status -u --porcelain=v2 --renames --branch --show-stash -z`
22+
# status = RubyGit::Status.parse(output)
23+
# status.branch.name #=> 'main'
24+
#
25+
# @param status_output [String] the raw output from git status command
26+
# @return [RubyGit::Status::Report] a structured representation of git status
27+
#
28+
# @api public
29+
def self.parse(status_output)
30+
Parser.parse(status_output)
31+
end
32+
end
33+
end

lib/ruby_git/status/branch.rb

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# frozen_string_literal: true
2+
3+
module RubyGit
4+
module Status
5+
# Represents git branch information
6+
#
7+
# @api public
8+
class Branch
9+
# @attribute [rw] name
10+
#
11+
# The name of the current branch
12+
#
13+
# @example
14+
# branch.name #=> 'main'
15+
#
16+
# @return [String, nil] branch name or nil if detached HEAD
17+
#
18+
# @api public
19+
attr_accessor :name
20+
21+
# @attribute [rw] oid
22+
#
23+
# The object ID (hash) of the current commit
24+
#
25+
# @example
26+
# branch.oid #=> 'abcdef1234567890'
27+
#
28+
# @return [String] commit hash
29+
#
30+
# @api public
31+
attr_accessor :oid
32+
33+
# @attribute [rw] upstream
34+
#
35+
# The name of the upstream branch
36+
#
37+
# @example
38+
# branch.upstream #=> 'origin/main'
39+
#
40+
# @return [String, nil] upstream branch name or nil if no upstream
41+
#
42+
# @api public
43+
attr_accessor :upstream
44+
45+
# @attribute [rw] ahead
46+
#
47+
# Number of commits ahead of upstream
48+
#
49+
# @example
50+
# branch.ahead #=> 2
51+
#
52+
# @return [Integer] number of commits ahead
53+
#
54+
# @api public
55+
attr_accessor :ahead
56+
57+
# @attribute [rw] behind
58+
#
59+
# Number of commits behind upstream
60+
#
61+
# @example
62+
# branch.behind #=> 3
63+
#
64+
# @return [Integer] number of commits behind
65+
#
66+
# @api public
67+
attr_accessor :behind
68+
69+
# Check if the branch has an upstream configured
70+
#
71+
# @example
72+
# branch.upstream? #=> true
73+
#
74+
# @return [Boolean] true if upstream is configured
75+
#
76+
def upstream?
77+
!@upstream.nil?
78+
end
79+
80+
# Check if HEAD is detached
81+
#
82+
# @example
83+
# branch.detached? #=> true
84+
#
85+
# @return [Boolean] true if HEAD is detached
86+
#
87+
def detached?
88+
@name.nil?
89+
end
90+
end
91+
end
92+
end

lib/ruby_git/status/entry.rb

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# frozen_string_literal: true
2+
3+
module RubyGit
4+
module Status
5+
# Base class for git status entries
6+
#
7+
# @api public
8+
class Entry
9+
# Status code mapping to symbols
10+
STATUS_CODES = {
11+
'.': :unmodified,
12+
M: :modified,
13+
T: :type_changed,
14+
A: :added,
15+
D: :deleted,
16+
R: :renamed,
17+
C: :copied,
18+
U: :updated_but_unmerged,
19+
'?': :untracked,
20+
'!': :ignored
21+
}.freeze
22+
23+
# Rename operation mapping to symbols
24+
RENAME_OPERATIONS = {
25+
'R' => :rename
26+
# git status doesn't actually try to detect copies
27+
# 'C' => :copy
28+
}.freeze
29+
30+
# @attribute [r] path
31+
#
32+
# The path of the file
33+
#
34+
# @example
35+
# entry.path #=> 'lib/example.rb'
36+
#
37+
# @return [String] file path
38+
#
39+
attr_reader :path
40+
41+
# Initialize a new entry
42+
#
43+
# @example
44+
# Entry.new('lib/example.rb')
45+
#
46+
# @param path [String] file path
47+
#
48+
def initialize(path)
49+
@path = path
50+
end
51+
52+
# Convert a status code to a symbol
53+
#
54+
# @example
55+
# Entry.status_to_symbol('M') #=> :modified
56+
#
57+
# @param code [String] status code
58+
# @return [Symbol] status as symbol
59+
#
60+
def self.status_to_symbol(code)
61+
STATUS_CODES[code.to_sym] || :unknown
62+
end
63+
64+
# Convert a rename operation to a symbol
65+
#
66+
# @example
67+
# Entry.rename_operation_to_symbol('R') #=> :rename
68+
#
69+
# @param code [String] the operation code
70+
# @return [Symbol] operation as symbol
71+
#
72+
def self.rename_operation_to_symbol(code)
73+
RENAME_OPERATIONS[code] || :unknown
74+
end
75+
76+
# Get the staging status
77+
#
78+
# @example
79+
# entry.staging_status #=> :modified
80+
#
81+
# @return [Symbol, nil] staging status symbol or nil if not applicable
82+
#
83+
def index_status = nil
84+
85+
# Get the worktree status
86+
#
87+
# @example
88+
# entry.worktree_status #=> :unchanged
89+
#
90+
# @return [Symbol, nil] worktree status symbol or nil if not applicable
91+
#
92+
def worktree_status = nil
93+
end
94+
end
95+
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# frozen_string_literal: true
2+
3+
require_relative 'entry'
4+
5+
module RubyGit
6+
module Status
7+
# Represents an ignored file in git status
8+
#
9+
# @api public
10+
class IgnoredEntry < Entry
11+
# Parse a git status line to create an ignored entry
12+
#
13+
# @example
14+
# IgnoredEntry.parse('!! lib/example.rb') #=> #<RubyGit::Status::IgnoredEntry:0x00000001046bd488 ...>
15+
#
16+
# @param line [String] line from git status
17+
# @return [RubyGit::Status::IgnoredEntry] parsed entry
18+
#
19+
def self.parse(line)
20+
tokens = line.split(' ', 2)
21+
new(path: tokens[1])
22+
end
23+
24+
# Initialize with the path
25+
#
26+
# @example
27+
# IgnoredEntry.new(path: 'lib/example.rb')
28+
#
29+
# @param path [String] file path
30+
#
31+
def initialize(path:)
32+
super(path)
33+
end
34+
end
35+
end
36+
end

0 commit comments

Comments
 (0)