Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions lib/ruby_git/repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,50 @@ class Repository
# RubyGit::Repository.new('/path/to/repository') #=> #<RubyGit::Repository ...>
#
# @param [String] repository_path the path to the repository
# @param normalize_path [Boolean] if true, path is converted to an absolute path to the root of the working tree
#
def initialize(repository_path)
@path = File.realpath(repository_path)
# The purpose of this flag is to allow tests to not have to mock the
# normalization of the path. This allows testing that the right git command
# is contructed based on the options passed any particular method.
#
# @raise [ArgumentError] if the path is not a directory
#
def initialize(repository_path, normalize_path: true)
@normalize_path = normalize_path

@path =
if normalize_path?
normalize_path(repository_path)
else
repository_path
end
end

private

# true if the path should be expanded and converted to a absolute, real path
# @return [Boolean]
# @api private
def normalize_path? = @normalize_path

# Expand and convert the given path to an absolute, real path
#
# @example Expand the path
# normalize_path('~/repository.git') #=> '/Users/james/repository.git'
#
# @example Convert to an absolute path
# File.chdir('/User/james/repository/.git')
# normalize_path('.') #=> '/User/james/repository/.git'
#
# @param path [String] the path to normalize
#
# @return [String]
#
# @api private
def normalize_path(path)
raise ArgumentError, "Directory '#{path}' does not exist." unless File.directory?(path)

File.realpath(File.expand_path(path))
end
end
end
109 changes: 82 additions & 27 deletions lib/ruby_git/worktree.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ class Worktree
#
# @return [RubyGit::Worktree] the working tree whose root is at `path`
#
def self.init(worktree_path)
def self.init(worktree_path, normalize_path: true)
raise RubyGit::Error, "Path '#{worktree_path}' not valid." unless File.directory?(worktree_path)

command = ['init']
options = { chdir: worktree_path, out: StringIO.new, err: StringIO.new }
RubyGit::CommandLine.run(*command, **options)

new(worktree_path)
new(worktree_path, normalize_path:)
end

# Open an existing Git working tree that contains worktree_path
Expand All @@ -58,8 +58,8 @@ def self.init(worktree_path)
#
# @return [RubyGit::Worktree] the Git working tree that contains `worktree_path`
#
def self.open(worktree_path)
new(worktree_path)
def self.open(worktree_path, normalize_path: true)
new(worktree_path, normalize_path:)
end

# Copy the remote repository and checkout the default branch
Expand Down Expand Up @@ -97,23 +97,12 @@ def self.open(worktree_path)
#
# @return [RubyGit::Worktree] the Git working tree checked out from the cloned repository
#
def self.clone(repository_url, to_path: nil)
def self.clone(repository_url, to_path: nil, normalize_path: true)
command = ['clone', '--', repository_url]
command << to_path if to_path
options = { out: StringIO.new, err: StringIO.new }
clone_output = RubyGit::CommandLine.run(*command, **options).stderr
new(cloned_to(clone_output))
end

