git 5.0.0.beta.1 → 5.0.0.beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +6 -0
- data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
- data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +28 -14
- data/.github/skills/facade-implementation/SKILL.md +14 -0
- data/.github/skills/facade-test-conventions/SKILL.md +14 -0
- data/.rubocop.yml +5 -0
- data/README.md +90 -22
- data/UPGRADING.md +141 -0
- data/git.gemspec +5 -0
- data/lib/git/branch.rb +7 -18
- data/lib/git/branches.rb +2 -10
- data/lib/git/command_line/base.rb +10 -0
- data/lib/git/command_line/capturing.rb +5 -3
- data/lib/git/command_line/streaming.rb +5 -3
- data/lib/git/command_line.rb +3 -3
- data/lib/git/commands/base.rb +7 -6
- data/lib/git/commands/cat_file/batch.rb +6 -1
- data/lib/git/commands/cat_file/raw.rb +7 -1
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +5 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
- data/lib/git/commands/update_ref/batch.rb +1 -1
- data/lib/git/commands/version.rb +5 -0
- data/lib/git/commands.rb +5 -7
- data/lib/git/config.rb +17 -0
- data/lib/git/config_entry_info.rb +106 -0
- data/lib/git/configuring.rb +665 -0
- data/lib/git/deprecation.rb +9 -0
- data/lib/git/diff.rb +4 -8
- data/lib/git/diff_path_status.rb +2 -13
- data/lib/git/diff_stats.rb +1 -9
- data/lib/git/execution_context/global.rb +3 -28
- data/lib/git/execution_context/repository.rb +30 -41
- data/lib/git/execution_context.rb +43 -24
- data/lib/git/log.rb +3 -9
- data/lib/git/object.rb +14 -21
- data/lib/git/parsers/config_entry.rb +110 -0
- data/lib/git/parsers/ls_remote.rb +79 -0
- data/lib/git/remote.rb +7 -20
- data/lib/git/repository/branching.rb +183 -12
- data/lib/git/repository/committing.rb +64 -68
- data/lib/git/repository/context_helpers.rb +264 -0
- data/lib/git/repository/factories.rb +682 -0
- data/lib/git/repository/inspecting.rb +99 -0
- data/lib/git/repository/maintenance.rb +65 -0
- data/lib/git/repository/merging.rb +63 -1
- data/lib/git/repository/object_operations.rb +133 -35
- data/lib/git/repository/path_resolver.rb +1 -1
- data/lib/git/repository/remote_operations.rb +166 -21
- data/lib/git/repository/staging.rb +187 -23
- data/lib/git/repository/stashing.rb +39 -3
- data/lib/git/repository/status_operations.rb +21 -0
- data/lib/git/repository.rb +288 -128
- data/lib/git/stash.rb +2 -9
- data/lib/git/stashes.rb +2 -7
- data/lib/git/status.rb +12 -23
- data/lib/git/version.rb +2 -2
- data/lib/git/worktree.rb +2 -15
- data/lib/git/worktrees.rb +2 -15
- data/lib/git.rb +182 -77
- data/redesign/3_architecture_implementation.md +170 -112
- data/redesign/Phase 4 - Step A.md +366 -0
- data/redesign/beta_release.md +107 -0
- data/redesign/c1c2_audit.md +566 -0
- data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
- data/redesign/config_design.rb +501 -0
- metadata +19 -6
- data/lib/git/base.rb +0 -1204
- data/lib/git/lib.rb +0 -2855
- data/lib/git/repository/configuring.rb +0 -156
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'git/execution_context/repository'
|
|
7
|
+
|
|
8
|
+
module Git
|
|
9
|
+
class Repository
|
|
10
|
+
# Facade methods for block-based directory and index context helpers
|
|
11
|
+
#
|
|
12
|
+
# These helpers allow callers to temporarily change the working directory,
|
|
13
|
+
# the git index, or both, restoring the original state unconditionally when
|
|
14
|
+
# the block exits — even if the block raises an exception.
|
|
15
|
+
#
|
|
16
|
+
# Included by {Git::Repository}.
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
#
|
|
20
|
+
module ContextHelpers
|
|
21
|
+
# Changes the current working directory to the repository working directory
|
|
22
|
+
# for the duration of the block
|
|
23
|
+
#
|
|
24
|
+
# @example Write a file inside the repository working directory
|
|
25
|
+
# repo.chdir do |dir|
|
|
26
|
+
# File.write('hello.txt', 'Hello, world!')
|
|
27
|
+
# repo.add('hello.txt')
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# @yield [dir] the repository working directory
|
|
31
|
+
#
|
|
32
|
+
# @yieldparam dir [Pathname] the working directory path
|
|
33
|
+
#
|
|
34
|
+
# @yieldreturn [Object] returned as the method's return value
|
|
35
|
+
#
|
|
36
|
+
# @return [Object] the value returned by the block
|
|
37
|
+
#
|
|
38
|
+
# @raise [ArgumentError] if the repository has no working directory (bare
|
|
39
|
+
# repository)
|
|
40
|
+
#
|
|
41
|
+
def chdir
|
|
42
|
+
raise ArgumentError, 'cannot chdir: repository has no working directory (bare repository)' if dir.nil?
|
|
43
|
+
|
|
44
|
+
Dir.chdir(dir.to_s) { yield dir }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Temporarily switches the git index to `new_index` for the duration of
|
|
48
|
+
# the block
|
|
49
|
+
#
|
|
50
|
+
# Rebuilds the repository execution context to point to the new index file,
|
|
51
|
+
# yields `self`, then unconditionally restores the original execution
|
|
52
|
+
# context — even if the block raises an exception.
|
|
53
|
+
#
|
|
54
|
+
# @example Read a tree into a custom index
|
|
55
|
+
# repo.with_index('/tmp/custom.index') do
|
|
56
|
+
# repo.read_tree('HEAD')
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# @param new_index [String, Pathname] path to the replacement index file
|
|
60
|
+
#
|
|
61
|
+
# @yield [repo] the repository instance with the new index active
|
|
62
|
+
#
|
|
63
|
+
# @yieldparam repo [Git::Repository] `self`
|
|
64
|
+
#
|
|
65
|
+
# @yieldreturn [Object] returned as the method's return value
|
|
66
|
+
#
|
|
67
|
+
# @return [Object] the value returned by the block
|
|
68
|
+
#
|
|
69
|
+
def with_index(new_index) # :yields: self
|
|
70
|
+
old_context = @execution_context
|
|
71
|
+
set_index(new_index, must_exist: false)
|
|
72
|
+
yield self
|
|
73
|
+
ensure
|
|
74
|
+
@execution_context = old_context
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Temporarily switches the git index to a new temporary file for the
|
|
78
|
+
# duration of the block, then removes the file
|
|
79
|
+
#
|
|
80
|
+
# The temporary index file does not exist until git creates it on first
|
|
81
|
+
# write. A unique temporary directory is created to hold the index path,
|
|
82
|
+
# avoiding the risk of presenting an empty file to git (which git would
|
|
83
|
+
# reject as a corrupt index). The directory — and any files inside it —
|
|
84
|
+
# are removed unconditionally after the block exits, even if the block
|
|
85
|
+
# raises an exception.
|
|
86
|
+
#
|
|
87
|
+
# @example Stage changes using a temporary index
|
|
88
|
+
# repo.with_temp_index do
|
|
89
|
+
# repo.read_tree('HEAD')
|
|
90
|
+
# repo.write_tree
|
|
91
|
+
# end
|
|
92
|
+
#
|
|
93
|
+
# @yield [repo] the repository instance with the temporary index active
|
|
94
|
+
#
|
|
95
|
+
# @yieldparam repo [Git::Repository] `self`
|
|
96
|
+
#
|
|
97
|
+
# @yieldreturn [Object] returned as the method's return value
|
|
98
|
+
#
|
|
99
|
+
# @return [Object] the value returned by the block
|
|
100
|
+
#
|
|
101
|
+
def with_temp_index(&) # :yields: self
|
|
102
|
+
# Use a unique temp directory so the index file path is collision-free
|
|
103
|
+
# and does not exist until git writes it. An existing empty file would
|
|
104
|
+
# be treated as a corrupt index by git.
|
|
105
|
+
temp_dir = Dir.mktmpdir('git-temp-index-')
|
|
106
|
+
begin
|
|
107
|
+
with_index(File.join(temp_dir, 'index'), &)
|
|
108
|
+
ensure
|
|
109
|
+
FileUtils.remove_entry(temp_dir, true)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Temporarily switches the git working directory to `work_dir` for the
|
|
114
|
+
# duration of the block
|
|
115
|
+
#
|
|
116
|
+
# Rebuilds the repository execution context to point to the new working
|
|
117
|
+
# directory, changes the process working directory via `Dir.chdir`, yields
|
|
118
|
+
# `self`, then unconditionally restores the original execution context —
|
|
119
|
+
# even if the block raises an exception.
|
|
120
|
+
#
|
|
121
|
+
# @example Commit changes from a different worktree path
|
|
122
|
+
# repo.with_working('/path/to/worktree') do
|
|
123
|
+
# repo.add('.')
|
|
124
|
+
# repo.commit('chore: automated update')
|
|
125
|
+
# end
|
|
126
|
+
#
|
|
127
|
+
# @param work_dir [String, Pathname] path to the replacement working
|
|
128
|
+
# directory
|
|
129
|
+
#
|
|
130
|
+
# @yield [repo] the repository instance with the new working directory
|
|
131
|
+
# active
|
|
132
|
+
#
|
|
133
|
+
# @yieldparam repo [Git::Repository] `self`
|
|
134
|
+
#
|
|
135
|
+
# @yieldreturn [Object] returned as the method's return value
|
|
136
|
+
#
|
|
137
|
+
# @return [Object] the value returned by the block
|
|
138
|
+
#
|
|
139
|
+
# @raise [ArgumentError] if `work_dir` does not exist on disk
|
|
140
|
+
#
|
|
141
|
+
def with_working(work_dir) # :yields: self
|
|
142
|
+
old_context = @execution_context
|
|
143
|
+
set_working(work_dir)
|
|
144
|
+
Dir.chdir(dir.to_s) { yield self }
|
|
145
|
+
ensure
|
|
146
|
+
@execution_context = old_context
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Temporarily switches the git working directory to a new temporary
|
|
150
|
+
# directory for the duration of the block, then removes the directory and
|
|
151
|
+
# its contents
|
|
152
|
+
#
|
|
153
|
+
# The temporary directory is removed unconditionally after the block
|
|
154
|
+
# exits, even if the block raises an exception.
|
|
155
|
+
#
|
|
156
|
+
# @example Write files in an isolated temporary working directory
|
|
157
|
+
# repo.with_temp_working do
|
|
158
|
+
# File.write('scratch.txt', 'temporary content')
|
|
159
|
+
# end
|
|
160
|
+
#
|
|
161
|
+
# @yield [repo] the repository instance with the temporary working
|
|
162
|
+
# directory active
|
|
163
|
+
#
|
|
164
|
+
# @yieldparam repo [Git::Repository] `self`
|
|
165
|
+
#
|
|
166
|
+
# @yieldreturn [Object] returned as the method's return value
|
|
167
|
+
#
|
|
168
|
+
# @return [Object] the value returned by the block
|
|
169
|
+
#
|
|
170
|
+
def with_temp_working(&block) # :yields: self
|
|
171
|
+
Dir.mktmpdir('temp-workdir') { |temp_dir| with_working(temp_dir, &block) }
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Sets the git index to `index_file` and rebuilds the execution context
|
|
175
|
+
#
|
|
176
|
+
# By default raises if `index_file` does not exist. Pass `must_exist:
|
|
177
|
+
# false` to skip the existence check (useful when the index will be
|
|
178
|
+
# created by git later).
|
|
179
|
+
#
|
|
180
|
+
# @example Set the index to a custom path
|
|
181
|
+
# repo.set_index('/path/to/custom.index')
|
|
182
|
+
#
|
|
183
|
+
# @param index_file [String, Pathname] path to the new index file
|
|
184
|
+
#
|
|
185
|
+
# @param check [Boolean, nil] deprecated positional argument — use
|
|
186
|
+
# `must_exist:` instead; emits a deprecation warning when non-`nil`
|
|
187
|
+
#
|
|
188
|
+
# @param must_exist [Boolean, nil] when `true` (the default), raises
|
|
189
|
+
# `ArgumentError` if `index_file` does not exist on disk
|
|
190
|
+
#
|
|
191
|
+
# @return [void]
|
|
192
|
+
#
|
|
193
|
+
# @raise [ArgumentError] if `must_exist: true` (the default) and
|
|
194
|
+
# `index_file` does not exist
|
|
195
|
+
#
|
|
196
|
+
def set_index(index_file, check = nil, must_exist: nil)
|
|
197
|
+
must_exist = context_helpers_deprecate_check_argument(check, must_exist)
|
|
198
|
+
new_path = context_helpers_validate_path(index_file, must_exist)
|
|
199
|
+
context_helpers_rebuild_context(git_index_file: new_path.to_s)
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Sets the git working directory to `work_dir` and rebuilds the execution
|
|
204
|
+
# context
|
|
205
|
+
#
|
|
206
|
+
# By default raises if `work_dir` does not exist. Pass `must_exist:
|
|
207
|
+
# false` to skip the existence check.
|
|
208
|
+
#
|
|
209
|
+
# @example Set the working directory to a custom path
|
|
210
|
+
# repo.set_working('/path/to/working')
|
|
211
|
+
#
|
|
212
|
+
# @param work_dir [String, Pathname] path to the new working directory
|
|
213
|
+
#
|
|
214
|
+
# @param check [Boolean, nil] deprecated positional argument — use
|
|
215
|
+
# `must_exist:` instead; emits a deprecation warning when non-`nil`
|
|
216
|
+
#
|
|
217
|
+
# @param must_exist [Boolean, nil] when `true` (the default), raises
|
|
218
|
+
# `ArgumentError` if `work_dir` does not exist on disk
|
|
219
|
+
#
|
|
220
|
+
# @return [void]
|
|
221
|
+
#
|
|
222
|
+
# @raise [ArgumentError] if `must_exist: true` (the default) and
|
|
223
|
+
# `work_dir` does not exist
|
|
224
|
+
#
|
|
225
|
+
def set_working(work_dir, check = nil, must_exist: nil)
|
|
226
|
+
must_exist = context_helpers_deprecate_check_argument(check, must_exist)
|
|
227
|
+
new_path = context_helpers_validate_path(work_dir, must_exist)
|
|
228
|
+
context_helpers_rebuild_context(git_work_dir: new_path.to_s)
|
|
229
|
+
nil
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
private
|
|
233
|
+
|
|
234
|
+
def context_helpers_deprecate_check_argument(check, must_exist)
|
|
235
|
+
if !check.nil? && defined?(Git::Deprecation)
|
|
236
|
+
Git::Deprecation.warn(
|
|
237
|
+
'The "check" argument is deprecated and will be removed in a future version. ' \
|
|
238
|
+
'Use "must_exist:" instead.'
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
# Preserve the original Git::Base semantics: when both the deprecated
|
|
242
|
+
# positional `check` and the new `must_exist:` keyword are given, OR
|
|
243
|
+
# them so the more restrictive value wins.
|
|
244
|
+
#
|
|
245
|
+
# NilClass#| is defined in Ruby: nil | false → false, nil | true → true.
|
|
246
|
+
# This means single-argument callers (check only, or must_exist: only)
|
|
247
|
+
# are handled correctly without any nil-special-casing.
|
|
248
|
+
return true if must_exist.nil? && check.nil?
|
|
249
|
+
|
|
250
|
+
must_exist | check
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def context_helpers_validate_path(path, must_exist)
|
|
254
|
+
Pathname.new(File.expand_path(path.to_s)).tap do |expanded_path|
|
|
255
|
+
raise ArgumentError, "path does not exist: #{expanded_path}" if must_exist && !expanded_path.exist?
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def context_helpers_rebuild_context(**overrides)
|
|
260
|
+
@execution_context = @execution_context.dup_with(**overrides)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|