Skip to content

Commit d7a3232

Browse files
committed
feat: add Worktree#status
1 parent 7bae4b6 commit d7a3232

File tree

13 files changed

+301
-23
lines changed

13 files changed

+301
-23
lines changed

.github/workflows/continuous_integration.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ env:
1313
# SimpleCov suggests setting the JRuby --debug flag to ensure that coverage
1414
# results from JRuby are complete.
1515
JRUBY_OPTS: --debug
16+
GIT_AUTHOR_NAME: Git Author
17+
GIT_AUTHOR_EMAIL: git_author@example.com
18+
GIT_COMMITTER_NAME: Git Committer
19+
GIT_COMMITTER_EMAIL: git_committer@example.com
1620

1721
# Supported platforms / Ruby versions:
1822
# - Ubuntu: MRI (3.1, 3.2, 3.3, 3.4), TruffleRuby (24), JRuby (9.4)

.github/workflows/experimental_ruby_builds.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ jobs:
2222
runs-on: ${{ matrix.operating-system }}
2323
continue-on-error: true
2424

25+
env:
26+
FAIL_ON_LOW_COVERAGE: ${{ matrix.fail_on_low_coverage }}
27+
GIT_AUTHOR_NAME: Git Author
28+
GIT_AUTHOR_EMAIL: git_author@example.com
29+
GIT_COMMITTER_NAME: Git Committer
30+
GIT_COMMITTER_EMAIL: git_committer@example.com
31+
2532
strategy:
2633
fail-fast: false
2734
matrix:

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/repository'
67
require_relative 'ruby_git/status'
78
require_relative 'ruby_git/version'
89
require_relative 'ruby_git/worktree'

lib/ruby_git/command_line/runner.rb

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def call(*args, **options_hash)
166166
options_hash[:raise_errors] = false
167167
options = RubyGit::CommandLine::Options.new(logger: logger, **options_hash)
168168
begin
169-
result = ProcessExecuter.run_with_options([env, *build_git_cmd(args)], options)
169+
result = run_with_chdir([env, *build_git_cmd(args)], options)
170170
rescue ProcessExecuter::ProcessIOError => e
171171
raise RubyGit::ProcessIOError.new(e.message), cause: e.exception.cause
172172
end
@@ -175,6 +175,39 @@ def call(*args, **options_hash)
175175

176176
private
177177

178+
# Run command with options with special handling for the `chdir` option on JRuby
179+
#
180+
# JRuby does not support the `chdir` option in `Process.spawn`. Note that this
181+
# workaround means that this library is not thread-safe when using JRuby.
182+
#
183+
# @param args [Array<String>] the command to run
184+
# @param options [RubyGit::CommandLine::Options] the options to pass to `Process.spawn`
185+
#
186+
# @return [ProcessExecuter::Result] the result of the command
187+
#
188+
# @api private
189+
#
190+
def run_with_chdir(args, options)
191+
return ProcessExecuter.run_with_options(args, options) unless jruby? && options.chdir != :not_set
192+
193+
# :nocov: Not executed in MRI Ruby
194+
Dir.chdir(options.chdir) do
195+
saved_chdir = options.chdir
196+
options.merge!(chdir: :not_set)
197+
ProcessExecuter.run_with_options(args, options).tap do
198+
options.merge!(chdir: saved_chdir)
199+
end
200+
end
201+
# :nocov:
202+
end
203+
204+
# Returns true if running on JRuby
205+
#
206+
# @return [Boolean]
207+
#
208+
# @api private
209+
def jruby? = RUBY_ENGINE == 'jruby'
210+
178211
# Build the git command line from the available sources to send to `Process.spawn`
179212
# @return [Array<String>]
180213
# @api private

lib/ruby_git/repository.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+
module RubyGit
4+
# The repository is the database of all the objects, refs, and other data that
5+
# make up the history of a project.
6+
#
7+
# @api public
8+
#
9+
class Repository
10+
# @attribute [r] path
11+
#
12+
# The absolute path to the repository
13+
#
14+
# @example
15+
# repository = RubyGit::Repository.new('.git')
16+
# repository.path = '/absolute/path/.git'
17+
#
18+
# @return [String]
19+
#
20+
attr_reader :path
21+
22+
# Create a new Repository object with the given repository path
23+
#
24+
# @example
25+
# RubyGit::Repository.new('/path/to/repository') #=> #<RubyGit::Repository ...>
26+
#
27+
# @param [String] repository_path the path to the repository
28+
#
29+
def initialize(repository_path)
30+
@path = File.expand_path(repository_path)
31+
end
32+
end
33+
end

lib/ruby_git/worktree.rb

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# frozen_string_literal: true
22

3-
require 'open3'
4-
53
module RubyGit
64
# The working tree is a directory tree consisting of the checked out files that
75
# you are currently working on.
@@ -106,9 +104,64 @@ def self.clone(repository_url, to_path: '')
106104
new(to_path)
107105
end
108106