# Get path of the cloned worktree from `git clone` stderr output
#
# @param clone_output [String] the stderr output of the `git clone` command
#
# @return [String] the path of the cloned worktree
#
# @api private
def self.cloned_to(clone_output)
clone_output.match(/Cloning into ['"](.+)['"]\.\.\./)[1]
new(cloned_to(clone_output), normalize_path:)
end

# Show the working tree and index status
Expand Down Expand Up @@ -154,7 +143,7 @@ def status(*path_specs, untracked_files: :all, ignored: :no, ignore_submodules:
command << '--' unless path_specs.empty?
command.concat(path_specs)
options = { out: StringIO.new, err: StringIO.new }
status_output = run(*command, **options).stdout
status_output = run_with_context(*command, **options).stdout
RubyGit::Status.parse(status_output)
end

Expand Down Expand Up @@ -194,7 +183,7 @@ def add(*pathspecs, all: false, force: false, refresh: false, update: false) # r

options = { out: StringIO.new, err: StringIO.new }

run(*command, **options)
run_with_context(*command, **options)
end

# Return the repository associated with the worktree
Expand All @@ -211,7 +200,7 @@ def repository
options = { chdir: path, chomp: true, out: StringIO.new, err: StringIO.new }
# rev-parse path might be relative to the worktree, thus the need to expand it
git_dir = File.realpath(RubyGit::CommandLine.run(*command, **options).stdout, path)
Repository.new(git_dir)
Repository.new(git_dir, normalize_path: normalize_path?)
end
end

Expand All @@ -220,28 +209,94 @@ def repository
# Create a Worktree object
#
# @param worktree_path [String] a path anywhere in the worktree
# @param normalize_path [Boolean] if true, path is converted to an absolute path to the root of the working tree
#
# The purpose of this flag is to allow tests to not have to mock the
# normalization of the path. This allows testing that the right git command
# is contructed based on the options passed any particular method.
#
# @raise [ArgumentError] if the path is not a directory or the path is not in a
# git working tree
#
# @return [RubyGit::Worktree] the worktree whose root is at `path`
# @api private
#
def initialize(worktree_path)
raise RubyGit::Error, "Path '#{worktree_path}' not valid." unless File.directory?(worktree_path)
def initialize(worktree_path, normalize_path: true)
@normalize_path = normalize_path

@path =
if normalize_path?
normalize_worktree_path(worktree_path)
else
worktree_path
end

@path = root_path(worktree_path)
RubyGit.logger.debug("Created #{inspect}")
end

# Find the root path of a Git working tree containing `path`
# Get path of the cloned worktree from `git clone` stderr output
#
# @param clone_output [String] the stderr output of the `git clone` command
#
# @return [String] the path of the cloned worktree
#
# @api private
private_class_method def self.cloned_to(clone_output)
clone_output.match(/Cloning into ['"](.+)['"]\.\.\./)[1]
end

# True if the path should be normalized
#
# This means that the path should be expanded and converted to a absolute, real
# path to the working tree root dir.
#
# @return [Boolean]
#
# @api private
#
def normalize_path? = @normalize_path

# Return the absolute path to the root of the working tree containing path
#
# @example Expand the path
# normalize_path('~/worktree') #=> '/Users/james/worktree'
#
# @example Convert to an absolute path
# File.chdir('/User/james/worktree')
# normalize_path('.') #=> '/User/james/worktree'
#
# @raise [RubyGit::FailedError] if the path is not in a Git working tree
# @param path [String] a (possibly relative) path within the worktree
#
# @return [String]
#
# @raise [ArgumentError] if the path is not a directory or the path is not in a
# git working tree
#
# @api private
#
def normalize_worktree_path(path)
raise ArgumentError, "Directory '#{path}' does not exist." unless File.directory?(path)

begin
root_path(path)
rescue RubyGit::FailedError => e
raise ArgumentError, e.message
end
end

# Find the root path of a Git working tree containing `path`
#
# @return [String] the root path of the Git working tree containing `path`
#
# @raise [ArgumentError] if the path is not in a Git working tree
#
# @api private
#
def root_path(worktree_path)
command = %w[rev-parse --show-toplevel]
options = { chdir: worktree_path, chomp: true, out: StringIO.new, err: StringIO.new }
File.realpath(RubyGit::CommandLine.run(*command, **options).stdout)
root_path = RubyGit::CommandLine.run(*command, **options).stdout
File.realpath(File.expand_path(root_path))
end

# Run a Git command in this worktree
Expand All @@ -255,7 +310,7 @@ def root_path(worktree_path)
#
# @api private
#
def run(*command, **options)
def run_with_context(*command, **options)
RubyGit::CommandLine.run(*command, repository_path: repository.path, worktree_path: path, **options)
end

Expand Down
28 changes: 18 additions & 10 deletions spec/lib/ruby_git/repository_spec.rb
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
# frozen_string_literal: true

RSpec.describe RubyGit::Repository do
let(:repository) { RubyGit::Repository.new(repository_path) }
let(:repository_path) { File.realpath(@repository_path) }
let(:repository) { RubyGit::Repository.new(repository_path, normalize_path:) }
let(:repository_path) { '.' }

describe '#initialize' do
context 'when given a repository path' do
around do |example|
in_temp_dir do |repository_path|
@repository_path = repository_path
example.run
end
subject { repository }

around do |example|
in_temp_dir do |_repository_path|
example.run
end
end

subject { repository }
context 'when normalize_path is true' do
let(:normalize_path) { true }
it 'path should be the absolute path to the repository' do
expected_path = File.realpath(File.expand_path(repository_path))
expect(subject).to have_attributes(path: expected_path)
end
end

it 'should set the path to the given repository path' do
context 'when normalize_path is false' do
let(:normalize_path) { false }
it 'path should be as given' do
expect(subject).to have_attributes(path: repository_path)
end
end
Expand Down
Loading