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
@@ -2,9 +2,7 @@
2
2
 
3
3
  require 'git/commands/worktree'
4
4
  require 'git/parsers/worktree'
5
- require 'git/worktree'
6
5
  require 'git/worktree_info'
7
- require 'git/worktrees'
8
6
 
9
7
  module Git
10
8
  class Repository
@@ -46,66 +44,56 @@ module Git
46
44
  Git::Parsers::Worktree.parse_list(result.stdout)
47
45
  end
48
46
 
49
- # Returns all worktrees as an array of directory and SHA pairs
50
- #
51
- # Lists the main worktree and all linked worktrees. The main worktree of
52
- # a bare repository has no checked-out commit and is omitted.
53
- #
54
- # @example List all worktrees
55
- # repo.worktrees_all
56
- # #=> [["/path/to/main", "4bef5ab..."], ["/tmp/worktree-1", "b8c6320..."]]
57
- #
58
- # @return [Array<Array(String, String)>] array of `[directory, sha]` pairs
59
- #
60
- # `directory` is the worktree path reported by git (absolute or relative,
61
- # depending on repository configuration); `sha` is the full SHA of the
62
- # checked-out HEAD commit
63
- #
64
- # @raise [Git::FailedError] if git exits with a non-zero exit status
65
- #
66
- # @deprecated Use {#worktree_list} instead
67
- #
68
- # {#worktree_list} returns one {Git::WorktreeInfo} per worktree, with
69
- # `path` and `head` in place of the pair, and includes the main worktree
70
- # of a bare repository.
71
- #
72
- # @see #worktree_list
73
- #
74
- # @see https://git-scm.com/docs/git-worktree git-worktree documentation
75
- #
76
- def worktrees_all
77
- Git::Deprecation.warn(
78
- 'Git::Repository#worktrees_all is deprecated and will be removed in v6.0.0. ' \
79
- 'Use Git::Repository#worktree_list instead.'
80
- )
81
- worktree_list.reject { |worktree| worktree.head.nil? }.map { |worktree| [worktree.path, worktree.head] }
82
- end
83
-
84
47
  # Create a new linked worktree at the given directory
85
48
  #
49
+ # Returns the entry that {#worktree_list} reports for the new worktree.
50
+ # Its `path` is the directory as git records it: absolute, with symlinks
51
+ # resolved, so `/tmp/feature` on macOS comes back as
52
+ # `/private/tmp/feature`.
53
+ #
86
54
  # @example Create a worktree at a path (auto-creates a branch)
87
- # repo.worktree_add('/tmp/feature')
55
+ # info = repo.worktree_add('/tmp/feature')
56
+ # info.path #=> "/private/tmp/feature"
57
+ # info.branch #=> "refs/heads/feature"
88
58
  #
89
59
  # @example Create a worktree and check out an existing commitish
90
- # repo.worktree_add('/tmp/hotfix', 'main')
60
+ # repo.worktree_add('/tmp/hotfix', 'main').head
61
+ # #=> "4bef5ab0c8e7c19c6be2c0f55ccd45eec1f3d32a"
91
62
  #
92
- # @param dir [String] filesystem path for the new worktree
63
+ # @param dir [String] filesystem path for the new worktree, absolute or
64
+ # relative to the current directory
93
65
  #
94
66
  # @param commitish [String, nil] branch, tag, or commit to check out
95
67
  #
96
- # When `nil`, git creates a new branch named after the final path component
68
+ # When `nil`, git checks out the branch named after the final path
69
+ # component, creating it when no such branch exists
97
70
  #
98
- # @return [String] the output from the git worktree add command
71
+ # @return [Git::WorktreeInfo] the new worktree's entry from {#worktree_list}
99
72
  #
100
73
  # @raise [Git::FailedError] if git exits with a non-zero exit status
101
74
  #
