git 5.6.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -16
  3. data/CONTRIBUTING.md +44 -31
  4. data/README.md +135 -67
  5. data/UPGRADING.md +915 -268
  6. data/git.gemspec +6 -8
  7. data/lib/git/author_info.rb +1 -2
  8. data/lib/git/commands/base.rb +22 -2
  9. data/lib/git/commands/cat_file/raw.rb +0 -68
  10. data/lib/git/commands/maintenance/register.rb +0 -4
  11. data/lib/git/commands/maintenance/run.rb +0 -4
  12. data/lib/git/commands/maintenance/start.rb +0 -4
  13. data/lib/git/commands/maintenance/stop.rb +0 -4
  14. data/lib/git/commands/maintenance/unregister.rb +0 -4
  15. data/lib/git/commands/maintenance.rb +0 -2
  16. data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
  17. data/lib/git/commands/show_ref/exists.rb +0 -5
  18. data/lib/git/commands/show_ref/list.rb +1 -1
  19. data/lib/git/commands/show_ref/verify.rb +1 -1
  20. data/lib/git/commands/show_ref.rb +1 -1
  21. data/lib/git/commands/worktree/repair.rb +0 -5
  22. data/lib/git/diff_path_status.rb +2 -2
  23. data/lib/git/encoding_utils.rb +25 -0
  24. data/lib/git/errors.rb +23 -5
  25. data/lib/git/factories.rb +361 -125
  26. data/lib/git/log.rb +3 -75
  27. data/lib/git/object.rb +4 -185
  28. data/lib/git/path_resolver.rb +104 -36
  29. data/lib/git/repository/branching.rb +21 -189
  30. data/lib/git/repository/committing.rb +0 -25
  31. data/lib/git/repository/context_helpers.rb +249 -155
  32. data/lib/git/repository/diffing.rb +2 -44
  33. data/lib/git/repository/merging.rb +32 -54
  34. data/lib/git/repository/object_operations.rb +121 -338
  35. data/lib/git/repository/remote_operations.rb +7 -219
  36. data/lib/git/repository/staging.rb +6 -184
  37. data/lib/git/repository/stashing.rb +126 -130
  38. data/lib/git/repository/status_operations.rb +1 -54
  39. data/lib/git/repository/worktree_operations.rb +31 -118
  40. data/lib/git/repository.rb +20 -299
  41. data/lib/git/system_call_guard.rb +95 -0
  42. data/lib/git/version.rb +1 -1
  43. data/lib/git.rb +47 -364
  44. metadata +25 -19
  45. data/lib/git/author.rb +0 -46
  46. data/lib/git/branch.rb +0 -693
  47. data/lib/git/branches.rb +0 -194
  48. data/lib/git/remote.rb +0 -191
  49. data/lib/git/stash.rb +0 -124
  50. data/lib/git/stashes.rb +0 -198
  51. data/lib/git/status.rb +0 -384
  52. data/lib/git/worktree.rb +0 -180
  53. data/lib/git/worktrees.rb +0 -152
