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
@@ -3,16 +3,20 @@
3
3
  require 'find'
4
4
  require 'pathname'
5
5
 
6
+ require 'git/configuring'
7
+ require 'git/deprecation'
6
8
  require 'git/execution_context/repository'
7
9
  require 'git/repository/branching'
10
+ require 'git/repository/context_helpers'
8
11
  require 'git/repository/committing'
9
12
  require 'git/repository/configuring'
10
13
  require 'git/repository/diffing'
14
+ require 'git/repository/factories'
11
15
  require 'git/repository/inspecting'
12
16
  require 'git/repository/logging'
17
+ require 'git/repository/maintenance'
13
18
  require 'git/repository/merging'
14
19
  require 'git/repository/object_operations'
15
- require 'git/repository/path_resolver'
16
20
  require 'git/repository/remote_operations'
17
21
  require 'git/repository/staging'
18
22
  require 'git/repository/stashing'
@@ -45,12 +49,17 @@ module Git
45
49
  # @api public
46
50
  #
47
51
  class Repository
52
+ extend Git::Repository::Factories
53
+
54
+ include Git::Configuring
48
55
  include Git::Repository::Branching
56
+ include Git::Repository::ContextHelpers
49
57
  include Git::Repository::Committing
50
58
  include Git::Repository::Configuring
51
59
  include Git::Repository::Diffing
52
60
  include Git::Repository::Inspecting
53
61
  include Git::Repository::Logging
62
+ include Git::Repository::Maintenance
54
63
  include Git::Repository::Merging
55
64
  include Git::Repository::ObjectOperations
56
65
  include Git::Repository::RemoteOperations
@@ -59,134 +68,6 @@ module Git
59
68
  include Git::Repository::StatusOperations
60
69
  include Git::Repository::WorktreeOperations
61
70
 
62
- # Open a working copy at an existing path
63
- #
64
- # The new repository factories are additive scaffolding introduced by the
65
- # architectural redesign. The top-level {Git.open} entry point still returns a
66
- # {Git::Base} object; this method exists so future work can route construction
67
- # through {Git::Repository} without changing the public entry points.
68
- #
69
- # Note: this method opens working copies only. To open a bare repository, use
70
- # {Git::Repository.bare}.
71
- #
72
- # @example Open the working copy in the current directory
73
- # repository = Git::Repository.open('.')
74
- #
75
- # @param working_dir [String] the path to the root of the working copy
76
- #
77
- # May be any path inside the working tree when `:repository` is not given.
78
- #
79
- # @param options [Hash] options that control how the repository is located
80
- #
81
- # @option options [String] :repository a non-standard path to the `.git`
82
- # directory
83
- #
84
- # When given, `working_dir` is used as-is (the working tree root is not
85
- # auto-detected).
86
- #
87
- # @option options [String] :index a non-standard path to the index file
88
- #
89
- # @option options [Logger] :log a logger forwarded to the command layer
90
- #
91
- # @option options [String, nil, :use_global_config] :git_ssh path to a custom SSH executable;
92
- # pass `:use_global_config` (the default) to use `Git::Base.config.git_ssh`
93
- #
94
- # @option options [String, :use_global_config] :binary_path path to the git binary;
95
- # pass `:use_global_config` (the default) to use `Git::Base.config.binary_path`
96
- #
97
- # @return [Git::Repository] a repository bound to the resolved paths
98
- #
99
- # @raise [ArgumentError] if `working_dir` is not a directory or is not inside
100
- # a git working tree
101
- #
102
- def self.open(working_dir, options = {})
103
- raise ArgumentError, "'#{working_dir}' is not a directory" unless Dir.exist?(working_dir)
104
-
105
- working_dir = resolve_open_working_dir(working_dir, options) unless options[:repository]
106
-
107
- paths = PathResolver.resolve_paths(
108
- working_directory: working_dir,
109
- repository: options[:repository],
110
- index: options[:index]
111
- )
112
-
113
- from_paths(options, paths)
114
- end
115
-
116
- # Open an existing bare repository at `git_dir`
117
- #
118
- # The new repository factories are additive scaffolding introduced by the
119
- # architectural redesign. The top-level {Git.bare} entry point still returns a
120
- # {Git::Base} object; this method exists so future work can route construction
121
- # through {Git::Repository} without changing the public entry points.
122
- #
123
- # @example Open a bare repository
124
- # repository = Git::Repository.bare('/path/to/repo.git')
125
- #
126
- # @param git_dir [String] the path to the bare repository directory
127
- #
128
- # @param options [Hash] options forwarded to the constructed repository
129
- #
130
- # @option options [Logger] :log a logger forwarded to the command layer
131
- #
132
- # @option options [String, nil, :use_global_config] :git_ssh path to a custom SSH executable;
133
- # pass `:use_global_config` (the default) to use `Git::Base.config.git_ssh`
134
- #
135
- # @option options [String, :use_global_config] :binary_path path to the git binary;
136
- # pass `:use_global_config` (the default) to use `Git::Base.config.binary_path`
137
- #
138
- # @return [Git::Repository] a repository bound to the bare repository directory
139
- #
140
- def self.bare(git_dir, options = {})
141
- paths = PathResolver.resolve_paths(repository: git_dir, bare: true)
142
-
143
- from_paths(options, paths)
144
- end
145
-
146
- # Resolve the worktree root to use as the working directory for `.open`
147
- #
148
- # Delegates to {PathResolver.root_of_worktree}, forwarding `:binary_path`
149
- # and `:git_ssh` from `options`.
150
- #
151
- # @param working_dir [String] a path inside the working tree
152
- #
153
- # @param options [Hash] the caller-supplied options hash from `.open`
154
- #
155
- # @return [String] the absolute path to the root of the working tree
156
- #
157
- # @raise [ArgumentError] if `working_dir` is not inside a git working tree
158
- #
159
- # @api private
160
- #
161
- def self.resolve_open_working_dir(working_dir, options)
162
- PathResolver.root_of_worktree(
163
- working_dir,
164
- binary_path: options.fetch(:binary_path, :use_global_config),
165
- git_ssh: options.fetch(:git_ssh, :use_global_config)
166
- )
167
- end
168
- private_class_method :resolve_open_working_dir
169
-
170
- # Build a repository from caller options and resolved paths
171
- #
172
- # @param options [Hash] the caller-supplied options (`:git_ssh`,
173
- # `:binary_path`, `:log`)
174
- #
175
- # @param paths [Hash{Symbol => (String, nil)}] the resolved
176
- # `:working_directory`, `:repository`, and `:index` paths
177
- #
178
- # @return [Git::Repository] the constructed repository
179
- #
180
- # @api private
181
- #
182
- def self.from_paths(options, paths)
183
- execution_context = Git::ExecutionContext::Repository.from_hash(
184
- options.merge(paths), logger: options[:log]
185
- )
186
- new(execution_context: execution_context)
187
- end
188
- private_class_method :from_paths
189
-
190
71
  # @return [Git::ExecutionContext::Repository] the execution context used to run
