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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +6 -0
  3. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
  4. data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
  5. data/.github/skills/extract-facade-from-base-lib/SKILL.md +28 -14
  6. data/.github/skills/facade-implementation/SKILL.md +14 -0
  7. data/.github/skills/facade-test-conventions/SKILL.md +14 -0
  8. data/.rubocop.yml +5 -0
  9. data/README.md +51 -11
  10. data/UPGRADING.md +141 -0
  11. data/git.gemspec +5 -0
  12. data/lib/git/branch.rb +7 -18
  13. data/lib/git/branches.rb +2 -10
  14. data/lib/git/command_line/base.rb +10 -0
  15. data/lib/git/command_line/capturing.rb +5 -3
  16. data/lib/git/command_line/streaming.rb +5 -3
  17. data/lib/git/command_line.rb +3 -3
  18. data/lib/git/commands/base.rb +7 -6
  19. data/lib/git/commands/cat_file/batch.rb +6 -1
  20. data/lib/git/commands/cat_file/raw.rb +7 -1
  21. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +5 -0
  22. data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
  23. data/lib/git/commands/update_ref/batch.rb +1 -1
  24. data/lib/git/commands/version.rb +5 -0
  25. data/lib/git/commands.rb +5 -7
  26. data/lib/git/config.rb +17 -0
  27. data/lib/git/config_entry_info.rb +106 -0
  28. data/lib/git/configuring.rb +665 -0
  29. data/lib/git/deprecation.rb +9 -0
  30. data/lib/git/diff.rb +4 -8
  31. data/lib/git/diff_path_status.rb +2 -13
  32. data/lib/git/diff_stats.rb +1 -9
  33. data/lib/git/execution_context/global.rb +3 -28
  34. data/lib/git/execution_context/repository.rb +30 -41
  35. data/lib/git/execution_context.rb +43 -24
  36. data/lib/git/log.rb +3 -9
  37. data/lib/git/object.rb +14 -21
  38. data/lib/git/parsers/config_entry.rb +110 -0
  39. data/lib/git/parsers/ls_remote.rb +79 -0
  40. data/lib/git/remote.rb +7 -20
  41. data/lib/git/repository/branching.rb +183 -12
  42. data/lib/git/repository/committing.rb +64 -68
  43. data/lib/git/repository/configuring.rb +208 -13
  44. data/lib/git/repository/context_helpers.rb +264 -0
  45. data/lib/git/repository/factories.rb +682 -0
  46. data/lib/git/repository/inspecting.rb +99 -0
  47. data/lib/git/repository/maintenance.rb +65 -0
  48. data/lib/git/repository/merging.rb +63 -1
  49. data/lib/git/repository/object_operations.rb +133 -35
  50. data/lib/git/repository/path_resolver.rb +1 -1
  51. data/lib/git/repository/remote_operations.rb +166 -21
  52. data/lib/git/repository/staging.rb +187 -23
  53. data/lib/git/repository/stashing.rb +39 -3
  54. data/lib/git/repository/status_operations.rb +21 -0
  55. data/lib/git/repository.rb +68 -129
  56. data/lib/git/stash.rb +2 -9
  57. data/lib/git/stashes.rb +2 -7
  58. data/lib/git/status.rb +8 -17
  59. data/lib/git/version.rb +2 -2
  60. data/lib/git/worktree.rb +2 -15
  61. data/lib/git/worktrees.rb +2 -15
  62. data/lib/git.rb +180 -77
  63. data/redesign/3_architecture_implementation.md +148 -111
  64. data/redesign/Phase 4 - Step A.md +360 -0
  65. data/redesign/beta_release.md +107 -0
  66. data/redesign/c1c2_audit.md +566 -0
  67. data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
  68. data/redesign/config_design.rb +501 -0
  69. metadata +19 -5
  70. data/lib/git/base.rb +0 -1204
  71. data/lib/git/lib.rb +0 -2855