75
+ # @raise [Git::UnexpectedResultError] if the worktree listing cannot be
76
+ # parsed or does not contain an entry for the new worktree's directory
77
+ # (for example, it was removed by another process before the lookup)
78
+ #
79
+ # @note When `commitish` is `nil` and git creates the branch, it does so
80
+ # before it creates the worktree, so a failure after that point leaves
81
+ # the new branch in place without a worktree. A branch that already
82
+ # existed is left as it was.
83
+ #
102
84
  # @see https://git-scm.com/docs/git-worktree git-worktree documentation
103
85
  #
104
86
  def worktree_add(dir, commitish = nil)
105
87
  args = [dir]
106
88
  args << commitish unless commitish.nil?
107
89
 
108
- Git::Commands::Worktree::Add.new(@execution_context).call(*args).stdout
90
+ Git::Commands::Worktree::Add.new(@execution_context).call(*args)
91
+
92
+ # git records the path as given with symlinks resolved, so compare by
93
+ # identity rather than by string: this covers symlinks, case-insensitive
94
+ # filesystems, and Unicode normalization on macOS
95
+ worktree_list.find { |worktree| File.identical?(worktree.path, dir) } ||
96
+ raise(Git::UnexpectedResultError, "worktree was created but not found in the worktree list: #{dir}")
109
97
  end
110
98
 
111
99
  # Remove a linked worktree
@@ -235,8 +223,6 @@ module Git
235
223
  #
236
224
  # @raise [Git::FailedError] if git exits with a non-zero exit status
237
225
  #
238
- # @raise [Git::VersionError] if the installed git is older than 2.29.0
239
- #
240
226
  # @see https://git-scm.com/docs/git-worktree git-worktree documentation
241
227
  #
242
228
  def worktree_repair(*paths)
@@ -261,79 +247,6 @@ module Git
261
247
  def worktree_prune
262
248
  Git::Commands::Worktree::Prune.new(@execution_context).call.stdout
263
249
  end
264
-
265
- # Return a {Git::Worktree} object for the given directory and optional commitish
266
- #
267
- # This is a factory method — it constructs the domain object but does not
268
- # immediately execute any git commands.
269
- #
270
- # @example Get a worktree object for a new path
271
- # wt = repo.worktree('/tmp/feature')
272
- #
273
- # @example Get a worktree object for a specific branch or commit
274
- # wt = repo.worktree('/tmp/hotfix', 'main')
275
- #
276
- # @param dir [String] filesystem path for the worktree
277
- #
278
- # @param commitish [String, nil] branch, tag, or commit to associate with
279
- # the worktree; `nil` means no commitish is specified
280
- #
281
- # @return [Git::Worktree] a worktree domain object for the given path
282
- #
283
- # @deprecated Use {#worktree_add} and {#worktree_remove} instead
284
- #
285
- # `repo.worktree(dir, commitish).add` becomes
286
- # `repo.worktree_add(dir, commitish)` and `repo.worktree(dir).remove`
287
- # becomes `repo.worktree_remove(dir)`. Read a worktree's checked-out
288
- # commit from {Git::WorktreeInfo#head} via {#worktree_list}.
289
- #
290
- # @see #worktree_add
291
- #
292
- # @see #worktree_remove
293
- #
294
- def worktree(dir, commitish = nil)
295
- Git::Deprecation.warn(
296
- 'Git::Repository#worktree is deprecated and will be removed in v6.0.0. ' \
297
- 'Use Git::Repository#worktree_add and Git::Repository#worktree_remove instead.'
298
- )
299
- Git::Worktree.new(self, dir, commitish)
300
- end
301
-
302
- # Return a {Git::Worktrees} collection of all worktrees (main and linked)
303
- #
304
- # The collection is populated eagerly when this method is called (git runs
305
- # at construction time). It is enumerable and supports indexed access by
306
- # worktree path.
307
- #
308
- # @example Iterate over all worktrees
309
- # repo.worktrees.each { |wt| puts wt.dir }
310
- #
311
- # @example Count worktrees
312
- # repo.worktrees.size
313
- #
314
- # @example Access a specific worktree by path
315
- # repo.worktrees['/tmp/feature']
316
- #
317
- # @return [Git::Worktrees] an enumerable collection of all worktrees
318
- #
319
- # @raise [Git::FailedError] if git exits with a non-zero exit status
320
- #
321
- # @deprecated Use {#worktree_list} instead
322
- #
323
- # {#worktree_list} returns `Array<Git::WorktreeInfo>`. Look a worktree up
324
- # by path with `worktree_list.find { |w| w.path == path }` in place of
325
- # `worktrees[path]`, and call {#worktree_prune} in place of
326
- # `worktrees.prune`. Calling this method emits one deprecation warning.
327
- #
328
- # @see #worktree_list
329
- #
330
- def worktrees
331
- Git::Deprecation.warn(
332
- 'Git::Repository#worktrees is deprecated and will be removed in v6.0.0. ' \
333
- 'Use Git::Repository#worktree_list instead.'
334
- )
335
- Git::Worktrees.new(self)
336
- end
337
250
  end
