git 5.0.0.beta.1 → 5.0.0.beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +6 -0
- data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
- data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +28 -14
- data/.github/skills/facade-implementation/SKILL.md +14 -0
- data/.github/skills/facade-test-conventions/SKILL.md +14 -0
- data/.rubocop.yml +5 -0
- data/README.md +51 -11
- data/UPGRADING.md +141 -0
- data/git.gemspec +5 -0
- data/lib/git/branch.rb +7 -18
- data/lib/git/branches.rb +2 -10
- data/lib/git/command_line/base.rb +10 -0
- data/lib/git/command_line/capturing.rb +5 -3
- data/lib/git/command_line/streaming.rb +5 -3
- data/lib/git/command_line.rb +3 -3
- data/lib/git/commands/base.rb +7 -6
- data/lib/git/commands/cat_file/batch.rb +6 -1
- data/lib/git/commands/cat_file/raw.rb +7 -1
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +5 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
- data/lib/git/commands/update_ref/batch.rb +1 -1
- data/lib/git/commands/version.rb +5 -0
- data/lib/git/commands.rb +5 -7
- data/lib/git/config.rb +17 -0
- data/lib/git/config_entry_info.rb +106 -0
- data/lib/git/configuring.rb +665 -0
- data/lib/git/deprecation.rb +9 -0
- data/lib/git/diff.rb +4 -8
- data/lib/git/diff_path_status.rb +2 -13
- data/lib/git/diff_stats.rb +1 -9
- data/lib/git/execution_context/global.rb +3 -28
- data/lib/git/execution_context/repository.rb +30 -41
- data/lib/git/execution_context.rb +43 -24
- data/lib/git/log.rb +3 -9
- data/lib/git/object.rb +14 -21
- data/lib/git/parsers/config_entry.rb +110 -0
- data/lib/git/parsers/ls_remote.rb +79 -0
- data/lib/git/remote.rb +7 -20
- data/lib/git/repository/branching.rb +183 -12
- data/lib/git/repository/committing.rb +64 -68
- data/lib/git/repository/configuring.rb +208 -13
- data/lib/git/repository/context_helpers.rb +264 -0
- data/lib/git/repository/factories.rb +682 -0
- data/lib/git/repository/inspecting.rb +99 -0
- data/lib/git/repository/maintenance.rb +65 -0
- data/lib/git/repository/merging.rb +63 -1
- data/lib/git/repository/object_operations.rb +133 -35
- data/lib/git/repository/path_resolver.rb +1 -1
- data/lib/git/repository/remote_operations.rb +166 -21
- data/lib/git/repository/staging.rb +187 -23
- data/lib/git/repository/stashing.rb +39 -3
- data/lib/git/repository/status_operations.rb +21 -0
- data/lib/git/repository.rb +68 -129
- data/lib/git/stash.rb +2 -9
- data/lib/git/stashes.rb +2 -7
- data/lib/git/status.rb +8 -17
- data/lib/git/version.rb +2 -2
- data/lib/git/worktree.rb +2 -15
- data/lib/git/worktrees.rb +2 -15
- data/lib/git.rb +180 -77
- data/redesign/3_architecture_implementation.md +148 -111
- data/redesign/Phase 4 - Step A.md +360 -0
- data/redesign/beta_release.md +107 -0
- data/redesign/c1c2_audit.md +566 -0
- data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
- data/redesign/config_design.rb +501 -0
- metadata +19 -5
- data/lib/git/base.rb +0 -1204
- data/lib/git/lib.rb +0 -2855
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/commands/clone'
|
|
4
|
+
require 'git/commands/init'
|
|
5
|
+
require 'git/execution_context/global'
|
|
6
|
+
require 'git/execution_context/repository'
|
|
7
|
+
require 'git/repository/path_resolver'
|
|
8
|
+
require 'pathname'
|
|
9
|
+
|
|
10
|
+
module Git
|
|
11
|
+
class Repository
|
|
12
|
+
# Factory class methods for constructing {Git::Repository} instances
|
|
13
|
+
#
|
|
14
|
+
# The four public factories — {clone}, {init}, {open}, {bare} — mirror the
|
|
15
|
+
# top-level `Git.*` entry points and return a {Git::Repository}.
|
|
16
|
+
#
|
|
17
|
+
# Extended by {Git::Repository}.
|
|
18
|
+
#
|
|
19
|
+
# @api public
|
|
20
|
+
#
|
|
21
|
+
module Factories # rubocop:disable Metrics/ModuleLength
|
|
22
|
+
# Clone a repository into a new directory
|
|
23
|
+
#
|
|
24
|
+
# @example Clone into the default directory
|
|
25
|
+
# repository = Git::Repository.clone('https://github.com/ruby-git/ruby-git.git')
|
|
26
|
+
#
|
|
27
|
+
# @example Clone into a specific directory
|
|
28
|
+
# repo_url = 'https://github.com/ruby-git/ruby-git.git'
|
|
29
|
+
# repository = Git::Repository.clone(repo_url, 'local')
|
|
30
|
+
#
|
|
31
|
+
# @example Clone a bare repository
|
|
32
|
+
# repo_url = 'https://github.com/ruby-git/ruby-git.git'
|
|
33
|
+
# repository = Git::Repository.clone(repo_url, nil, bare: true)
|
|
34
|
+
#
|
|
35
|
+
# @param repository_url [String] the URL or path of the repository to clone
|
|
36
|
+
#
|
|
37
|
+
# @param directory [String, nil] the local directory name to clone into;
|
|
38
|
+
# git derives the name from the URL when `nil`
|
|
39
|
+
#
|
|
40
|
+
# @param options [Hash] options that control cloning
|
|
41
|
+
#
|
|
42
|
+
# Some options configure the returned {Git::Repository} instance after
|
|
43
|
+
# the clone completes. Supported `git clone` options are forwarded.
|
|
44
|
+
#
|
|
45
|
+
# @option options [String, nil] :template template directory to use
|
|
46
|
+
#
|
|
47
|
+
# @option options [Boolean, nil] :local use the local clone optimization
|
|
48
|
+
#
|
|
49
|
+
# @option options [Boolean, nil] :no_local disable the local clone optimization
|
|
50
|
+
#
|
|
51
|
+
# @option options [Boolean, nil] :shared set up a shared clone
|
|
52
|
+
#
|
|
53
|
+
# @option options [Boolean, nil] :no_hardlinks copy files instead of hardlinks
|
|
54
|
+
#
|
|
55
|
+
# @option options [Boolean, nil] :quiet suppress progress output
|
|
56
|
+
#
|
|
57
|
+
# @option options [Boolean, nil] :verbose run verbosely
|
|
58
|
+
#
|
|
59
|
+
# @option options [Boolean, nil] :progress force progress output
|
|
60
|
+
#
|
|
61
|
+
# @option options [Boolean, nil] :no_checkout skip checking out `HEAD`
|
|
62
|
+
#
|
|
63
|
+
# @option options [Boolean, nil] :bare clone as a bare repository
|
|
64
|
+
#
|
|
65
|
+
# @option options [Boolean, nil] :mirror set up a mirror of the source
|
|
66
|
+
# (implies `:bare`)
|
|
67
|
+
#
|
|
68
|
+
# @option options [String, nil] :origin remote name to use instead of `origin`
|
|
69
|
+
#
|
|
70
|
+
# @option options [String, nil] :branch the branch or tag to check out after cloning
|
|
71
|
+
#
|
|
72
|
+
# @option options [String, nil] :revision revision to check out after cloning
|
|
73
|
+
#
|
|
74
|
+
# @option options [String, nil] :upload_pack remote `git-upload-pack` path
|
|
75
|
+
#
|
|
76
|
+
# @option options [String, Array<String>, nil] :reference reference repository
|
|
77
|
+
#
|
|
78
|
+
# @option options [String, Array<String>, nil] :reference_if_able
|
|
79
|
+
# optional reference repository
|
|
80
|
+
#
|
|
81
|
+
# @option options [Boolean, nil] :dissociate stop borrowing from references
|
|
82
|
+
#
|
|
83
|
+
# @option options [String, nil] :separate_git_dir alternate git directory path
|
|
84
|
+
#
|
|
85
|
+
# @option options [String, Array<String>, nil] :server_option
|
|
86
|
+
# protocol-v2 server options
|
|
87
|
+
#
|
|
88
|
+
# @option options [Integer, String, nil] :depth create a shallow clone
|
|
89
|
+
#
|
|
90
|
+
# @option options [String, nil] :shallow_since create a shallow clone by date
|
|
91
|
+
#
|
|
92
|
+
# @option options [String, Array<String>, nil] :shallow_exclude
|
|
93
|
+
# exclude commits reachable from a ref
|
|
94
|
+
#
|
|
95
|
+
# @option options [Boolean, nil] :single_branch clone one branch's history
|
|
96
|
+
#
|
|
97
|
+
# @option options [Boolean, nil] :no_single_branch clone all branch history
|
|
98
|
+
#
|
|
99
|
+
# @option options [Boolean, nil] :tags include tags in the clone
|
|
100
|
+
#
|
|
101
|
+
# @option options [Boolean, nil] :no_tags exclude tags from the clone
|
|
102
|
+
#
|
|
103
|
+
# @option options [Boolean, String, Array<String>, nil] :recurse_submodules
|
|
104
|
+
# initialize submodules after cloning
|
|
105
|
+
#
|
|
106
|
+
# Pass `true` to initialize all submodules, or pass a pathspec string or
|
|
107
|
+
# array for a subset.
|
|
108
|
+
#
|
|
109
|
+
# @option options [Boolean, nil] :shallow_submodules use depth 1 for submodules
|
|
110
|
+
#
|
|
111
|
+
# @option options [Boolean, nil] :no_shallow_submodules use full submodule history
|
|
112
|
+
#
|
|
113
|
+
# @option options [Boolean, nil] :remote_submodules use submodule remote branches
|
|
114
|
+
#
|
|
115
|
+
# @option options [Boolean, nil] :no_remote_submodules use recorded submodule SHAs
|
|
116
|
+
#
|
|
117
|
+
# @option options [Integer, String, nil] :jobs submodule jobs to run concurrently
|
|
118
|
+
#
|
|
119
|
+
# @option options [Boolean, nil] :sparse enable sparse checkout
|
|
120
|
+
#
|
|
121
|
+
# @option options [Boolean, nil] :reject_shallow reject shallow source repositories
|
|
122
|
+
#
|
|
123
|
+
# @option options [Boolean, nil] :no_reject_shallow allow shallow sources
|
|
124
|
+
#
|
|
125
|
+
# @option options [String, nil] :filter specify a partial clone filter
|
|
126
|
+
#
|
|
127
|
+
# @option options [Boolean, nil] :also_filter_submodules filter submodules too
|
|
128
|
+
#
|
|
129
|
+
# @option options [String, Array<String>, nil] :config repository config entries
|
|
130
|
+
#
|
|
131
|
+
# @option options [String, nil] :bundle_uri bundle URI to prefetch
|
|
132
|
+
#
|
|
133
|
+
# @option options [String, nil] :ref_format ref storage format
|
|
134
|
+
#
|
|
135
|
+
# @option options [Numeric, nil] :timeout command timeout in seconds
|
|
136
|
+
#
|
|
137
|
+
# @option options [String, nil] :repository alternate git directory path
|
|
138
|
+
#
|
|
139
|
+
# Preferred facade spelling for `git clone --separate-git-dir`.
|
|
140
|
+
#
|
|
141
|
+
# @option options [String, nil, :use_global_config] :git_ssh path to a custom
|
|
142
|
+
# SSH executable
|
|
143
|
+
#
|
|
144
|
+
# Pass `:use_global_config` (the default) to use
|
|
145
|
+
# `Git.config.git_ssh`.
|
|
146
|
+
#
|
|
147
|
+
# @option options [String, :use_global_config] :binary_path path to the git
|
|
148
|
+
# binary
|
|
149
|
+
#
|
|
150
|
+
# Pass `:use_global_config` (the default) to use
|
|
151
|
+
# `Git.config.binary_path`.
|
|
152
|
+
#
|
|
153
|
+
# @option options [Logger, nil] :log logger used for git operations
|
|
154
|
+
#
|
|
155
|
+
# @option options [String, nil] :index a non-standard path to the index file
|
|
156
|
+
#
|
|
157
|
+
# @option options [String, Pathname, nil] :chdir run `git clone` from within
|
|
158
|
+
# this directory
|
|
159
|
+
#
|
|
160
|
+
# @option options [String, Pathname, nil] :path deprecated; use `:chdir` instead
|
|
161
|
+
#
|
|
162
|
+
# @option options [Boolean, nil] :recursive deprecated; use
|
|
163
|
+
# `:recurse_submodules` instead
|
|
164
|
+
#
|
|
165
|
+
# @option options [String, nil] :remote deprecated; use `:origin` instead
|
|
166
|
+
#
|
|
167
|
+
# @return [Git::Repository] a repository bound to the cloned working copy or
|
|
168
|
+
# bare repository
|
|
169
|
+
#
|
|
170
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
171
|
+
#
|
|
172
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
173
|
+
#
|
|
174
|
+
# @raise [Git::UnexpectedResultError] if the cloned directory cannot be
|
|
175
|
+
# determined from git's output
|
|
176
|
+
#
|
|
177
|
+
# @api public
|
|
178
|
+
#
|
|
179
|
+
def clone(repository_url, directory = nil, options = {})
|
|
180
|
+
opts, context_opts = prepare_clone_options(options)
|
|
181
|
+
clone_result = run_clone_command(repository_url, directory, opts, context_opts)
|
|
182
|
+
paths = resolve_paths_from_clone_result(clone_result, opts, context_opts)
|
|
183
|
+
|
|
184
|
+
from_paths(clone_repository_options(context_opts), paths)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Create an empty Git repository or reinitialize an existing one
|
|
188
|
+
#
|
|
189
|
+
# @example Initialize in the current directory
|
|
190
|
+
# repository = Git::Repository.init
|
|
191
|
+
#
|
|
192
|
+
# @example Initialize in a specific directory
|
|
193
|
+
# repository = Git::Repository.init('/path/to/project')
|
|
194
|
+
#
|
|
195
|
+
# @example Initialize a bare repository
|
|
196
|
+
# repository = Git::Repository.init('/path/to/project.git', bare: true)
|
|
197
|
+
#
|
|
198
|
+
# @param directory [String] the directory to initialize; defaults to `'.'`
|
|
199
|
+
#
|
|
200
|
+
# @param options [Hash] options that control initialization
|
|
201
|
+
#
|
|
202
|
+
# Some options configure the returned {Git::Repository} instance after
|
|
203
|
+
# the repository is initialized.
|
|
204
|
+
#
|
|
205
|
+
# @option options [Boolean, nil] :bare create a bare repository at `directory`
|
|
206
|
+
#
|
|
207
|
+
# @option options [String, nil] :initial_branch the name for the initial branch
|
|
208
|
+
#
|
|
209
|
+
# @option options [String, nil] :repository path for the `.git` directory
|
|
210
|
+
#
|
|
211
|
+
# Writes a gitfile in the working tree. Alias: `:separate_git_dir`.
|
|
212
|
+
#
|
|
213
|
+
# @option options [String, nil] :separate_git_dir alias for `:repository`
|
|
214
|
+
#
|
|
215
|
+
# @option options [String, nil, :use_global_config] :git_ssh path to a custom
|
|
216
|
+
# SSH executable
|
|
217
|
+
#
|
|
218
|
+
# Pass `:use_global_config` (the default) to use
|
|
219
|
+
# `Git.config.git_ssh`.
|
|
220
|
+
#
|
|
221
|
+
# @option options [String, :use_global_config] :binary_path path to the git
|
|
222
|
+
# binary
|
|
223
|
+
#
|
|
224
|
+
# Pass `:use_global_config` (the default) to use
|
|
225
|
+
# `Git.config.binary_path`.
|
|
226
|
+
#
|
|
227
|
+
# @option options [Logger, nil] :log logger used for git operations
|
|
228
|
+
#
|
|
229
|
+
# @option options [String, nil] :index custom index path for the returned
|
|
230
|
+
# repository
|
|
231
|
+
#
|
|
232
|
+
# Ignored when `:bare` is `true`.
|
|
233
|
+
#
|
|
234
|
+
# @return [Git::Repository] a repository bound to the newly initialized repository
|
|
235
|
+
#
|
|
236
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
237
|
+
#
|
|
238
|
+
# @api public
|
|
239
|
+
#
|
|
240
|
+
def init(directory = '.', options = {})
|
|
241
|
+
options = options.dup
|
|
242
|
+
if options.key?(:separate_git_dir) && options[:repository].nil?
|
|
243
|
+
options[:repository] = options.delete(:separate_git_dir)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
run_init_command(directory, options)
|
|
247
|
+
open_after_init(directory, options)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Open a working copy at an existing path
|
|
251
|
+
#
|
|
252
|
+
# Note: this method opens working copies only. To open a bare repository, use
|
|
253
|
+
# `Git::Repository.bare`.
|
|
254
|
+
#
|
|
255
|
+
# @example Open the working copy in the current directory
|
|
256
|
+
# repository = Git::Repository.open('.')
|
|
257
|
+
#
|
|
258
|
+
# @param working_dir [String] the path to the root of the working copy
|
|
259
|
+
#
|
|
260
|
+
# May be any path inside the working tree when `:repository` is not given.
|
|
261
|
+
#
|
|
262
|
+
# @param options [Hash] options that control how the repository is located
|
|
263
|
+
#
|
|
264
|
+
# @option options [String, nil] :repository a non-standard path to the
|
|
265
|
+
# `.git` directory
|
|
266
|
+
#
|
|
267
|
+
# When given, `working_dir` is used as-is (the working tree root is not
|
|
268
|
+
# auto-detected).
|
|
269
|
+
#
|
|
270
|
+
# @option options [String, nil] :index a non-standard path to the index file
|
|
271
|
+
#
|
|
272
|
+
# @option options [Logger, nil] :log logger used for git operations
|
|
273
|
+
#
|
|
274
|
+
# @option options [String, nil, :use_global_config] :git_ssh
|
|
275
|
+
# path to a custom SSH executable
|
|
276
|
+
#
|
|
277
|
+
# Pass `:use_global_config` (the default) to use
|
|
278
|
+
# `Git.config.git_ssh`.
|
|
279
|
+
#
|
|
280
|
+
# @option options [String, :use_global_config] :binary_path
|
|
281
|
+
# path to the git binary
|
|
282
|
+
#
|
|
283
|
+
# Pass `:use_global_config` (the default) to use
|
|
284
|
+
# `Git.config.binary_path`.
|
|
285
|
+
#
|
|
286
|
+
# @return [Git::Repository] a repository bound to the resolved paths
|
|
287
|
+
#
|
|
288
|
+
# @raise [ArgumentError] if `working_dir` is not a directory or is not inside
|
|
289
|
+
# a git working tree
|
|
290
|
+
#
|
|
291
|
+
# @api public
|
|
292
|
+
#
|
|
293
|
+
def open(working_dir, options = {})
|
|
294
|
+
raise ArgumentError, "'#{working_dir}' is not a directory" unless Dir.exist?(working_dir)
|
|
295
|
+
|
|
296
|
+
working_dir = resolve_open_working_dir(working_dir, options) unless options[:repository]
|
|
297
|
+
|
|
298
|
+
paths = PathResolver.resolve_paths(
|
|
299
|
+
working_directory: working_dir,
|
|
300
|
+
repository: options[:repository],
|
|
301
|
+
index: options[:index]
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
from_paths(options, paths)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Open an existing bare repository at `git_dir`
|
|
308
|
+
#
|
|
309
|
+
# @example Open a bare repository
|
|
310
|
+
# repository = Git::Repository.bare('/path/to/repo.git')
|
|
311
|
+
#
|
|
312
|
+
# @param git_dir [String] the path to the bare repository directory
|
|
313
|
+
#
|
|
314
|
+
# @param options [Hash] options used to configure the repository instance
|
|
315
|
+
#
|
|
316
|
+
# @option options [Logger, nil] :log logger used for git operations
|
|
317
|
+
#
|
|
318
|
+
# @option options [String, nil, :use_global_config] :git_ssh
|
|
319
|
+
# path to a custom SSH executable
|
|
320
|
+
#
|
|
321
|
+
# Pass `:use_global_config` (the default) to use
|
|
322
|
+
# `Git.config.git_ssh`.
|
|
323
|
+
#
|
|
324
|
+
# @option options [String, :use_global_config] :binary_path
|
|
325
|
+
# path to the git binary
|
|
326
|
+
#
|
|
327
|
+
# Pass `:use_global_config` (the default) to use
|
|
328
|
+
# `Git.config.binary_path`.
|
|
329
|
+
#
|
|
330
|
+
# @return [Git::Repository] a repository bound to the bare repository directory
|
|
331
|
+
#
|
|
332
|
+
# @api public
|
|
333
|
+
#
|
|
334
|
+
def bare(git_dir, options = {})
|
|
335
|
+
paths = PathResolver.resolve_paths(repository: git_dir, bare: true)
|
|
336
|
+
|
|
337
|
+
from_paths(options, paths)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
private
|
|
341
|
+
|
|
342
|
+
# Run the `git clone` command using a global execution context
|
|
343
|
+
#
|
|
344
|
+
# @param repository_url [String] the URL or path of the repository to clone
|
|
345
|
+
#
|
|
346
|
+
# @param directory [String, nil] the local directory name to clone into
|
|
347
|
+
#
|
|
348
|
+
# @param opts [Hash] command-ready clone options
|
|
349
|
+
#
|
|
350
|
+
# @param context_opts [Hash] context options produced while preparing clone
|
|
351
|
+
# options
|
|
352
|
+
#
|
|
353
|
+
# @return [Git::CommandLineResult] the result of running `git clone`
|
|
354
|
+
#
|
|
355
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
356
|
+
#
|
|
357
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
358
|
+
#
|
|
359
|
+
# @api private
|
|
360
|
+
#
|
|
361
|
+
def run_clone_command(repository_url, directory, opts, context_opts)
|
|
362
|
+
context = Git::ExecutionContext::Global.new(
|
|
363
|
+
binary_path: context_opts[:binary_path],
|
|
364
|
+
git_ssh: context_opts[:git_ssh],
|
|
365
|
+
logger: context_opts[:logger]
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
Git::Commands::Clone.new(context).call(repository_url, directory, **opts)
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
# Resolve repository paths from a completed clone result
|
|
372
|
+
#
|
|
373
|
+
# @param clone_result [Git::CommandLineResult] the completed clone result
|
|
374
|
+
#
|
|
375
|
+
# @param opts [Hash] command-ready clone options
|
|
376
|
+
#
|
|
377
|
+
# @param context_opts [Hash] context options produced while preparing clone
|
|
378
|
+
# options
|
|
379
|
+
#
|
|
380
|
+
# @return [Hash{Symbol => (String, nil)}] resolved path hash
|
|
381
|
+
#
|
|
382
|
+
# @raise [Git::UnexpectedResultError] if the clone directory cannot be parsed
|
|
383
|
+
#
|
|
384
|
+
# @api private
|
|
385
|
+
#
|
|
386
|
+
def resolve_paths_from_clone_result(clone_result, opts, context_opts)
|
|
387
|
+
clone_dir, cloned_bare = parse_clone_stderr(clone_result.stderr)
|
|
388
|
+
chdir = opts[:chdir]
|
|
389
|
+
clone_dir = File.join(chdir, clone_dir) if chdir && !Pathname.new(clone_dir).absolute?
|
|
390
|
+
|
|
391
|
+
bare = opts[:bare] || opts[:mirror] || cloned_bare
|
|
392
|
+
resolve_clone_paths(clone_dir, bare, context_opts[:index])
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Build repository construction options from clone context options
|
|
396
|
+
#
|
|
397
|
+
# @param context_opts [Hash] context options produced while preparing clone
|
|
398
|
+
# options
|
|
399
|
+
#
|
|
400
|
+
# @return [Hash{Symbol => Object}] repository construction options
|
|
401
|
+
#
|
|
402
|
+
# @api private
|
|
403
|
+
#
|
|
404
|
+
def clone_repository_options(context_opts)
|
|
405
|
+
{
|
|
406
|
+
git_ssh: context_opts[:git_ssh],
|
|
407
|
+
binary_path: context_opts[:binary_path],
|
|
408
|
+
log: context_opts[:logger]
|
|
409
|
+
}
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Build the `:binary_path` and `:git_ssh` execution-context defaults
|
|
413
|
+
#
|
|
414
|
+
# Reads the values from the caller-supplied options, falling back to the
|
|
415
|
+
# `:use_global_config` sentinel for any the caller did not provide so the
|
|
416
|
+
# value is resolved from `Git::Config.instance` at call time.
|
|
417
|
+
#
|
|
418
|
+
# @param options [Hash] the caller-supplied options hash
|
|
419
|
+
#
|
|
420
|
+
# @return [Hash] context defaults with two keys: `:binary_path`
|
|
421
|
+
# (`String` or `:use_global_config` — `nil` is not valid and raises
|
|
422
|
+
# `ArgumentError` in {Git::ExecutionContext#initialize}) and `:git_ssh`
|
|
423
|
+
# (`String`, `nil`, or `:use_global_config`)
|
|
424
|
+
#
|
|
425
|
+
# @api private
|
|
426
|
+
#
|
|
427
|
+
def context_defaults(options)
|
|
428
|
+
{
|
|
429
|
+
binary_path: options.fetch(:binary_path, :use_global_config),
|
|
430
|
+
git_ssh: options.fetch(:git_ssh, :use_global_config)
|
|
431
|
+
}
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# Resolve the worktree root to use as the working directory for {.open}
|
|
435
|
+
#
|
|
436
|
+
# @param working_dir [String] a path inside the working tree
|
|
437
|
+
#
|
|
438
|
+
# @param options [Hash] the caller-supplied options hash from {.open}
|
|
439
|
+
#
|
|
440
|
+
# @return [String] the absolute path to the root of the working tree
|
|
441
|
+
#
|
|
442
|
+
# @raise [ArgumentError] if `working_dir` is not inside a git working tree
|
|
443
|
+
#
|
|
444
|
+
# @api private
|
|
445
|
+
#
|
|
446
|
+
def resolve_open_working_dir(working_dir, options)
|
|
447
|
+
PathResolver.root_of_worktree(working_dir, **context_defaults(options))
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Build a repository from caller options and resolved paths
|
|
451
|
+
#
|
|
452
|
+
# @param options [Hash] the caller-supplied options (`:git_ssh`,
|
|
453
|
+
# `:binary_path`, `:log`)
|
|
454
|
+
#
|
|
455
|
+
# @param paths [Hash{Symbol => (String, nil)}] the resolved paths
|
|
456
|
+
#
|
|
457
|
+
# @return [Git::Repository] the constructed repository
|
|
458
|
+
#
|
|
459
|
+
# @api private
|
|
460
|
+
#
|
|
461
|
+
def from_paths(options, paths)
|
|
462
|
+
new(execution_context: Git::ExecutionContext::Repository.from_hash(
|
|
463
|
+
options.merge(paths), logger: options[:log]
|
|
464
|
+
))
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Extract facade-level options from the raw clone options and return
|
|
468
|
+
# command-ready options
|
|
469
|
+
#
|
|
470
|
+
# Returns `[command_opts, context_opts]` where `command_opts` is the
|
|
471
|
+
# caller's options with facade-level keys removed. Remaining keys are
|
|
472
|
+
# forwarded to `Git::Commands::Clone`, which raises `ArgumentError` for
|
|
473
|
+
# unsupported ones. `context_opts` contains values used for the execution
|
|
474
|
+
# context and post-clone path resolution.
|
|
475
|
+
#
|
|
476
|
+
# @param options [Hash] raw caller-supplied options
|
|
477
|
+
#
|
|
478
|
+
# @return [Array<Hash>] a two-element tuple `[command_opts, context_opts]`
|
|
479
|
+
#
|
|
480
|
+
# @api private
|
|
481
|
+
#
|
|
482
|
+
def prepare_clone_options(options)
|
|
483
|
+
opts = options.dup
|
|
484
|
+
deprecate_clone_path_option!(opts)
|
|
485
|
+
deprecate_clone_recursive_option!(opts)
|
|
486
|
+
deprecate_clone_remote_option!(opts)
|
|
487
|
+
context_opts = extract_clone_context_options!(opts)
|
|
488
|
+
normalize_clone_repository_option!(opts)
|
|
489
|
+
|
|
490
|
+
[opts, context_opts]
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# Extract clone context options from command-ready options
|
|
494
|
+
#
|
|
495
|
+
# @param opts [Hash] clone options (mutated in place)
|
|
496
|
+
#
|
|
497
|
+
# @return [Hash{Symbol => Object}] context options for clone setup
|
|
498
|
+
#
|
|
499
|
+
# @api private
|
|
500
|
+
#
|
|
501
|
+
def extract_clone_context_options!(opts)
|
|
502
|
+
{
|
|
503
|
+
logger: opts.delete(:log),
|
|
504
|
+
git_ssh: opts.key?(:git_ssh) ? opts.delete(:git_ssh) : :use_global_config,
|
|
505
|
+
binary_path: opts.key?(:binary_path) ? opts.delete(:binary_path) : :use_global_config,
|
|
506
|
+
index: opts.delete(:index)
|
|
507
|
+
}
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Normalize the clone repository option for `git clone`
|
|
511
|
+
#
|
|
512
|
+
# @param opts [Hash] clone options (mutated in place)
|
|
513
|
+
#
|
|
514
|
+
# @return [void] mutates `opts` in place
|
|
515
|
+
#
|
|
516
|
+
# @api private
|
|
517
|
+
#
|
|
518
|
+
def normalize_clone_repository_option!(opts)
|
|
519
|
+
return unless opts.key?(:repository)
|
|
520
|
+
|
|
521
|
+
repository_val = opts.delete(:repository)
|
|
522
|
+
opts[:separate_git_dir] = repository_val if repository_val
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# Resolve paths for the cloned repository
|
|
526
|
+
#
|
|
527
|
+
# @param clone_dir [String] the directory reported by `git clone`
|
|
528
|
+
#
|
|
529
|
+
# @param bare [Boolean] whether the clone is bare
|
|
530
|
+
#
|
|
531
|
+
# @param index [String, nil] optional custom index path
|
|
532
|
+
#
|
|
533
|
+
# @return [Hash{Symbol => (String, nil)}] resolved path hash
|
|
534
|
+
#
|
|
535
|
+
# @api private
|
|
536
|
+
#
|
|
537
|
+
def resolve_clone_paths(clone_dir, bare, index)
|
|
538
|
+
args = bare ? { repository: clone_dir, bare: true } : { working_directory: clone_dir }
|
|
539
|
+
PathResolver.resolve_paths(**args, index: index)
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Run the `git init` command using a global execution context
|
|
543
|
+
#
|
|
544
|
+
# @param directory [String] the directory to initialize
|
|
545
|
+
#
|
|
546
|
+
# @param options [Hash] the normalized options hash (after alias resolution)
|
|
547
|
+
#
|
|
548
|
+
# @return [Git::CommandLineResult] the result of running `git init`
|
|
549
|
+
#
|
|
550
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
551
|
+
#
|
|
552
|
+
# @api private
|
|
553
|
+
#
|
|
554
|
+
def run_init_command(directory, options)
|
|
555
|
+
init_opts = options.slice(:bare, :initial_branch)
|
|
556
|
+
init_opts[:separate_git_dir] = options[:repository] if options.key?(:repository)
|
|
557
|
+
|
|
558
|
+
context = Git::ExecutionContext::Global.new(**context_defaults(options), logger: options[:log])
|
|
559
|
+
Git::Commands::Init.new(context).call(directory, **init_opts)
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# Open the repository produced by `git init`
|
|
563
|
+
#
|
|
564
|
+
# @param directory [String] the initialized directory
|
|
565
|
+
#
|
|
566
|
+
# @param options [Hash] the normalized options hash
|
|
567
|
+
#
|
|
568
|
+
# @return [Git::Repository] the repository opened after initialization
|
|
569
|
+
#
|
|
570
|
+
# @api private
|
|
571
|
+
#
|
|
572
|
+
def open_after_init(directory, options)
|
|
573
|
+
return bare(options[:repository] || directory, base_open_options_after_init(options)) if options[:bare]
|
|
574
|
+
|
|
575
|
+
self.open(directory, worktree_open_options_after_init(options))
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# Build common options for opening a repository after `git init`
|
|
579
|
+
#
|
|
580
|
+
# @param options [Hash] the normalized options hash
|
|
581
|
+
#
|
|
582
|
+
# @return [Hash{Symbol => Object}] options accepted by {.open} and {.bare}
|
|
583
|
+
#
|
|
584
|
+
# @api private
|
|
585
|
+
#
|
|
586
|
+
def base_open_options_after_init(options)
|
|
587
|
+
{
|
|
588
|
+
git_ssh: options.fetch(:git_ssh, :use_global_config),
|
|
589
|
+
binary_path: options.fetch(:binary_path, :use_global_config)
|
|
590
|
+
}.tap do |open_opts|
|
|
591
|
+
open_opts[:log] = options[:log] if options[:log]
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Build worktree options for opening a repository after `git init`
|
|
596
|
+
#
|
|
597
|
+
# @param options [Hash] the normalized options hash
|
|
598
|
+
#
|
|
599
|
+
# @return [Hash{Symbol => Object}] options accepted by {.open}
|
|
600
|
+
#
|
|
601
|
+
# @api private
|
|
602
|
+
#
|
|
603
|
+
def worktree_open_options_after_init(options)
|
|
604
|
+
base_open_options_after_init(options).tap do |open_opts|
|
|
605
|
+
open_opts[:index] = options[:index] if options[:index]
|
|
606
|
+
open_opts[:repository] = options[:repository] if options[:repository]
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Parse the clone directory and bare status from `git clone` stderr output
|
|
611
|
+
#
|
|
612
|
+
# @param stderr [String] stderr output from `git clone`
|
|
613
|
+
#
|
|
614
|
+
# @return [Array] a two-element tuple `[clone_dir, bare]`
|
|
615
|
+
#
|
|
616
|
+
# @raise [Git::UnexpectedResultError] if the stderr output cannot be parsed
|
|
617
|
+
#
|
|
618
|
+
# @api private
|
|
619
|
+
#
|
|
620
|
+
def parse_clone_stderr(stderr)
|
|
621
|
+
match = stderr.match(/Cloning into (?:(bare repository) )?'(.+)'\.\.\./)
|
|
622
|
+
raise Git::UnexpectedResultError, "Unable to determine clone directory from: #{stderr}" unless match
|
|
623
|
+
|
|
624
|
+
[match[2], !match[1].nil?]
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
# Handle the deprecated `:path` option for {clone}
|
|
628
|
+
#
|
|
629
|
+
# @param opts [Hash] clone options (mutated in place)
|
|
630
|
+
#
|
|
631
|
+
# @return [void] mutates `opts` in place
|
|
632
|
+
#
|
|
633
|
+
# @api private
|
|
634
|
+
#
|
|
635
|
+
def deprecate_clone_path_option!(opts)
|
|
636
|
+
return unless opts.key?(:path)
|
|
637
|
+
|
|
638
|
+
if defined?(Git::Deprecation)
|
|
639
|
+
Git::Deprecation.warn('The :path option for Git::Repository.clone is deprecated, use :chdir instead')
|
|
640
|
+
end
|
|
641
|
+
path = opts.delete(:path)
|
|
642
|
+
opts[:chdir] ||= path
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# Handle the deprecated `:recursive` option for {clone}
|
|
646
|
+
#
|
|
647
|
+
# @param opts [Hash] clone options (mutated in place)
|
|
648
|
+
#
|
|
649
|
+
# @return [void] mutates `opts` in place
|
|
650
|
+
#
|
|
651
|
+
# @api private
|
|
652
|
+
#
|
|
653
|
+
def deprecate_clone_recursive_option!(opts)
|
|
654
|
+
return unless opts.key?(:recursive)
|
|
655
|
+
|
|
656
|
+
if defined?(Git::Deprecation)
|
|
657
|
+
Git::Deprecation.warn(
|
|
658
|
+
'The :recursive option for Git::Repository.clone is deprecated, use :recurse_submodules instead'
|
|
659
|
+
)
|
|
660
|
+
end
|
|
661
|
+
opts[:recurse_submodules] = opts.delete(:recursive)
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# Handle the deprecated `:remote` option for {clone}
|
|
665
|
+
#
|
|
666
|
+
# @param opts [Hash] clone options (mutated in place)
|
|
667
|
+
#
|
|
668
|
+
# @return [void] mutates `opts` in place
|
|
669
|
+
#
|
|
670
|
+
# @api private
|
|
671
|
+
#
|
|
672
|
+
def deprecate_clone_remote_option!(opts)
|
|
673
|
+
return unless opts.key?(:remote)
|
|
674
|
+
|
|
675
|
+
if defined?(Git::Deprecation)
|
|
676
|
+
Git::Deprecation.warn('The :remote option for Git::Repository.clone is deprecated, use :origin instead')
|
|
677
|
+
end
|
|
678
|
+
opts[:origin] = opts.delete(:remote)
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
end
|