Skip to content

Commit d37b4ad

Browse files
authored
Add init, clone, and open methods to Worktree (#17)
1 parent db85c0b commit d37b4ad

10 files changed

+691
-230
lines changed

.rubocop.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ AllCops:
1212
Layout/LineLength:
1313
Max: 120
1414

15-
# The DSL for RSpec and the gemspec file make it very hard to limit block length:
15+
# The DSL for RSpec makes it very hard to limit block length:
1616
Metrics/BlockLength:
1717
Exclude:
1818
- "spec/**/*_spec.rb"

lib/ruby_git.rb

+78-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# frozen_string_literal: true
22

3-
require 'ruby_git/version'
3+
require 'ruby_git/error'
44
require 'ruby_git/file_helpers'
55
require 'ruby_git/git_binary'
6+
require 'ruby_git/version'
7+
require 'ruby_git/worktree'
68

79
# RubyGit is an object-oriented wrapper for the `git` command line tool for
810
# working with Worktrees and Repositories. It tries to make more sense out
@@ -24,4 +26,79 @@ module RubyGit
2426
def self.git
2527
(@git ||= RubyGit::GitBinary.new)
2628
end
29+
30+
# Create an empty Git repository under the root worktree `path`
31+
#
32+
# If the repository already exists, it will not be overwritten.
33+
#
34+
# @see https://git-scm.com/docs/git-init git-init
35+
#
36+
# @example
37+
# worktree = Worktree.init(worktree_path)
38+
#
39+
# @param [String] worktree_path the root path of a worktree
40+
#
41+
# @raise [RubyGit::Error] if worktree_path is not a directory
42+
#
43+
# @return [RubyGit::Worktree] the worktree whose root is at `path`
44+
#
45+
def self.init(worktree_path)
46+
RubyGit::Worktree.init(worktree_path)
47+
end
48+
49+
# Open an existing Git worktree that contains worktree_path
50+
#
51+
# @see https://git-scm.com/docs/git-open git-open
52+
#
53+
# @example
54+
# worktree = Worktree.open(worktree_path)
55+
#
56+
# @param [String] worktree_path the root path of a worktree
57+
#
58+
# @raise [RubyGit::Error] if `worktree_path` does not exist, is not a directory, or is not within a Git worktree.
59+
#
60+
# @return [RubyGit::Worktree] the worktree that contains `worktree_path`
61+
#
62+
def self.open(worktree_path)
63+
RubyGit::Worktree.open(worktree_path)
64+
end
65+
66+
# Copy the remote repository and checkout the default branch
67+
#
68+
# Clones the repository referred to by `repository_url` into a newly created
69+
# directory, creates remote-tracking branches for each branch in the cloned repository,
70+
# and checks out the default branch in the worktree whose root directory is `to_path`.
71+
#
72+
# @see https://git-scm.com/docs/git-clone git-clone
73+
#
74+
# @example Using default for Worktree path
75+
# FileUtils.pwd
76+
# => "/Users/jsmith"
77+
# worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git')
78+
# worktree.path
79+
# => "/Users/jsmith/ruby_git"
80+
#
81+
# @example Using a specified worktree_path
82+
# FileUtils.pwd
83+
# => "/Users/jsmith"
84+
# worktree_path = '/tmp/project'
85+
# worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git', to_path: worktree_path)
86+
# worktree.path
87+
# => "/tmp/project"
88+
#
89+
# @param [String] repository_url a reference to a Git repository
90+
#
91+
# @param [String] to_path where to put the checked out worktree once the repository is cloned
92+
#
93+
# `to_path` will be created if it does not exist. An error is raised if `to_path` exists and
94+
# not an empty directory.
95+
#
96+
# @raise [RubyGit::Error] if (1) `repository_url` is not valid or does not point to a valid repository OR
97+
# (2) `to_path` is not an empty directory.
98+
#
99+
# @return [RubyGit::Worktree] the worktree checked out from the cloned repository
100+
#
101+
def self.clone(repository_url, to_path: '')
102+
RubyGit::Worktree.clone(repository_url, to_path: to_path)
103+
end
27104
end

lib/ruby_git/error.rb

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
module RubyGit
4+
# Errors specific to RubyGit raise RubyGit::Error
5+
#
6+
class Error < StandardError
7+
end
8+
end

lib/ruby_git/git_binary.rb

+18-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ class GitBinary
1111
# @example
1212
# GitBinary.new
1313
#
14-
def initialize
15-
@path = nil
14+
def initialize(path = nil)
15+
@path = Pathname.new(path) unless path.nil?
1616
end
1717

1818
# Sets the path to the git binary
@@ -86,5 +86,21 @@ def version
8686
version = output[/\d+\.\d+(\.\d+)+/]
8787
version.split('.').collect(&:to_i)
8888
end
89+
90+
# Return the path as a string
91+
#
92+
# @example
93+
# git = RubyGit::GitBinary.new('/usr/bin/git')
94+
# git.to_s
95+
# => '/usr/bin/git'
96+
#
97+
# @return [String] the path to the binary
98+
#
99+
# @raise [RuntimeError] if path was not set via `path=` and either PATH is not set
100+
# or git was not found on the path.
101+
#
102+
def to_s
103+
path.to_s
104+
end
89105
end
90106
end

lib/ruby_git/worktree.rb

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# frozen_string_literal: true
2+
3+
require 'open3'
4+
5+
module RubyGit
6+
# The Worktree is a directory tree consisting of the checked out files that
7+
# you are currently working on.
8+
#
9+
# Create a new Worktree using {.init}, {.clone}, or {.open}.
10+
#
11+
class Worktree
12+
# The root path of the worktree
13+
#
14+
# @example
15+
# worktree_path = '/Users/James/myproject'
16+
# worktree = Worktree.open(worktree_path)
17+
# worktree.path
18+
# => '/Users/James/myproject'
19+
#
20+
# @return [Pathname] the root path of the worktree
21+
#
22+
attr_reader :path
23+
24+
# Create an empty Git repository under the root worktree `path`
25+
#
26+
# If the repository already exists, it will not be overwritten.
27+
#
28+
# @see https://git-scm.com/docs/git-init git-init
29+
#
30+
# @example
31+
# worktree = Worktree.init(worktree_path)
32+
#
33+
# @param [String] worktree_path the root path of a worktree
34+
#
35+
# @raise [RubyGit::Error] if worktree_path is not a directory
36+
#
37+
# @return [RubyGit::Worktree] the worktree whose root is at `path`
38+
#
39+
def self.init(worktree_path)
40+
raise RubyGit::Error, "Path '#{worktree_path}' not valid." unless File.directory?(worktree_path)
41+
42+
command = [RubyGit.git.path.to_s, 'init']
43+
_out, err, status = Open3.capture3(*command, chdir: worktree_path)
44+
raise RubyGit::Error, err unless status.success?
45+
46+
Worktree.new(worktree_path)
47+
end
48+
49+
# Open an existing Git worktree that contains worktree_path
50+
#
51+
# @see https://git-scm.com/docs/git-open git-open
52+
#
53+
# @example
54+
# worktree = Worktree.open(worktree_path)
55+
#
56+
# @param [String] worktree_path the root path of a worktree
57+
#
58+
# @raise [RubyGit::Error] if `worktree_path` does not exist, is not a directory, or is not within a Git worktree.
59+
#
60+
# @return [RubyGit::Worktree] the worktree that contains `worktree_path`
61+
#
62+
def self.open(worktree_path)
63+
new(worktree_path)
64+
end
65+
66+
# Copy the remote repository and checkout the default branch
67+
#
68+
# Clones the repository referred to by `repository_url` into a newly created
69+
# directory, creates remote-tracking branches for each branch in the cloned repository,
70+
# and checks out the default branch in the worktree whose root directory is `to_path`.
71+
#
72+
# @see https://git-scm.com/docs/git-clone git-clone
73+
#
74+
# @example Using default for Worktree path
75+
# FileUtils.pwd
76+
# => "/Users/jsmith"
77+
# worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git')
78+
# worktree.path
79+
# => "/Users/jsmith/ruby_git"
80+
#
81+
# @example Using a specified worktree_path
82+
# FileUtils.pwd
83+
# => "/Users/jsmith"
84+
# worktree_path = '/tmp/project'
85+
# worktree = Worktree.clone('https://github.com/main-branch/ruby_git.git', to_path: worktree_path)
86+
# worktree.path
87+
# => "/tmp/project"
88+
#
89+
# @param [String] repository_url a reference to a Git repository
90+
#
91+
# @param [String] to_path where to put the checked out worktree once the repository is cloned
92+
#
93+
# `to_path` will be created if it does not exist. An error is raised if `to_path` exists and
94+
# not an empty directory.
95+
#
96+
# @raise [RubyGit::Error] if (1) `repository_url` is not valid or does not point to a valid repository OR
97+
# (2) `to_path` is not an empty directory.
98+
#
99+
# @return [RubyGit::Worktree] the worktree checked out from the cloned repository
100+
#
101+
def self.clone(repository_url, to_path: '')
102+
command = [RubyGit.git.path.to_s, 'clone', '--', repository_url, to_path]
103+
_out, err, status = Open3.capture3(*command)
104+
raise RubyGit::Error, err unless status.success?
105+
106+
new(to_path)
107+
end
108+
109+
private
110+
111+
# Create a Worktree object
112+
# @api private
113+
def initialize(worktree_path)
114+
raise RubyGit::Error, "Path '#{worktree_path}' not valid." unless File.directory?(worktree_path)
115+
116+
@path = root_path(worktree_path)
117+
end
118+
119+
# Find the root path of a worktree containing `path`
120+
#
121+
# @raise [RubyGit::Error] if the path is not in a worktree
122+
#
123+
# @return [String] the root path of the worktree containing `path`
124+
#
125+
# @api private
126+
#
127+
def root_path(worktree_path)
128+
command = [RubyGit.git.path.to_s, 'rev-parse', '--show-toplevel']
129+
out, err, status = Open3.capture3(*command, chdir: worktree_path)
130+
raise RubyGit::Error, err unless status.success?
131+
132+
out.chomp
133+
end
134+
end
135+
end

0 commit comments

Comments
 (0)