git 5.7.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 -35
- data/CONTRIBUTING.md +44 -31
- data/README.md +138 -100
- data/UPGRADING.md +867 -262
- 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 +1 -1
- data/lib/git/encoding_utils.rb +25 -0
- data/lib/git/errors.rb +24 -18
- 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 -72
- data/lib/git/repository/merging.rb +32 -54
- data/lib/git/repository/object_operations.rb +120 -497
- 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 +30 -125
- 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 +24 -18
- 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
|
@@ -4,14 +4,33 @@ require 'fileutils'
|
|
|
4
4
|
require 'pathname'
|
|
5
5
|
require 'tmpdir'
|
|
6
6
|
require 'git/execution_context/repository'
|
|
7
|
+
require 'git/system_call_guard'
|
|
7
8
|
|
|
8
9
|
module Git
|
|
9
10
|
class Repository
|
|
10
11
|
# Facade methods for block-based directory and index context helpers
|
|
11
12
|
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
13
|
+
# The `with_*` helpers build a second {Git::Repository} bound to a different
|
|
14
|
+
# index file or working directory, yield it, and leave the receiver bound to
|
|
15
|
+
# its original index and working directory. Nothing on the receiver changes,
|
|
16
|
+
# so nothing on it is restored when the block exits. The working directory
|
|
17
|
+
# variants change the process working directory for the duration of the
|
|
18
|
+
# block and change it back afterward. The temporary variants remove the
|
|
19
|
+
# scratch directory they created, even if the block raises an exception.
|
|
20
|
+
#
|
|
21
|
+
# The second repository is built with `self.class.new(execution_context:)`,
|
|
22
|
+
# so a subclass of {Git::Repository} must accept that constructor call for
|
|
23
|
+
# the helpers to yield an instance of the subclass.
|
|
24
|
+
#
|
|
25
|
+
# Each `with_*` helper requires a block that declares a positional parameter
|
|
26
|
+
# for the yielded repository and raises `ArgumentError` otherwise. A block with no
|
|
27
|
+
# positional parameter is the v5.x form, which relied on the receiver being
|
|
28
|
+
# rebound; failing fast keeps that form from running against the original
|
|
29
|
+
# index or working directory.
|
|
30
|
+
#
|
|
31
|
+
# {#set_index} and {#set_working} are deprecated and warn on every call
|
|
32
|
+
# through {Git::Deprecation}; UPGRADING.md explains why and shows the
|
|
33
|
+
# replacements.
|
|
15
34
|
#
|
|
16
35
|
# Included by {Git::Repository}.
|
|
17
36
|
#
|
|
@@ -22,6 +41,18 @@ module Git
|
|
|
22
41
|
CONTEXT_HELPERS_POSITIONAL_PARAMETERS = %i[req opt rest].freeze
|
|
23
42
|
private_constant :CONTEXT_HELPERS_POSITIONAL_PARAMETERS
|
|
24
43
|
|
|
44
|
+
# Deprecation message for {#set_index}
|
|
45
|
+
SET_INDEX_DEPRECATION = 'Git::Repository#set_index is deprecated and will be removed in a future major ' \
|
|
46
|
+
'release. Open a repository bound to the index instead: ' \
|
|
47
|
+
'Git.open(dir, index: path) or Git.bare(git_dir, index: path).'
|
|
48
|
+
private_constant :SET_INDEX_DEPRECATION
|
|
49
|
+
|
|
50
|
+
# Deprecation message for {#set_working}
|
|
51
|
+
SET_WORKING_DEPRECATION = 'Git::Repository#set_working is deprecated and will be removed in a future major ' \
|
|
52
|
+
'release. Open a repository bound to the working tree instead: ' \
|
|
53
|
+
'Git.open(work_dir, repository: git_dir).'
|
|
54
|
+
private_constant :SET_WORKING_DEPRECATION
|
|
55
|
+
|
|
25
56
|
# Changes the current working directory to the repository working directory
|
|
26
57
|
# for the duration of the block
|
|
27
58
|
#
|
|
@@ -36,6 +67,8 @@ module Git
|
|
|
36
67
|
# @raise [ArgumentError] if the repository has no working directory (bare
|
|
37
68
|
# repository)
|
|
38
69
|
#
|
|
70
|
+
# @raise [Git::Error] if the working directory cannot be entered
|
|
71
|
+
#
|
|
39
72
|
# @yield [dir] the repository working directory
|
|
40
73
|
#
|
|
41
74
|
# @yieldparam dir [Pathname] the working directory path
|
|
@@ -45,104 +78,114 @@ module Git
|
|
|
45
78
|
def chdir
|
|
46
79
|
raise ArgumentError, 'cannot chdir: repository has no working directory (bare repository)' if dir.nil?
|
|
47
80
|
|
|
48
|
-
|
|
81
|
+
context_helpers_chdir(dir.to_s) { yield dir }
|
|
49
82
|
end
|
|
50
83
|
|
|
51
|
-
#
|
|
52
|
-
# the block
|
|
84
|
+
# Yields a repository bound to `new_index` in place of the receiver's index
|
|
53
85
|
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# Deprecated form: a block that declares no positional parameter emits a
|
|
59
|
-
# deprecation warning, because v6.0.0 yields a separate repository
|
|
60
|
-
# instead of `self`.
|
|
86
|
+
# Yields a second repository bound to `new_index`; its working directory
|
|
87
|
+
# and git directory are the receiver's. The receiver is not changed, so
|
|
88
|
+
# calls on it inside the block still use the original index.
|
|
61
89
|
#
|
|
62
90
|
# @example Read a tree into a custom index
|
|
63
|
-
# repo.with_index('/tmp/custom.index') do |
|
|
64
|
-
#
|
|
91
|
+
# repo.with_index('/tmp/custom.index') do |indexed|
|
|
92
|
+
# indexed.read_tree('HEAD')
|
|
65
93
|
# end
|
|
66
94
|
#
|
|
67
95
|
# @param new_index [String, Pathname] path to the replacement index file
|
|
68
96
|
#
|
|
69
97
|
# @return [Object] the value returned by the block
|
|
70
98
|
#
|
|
71
|
-
# @
|
|
99
|
+
# @raise [ArgumentError] if no block is given or the block declares no
|
|
100
|
+
# positional parameter
|
|
101
|
+
#
|
|
102
|
+
# @raise [Git::Error] if `new_index` cannot be expanded to an absolute path
|
|
103
|
+
#
|
|
104
|
+
# @yield [repo] the repository bound to `new_index`
|
|
72
105
|
#
|
|
73
|
-
# @yieldparam repo [Git::Repository]
|
|
106
|
+
# @yieldparam repo [Git::Repository] a new repository of the receiver's
|
|
107
|
+
# class bound to `new_index`
|
|
74
108
|
#
|
|
75
109
|
# @yieldreturn [Object] returned as the method's return value
|
|
76
110
|
#
|
|
77
|
-
def with_index(new_index, &block)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
yield self
|
|
82
|
-
ensure
|
|
83
|
-
@execution_context = old_context
|
|
111
|
+
def with_index(new_index, &block)
|
|
112
|
+
context_helpers_require_repository_block(block)
|
|
113
|
+
new_path = context_helpers_validate_path(new_index, false)
|
|
114
|
+
yield context_helpers_derived_repository(git_index_file: new_path.to_s)
|
|
84
115
|
end
|
|
85
116
|
|
|
86
|
-
#
|
|
87
|
-
#
|
|
117
|
+
# Yields a repository bound to a new temporary index file, then removes
|
|
118
|
+
# the file
|
|
88
119
|
#
|
|
89
120
|
# The temporary index file does not exist until git creates it on first
|
|
90
121
|
# write. A unique temporary directory is created to hold the index path,
|
|
91
122
|
# avoiding the risk of presenting an empty file to git (which git would
|
|
92
|
-
# reject as a corrupt index). The directory
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
# repo.with_temp_index do |r|
|
|
102
|
-
# r.read_tree('HEAD')
|
|
103
|
-
# r.write_tree
|
|
123
|
+
# reject as a corrupt index). The directory, and any files inside it, are
|
|
124
|
+
# removed unconditionally after the block exits, even if the block raises
|
|
125
|
+
# an exception. The yielded repository is bound as {#with_index} binds it.
|
|
126
|
+
#
|
|
127
|
+
# @example Write a tree from a temporary index
|
|
128
|
+
# tree_sha = repo.with_temp_index do |indexed|
|
|
129
|
+
# indexed.read_tree('HEAD')
|
|
130
|
+
# indexed.add('generated.txt')
|
|
131
|
+
# indexed.write_tree
|
|
104
132
|
# end
|
|
105
133
|
#
|
|
106
134
|
# @return [Object] the value returned by the block
|
|
107
135
|
#
|
|
108
|
-
# @
|
|
136
|
+
# @raise [ArgumentError] if no block is given or the block declares no
|
|
137
|
+
# positional parameter
|
|
109
138
|
#
|
|
110
|
-
# @
|
|
139
|
+
# @raise [Git::Error] if the temporary directory cannot be created
|
|
140
|
+
#
|
|
141
|
+
# @yield [repo] the repository bound to the temporary index
|
|
142
|
+
#
|
|
143
|
+
# @yieldparam repo [Git::Repository] a new repository of the receiver's
|
|
144
|
+
# class bound to an index file inside the temporary directory
|
|
111
145
|
#
|
|
112
146
|
# @yieldreturn [Object] returned as the method's return value
|
|
113
147
|
#
|
|
114
|
-
|
|
115
|
-
|
|
148
|
+
# @note The yielded repository stays bound to the temporary index path
|
|
149
|
+
# after the directory that held it is removed. Do not let it, or an
|
|
150
|
+
# object that holds it and later runs commands against the index,
|
|
151
|
+
# escape the block. Git treats the missing index file, even with its
|
|
152
|
+
# directory gone, as an empty index, so reads such as {Git::Repository::StatusOperations#ls_files}
|
|
153
|
+
# return nothing and {Git::Repository::StatusOperations#status_info}
|
|
154
|
+
# reports every tracked file both as deleted and as untracked; writes
|
|
155
|
+
# such as {Git::Repository::Staging#add} raise {Git::Error}. Reads from
|
|
156
|
+
# the object database, such as {Git::Object::AbstractObject#contents},
|
|
157
|
+
# still work.
|
|
158
|
+
#
|
|
159
|
+
def with_temp_index(&block)
|
|
160
|
+
context_helpers_require_repository_block(block)
|
|
116
161
|
# Use a unique temp directory so the index file path is collision-free
|
|
117
162
|
# and does not exist until git writes it. An existing empty file would
|
|
118
163
|
# be treated as a corrupt index by git.
|
|
119
|
-
temp_dir =
|
|
164
|
+
temp_dir = context_helpers_mktmpdir('git-temp-index-')
|
|
120
165
|
begin
|
|
121
|
-
|
|
122
|
-
# second time under its own name; forwarding &block would.
|
|
123
|
-
# rubocop:disable-next Style/ExplicitBlockArgument
|
|
124
|
-
with_index(File.join(temp_dir, 'index')) { |repo| yield repo }
|
|
166
|
+
with_index(File.join(temp_dir, 'index'), &block)
|
|
125
167
|
ensure
|
|
126
168
|
FileUtils.remove_entry(temp_dir, true)
|
|
127
169
|
end
|
|
128
170
|
end
|
|
129
171
|
|
|
130
|
-
#
|
|
131
|
-
#
|
|
172
|
+
# Yields a repository bound to `work_dir` in place of the receiver's
|
|
173
|
+
# working directory
|
|
132
174
|
#
|
|
133
|
-
#
|
|
134
|
-
# directory,
|
|
135
|
-
#
|
|
136
|
-
#
|
|
175
|
+
# Yields a second repository bound to `work_dir`. It shares the
|
|
176
|
+
# receiver's index and git directory, so staging through it changes what
|
|
177
|
+
# the receiver sees as staged. The process working directory is changed
|
|
178
|
+
# to `work_dir` via `Dir.chdir` for the duration of the block and changed
|
|
179
|
+
# back afterward. The receiver is not changed, so calls on it inside the
|
|
180
|
+
# block still use the original working directory.
|
|
137
181
|
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
# instead of `self`.
|
|
182
|
+
# `Dir.chdir` is process-global, so this method is not safe to call from
|
|
183
|
+
# more than one thread at a time.
|
|
141
184
|
#
|
|
142
185
|
# @example Commit changes from a different worktree path
|
|
143
|
-
# repo.with_working('/path/to/worktree') do |
|
|
144
|
-
#
|
|
145
|
-
#
|
|
186
|
+
# repo.with_working('/path/to/worktree') do |worktree|
|
|
187
|
+
# worktree.add('.')
|
|
188
|
+
# worktree.commit('chore: automated update')
|
|
146
189
|
# end
|
|
147
190
|
#
|
|
148
191
|
# @param work_dir [String, Pathname] path to the replacement working
|
|
@@ -150,56 +193,69 @@ module Git
|
|
|
150
193
|
#
|
|
151
194
|
# @return [Object] the value returned by the block
|
|
152
195
|
#
|
|
153
|
-
# @raise [ArgumentError] if `work_dir` does not exist on disk
|
|
196
|
+
# @raise [ArgumentError] if `work_dir` does not exist on disk, if no block
|
|
197
|
+
# is given, or if the block declares no positional parameter
|
|
198
|
+
#
|
|
199
|
+
# @raise [Git::Error] if `work_dir` cannot be expanded to an absolute path
|
|
200
|
+
# or cannot be entered
|
|
154
201
|
#
|
|
155
|
-
# @yield [repo] the repository
|
|
156
|
-
# active
|
|
202
|
+
# @yield [repo] the repository bound to `work_dir`
|
|
157
203
|
#
|
|
158
|
-
# @yieldparam repo [Git::Repository]
|
|
204
|
+
# @yieldparam repo [Git::Repository] a new repository of the receiver's
|
|
205
|
+
# class bound to `work_dir`
|
|
159
206
|
#
|
|
160
207
|
# @yieldreturn [Object] returned as the method's return value
|
|
161
208
|
#
|
|
162
|
-
def with_working(work_dir, &block)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
ensure
|
|
168
|
-
@execution_context = old_context
|
|
209
|
+
def with_working(work_dir, &block)
|
|
210
|
+
context_helpers_require_repository_block(block)
|
|
211
|
+
new_path = context_helpers_validate_path(work_dir, true)
|
|
212
|
+
repo = context_helpers_derived_repository(git_work_dir: new_path.to_s)
|
|
213
|
+
context_helpers_chdir(new_path.to_s) { yield repo }
|
|
169
214
|
end
|
|
170
215
|
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
# its contents
|
|
216
|
+
# Yields a repository bound to a new temporary working directory, then
|
|
217
|
+
# removes the directory and its contents
|
|
174
218
|
#
|
|
175
|
-
# The
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
219
|
+
# The process working directory is changed to the temporary directory for
|
|
220
|
+
# the duration of the block, as {#with_working} does, so this method is
|
|
221
|
+
# not safe to call from more than one thread at a time. The directory is
|
|
222
|
+
# removed unconditionally after the block exits, even if the block raises
|
|
223
|
+
# an exception. The yielded repository is bound as {#with_working} binds
|
|
224
|
+
# it.
|
|
181
225
|
#
|
|
182
226
|
# @example Write files in an isolated temporary working directory
|
|
183
|
-
# repo.with_temp_working do |
|
|
227
|
+
# repo.with_temp_working do |scratch|
|
|
184
228
|
# File.write('scratch.txt', 'temporary content')
|
|
185
|
-
#
|
|
229
|
+
# scratch.untracked_files #=> ["scratch.txt"]
|
|
186
230
|
# end
|
|
187
231
|
#
|
|
188
232
|
# @return [Object] the value returned by the block
|
|
189
233
|
#
|
|
190
|
-
# @
|
|
191
|
-
#
|
|
234
|
+
# @raise [ArgumentError] if no block is given or the block declares no
|
|
235
|
+
# positional parameter
|
|
236
|
+
#
|
|
237
|
+
# @raise [Git::Error] if the temporary directory cannot be created or
|
|
238
|
+
# removed
|
|
239
|
+
#
|
|
240
|
+
# @yield [repo] the repository bound to the temporary working directory
|
|
192
241
|
#
|
|
193
|
-
# @yieldparam repo [Git::Repository]
|
|
242
|
+
# @yieldparam repo [Git::Repository] a new repository of the receiver's
|
|
243
|
+
# class bound to the temporary directory
|
|
194
244
|
#
|
|
195
245
|
# @yieldreturn [Object] returned as the method's return value
|
|
196
246
|
#
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
247
|
+
# @note The yielded repository stays bound to the temporary working
|
|
248
|
+
# directory after it is removed. Do not let it, or an object that holds
|
|
249
|
+
# it and later runs commands against the working tree, escape the
|
|
250
|
+
# block: calls that read or write the working tree then raise
|
|
251
|
+
# {Git::Error}. Reads from the object database, such as
|
|
252
|
+
# {Git::Object::AbstractObject#contents}, still work.
|
|
253
|
+
#
|
|
254
|
+
def with_temp_working(&block)
|
|
255
|
+
context_helpers_require_repository_block(block)
|
|
256
|
+
Git::SystemCallGuard.call('Failed to create or remove a temporary directory') do |guard|
|
|
257
|
+
Dir.mktmpdir('temp-workdir') { |temp_dir| guard.unguarded { with_working(temp_dir, &block) } }
|
|
258
|
+
end
|
|
203
259
|
end
|
|
204
260
|
|
|
205
261
|
# Sets the git index to `index_file` and rebuilds the execution context
|
|
@@ -208,15 +264,20 @@ module Git
|
|
|
208
264
|
# false` to skip the existence check (useful when the index will be
|
|
209
265
|
# created by git later).
|
|
210
266
|
#
|
|
267
|
+
# Every call warns through {Git::Deprecation}; see {ContextHelpers}.
|
|
268
|
+
#
|
|
211
269
|
# @example Set the index to a custom path
|
|
212
270
|
# repo.set_index('/path/to/custom.index')
|
|
213
271
|
#
|
|
214
|
-
# @
|
|
272
|
+
# @example Open a repository bound to the index instead
|
|
273
|
+
# repo = Git.open('/path/to/repo', index: '/path/to/custom.index')
|
|
215
274
|
#
|
|
216
|
-
# @
|
|
217
|
-
#
|
|
275
|
+
# @example Open a bare repository bound to the index instead
|
|
276
|
+
# repo = Git.bare('/srv/repo.git', index: '/tmp/scratch.index')
|
|
218
277
|
#
|
|
219
|
-
# @param
|
|
278
|
+
# @param index_file [String, Pathname] path to the new index file
|
|
279
|
+
#
|
|
280
|
+
# @param must_exist [Boolean] when `true` (the default), raises
|
|
220
281
|
# `ArgumentError` if `index_file` does not exist on disk
|
|
221
282
|
#
|
|
222
283
|
# @return [void]
|
|
@@ -224,10 +285,15 @@ module Git
|
|
|
224
285
|
# @raise [ArgumentError] if `must_exist: true` (the default) and
|
|
225
286
|
# `index_file` does not exist
|
|
226
287
|
#
|
|
227
|
-
|
|
228
|
-
|
|
288
|
+
# @deprecated Use `Git.open(dir, index: path)` or
|
|
289
|
+
# `Git.bare(git_dir, index: path)` to open a repository bound to the
|
|
290
|
+
# index, or {#with_index} to bind a copy for the duration of a block.
|
|
291
|
+
# Will be removed in a future major release.
|
|
292
|
+
#
|
|
293
|
+
def set_index(index_file, must_exist: true)
|
|
294
|
+
Git::Deprecation.warn(SET_INDEX_DEPRECATION)
|
|
229
295
|
new_path = context_helpers_validate_path(index_file, must_exist)
|
|
230
|
-
|
|
296
|
+
@execution_context = @execution_context.dup_with(git_index_file: new_path.to_s)
|
|
231
297
|
nil
|
|
232
298
|
end
|
|
233
299
|
|
|
@@ -237,15 +303,25 @@ module Git
|
|
|
237
303
|
# By default raises if `work_dir` does not exist. Pass `must_exist:
|
|
238
304
|
# false` to skip the existence check.
|
|
239
305
|
#
|
|
306
|
+
# Every call warns through {Git::Deprecation}; see {ContextHelpers}.
|
|
307
|
+
#
|
|
308
|
+
# `set_working` keeps the receiver's index. `Git.open` with `:repository`
|
|
309
|
+
# alone resolves the index to `<git_dir>/index`, so pass `:index` as well
|
|
310
|
+
# whenever the receiver's index was custom.
|
|
311
|
+
#
|
|
240
312
|
# @example Set the working directory to a custom path
|
|
241
313
|
# repo.set_working('/path/to/working')
|
|
242
314
|
#
|
|
243
|
-
# @
|
|
315
|
+
# @example Open a repository bound to the working tree instead
|
|
316
|
+
# repo = Git.open('/path/to/working', repository: '/path/to/repo/.git')
|
|
244
317
|
#
|
|
245
|
-
# @
|
|
246
|
-
#
|
|
318
|
+
# @example Open a repository bound to the working tree and a custom index
|
|
319
|
+
# repo = Git.open('/path/to/working', repository: '/path/to/repo/.git',
|
|
320
|
+
# index: '/path/to/custom.index')
|
|
247
321
|
#
|
|
248
|
-
# @param
|
|
322
|
+
# @param work_dir [String, Pathname] path to the new working directory
|
|
323
|
+
#
|
|
324
|
+
# @param must_exist [Boolean] when `true` (the default), raises
|
|
249
325
|
# `ArgumentError` if `work_dir` does not exist on disk
|
|
250
326
|
#
|
|
251
327
|
# @return [void]
|
|
@@ -253,76 +329,86 @@ module Git
|
|
|
253
329
|
# @raise [ArgumentError] if `must_exist: true` (the default) and
|
|
254
330
|
# `work_dir` does not exist
|
|
255
331
|
#
|
|
256
|
-
|
|
257
|
-
|
|
332
|
+
# @deprecated Use `Git.open(work_dir, repository: git_dir)` to open a
|
|
333
|
+
# repository bound to the working tree, adding `index:` when the
|
|
334
|
+
# receiver's index was custom, or {#with_working} to bind a copy for
|
|
335
|
+
# the duration of a block. Will be removed in a future major release.
|
|
336
|
+
#
|
|
337
|
+
def set_working(work_dir, must_exist: true)
|
|
338
|
+
Git::Deprecation.warn(SET_WORKING_DEPRECATION)
|
|
258
339
|
new_path = context_helpers_validate_path(work_dir, must_exist)
|
|
259
|
-
|
|
340
|
+
@execution_context = @execution_context.dup_with(git_work_dir: new_path.to_s)
|
|
260
341
|
nil
|
|
261
342
|
end
|
|
262
343
|
|
|
263
344
|
private
|
|
264
345
|
|
|
265
|
-
#
|
|
346
|
+
# Runs the block with the process working directory set to `path`
|
|
347
|
+
#
|
|
348
|
+
# A `SystemCallError` from entering or leaving `path` is raised as
|
|
349
|
+
# {Git::Error}. A `SystemCallError` raised by the block propagates
|
|
350
|
+
# unchanged.
|
|
351
|
+
#
|
|
352
|
+
# The message does not name a directory because `Dir.chdir` fails on
|
|
353
|
+
# either leg: entering `path`, or restoring the previous directory
|
|
354
|
+
# afterward. The underlying `SystemCallError` names the directory that
|
|
355
|
+
# actually failed.
|
|
356
|
+
#
|
|
357
|
+
# @param path [String] the directory to enter
|
|
266
358
|
#
|
|
267
|
-
#
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
# `ArgumentError` for that form using this same rule: the block must
|
|
271
|
-
# declare a required, optional, or splat positional parameter. The rule
|
|
272
|
-
# reads `Proc#parameters` rather than arity because an optional parameter
|
|
273
|
-
# gives a proc an arity of zero although it receives the repository,
|
|
274
|
-
# while keyword and block parameters cannot receive it.
|
|
359
|
+
# @return [Object] the value returned by the block
|
|
360
|
+
#
|
|
361
|
+
# @raise [Git::Error] if `path` cannot be entered
|
|
275
362
|
#
|
|
276
|
-
#
|
|
277
|
-
# `LocalJumpError` as before.
|
|
363
|
+
# @yield the code to run inside `path`
|
|
278
364
|
#
|
|
279
|
-
#
|
|
280
|
-
# the location points at the block that needs changing.
|
|
365
|
+
# @yieldreturn [Object] returned as the method's return value
|
|
281
366
|
#
|
|
282
|
-
# @
|
|
367
|
+
# @api private
|
|
283
368
|
#
|
|
284
|
-
|
|
369
|
+
def context_helpers_chdir(path, &block)
|
|
370
|
+
Git::SystemCallGuard.call('Failed to change directory') do |guard|
|
|
371
|
+
Dir.chdir(path) { guard.unguarded(&block) }
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Raises unless `block` declares a positional parameter for the yielded
|
|
376
|
+
# repository
|
|
377
|
+
#
|
|
378
|
+
# A block with no positional parameter is the v5.x form that relied on
|
|
379
|
+
# the receiver being rebound; it fails fast rather than running against
|
|
380
|
+
# the original index or working directory. The check reads the block's
|
|
381
|
+
# parameter list rather than its arity because an optional parameter
|
|
382
|
+
# (`|repo = nil|`) gives a proc an arity of zero although it receives the
|
|
383
|
+
# repository, while keyword and block parameters cannot receive it.
|
|
384
|
+
#
|
|
385
|
+
# @param block [Proc, nil] the block passed to a `with_*` helper
|
|
285
386
|
#
|
|
286
387
|
# @return [void]
|
|
287
388
|
#
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
389
|
+
# @raise [ArgumentError] if `block` is nil or declares no positional
|
|
390
|
+
# parameter
|
|
391
|
+
#
|
|
392
|
+
# @api private
|
|
393
|
+
#
|
|
394
|
+
def context_helpers_require_repository_block(block)
|
|
395
|
+
return if block&.parameters&.any? { |type, _name| CONTEXT_HELPERS_POSITIONAL_PARAMETERS.include?(type) }
|
|
291
396
|
|
|
292
|
-
|
|
293
|
-
"Calling Git::Repository##{helper} with a block that declares no positional " \
|
|
294
|
-
'parameter is deprecated and will be removed in v6.0.0, where the helper ' \
|
|
295
|
-
'yields a separate repository instead of self. Declare a block parameter and ' \
|
|
296
|
-
"call methods on it instead: #{helper} { |repo| repo.some_method }",
|
|
297
|
-
caller_locations(2)
|
|
298
|
-
)
|
|
397
|
+
raise ArgumentError, 'a block that accepts the yielded repository is required'
|
|
299
398
|
end
|
|
300
399
|
|
|
301
|
-
#
|
|
400
|
+
# Creates a temporary directory, raising {Git::Error} on failure
|
|
302
401
|
#
|
|
303
|
-
# @param
|
|
402
|
+
# @param prefix [String] the directory name prefix
|
|
304
403
|
#
|
|
305
|
-
# @
|
|
404
|
+
# @return [String] the path of the new directory
|
|
306
405
|
#
|
|
307
|
-
# @
|
|
406
|
+
# @raise [Git::Error] if the directory cannot be created
|
|
308
407
|
#
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
'Use "must_exist:" instead.'
|
|
314
|
-
)
|
|
315
|
-
end
|
|
316
|
-
# Preserve the original Git::Base semantics: when both the deprecated
|
|
317
|
-
# positional `check` and the new `must_exist:` keyword are given, OR
|
|
318
|
-
# them so the more restrictive value wins.
|
|
319
|
-
#
|
|
320
|
-
# NilClass#| is defined in Ruby: nil | false → false, nil | true → true.
|
|
321
|
-
# This means single-argument callers (check only, or must_exist: only)
|
|
322
|
-
# are handled correctly without any nil-special-casing.
|
|
323
|
-
return true if must_exist.nil? && check.nil?
|
|
324
|
-
|
|
325
|
-
must_exist | check
|
|
408
|
+
# @api private
|
|
409
|
+
#
|
|
410
|
+
def context_helpers_mktmpdir(prefix)
|
|
411
|
+
Git::SystemCallGuard.call('Failed to create a temporary directory') { Dir.mktmpdir(prefix) }
|
|
326
412
|
end
|
|
327
413
|
|
|
328
414
|
# Expands `path` and validates existence when required
|
|
@@ -335,13 +421,19 @@ module Git
|
|
|
335
421
|
#
|
|
336
422
|
# @raise [ArgumentError] if `must_exist` is `true` and the path does not exist
|
|
337
423
|
#
|
|
424
|
+
# @raise [Git::Error] if the path cannot be expanded, which happens when
|
|
425
|
+
# `path` is relative and the process working directory has been removed
|
|
426
|
+
#
|
|
338
427
|
def context_helpers_validate_path(path, must_exist)
|
|
339
|
-
|
|
428
|
+
expanded = Git::SystemCallGuard.call('Failed to expand the path') { File.expand_path(path.to_s) }
|
|
429
|
+
|
|
430
|
+
Pathname.new(expanded).tap do |expanded_path|
|
|
340
431
|
raise ArgumentError, "path does not exist: #{expanded_path}" if must_exist && !expanded_path.exist?
|
|
341
432
|
end
|
|
342
433
|
end
|
|
343
434
|
|
|
344
|
-
#
|
|
435
|
+
# Builds a repository of the receiver's class bound to a copy of this
|
|
436
|
+
# execution context with selected overrides, leaving the receiver untouched
|
|
345
437
|
#
|
|
346
438
|
# @param overrides [Hash] execution-context attributes to override
|
|
347
439
|
#
|
|
@@ -350,10 +442,12 @@ module Git
|
|
|
350
442
|
# @option overrides [String, nil] :git_work_dir replacement working directory
|
|
351
443
|
# path
|
|
352
444
|
#
|
|
353
|
-
# @return [
|
|
445
|
+
# @return [Git::Repository] a new repository built from the derived context
|
|
446
|
+
#
|
|
447
|
+
# @api private
|
|
354
448
|
#
|
|
355
|
-
def
|
|
356
|
-
|
|
449
|
+
def context_helpers_derived_repository(**overrides)
|
|
450
|
+
self.class.new(execution_context: @execution_context.dup_with(**overrides))
|
|
357
451
|
end
|
|
358
452
|
end
|
|
359
453
|
end
|