107+
# Show the working tree and index status
108+
#
109+
# @example worktree = Worktree.open(worktree_path) worktree.status #=>
110+
# #<RubyGit::Status::Report ...>
111+
#
112+
# @param untracked_files [:all, :normal, :no] Defines how untracked files will be
113+
# handled
114+
#
115+
# See [git-staus
116+
# --untracked-files](https://git-scm.com/docs/git-status#Documentation/git-status.txt---untracked-filesltmodegt).
117+
#
118+
# @param ignored [:traditional, :matching, :no] Defines how ignored files will be
119+
# handled, :no to not include ignored files
120+
#
121+
# See [git-staus
122+
# --ignored](https://git-scm.com/docs/git-status#Documentation/git-status.txt---ignoredltmodegt).
123+
#
124+
# @param ignore_submodules [:all, :dirty, :untracked, :none] Default is :all
125+
#
126+
# See [git-staus
127+
# --ignore-submodules](https://git-scm.com/docs/git-status#Documentation/git-status.txt---ignore-submodulesltwhengt).
128+
#
129+
# @return [RubyGit::Status::Report] the status of the working tree
130+
#
131+
def status(untracked_files: :all, ignored: :no, ignore_submodules: :all)
132+
command = %w[status --porcelain=v2 --branch --show-stash --ahead-behind --renames -z]
133+
command << "--untracked-files=#{untracked_files}"
134+
command << "--ignored=#{ignored}"
135+
command << "--ignore-submodules=#{ignore_submodules}"
136+
options = { out: StringIO.new, err: StringIO.new }
137+
status_output = run(*command, **options).stdout
138+
RubyGit::Status.parse(status_output)
139+
end
140+
141+
# Return the repository associated with the worktree
142+
#
143+
# @example
144+
# worktree = Worktree.open(worktree_path)
145+
# worktree.repository #=> #<RubyGit::Repository ...>
146+
#
147+
# @return [RubyGit::Repository] the repository associated with the worktree
148+
#
149+
def repository
150+
@repository ||= begin
151+
command = %w[rev-parse --git-dir]
152+
options = { chdir: path, chomp: true, out: StringIO.new, err: StringIO.new }
153+
# rev-parse path might be relative to the worktree, thus the need to expand it
154+
git_dir = File.expand_path(RubyGit::CommandLine.run(*command, **options).stdout, path)
155+
Repository.new(git_dir)
156+
end
157+
end
158+
109159
private
110160

111161
# Create a Worktree object
162+
#
163+
# @param worktree_path [String] a path anywhere in the worktree
164+
#
112165
# @api private
113166
#
114167
def initialize(worktree_path)
@@ -132,25 +185,19 @@ def root_path(worktree_path)
132185
RubyGit::CommandLine.run(*command, **options).stdout
133186
end
134187

135-
# def run(*command, **options)
136-
# RubyGit::CommandLine.run(*command, worktree_path: path, **options)
137-
# end
138-
139-
# #
140-
# # @param untracked_files [Symbol] Can be :all, :normal, :no
141-
# # @param ignore_submodules [Symbol] Can be :all, :dirty, :untracked, :none
142-
# # @param ignored [Symbol] Can be :traditional, :matching, :no
143-
# # @param renames [Boolean] Whether to detect renames
144-
# def status(untracked_files:)
145-
# # -z for null-terminated output
146-
# # --porcelain for machine-readable output
147-
# git status --porcelain=v2 --untracked-files --branch --show-stash --ahead-behind --renames -z
148-
# command = ['status', '--porcelain', '--branch', '-z']
149-
# command << '--untracked-files=all' if untracked_files == :all
150-
# command << '--untracked-files=no' if untracked_files == :no
151-
# options = { chdir: path, out: StringIO.new, err: StringIO.new }
152-
# result = RubyGit::CommandLine.run(*command, **options)
153-
# result.stdout
154-
# end
188+
# Run a Git command in this worktree
189+
#
190+
# Passes the repository path and worktree path to RubyGit::CommandLine.run
191+
#
192+
# @param command [Array<String>] the git command to run
193+
# @param options [Hash] options to pass to RubyGit::CommandLine.run
194+
#
195+
# @return [RubyGit::CommandLineResult] the result of the git command
196+
#
197+
# @api private
198+
#
199+
def run(*command, **options)
200+
RubyGit::CommandLine.run(*command, repository_path: repository.path, worktree_path: path, **options)
201+
end
155202
end
156203
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RubyGit::Repository do
4+
let(:repository) { RubyGit::Repository.new(repository_path) }
5+
6+
describe '#initialize' do
7+
subject { repository }
8+
9+
context 'when given a repository path' do
10+
let(:repository_path) { File.expand_path('/path/to/repository') }
11+
12+
it 'should set the path to the given repository path' do
13+
expect(subject).to have_attributes(path: repository_path)
14+
end
15+
end
16+
end
17+
end
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)