191
72
  # git commands for this repository
192
73
  # @api private
@@ -239,6 +120,54 @@ module Git
239
120
  index_file && Pathname.new(index_file)
240
121
  end
241
122
 
123
+ # Returns `self` after emitting a deprecation warning.
124
+ #
125
+ # Legacy callers that used `git.lib.some_method` can migrate to calling the
126
+ # facade method directly on the repository object. This shim will be removed
127
+ # in v6.0.0.
128
+ #
129
+ # @return [self]
130
+ #
131
+ # @api private
132
+ #
133
+ def lib
134
+ Git::Deprecation.warn(
135
+ 'Git::Repository#lib is deprecated and will be removed in v6.0.0. ' \
136
+ 'Use the repository object directly.'
137
+ )
138
+ self
139
+ end
140
+
141
+ # @return [String, nil] the git directory path
142
+ #
143
+ # @api private
144
+ def git_dir = execution_context.git_dir
145
+
146
+ # @return [String, nil] the working directory path
147
+ #
148
+ # @api private
149
+ def git_work_dir = execution_context.git_work_dir
150
+
151
+ # @return [String, nil] the index file path
152
+ #
153
+ # @api private
154
+ def git_index_file = execution_context.git_index_file
155
+
156
+ # @return [Git::Version] the installed git version
157
+ #
158
+ # @api private
159
+ def git_version(timeout: nil) = execution_context.git_version(timeout: timeout)
160
+
161
+ # @return [String, nil] the SSH wrapper path
162
+ #
163
+ # @api private
164
+ def git_ssh = execution_context.git_ssh
165
+
166
+ # @return [String, :use_global_config] the path to the git binary
167
+ #
168
+ # @api private
169
+ def binary_path = execution_context.binary_path
170
+
242
171
  # Returns the size of the repository directory in bytes
243
172
  #
244
173
  # Sums the sizes of every regular file under the repository (`.git`)
@@ -265,5 +194,15 @@ module Git
265
194
  end
266
195
  total
267
196
  end
197
+
198
+ private
199
+
200
+ # All git config scopes are valid in a repository context
201
+ #
202
+ # @return [void]
203
+ #
204
+ def assert_valid_scope!(**)
205
+ nil
206
+ end
268
207
  end
269
208
  end
data/lib/git/stash.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git/base'
4
-
5
3
  module Git
6
4
  # Represents a single stash entry in a Git repository