338
251
  end
339
252
  end
@@ -15,11 +15,11 @@ require 'git/repository/maintenance'
15
15
  require 'git/repository/merging'
16
16
  require 'git/repository/object_operations'
17
17
  require 'git/repository/remote_operations'
18
- require 'git/repository/shared_private'
19
18
  require 'git/repository/staging'
20
19
  require 'git/repository/stashing'
21
20
  require 'git/repository/status_operations'
22
21
  require 'git/repository/worktree_operations'
22
+ require 'git/system_call_guard'
23
23
 
24
24
  module Git
25
25
  # The main public interface for interacting with a Git repository
@@ -46,7 +46,7 @@ module Git
46
46
  #
47
47
  # @api public
48
48
  #
49
- class Repository # rubocop:disable Metrics/ClassLength
49
+ class Repository
50
50
  include Git::Configuring
51
51
  include Git::Repository::Branching
52
52
  include Git::Repository::ContextHelpers
@@ -63,39 +63,6 @@ module Git
63
63
  include Git::Repository::StatusOperations
64
64
  include Git::Repository::WorktreeOperations
65
65
 
66
- # Allowed keyword options for deprecated config write operations
67
- #
68
- # @return [Array<Symbol>] accepted option keys
69
- #
70
- CONFIG_SET_ALLOWED_OPTS = %i[file].freeze
71
- private_constant :CONFIG_SET_ALLOWED_OPTS
72
-
73
- # Allowed keyword options for deprecated config read operations
74
- #
75
- # @return [Array<Symbol>] accepted option keys
76
- #
77
- CONFIG_READ_ALLOWED_OPTS = %i[file].freeze
78
- private_constant :CONFIG_READ_ALLOWED_OPTS
79
-
80
- # Deprecation warning emitted by {#config}
81
- #
82
- # @return [String] the warning message shown to callers
83
- #
84
- CONFIG_DEPRECATION_WARNING =
85
- 'Git::Repository#config is deprecated and will be removed in v6.0.0. ' \
86
- 'Use config_get(name), config_set(name, value), or config_list instead.'
87
- private_constant :CONFIG_DEPRECATION_WARNING
88
-
89
- # Deprecation warning emitted by {#global_config}
90
- #
91
- # @return [String] the warning message shown to callers
92
- #
93
- GLOBAL_CONFIG_DEPRECATION_WARNING =
94
- 'Git::Repository#global_config is deprecated and will be removed in v6.0.0. ' \
95
- 'Use config_get(name, global: true), config_set(name, value, global: true), ' \
96
- 'or config_list(global: true) instead.'
97
- private_constant :GLOBAL_CONFIG_DEPRECATION_WARNING
98
-
99
66
  # @return [Git::ExecutionContext::Repository] the execution context used to run
100
67
  # git commands for this repository
101
68
  #
@@ -149,24 +116,6 @@ module Git
149
116
  index_file && Pathname.new(index_file)
150
117
  end
151
118
 