data/lib/git/base.rb DELETED
@@ -1,1204 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'logger'
4
- require 'pathname'
5
- require 'git/repository/path_resolver'
6
-
7
- module Git
8
- # The main public interface for interacting with Git commands
9
- #
10
- # Instead of creating a Git::Base directly, obtain a Git::Base instance by
11
- # calling one of the follow {Git} class methods: {Git.open}, {Git.init},
12
- # {Git.clone}, or {Git.bare}.
13
- #
14
- # @api public
15
- #
16
- class Base
17
- # (see Git.bare)
18
- def self.bare(git_dir, options = {})
19
- paths = Git::Repository::PathResolver.resolve_paths(repository: git_dir, bare: true)
20
- new(options.merge(paths))
21
- end
22
-
23
- # (see Git.clone)
24
- def self.clone(repository_url, directory, options = {})
25
- lib_options = {}
26
- lib_options[:git_ssh] = options[:git_ssh] if options.key?(:git_ssh)
27
- clone_result = Git::Lib.new(lib_options, options[:log]).clone(repository_url, directory, options)
28
- bare = options[:bare] || options[:mirror]
29
- paths = Git::Repository::PathResolver.resolve_paths(
30
- working_directory: clone_result[:working_directory],
31
- repository: clone_result[:repository],
32
- bare: bare
33
- )
34
- new(options.merge(paths))
35
- end
36
-
37
- # (see Git.default_branch)
38
- def self.repository_default_branch(repository, options = {})
39
- Git::Lib.new(nil, options[:log]).repository_default_branch(repository)
40
- end
41
-
42
- # Returns (and initialize if needed) a Git::Config instance
43
- #
44
- # @return [Git::Config] the current config instance.
45
- def self.config
46
- @config ||= Config.new
47
- end
48
-
49
- # @deprecated Use {Git.git_version} instead, which returns a {Git::Version} (not an Array).
50
- # For the legacy array shape, call: `Git.git_version.to_a`
51
- #
52
- def self.binary_version(binary_path)
53
- Git::Deprecation.warn(
54
- 'Git::Base.binary_version is deprecated and will be removed in 6.0. ' \
55
- 'Use Git.git_version instead, which returns a Git::Version ' \
56
- '(not an Array). For the legacy array shape, call: Git.git_version.to_a'
57
- )
58
- Git.git_version(binary_path).to_a
59
- end
60
-
61
- # Find the root of the working tree that contains `working_dir`
62
- #
63
- # Delegates to {Git::Repository::PathResolver.root_of_worktree}, using the
64
- # global config for `binary_path` and `git_ssh`.
65
- #
66
- # @param working_dir [String] a path inside the working tree
67
- #
68
- # @return [String] the absolute path to the root of the working tree
69
- #
70
- # @raise [ArgumentError] if `working_dir` does not exist or is not inside a
71
- # git working tree
72
- #
73
- def self.root_of_worktree(working_dir)
74
- Git::Repository::PathResolver.root_of_worktree(working_dir)
75
- end
76
-
77
- # (see Git.open)
78
- def self.open(working_dir, options = {})
79
- raise ArgumentError, "'#{working_dir}' is not a directory" unless Dir.exist?(working_dir)
80
-
81
- working_dir = root_of_worktree(working_dir) unless options[:repository]
82
-
83
- paths = Git::Repository::PathResolver.resolve_paths(
84
- working_directory: working_dir,
85
- repository: options[:repository],
86
- index: options[:index]
87
- )
88
-
89
- new(options.merge(paths))
90
- end
91
-
92
- # Create an object that executes Git commands in the context of a working
93
- # copy or a bare repository.
94
- #
95
- # @param [Hash] options The options for this command (see list of valid
96
- # options below)
97
- #
98
- # @option options [Pathname] :working_directory the path to the root of the working
99
- # directory or `nil` if executing commands on a bare repository
100
- #
101
- # @option options [Pathname] :repository used to specify a non-standard path to
102
- # the repository directory
103
- #
104
- # The default is `"<working_directory>/.git"`.
105
- #
106
- # @option options [Pathname] :index used to specify a non-standard path to an
107
- # index file
108
- #
109
- # The default is `"<working_directory>/.git/index"`
110
- #
111
- # @option options [Logger] :log A logger to use for Git operations
112
- #
113
- # Git commands are logged at the `:info` level. Additional logging is done
114
- # at the `:debug` level.
115
- #
116
- # @option options [String, nil] :git_ssh Path to a custom SSH executable or script
117
- #
118
- # Controls how SSH is configured for this {Git::Base} instance:
119
- # - If this option is not provided, the global Git::Base.config.git_ssh setting is used.
120
- # - If this option is explicitly set to nil, SSH is disabled for this instance.
121
- # - If this option is a non-empty String, that value is used as the SSH command for
122
- # this instance, overriding the global Git::Base.config.git_ssh setting.
123
- #
124
- # @option options [String, :use_global_config] :binary_path Path to the git binary
125
- #
126
- # Controls which git binary is used for commands routed through
127
- # {Git::ExecutionContext} (i.e., commands already migrated to
128
- # +Git::Commands::*+ classes). Commands still delegating through +Git::Lib+
129
- # continue to use the global `Git::Base.config.binary_path` setting.
130
- #
131
- # This limitation will be resolved when the architectural migration to
132
- # +Git::Repository+ is complete.
133
- #
134
- # - If this option is not provided, the global Git::Base.config.binary_path setting is used.
135
- # - If this option is a String, that value is used as the git binary path for
136
- # migrated commands, overriding the global Git::Base.config.binary_path setting.
137
- # - Passing `nil` raises ArgumentError — there is no "unset the binary" semantic.
138
- #
139
- # @return [Git::Base] an object that can execute git commands on a working copy or
140
- # bare repository
141
- #
142
- # @raise [ArgumentError] if `binary_path` is `nil`
143
- #
144
- def initialize(options = {})
145
- setup_logger(options[:log])
146
- @git_ssh = options.key?(:git_ssh) ? options[:git_ssh] : :use_global_config
147
- if options.key?(:binary_path)
148
- raise ArgumentError, 'binary_path must not be nil' if options[:binary_path].nil?
149
-
150
- @binary_path = options[:binary_path]
151
- else
152
- @binary_path = :use_global_config
153
- end
154
- initialize_components(options)
155
- end
156
-
157
- # Update the index from the current worktree to prepare for the next commit
158
- #
159
- # @overload add(paths = '.', **options)
160
- #
161
- # @example Stage all changed files
162
- # git.add
163
- #
164
- # @example Stage a specific file
165
- # git.add('path/to/file.rb')
166
- #
167
- # @example Stage multiple files
168
- # git.add(['path/to/file1.rb', 'path/to/file2.rb'])
169
- #
170
- # @example Stage all changes including deletions
171
- # git.add(all: true)
172
- #
173
- # @param paths [String, Array<String>] a file or files to add (relative to
174
- # the worktree root); defaults to `'.'` (all files)
175
- #
176
- # @param options [Hash] options for the add command
177
- #
178
- # @option options [Boolean, nil] :all (nil) add, modify, and remove index
179
- # entries to match the worktree
180
- #
181
- # @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
182
- # files
183
- #
184
- # @return [String] git's stdout from the add
185
- #
186
- # @raise [ArgumentError] if unsupported options are provided
187
- #
188
- # @raise [Git::FailedError] if git exits with a non-zero exit status
189
- #
190
- def add(paths = '.', **)
191
- facade_repository.add(paths, **)
192
- end
193
-
194
- # adds a new remote to this repository
195
- # url can be a git url or a Git::Base object if it's a local reference
196
- #
197
- # @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
198
- # @git.fetch('scotts_git')
199
- # @git.merge('scotts_git/master')
200
- #
201
- # Options:
202
- # :fetch => true
203
- # :track => <branch_name>
204
- def add_remote(name, url, opts = {})
205
- facade_repository.add_remote(name, url, opts)
206
- end
207
-
208
- # changes current working directory for a block
209
- # to the git working directory
210
- #
211
- # example
212
- # @git.chdir do
213
- # # write files
214
- # @git.add
215
- # @git.commit('message')
216
- # end
217
- def chdir # :yields: the working directory Pathname
218
- Dir.chdir(dir.to_s) do
219
- yield dir
220
- end
221
- end
222
-
223
- # g.config('user.name', 'Scott Chacon') # sets value
224
- # g.config('user.email', 'email@email.com') # sets value
225
- # g.config('user.email', 'email@email.com', file: 'path/to/custom/config') # sets value in file
226
- # g.config('user.name') # returns 'Scott Chacon'
227
- # g.config # returns whole config hash
228
- def config(name = nil, value = nil, options = {})
229
- facade_repository.config(name, value, options)
230
- end
231
-
232
- # Returns a reference to the working directory
233
- #
234
- # @example
235
- # @git.dir.to_s
236
- # @git.dir.writable?
237
- #
238
- # @return [Pathname] the working directory path
239
- #
240
- def dir
241
- @working_directory
242
- end
243
-
244
- # Returns a reference to the git index file
245
- #
246
- # @return [Pathname] the index file path
247
- #
248
- attr_reader :index
249
-
250
- # Returns a reference to the git repository directory
251
- #
252
- # @example
253
- # @git.repo.to_s
254
- #
255
- # @return [Pathname] the repository directory path
256
- #
257
- def repo
258
- @repository
259
- end
260
-
261
- # returns the repository size in bytes
262
- def repo_size
263
- all_files = Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH)
264
-
265
- all_files.reject { |file| file.include?('..') }
266
- .map { |file| File.expand_path(file) }
267
- .uniq
268
- .sum { |file| File.stat(file).size.to_i }
269
- end
270
-
271
- private
272
-
273
- def deprecate_check_argument(check, must_exist)
274
- unless check.nil?
275
- Git::Deprecation.warn(
276
- 'The "check" argument is deprecated and will be removed in a future version. ' \
277
- 'Use "must_exist:" instead.'
278
- )
279
- end
280
- # default is true
281
- must_exist.nil? && check.nil? ? true : must_exist | check
282
- end
283
-
284
- def validate_path(path, must_exist)
285
- Pathname.new(File.expand_path(path.to_s)).tap do |expanded_path|
286
- raise ArgumentError, "path does not exist: #{expanded_path}" if must_exist && !expanded_path.exist?
287
- end
288
- end
289
-
290
- public
291
-
292
- def set_index(index_file, check = nil, must_exist: nil)
293
- must_exist = deprecate_check_argument(check, must_exist)
294
- @lib = nil
295
- @facade_repository = nil
296
- @index = validate_path(index_file, must_exist)
297
- end
298
-
299
- def set_working(work_dir, check = nil, must_exist: nil)
300
- must_exist = deprecate_check_argument(check, must_exist)
301
- @lib = nil
302
- @facade_repository = nil
303
- @working_directory = validate_path(work_dir, must_exist)
304
- end
305
-
306
- # returns +true+ if the branch exists locally
307
- def local_branch?(branch)
308
- facade_repository.local_branch?(branch)
309
- end
310
-
311
- def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
312
- Git::Deprecation.warn(
313
- 'Git::Base#is_local_branch? is deprecated and will be removed in a future version. ' \
314
- 'Use Git::Base#local_branch? instead.'
315
- )
316
- local_branch?(branch)
317
- end
318
-
319
- # returns +true+ if the branch exists remotely
320
- def remote_branch?(branch)
321
- facade_repository.remote_branch?(branch)
322
- end
323
-
324
- def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
325
- Git::Deprecation.warn(
326
- 'Git::Base#is_remote_branch? is deprecated and will be removed in a future version. ' \
327
- 'Use Git::Base#remote_branch? instead.'
328
- )
329
- remote_branch?(branch)
330
- end
331
-
332
- # returns +true+ if the branch exists
333
- def branch?(branch)
334
- facade_repository.branch?(branch)
335
- end
336
-
337
- def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
338
- Git::Deprecation.warn(
339
- 'Git::Base#is_branch? is deprecated and will be removed in a future version. ' \
340
- 'Use Git::Base#branch? instead.'
341
- )
342
- branch?(branch)
343
- end
344
-
345
- # this is a convenience method for accessing the class that wraps all the
346
- # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
347
- # class with one that uses native methods or libgit C bindings
348
- def lib
349
- @lib ||= Git::Lib.new(self, @logger)
350
- end
351
-
352
- # Returns the {Git::Repository} facade for this repository
353
- #
354
- # @return [Git::Repository]
355
- # @api private
356
- def facade_repository
357
- @facade_repository ||= Git::Repository.new(
358
- execution_context: Git::ExecutionContext::Repository.from_base(self, logger: @logger)
359
- )
360
- end
361
-
362
- # Returns the per-instance git_ssh configuration value
363
- #
364
- # This may be:
365
- # * a [String] path when an explicit git_ssh command has been configured
366
- # * the Symbol `:use_global_config` when this instance is using the global config
367
- # * `nil` when SSH has been explicitly disabled for this instance
368
- #
369
- # @return [String, Symbol, nil] the git_ssh configuration value for this instance
370
- # @api private
371
- attr_reader :git_ssh
372
-
373
- # Returns the per-instance git binary path configuration value
374
- #
375
- # This may be:
376
- # * a [String] path when an explicit binary path has been configured
377
- # * the Symbol `:use_global_config` when this instance is using the global config
378
- #
379
- # @return [String, Symbol] the binary_path configuration value for this instance
380
- # @api private
381
- attr_reader :binary_path
382
-
383
- # Run a grep for 'string' on the HEAD of the git repository
384
- #
385
- # @example Limit grep's scope by calling grep() from a specific object:
386
- # git.object("v2.3").grep('TODO')
387
- #
388
- # @example Using grep results:
389
- # git.grep("TODO").each do |sha, arr|
390
- # puts "in blob #{sha}:"
391
- # arr.each do |line_no, match_string|
392
- # puts "\t line #{line_no}: '#{match_string}'"
393
- # end
394
- # end
395
- #
396
- # @param string [String] the string to search for
397
- # @param path_limiter [String, Pathname, Array<String, Pathname>] a path or array
398
- # of paths to limit the search to or nil for no limit
399
- # @param opts [Hash] options to pass to the underlying `git grep` command
400
- #
401
- # @option opts [Boolean, nil] :ignore_case (nil) ignore case when matching
402
- # @option opts [Boolean, nil] :invert_match (nil) select non-matching lines
403
- # @option opts [Boolean, nil] :extended_regexp (nil) use extended regular expressions
404
- # @option opts [String] :object (HEAD) the object to search from
405
- #
406
- # @return [Hash<String, Array>] a hash of arrays
407
- # ```Ruby
408
- # {
409
- # 'tree-ish1' => [[line_no1, match_string1], ...],
410
- # 'tree-ish2' => [[line_no1, match_string1], ...],
411
- # ...
412
- # }
413
- # ```
414
- #
415
- def grep(string, path_limiter = nil, opts = {})
416
- opts = opts.merge(object: facade_repository.rev_parse('HEAD')) unless opts.key?(:object)
417
- facade_repository.grep(string, path_limiter, opts)
418
- end
419
-
420
- # List the files in the worktree that are ignored by git
421
- # @return [Array<String>] the list of ignored files relative to the root of the worktree
422
- #
423
- def ignored_files
424
- facade_repository.ignored_files
425
- end
426
-
427
- # removes file(s) from the git repository
428
- def rm(path = '.', opts = {})
429
- facade_repository.rm(path, opts)
430
- end
431
-
432
- alias remove rm
433
-
434
- # resets the working directory to the provided commitish
435
- def reset(commitish = nil, opts = {})
436
- facade_repository.reset(commitish, **opts)
437
- end
438
-
439
- # resets the working directory to the commitish with '--hard'
440
- #
441
- # @deprecated Use {#reset} with `hard: true` instead.
442
- #
443
- def reset_hard(commitish = nil, opts = {})
444
- Git::Deprecation.warn(
445
- 'Git::Base#reset_hard is deprecated and will be removed in a future version. ' \
446
- 'Use Git::Base#reset(commitish, hard: true) instead.'
447
- )
448
- opts = { hard: true }.merge(opts)
449
- lib.reset(commitish, opts)
450
- end
451
-
452
- # cleans the working directory
453
- #
454
- # options:
455
- # :force
456
- # :d
457
- # :ff
458
- #
459
- def clean(opts = {})
460
- facade_repository.clean(opts)
461
- end
462
-
463
- # returns the most recent tag that is reachable from a commit
464
- #
465
- # options:
466
- # :all
467
- # :tags
468
- # :contains
469
- # :debug
470
- # :exact_match
471
- # :dirty
472
- # :abbrev
473
- # :candidates
474
- # :long
475
- # :always
476
- # :match
477
- #
478
- def describe(committish = nil, opts = {})
479
- lib.describe(committish, opts)
480
- end
481
-
482
- # reverts the working directory to the provided commitish.
483
- # Accepts a range, such as comittish..HEAD
484
- #
485
- # options:
486
- # :no_edit
487
- #
488
- def revert(commitish = nil, opts = {})
489
- facade_repository.revert(commitish, **opts)
490
- end
491
-
492
- # commits all pending changes in the index file to the git repository
493
- #
494
- # options:
495
- # :all
496
- # :allow_empty
497
- # :amend
498
- # :author
499
- #
500
- def commit(message, opts = {})
501
- facade_repository.commit(message, **opts)
502
- end
503
-
504
- # commits all pending changes in the index file to the git repository,
505
- # but automatically adds all modified files without having to explicitly
506
- # calling @git.add() on them.
507
- def commit_all(message, opts = {})
508
- facade_repository.commit_all(message, **opts)
509
- end
510
-
511
- # checks out a branch as the new git working directory
512
- def checkout(*, **)
513
- facade_repository.checkout(*, **)
514
- end
515
-
516
- # checks out an old version of a file
517
- def checkout_file(version, file)
518
- facade_repository.checkout_file(version, file)
519
- end
520
-
521
- # fetches changes from a remote branch - this does not modify the working directory,
522
- # it just gets the changes from the remote if there are any
523
- def fetch(remote = 'origin', opts = {})
524
- facade_repository.fetch(remote, opts)
525
- end
526
-
527
- # Push changes to a remote repository
528
- #
529
- # @overload push(remote = nil, branch = nil, options = {})
530
- #
531
- # @param remote [String] the remote repository to push to
532
- #
533
- # @param branch [String] the branch to push
534
- #
535
- # @param options [Hash] options to pass to the push command
536
- #
537
- # @option options [Boolean, nil] :mirror (nil) push all refs under refs/heads/, refs/tags/ and refs/remotes/
538
- #
539
- # @option options [Boolean, nil] :delete (nil) delete refs that don't exist on the remote
540
- #
541
- # @option options [Boolean, nil] :force (nil) force updates
542
- #
543
- # @option options [Boolean, nil] :tags (nil) push all refs under refs/tags/
544
- #
545
- # @option options [String, Array<String>] :push_option (nil) push options to transmit
546
- #
547
- # @return [String] the stdout output from the push command
548
- #
549
- # @raise [Git::FailedError] if the push fails
550
- # @raise [ArgumentError] if a branch is given without a remote
551
- #
552
- def push(*, **)
553
- facade_repository.push(*, **)
554
- end
555
-
556
- # merges one or more branches into the current working branch
557
- #
558
- # you can specify more than one branch to merge by passing an array of branches
559
- def merge(branch, message = 'merge', opts = {})
560
- facade_repository.merge(branch, message, opts)
561
- end
562
-
563
- # iterates over the files which are unmerged
564
- def each_conflict(&)
565
- facade_repository.each_conflict(&)
566
- end
567
-
568
- # Pulls the given branch from the given remote into the current branch
569
- #
570
- # @param remote [String] the remote repository to pull from
571
- # @param branch [String] the branch to pull from
572
- # @param opts [Hash] options to pass to the pull command
573
- #
574
- # @option opts [Boolean, nil] :allow_unrelated_histories (nil) merges histories of
575
- # two projects that started their lives independently
576
- # @example pulls from origin/master
577
- # @git.pull
578
- # @example pulls from upstream/master
579
- # @git.pull('upstream')
580
- # @example pulls from upstream/develop
581
- # @git.pull('upstream', 'develop')
582
- #
583
- # @return [String] the stdout output from the pull command
584
- #
585
- # @raise [Git::FailedError] if the pull fails
586
- # @raise [ArgumentError] if a branch is given without a remote
587
- def pull(remote = nil, branch = nil, opts = {})
588
- facade_repository.pull(remote, branch, opts)
589
- end
590
-
591
- # returns an array of Git:Remote objects
592
- def remotes
593
- facade_repository.remotes
594
- end
595
-
596
- # sets the url for a remote
597
- # url can be a git url or a Git::Base object if it's a local reference
598
- #
599
- # @git.set_remote_url('scotts_git', 'git://repo.or.cz/rubygit.git')
600
- #
601
- def set_remote_url(name, url)
602
- facade_repository.set_remote_url(name, url)
603
- end
604
-
605
- # Configures which branches are fetched for a remote
606
- #
607
- # Uses `git remote set-branches` to set or append fetch refspecs. When the `add:`
608
- # option is not given, the `--add` option is not passed to the git command
609
- #
610
- # @example Replace fetched branches with a single glob pattern
611
- # git = Git.open('/path/to/repo')
612
- # # Only fetch branches matching "feature/*" from origin
613
- # git.remote_set_branches('origin', 'feature/*')
614
- #
615
- # @example Append a glob pattern to existing fetched branches
616
- # git = Git.open('/path/to/repo')
617
- # # Keep existing fetch refspecs and add all release branches
618
- # git.remote_set_branches('origin', 'release/*', add: true)
619
- #
620
- # @example Configure multiple explicit branches
621
- # git = Git.open('/path/to/repo')
622
- # git.remote_set_branches('origin', 'main', 'development', 'hotfix')
623
- #
624
- # @param name [String] the remote name (for example, "origin")
625
- # @param branches [Array<String>] branch names or globs (for example, '*')
626
- # @param add [Boolean] when true, append to existing refspecs instead of replacing them
627
- #
628
- # @return [nil]
629
- #
630
- # @raise [ArgumentError] if no branches are provided @raise [Git::FailedError] if
631
- # the underlying git command fails
632
- #
633
- def remote_set_branches(name, *branches, add: false)
634
- facade_repository.remote_set_branches(name, *branches, add: add)
635
- end
636
-
637
- # removes a remote from this repository
638
- #
639
- # @git.remove_remote('scott_git')
640
- def remove_remote(name)
641
- facade_repository.remove_remote(name)
642
- end
643
-
644
- # returns an array of all Git::Object::Tag objects for this repository
645
- def tags
646
- facade_repository.tags
647
- end
648
-
649
- # Create a new git tag
650
- #
651
- # @example
652
- # repo.add_tag('tag_name', object_reference)
653
- # repo.add_tag('tag_name', object_reference, {:options => 'here'})
654
- # repo.add_tag('tag_name', {:options => 'here'})
655
- #
656
- # @param [String] name The name of the tag to add
657
- # @param [Hash] options Options to pass to `git tag`.
658
- # See [git-tag](https://git-scm.com/docs/git-tag) for more details.
659
- # @option options [Boolean, nil] :annotate (nil) make an unsigned, annotated tag object
660
- # @option options [Boolean, nil] :a (nil) an alias for the `:annotate` option
661
- # @option options [Boolean, nil] :d (nil) delete existing tag with the given name —
662
- # deprecated; use {#delete_tag} instead (alias: `:delete`)
663
- # @option options [Boolean, nil] :delete (nil) delete existing tag with the given name —
664
- # deprecated; use {#delete_tag} instead (alias: `:d`)
665
- # @option options [Boolean, nil] :f (nil) replace an existing tag with the given name (instead of failing)
666
- # @option options [String] :message Use the given tag message
667
- # @option options [String] :m An alias for the `:message` option
668
- # @option options [Boolean, nil] :s (nil) make a GPG-signed tag
669
- #
670
- def add_tag(name, *options)
671
- facade_repository.add_tag(name, *options)
672
- end
673
-
674
- # deletes a tag
675
- def delete_tag(name)
676
- facade_repository.delete_tag(name)
677
- end
678
-
679
- # Creates an archive of the given tree-ish and writes it to a file
680
- #
681
- # @api public
682
- #
683
- # @param treeish [String] the commit, tag, branch, or tree to archive
684
- #
685
- # @param file [String, nil] destination file path; a temp file is created
686
- # if `nil`
687
- #
688
- # @param opts [Hash] archive options (see {Git::Repository::ObjectOperations#archive})
689
- #
690
- # @return [String] the path to the written archive file
691
- #
692
- # @raise [ArgumentError] if unsupported options are provided
693
- #
694
- # @raise [ArgumentError] if `file` is an existing directory
695
- #
696
- # @raise [Git::FailedError] if git exits with a non-zero exit status
697
- #
698
- # @example Archive HEAD to a zip file
699
- # git.archive('HEAD', '/tmp/release.zip', format: 'zip')
700
- # #=> "/tmp/release.zip"
701
- #
702
- def archive(treeish, file = nil, opts = {})
703
- facade_repository.archive(treeish, file, opts)
704
- end
705
-
706
- # repacks the repository
707
- def repack
708
- lib.repack
709
- end
710
-
711
- def gc
712
- lib.gc
713
- end
714
-
715
- # Verifies the connectivity and validity of objects in the database
716
- #
717
- # Runs `git fsck` to check repository integrity and identify dangling,
718
- # missing, or unreachable objects.
719
- #
720
- # @overload fsck(objects = [], options = {})
721
- # @param objects [Array<String>] specific objects to treat as heads for unreachability trace.
722
- # If no objects are given, git fsck defaults to using the index file, all SHA-1
723
- # references in the refs namespace, and all reflogs.
724
- # @param [Hash] options options to pass to the underlying `git fsck` command
725
- #
726
- # @option options [Boolean, nil] :unreachable (nil) print unreachable objects
727
- # @option options [Boolean, nil] :strict (nil) enable strict checking
728
- # @option options [Boolean, nil] :connectivity_only (nil) check only connectivity (faster)
729
- # @option options [Boolean, nil] :root (nil) report root nodes
730
- # @option options [Boolean, nil] :tags (nil) report tags
731
- # @option options [Boolean, nil] :cache (nil) consider objects in the index
732
- # @option options [Boolean, nil] :no_reflogs (nil) do not consider reflogs
733
- # @option options [Boolean, nil] :lost_found (nil) write dangling objects to .git/lost-found
734
- # (note: this modifies the repository by creating files)
735
- # @option options [Boolean, nil] :dangling print dangling objects (true/false/nil for default)
736
- # @option options [Boolean, nil] :full check objects in alternate pools (true/false/nil for default)
737
- # @option options [Boolean, nil] :name_objects name objects by refs (true/false/nil for default)
738
- # @option options [Boolean, nil] :references check refs database consistency (true/false/nil for default)
739
- #
740
- # @return [Git::FsckResult] categorized objects flagged by fsck
741
- #
742
- # @example Check repository integrity
743
- # result = git.fsck
744
- # result.dangling.each { |obj| puts "#{obj.type}: #{obj.oid}" }
745
- #
746
- # @example Check with strict mode and suppress dangling output
747
- # result = git.fsck(strict: true, no_dangling: true)
748
- #
749
- # @example Check if repository has any issues
750
- # result = git.fsck
751
- # puts "Repository is clean" if result.empty?
752
- #
753
- # @example List root commits
754
- # result = git.fsck(root: true)
755
- # result.root.each { |obj| puts obj.oid }
756
- #
757
- # @example Check specific objects
758
- # result = git.fsck('abc1234', 'def5678')
759
- #
760
- # rubocop:disable Style/ArgumentsForwarding
761
- def fsck(*objects, **opts)
762
- facade_repository.fsck(*objects, **opts)
763
- end
764
- # rubocop:enable Style/ArgumentsForwarding
765
-
766
- def apply(file)
767
- return unless File.exist?(file)
768
-
769
- lib.apply(file)
770
- end
771
-
772
- def apply_mail(file)
773
- lib.apply_mail(file) if File.exist?(file)
774
- end
775
-
776
- # Shows objects
777
- #
778
- # @param [String|NilClass] objectish the target object reference (nil == HEAD)
779
- # @param [String|NilClass] path the path of the file to be shown
780
- # @return [String] the object information
781
- def show(objectish = nil, path = nil)
782
- facade_repository.show(objectish, path)
783
- end
784
-
785
- ## LOWER LEVEL INDEX OPERATIONS ##
786
-
787
- def with_index(new_index) # :yields: new_index
788
- old_index = @index
789
- set_index(new_index, false)
790
- return_value = yield @index
791
- set_index(old_index)
792
- return_value
793
- end
794
-
795
- def with_temp_index(&)
796
- # Workaround for JRUBY, since they handle the TempFile path different.
797
- # MUST be improved to be safer and OS independent.
798
- if RUBY_PLATFORM == 'java'
799
- temp_path = "/tmp/temp-index-#{(0...15).map { ('a'..'z').to_a[rand(26)] }.join}"
800
- else
801
- tempfile = Tempfile.new('temp-index')
802
- temp_path = tempfile.path
803
- tempfile.close
804
- tempfile.unlink
805
- end
806
-
807
- with_index(temp_path, &)
808
- end
809
-
810
- def checkout_index(opts = {})
811
- facade_repository.checkout_index(opts)
812
- end
813
-
814
- def read_tree(treeish, opts = {})
815
- lib.read_tree(treeish, opts)
816
- end
817
-
818
- def write_tree
819
- facade_repository.write_tree
820
- end
821
-
822
- def write_and_commit_tree(opts = {})
823
- Git::Object::Commit.new(self, facade_repository.write_and_commit_tree(**opts))
824
- end
825
-
826
- def update_ref(branch, commit)
827
- facade_repository.update_ref(branch, commit)
828
- end
829
-
830
- def ls_files(location = nil)
831
- facade_repository.ls_files(location)
832
- end
833
-
834
- def with_working(work_dir) # :yields: the working directory Pathname
835
- return_value = false
836
- old_working = @working_directory
837
- set_working(work_dir)
838
- Dir.chdir work_dir do
839
- return_value = yield @working_directory
840
- end
841
- set_working(old_working)
842
- return_value
843
- end
844
-
845
- def with_temp_working(&)
846
- tempfile = Tempfile.new('temp-workdir')
847
- temp_dir = tempfile.path
848
- tempfile.close
849
- tempfile.unlink
850
- Dir.mkdir(temp_dir, 0o700)
851
- with_working(temp_dir, &)
852
- end
853
-
854
- # runs git rev-parse to convert the objectish to a full sha
855
- #
856
- # @example
857
- # git.rev_parse("HEAD^^")
858
- # git.rev_parse('v2.4^{tree}')
859
- # git.rev_parse('v2.4:/doc/index.html')
860
- #
861
- def rev_parse(objectish)
862
- facade_repository.rev_parse(objectish)
863
- end
864
-
865
- # For backwards compatibility
866
- alias revparse rev_parse
867
-
868
- # Returns the number of entries in a git tree object
869
- #
870
- # @example Count recursive entries in the HEAD tree
871
- # git.tree_depth('HEAD^{tree}') #=> 42
872
- #
873
- # @param objectish [String] the tree-ish object to recurse into
874
- #
875
- # @return [Integer] the number of entries in the recursive tree listing
876
- #
877
- # @raise [Git::FailedError] when git exits with a non-zero exit status
878
- #
879
- # @see Git::Repository::ObjectOperations#tree_depth
880
- #
881
- def tree_depth(objectish)
882
- facade_repository.tree_depth(objectish)
883
- end
884
-
885
- # Lists the objects in a git tree object
886
- #
887
- # @example List all top-level objects
888
- # git.ls_tree('HEAD')
889
- # # => { 'blob' => { 'README.md' => { mode: '100644', sha: '...' } }, ... }
890
- #
891
- # @param objectish [String] the tree-ish object to list
892
- #
893
- # @param opts [Hash] additional options
894
- #
895
- # @option opts [Boolean, nil] :recursive (nil) recurse into subtrees
896
- #
897
- # @option opts [String, Array<String>] :path (nil) limit the listing to
898
- # the given path or array of paths
899
- #
900
- # @return [Hash<String, Hash<String, Hash>>] a three-level Hash keyed by
901
- # object type (`'blob'`, `'tree'`, `'commit'`), then by filename, then
902
- # holding `:mode` and `:sha` values
903
- #
904
- # @raise [ArgumentError] when unsupported options are provided
905
- #
906
- # @raise [Git::FailedError] when git exits with a non-zero exit status
907
- #
908
- def ls_tree(objectish, opts = {})
909
- facade_repository.ls_tree(objectish, opts)
910
- end
911
-
912
- def cat_file(objectish)
913
- lib.cat_file(objectish)
914
- end
915
-
916
- # The name of the branch HEAD refers to or 'HEAD' if detached
917
- #
918
- # Returns one of the following:
919
- # * The branch name that HEAD refers to (even if it is an unborn branch)
920
- # * 'HEAD' if in a detached HEAD state
921
- #
922
- # @return [String] the name of the branch HEAD refers to or 'HEAD' if detached
923
- #
924
- def current_branch
925
- facade_repository.current_branch
926
- end
927
-
928
- # @return [Git::Branch] an object for branch_name
929
- def branch(branch_name = current_branch)
930
- facade_repository.branch(branch_name)
931
- end
932
-
933
- # @return [Git::Branches] a collection of all the branches in the repository.
934
- # Each branch is represented as a {Git::Branch}.
935
- def branches
936
- facade_repository.branches
937
- end
938
-
939
- # Returns a {Git::Worktree} object for the given path and optional commitish
940
- #
941
- # @example Create a worktree object for an existing path
942
- # worktree = repo.worktree('/path/to/worktree')
943
- #
944
- # @param dir [String] filesystem path of the worktree
945
- #
946
- # @param commitish [String, nil] branch, tag, or commit to check out
947
- #
948
- # @return [Git::Worktree] worktree object for the given path
949
- #
950
- def worktree(dir, commitish = nil)
951
- facade_repository.worktree(dir, commitish)
952
- end
953
-
954
- # Returns a {Git::Worktrees} collection of all worktrees in the repository
955
- #
956
- # @example List paths for all worktrees
957
- # repo.worktrees.each { |wt| puts wt.dir }
958
- #
959
- # @return [Git::Worktrees] all linked and main worktrees
960
- #
961
- # @raise [Git::FailedError] if git exits with a non-zero exit status
962
- #
963
- def worktrees
964
- facade_repository.worktrees
965
- end
966
-
967
- # @return [Git::Object::Commit] a commit object
968
- def commit_tree(tree = nil, opts = {})
969
- Git::Object::Commit.new(self, facade_repository.commit_tree(tree, **opts))
970
- end
971
-
972
- # @return [Git::Diff] a Git::Diff object
973
- def diff(objectish = 'HEAD', obj2 = nil)
974
- facade_repository.diff(objectish, obj2)
975
- end
976
-
977
- # @return [Git::Object] a Git object
978
- def gblob(objectish)
979
- facade_repository.gblob(objectish)
980
- end
981
-
982
- # @return [Git::Object] a Git object
983
- def gcommit(objectish)
984
- facade_repository.gcommit(objectish)
985
- end
986
-
987
- # @return [Git::Object] a Git object
988
- def gtree(objectish)
989
- facade_repository.gtree(objectish)
990
- end
991
-
992
- # @return [Git::Log] a log with the specified number of commits
993
- def log(count = 30)
994
- facade_repository.log(count)
995
- end
996
-
997
- # Return commits that are within the given revision range
998
- #
999
- # @param opts [Hash] options for the log query
1000
- # @return [Array<Hash>] the parsed raw log output for each commit
1001
- def full_log_commits(opts = {})
1002
- facade_repository.full_log_commits(opts)
1003
- end
1004
-
1005
- # returns a Git::Object of the appropriate type
1006
- # you can also call @git.gtree('tree'), but that's
1007
- # just for readability. If you call @git.gtree('HEAD') it will
1008
- # still return a Git::Object::Commit object.
1009
- #
1010
- # object calls a method that will run a rev-parse
1011
- # on the objectish and determine the type of the object and return
1012
- # an appropriate object for that type
1013
- #
1014
- # @return [Git::Object] an instance of the appropriate type of Git::Object
1015
- def object(objectish)
1016
- facade_repository.object(objectish)
1017
- end
1018
-
1019
- # @return [Git::Remote] a remote of the specified name
1020
- def remote(remote_name = 'origin')
1021
- facade_repository.remote(remote_name)
1022
- end
1023
-
1024
- # @return [Git::Status] a status object
1025
- def status
1026
- facade_repository.status
1027
- end
1028
-
1029
- # @return [Git::Object::Tag] a tag object
1030
- def tag(tag_name)
1031
- facade_repository.tag(tag_name)
1032
- end
1033
-
1034
- # Find as good common ancestors as possible for a merge
1035
- # example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
1036
- #
1037
- # @return [Array<Git::Object::Commit>] a collection of common ancestors
1038
- def merge_base(*)
1039
- facade_repository.merge_base(*).map { |sha| gcommit(sha) }
1040
- end
1041
-
1042
- # Returns the full unified diff patch text between two commits
1043
- #
1044
- # @example Get the patch for the most recent commit
1045
- # repo.diff_full #=> "diff --git a/lib/foo.rb b/lib/foo.rb\n..."
1046
- #
1047
- # @param obj1 [String] the first commit or object to compare; defaults to
1048
- # `'HEAD'`
1049
- #
1050
- # @param obj2 [String, nil] the second commit or object to compare
1051
- #
1052
- # When `nil`, the comparison is against the index or working tree.
1053
- #
1054
- # @param opts [Hash] options to filter the diff
1055
- #
1056
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
1057
- # limit the diff to the given path(s)
1058
- #
1059
- # @return [String] the unified diff patch output
1060
- #
1061
- # @note Unknown option keys are silently ignored for backward compatibility;
1062
- # only `:path_limiter` is forwarded to the underlying command.
1063
- #
1064
- # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
1065
- #
1066
- # @see Git::Repository::Diffing#diff_full
1067
- #
1068
- def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
1069
- facade_repository.diff_full(obj1, obj2, opts.slice(:path_limiter))
1070
- end
1071
-
1072
- # Returns a lazy {Git::DiffStats} object for accessing diff statistics
1073
- #
1074
- # Compares (1) two commits, (2) a commit against the working tree, or (3) the
1075
- # index against the working tree and constructs a lazy {Git::DiffStats} that
1076
- # computes per-file insertion and deletion counts on demand when its accessor
1077
- # methods are called.
1078
- #
1079
- # **Comparing two commits**
1080
- #
1081
- # When both objectish and obj2 are provided, the comparison is between those two
1082
- # refs (commits, tags, branches, etc.).
1083
- #
1084
- # **Comparing a commit against the working tree**
1085
- #
1086
- # When only objectish is provided (and isn't nil), the comparison is between
1087
- # objectish and the working tree; the stats reflect all changes since objectish.
1088
- #
1089
- # **Comparing the index against the working tree**
1090
- #
1091
- # When objectish is explicitly `nil` then obj2 must be omitted or `nil`. In this
1092
- # case, the comparison is between the index and the working tree; the stats reflect
1093
- # unstaged changes.
1094
- #
1095
- # @example Get working tree stats since HEAD
1096
- # repo.diff_stats.insertions #=> 3
1097
- #
1098
- # @example Compare two specific commits
1099
- # repo.diff_stats('abc1234', 'def5678')
1100
- #
1101
- # @example Get unstaged stats (index vs. working tree)
1102
- # repo.diff_stats(nil).insertions
1103
- #
1104
- # @example Limit stats to a sub-path
1105
- # repo.diff_stats('HEAD~1', 'HEAD', path_limiter: 'lib/')
1106
- #
1107
- # @param objectish [String, nil] the first commit or object to compare; defaults to
1108
- # `'HEAD'`; pass `nil` to compare the index against the working tree
1109
- #
1110
- # @param obj2 [String, nil] the second commit or object to compare
1111
- #
1112
- # @param opts [Hash] options to filter the diff
1113
- #
1114
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
1115
- # limit the stats to the given path(s)
1116
- #
1117
- # @return [Git::DiffStats] a lazy stats object for the comparison
1118
- #
1119
- # @note Unknown option keys are silently ignored for backward compatibility;
1120
- # only `:path_limiter` is forwarded to the underlying command.
1121
- #
1122
- # @raise [ArgumentError] if `objectish` or `obj2` starts with `"-"`
1123
- #
1124
- # @raise [ArgumentError] if `objectish` is `nil` but `obj2` is not
1125
- #
1126
- # @see Git::Repository::Diffing#diff_stats
1127
- #
1128
- def diff_stats(objectish = 'HEAD', obj2 = nil, opts = {})
1129
- facade_repository.diff_stats(objectish, obj2, opts.slice(:path_limiter))
1130
- end
1131
-
1132
- # Returns the file path status between two commits
1133
- #
1134
- # @example Get all changed files between HEAD and the previous commit
1135
- # repo.diff_path_status.to_h #=> { "README.md" => "M", "lib/foo.rb" => "A" }
1136
- #
1137
- # @param objectish [String] the first commit or object to compare; defaults to
1138
- # `'HEAD'`
1139
- #
1140
- # @param obj2 [String, nil] the second commit or object to compare
1141
- #
1142
- # @param opts [Hash] options to filter the diff
1143
- #
1144
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
1145
- # limit the status report to specified path(s)
1146
- #
1147
- # @option opts [String, Pathname, Array<String, Pathname>, nil] :path (nil)
1148
- # deprecated; use `:path_limiter` instead
1149
- #
1150
- # @return [Git::DiffPathStatus] the name-status report for the comparison
1151
- #
1152
- # @raise [ArgumentError] if `objectish` or `obj2` starts with `"-"`
1153
- #
1154
- # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
1155
- #
1156
- # @see Git::Repository::Diffing#diff_path_status
1157
- #
1158
- def diff_path_status(objectish = 'HEAD', obj2 = nil, opts = {})
1159
- facade_repository.diff_path_status(objectish, obj2, opts.slice(:path_limiter, :path))
1160
- end
1161
-
1162
- # Compares the index and the working directory
1163
- #
1164
- # @example List all files with unstaged changes
1165
- # repo.diff_files #=> { "lib/foo.rb" => { mode_index: "100644", ... } }
1166
- #
1167
- # @return [Hash{String => Hash}] a hash keyed by file path; see
1168
- # {Git::Repository::Diffing#diff_files} for the full key list
1169
- #
1170
- # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
1171
- #
1172
- # @see Git::Repository::Diffing#diff_files
1173
- #
1174
- def diff_files
1175
- facade_repository.diff_files
1176
- end
1177
-
1178
- # Alias for {#diff_path_status}; provided for backward compatibility
1179
- #
1180
- # @return [Git::DiffPathStatus] the name-status report for the comparison
1181
- #
1182
- # @deprecated Use {#diff_path_status} instead
1183
- #
1184
- # @see #diff_path_status
1185
- alias diff_name_status diff_path_status
1186
-
1187
- private
1188
-
1189
- # Initializes the logger from the provided options
1190
- # @param log_option [Logger, nil] The logger instance from options.
1191
- def setup_logger(log_option)
1192
- @logger = log_option || Logger.new(nil)
1193
- @logger.info('Starting Git')
1194
- end
1195
-
1196
- # Initializes the core git objects based on the provided options
1197
- # @param options [Hash] The processed options hash.
1198
- def initialize_components(options)
1199
- @working_directory = Pathname.new(options[:working_directory]) if options[:working_directory]
1200
- @repository = Pathname.new(options[:repository]) if options[:repository]
1201
- @index = Pathname.new(options[:index]) if options[:index]
1202
- end
1203
- end
1204
- end