7
5
  #
@@ -18,7 +16,7 @@ module Git
18
16
  # When `existing` is `false` (the default), immediately calls {#save} to push
19
17
  # the current working-directory state onto the stash stack.
20
18
  #
21
- # @param base [Git::Repository, Git::Base] the git repository
19
+ # @param base [Git::Repository] the git repository
22
20
  #
23
21
  # @param message [String] the stash message
24
22
  #
@@ -93,15 +91,10 @@ module Git
93
91
 
94
92
  private
95
93
 
96
- # Returns the facade interface for stash operations
97
- #
98
- # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
99
- # The `is_a?` guard will be removed when {Git::Base} is deleted in Phase 4.
100
- #
101
94
  # @return [Git::Repository]
102
95
  #
103
96
  def stash_repository
104
- @base.is_a?(Git::Base) ? @base.facade_repository : @base
97
+ @base
105
98
  end
106
99
  end
107
100
  end
data/lib/git/stashes.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git/base'
4
-
5
3
  module Git
6
4
  # Collection of stash entries for a Git repository
7
5
  #
@@ -21,7 +19,7 @@ module Git
21
19
  #
22
20
  # Loads all existing stash entries from the repository at construction time.
23
21
  #
24
- # @param base [Git::Repository, Git::Base] the git repository
22
+ # @param base [Git::Repository] the git repository
25
23
  #
26
24
  # @return [void]
27
25
  #
@@ -161,13 +159,10 @@ module Git
161
159
 
162
160
  # Returns the facade interface for stash operations
163
161
  #
164
- # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
165
- # The `is_a?` guard will be removed when {Git::Base} is deleted in Phase 4.
166
- #
167
162
  # @return [Git::Repository]
168
163
  #
169
164
  def stash_repository
170
- @base.is_a?(Git::Base) ? @base.facade_repository : @base
165
+ @base
171
166
  end
172
167
  end
173
168
  end
data/lib/git/status.rb CHANGED
@@ -14,7 +14,7 @@ module Git
14
14
 
15
15
  # Create a new Status for the given repository
16
16
  #
17
- # @param base [Git::Base, Git::Repository] the git object backing this status
17
+ # @param base [Git::Repository] the git object backing this status
18
18
  #
19
19
  def initialize(base)
20
20
  @base = base
@@ -239,7 +239,7 @@ module Git
239
239
 
240
240
  # Initialize a new StatusFile with the given git object and data hash
241
241
  #
242
- # @param base [Git::Base, Git::Repository] the git object
242
+ # @param base [Git::Repository] the git object
243
243
  #
244
244
  # @param hash [Hash] raw status data for this file
245
245
  #
@@ -281,18 +281,10 @@ module Git
281
281
  class StatusFileFactory
282
282
  # Create a new factory backed by the given git object
283
283
  #
284
- # When `base` is a {Git::Repository} (which exposes `#diff_index`
285
- # directly), it is used as the data provider. When `base` is a
286
- # {Git::Base} (the legacy path), `base.lib` is used instead.
287
- #
288
- # @param base [Git::Base, Git::Repository] the git object used as the
289
- # status data provider
284
+ # @param base [Git::Repository] the git object used as the status data provider
290
285
  #
291
286
  def initialize(base)
292
287
  @base = base
293
- # When base is Git::Repository (which exposes #diff_index directly),
294
- # use it as the data provider. Otherwise use base.lib (legacy path).
295
- @provider = base.respond_to?(:diff_index) ? base : base.lib
296
288
  end
297
289
 
298
290
  # Gather all status data and build a hash of file paths to StatusFile objects
@@ -314,7 +306,7 @@ module Git
314
306
  # @return [Hash{String => Hash}] raw per-file status data keyed by path
315
307
  #
316
308
  def fetch_all_files_data
317
- files = @provider.ls_files # Start with files tracked in the index.
309
+ files = @base.ls_files # Start with files tracked in the index.
318
310
  merge_untracked_files(files)
319
311
  merge_modified_files(files)
320
312
  merge_head_diffs(files)
@@ -328,7 +320,7 @@ module Git
328
320
  # @return [void]
329
321
  #
330
322
  def merge_untracked_files(files)
331
- @provider.untracked_files.each do |file|
323
+ @base.untracked_files.each do |file|
332
324
  files[file] = { path: file, untracked: true }
333
325
  end
334
326
  end
@@ -341,7 +333,7 @@ module Git
341
333
  #
342
334
  def merge_modified_files(files)
343
335
  # Merge changes between the index and the working directory.
344
- @provider.diff_files.each do |path, data|
336
+ @base.diff_files.each do |path, data|
345
337
  (files[path] ||= {}).merge!(data)
346
338
  end
347
339
  end
