git 5.6.0 → 6.0.0
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/CHANGELOG.md +132 -16
- data/CONTRIBUTING.md +44 -31
- data/README.md +135 -67
- data/UPGRADING.md +915 -268
- data/git.gemspec +6 -8
- data/lib/git/author_info.rb +1 -2
- data/lib/git/commands/base.rb +22 -2
- data/lib/git/commands/cat_file/raw.rb +0 -68
- data/lib/git/commands/maintenance/register.rb +0 -4
- data/lib/git/commands/maintenance/run.rb +0 -4
- data/lib/git/commands/maintenance/start.rb +0 -4
- data/lib/git/commands/maintenance/stop.rb +0 -4
- data/lib/git/commands/maintenance/unregister.rb +0 -4
- data/lib/git/commands/maintenance.rb +0 -2
- data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
- data/lib/git/commands/show_ref/exists.rb +0 -5
- data/lib/git/commands/show_ref/list.rb +1 -1
- data/lib/git/commands/show_ref/verify.rb +1 -1
- data/lib/git/commands/show_ref.rb +1 -1
- data/lib/git/commands/worktree/repair.rb +0 -5
- data/lib/git/diff_path_status.rb +2 -2
- data/lib/git/encoding_utils.rb +25 -0
- data/lib/git/errors.rb +23 -5
- data/lib/git/factories.rb +361 -125
- data/lib/git/log.rb +3 -75
- data/lib/git/object.rb +4 -185
- data/lib/git/path_resolver.rb +104 -36
- data/lib/git/repository/branching.rb +21 -189
- data/lib/git/repository/committing.rb +0 -25
- data/lib/git/repository/context_helpers.rb +249 -155
- data/lib/git/repository/diffing.rb +2 -44
- data/lib/git/repository/merging.rb +32 -54
- data/lib/git/repository/object_operations.rb +121 -338
- data/lib/git/repository/remote_operations.rb +7 -219
- data/lib/git/repository/staging.rb +6 -184
- data/lib/git/repository/stashing.rb +126 -130
- data/lib/git/repository/status_operations.rb +1 -54
- data/lib/git/repository/worktree_operations.rb +31 -118
- data/lib/git/repository.rb +20 -299
- data/lib/git/system_call_guard.rb +95 -0
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +47 -364
- metadata +25 -19
- data/lib/git/author.rb +0 -46
- data/lib/git/branch.rb +0 -693
- data/lib/git/branches.rb +0 -194
- data/lib/git/remote.rb +0 -191
- data/lib/git/stash.rb +0 -124
- data/lib/git/stashes.rb +0 -198
- data/lib/git/status.rb +0 -384
- data/lib/git/worktree.rb +0 -180
- data/lib/git/worktrees.rb +0 -152
data/lib/git/factories.rb
CHANGED
|
@@ -18,6 +18,33 @@ module Git
|
|
|
18
18
|
# @api private
|
|
19
19
|
#
|
|
20
20
|
module Factories # rubocop:disable Metrics/ModuleLength
|
|
21
|
+
# Option keys {Git.open} accepts
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<Symbol>]
|
|
24
|
+
#
|
|
25
|
+
# @api private
|
|
26
|
+
#
|
|
27
|
+
OPEN_ALLOWED_OPTS = %i[repository index log git_ssh binary_path].freeze
|
|
28
|
+
private_constant :OPEN_ALLOWED_OPTS
|
|
29
|
+
|
|
30
|
+
# Option keys {Git.bare} accepts
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Symbol>]
|
|
33
|
+
#
|
|
34
|
+
# @api private
|
|
35
|
+
#
|
|
36
|
+
BARE_ALLOWED_OPTS = %i[index log git_ssh binary_path].freeze
|
|
37
|
+
private_constant :BARE_ALLOWED_OPTS
|
|
38
|
+
|
|
39
|
+
# Option keys {Git.init} accepts
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<Symbol>]
|
|
42
|
+
#
|
|
43
|
+
# @api private
|
|
44
|
+
#
|
|
45
|
+
INIT_ALLOWED_OPTS = %i[bare initial_branch repository separate_git_dir index log git_ssh binary_path].freeze
|
|
46
|
+
private_constant :INIT_ALLOWED_OPTS
|
|
47
|
+
|
|
21
48
|
# Clone a repository into a new directory
|
|
22
49
|
#
|
|
23
50
|
# @example Clone into the default directory
|
|
@@ -36,6 +63,11 @@ module Git
|
|
|
36
63
|
# @param directory [String, Pathname, nil] the local directory name to clone into;
|
|
37
64
|
# git derives the name from the URL when `nil`
|
|
38
65
|
#
|
|
66
|
+
# Expanded before git runs, so `~` is the home directory and a relative
|
|
67
|
+
# path is relative to `:chdir` when given and to the process working
|
|
68
|
+
# directory otherwise. git creates the expanded directory, and the
|
|
69
|
+
# returned repository is bound to it.
|
|
70
|
+
#
|
|
39
71
|
# @param options [Hash] options that control cloning
|
|
40
72
|
#
|
|
41
73
|
# Some options configure the returned {Git::Repository} instance after
|
|
@@ -79,7 +111,14 @@ module Git
|
|
|
79
111
|
#
|
|
80
112
|
# @option options [Boolean, nil] :dissociate stop borrowing from references
|
|
81
113
|
#
|
|
82
|
-
# @option options [String, nil] :
|
|
114
|
+
# @option options [String, nil] :repository alternate git directory path
|
|
115
|
+
#
|
|
116
|
+
# Writes a gitfile in the working tree. Alias: `:separate_git_dir`.
|
|
117
|
+
# Expanded before git runs, like `directory`: `~` is the home directory
|
|
118
|
+
# and a relative path is relative to `:chdir` when given and to the
|
|
119
|
+
# process working directory otherwise.
|
|
120
|
+
#
|
|
121
|
+
# @option options [String, nil] :separate_git_dir alias for `:repository`
|
|
83
122
|
#
|
|
84
123
|
# @option options [String, Array<String>, nil] :server_option
|
|
85
124
|
# protocol-v2 server options
|
|
@@ -153,32 +192,47 @@ module Git
|
|
|
153
192
|
#
|
|
154
193
|
# @option options [String, nil] :index a non-standard path to the index file
|
|
155
194
|
#
|
|
195
|
+
# Expanded before git runs, like `directory`: a relative path is expanded
|
|
196
|
+
# against `:chdir` when given, and against the process working directory
|
|
197
|
+
# otherwise.
|
|
198
|
+
#
|
|
156
199
|
# @option options [String, Pathname, nil] :chdir run `git clone` from within
|
|
157
200
|
# this directory
|
|
158
201
|
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
# `:recurse_submodules` instead
|
|
163
|
-
#
|
|
164
|
-
# @option options [String, nil] :remote deprecated; use `:origin` instead
|
|
202
|
+
# Expanded before git runs, so `~` is the home directory and a relative
|
|
203
|
+
# path is relative to the process working directory. `directory`,
|
|
204
|
+
# `:repository`, and `:index` are expanded against the expanded value.
|
|
165
205
|
#
|
|
166
206
|
# @return [Git::Repository] a repository bound to the cloned working copy or
|
|
167
207
|
# bare repository
|
|
168
208
|
#
|
|
169
|
-
# @raise [ArgumentError] if unsupported options are provided
|
|
209
|
+
# @raise [ArgumentError] if unsupported options are provided, or if
|
|
210
|
+
# `directory`, `:repository`, `:index`, or `:chdir` begins with `~user`
|
|
211
|
+
# for a user that does not exist
|
|
170
212
|
#
|
|
171
213
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
172
214
|
#
|
|
215
|
+
# @raise [Git::Error] if a filesystem call made while resolving the paths
|
|
216
|
+
# fails
|
|
217
|
+
#
|
|
173
218
|
# @raise [Git::UnexpectedResultError] if the cloned directory cannot be
|
|
174
219
|
# determined from git's output
|
|
175
220
|
#
|
|
221
|
+
# @note If git fails before the transfer completes, git removes `directory`
|
|
222
|
+
# and the separate git directory it created, or empties `directory` when
|
|
223
|
+
# it existed before the call. A `:repository` path that already exists is
|
|
224
|
+
# refused before the transfer starts. If the transfer completes but the
|
|
225
|
+
# checkout fails, or the `:timeout` or the global timeout kills git, what
|
|
226
|
+
# git wrote is left in place. If {Git::UnexpectedResultError} is raised,
|
|
227
|
+
# the completed clone is left in place.
|
|
228
|
+
#
|
|
176
229
|
# @api public
|
|
177
230
|
#
|
|
178
231
|
def clone(repository_url, directory = nil, options = {})
|
|
179
232
|
opts, context_opts = prepare_clone_options(options)
|
|
233
|
+
directory = expand_directory(directory, bare: context_opts[:bare], base: opts[:chdir]) if directory
|
|
180
234
|
clone_result = run_clone_command(repository_url, directory, opts, context_opts)
|
|
181
|
-
paths = resolve_paths_from_clone_result(clone_result, opts, context_opts)
|
|
235
|
+
paths = resolve_paths_from_clone_result(clone_result, directory, opts, context_opts)
|
|
182
236
|
|
|
183
237
|
from_paths(clone_repository_options(context_opts), paths)
|
|
184
238
|
end
|
|
@@ -196,6 +250,10 @@ module Git
|
|
|
196
250
|
#
|
|
197
251
|
# @param directory [String] the directory to initialize; defaults to `'.'`
|
|
198
252
|
#
|
|
253
|
+
# Expanded before git runs, so `~` is the home directory and a relative
|
|
254
|
+
# path is relative to the process working directory. git creates the
|
|
255
|
+
# expanded directory, and the returned repository is bound to it.
|
|
256
|
+
#
|
|
199
257
|
# @param options [Hash] options that control initialization
|
|
200
258
|
#
|
|
201
259
|
# Some options configure the returned {Git::Repository} instance after
|
|
@@ -208,6 +266,8 @@ module Git
|
|
|
208
266
|
# @option options [String, nil] :repository path for the `.git` directory
|
|
209
267
|
#
|
|
210
268
|
# Writes a gitfile in the working tree. Alias: `:separate_git_dir`.
|
|
269
|
+
# Expanded before git runs, like `directory`: `~` is the home directory
|
|
270
|
+
# and a relative path is relative to the process working directory.
|
|
211
271
|
#
|
|
212
272
|
# @option options [String, nil] :separate_git_dir alias for `:repository`
|
|
213
273
|
#
|
|
@@ -228,19 +288,33 @@ module Git
|
|
|
228
288
|
# @option options [String, nil] :index custom index path for the returned
|
|
229
289
|
# repository
|
|
230
290
|
#
|
|
231
|
-
#
|
|
291
|
+
# Expanded before git runs, like `directory`: `~` is the home directory
|
|
292
|
+
# and a relative path is relative to the process working directory.
|
|
232
293
|
#
|
|
233
294
|
# @return [Git::Repository] a repository bound to the newly initialized repository
|
|
234
295
|
#
|
|
296
|
+
# @raise [ArgumentError] if `options` contains an unknown key
|
|
297
|
+
#
|
|
298
|
+
# @raise [ArgumentError] if `directory`, `:repository`, or `:index` begins
|
|
299
|
+
# with `~user` for a user that does not exist
|
|
300
|
+
#
|
|
235
301
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
236
302
|
#
|
|
303
|
+
# @raise [Git::Error] if a filesystem call made while resolving the paths
|
|
304
|
+
# fails
|
|
305
|
+
#
|
|
306
|
+
# @note If git exits non-zero after it starts writing, whatever it wrote is
|
|
307
|
+
# left in place: `directory` when git created it, and a git directory
|
|
308
|
+
# (`.git`, or the `:repository` path and the gitfile pointing at it) that
|
|
309
|
+
# git may not recognize as a repository. If git succeeds but the
|
|
310
|
+
# repository cannot be opened, the initialized repository is left in place.
|
|
311
|
+
#
|
|
237
312
|
# @api public
|
|
238
313
|
#
|
|
239
314
|
def init(directory = '.', options = {})
|
|
240
|
-
options
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
end
|
|
315
|
+
reject_unknown_option_keys!(options.keys, INIT_ALLOWED_OPTS)
|
|
316
|
+
options = normalize_init_options(options)
|
|
317
|
+
directory = expand_directory(directory, bare: options[:bare])
|
|
244
318
|
|
|
245
319
|
run_init_command(directory, options)
|
|
246
320
|
open_after_init(directory, options)
|
|
@@ -254,6 +328,8 @@ module Git
|
|
|
254
328
|
# @param working_dir [String] the path to the root of the working copy
|
|
255
329
|
#
|
|
256
330
|
# May be any path inside the working tree when `:repository` is not given.
|
|
331
|
+
# Expanded before use: `~` is the home directory and a relative path is
|
|
332
|
+
# relative to the process working directory.
|
|
257
333
|
#
|
|
258
334
|
# @param options [Hash] options that control how the repository is located
|
|
259
335
|
#
|
|
@@ -261,10 +337,13 @@ module Git
|
|
|
261
337
|
# `.git` directory
|
|
262
338
|
#
|
|
263
339
|
# When given, `working_dir` is used as-is (the working tree root is not
|
|
264
|
-
# auto-detected).
|
|
340
|
+
# auto-detected). A relative path is expanded against the process working
|
|
341
|
+
# directory, not against `working_dir`.
|
|
265
342
|
#
|
|
266
343
|
# @option options [String, nil] :index a non-standard path to the index file
|
|
267
344
|
#
|
|
345
|
+
# A relative path is expanded against the process working directory.
|
|
346
|
+
#
|
|
268
347
|
# @option options [Logger, nil] :log logger used for git operations
|
|
269
348
|
#
|
|
270
349
|
# @option options [String, nil, :use_global_config] :git_ssh
|
|
@@ -281,8 +360,15 @@ module Git
|
|
|
281
360
|
#
|
|
282
361
|
# @return [Git::Repository] a repository bound to the resolved paths
|
|
283
362
|
#
|
|
363
|
+
# @raise [ArgumentError] if `options` contains an unknown key
|
|
364
|
+
#
|
|
284
365
|
# @raise [ArgumentError] if `working_dir` is not a directory or is not inside
|
|
285
|
-
# a git working tree
|
|
366
|
+
# a git working tree, or if it begins with `~user` for a user that does not
|
|
367
|
+
# exist
|
|
368
|
+
#
|
|
369
|
+
# @raise [Git::Error] if the `.git` path is a gitdir pointer file that cannot
|
|
370
|
+
# be read, if the git binary cannot be found or fails to launch, or if a
|
|
371
|
+
# filesystem call made while resolving the paths fails
|
|
286
372
|
#
|
|
287
373
|
# @note This method opens working copies only. To open a bare repository, use
|
|
288
374
|
# `Git.bare`.
|
|
@@ -290,14 +376,14 @@ module Git
|
|
|
290
376
|
# @api public
|
|
291
377
|
#
|
|
292
378
|
def open(working_dir, options = {})
|
|
293
|
-
|
|
379
|
+
reject_unknown_option_keys!(options.keys, OPEN_ALLOWED_OPTS)
|
|
380
|
+
expanded_dir = PathResolver.expand_path(working_dir, PathResolver::WORKING_DIRECTORY_MESSAGE)
|
|
381
|
+
raise ArgumentError, "'#{working_dir}' is not a directory" unless Dir.exist?(expanded_dir)
|
|
294
382
|
|
|
295
|
-
working_dir = resolve_open_working_dir(
|
|
383
|
+
working_dir = options[:repository] ? expanded_dir : resolve_open_working_dir(expanded_dir, options)
|
|
296
384
|
|
|
297
|
-
paths =
|
|
298
|
-
|
|
299
|
-
repository: options[:repository],
|
|
300
|
-
index: options[:index]
|
|
385
|
+
paths = resolve_repository_paths(
|
|
386
|
+
working_dir, bare: false, repository: options[:repository], index: options[:index]
|
|
301
387
|
)
|
|
302
388
|
|
|
303
389
|
from_paths(options, paths)
|
|
@@ -308,10 +394,19 @@ module Git
|
|
|
308
394
|
# @example Open a bare repository
|
|
309
395
|
# repository = Git.bare('/path/to/repo.git')
|
|
310
396
|
#
|
|
397
|
+
# @example Bind a bare repository to a scratch index
|
|
398
|
+
# repository = Git.bare('/srv/repo.git', index: '/tmp/scratch.index')
|
|
399
|
+
# repository.read_tree('HEAD')
|
|
400
|
+
# tree_sha = repository.write_tree
|
|
401
|
+
#
|
|
311
402
|
# @param git_dir [String] the path to the bare repository directory
|
|
312
403
|
#
|
|
313
404
|
# @param options [Hash] options used to configure the repository instance
|
|
314
405
|
#
|
|
406
|
+
# @option options [String, nil] :index a non-standard path to the index file
|
|
407
|
+
#
|
|
408
|
+
# A relative path is expanded against the process working directory.
|
|
409
|
+
#
|
|
315
410
|
# @option options [Logger, nil] :log logger used for git operations
|
|
316
411
|
#
|
|
317
412
|
# @option options [String, nil, :use_global_config] :git_ssh
|
|
@@ -328,10 +423,15 @@ module Git
|
|
|
328
423
|
#
|
|
329
424
|
# @return [Git::Repository] a repository bound to the bare repository directory
|
|
330
425
|
#
|
|
426
|
+
# @raise [ArgumentError] if `options` contains an unknown key
|
|
427
|
+
#
|
|
428
|
+
# @raise [Git::Error] if `git_dir` is a gitdir pointer file that cannot be read
|
|
429
|
+
#
|
|
331
430
|
# @api public
|
|
332
431
|
#
|
|
333
432
|
def bare(git_dir, options = {})
|
|
334
|
-
|
|
433
|
+
reject_unknown_option_keys!(options.keys, BARE_ALLOWED_OPTS)
|
|
434
|
+
paths = resolve_repository_paths(git_dir, bare: true, index: options[:index])
|
|
335
435
|
|
|
336
436
|
from_paths(options, paths)
|
|
337
437
|
end
|
|
@@ -380,8 +480,15 @@ module Git
|
|
|
380
480
|
|
|
381
481
|
# Resolve repository paths from a completed clone result
|
|
382
482
|
#
|
|
483
|
+
# When the caller gave a directory, it was expanded before git ran and is
|
|
484
|
+
# used as is. Only when git derived the name from the URL is the directory
|
|
485
|
+
# read from git's output, where it is a relative name under `:chdir`.
|
|
486
|
+
#
|
|
383
487
|
# @param clone_result [Git::CommandLine::Result] the completed clone result
|
|
384
488
|
#
|
|
489
|
+
# @param directory [String, nil] the expanded directory given to {.clone},
|
|
490
|
+
# or `nil` when git derived the name from the URL
|
|
491
|
+
#
|
|
385
492
|
# @param opts [Hash] command-ready clone options
|
|
386
493
|
#
|
|
387
494
|
# @param context_opts [Hash] context options produced while preparing clone
|
|
@@ -390,26 +497,75 @@ module Git
|
|
|
390
497
|
# @option opts [String, Pathname, nil] :chdir run `git clone` from within
|
|
391
498
|
# this directory
|
|
392
499
|
#
|
|
393
|
-
# @option
|
|
394
|
-
#
|
|
395
|
-
# @option opts [Boolean, nil] :mirror set up a mirror of the source
|
|
500
|
+
# @option context_opts [Boolean] :bare whether `:bare` or `:mirror` was
|
|
501
|
+
# requested
|
|
396
502
|
#
|
|
397
|
-
# @option context_opts [String, nil] :index
|
|
398
|
-
# returned repository
|
|
503
|
+
# @option context_opts [String, nil] :index the already expanded custom
|
|
504
|
+
# index path for the returned repository
|
|
399
505
|
#
|
|
400
506
|
# @return [Hash{Symbol => (String, nil)}] resolved path hash
|
|
401
507
|
#
|
|
402
|
-
# @raise [Git::UnexpectedResultError] if
|
|
508
|
+
# @raise [Git::UnexpectedResultError] if no directory was given and the clone
|
|
509
|
+
# directory cannot be parsed from git's output
|
|
510
|
+
#
|
|
511
|
+
# @api private
|
|
512
|
+
#
|
|
513
|
+
def resolve_paths_from_clone_result(clone_result, directory, opts, context_opts)
|
|
514
|
+
clone_dir = directory || prefix_with_chdir(parse_clone_directory(clone_result.stderr), opts[:chdir])
|
|
515
|
+
|
|
516
|
+
bare = context_opts[:bare] || bare_clone?(clone_result.stderr)
|
|
517
|
+
resolve_repository_paths(clone_dir, bare: bare, index: context_opts[:index])
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# Expand the directory given to {.init} or {.clone} before git sees it
|
|
521
|
+
#
|
|
522
|
+
# git does not expand `~` in a path on its command line, so the expansion
|
|
523
|
+
# happens here. The directory git creates is then the one the returned
|
|
524
|
+
# repository is bound to.
|
|
525
|
+
#
|
|
526
|
+
# @param directory [String, Pathname] the directory to initialize or clone
|
|
527
|
+
# into
|
|
528
|
+
#
|
|
529
|
+
# @param bare [Boolean, nil] whether the directory is the bare repository
|
|
530
|
+
# itself rather than a working directory; selects the error message
|
|
531
|
+
#
|
|
532
|
+
# @param base [String, Pathname, nil] the directory a relative path is
|
|
533
|
+
# expanded against, or `nil` for the process working directory
|
|
534
|
+
#
|
|
535
|
+
# @return [String] the absolute path
|
|
536
|
+
#
|
|
537
|
+
# @raise [ArgumentError] if the path begins with `~user` for a user that
|
|
538
|
+
# does not exist
|
|
539
|
+
#
|
|
540
|
+
# @raise [Git::Error] if the path cannot be expanded, which happens when the
|
|
541
|
+
# process working directory has been removed
|
|
542
|
+
#
|
|
543
|
+
# @api private
|
|
544
|
+
#
|
|
545
|
+
def expand_directory(directory, bare:, base: nil)
|
|
546
|
+
message = bare ? PathResolver::REPOSITORY_MESSAGE : PathResolver::WORKING_DIRECTORY_MESSAGE
|
|
547
|
+
PathResolver.expand_path(directory, message, base: base)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Join the clone directory git reported onto the `:chdir` it ran in
|
|
551
|
+
#
|
|
552
|
+
# Used only when no directory was given to {.clone}: git derives a relative
|
|
553
|
+
# name from the URL and creates it under `chdir`. An absolute path, should
|
|
554
|
+
# git ever report one, passes through unchanged.
|
|
555
|
+
#
|
|
556
|
+
# @param path [String] the clone directory reported by `git clone`
|
|
557
|
+
#
|
|
558
|
+
# @param chdir [String, Pathname, nil] the directory git ran in, or `nil`
|
|
559
|
+
#
|
|
560
|
+
# @return [String] `path` prefixed with `chdir` when `chdir` is given and
|
|
561
|
+
# `path` is relative; otherwise `path` unchanged
|
|
403
562
|
#
|
|
404
563
|
# @api private
|
|
405
564
|
#
|
|
406
|
-
def
|
|
407
|
-
|
|
408
|
-
chdir = opts[:chdir]
|
|
409
|
-
clone_dir = File.join(chdir, clone_dir) if chdir && !Pathname.new(clone_dir).absolute?
|
|
565
|
+
def prefix_with_chdir(path, chdir)
|
|
566
|
+
return path if chdir.nil? || Pathname.new(path).absolute?
|
|
410
567
|
|
|
411
|
-
|
|
412
|
-
resolve_clone_paths(clone_dir, bare, context_opts[:index])
|
|
568
|
+
File.join(chdir, path)
|
|
413
569
|
end
|
|
414
570
|
|
|
415
571
|
# Build repository construction options from clone context options
|
|
@@ -473,12 +629,32 @@ module Git
|
|
|
473
629
|
#
|
|
474
630
|
# @raise [ArgumentError] if `working_dir` is not inside a git working tree
|
|
475
631
|
#
|
|
632
|
+
# @raise [Git::Error] if the git binary cannot be found or fails to launch, or
|
|
633
|
+
# if `working_dir` cannot be expanded to an absolute path
|
|
634
|
+
#
|
|
476
635
|
# @api private
|
|
477
636
|
#
|
|
478
637
|
def resolve_open_working_dir(working_dir, options)
|
|
479
638
|
PathResolver.root_of_worktree(working_dir, **context_defaults(options))
|
|
480
639
|
end
|
|
481
640
|
|
|
641
|
+
# Raise if `keys` contains a key outside `allowed`
|
|
642
|
+
#
|
|
643
|
+
# @param keys [Array<Symbol>] the keys of the caller-supplied options
|
|
644
|
+
#
|
|
645
|
+
# @param allowed [Array<Symbol>] the keys the factory accepts
|
|
646
|
+
#
|
|
647
|
+
# @return [void]
|
|
648
|
+
#
|
|
649
|
+
# @raise [ArgumentError] if `keys` contains an unknown key
|
|
650
|
+
#
|
|
651
|
+
# @api private
|
|
652
|
+
#
|
|
653
|
+
def reject_unknown_option_keys!(keys, allowed)
|
|
654
|
+
unknown = keys - allowed
|
|
655
|
+
raise ArgumentError, "Unknown options: #{unknown.join(', ')}" unless unknown.empty?
|
|
656
|
+
end
|
|
657
|
+
|
|
482
658
|
# Build a repository from caller options and resolved paths
|
|
483
659
|
#
|
|
484
660
|
# @param options [Hash] the caller-supplied options (`:git_ssh`,
|
|
@@ -523,15 +699,45 @@ module Git
|
|
|
523
699
|
#
|
|
524
700
|
def prepare_clone_options(options)
|
|
525
701
|
opts = options.dup
|
|
526
|
-
deprecate_clone_path_option!(opts)
|
|
527
|
-
deprecate_clone_recursive_option!(opts)
|
|
528
|
-
deprecate_clone_remote_option!(opts)
|
|
529
702
|
context_opts = extract_clone_context_options!(opts)
|
|
703
|
+
expand_path_option!(opts, :chdir, PathResolver::CHDIR_MESSAGE)
|
|
530
704
|
normalize_clone_repository_option!(opts)
|
|
705
|
+
expand_path_option!(context_opts, :index, PathResolver::INDEX_MESSAGE, base: opts[:chdir])
|
|
531
706
|
|
|
532
707
|
[opts, context_opts]
|
|
533
708
|
end
|
|
534
709
|
|
|
710
|
+
# Expand a path option in place when it is present
|
|
711
|
+
#
|
|
712
|
+
# git does no tilde expansion and ruby-git starts it without a shell, so
|
|
713
|
+
# every path option is expanded here before git sees it. A path that cannot
|
|
714
|
+
# be expanded is rejected before git runs.
|
|
715
|
+
#
|
|
716
|
+
# @param hash [Hash] the options hash (mutated in place)
|
|
717
|
+
#
|
|
718
|
+
# @param key [Symbol] the option to expand; left alone when `nil` or absent
|
|
719
|
+
#
|
|
720
|
+
# @param message [String] the {Git::Error} message when expansion fails
|
|
721
|
+
#
|
|
722
|
+
# @param base [String, Pathname, nil] the directory a relative path is
|
|
723
|
+
# expanded against, or `nil` for the process working directory
|
|
724
|
+
#
|
|
725
|
+
# @return [void] mutates `hash` in place
|
|
726
|
+
#
|
|
727
|
+
# @raise [ArgumentError] if the path begins with `~user` for a user that
|
|
728
|
+
# does not exist
|
|
729
|
+
#
|
|
730
|
+
# @raise [Git::Error] if the path cannot be expanded, which happens when the
|
|
731
|
+
# process working directory has been removed
|
|
732
|
+
#
|
|
733
|
+
# @api private
|
|
734
|
+
#
|
|
735
|
+
def expand_path_option!(hash, key, message, base: nil)
|
|
736
|
+
return unless hash[key]
|
|
737
|
+
|
|
738
|
+
hash[key] = PathResolver.expand_path(hash[key], message, base: base)
|
|
739
|
+
end
|
|
740
|
+
|
|
535
741
|
# Extract clone context options from command-ready options
|
|
536
742
|
#
|
|
537
743
|
# @param opts [Hash] clone options (mutated in place)
|
|
@@ -547,7 +753,14 @@ module Git
|
|
|
547
753
|
# @option opts [String, nil] :index custom index path for the returned
|
|
548
754
|
# repository
|
|
549
755
|
#
|
|
550
|
-
# @
|
|
756
|
+
# @option opts [Boolean, nil] :bare clone as a bare repository; left in
|
|
757
|
+
# `opts` for git
|
|
758
|
+
#
|
|
759
|
+
# @option opts [Boolean, nil] :mirror set up a mirror of the source; left in
|
|
760
|
+
# `opts` for git
|
|
761
|
+
#
|
|
762
|
+
# @return [Hash{Symbol => Object}] context options for clone setup, with
|
|
763
|
+
# `:bare` true when either `:bare` or `:mirror` was requested
|
|
551
764
|
#
|
|
552
765
|
# @api private
|
|
553
766
|
#
|
|
@@ -556,47 +769,127 @@ module Git
|
|
|
556
769
|
logger: opts.delete(:log),
|
|
557
770
|
git_ssh: opts.key?(:git_ssh) ? opts.delete(:git_ssh) : :use_global_config,
|
|
558
771
|
binary_path: opts.key?(:binary_path) ? opts.delete(:binary_path) : :use_global_config,
|
|
559
|
-
index: opts.delete(:index)
|
|
772
|
+
index: opts.delete(:index),
|
|
773
|
+
bare: opts[:bare] || opts[:mirror] || false
|
|
560
774
|
}
|
|
561
775
|
end
|
|
562
776
|
|
|
563
777
|
# Normalize the clone repository option for `git clone`
|
|
564
778
|
#
|
|
779
|
+
# Resolves the `:separate_git_dir` alias to `:repository`, then renames
|
|
780
|
+
# `:repository` to `:separate_git_dir` and expands it before git runs,
|
|
781
|
+
# against `:chdir` when given, like the clone directory. git does no tilde
|
|
782
|
+
# expansion, so a `~`-prefixed path handed to git would create a directory
|
|
783
|
+
# literally named `~`.
|
|
784
|
+
#
|
|
565
785
|
# @param opts [Hash] clone options (mutated in place)
|
|
566
786
|
#
|
|
567
787
|
# @option opts [String, nil] :repository alternate git directory path
|
|
568
788
|
#
|
|
789
|
+
# @option opts [String, nil] :separate_git_dir alias for `:repository`
|
|
790
|
+
#
|
|
791
|
+
# @option opts [String, Pathname, nil] :chdir the directory git runs in
|
|
792
|
+
#
|
|
569
793
|
# @return [void] mutates `opts` in place
|
|
570
794
|
#
|
|
795
|
+
# @raise [ArgumentError] if `:repository` begins with `~user` for a user
|
|
796
|
+
# that does not exist
|
|
797
|
+
#
|
|
798
|
+
# @raise [Git::Error] if the path cannot be expanded, which happens when the
|
|
799
|
+
# process working directory has been removed
|
|
800
|
+
#
|
|
571
801
|
# @api private
|
|
572
802
|
#
|
|
573
803
|
def normalize_clone_repository_option!(opts)
|
|
574
|
-
|
|
575
|
-
|
|
804
|
+
resolve_repository_alias!(opts)
|
|
576
805
|
repository_val = opts.delete(:repository)
|
|
577
|
-
|
|
806
|
+
return unless repository_val
|
|
807
|
+
|
|
808
|
+
opts[:separate_git_dir] =
|
|
809
|
+
PathResolver.expand_path(repository_val, PathResolver::REPOSITORY_MESSAGE, base: opts[:chdir])
|
|
578
810
|
end
|
|
579
811
|
|
|
580
|
-
# Resolve paths for
|
|
812
|
+
# Resolve the repository, working directory, and index paths for a repository
|
|
581
813
|
#
|
|
582
|
-
# @param
|
|
814
|
+
# @param dir [String] the bare git directory, or the working directory of a
|
|
815
|
+
# non-bare repository
|
|
583
816
|
#
|
|
584
|
-
# @param bare [Boolean] whether the
|
|
817
|
+
# @param bare [Boolean] whether the repository is bare
|
|
585
818
|
#
|
|
586
819
|
# @param index [String, nil] optional custom index path
|
|
587
820
|
#
|
|
821
|
+
# @param repository [String, nil] optional custom `.git` directory path;
|
|
822
|
+
# ignored when `bare` is `true`
|
|
823
|
+
#
|
|
588
824
|
# @return [Hash{Symbol => (String, nil)}] resolved path hash
|
|
589
825
|
#
|
|
590
826
|
# @api private
|
|
591
827
|
#
|
|
592
|
-
def
|
|
593
|
-
args = bare ? { repository:
|
|
828
|
+
def resolve_repository_paths(dir, bare:, index:, repository: nil)
|
|
829
|
+
args = bare ? { repository: dir, bare: true } : { working_directory: dir, repository: repository }
|
|
594
830
|
PathResolver.resolve_paths(**args, index: index)
|
|
595
831
|
end
|
|
596
832
|
|
|
833
|
+
# Normalize the options given to {.init}
|
|
834
|
+
#
|
|
835
|
+
# Resolves the `:separate_git_dir` alias to `:repository` and expands
|
|
836
|
+
# `:repository` and `:index` before git runs, so the git directory git
|
|
837
|
+
# creates is the one the returned repository is bound to and a path that
|
|
838
|
+
# cannot be expanded is rejected before a repository is left on disk.
|
|
839
|
+
#
|
|
840
|
+
# @param options [Hash] the caller-supplied options hash
|
|
841
|
+
#
|
|
842
|
+
# @option options [String, nil] :repository path for the `.git` directory
|
|
843
|
+
#
|
|
844
|
+
# @option options [String, nil] :separate_git_dir alias for `:repository`
|
|
845
|
+
#
|
|
846
|
+
# @option options [String, nil] :index custom index path for the returned
|
|
847
|
+
# repository
|
|
848
|
+
#
|
|
849
|
+
# @return [Hash] a new options hash with `:repository` set and expanded when
|
|
850
|
+
# either spelling was given, and `:index` expanded when given
|
|
851
|
+
#
|
|
852
|
+
# @raise [ArgumentError] if `:repository` or `:index` begins with `~user`
|
|
853
|
+
# for a user that does not exist
|
|
854
|
+
#
|
|
855
|
+
# @raise [Git::Error] if a path cannot be expanded, which happens when the
|
|
856
|
+
# process working directory has been removed
|
|
857
|
+
#
|
|
858
|
+
# @api private
|
|
859
|
+
#
|
|
860
|
+
def normalize_init_options(options)
|
|
861
|
+
options = options.dup
|
|
862
|
+
resolve_repository_alias!(options)
|
|
863
|
+
expand_path_option!(options, :repository, PathResolver::REPOSITORY_MESSAGE)
|
|
864
|
+
expand_path_option!(options, :index, PathResolver::INDEX_MESSAGE)
|
|
865
|
+
options
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
# Resolve the `:separate_git_dir` alias onto `:repository`
|
|
869
|
+
#
|
|
870
|
+
# `:separate_git_dir` is removed. Its value becomes `:repository` unless
|
|
871
|
+
# `:repository` was given a non-nil value, which wins.
|
|
872
|
+
#
|
|
873
|
+
# @param options [Hash] factory options (mutated in place)
|
|
874
|
+
#
|
|
875
|
+
# @option options [String, nil] :repository path for the git directory
|
|
876
|
+
#
|
|
877
|
+
# @option options [String, nil] :separate_git_dir alias for `:repository`
|
|
878
|
+
#
|
|
879
|
+
# @return [void] mutates `options` in place
|
|
880
|
+
#
|
|
881
|
+
# @api private
|
|
882
|
+
#
|
|
883
|
+
def resolve_repository_alias!(options)
|
|
884
|
+
return unless options.key?(:separate_git_dir)
|
|
885
|
+
|
|
886
|
+
separate_git_dir = options.delete(:separate_git_dir)
|
|
887
|
+
options[:repository] = separate_git_dir if options[:repository].nil?
|
|
888
|
+
end
|
|
889
|
+
|
|
597
890
|
# Run the `git init` command using a global execution context
|
|
598
891
|
#
|
|
599
|
-
# @param directory [String] the directory to initialize
|
|
892
|
+
# @param directory [String] the absolute path of the directory to initialize
|
|
600
893
|
#
|
|
601
894
|
# @param options [Hash] the normalized options hash (after alias resolution)
|
|
602
895
|
#
|
|
@@ -674,6 +967,9 @@ module Git
|
|
|
674
967
|
#
|
|
675
968
|
# @option options [Logger, nil] :log logger used for git operations
|
|
676
969
|
#
|
|
970
|
+
# @option options [String, nil] :index custom index path for the returned
|
|
971
|
+
# repository
|
|
972
|
+
#
|
|
677
973
|
# @return [Hash{Symbol => Object}] options accepted by {.open} and {.bare}
|
|
678
974
|
#
|
|
679
975
|
# @api private
|
|
@@ -684,6 +980,7 @@ module Git
|
|
|
684
980
|
binary_path: options.fetch(:binary_path, :use_global_config)
|
|
685
981
|
}.tap do |open_opts|
|
|
686
982
|
open_opts[:log] = options[:log] if options[:log]
|
|
983
|
+
open_opts[:index] = options[:index] if options[:index]
|
|
687
984
|
end
|
|
688
985
|
end
|
|
689
986
|
|
|
@@ -710,104 +1007,43 @@ module Git
|
|
|
710
1007
|
#
|
|
711
1008
|
def worktree_open_options_after_init(options)
|
|
712
1009
|
base_open_options_after_init(options).tap do |open_opts|
|
|
713
|
-
open_opts[:index] = options[:index] if options[:index]
|
|
714
1010
|
open_opts[:repository] = options[:repository] if options[:repository]
|
|
715
1011
|
end
|
|
716
1012
|
end
|
|
717
1013
|
|
|
718
|
-
# Parse the
|
|
1014
|
+
# Parse the directory git derived from the URL out of `git clone` stderr
|
|
1015
|
+
#
|
|
1016
|
+
# Only called when no directory was given to {.clone}. The name git derives
|
|
1017
|
+
# is a single path component, so it never spans a line.
|
|
719
1018
|
#
|
|
720
1019
|
# @param stderr [String] stderr output from `git clone`
|
|
721
1020
|
#
|
|
722
|
-
# @return [
|
|
1021
|
+
# @return [String] the clone directory git reported
|
|
723
1022
|
#
|
|
724
1023
|
# @raise [Git::UnexpectedResultError] if the stderr output cannot be parsed
|
|
725
1024
|
#
|
|
726
1025
|
# @api private
|
|
727
1026
|
#
|
|
728
|
-
def
|
|
729
|
-
match = stderr.match(
|
|
1027
|
+
def parse_clone_directory(stderr)
|
|
1028
|
+
match = stderr.match(/^Cloning into (?:bare repository )?'(.+)'\.\.\.$/)
|
|
730
1029
|
raise Git::UnexpectedResultError, "Unable to determine clone directory from: #{stderr}" unless match
|
|
731
1030
|
|
|
732
|
-
|
|
1031
|
+
match[1]
|
|
733
1032
|
end
|
|
734
1033
|
|
|
735
|
-
#
|
|
736
|
-
#
|
|
737
|
-
# @param opts [Hash] clone options (mutated in place)
|
|
738
|
-
#
|
|
739
|
-
# @option opts [String, Pathname, nil] :path deprecated; use `:chdir`
|
|
740
|
-
# instead
|
|
1034
|
+
# Whether `git clone` reported that it created a bare repository
|
|
741
1035
|
#
|
|
742
|
-
#
|
|
743
|
-
#
|
|
744
|
-
#
|
|
745
|
-
# @return [void] mutates `opts` in place
|
|
746
|
-
#
|
|
747
|
-
# @api private
|
|
748
|
-
#
|
|
749
|
-
def deprecate_clone_path_option!(opts)
|
|
750
|
-
return unless opts.key?(:path)
|
|
751
|
-
|
|
752
|
-
if defined?(Git::Deprecation)
|
|
753
|
-
Git::Deprecation.warn(
|
|
754
|
-
'The :path option for Git.clone is deprecated and will be removed in v6.0.0. ' \
|
|
755
|
-
'Use :chdir instead.'
|
|
756
|
-
)
|
|
757
|
-
end
|
|
758
|
-
path = opts.delete(:path)
|
|
759
|
-
opts[:chdir] ||= path
|
|
760
|
-
end
|
|
761
|
-
|
|
762
|
-
# Handle the deprecated `:recursive` option for {clone}
|
|
1036
|
+
# Matches only the fixed prefix of git's progress line, so a destination
|
|
1037
|
+
# that spans several lines does not affect the result.
|
|
763
1038
|
#
|
|
764
|
-
# @param
|
|
765
|
-
#
|
|
766
|
-
# @option opts [Boolean, nil] :recursive deprecated; use
|
|
767
|
-
# `:recurse_submodules` instead
|
|
768
|
-
#
|
|
769
|
-
# @option opts [Boolean, String, Array<String>, nil] :recurse_submodules
|
|
770
|
-
# initialize submodules after cloning
|
|
771
|
-
#
|
|
772
|
-
# @return [void] mutates `opts` in place
|
|
773
|
-
#
|
|
774
|
-
# @api private
|
|
775
|
-
#
|
|
776
|
-
def deprecate_clone_recursive_option!(opts)
|
|
777
|
-
return unless opts.key?(:recursive)
|
|
778
|
-
|
|
779
|
-
if defined?(Git::Deprecation)
|
|
780
|
-
Git::Deprecation.warn(
|
|
781
|
-
'The :recursive option for Git.clone is deprecated and will be removed in v6.0.0. ' \
|
|
782
|
-
'Use :recurse_submodules instead.'
|
|
783
|
-
)
|
|
784
|
-
end
|
|
785
|
-
opts[:recurse_submodules] = opts.delete(:recursive)
|
|
786
|
-
end
|
|
787
|
-
|
|
788
|
-
# Handle the deprecated `:remote` option for {clone}
|
|
789
|
-
#
|
|
790
|
-
# @param opts [Hash] clone options (mutated in place)
|
|
791
|
-
#
|
|
792
|
-
# @option opts [String, nil] :remote deprecated; use `:origin` instead
|
|
793
|
-
#
|
|
794
|
-
# @option opts [String, nil] :origin remote name to use instead of
|
|
795
|
-
# `origin`
|
|
1039
|
+
# @param stderr [String] stderr output from `git clone`
|
|
796
1040
|
#
|
|
797
|
-
# @return [
|
|
1041
|
+
# @return [Boolean] true when git reported a bare repository
|
|
798
1042
|
#
|
|
799
1043
|
# @api private
|
|
800
1044
|
#
|
|
801
|
-
def
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if defined?(Git::Deprecation)
|
|
805
|
-
Git::Deprecation.warn(
|
|
806
|
-
'The :remote option for Git.clone is deprecated and will be removed in v6.0.0. ' \
|
|
807
|
-
'Use :origin instead.'
|
|
808
|
-
)
|
|
809
|
-
end
|
|
810
|
-
opts[:origin] = opts.delete(:remote)
|
|
1045
|
+
def bare_clone?(stderr)
|
|
1046
|
+
stderr.match?(/^Cloning into bare repository '/)
|
|
811
1047
|
end
|
|
812
1048
|
end
|
|
813
1049
|
end
|