152
- # Returns `self` after emitting a deprecation warning.
153
- #
154
- # Legacy callers that used `git.lib.some_method` can migrate to calling the
155
- # facade method directly on the repository object. This shim will be removed
156
- # in v6.0.0.
157
- #
158
- # @return [self]
159
- #
160
- # @api private
161
- #
162
- def lib
163
- Git::Deprecation.warn(
164
- 'Git::Repository#lib is deprecated and will be removed in v6.0.0. ' \
165
- 'Use the repository object directly.'
166
- )
167
- self
168
- end
169
-
170
119
  # @return [String, nil] the git directory path
171
120
  #
172
121
  # @api private
@@ -202,111 +151,6 @@ module Git
202
151
  # @api private
203
152
  def binary_path = execution_context.binary_path
204
153
 
205
- # Reads or writes a git configuration entry
206
- #
207
- # Dispatches to one of three modes depending on the arguments supplied:
208
- #
209
- # * **List** — `config()` returns all visible config entries as a `Hash`.
210
- # * **Get** — `config(name)` returns the value for a single key as a `String`.
211
- # * **Set** — `config(name, value)` writes a value and returns the raw
212
- # command result.
213
- #
214
- # @example List all config entries
215
- # repo.config #=> { "user.name" => "Alice", "core.bare" => "false" }
216
- #
217
- # @example Read a config value
218
- # repo.config('user.name') #=> "Alice"
219
- #
220
- # @example Set a config value
221
- # repo.config('user.name', 'Alice')
222
- #
223
- # @param name [String, Hash, nil] the dotted config key, or an options hash
224
- # for list mode when `value` and `options` are omitted
225
- #
226
- # @param value [#to_s, Hash, nil] the value to set, or an options hash in the
227
- # legacy `config(name, options)` call shape
228
- #
229
- # @param options [Hash] options forwarded to git config
230
- #
231
- # @option options [String, nil] :file (nil) path to a custom config file
232
- #
233
- # @return [Hash{String => String}, String, Git::CommandLine::Result] all config
234
- # entries, a single value, or the command result for set mode
235
- #
236
- # @raise [ArgumentError] if unsupported options are provided
237
- #
238
- # @raise [Git::FailedError] if git exits with a non-zero exit status
239
- #
240
- def config(name = nil, value = nil, options = {})
241
- Git::Deprecation.warn(CONFIG_DEPRECATION_WARNING)
242
- name, value, options = deprecated_normalize_config_args(name, value, options)
243
-
244
- if !name.nil? && !value.nil?
245
- deprecated_config_set(name, value, **options)
246
- elsif name
247
- deprecated_config_get(name, **options)
248
- else
249
- deprecated_config_list(**options)
250
- end
251
- end
252
-
253
- # Read or write a global git configuration entry
254
- #
255
- # Dispatches to one of three modes depending on the arguments supplied,
256
- # targeting the git global config scope (`git config --global`):
257
- #
258
- # * **List** — `global_config()` returns all global config entries as a `Hash`.
259
- # * **Get** — `global_config(name)` returns the value for a single key as a `String`.
260
- # * **Set** — `global_config(name, value)` writes a value and returns the raw
261
- # command result.
262
- #
263
- # @overload global_config
264
- #
265
- # @example List all global config entries
266
- # repo.global_config #=> { "user.name" => "Alice", "core.autocrlf" => "false" }
267
- #
268
- # @return [Hash{String => String}] all global config entries, keyed by their
269
- # full dotted key names (e.g. `"user.name"`)
270
- #
271
- # @raise [Git::FailedError] if git exits with a non-zero exit status
272
- #
273
- # @overload global_config(name)
274
- #
275
- # @example Read the global committer name
276
- # repo.global_config('user.name') #=> "Alice"
277
- #
278
- # @param name [String] the dotted config key to look up (e.g. `"user.name"`)
279
- #
280
- # @return [String] the value of the global config entry
281
- #
282
- # @raise [Git::FailedError] if git exits with a non-zero exit status
283
- #
284
- # @overload global_config(name, value)
285
- #
286
- # @example Set the global committer name
287
- # repo.global_config('user.name', 'Alice')
288
- #
289
- # @param name [String] the dotted config key to write (e.g. `"user.name"`)
290
- #
291
- # @param value [#to_s] the value to assign; any object is accepted and
292
- # converted to a String via `#to_s` before being passed to git
293
- #
294
- # @return [Git::CommandLine::Result] the raw result of
295
- # `git config --global <name> <value>`
296
- #
297
- # @raise [Git::FailedError] if git exits with a non-zero exit status
298
- #
299
- def global_config(name = nil, value = nil)
300
- Git::Deprecation.warn(GLOBAL_CONFIG_DEPRECATION_WARNING)
301
- if !name.nil? && !value.nil?
302
- deprecated_global_config_set(name, value)
303
- elsif !name.nil?
304
- deprecated_global_config_get(name)
305
- else
306
- deprecated_global_config_list
307
- end
308
- end
309
-
310
154
  # Returns the size of the repository directory in bytes