@@ -353,12 +345,11 @@ module Git
353
345
  # @return [void]
354
346
  #
355
347
  def merge_head_diffs(files)
356
- # Git::Repository exposes #no_commits?; Git::Lib exposes #empty?.
357
- is_empty = @provider.respond_to?(:no_commits?) ? @provider.no_commits? : @provider.empty?
348
+ is_empty = @base.no_commits?
358
349
  return if is_empty
359
350
 
360
351
  # Merge changes between HEAD and the index.
361
- @provider.diff_index('HEAD').each do |path, data|
352
+ @base.diff_index('HEAD').each do |path, data|
362
353
  (files[path] ||= {}).merge!(data)
363
354
  end
364
355
  end
data/lib/git/version.rb CHANGED
@@ -4,7 +4,7 @@ module Git
4
4
  # The current gem version
5
5
  #
6
6
  # @return [String] the current gem version
7
- VERSION = '5.0.0.beta.1'
7
+ VERSION = '5.0.0.beta.2'
8
8
 
9
9
  # Represents a git version with major, minor, and patch components
10
10
  #
@@ -100,7 +100,7 @@ module Git
100
100
  # Return the version as an array of integers
101
101
  #
102
102
  # Useful when legacy code expects the array shape returned by the
103
- # deprecated {Git::Lib#current_command_version} method.
103
+ # deprecated `Git::Lib#current_command_version` method.
104
104
  #
105
105
  # @return [Array<Integer>] [major, minor, patch]
106
106
  #
data/lib/git/worktree.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git/base'
4
-
5
3
  module Git
6
4
  # A worktree in a Git repository
7
5
  #
@@ -9,11 +7,6 @@ module Git
9
7
  # {Git::Repository::WorktreeOperations#worktree} or populated by
10
8
  # {Git::Worktrees}.
11
9
  #
12
- # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy form)
13
- # as the `base` argument. The `is_a?(Git::Base)` guard routes git operations
14
- # through the facade repository and will be removed when {Git::Base} is
15
- # deleted in Phase 4.
16
- #
17
10
  # @example Add and remove a linked worktree
18
11
  # worktree = repo.worktree('/path/to/new-worktree')
19
12
  # worktree.add
@@ -37,7 +30,7 @@ module Git
37
30
 
38
31
  # Creates a new Worktree object
39
32
  #
40
- # @param base [Git::Base, Git::Repository] the repository that owns this
33
+ # @param base [Git::Repository] the repository that owns this
41
34
  # worktree
42
35
  #
43
36
  # @param dir [String] filesystem path of the worktree
@@ -137,18 +130,12 @@ module Git
137
130
 
138
131
  private
139
132
 
140
- # Resolves the {Git::Repository} for worktree operations
141
- #
142
- # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
143
- # The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
144
- # in Phase 4.
145
- #
146
133
  # @return [Git::Repository] the repository used for worktree operations
147
134
  #
148
135
  # @api private
149
136
  #
150
137
  def worktree_repository
151
- @base.is_a?(Git::Base) ? @base.facade_repository : @base
138
+ @base
152
139
  end
153
140
  end
154
141
  end
data/lib/git/worktrees.rb CHANGED
@@ -1,18 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git/base'
4
-
5
3
  module Git
6
4
  # Collection of all Git worktrees in a repository
7
5
  #
8
6
  # Wraps every linked and main worktree and provides enumeration and
9
7
  # path-based lookup.
10
8
  #
11
- # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy
12
- # form) as the `base` argument. The `is_a?(Git::Base)` guard routes git
13
- # operations through the facade repository and will be removed when
14
- # {Git::Base} is deleted in Phase 4.
15
- #
16
9
  # @example Enumerate all worktrees
17
10
  # worktrees = repo.worktrees
18
11
  # worktrees.each { |wt| puts wt.dir }
@@ -24,7 +17,7 @@ module Git
24
17
 
25
18
  # Creates a new Worktrees collection populated from the given repository
26
19
  #
27
- # @param base [Git::Base, Git::Repository] the repository to enumerate
20
+ # @param base [Git::Repository] the repository to enumerate
28
21
  # worktrees from
29
22
  #
30
23
  # @return [void]
@@ -130,18 +123,12 @@ module Git
130
123
 
131
124
  private
132
125
 
133
- # Resolves the {Git::Repository} for this collection of worktrees
134
- #
135
- # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
136
- # The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
137
- # in Phase 4.
138
- #
139
126
  # @return [Git::Repository] the repository used to enumerate worktrees
140
127
  #
141
128
  # @api private
142
129
  #
143
130
  def worktree_repository
144
- @base.is_a?(Git::Base) ? @base.facade_repository : @base
131
+ @base
145
132
  end
146
133
  end
147
134
  end