git 5.3.0 → 5.4.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 +16 -0
- data/README.md +1 -1
- data/UPGRADING.md +320 -22
- data/lib/git/branch.rb +9 -5
- data/lib/git/object.rb +56 -8
- data/lib/git/parsers/status.rb +251 -0
- data/lib/git/parsers/worktree.rb +185 -0
- data/lib/git/repository/object_operations.rb +257 -20
- data/lib/git/repository/stashing.rb +532 -52
- data/lib/git/repository/status_operations.rb +56 -8
- data/lib/git/repository/worktree_operations.rb +198 -18
- data/lib/git/stash.rb +31 -2
- data/lib/git/stashes.rb +47 -11
- data/lib/git/status.rb +14 -0
- data/lib/git/status_file_info.rb +258 -0
- data/lib/git/status_info.rb +189 -0
- data/lib/git/tag_info.rb +2 -1
- data/lib/git/version.rb +1 -1
- data/lib/git/worktree.rb +39 -0
- data/lib/git/worktree_info.rb +128 -0
- data/lib/git/worktrees.rb +19 -1
- data/lib/git.rb +4 -0
- metadata +8 -3
|
@@ -2,16 +2,466 @@
|
|
|
2
2
|
|
|
3
3
|
require 'git/commands/stash'
|
|
4
4
|
require 'git/parsers/stash'
|
|
5
|
+
require 'git/repository/shared_private'
|
|
5
6
|
|
|
6
7
|
module Git
|
|
7
8
|
class Repository
|
|
8
9
|
# Facade methods for stash operations
|
|
9
10
|
#
|
|
11
|
+
# Each method maps onto a `git stash` subcommand. Methods that identify or
|
|
12
|
+
# create a stash entry ({#stash_infos}, {#stash_push}, {#stash_store}) return
|
|
13
|
+
# {Git::StashInfo} values; methods that only change or display the stash
|
|
14
|
+
# return git's stdout. Every method that takes a stash ({#stash_apply},
|
|
15
|
+
# {#stash_pop}, {#stash_drop}, {#stash_show}, {#stash_branch}) accepts a
|
|
16
|
+
# {Git::StashInfo}, a `stash@{N}` name, an Integer index (`0` is the most
|
|
17
|
+
# recent entry), or `nil` for the most recent entry. Methods that take
|
|
18
|
+
# options accept them as a trailing positional Hash, so
|
|
19
|
+
# `repo.stash_apply(index: true)` and `repo.stash_apply(nil, opts)` with a
|
|
20
|
+
# stored `opts` Hash both work.
|
|
21
|
+
#
|
|
22
|
+
# {#stashes_all}, {#stash_save}, and {#stash_list} are the legacy surface.
|
|
23
|
+
# They are deprecated and will be removed in v6.0.0.
|
|
24
|
+
#
|
|
10
25
|
# Included by {Git::Repository}.
|
|
11
26
|
#
|
|
12
27
|
# @api private
|
|
13
28
|
#
|
|
14
29
|
module Stashing
|
|
30
|
+
# Returns every stash entry as a {Git::StashInfo}, newest first
|
|
31
|
+
#
|
|
32
|
+
# The order and indices match `git stash list`: the first element is
|
|
33
|
+
# `stash@{0}`, the most recent entry.
|
|
34
|
+
#
|
|
35
|
+
# @example List stash entries (newest first)
|
|
36
|
+
# repo.stash_infos.map(&:name) #=> ["stash@{0}", "stash@{1}"]
|
|
37
|
+
#
|
|
38
|
+
# @example Apply the oldest entry
|
|
39
|
+
# repo.stash_apply(repo.stash_infos.last)
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<Git::StashInfo>] the stash entries, newest first; empty
|
|
42
|
+
# when there are none
|
|
43
|
+
#
|
|
44
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
45
|
+
#
|
|
46
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
47
|
+
#
|
|
48
|
+
def stash_infos
|
|
49
|
+
result = Git::Commands::Stash::List.new(@execution_context).call
|
|
50
|
+
Git::Parsers::Stash.parse_list(result.stdout)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Option keys accepted by {#stash_push}
|
|
54
|
+
STASH_PUSH_ALLOWED_OPTS = %i[
|
|
55
|
+
patch p staged S keep_index k no_keep_index quiet q include_untracked u all a
|
|
56
|
+
message m pathspec_from_file pathspec_file_nul
|
|
57
|
+
].freeze
|
|
58
|
+
private_constant :STASH_PUSH_ALLOWED_OPTS
|
|
59
|
+
|
|
60
|
+
# Save the working tree and index state to a new stash entry
|
|
61
|
+
#
|
|
62
|
+
# The stash list is read before and after the push and the entry counts are
|
|
63
|
+
# compared, so the return value is `nil` whenever git created no entry. This
|
|
64
|
+
# holds with `quiet: true`, which suppresses git's "No local changes to
|
|
65
|
+
# save" message.
|
|
66
|
+
#
|
|
67
|
+
# @overload stash_push(*pathspec, options = {})
|
|
68
|
+
#
|
|
69
|
+
# @example Stash all changes with a message
|
|
70
|
+
# info = repo.stash_push(message: 'WIP: feature work')
|
|
71
|
+
# info.name #=> "stash@{0}"
|
|
72
|
+
# info.message #=> "On main: WIP: feature work"
|
|
73
|
+
#
|
|
74
|
+
# @example Stash only specific paths
|
|
75
|
+
# repo.stash_push('src/a.rb', 'src/b.rb', message: 'partial work')
|
|
76
|
+
#
|
|
77
|
+
# @example Nothing to stash
|
|
78
|
+
# repo.stash_push #=> nil
|
|
79
|
+
#
|
|
80
|
+
# @param pathspec [Array<String>] paths that limit what gets stashed; when
|
|
81
|
+
# empty, all changes are stashed
|
|
82
|
+
#
|
|
83
|
+
# @param options [Hash] options for the push
|
|
84
|
+
#
|
|
85
|
+
# @option options [Boolean, nil] :patch (nil) interactively select hunks to
|
|
86
|
+
# stash (alias: `:p`)
|
|
87
|
+
#
|
|
88
|
+
# @option options [Boolean, nil] :p (nil) alias for `:patch`
|
|
89
|
+
#
|
|
90
|
+
# @option options [Boolean, nil] :staged (nil) stash only the staged changes
|
|
91
|
+
# (alias: `:S`; requires git 2.35+)
|
|
92
|
+
#
|
|
93
|
+
# @option options [Boolean, nil] :S (nil) alias for `:staged` (requires git
|
|
94
|
+
# 2.35+)
|
|
95
|
+
#
|
|
96
|
+
# @option options [Boolean, nil] :keep_index (nil) keep the staged changes
|
|
97
|
+
# in the index (alias: `:k`)
|
|
98
|
+
#
|
|
99
|
+
# @option options [Boolean, nil] :k (nil) alias for `:keep_index`
|
|
100
|
+
#
|
|
101
|
+
# @option options [Boolean, nil] :no_keep_index (nil) do not keep the staged
|
|
102
|
+
# changes in the index
|
|
103
|
+
#
|
|
104
|
+
# @option options [Boolean, nil] :quiet (nil) suppress informational
|
|
105
|
+
# messages (alias: `:q`)
|
|
106
|
+
#
|
|
107
|
+
# @option options [Boolean, nil] :q (nil) alias for `:quiet`
|
|
108
|
+
#
|
|
109
|
+
# @option options [Boolean, nil] :include_untracked (nil) include untracked
|
|
110
|
+
# files in the stash (alias: `:u`)
|
|
111
|
+
#
|
|
112
|
+
# @option options [Boolean, nil] :u (nil) alias for `:include_untracked`
|
|
113
|
+
#
|
|
114
|
+
# @option options [Boolean, nil] :all (nil) include untracked and ignored
|
|
115
|
+
# files in the stash (alias: `:a`)
|
|
116
|
+
#
|
|
117
|
+
# @option options [Boolean, nil] :a (nil) alias for `:all`
|
|
118
|
+
#
|
|
119
|
+
# @option options [String] :message (nil) the stash message (alias: `:m`)
|
|
120
|
+
#
|
|
121
|
+
# @option options [String] :m (nil) alias for `:message`
|
|
122
|
+
#
|
|
123
|
+
# @option options [String] :pathspec_from_file (nil) read pathspecs from the
|
|
124
|
+
# given file; pass `-` to read from standard input
|
|
125
|
+
#
|
|
126
|
+
# @option options [Boolean, nil] :pathspec_file_nul (nil) when used with
|
|
127
|
+
# `:pathspec_from_file`, pathspecs are NUL-separated
|
|
128
|
+
#
|
|
129
|
+
# @return [Git::StashInfo, nil] the new entry, or `nil` when there were no
|
|
130
|
+
# local changes to save
|
|
131
|
+
#
|
|
132
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
133
|
+
#
|
|
134
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
135
|
+
#
|
|
136
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
137
|
+
#
|
|
138
|
+
def stash_push(*pathspec)
|
|
139
|
+
pathspec, opts = Private.split_pathspec_and_opts(pathspec)
|
|
140
|
+
SharedPrivate.assert_valid_opts!(STASH_PUSH_ALLOWED_OPTS, **opts)
|
|
141
|
+
previous_count = stash_infos.size
|
|
142
|
+
Git::Commands::Stash::Push.new(@execution_context).call(*pathspec, **opts)
|
|
143
|
+
entries = stash_infos
|
|
144
|
+
entries.first if entries.size > previous_count
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Option keys accepted by {#stash_apply}
|
|
148
|
+
STASH_APPLY_ALLOWED_OPTS = %i[index quiet q].freeze
|
|
149
|
+
private_constant :STASH_APPLY_ALLOWED_OPTS
|
|
150
|
+
|
|
151
|
+
# Apply a stash entry to the working tree, keeping it in the stash list
|
|
152
|
+
#
|
|
153
|
+
# @example Apply the most recent entry
|
|
154
|
+
# repo.stash_apply #=> "On branch main\nChanges not staged for commit:..."
|
|
155
|
+
#
|
|
156
|
+
# @example Apply an entry from {#stash_infos}
|
|
157
|
+
# repo.stash_apply(repo.stash_infos.last)
|
|
158
|
+
#
|
|
159
|
+
# @example Apply an entry by name and restore the index too
|
|
160
|
+
# repo.stash_apply('stash@{1}', index: true)
|
|
161
|
+
#
|
|
162
|
+
# @param stash [Git::StashInfo, String, Integer, nil] the entry to apply: a
|
|
163
|
+
# {Git::StashInfo}, a `stash@{N}` name, an Integer `N` (`0` is the most
|
|
164
|
+
# recent entry), or `nil` for the most recent entry
|
|
165
|
+
#
|
|
166
|
+
# @param opts [Hash] options for the apply
|
|
167
|
+
#
|
|
168
|
+
# @option opts [Boolean, nil] :index (nil) restore the index state as
|
|
169
|
+
# well as the working tree
|
|
170
|
+
#
|
|
171
|
+
# @option opts [Boolean, nil] :quiet (nil) suppress informational
|
|
172
|
+
# messages (alias: `:q`)
|
|
173
|
+
#
|
|
174
|
+
# @option opts [Boolean, nil] :q (nil) alias for `:quiet`
|
|
175
|
+
#
|
|
176
|
+
# @return [String] git's stdout from the apply
|
|
177
|
+
#
|
|
178
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
179
|
+
#
|
|
180
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
181
|
+
#
|
|
182
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
183
|
+
#
|
|
184
|
+
def stash_apply(stash = nil, opts = {})
|
|
185
|
+
stash, opts = Private.split_stash_and_opts(stash, opts)
|
|
186
|
+
SharedPrivate.assert_valid_opts!(STASH_APPLY_ALLOWED_OPTS, **opts)
|
|
187
|
+
Git::Commands::Stash::Apply.new(@execution_context).call(stash, **opts).stdout
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Option keys accepted by {#stash_pop}
|
|
191
|
+
STASH_POP_ALLOWED_OPTS = %i[index quiet q].freeze
|
|
192
|
+
private_constant :STASH_POP_ALLOWED_OPTS
|
|
193
|
+
|
|
194
|
+
# Apply a stash entry to the working tree and remove it from the stash list
|
|
195
|
+
#
|
|
196
|
+
# @example Pop the most recent entry
|
|
197
|
+
# repo.stash_pop #=> "On branch main\n...Dropped refs/stash@{0} (abc1234...)"
|
|
198
|
+
#
|
|
199
|
+
# @example Pop an entry from {#stash_infos}
|
|
200
|
+
# repo.stash_pop(repo.stash_infos.last)
|
|
201
|
+
#
|
|
202
|
+
# @param stash [Git::StashInfo, String, Integer, nil] the entry to pop: a
|
|
203
|
+
# {Git::StashInfo}, a `stash@{N}` name, an Integer `N` (`0` is the most
|
|
204
|
+
# recent entry), or `nil` for the most recent entry
|
|
205
|
+
#
|
|
206
|
+
# @param opts [Hash] options for the pop
|
|
207
|
+
#
|
|
208
|
+
# @option opts [Boolean, nil] :index (nil) restore the index state as
|
|
209
|
+
# well as the working tree
|
|
210
|
+
#
|
|
211
|
+
# @option opts [Boolean, nil] :quiet (nil) suppress informational
|
|
212
|
+
# messages (alias: `:q`)
|
|
213
|
+
#
|
|
214
|
+
# @option opts [Boolean, nil] :q (nil) alias for `:quiet`
|
|
215
|
+
#
|
|
216
|
+
# @return [String] git's stdout from the pop
|
|
217
|
+
#
|
|
218
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
219
|
+
#
|
|
220
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
221
|
+
#
|
|
222
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
223
|
+
#
|
|
224
|
+
def stash_pop(stash = nil, opts = {})
|
|
225
|
+
stash, opts = Private.split_stash_and_opts(stash, opts)
|
|
226
|
+
SharedPrivate.assert_valid_opts!(STASH_POP_ALLOWED_OPTS, **opts)
|
|
227
|
+
Git::Commands::Stash::Pop.new(@execution_context).call(stash, **opts).stdout
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Option keys accepted by {#stash_drop}
|
|
231
|
+
STASH_DROP_ALLOWED_OPTS = %i[quiet q].freeze
|
|
232
|
+
private_constant :STASH_DROP_ALLOWED_OPTS
|
|
233
|
+
|
|
234
|
+
# Remove a single stash entry from the stash list
|
|
235
|
+
#
|
|
236
|
+
# @example Drop the most recent entry
|
|
237
|
+
# repo.stash_drop #=> "Dropped refs/stash@{0} (abc1234...)"
|
|
238
|
+
#
|
|
239
|
+
# @example Drop an entry from {#stash_infos}
|
|
240
|
+
# repo.stash_drop(repo.stash_infos.last)
|
|
241
|
+
#
|
|
242
|
+
# @param stash [Git::StashInfo, String, Integer, nil] the entry to drop: a
|
|
243
|
+
# {Git::StashInfo}, a `stash@{N}` name, an Integer `N` (`0` is the most
|
|
244
|
+
# recent entry), or `nil` for the most recent entry
|
|
245
|
+
#
|
|
246
|
+
# @param opts [Hash] options for the drop
|
|
247
|
+
#
|
|
248
|
+
# @option opts [Boolean, nil] :quiet (nil) suppress informational
|
|
249
|
+
# messages (alias: `:q`)
|
|
250
|
+
#
|
|
251
|
+
# @option opts [Boolean, nil] :q (nil) alias for `:quiet`
|
|
252
|
+
#
|
|
253
|
+
# @return [String] git's stdout from the drop
|
|
254
|
+
#
|
|
255
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
256
|
+
#
|
|
257
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
258
|
+
#
|
|
259
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
260
|
+
#
|
|
261
|
+
def stash_drop(stash = nil, opts = {})
|
|
262
|
+
stash, opts = Private.split_stash_and_opts(stash, opts)
|
|
263
|
+
SharedPrivate.assert_valid_opts!(STASH_DROP_ALLOWED_OPTS, **opts)
|
|
264
|
+
Git::Commands::Stash::Drop.new(@execution_context).call(stash, **opts).stdout
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Option keys accepted by {#stash_show}
|
|
268
|
+
STASH_SHOW_ALLOWED_OPTS = %i[
|
|
269
|
+
patch numstat raw shortstat unified U include_untracked u no_include_untracked
|
|
270
|
+
only_untracked find_renames M find_copies C find_copies_harder inter_hunk_context dirstat
|
|
271
|
+
].freeze
|
|
272
|
+
private_constant :STASH_SHOW_ALLOWED_OPTS
|
|
273
|
+
|
|
274
|
+
# Show the changes recorded in a stash entry as a diff
|
|
275
|
+
#
|
|
276
|
+
# Without options, git prints a diffstat. The output is returned as git
|
|
277
|
+
# prints it.
|
|
278
|
+
#
|
|
279
|
+
# @example Show the diffstat of the most recent entry
|
|
280
|
+
# repo.stash_show #=> " file.txt | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)"
|
|
281
|
+
#
|
|
282
|
+
# @example Show the full patch of an entry from {#stash_infos}
|
|
283
|
+
# repo.stash_show(repo.stash_infos.last, patch: true)
|
|
284
|
+
#
|
|
285
|
+
# @param stash [Git::StashInfo, String, Integer, nil] the entry to show: a
|
|
286
|
+
# {Git::StashInfo}, a `stash@{N}` name, an Integer `N` (`0` is the most
|
|
287
|
+
# recent entry), or `nil` for the most recent entry
|
|
288
|
+
#
|
|
289
|
+
# @param opts [Hash] options for the show
|
|
290
|
+
#
|
|
291
|
+
# @option opts [Boolean, nil] :patch (nil) show the diff as a patch
|
|
292
|
+
#
|
|
293
|
+
# @option opts [Boolean, nil] :numstat (nil) show per-file insertion and
|
|
294
|
+
# deletion counts
|
|
295
|
+
#
|
|
296
|
+
# @option opts [Boolean, nil] :raw (nil) show per-file mode, object id,
|
|
297
|
+
# and status metadata
|
|
298
|
+
#
|
|
299
|
+
# @option opts [Boolean, nil] :shortstat (nil) show only the summary line
|
|
300
|
+
#
|
|
301
|
+
# @option opts [Integer, String] :unified (nil) number of context lines
|
|
302
|
+
# in the patch (alias: `:U`)
|
|
303
|
+
#
|
|
304
|
+
# @option opts [Integer, String] :U (nil) alias for `:unified`
|
|
305
|
+
#
|
|
306
|
+
# @option opts [Boolean, nil] :include_untracked (nil) include the
|
|
307
|
+
# untracked files recorded in the entry (alias: `:u`; requires git 2.30+)
|
|
308
|
+
#
|
|
309
|
+
# @option opts [Boolean, nil] :u (nil) alias for `:include_untracked`
|
|
310
|
+
# (requires git 2.30+)
|
|
311
|
+
#
|
|
312
|
+
# @option opts [Boolean, nil] :no_include_untracked (nil) exclude the
|
|
313
|
+
# untracked files recorded in the entry (requires git 2.30+)
|
|
314
|
+
#
|
|
315
|
+
# @option opts [Boolean, nil] :only_untracked (nil) show only the
|
|
316
|
+
# untracked files recorded in the entry (requires git 2.30+)
|
|
317
|
+
#
|
|
318
|
+
# @option opts [Boolean, Integer, nil] :find_renames (nil) detect
|
|
319
|
+
# renames, optionally with a similarity threshold (alias: `:M`)
|
|
320
|
+
#
|
|
321
|
+
# @option opts [Boolean, Integer, nil] :M (nil) alias for `:find_renames`
|
|
322
|
+
#
|
|
323
|
+
# @option opts [Boolean, Integer, nil] :find_copies (nil) detect copies
|
|
324
|
+
# as well as renames, optionally with a similarity threshold (alias: `:C`)
|
|
325
|
+
#
|
|
326
|
+
# @option opts [Boolean, Integer, nil] :C (nil) alias for `:find_copies`
|
|
327
|
+
#
|
|
328
|
+
# @option opts [Boolean, nil] :find_copies_harder (nil) inspect unmodified
|
|
329
|
+
# files as copy sources
|
|
330
|
+
#
|
|
331
|
+
# @option opts [Integer, String] :inter_hunk_context (nil) number of
|
|
332
|
+
# context lines between hunks before they are merged
|
|
333
|
+
#
|
|
334
|
+
# @option opts [Boolean, String, nil] :dirstat (nil) include directory
|
|
335
|
+
# statistics, optionally with parameters
|
|
336
|
+
#
|
|
337
|
+
# @return [String] git's stdout from the show
|
|
338
|
+
#
|
|
339
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
340
|
+
#
|
|
341
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
342
|
+
#
|
|
343
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
344
|
+
#
|
|
345
|
+
def stash_show(stash = nil, opts = {})
|
|
346
|
+
stash, opts = Private.split_stash_and_opts(stash, opts)
|
|
347
|
+
SharedPrivate.assert_valid_opts!(STASH_SHOW_ALLOWED_OPTS, **opts)
|
|
348
|
+
Git::Commands::Stash::Show.new(@execution_context).call(stash, **opts).stdout
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# Create and check out a branch from the commit a stash entry was based on
|
|
352
|
+
#
|
|
353
|
+
# Applies the entry on the new branch and, when that succeeds, drops the
|
|
354
|
+
# entry from the stash list.
|
|
355
|
+
#
|
|
356
|
+
# @example Branch from the most recent entry
|
|
357
|
+
# repo.stash_branch('feature') #=> "Switched to a new branch 'feature'\n..."
|
|
358
|
+
#
|
|
359
|
+
# @example Branch from an entry from {#stash_infos}
|
|
360
|
+
# repo.stash_branch('feature', repo.stash_infos.last)
|
|
361
|
+
#
|
|
362
|
+
# @param branch_name [String] the name of the branch to create
|
|
363
|
+
#
|
|
364
|
+
# @param stash [Git::StashInfo, String, Integer, nil] the entry to branch
|
|
365
|
+
# from: a {Git::StashInfo}, a `stash@{N}` name, an Integer `N` (`0` is the
|
|
366
|
+
# most recent entry), or `nil` for the most recent entry
|
|
367
|
+
#
|
|
368
|
+
# @return [String] git's stdout from the branch command
|
|
369
|
+
#
|
|
370
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
371
|
+
#
|
|
372
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
373
|
+
#
|
|
374
|
+
def stash_branch(branch_name, stash = nil)
|
|
375
|
+
Git::Commands::Stash::Branch.new(@execution_context).call(branch_name, stash).stdout
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Create a stash commit without adding it to the stash list
|
|
379
|
+
#
|
|
380
|
+
# The working tree and index are left unchanged. Pass the returned object
|
|
381
|
+
# id to {#stash_store} to add it to the stash list later.
|
|
382
|
+
#
|
|
383
|
+
# @example Create a stash commit
|
|
384
|
+
# repo.stash_create('WIP') #=> "3f8b2d9c..." (the full object id)
|
|
385
|
+
#
|
|
386
|
+
# @example Nothing to stash
|
|
387
|
+
# repo.stash_create #=> nil
|
|
388
|
+
#
|
|
389
|
+
# @param message [String, nil] the message for the stash commit; `nil` for
|
|
390
|
+
# git's default message
|
|
391
|
+
#
|
|
392
|
+
# @return [String, nil] the object id of the stash commit, or `nil` when
|
|
393
|
+
# there were no local changes
|
|
394
|
+
#
|
|
395
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
396
|
+
#
|
|
397
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
398
|
+
#
|
|
399
|
+
def stash_create(message = nil)
|
|
400
|
+
oid = Git::Commands::Stash::Create.new(@execution_context).call(message).stdout.strip
|
|
401
|
+
oid.empty? ? nil : oid
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Option keys accepted by {#stash_store}
|
|
405
|
+
STASH_STORE_ALLOWED_OPTS = %i[message m quiet q].freeze
|
|
406
|
+
private_constant :STASH_STORE_ALLOWED_OPTS
|
|
407
|
+
|
|
408
|
+
# Add a stash commit created by {#stash_create} to the stash list
|
|
409
|
+
#
|
|
410
|
+
# The stash list is read after the store to return the new top entry.
|
|
411
|
+
#
|
|
412
|
+
# @example Store a stash commit
|
|
413
|
+
# oid = repo.stash_create
|
|
414
|
+
# info = repo.stash_store(oid, message: 'saved for later')
|
|
415
|
+
# info.name #=> "stash@{0}"
|
|
416
|
+
# info.message #=> "saved for later"
|
|
417
|
+
#
|
|
418
|
+
# @param commit [String] the object id of the stash commit to store
|
|
419
|
+
#
|
|
420
|
+
# @param opts [Hash] options for the store
|
|
421
|
+
#
|
|
422
|
+
# @option opts [String] :message (nil) the message for the stash entry
|
|
423
|
+
# (alias: `:m`)
|
|
424
|
+
#
|
|
425
|
+
# @option opts [String] :m (nil) alias for `:message`
|
|
426
|
+
#
|
|
427
|
+
# @option opts [Boolean, nil] :quiet (nil) suppress informational
|
|
428
|
+
# messages (alias: `:q`)
|
|
429
|
+
#
|
|
430
|
+
# @option opts [Boolean, nil] :q (nil) alias for `:quiet`
|
|
431
|
+
#
|
|
432
|
+
# @return [Git::StashInfo] the stored entry, now at the top of the stash list
|
|
433
|
+
#
|
|
434
|
+
# @raise [ArgumentError] if unsupported options are provided
|
|
435
|
+
#
|
|
436
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
437
|
+
#
|
|
438
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
439
|
+
#
|
|
440
|
+
def stash_store(commit, opts = {})
|
|
441
|
+
SharedPrivate.assert_valid_opts!(STASH_STORE_ALLOWED_OPTS, **opts)
|
|
442
|
+
Git::Commands::Stash::Store.new(@execution_context).call(commit, **opts)
|
|
443
|
+
stash_infos.first
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# Remove all stash entries
|
|
447
|
+
#
|
|
448
|
+
# Removes all entries from the stash list. Use with caution as this
|
|
449
|
+
# operation cannot be undone.
|
|
450
|
+
#
|
|
451
|
+
# @example Clear all stashes
|
|
452
|
+
# repo.stash_clear #=> ""
|
|
453
|
+
#
|
|
454
|
+
# @return [String] the output from the git stash clear command
|
|
455
|
+
# (typically empty)
|
|
456
|
+
#
|
|
457
|
+
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
458
|
+
#
|
|
459
|
+
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
460
|
+
#
|
|
461
|
+
def stash_clear
|
|
462
|
+
Git::Commands::Stash::Clear.new(@execution_context).call.stdout
|
|
463
|
+
end
|
|
464
|
+
|
|
15
465
|
# Returns all stash entries as an array of index and message pairs
|
|
16
466
|
#
|
|
17
467
|
# Lists all stash entries in the repository ordered from oldest to newest.
|
|
@@ -35,12 +485,20 @@ module Git
|
|
|
35
485
|
# reference: `'stash@{%d}' % (total - 1 - index)`, or pass the string
|
|
36
486
|
# reference directly to {#stash_apply}.
|
|
37
487
|
#
|
|
488
|
+
# @deprecated Use {#stash_infos} instead. It returns {Git::StashInfo} entries
|
|
489
|
+
# newest first with git's own `stash@{N}` indices and the full message.
|
|
490
|
+
# This method will be removed in v6.0.0.
|
|
491
|
+
#
|
|
492
|
+
# @see #stash_infos
|
|
493
|
+
#
|
|
38
494
|
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
39
495
|
#
|
|
40
496
|
def stashes_all
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
497
|
+
Git::Deprecation.warn(
|
|
498
|
+
'Git::Repository#stashes_all is deprecated and will be removed in v6.0.0. ' \
|
|
499
|
+
'Use Git::Repository#stash_infos instead.'
|
|
500
|
+
)
|
|
501
|
+
stash_infos.reverse.each_with_index.map do |info, i|
|
|
44
502
|
message = info.message.sub(/^(?:WIP on|On)\s+[^:]+:\s*/, '')
|
|
45
503
|
[i, message]
|
|
46
504
|
end
|
|
@@ -57,26 +515,27 @@ module Git
|
|
|
57
515
|
#
|
|
58
516
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
59
517
|
#
|
|
60
|
-
# @deprecated Use {#
|
|
518
|
+
# @deprecated Use {#stash_infos} instead and format the entries yourself:
|
|
519
|
+
# `repo.stash_infos.map { |s| "#{s.name}: #{s.message}" }.join("\n")`.
|
|
520
|
+
# This method will be removed in v6.0.0, and a later release will reuse
|
|
521
|
+
# the name for a method returning `Array<Git::StashInfo>`.
|
|
61
522
|
#
|
|
62
|
-
# @see #
|
|
523
|
+
# @see #stash_infos
|
|
63
524
|
#
|
|
64
525
|
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
65
526
|
#
|
|
66
527
|
def stash_list
|
|
67
528
|
Git::Deprecation.warn(
|
|
68
529
|
'Git::Repository#stash_list is deprecated and will be removed in v6.0.0. ' \
|
|
69
|
-
'Use Git::Repository#
|
|
530
|
+
'Use Git::Repository#stash_infos instead.'
|
|
70
531
|
)
|
|
71
|
-
|
|
72
|
-
stashes = Git::Parsers::Stash.parse_list(result.stdout)
|
|
73
|
-
stashes.map { |info| "#{info.name}: #{info.message}" }.join("\n")
|
|
532
|
+
stash_infos.map { |info| "#{info.name}: #{info.message}" }.join("\n")
|
|
74
533
|
end
|
|
75
534
|
|
|
76
535
|
# Save the current working directory and index state to a new stash
|
|
77
536
|
#
|
|
78
537
|
# @example Save current changes
|
|
79
|
-
# repo.stash_save('WIP: feature work')
|
|
538
|
+
# repo.stash_save('WIP: feature work') #=> true
|
|
80
539
|
#
|
|
81
540
|
# @param message [String] the stash message
|
|
82
541
|
#
|
|
@@ -85,59 +544,80 @@ module Git
|
|
|
85
544
|
#
|
|
86
545
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
87
546
|
#
|
|
547
|
+
# @deprecated Use {#stash_push} with the `:message` option instead. It
|
|
548
|
+
# returns the new {Git::StashInfo}, or `nil` when there were no local
|
|
549
|
+
# changes to save. This method will be removed in v6.0.0.
|
|
550
|
+
#
|
|
551
|
+
# @see #stash_push
|
|
552
|
+
#
|
|
88
553
|
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
89
554
|
#
|
|
90
555
|
def stash_save(message) # rubocop:disable Naming/PredicateMethod
|
|
556
|
+
Git::Deprecation.warn(
|
|
557
|
+
'Git::Repository#stash_save is deprecated and will be removed in v6.0.0. ' \
|
|
558
|
+
'Use Git::Repository#stash_push(message: ...) instead.'
|
|
559
|
+
)
|
|
91
560
|
result = Git::Commands::Stash::Push.new(@execution_context).call(message: message)
|
|
92
561
|
!result.stdout.include?('No local changes to save')
|
|
93
562
|
end
|
|
94
563
|
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
# Applies the changes recorded in a stash entry to the working directory
|
|
98
|
-
# without removing the entry from the stash list. Unlike `git stash pop`,
|
|
99
|
-
# the stash entry is preserved after applying.
|
|
100
|
-
#
|
|
101
|
-
# @example Apply the most recent stash
|
|
102
|
-
# repo.stash_apply #=> "HEAD is now at abc1234 Initial commit"
|
|
564
|
+
# Private helpers local to {Git::Repository::Stashing}
|
|
103
565
|
#
|
|
104
|
-
# @
|
|
105
|
-
# repo.stash_apply('stash@{1}') #=> "HEAD is now at abc1234 Initial commit"
|
|
566
|
+
# @api private
|
|
106
567
|
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# given it is passed directly to git as `stash@{N}`, where `0` is the
|
|
110
|
-
# **most recent** stash — the opposite order from {#stashes_all}'s
|
|
111
|
-
# sequential indices, where `0` is the **oldest** stash.
|
|
112
|
-
#
|
|
113
|
-
# @return [String] the output from the git stash apply command
|
|
114
|
-
#
|
|
115
|
-
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
116
|
-
#
|
|
117
|
-
# @see https://git-scm.com/docs/git-stash git-stash documentation
|
|
118
|
-
#
|
|
119
|
-
def stash_apply(id = nil)
|
|
120
|
-
Git::Commands::Stash::Apply.new(@execution_context).call(id).stdout
|
|
121
|
-
end
|
|
568
|
+
module Private
|
|
569
|
+
module_function
|
|
122
570
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
571
|
+
# Separate the stash operand from a positional options Hash
|
|
572
|
+
#
|
|
573
|
+
# The stash-taking facade methods are declared `(stash = nil, opts = {})`.
|
|
574
|
+
# When the caller omits the stash and passes only options
|
|
575
|
+
# (`repo.stash_apply(index: true)`), Ruby binds the Hash to `stash`; this
|
|
576
|
+
# moves it to `opts` so both call shapes reach the command the same way.
|
|
577
|
+
#
|
|
578
|
+
# @example Options passed without a stash
|
|
579
|
+
# Private.split_stash_and_opts({ index: true }, {}) #=> [nil, { index: true }]
|
|
580
|
+
#
|
|
581
|
+
# @example A stash and options
|
|
582
|
+
# Private.split_stash_and_opts('stash@{1}', { index: true }) #=> ["stash@{1}", { index: true }]
|
|
583
|
+
#
|
|
584
|
+
# @param stash [Git::StashInfo, String, Integer, Hash, nil] the stash
|
|
585
|
+
# operand, or the options Hash when the stash was omitted
|
|
586
|
+
#
|
|
587
|
+
# @param trailing [Hash] the second positional argument as bound by Ruby
|
|
588
|
+
#
|
|
589
|
+
# @return [Array(Object, Hash)] the stash operand and the options Hash
|
|
590
|
+
#
|
|
591
|
+
def split_stash_and_opts(stash, trailing)
|
|
592
|
+
return [nil, stash] if stash.is_a?(Hash) && trailing.empty?
|
|
593
|
+
|
|
594
|
+
[stash, trailing]
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# Separate a trailing options Hash from a pathspec list
|
|
598
|
+
#
|
|
599
|
+
# {Git::Repository::Stashing#stash_push} takes a variadic pathspec, so
|
|
600
|
+
# its options arrive as the last positional argument when present.
|
|
601
|
+
#
|
|
602
|
+
# @example Pathspecs followed by options
|
|
603
|
+
# Private.split_pathspec_and_opts(['a.rb', { message: 'WIP' }])
|
|
604
|
+
# #=> [["a.rb"], { message: "WIP" }]
|
|
605
|
+
#
|
|
606
|
+
# @example Pathspecs only
|
|
607
|
+
# Private.split_pathspec_and_opts(['a.rb']) #=> [["a.rb"], {}]
|
|
608
|
+
#
|
|
609
|
+
# @param pathspec [Array<String, Hash>] the positional arguments, possibly
|
|
610
|
+
# ending in an options Hash
|
|
611
|
+
#
|
|
612
|
+
# @return [Array(Array<String>, Hash)] the pathspecs and the options Hash
|
|
613
|
+
#
|
|
614
|
+
def split_pathspec_and_opts(pathspec)
|
|
615
|
+
return [pathspec[0...-1], pathspec.last] if pathspec.last.is_a?(Hash)
|
|
616
|
+
|
|
617
|
+
[pathspec, {}]
|
|
618
|
+
end
|
|
140
619
|
end
|
|
620
|
+
private_constant :Private
|
|
141
621
|
end
|
|
142
622
|
end
|
|
143
623
|
end
|