311
155
  #
312
156
  # Sums the sizes of every regular file under the repository (`.git`)
@@ -320,161 +164,38 @@ module Git
320
164
  #
321
165
  # @return [Integer] the total size in bytes of the repository directory
322
166
  #
167
+ # @raise [Git::Error] if the repository directory cannot be traversed, for
168
+ # example when a subdirectory cannot be searched
169
+ #
323
170
  def repo_size
324
171
  repository = repo
325
172
  return 0 unless repository&.directory?
326
173
 
327
- total = 0
328
- Find.find(repository.to_s) do |path|
329
- stat = File.lstat(path)
330
- total += stat.size if stat.file?
331
- rescue Errno::ENOENT
332
- next
174
+ Git::SystemCallGuard.call('Failed to compute the repository size') do
175
+ repo_size_of_directory(repository.to_s)
333
176
  end
334
- total
335
177
  end
336
178
 
337
179
  private
338
180
 
339
- # Normalizes deprecated `config` call shapes into positional arguments
340
- #
341
- # @param name [String, Hash, nil] config key or an options hash
342
- #
343
- # @param value [#to_s, Hash, nil] config value or an options hash
344
- #
345
- # @param options [Hash] explicit options hash argument
346
- #
347
- # @option options [String, nil] :file (nil) path to a custom config file
348
- #
349
- # @return [Array((String, nil), (#to_s, nil), Hash)] normalized
350
- # `[name, value, options]`
351
- #
352
- # @raise [ArgumentError] if deprecated arguments mix an options hash with
353
- # unexpected additional positional arguments
354
- #
355
- def deprecated_normalize_config_args(name, value, options)
356
- if name.is_a?(Hash)
357
- raise ArgumentError, 'unexpected positional arguments after options hash' if !value.nil? || !options.empty?
358
-
359
- [nil, nil, name]
360
- elsif value.is_a?(Hash)
361
- raise ArgumentError, 'unexpected third argument when second argument is options hash' unless options.empty?
362
-
363
- [name, nil, value]
364
- else
365
- [name, value, options]
366
- end
367
- end
368
-
369
- # Writes a config value using the deprecated `config(name, value, ...)` path
370
- #
371
- # @overload deprecated_config_set(name, value, **options)
372
- #
373
- # @param name [String] the dotted config key to write
374
- #
375
- # @param value [#to_s] the value to assign
376
- #
377
- # @param options [Hash] command options
378
- #
379
- # @option options [String, nil] :file (nil) path to a custom config file
380
- #
381
- # @return [Git::CommandLine::Result] the command result
382
- #
383
- # @raise [ArgumentError] if unsupported options are provided
384
- #
385
- # @raise [Git::FailedError] if git exits with a non-zero exit status
386
- #
387
- def deprecated_config_set(name, value, **)
388
- SharedPrivate.assert_valid_opts!(CONFIG_SET_ALLOWED_OPTS, **)
389
- Git::Commands::ConfigOptionSyntax::Set.new(@execution_context).call(name, value, **)
390
- end
391
-
392
- # Reads a config value using the deprecated `config(name, ...)` path
393
- #
394
- # @param name [String] the dotted config key to read
395
- #
396
- # @param options [Hash] command options
397
- #
398
- # @option options [String, nil] :file (nil) path to a custom config file
399
- #
400
- # @return [String] the config value
401
- #
402
- # @raise [ArgumentError] if unsupported options are provided
403
- #
404
- # @raise [Git::FailedError] if git exits with a non-zero exit status
405
- #
406
- def deprecated_config_get(name, **options)
407
- SharedPrivate.assert_valid_opts!(CONFIG_READ_ALLOWED_OPTS, **options)
408
- opts = options[:file] ? { file: options[:file] } : {}
409
- result = Git::Commands::ConfigOptionSyntax::Get.new(@execution_context).call(name, **opts)
410
- raise Git::FailedError, result if result.status.exitstatus != 0
411
-
412
- result.stdout
413
- end
414
-
415
- # Lists config entries using the deprecated `config(...)` path
416
- #
417
- # @param options [Hash] command options
418
- #
419
- # @option options [String, nil] :file (nil) path to a custom config file
420
- #
421
- # @return [Hash{String => String}] all visible config entries keyed by name
422
- #
423
- # @raise [ArgumentError] if unsupported options are provided
424
- #
425
- # @raise [Git::FailedError] if git exits with a non-zero exit status
426
- #
427
- def deprecated_config_list(**options)
428
- SharedPrivate.assert_valid_opts!(CONFIG_READ_ALLOWED_OPTS, **options)
429
- opts = options[:file] ? { file: options[:file] } : {}
430
- lines = Git::Commands::ConfigOptionSyntax::List.new(@execution_context).call(**opts).stdout.split("\n")
431
- lines.each_with_object({}) do |line, hsh|
432
- key, value = line.split('=', 2)
433
- hsh[key] = value || ''
434
- end
435
- end
436
-
437
- # Reads a global config value using the deprecated `global_config(name)` path
181
+ # Sums the size of every regular file under `dir`
438
182
  #