data/lib/git/status.rb DELETED
@@ -1,384 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Git
4
- # The Status class gets the status of a git repository. It identifies which
5
- # files have been modified, added, or deleted, including untracked files.
6
- # The Status object is an Enumerable of StatusFile objects.
7
- #
8
- # @example Inspect repository status
9
- # repo = Git.open('/path/to/repo')
10
- # status = repo.status
11
- # status.changed.each { |path, _file| puts "Modified: #{path}" }
12
- # status.added.each { |path, _file| puts "Added: #{path}" }
13
- # status.deleted.each { |path, _file| puts "Deleted: #{path}" }
14
- # status.untracked.each { |path, _file| puts "Untracked: #{path}" }
15
- #
16
- # @deprecated Use {Git::StatusInfo}, returned by {Git::Repository#status_info},
17
- # instead; this class will be removed in v6.0.0
18
- #
19
- # @api public
20
- #
21
- class Status
22
- include Enumerable
23
-
24
- # Create a new Status for the given repository
25
- #
26
- # @param base [Git::Repository] the git object backing this status
27
- #
28
- # @deprecated Use {Git::Repository#status_info} instead
29
- #
30
- def initialize(base)
31
- if defined?(Git::Deprecation)
32
- Git::Deprecation.warn(
33
- 'Git::Status is deprecated and will be removed in v6.0.0. ' \
34
- 'Use Git::Repository#status_info instead.'
35
- )
36
- end
37
- @base = base
38
- # The factory returns a hash of file paths to StatusFile objects.
39
- @files = StatusFileFactory.new(base).construct_files
40
- end
41
-
42
- # Return files modified in the index and/or working tree
43
- #
44
- # Includes both staged modifications (index vs HEAD) and unstaged modifications
45
- # (working tree vs index).
46
- #
47
- # @return [Hash{String => Git::Status::StatusFile}] changed files keyed by path
48
- #
49
- def changed = @changed ||= select_files { |f| f.type == 'M' }
50
-
51
- # Return files added to the index that are not yet in HEAD
52
- #
53
- # @return [Hash{String => Git::Status::StatusFile}] added files keyed by path
54
- #
55
- def added = @added ||= select_files { |f| f.type == 'A' }
56
-
57
- # Return files deleted from the index
58
- #
59
- # @return [Hash{String => Git::Status::StatusFile}] deleted files keyed by path
60
- #
61
- def deleted = @deleted ||= select_files { |f| f.type == 'D' }
62
-
63
- # Return files present in the working tree but not tracked by git
64
- #
65
- # @return [Hash{String => Git::Status::StatusFile}] untracked files keyed by path
66
- #
67
- def untracked = @untracked ||= select_files(&:untracked)
68
-
69
- # Return `true` if `file` has been modified in the index or working tree
70
- #
71
- # @param file [String] the repository-relative path to check
72
- #
73
- # @return [Boolean] `true` if the file has been modified
74
- #
75
- def changed?(file) = file_in_collection?(:changed, file)
76
-
77
- # Return `true` if `file` has been added to the index
78
- #
79
- # @param file [String] the repository-relative path to check
80
- #
81
- # @return [Boolean] `true` if the file has been added
82
- #
83
- def added?(file) = file_in_collection?(:added, file)
84
-
85
- # Return `true` if `file` has been deleted from the index
86
- #
87
- # @param file [String] the repository-relative path to check
88
- #
89
- # @return [Boolean] `true` if the file has been deleted
90
- #
91
- def deleted?(file) = file_in_collection?(:deleted, file)
92
-
93
- # Return `true` if `file` is not tracked by git
94
- #
95
- # @param file [String] the repository-relative path to check
96
- #
97
- # @return [Boolean] `true` if the file is untracked
98
- #
99
- def untracked?(file) = file_in_collection?(:untracked, file)
100
-
101
- # Return the {Git::Status::StatusFile} for the given path
102
- #
103
- # @param file [String] the repository-relative path
104
- #
105
- # @return [Git::Status::StatusFile, nil] the status file, or `nil` if not found
106
- #
107
- def [](file) = @files[file]
108
-
109
- # Iterate over all status files
110
- #
111
- # @overload each
112
- #
113
- # @return [Enumerator<Git::Status::StatusFile>] an enumerator over all status files
114
- #
115
- # @overload each(&block)
116
- #
117
- # @return [Array<Git::Status::StatusFile>] the full list of status files
118
- #
119
- # @yield [file] each {Git::Status::StatusFile} in the repository
120
- #
121
- # @yieldparam file [Git::Status::StatusFile] a single file's status
122
- #
123
- # @yieldreturn [void]
124
- #
125
- def each(&) = @files.values.each(&)
126
-
127
- # Return a formatted multi-line string representation of the status
128
- #
129
- # @return [String] one indented block per file showing its SHA, mode, type,
130
- # stage, and untracked flag
131
- #
132
- def pretty
133
- map { |file| pretty_file(file) }.join << "\n"
134
- end
135
-
136
- private
137
-
138
- # Format a single file's status as an indented multi-line string
139
- #
140
- # @param file [Git::Status::StatusFile] the file to format
141
- #
142
- # @return [String] the formatted status block for this file
143
- #
144
- def pretty_file(file)
145
- <<~FILE
146
- #{file.path}
147
- \tsha(r) #{file.sha_repo} #{file.mode_repo}
148
- \tsha(i) #{file.sha_index} #{file.mode_index}
149
- \ttype #{file.type}
150
- \tstage #{file.stage}
151
- \tuntrac #{file.untracked}
152
- FILE
153
- end
154
-
155
- # Return a hash of files for which the block returns a truthy value
156
- #
157
- # @return [Hash{String => Git::Status::StatusFile}] matching files keyed by path
158
- #
159
- # @yield [file] each {Git::Status::StatusFile} in the repository
160
- #
161
- # @yieldparam file [Git::Status::StatusFile] a single file's status
162
- #
163
- # @yieldreturn [Boolean] truthy to include the file in the result
164
- #
165
- def select_files(&block)
166
- @files.select { |_path, file| block.call(file) }
167
- end
168
-
169
- # Return `true` if `file_path` exists in the named status collection
170
- #
171
- # @param collection_name [Symbol] the collection to check (e.g. `:changed`)
172
- #
173
- # @param file_path [String] the repository-relative path to look up
174
- #
175
- # @return [Boolean] `true` if the path is present in the collection
176
- #
177
- def file_in_collection?(collection_name, file_path)
178
- collection = public_send(collection_name)
179
- if ignore_case?
180
- downcased_keys(collection_name).include?(file_path.downcase)
181
- else
182
- collection.key?(file_path)
183
- end
184
- end
185
-
186
- # Return a memoized set of downcased keys for the named collection
187
- #
188
- # @param collection_name [Symbol] the collection whose keys to downcase
189
- #
190
- # @return [Set<String>] the lowercased path keys
191
- #
192
- def downcased_keys(collection_name)
193
- @_downcased_keys ||= {}
194
- @_downcased_keys[collection_name] ||=
195
- public_send(collection_name).keys.to_set(&:downcase)
196
- end
197
-
198
- # Return `true` when git is configured to ignore filename case
199
- #
200
- # Reads `core.ignoreCase` with {Git::Repository#config_get}. Returns `false`
201
- # when the config value is absent.
202
- #
203
- # @return [Boolean] `true` when `core.ignoreCase` is `"true"`
204
- #
205
- def ignore_case?
206
- return @ignore_case if defined?(@ignore_case)
207
-
208
- @ignore_case = (@base.config_get('core.ignoreCase')&.value == 'true')
209
- end
210
- end
211
- end
212
-
213
- module Git
214
- class Status
215
- # Represents a single file's status in the git repository. Each instance
216
- # holds information about a file's state in the index and working tree.
217
- #
218
- # @deprecated Use {Git::StatusFileInfo}, held by {Git::StatusInfo#files},
219
- # instead; this class will be removed in v6.0.0
220
- #
221
- # @api public
222
- #
223
- class StatusFile
224
- # The repository-relative file path
225
- #
226
- # @return [String] the path
227
- attr_reader :path
228
-
229
- # The change type for this file
230
- #
231
- # @return [String, nil] `"M"` for modified, `"A"` for added, `"D"` for deleted,
232
- # or `nil` when not applicable
233
- attr_reader :type
234
-
235
- # The merge stage for this file
236
- #
237
- # @return [String, nil] `"0"` for normal entries, or a non-zero value during
238
- # a merge conflict
239
- attr_reader :stage
240
-
241
- # The file mode recorded in the index
242
- #
243
- # @return [String, nil] the octal file mode (e.g. `"100644"`), or `nil`
244
- attr_reader :mode_index
245
-
246
- # The file mode recorded in HEAD
247
- #
248
- # @return [String, nil] the octal file mode (e.g. `"100644"`), or `nil`
249
- attr_reader :mode_repo
250
-
251
- # The SHA of the index version of this file
252
- #
253
- # @return [String, nil] the SHA-1 hex digest, or `nil` if unavailable
254
- attr_reader :sha_index
255
-
256
- # The SHA of the HEAD version of this file
257
- #
258
- # @return [String, nil] the SHA-1 hex digest, or `nil` if unavailable
259
- attr_reader :sha_repo
260
-
261
- # Whether this file is untracked
262
- #
263
- # @return [Boolean, nil] `true` when the file is not tracked by git
264
- attr_reader :untracked
265
-
266
- # Initialize a new StatusFile with the given git object and data hash
267
- #
268
- # @param base [Git::Repository] the git object
269
- #
270
- # @param hash [Hash] raw status data for this file
271
- #
272
- def initialize(base, hash)
273
- @base = base
274
- @path = hash[:path]
275
- @type = hash[:type]
276
- @stage = hash[:stage]
277
- @mode_index = hash[:mode_index]
278
- @mode_repo = hash[:mode_repo]
279
- @sha_index = hash[:sha_index]
280
- @sha_repo = hash[:sha_repo]
281
- @untracked = hash[:untracked]
282
- end
283
-
284
- # Return a blob object for the index or repo version of this file
285
- #
286
- # @param type [Symbol] `:index` (default) for the index version, or
287
- # `:repo` for the HEAD version
288
- #
289
- # @return [Git::Object::Blob, nil] the blob object, or `nil` if no SHA
290
- # is available for the requested version
291
- #
292
- def blob(type = :index)
293
- sha = type == :repo ? sha_repo : (sha_index || sha_repo)
294
- @base.object(sha) if sha
295
- end
296
- end
297
- end
298
- end
299
-
300
- module Git
301
- class Status
302
- # A factory class responsible for fetching git status data and building
303
- # a hash of StatusFile objects.
304
- #
305
- # @api private
306
- #
307
- class StatusFileFactory
308
- # Create a new factory backed by the given git object
309
- #
310
- # @param base [Git::Repository] the git object used as the status data provider
311
- #
312
- def initialize(base)
313
- @base = base
314
- end
315
-
316
- # Gather all status data and build a hash of file paths to StatusFile objects
317
- #
318
- # @return [Hash{String => Git::Status::StatusFile}] file paths mapped to
319
- # their status objects
320
- #
321
- def construct_files
322
- files_data = fetch_all_files_data
323
- files_data.transform_values do |data|
324
- StatusFile.new(@base, data)
325
- end
326
- end
327
-
328
- private
329
-
330
- # Fetch and merge status information from multiple git commands
331
- #
332
- # @return [Hash{String => Hash}] raw per-file status data keyed by path
333
- #
334
- def fetch_all_files_data
335
- files = @base.ls_files # Start with files tracked in the index.
336
- merge_untracked_files(files)
337
- merge_modified_files(files)
338
- merge_head_diffs(files)
339
- files
340
- end
341
-
342
- # Merge untracked working-tree files into `files`
343
- #
344
- # @param files [Hash] the in-progress files hash to update in place
345
- #
346
- # @return [void]
347
- #
348
- def merge_untracked_files(files)
349
- @base.untracked_files.each do |file|
350
- files[file] = { path: file, untracked: true }
351
- end
352
- end
353
-
354
- # Merge index-versus-working-tree diff data into `files`
355
- #
356
- # @param files [Hash] the in-progress files hash to update in place
357
- #
358
- # @return [void]
359
- #
360
- def merge_modified_files(files)
361
- # Merge changes between the index and the working directory.
362
- @base.diff_files.each do |path, data|
363
- (files[path] ||= {}).merge!(data)
364
- end
365
- end
366
-
367
- # Merge HEAD-versus-index diff data into `files`, if commits exist
368
- #
369
- # @param files [Hash] the in-progress files hash to update in place
370
- #
371
- # @return [void]
372
- #
373
- def merge_head_diffs(files)
374
- is_empty = @base.no_commits?
375
- return if is_empty
376
-
377
- # Merge changes between HEAD and the index.
378
- @base.diff_index('HEAD').each do |path, data|
379
- (files[path] ||= {}).merge!(data)
380
- end
381
- end
382
- end
383
- end
384
- end
data/lib/git/worktree.rb DELETED
@@ -1,180 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Git
4
- # A worktree in a Git repository
5
- #
6
- # Represents a single linked or main worktree. Constructed by
7
- # {Git::Repository::WorktreeOperations#worktree} or populated by
8
- # {Git::Worktrees}.
9
- #
10
- # @example Add and remove a linked worktree
11
- # worktree = repo.worktree('/path/to/new-worktree')
12
- # worktree.add
13
- # worktree.remove
14
- #
15
- # @deprecated Use {Git::Repository::WorktreeOperations#worktree_list} and the
16
- # path-based worktree operations on {Git::Repository} instead
17
- #
18
- # {Git::Repository::WorktreeOperations#worktree_list} returns immutable
19
- # {Git::WorktreeInfo} value objects. Operations that lived on this class are
20
- # called on the repository with the worktree path instead (for example
21
- # {Git::Repository::WorktreeOperations#worktree_add} and
22
- # {Git::Repository::WorktreeOperations#worktree_remove}). {#gcommit},
23
- # {#add}, and {#remove} each emit a deprecation warning; the `dir`, `full`,
24
- # `to_s`, and `to_a` readers do not.
25
- #
26
- # @api public
27
- #
28
- class Worktree
29
- # Full worktree descriptor including the optional commitish
30
- #
31
- # @return [String] the filesystem path, space-separated with the commitish
32
- # when one was given at construction time
33
- #
34
- attr_accessor :full
35
-
36
- # Filesystem path of this worktree
37
- #
38
- # @return [String] the filesystem path of the worktree directory
39
- #
40
- attr_accessor :dir
41
-
42
- # Creates a new Worktree object
43
- #
44
- # @param base [Git::Repository] the repository that owns this
45
- # worktree
46
- #
47
- # @param dir [String] filesystem path of the worktree
48
- #
49
- # @param gcommit [String, nil] commitish associated with the worktree;
50
- # when non-nil it is appended to {#full}
51
- #
52
- # @return [void]
53
- #
54
- def initialize(base, dir, gcommit = nil)
55
- @full = dir
56
- @full += " #{gcommit}" unless gcommit.nil?
57
- @base = base
58
- @dir = dir
59
- @gcommit = gcommit
60
- end
61
-
62
- # Returns the commit (or commitish string) associated with this worktree
63
- #
64
- # When a commitish string was supplied at construction time (e.g. by
65
- # {Git::Worktrees} which passes the raw SHA from `git worktree list`), that
66
- # string is returned as-is. Otherwise the value is lazily resolved on first
67
- # call via `worktree_repository.gcommit(@full)` and the result is memoized.
68
- #
69
- # @example When resolved lazily (no commitish at construction)
70
- # worktree = repo.worktree('/path/to/wt')
71
- # worktree.gcommit # => #<Git::Object::Commit ...>
72
- #
73
- # @example When the commitish was given at construction
74
- # worktree = repo.worktrees['/path/to/wt']
75
- # worktree.gcommit # => "4bef5ab8c9..." (raw SHA string)
76
- #
77
- # @return [Git::Object::Commit, String] a commit object when lazily
78
- # resolved, or the raw commitish string when pre-set at construction
79
- #
80
- # @raise [Git::FailedError] if git must resolve the commit and exits with a
81
- # non-zero exit status
82
- #
83
- # @deprecated Use {Git::WorktreeInfo#head} from
84
- # {Git::Repository::WorktreeOperations#worktree_list} instead
85
- #
86
- # `head` is always the commit SHA as a `String` (or `nil` for a bare main
87
- # worktree). Call `repo.gcommit(info.head)` for the commit object.
88
- #
89
- # @see Git::WorktreeInfo#head
90
- #
91
- def gcommit
92
- Git::Deprecation.warn(
93
- 'Git::Worktree#gcommit is deprecated and will be removed in v6.0.0. ' \
94
- 'Use Git::WorktreeInfo#head from Git::Repository#worktree_list instead.'
95
- )
96
- @gcommit ||= worktree_repository.gcommit(@full)
97
- @gcommit
98
- end
99
-
100
- # Creates this worktree on disk
101
- #
102
- # Runs `git worktree add` for {#dir}, optionally at the commitish passed
103
- # at construction time.
104
- #
105
- # @example Add a worktree
106
- # worktree = repo.worktree('/path/to/new-worktree')
107
- # worktree.add
108
- #
109
- # @return [String] stdout from the git command
110
- #
111
- # @raise [Git::FailedError] if git exits with a non-zero exit status
112
- #
113
- # @deprecated Use {Git::Repository::WorktreeOperations#worktree_add} instead
114
- #
115
- # @see Git::Repository::WorktreeOperations#worktree_add
116
- #
117
- def add
118
- Git::Deprecation.warn(
119
- 'Git::Worktree#add is deprecated and will be removed in v6.0.0. ' \
120
- 'Use Git::Repository#worktree_add instead.'
121
- )
122
- worktree_repository.worktree_add(@dir, @gcommit)
123
- end
124
-
125
- # Removes this worktree from disk
126
- #
127
- # Runs `git worktree remove` for {#dir}.
128
- #
129
- # @example Remove a worktree
130
- # worktree.remove
131
- #
132
- # @return [String] stdout from the git command (typically empty)
133
- #
134
- # @raise [Git::FailedError] if git exits with a non-zero exit status
135
- #
136
- # @deprecated Use {Git::Repository::WorktreeOperations#worktree_remove} instead
137
- #
138
- # @see Git::Repository::WorktreeOperations#worktree_remove
139
- #
140
- def remove
141
- Git::Deprecation.warn(
142
- 'Git::Worktree#remove is deprecated and will be removed in v6.0.0. ' \
143
- 'Use Git::Repository#worktree_remove instead.'
144
- )
145
- worktree_repository.worktree_remove(@dir)
146
- end
147
-
148
- # Returns an array containing the full worktree descriptor
149
- #
150
- # @example Get the descriptor array
151
- # worktree.to_a # => ["/path/to/worktree"]
152
- #
153
- # @return [Array<String>] array containing the full worktree descriptor
154
- #
155
- def to_a
156
- [@full]
157
- end
158
-
159
- # Returns the full worktree descriptor as a string
160
- #
161
- # @example Get the descriptor string
162
- # worktree.to_s # => "/path/to/worktree"
163
- #
164
- # @return [String] the full worktree descriptor (path and optional commitish)
165
- #
166
- def to_s
167
- @full
168
- end
169
-
170
- private
171
-
172
- # @return [Git::Repository] the repository used for worktree operations
173
- #
174
- # @api private
175
- #
176
- def worktree_repository
177
- @base
178
- end
179
- end
180
- end
data/lib/git/worktrees.rb DELETED
@@ -1,152 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Git
4
- # Collection of all Git worktrees in a repository
5
- #
6
- # Wraps every linked and main worktree and provides enumeration and
7
- # path-based lookup.
8
- #
9
- # @example Enumerate all worktrees
10
- # worktrees = repo.worktrees
11
- # worktrees.each { |wt| puts wt.dir }
12
- #
13
- # @deprecated Use {Git::Repository::WorktreeOperations#worktree_list} instead
14
- #
15
- # {Git::Repository::WorktreeOperations#worktree_list} returns
16
- # `Array<Git::WorktreeInfo>` (immutable value objects). Look a worktree up
17
- # by path with `worktree_list.find { |w| w.path == path }` in place of
18
- # {#[]}, and call {Git::Repository::WorktreeOperations#worktree_prune} in
19
- # place of {#prune}. Constructing a `Git::Worktrees` emits a deprecation
20
- # warning.
21
- #
22
- # @api public
23
- #
24
- class Worktrees
25
- include Enumerable
26
-
27
- # Creates a new Worktrees collection populated from the given repository
28
- #
29
- # @param base [Git::Repository] the repository to enumerate
30
- # worktrees from
31
- #
32
- # @return [void]
33
- #
34
- # @raise [Git::FailedError] if git exits with a non-zero exit status
35
- #
36
- # @deprecated Use {Git::Repository::WorktreeOperations#worktree_list} instead
37
- #
38
- # @see Git::Repository::WorktreeOperations#worktree_list
39
- #
40
- def initialize(base)
41
- Git::Deprecation.warn(
42
- 'Git::Worktrees is deprecated and will be removed in v6.0.0. ' \
43
- 'Use Git::Repository#worktree_list instead.'
44
- )
45
- @worktrees = {}
46
-
47
- @base = base
48
-
49
- # worktrees_all is deprecated too; silence it so one Git::Worktrees.new emits one warning
50
- Git::Deprecation.silence { worktree_repository.worktrees_all }.each do |w|
51
- @worktrees[w[0]] = Git::Worktree.new(@base, w[0], w[1])
52
- end
53
- end
54
-
55
- # Returns the number of worktrees in the collection
56
- #
57
- # @example Count all worktrees
58
- # repo.worktrees.size # => 2
59
- #
60
- # @return [Integer] the total number of worktrees
61
- #
62
- def size
63
- @worktrees.size
64
- end
65
-
66
- # Iterates over every worktree in the collection
67
- #
68
- # @overload each
69
- #
70
- # @example Get an enumerator over all worktrees
71
- # enum = repo.worktrees.each
72
- #
73
- # @return [Enumerator<Git::Worktree>] an enumerator over all worktrees
74
- #
75
- # @overload each(&block)
76
- #
77
- # @example Print every worktree path
78
- # repo.worktrees.each { |wt| puts wt.dir }
79
- #
80
- # @return [Array<Git::Worktree>] the full list of worktrees
81
- #
82
- # @yield [worktree] passes each worktree to the block
83
- #
84
- # @yieldparam worktree [Git::Worktree] a worktree in the repository
85
- #
86
- # @yieldreturn [void]
87
- #
88
- def each(&)
89
- @worktrees.values.each(&)
90
- end
91
-
92
- # Returns the worktree with the given path
93
- #
94
- # Supports lookup by the filesystem path of the worktree directory or by
95
- # the full worktree descriptor (path and optional commitish).
96
- #
97
- # @example Look up a worktree by path
98
- # repo.worktrees['/path/to/linked-worktree']
99
- #
100
- # @param worktree_name [#to_s] the path (or full descriptor) of the
101
- # worktree to retrieve
102
- #
103
- # @return [Git::Worktree, nil] the matching worktree, or `nil` if not found
104
- #
105
- def [](worktree_name)
106
- @worktrees.values.each_with_object(@worktrees) do |worktree, worktrees|
107
- worktrees[worktree.full] ||= worktree
108
- end[worktree_name.to_s]
109
- end
110
-
111
- # Returns a string listing all worktrees, one per line
112
- #
113
- # @example Display all worktrees
114
- # puts repo.worktrees.to_s
115
- #
116
- # @return [String] a newline-separated listing of worktree descriptors
117
- #
118
- def to_s
119
- out = +''
120
- @worktrees.each_value do |b|
121
- out << b.to_s << "\n"
122
- end
123
- out
124
- end
125
-
126
- # Removes stale administrative files for worktrees that no longer exist
127
- #
128
- # Runs `git worktree prune` to clean up any lingering worktree metadata
129
- # for linked worktrees whose directories have been deleted.
130
- #
131
- # @example Prune stale worktree metadata
132
- # repo.worktrees.prune
133
- #
134
- # @return [String] stdout from the git command (typically empty)
135
- #
136
- # @raise [Git::FailedError] if git exits with a non-zero exit status
137
- #
138
- def prune
139
- worktree_repository.worktree_prune
140
- end
141
-
142
- private
143
-
144
- # @return [Git::Repository] the repository used to enumerate worktrees
145
- #
146
- # @api private
147
- #
148
- def worktree_repository
149
- @base
150
- end
151
- end
152
- end