439
- # @param name [String] the dotted config key to read
183
+ # Symbolic links are not followed. Entries that disappear between the
184
+ # traversal and the `lstat` call are skipped.
440
185
  #
441
- # @return [String] the config value
186
+ # @param dir [String] the directory to traverse
442
187
  #
443
- # @raise [Git::FailedError] if git exits with a non-zero exit status
444
- #
445
- def deprecated_global_config_get(name)
446
- result = Git::Commands::ConfigOptionSyntax::Get.new(@execution_context).call(name, global: true)
447
- raise Git::FailedError, result if result.status.exitstatus != 0
448
-
449
- result.stdout
450
- end
451
-
452
- # Lists global config entries using the deprecated `global_config` path
188
+ # @return [Integer] the total size in bytes of the files under `dir`
453
189
  #
454
- # @return [Hash{String => String}] all global config entries keyed by name
455
- #
456
- # @raise [Git::FailedError] if git exits with a non-zero exit status
457
- #
458
- def deprecated_global_config_list
459
- lines = Git::Commands::ConfigOptionSyntax::List.new(@execution_context).call(global: true).stdout.split("\n")
460
- lines.each_with_object({}) do |line, hsh|
461
- key, value = line.split('=', 2)
462
- hsh[key] = value || ''
190
+ def repo_size_of_directory(dir)
191
+ total = 0
192
+ Find.find(dir) do |path|
193
+ stat = File.lstat(path)
194
+ total += stat.size if stat.file?
195
+ rescue Errno::ENOENT
196
+ next
463
197
  end
464
- end
465
-
466
- # Writes a global config value using the deprecated `global_config` path
467
- #
468
- # @param name [String] the dotted config key to write
469
- #
470
- # @param value [#to_s] the value to assign
471
- #
472
- # @return [Git::CommandLine::Result] the command result
473
- #
474
- # @raise [Git::FailedError] if git exits with a non-zero exit status
475
- #
476
- def deprecated_global_config_set(name, value)
477
- Git::Commands::ConfigOptionSyntax::Set.new(@execution_context).call(name, value, global: true)
198
+ total
478
199
  end
479
200
 
480
201
  # All git config scopes are valid in a repository context
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/errors'
4
+
5
+ module Git
6
+ # Convert `SystemCallError` raised by the gem's own filesystem calls to {Git::Error}
7
+ #
8
+ # The gem promises to raise only `ArgumentError` or a subclass of {Git::Error}.
9
+ # Filesystem calls such as `File.read`, `Dir.chdir`, or `Tempfile.create` can
10
+ # raise a bare `SystemCallError` (`Errno::ENOENT`, `Errno::EACCES`, and so on).
11
+ # Wrapping those calls in {.call} keeps the promise: the `SystemCallError` is
12
+ # re-raised as a {Git::Error} with the original error available as `cause`.
13
+ #
14
+ # Helpers that yield to caller-supplied blocks must not relabel errors raised
15
+ # by the caller's own code. {#unguarded} marks that region so a
16
+ # `SystemCallError` raised inside it propagates unchanged.
17
+ #
18
+ # @example Guard a single filesystem call
19
+ # content = Git::SystemCallGuard.call('Failed to read the gitdir pointer file') do
20
+ # File.read(path)
21
+ # end
22
+ #
23
+ # @example Guard setup and cleanup but not the caller's block
24
+ # def with_tempfile(&block)
25
+ # Git::SystemCallGuard.call('Failed to create a temporary file') do |guard|
26
+ # Tempfile.create do |file|
27
+ # guard.unguarded { block.call(file) }
28
+ # end
29
+ # end
30
+ # end
31
+ #
32
+ # @api private
33
+ #
34
+ class SystemCallGuard
35
+ # Run the block, converting any `SystemCallError` it raises to {Git::Error}
36
+ #
37
+ # @param message [String] prefix for the {Git::Error} message
38
+ #
39
+ # @return [Object] the value returned by the block
40
+ #
41
+ # @raise [Git::Error] if the block raises a `SystemCallError` outside an
42
+ # unguarded region
43
+ #
44
+ # @yield [guard] the guarded region
45
+ #
46
+ # @yieldparam guard [Git::SystemCallGuard] use {#unguarded} to exempt a region
47
+ #
48
+ # @yieldreturn [Object] returned as the method's return value
49
+ #
50
+ def self.call(message, &)
51
+ new.call(message, &)
52
+ end
53
+
54
+ # Run the block, converting any `SystemCallError` it raises to {Git::Error}
55
+ #
56
+ # @param (see .call)
57
+ #
58
+ # @return (see .call)
59
+ #
60
+ # @raise (see .call)
61
+ #
62
+ # @yield (see .call)
63
+ #
64
+ # @yieldparam (see .call)
65
+ #
66
+ # @yieldreturn (see .call)
67
+ #
68
+ def call(message)
69
+ yield self
70
+ rescue SystemCallError => e
71
+ raise if e.equal?(@unguarded_error)
72
+
73
+ raise Git::Error, "#{message}: #{e.message}"
74
+ end
75
+
76
+ # Run the block with `SystemCallError` conversion suspended
77
+ #
78
+ # If the block raises a `SystemCallError`, the enclosing {#call} re-raises
79
+ # that same error unchanged. Any other `SystemCallError` reaching {#call},
80
+ # including one raised while unwinding from the block's error, is converted.
81
+ #
82
+ # @return [Object] the value returned by the block
83
+ #
84
+ # @yield the region exempt from conversion
85
+ #
86
+ # @yieldreturn [Object] returned as the method's return value
87
+ #
88
+ def unguarded
89
+ yield
90
+ rescue SystemCallError => e
91
+ @unguarded_error = e
92
+ raise
93
+ end
94
+ end
95
+ end
data/lib/git/version.rb CHANGED
@@ -4,7 +4,7 @@ module Git
4
4
  # The current gem version
5
5
  #
6
6
  # @return [String] the current gem version
7
- VERSION = '5.6.0'
7
+ VERSION = '6.0.0'
8
8
 
9
9
  # Represents a git version with major, minor, and patch components
10
10
  #