git 5.0.0.beta.2 → 5.0.0.beta.4

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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +27 -0
  3. data/.github/copilot-instructions.md +3 -8
  4. data/.github/hooks/bin-setup-on-worktree.json +11 -0
  5. data/.github/hooks/run-bin-setup-once.sh +14 -0
  6. data/.github/skills/breaking-change-analysis/SKILL.md +6 -9
  7. data/.github/skills/ci-cd-troubleshooting/SKILL.md +2 -2
  8. data/.github/skills/command-implementation/REFERENCE.md +34 -33
  9. data/.github/skills/command-implementation/SKILL.md +5 -4
  10. data/.github/skills/command-test-conventions/SKILL.md +13 -9
  11. data/.github/skills/command-yard-documentation/SKILL.md +14 -6
  12. data/.github/skills/development-workflow/SKILL.md +20 -14
  13. data/.github/skills/facade-implementation/REFERENCE.md +19 -22
  14. data/.github/skills/facade-implementation/SKILL.md +0 -5
  15. data/.github/skills/facade-test-conventions/SKILL.md +4 -7
  16. data/.github/skills/facade-yard-documentation/SKILL.md +8 -2
  17. data/.github/skills/make-skill-template/SKILL.md +53 -3
  18. data/.github/skills/pr-readiness-review/SKILL.md +22 -2
  19. data/.github/skills/project-context/SKILL.md +16 -23
  20. data/.github/skills/pull-request-review/SKILL.md +2 -2
  21. data/.github/skills/rebase/SKILL.md +148 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +1 -1
  23. data/.github/skills/resolve-feedback/SKILL.md +288 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +3 -3
  25. data/.github/skills/reviewing-skills/SKILL.md +28 -3
  26. data/.github/skills/test-debugging/SKILL.md +20 -19
  27. data/.github/skills/yard-documentation/SKILL.md +671 -483
  28. data/.github/skills/yard-documentation/element-rules.md +162 -0
  29. data/.github/skills-deprecated/README.md +21 -0
  30. data/.github/{skills → skills-deprecated}/extract-command-from-lib/SKILL.md +9 -9
  31. data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/SKILL.md +12 -12
  32. data/.github/workflows/continuous_integration.yml +3 -2
  33. data/.rubocop.yml +7 -39
  34. data/.yard-lint.yml +75 -0
  35. data/CONTRIBUTING.md +186 -174
  36. data/Gemfile +17 -0
  37. data/README.md +86 -23
  38. data/Rakefile +3 -21
  39. data/UPGRADING.md +219 -70
  40. data/docker/test/Dockerfile +32 -0
  41. data/docker/test/docker-compose.yml +0 -0
  42. data/git.gemspec +3 -4
  43. data/lib/git/author.rb +20 -1
  44. data/lib/git/branch.rb +26 -6
  45. data/lib/git/branch_delete_failure.rb +3 -0
  46. data/lib/git/branch_delete_result.rb +6 -3
  47. data/lib/git/branch_info.rb +6 -6
  48. data/lib/git/command_line/base.rb +6 -13
  49. data/lib/git/command_line/capturing.rb +67 -10
  50. data/lib/git/command_line/result.rb +0 -8
  51. data/lib/git/command_line/streaming.rb +45 -5
  52. data/lib/git/command_line.rb +4 -3
  53. data/lib/git/commands/add.rb +61 -53
  54. data/lib/git/commands/am/abort.rb +2 -2
  55. data/lib/git/commands/am/apply.rb +109 -98
  56. data/lib/git/commands/am/continue.rb +2 -2
  57. data/lib/git/commands/am/quit.rb +2 -2
  58. data/lib/git/commands/am/retry.rb +4 -2
  59. data/lib/git/commands/am/show_current_patch.rb +1 -1
  60. data/lib/git/commands/am/skip.rb +2 -2
  61. data/lib/git/commands/am.rb +2 -2
  62. data/lib/git/commands/apply.rb +7 -2
  63. data/lib/git/commands/archive/list_formats.rb +3 -3
  64. data/lib/git/commands/archive.rb +7 -2
  65. data/lib/git/commands/arguments.rb +1370 -359
  66. data/lib/git/commands/base.rb +111 -13
  67. data/lib/git/commands/branch/copy.rb +29 -21
  68. data/lib/git/commands/branch/create.rb +72 -68
  69. data/lib/git/commands/branch/delete.rb +25 -17
  70. data/lib/git/commands/branch/list.rb +76 -60
  71. data/lib/git/commands/branch/move.rb +29 -21
  72. data/lib/git/commands/branch/set_upstream.rb +8 -8
  73. data/lib/git/commands/branch/show_current.rb +3 -3
  74. data/lib/git/commands/branch/unset_upstream.rb +4 -8
  75. data/lib/git/commands/branch.rb +2 -2
  76. data/lib/git/commands/cat_file/batch.rb +36 -20
  77. data/lib/git/commands/cat_file/filtered.rb +12 -12
  78. data/lib/git/commands/cat_file/raw.rb +13 -13
  79. data/lib/git/commands/cat_file.rb +4 -4
  80. data/lib/git/commands/checkout/branch.rb +7 -5
  81. data/lib/git/commands/checkout/files.rb +5 -4
  82. data/lib/git/commands/checkout.rb +4 -4
  83. data/lib/git/commands/checkout_index.rb +5 -4
  84. data/lib/git/commands/clean.rb +6 -4
  85. data/lib/git/commands/clone.rb +5 -4
  86. data/lib/git/commands/commit.rb +5 -4
  87. data/lib/git/commands/commit_tree.rb +5 -4
  88. data/lib/git/commands/config_option_syntax/add.rb +7 -4
  89. data/lib/git/commands/config_option_syntax/get.rb +9 -5
  90. data/lib/git/commands/config_option_syntax/get_all.rb +7 -4
  91. data/lib/git/commands/config_option_syntax/get_color.rb +8 -4
  92. data/lib/git/commands/config_option_syntax/get_color_bool.rb +7 -4
  93. data/lib/git/commands/config_option_syntax/get_regexp.rb +8 -4
  94. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +8 -4
  95. data/lib/git/commands/config_option_syntax/list.rb +8 -4
  96. data/lib/git/commands/config_option_syntax/remove_section.rb +7 -2
  97. data/lib/git/commands/config_option_syntax/rename_section.rb +7 -2
  98. data/lib/git/commands/config_option_syntax/replace_all.rb +7 -2
  99. data/lib/git/commands/config_option_syntax/set.rb +7 -2
  100. data/lib/git/commands/config_option_syntax/unset.rb +7 -4
  101. data/lib/git/commands/config_option_syntax/unset_all.rb +7 -2
  102. data/lib/git/commands/describe.rb +5 -4
  103. data/lib/git/commands/diff.rb +13 -12
  104. data/lib/git/commands/diff_files.rb +7 -6
  105. data/lib/git/commands/diff_index.rb +10 -7
  106. data/lib/git/commands/fetch.rb +6 -4
  107. data/lib/git/commands/fsck.rb +6 -4
  108. data/lib/git/commands/gc.rb +6 -4
  109. data/lib/git/commands/grep.rb +6 -5
  110. data/lib/git/commands/init.rb +6 -4
  111. data/lib/git/commands/log.rb +287 -285
  112. data/lib/git/commands/ls_files.rb +69 -65
  113. data/lib/git/commands/ls_remote.rb +62 -56
  114. data/lib/git/commands/ls_tree.rb +50 -46
  115. data/lib/git/commands/maintenance/register.rb +25 -23
  116. data/lib/git/commands/maintenance/run.rb +7 -2
  117. data/lib/git/commands/maintenance/start.rb +7 -2
  118. data/lib/git/commands/maintenance/stop.rb +7 -2
  119. data/lib/git/commands/maintenance/unregister.rb +7 -2
  120. data/lib/git/commands/merge/abort.rb +2 -2
  121. data/lib/git/commands/merge/continue.rb +2 -2
  122. data/lib/git/commands/merge/quit.rb +2 -2
  123. data/lib/git/commands/merge/start.rb +7 -2
  124. data/lib/git/commands/merge.rb +2 -2
  125. data/lib/git/commands/merge_base.rb +7 -2
  126. data/lib/git/commands/mv.rb +7 -2
  127. data/lib/git/commands/name_rev.rb +7 -2
  128. data/lib/git/commands/pull.rb +7 -2
  129. data/lib/git/commands/push.rb +7 -2
  130. data/lib/git/commands/read_tree.rb +7 -2
  131. data/lib/git/commands/remote/add.rb +7 -2
  132. data/lib/git/commands/remote/get_url.rb +4 -2
  133. data/lib/git/commands/remote/list.rb +4 -2
  134. data/lib/git/commands/remote/prune.rb +4 -2
  135. data/lib/git/commands/remote/remove.rb +4 -2
  136. data/lib/git/commands/remote/rename.rb +4 -2
  137. data/lib/git/commands/remote/set_branches.rb +9 -2
  138. data/lib/git/commands/remote/set_head.rb +12 -5
  139. data/lib/git/commands/remote/set_url.rb +9 -2
  140. data/lib/git/commands/remote/set_url_add.rb +11 -2
  141. data/lib/git/commands/remote/set_url_delete.rb +9 -2
  142. data/lib/git/commands/remote/show.rb +18 -12
  143. data/lib/git/commands/remote/update.rb +19 -12
  144. data/lib/git/commands/remote.rb +2 -2
  145. data/lib/git/commands/repack.rb +67 -63
  146. data/lib/git/commands/reset.rb +51 -44
  147. data/lib/git/commands/rev_parse.rb +135 -128
  148. data/lib/git/commands/revert/abort.rb +2 -2
  149. data/lib/git/commands/revert/continue.rb +7 -2
  150. data/lib/git/commands/revert/quit.rb +2 -2
  151. data/lib/git/commands/revert/skip.rb +2 -2
  152. data/lib/git/commands/revert/start.rb +7 -2
  153. data/lib/git/commands/revert.rb +2 -2
  154. data/lib/git/commands/rm.rb +35 -36
  155. data/lib/git/commands/show.rb +1 -1
  156. data/lib/git/commands/show_ref/exclude_existing.rb +3 -4
  157. data/lib/git/commands/show_ref/exists.rb +15 -13
  158. data/lib/git/commands/show_ref/list.rb +39 -35
  159. data/lib/git/commands/show_ref/verify.rb +29 -27
  160. data/lib/git/commands/show_ref.rb +4 -4
  161. data/lib/git/commands/stash/apply.rb +31 -25
  162. data/lib/git/commands/stash/branch.rb +10 -8
  163. data/lib/git/commands/stash/clear.rb +7 -5
  164. data/lib/git/commands/stash/create.rb +10 -8
  165. data/lib/git/commands/stash/drop.rb +15 -9
  166. data/lib/git/commands/stash/list.rb +9 -5
  167. data/lib/git/commands/stash/pop.rb +21 -12
  168. data/lib/git/commands/stash/push.rb +20 -11
  169. data/lib/git/commands/stash/show.rb +21 -12
  170. data/lib/git/commands/stash/store.rb +19 -10
  171. data/lib/git/commands/stash.rb +2 -2
  172. data/lib/git/commands/status.rb +7 -2
  173. data/lib/git/commands/symbolic_ref/delete.rb +18 -14
  174. data/lib/git/commands/symbolic_ref/read.rb +24 -20
  175. data/lib/git/commands/symbolic_ref/update.rb +20 -17
  176. data/lib/git/commands/symbolic_ref.rb +4 -4
  177. data/lib/git/commands/tag/create.rb +45 -42
  178. data/lib/git/commands/tag/delete.rb +5 -3
  179. data/lib/git/commands/tag/list.rb +47 -44
  180. data/lib/git/commands/tag/verify.rb +12 -12
  181. data/lib/git/commands/tag.rb +2 -2
  182. data/lib/git/commands/update_ref/batch.rb +6 -1
  183. data/lib/git/commands/update_ref/delete.rb +19 -21
  184. data/lib/git/commands/update_ref/update.rb +22 -25
  185. data/lib/git/commands/update_ref.rb +4 -4
  186. data/lib/git/commands/version.rb +14 -11
  187. data/lib/git/commands/worktree/add.rb +50 -51
  188. data/lib/git/commands/worktree/list.rb +17 -17
  189. data/lib/git/commands/worktree/lock.rb +12 -12
  190. data/lib/git/commands/worktree/move.rb +16 -16
  191. data/lib/git/commands/worktree/prune.rb +2 -2
  192. data/lib/git/commands/worktree/remove.rb +2 -2
  193. data/lib/git/commands/worktree/repair.rb +2 -2
  194. data/lib/git/commands/worktree/unlock.rb +2 -2
  195. data/lib/git/commands/write_tree.rb +2 -2
  196. data/lib/git/commands.rb +4 -3
  197. data/lib/git/config.rb +47 -1
  198. data/lib/git/configuring.rb +89 -15
  199. data/lib/git/detached_head_info.rb +4 -1
  200. data/lib/git/diff.rb +2 -5
  201. data/lib/git/diff_file_numstat_info.rb +3 -1
  202. data/lib/git/diff_file_patch_info.rb +3 -1
  203. data/lib/git/diff_file_raw_info.rb +3 -1
  204. data/lib/git/diff_info.rb +8 -15
  205. data/lib/git/diff_path_status.rb +16 -3
  206. data/lib/git/diff_result.rb +3 -1
  207. data/lib/git/diff_stats.rb +15 -0
  208. data/lib/git/dirstat_info.rb +24 -8
  209. data/lib/git/encoding_utils.rb +32 -1
  210. data/lib/git/errors.rb +19 -9
  211. data/lib/git/escaped_path.rb +55 -3
  212. data/lib/git/execution_context/repository.rb +15 -0
  213. data/lib/git/execution_context.rb +76 -74
  214. data/lib/git/file_ref.rb +6 -3
  215. data/lib/git/fsck_object.rb +8 -0
  216. data/lib/git/fsck_result.rb +26 -15
  217. data/lib/git/log.rb +152 -5
  218. data/lib/git/object.rb +284 -8
  219. data/lib/git/parsers/branch.rb +18 -0
  220. data/lib/git/parsers/cat_file.rb +2 -2
  221. data/lib/git/parsers/diff.rb +213 -5
  222. data/lib/git/parsers/fsck.rb +11 -0
  223. data/lib/git/parsers/ls_remote.rb +1 -1
  224. data/lib/git/parsers/stash.rb +51 -0
  225. data/lib/git/parsers/tag.rb +38 -0
  226. data/lib/git/remote.rb +34 -2
  227. data/lib/git/repository/branching.rb +23 -21
  228. data/lib/git/repository/committing.rb +46 -6
  229. data/lib/git/repository/context_helpers.rb +45 -16
  230. data/lib/git/repository/diffing.rb +32 -23
  231. data/lib/git/repository/factories.rb +153 -28
  232. data/lib/git/repository/inspecting.rb +1 -1
  233. data/lib/git/repository/logging.rb +169 -30
  234. data/lib/git/repository/maintenance.rb +1 -1
  235. data/lib/git/repository/merging.rb +3 -3
  236. data/lib/git/repository/object_operations.rb +108 -14
  237. data/lib/git/repository/remote_operations.rb +150 -171
  238. data/lib/git/repository/shared_private.rb +7 -5
  239. data/lib/git/repository/staging.rb +36 -12
  240. data/lib/git/repository/stashing.rb +16 -16
  241. data/lib/git/repository/status_operations.rb +1 -1
  242. data/lib/git/repository/worktree_operations.rb +1 -1
  243. data/lib/git/repository.rb +287 -6
  244. data/lib/git/stash.rb +16 -21
  245. data/lib/git/stash_info.rb +4 -4
  246. data/lib/git/stashes.rb +20 -26
  247. data/lib/git/status.rb +25 -13
  248. data/lib/git/tag_delete_failure.rb +3 -0
  249. data/lib/git/tag_delete_result.rb +6 -3
  250. data/lib/git/tag_info.rb +1 -0
  251. data/lib/git/url.rb +12 -5
  252. data/lib/git/version.rb +6 -4
  253. data/lib/git/version_constraint.rb +4 -0
  254. data/lib/git.rb +400 -314
  255. data/redesign/3_architecture_implementation.md +81 -30
  256. data/redesign/Phase 4 - Step A.md +6 -0
  257. data/redesign/Phase 4 - Step B.md +921 -0
  258. data/redesign/Phase 4 - Step C.md +833 -0
  259. data/redesign/c1a-public-api-scope.tsv +256 -0
  260. data/redesign/phase-4-step-b-test-audit.tsv +485 -0
  261. data/tasks/yard.rake +24 -17
  262. metadata +24 -58
  263. data/commitlint.test +0 -4
  264. data/lib/git/args_builder.rb +0 -103
  265. data/lib/git/command_line_result.rb +0 -8
  266. data/lib/git/deprecation.rb +0 -9
  267. data/lib/git/repository/configuring.rb +0 -351
  268. data/tasks/test.rake +0 -25
  269. /data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -0
  270. /data/.github/{skills → skills-deprecated}/review-backward-compatibility/SKILL.md +0 -0
data/lib/git.rb CHANGED
@@ -1,29 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support'
4
3
  require 'active_support/deprecation'
5
4
 
6
- require 'git/deprecation'
7
- require 'git/version'
8
-
5
+ # Define Git::Deprecation before requiring the rest of the library to ensure that
6
+ # any deprecation warnings emitted during the loading of the library are properly
7
+ # configured according to the GIT_DEPRECATION_BEHAVIOR environment variable.
8
+ #
9
9
  module Git
10
- # Minimum git version required by this gem
10
+ # The deprecation instance used to emit deprecation warnings for the Git gem
11
11
  #
12
- # Commands and features may require newer versions, but this is the absolute
13
- # minimum supported version for the gem as a whole.
12
+ # @api public
13
+ Deprecation = ActiveSupport::Deprecation.new('6.0.0', 'Git')
14
+
15
+ # Configure a deprecation instance from a GIT_DEPRECATION_BEHAVIOR value
14
16
  #
15
- # @return [Git::Version]
17
+ # @param deprecation [ActiveSupport::Deprecation] the deprecation instance to configure
16
18
  #
17
- # @api public
19
+ # @param behavior [String, nil] the desired behavior name (e.g. `'silence'`); when
20
+ # `nil` the deprecation instance is left unchanged
18
21
  #
19
- MINIMUM_GIT_VERSION = Version.parse('2.28.0')
22
+ # @return [void]
23
+ #
24
+ # @raise [ArgumentError] if `behavior` is not one of the keys of
25
+ # `ActiveSupport::Deprecation::DEFAULT_BEHAVIORS`
26
+ #
27
+ # @api private
28
+ #
29
+ def self.configure_deprecation_behavior(deprecation, behavior)
30
+ return if behavior.nil?
31
+
32
+ behavior = behavior.strip
33
+ allowed_behaviors = ActiveSupport::Deprecation::DEFAULT_BEHAVIORS.keys.map(&:to_s)
34
+
35
+ unless allowed_behaviors.include?(behavior)
36
+ raise ArgumentError,
37
+ "Invalid GIT_DEPRECATION_BEHAVIOR=#{behavior.inspect}; " \
38
+ "expected one of: #{allowed_behaviors.join(', ')}"
39
+ end
40
+
41
+ deprecation.behavior = behavior.to_sym
42
+ end
43
+ private_class_method :configure_deprecation_behavior
44
+
45
+ configure_deprecation_behavior(Deprecation, ENV.fetch('GIT_DEPRECATION_BEHAVIOR', nil))
20
46
  end
21
47
 
22
48
  require 'git/author'
23
49
  require 'git/branch'
24
50
  require 'git/branch_info'
25
51
  require 'git/branches'
26
- require 'git/command_line_result'
27
52
  require 'git/command_line'
28
53
  require 'process_executer'
29
54
  require 'git/config'
@@ -61,6 +86,7 @@ require 'git/tag_delete_failure'
61
86
  require 'git/tag_delete_result'
62
87
  require 'git/tag_info'
63
88
  require 'git/url'
89
+ require 'git/version'
64
90
  require 'git/worktree'
65
91
  require 'git/worktrees'
66
92
 
@@ -69,214 +95,141 @@ require 'git/worktrees'
69
95
  # open a bare repository, initialize a new repo or clone an
70
96
  # existing remote repository.
71
97
  #
72
- # @author Scott Chacon (mailto:schacon@gmail.com)
73
- #
74
98
  module Git
75
99
  extend Git::Configuring
100
+ extend Git::Repository::Factories
76
101
 
77
- # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
78
- # Use `Git.open(Dir.pwd).config(...)` instead.
79
- def config(name = nil, value = nil)
80
- Git::Deprecation.warn(
81
- 'Git#config is deprecated and will be removed in v6.0.0. ' \
82
- 'Use Git.open(Dir.pwd).config(...) instead.'
83
- )
84
- Git.__send__(:run_config_utility, name, value, global: false)
85
- end
86
-
87
- # Configures the gem by yielding {Git::Config.instance} to the block
88
- #
89
- # @example Set the global git binary path
90
- # Git.configure { |c| c.binary_path = '/usr/local/bin/git' }
91
- #
92
- # @yield [config] yields the singleton config object
102
+ # Minimum git version required by this gem
93
103
  #
94
- # @yieldparam config [Git::Config] the singleton config object
104
+ # Commands and features may require newer versions, but this is the absolute
105
+ # minimum supported version for the gem as a whole.
95
106
  #
96
- # @yieldreturn [void]
107
+ # @return [Git::Version]
97
108
  #
98
- # @return [void]
109
+ # @api public
99
110
  #
100
- def self.configure
101
- yield Git::Config.instance
102
- nil
103
- end
111
+ MINIMUM_GIT_VERSION = Version.parse('2.28.0')
104
112
 
105
- # Returns the process-wide {Git::Config} singleton
113
+ # Intercept the first lookup of the deprecated `Git::CommandLineResult` constant
106
114
  #
107
- # @example Read the configured binary path
108
- # Git.config.binary_path #=> "git"
115
+ # When `name` is `:CommandLineResult`, caches and returns {Git::CommandLine::Result}
116
+ # after emitting a deprecation warning. Calls `super` for any other unknown constant,
117
+ # preserving normal Ruby `NameError` behavior.
109
118
  #
110
- # @return [Git::Config] the singleton config object
119
+ # @param name [Symbol] the name of the missing constant
111
120
  #
112
- def self.config
113
- Git::Config.instance
114
- end
121
+ # @return [Class] the resolved constant value
122
+ #
123
+ # @api private
124
+ def self.const_missing(name)
125
+ return super unless name == :CommandLineResult
115
126
 
116
- # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
117
- # Use `Git.global_config(...)` instead.
118
- def global_config(name = nil, value = nil)
127
+ # Cache the constant first so subsequent accesses are zero-cost even if
128
+ # the deprecation behavior raises (e.g. in the test suite).
129
+ const_set(:CommandLineResult, Git::CommandLine::Result)
119
130
  Git::Deprecation.warn(
120
- 'Git#global_config is deprecated and will be removed in v6.0.0. ' \
121
- 'Use Git.global_config(...) instead.'
131
+ 'Git::CommandLineResult is deprecated and will be removed in v6.0.0. ' \
132
+ 'Use Git::CommandLine::Result instead.'
122
133
  )
123
- Git.global_config(name, value)
134
+ Git::CommandLine::Result
124
135
  end
125
136
 
126
- # Open a bare repository
137
+ # Gets or sets local git configuration options
127
138
  #
128
- # Opens a bare repository located in the `git_dir` directory.
129
- # Since there is no working copy, you can not checkout or commit
130
- # but you can do most read operations.
139
+ # @overload config(name, value)
140
+ # Set the value for the git named configuration option
131
141
  #
132
- # @see https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository
133
- # What is a bare repository?
142
+ # @param name [String] the name of the git configuration option
134
143
  #
135
- # @example Open a bare repository and retrieve the first commit SHA
136
- # repository = Git.bare('ruby-git.git')
137
- # puts repository.log[0].sha #=> "64c6fa011d3287bab9158049c85f3e85718854a0"
144
+ # @param value [String, Boolean] the value to set for the git configuration option
138
145
  #
139
- # @param [Pathname] git_dir The path to the bare repository directory
140
- # containing an initialized Git repository. If a relative path is given, it
141
- # is converted to an absolute path using
142
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
146
+ # @return [Git::CommandLine::Result] the result of the git configuration command
143
147
  #
144
- # @param [Hash] options The options for this command (see list of valid
145
- # options below)
148
+ # @overload config(name)
149
+ # Get the value for the git named configuration option
146
150
  #
147
- # @option options [String, nil] :git_ssh An optional custom SSH command
151
+ # @param name [String] the name of the git configuration option
148
152
  #
149
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
150
- # - If nil, disables SSH for this instance.
151
- # - If a non-empty string, uses that value for this instance.
153
+ # @return [String] the value of the git configuration option
152
154
  #
153
- # @option options [Logger] :log A logger to use for Git operations. Git commands
154
- # are logged at the `:info` level. Additional logging is done at the `:debug`
155
- # level.
155
+ # @overload config()
156
+ # List all git configuration options
156
157
  #
157
- # @return [Git::Repository] an object that can execute git commands in the context
158
- # of the bare repository.
158
+ # @return [Hash{String => String}] a hash of all git configuration options
159
+ #
160
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
161
+ # Use `Git.config_get(name)`, `Git.config_set(name, value)`, or `Git.config_list` instead.
159
162
  #
160
- def self.bare(git_dir, options = {})
161
- Git::Repository.bare(git_dir, options)
163
+ def config(name = nil, value = nil)
164
+ Git::Deprecation.warn(
165
+ 'Git#config is deprecated and will be removed in v6.0.0. ' \
166
+ 'Use Git.config_get(name), Git.config_set(name, value), or Git.config_list instead.'
167
+ )
168
+ Git.__send__(:legacy_config_set_get_list, name, value, global: false)
162
169
  end
163
170
 
164
- # Clone a repository into an empty or newly created directory
165
- #
166
- # @see https://git-scm.com/docs/git-clone git clone
167
- # @see https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a GIT URLs
168
- #
169
- # @param repository_url [URI, Pathname] The (possibly remote) repository url to clone
170
- # from. See [GIT URLS](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a)
171
- # for more information.
172
- #
173
- # @param directory [Pathname, nil] The directory to clone into
174
- #
175
- # If `directory` is a relative path it is relative to the `:chdir` option if
176
- # given. If `:chdir` is not given, `directory` is relative to the current
177
- # working directory.
178
- #
179
- # If `nil`, `directory` will be set to the basename of the last component of
180
- # the path from the `repository_url`. For example, for the URL:
181
- # `https://github.com/org/repo.git`, `directory` will be set to `repo`.
182
- #
183
- # If the last component of the path is `.git`, the next-to-last component of
184
- # the path is used. For example, for the URL `/Users/me/foo/.git`, `directory`
185
- # will be set to `foo`.
186
- #
187
- # @param [Hash] options The options for this command (see list of valid
188
- # options below)
189
- #
190
- # @option options [Boolean] :bare Make a bare Git repository. See
191
- # [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
192
- #
193
- # @option options [String] :branch The name of a branch or tag to checkout
194
- # instead of the default branch.
195
- #
196
- # @option options [Array, String] :config A list of configuration options to
197
- # set on the newly created repository.
198
- #
199
- # @option options [Integer] :depth Create a shallow clone with a history
200
- # truncated to the specified number of commits.
201
- #
202
- # @option options [String] :filter Request that the server send a partial
203
- # clone according to the given filter
204
- #
205
- # @option options [Boolean, nil] :single_branch Control whether the clone
206
- # limits fetch refspecs to a single branch.
207
- # - If nil (default), no flag is passed and the Git default is used.
208
- # - If true, `--single-branch` is passed to limit the refspec to the
209
- # checkout branch.
210
- # - If false, `--no-single-branch` is passed to broaden the refspec (useful
211
- # for shallow clones that should include all branches).
171
+ # Configures the gem by yielding {Git::Config.instance} to the block
212
172
  #
213
- # @option options [String, nil] :git_ssh An optional custom SSH command
173
+ # @example Set the global git binary path
174
+ # Git.configure { |c| c.binary_path = '/usr/local/bin/git' }
214
175
  #
215
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
216
- # - If nil, disables SSH for this instance.
217
- # - If a non-empty string, uses that value for this instance.
176
+ # @return [void]
218
177
  #
219
- # @option options [Logger] :log A logger to use for Git operations. Git
220
- # commands are logged at the `:info` level. Additional logging is done
221
- # at the `:debug` level.
178
+ # @yield [config] yields the singleton config object
222
179
  #
223
- # @option options [Boolean] :mirror Set up a mirror of the source repository.
180
+ # @yieldparam config [Git::Config] the singleton config object
224
181
  #
225
- # @option options [String] :origin Use the value instead `origin` to track
226
- # the upstream repository.
182
+ # @yieldreturn [void]
227
183
  #
228
- # @option options [Pathname] :chdir Run `git clone` from within this directory.
184
+ def self.configure
185
+ yield Git::Config.instance
186
+ nil
187
+ end
188
+
189
+ # Returns the process-wide {Git::Config} singleton
229
190
  #
230
- # The `directory` parameter (or the repository basename when `directory` is nil)
231
- # is resolved relative to `:chdir`, just as if you had `cd`'d into it before
232
- # running `git clone`. The returned path is the join of `:chdir` and the
233
- # cloned directory path.
191
+ # @example Read the configured binary path
192
+ # Git.config.binary_path #=> "git"
234
193
  #
235
- # @option options [Pathname] :path Deprecated use `:chdir` instead.
194
+ # @return [Git::Config] the singleton config object
236
195
  #
237
- # @option options [Boolean] :recursive After the clone is created, initialize
238
- # all submodules within, using their default settings.
196
+ def self.config
197
+ Git::Config.instance
198
+ end
199
+
200
+ # Gets or sets global git configuration options
239
201
  #
240
- # @example Clone into the default directory `ruby-git`
241
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git')
202
+ # @overload global_config(name, value)
203
+ # Set the value for the git named configuration option
242
204
  #
243
- # @example Clone and then checkout the `development` branch
244
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', branch: 'development')
205
+ # @param name [String] the name of the git configuration option
245
206
  #
246
- # @example Clone into a different directory `my-ruby-git`
247
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', 'my-ruby-git')
207
+ # @param value [String, Boolean] the value to set for the git configuration option
248
208
  #
249
- # @example Clone into a specific parent directory
250
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', chdir: '/path/to/projects')
251
- # # clones into /path/to/projects/ruby-git
209
+ # @return [Git::CommandLine::Result] the result of the git configuration command
252
210
  #
253
- # @example Create a bare repository in the directory `ruby-git.git`
254
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', bare: true)
211
+ # @overload global_config(name)
212
+ # Get the value for the git named configuration option
255
213
  #
256
- # @example Clone a repository and set a single config option
257
- # git = Git.clone(
258
- # 'https://github.com/ruby-git/ruby-git.git',
259
- # config: 'submodule.recurse=true'
260
- # )
214
+ # @param name [String] the name of the git configuration option
261
215
  #
262
- # @example Clone a repository and set multiple config options
263
- # git = Git.clone(
264
- # 'https://github.com/ruby-git/ruby-git.git',
265
- # config: ['user.name=John Doe', 'user.email=john@doe.com']
266
- # )
216
+ # @return [String] the value of the git configuration option
267
217
  #
268
- # @example Clone using a specific SSH key
269
- # git = Git.clone(
270
- # 'git@github.com:ruby-git/ruby-git.git',
271
- # 'local-dir',
272
- # git_ssh: 'ssh -i /path/to/private_key'
273
- # )
218
+ # @overload global_config()
219
+ # List all git configuration options
274
220
  #
275
- # @return [Git::Repository] an object that can execute git commands in the context
276
- # of the cloned local working copy or cloned repository.
221
+ # @return [Hash{String => String}] a hash of all git configuration options
277
222
  #
278
- def self.clone(repository_url, directory = nil, options = {})
279
- Git::Repository.clone(repository_url, directory, options)
223
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
224
+ # Use `Git.config_get(name, global: true)`, `Git.config_set(name, value, global: true)`, or
225
+ # `Git.config_list(global: true)` instead.
226
+ def global_config(name = nil, value = nil)
227
+ Git::Deprecation.warn(
228
+ 'Git#global_config is deprecated and will be removed in v6.0.0. ' \
229
+ 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
230
+ 'or Git.config_list(global: true) instead.'
231
+ )
232
+ Git.__send__(:legacy_config_set_get_list, name, value, global: true)
280
233
  end
281
234
 
282
235
  # Returns the name of the default branch of the given repository
@@ -299,15 +252,16 @@ module Git
299
252
  # @example with the logging option
300
253
  # logger = Logger.new(STDOUT, level: Logger::INFO)
301
254
  # Git.default_branch('.', log: logger) # => 'master'
302
- # I, [2022-04-13T16:01:33.221596 #18415] INFO -- : git '-c' 'core.quotePath=true'
303
- # '-c' 'color.ui=false' ls-remote '--symref' '--' '.' 'HEAD' 2>&1
255
+ # # Logs the executed git command to STDOUT, for example:
256
+ # # I, [2022-04-13T16:01:33.221596 #18415] INFO -- : git '-c' 'core.quotePath=true'
257
+ # # '-c' 'color.ui=false' ls-remote '--symref' '--' '.' 'HEAD' 2>&1
304
258
  #
305
259
  # @param repository [URI, Pathname, String] The (possibly remote) repository to get the default branch name for
306
260
  #
307
261
  # See [GIT URLS](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a)
308
262
  # for more information.
309
263
  #
310
- # @param [Hash] options The options for this command (see list of valid
264
+ # @param options [Hash] The options for this command (see list of valid
311
265
  # options below)
312
266
  #
313
267
  # @option options [Logger] :log A logger to use for Git operations. Git
@@ -322,94 +276,64 @@ module Git
322
276
  Git::Parsers::LsRemote.parse_default_branch(output)
323
277
  end
324
278
 
325
- # Export the current HEAD (or a branch, if <tt>options[:branch]</tt>
326
- # is specified) into the +name+ directory, then remove all traces of git from the
327
- # directory.
328
- #
329
- # See +clone+ for options. Does not obey the <tt>:remote</tt> option,
330
- # since the .git info will be deleted anyway; always uses the default
331
- # remote, 'origin.'
332
- def self.export(repository, name, options = {})
333
- options.delete(:remote)
334
- repo = clone(repository, name, { depth: 1 }.merge(options))
335
- repo.checkout("origin/#{options[:branch]}") if options[:branch]
336
- FileUtils.rm_r File.join(repo.dir.to_s, '.git')
337
- end
338
-
339
- # Same as g.config, but forces it to be at the global level
340
- #
341
- # g.config('user.name', 'Scott Chacon') # sets value
342
- # g.config('user.email', 'email@email.com') # sets value
343
- # g.config('user.name') # returns 'Scott Chacon'
344
- # g.config # returns whole config hash
345
- def self.global_config(name = nil, value = nil)
346
- run_config_utility(name, value, global: true)
347
- end
348
-
349
- # Create an empty Git repository or reinitialize an existing Git repository
279
+ # Clone a repository into `directory` then remove its `.git` directory
350
280
  #
351
- # @param [Pathname] directory If the `:bare` option is NOT given or is not
352
- # `true`, the repository will be created in `"#{directory}/.git"`.
353
- # Otherwise, the repository is created in `"#{directory}"`.
281
+ # Exports the current HEAD (or the specific branch given in <tt>options[:branch]</tt>)
282
+ # into the given `directory`. It then removes all traces of git from the directory.
354
283
  #
355
- # All directories along the path to `directory` are created if they do not exist.
284
+ # Takes the same options as {Git.clone} except that `:remote` is silently ignored
285
+ # and `:depth` defaults to 1.
356
286
  #
357
- # A relative path is referenced from the current working directory of the process
358
- # and converted to an absolute path using
359
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
287
+ # @param repository_url [String, URI, Pathname] the repository to export from
360
288
  #
361
- # @param [Hash] options The options for this command (see list of valid
362
- # options below)
363
- #
364
- # @option options [Boolean] :bare Instead of creating a repository at
365
- # `"#{directory}/.git"`, create a bare repository at `"#{directory}"`.
366
- # See [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
367
- #
368
- # @option options [String] :initial_branch Use the specified name for the
369
- # initial branch in the newly created repository.
370
- #
371
- # @option options [Pathname] :repository the path to put the newly initialized
372
- # Git repository. The default for non-bare repository is `"#{directory}/.git"`.
289
+ # @param directory [String, Pathname, nil] the directory to export into; defaults to the
290
+ # repository basename
373
291
  #
374
- # A relative path is referenced from the current working directory of the process
375
- # and converted to an absolute path using
376
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
292
+ # @param options [Hash] options forwarded to {Git.clone} (`:remote` is ignored;
293
+ # `:depth` defaults to 1)
377
294
  #
378
- # @option options [Pathname] :separate_git_dir Alias for `:repository`.
295
+ # @option options [String] :branch the branch or tag to export instead of HEAD
379
296
  #
380
- # @option options [String, nil] :git_ssh An optional custom SSH command
297
+ # @return [void]
381
298
  #
382
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
383
- # - If nil, disables SSH for this instance.
384
- # - If a non-empty string, uses that value for this instance.
299
+ def self.export(repository_url, directory = nil, options = {})
300
+ options.delete(:remote)
301
+ repo = clone(repository_url, directory, { depth: 1 }.merge(options))
302
+ repo.checkout("origin/#{options[:branch]}") if options[:branch]
303
+ FileUtils.rm_r File.join(repo.dir.to_s, '.git')
304
+ end
305
+
306
+ # Get or set a git global configuration value
385
307
  #
386
- # @option options [String, :use_global_config] :binary_path path to the git
387
- # binary; defaults to `Git::Config.instance.binary_path` when not specified.
388
- # Raises `ArgumentError` if set to `nil`.
308
+ # @example Set a value
309
+ # Git.global_config('user.name', 'Scott Chacon')
389
310
  #
390
- # @option options [Logger] :log A logger to use for Git operations. Git
391
- # commands are logged at the `:info` level. Additional logging is done
392
- # at the `:debug` level.
311
+ # @example Get a value
312
+ # Git.global_config('user.name') # => 'Scott Chacon'
393
313
  #
394
- # @return [Git::Repository] an object that can execute git commands in the context
395
- # of the newly initialized repository
314
+ # @example List all global config entries
315
+ # Git.global_config # => { 'user.name' => 'Scott Chacon', ... }
396
316
  #
397
- # @example Initialize a repository in the current directory
398
- # git = Git.init
317
+ # @param name [String, nil] the config key to get or set; omit to list all
399
318
  #
400
- # @example Initialize a repository in some other directory
401
- # git = Git.init '~/code/ruby-git'
319
+ # @param value [Object, nil] the value to set; omit to get or list
402
320
  #
403
- # @example Initialize a bare repository
404
- # git = Git.init '~/code/ruby-git.git', bare: true
321
+ # @return [String, Hash, Git::CommandLine::Result] the config value, all entries,
322
+ # or the result of the set command
405
323
  #
406
- # @example Initialize a repository in a non-default location (outside of the working copy)
407
- # git = Git.init '~/code/ruby-git', repository: '~/code/ruby-git.git'
324
+ # @deprecated Use {Git.config_get}, {Git.config_set}, or {Git.config_list} instead.
408
325
  #
409
- # @see https://git-scm.com/docs/git-init git init
326
+ # - `Git.global_config('user.name')` → `Git.config_get('user.name', global: true)`
327
+ # - `Git.global_config('user.name', 'Bob')` → `Git.config_set('user.name', 'Bob', global: true)`
328
+ # - `Git.global_config` → `Git.config_list(global: true)`
410
329
  #
411
- def self.init(directory = '.', options = {})
412
- Git::Repository.init(directory, options)
330
+ def self.global_config(name = nil, value = nil)
331
+ Git::Deprecation.warn(
332
+ 'Git.global_config is deprecated and will be removed in v6.0.0. ' \
333
+ 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
334
+ 'or Git.config_list(global: true) instead.'
335
+ )
336
+ legacy_config_set_get_list(name, value, global: true)
413
337
  end
414
338
 
415
339
  # Option keys accepted by {.ls_remote}
@@ -427,84 +351,129 @@ module Git
427
351
  ].freeze
428
352
  private_constant :LS_REMOTE_ALLOWED_OPTS
429
353
 
430
- # returns a Hash containing information about the references
431
- # of the target repository
354
+ # Displays references available in a remote repository along with the associated commit IDs
432
355
  #
433
- # options
434
- # :refs
356
+ # @example From a remote repository given its URL
357
+ # references = Git.ls_remote('https://github.com/user/repo.git')
435
358
  #
436
- # @param [String|NilClass] location the target repository location or nil for '.'
437
- # @return [{String=>Hash}] the available references of the target repo.
438
- def self.ls_remote(location = nil, options = {})
439
- options = options.dup
440
- log = options.delete(:log)
441
- unknown = options.keys - LS_REMOTE_ALLOWED_OPTS
442
- raise ArgumentError, "Unknown options: #{unknown.join(', ')}" unless unknown.empty?
443
-
444
- context = Git::ExecutionContext::Global.new(logger: log)
445
- repository = location || '.'
446
- output_lines = Git::Commands::LsRemote.new(context).call(repository, **options).stdout.split("\n")
447
- Git::Parsers::LsRemote.parse_output(output_lines)
448
- end
449
-
450
- # Open a an existing Git working directory
359
+ # @example From the default remote of the current repository
360
+ # references = Git.ls_remote
451
361
  #
452
- # Git.open will most likely be the most common way to create
453
- # a git reference, referring to an existing working directory.
362
+ # @example From a specific remote of the current repository
363
+ # references = Git.ls_remote('origin')
454
364
  #
455
- # If not provided in the options, the library will assume
456
- # the repository and index are in the default places (`.git/`, `.git/index`).
365
+ # @param repository [String, nil] the target repository location or the name of a remote
457
366
  #
458
- # @example Open the Git working directory in the current directory
459
- # git = Git.open
367
+ # Defaults to `'.'` (the current directory). Passing `nil` explicitly is
368
+ # deprecated and will be removed in v6.0.0; pass `'.'` or omit the argument.
460
369
  #
461
- # @example Open a Git working directory in some other directory
462
- # git = Git.open('~/Projects/ruby-git')
370
+ # @param options [Hash] the options to pass to the git command
463
371
  #
464
- # @example Use a logger to see what is going on
465
- # logger = Logger.new(STDOUT)
466
- # git = Git.open('~/Projects/ruby-git', log: logger)
372
+ # @option options [Boolean, nil] :branches (nil) limit output to refs under `refs/heads/`
467
373
  #
468
- # @example Open a working copy whose repository is in a non-standard directory
469
- # git = Git.open('~/Projects/ruby-git', repository: '~/Project/ruby-git.git')
374
+ # Alias: `:b`
470
375
  #
471
- # @param [Pathname] working_dir the path to the working directory to use
472
- # for git commands.
376
+ # @option options [Boolean, nil] :heads (nil) limit output to refs under `refs/heads/`
473
377
  #
474
- # A relative path is referenced from the current working directory of the process
475
- # and converted to an absolute path using
476
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
378
+ # Deprecated: use `:branches` instead. Kept for backward compatibility with
379
+ # older git versions where `--heads` is the only supported flag.
477
380
  #
478
- # @param [Hash] options The options for this command (see list of valid
479
- # options below)
381
+ # Alias: `:h`
480
382
  #
481
- # @option options [Pathname] :repository used to specify a non-standard path to
482
- # the repository directory. The default is `"#{working_dir}/.git"`.
383
+ # @option options [Boolean, nil] :tags (nil) limit output to refs under `refs/tags/`
483
384
  #
484
- # @option options [Pathname] :index used to specify a non-standard path to an
485
- # index file. The default is `"#{working_dir}/.git/index"`
385
+ # Alias: `:t`
486
386
  #
487
- # @option options [String, nil] :git_ssh An optional custom SSH command
387
+ # @option options [Boolean, nil] :refs (nil) exclude peeled tags and pseudorefs
388
+ # like `HEAD` from the output
488
389
  #
489
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
490
- # - If nil, disables SSH for this instance.
491
- # - If a non-empty string, uses that value for this instance.
390
+ # @option options [String] :upload_pack (nil) full path to `git-upload-pack` on the
391
+ # remote host
492
392
  #
493
- # @option options [Logger] :log A logger to use for Git operations. Git
494
- # commands are logged at the `:info` level. Additional logging is done
495
- # at the `:debug` level.
393
+ # Useful when accessing repositories via SSH where the daemon does not use the
394
+ # PATH configured by the user.
395
+ #
396
+ # @option options [Boolean, nil] :quiet (nil) do not print the remote URL to stderr
397
+ #
398
+ # Alias: `:q`
399
+ #
400
+ # @option options [Boolean, nil] :exit_code (nil) exit with status `2` when no
401
+ # matching refs are found in the remote repository
402
+ #
403
+ # Without this option, the command exits `0` whenever it successfully
404
+ # communicates with the remote, even if no refs match.
405
+ #
406
+ # @option options [String] :sort (nil) sort output by the given key
407
+ #
408
+ # Prefix `-` for descending order. Supports `"version:refname"` or `"v:refname"`.
409
+ # See `git for-each-ref` for sort key documentation.
410
+ #
411
+ # @option options [String, Array<String>] :server_option (nil) transmit a string to
412
+ # the server when communicating using protocol version 2
413
+ #
414
+ # The string must not contain NUL or LF characters. Repeatable by passing an
415
+ # Array. Alias: `:o`
416
+ #
417
+ # @option options [Numeric] :timeout (nil) execution timeout in seconds
418
+ #
419
+ # @option options [Logger] :log (nil) a logger to use for Git operations
420
+ #
421
+ # Git commands are logged at the `:info` level. Additional logging is done at
422
+ # the `:debug` level.
496
423
  #
497
- # @return [Git::Repository] an object that can execute git commands in the context
498
- # of the opened working copy
424
+ # @return [Hash{String => Hash}] the available references of the target repo
499
425
  #
500
- def self.open(working_dir, options = {})
501
- Git::Repository.open(working_dir, options)
426
+ def self.ls_remote(repository = '.', options = {})
427
+ repository = normalize_ls_remote_repository(repository)
428
+ options = options.dup
429
+ log = options.delete(:log)
430
+ unknown = options.keys - LS_REMOTE_ALLOWED_OPTS
431
+ raise ArgumentError, "Unknown options: #{unknown.join(', ')}" unless unknown.empty?
432
+
433
+ context = Git::ExecutionContext::Global.new(logger: log)
434
+ output_lines = Git::Commands::LsRemote.new(context).call(repository, **options).stdout.split("\n")
435
+ Git::Parsers::LsRemote.parse_output(output_lines)
436
+ end
437
+
438
+ # Normalize the repository argument for {.ls_remote}
439
+ #
440
+ # Returns the repository unchanged unless it is nil, in which case a
441
+ # deprecation warning is emitted and `'.'` is returned.
442
+ #
443
+ # @param repository [String, nil] the repository argument passed by the caller
444
+ #
445
+ # @return [String] the normalized repository value (`'.'` when nil was given)
446
+ #
447
+ # @api private
448
+ #
449
+ def self.normalize_ls_remote_repository(repository)
450
+ return repository unless repository.nil?
451
+
452
+ Git::Deprecation.warn(
453
+ 'Passing nil as the repository to Git.ls_remote is deprecated and will ' \
454
+ "be removed in v6.0.0. Pass '.' explicitly or omit the argument instead."
455
+ )
456
+
457
+ '.'
502
458
  end
459
+ private_class_method :normalize_ls_remote_repository
503
460
 
504
461
  # Thread-safe cache for git versions, keyed by binary path.
505
462
  @git_version_cache_mutex = Mutex.new
506
463
  @git_version_cache = {}
507
464
 
465
+ # Return the cached git version for the given binary path
466
+ #
467
+ # If it isn't already known, compute it using the given block.
468
+ #
469
+ # @param binary_path [String] the path to the git binary
470
+ #
471
+ # @return [Git::Version] the git version
472
+ #
473
+ # @yield [] compute the git version if it is not cached
474
+ #
475
+ # @yieldreturn [Git::Version] the computed git version
476
+ #
508
477
  # @api private
509
478
  def self.cached_git_version(binary_path, &block)
510
479
  @git_version_cache_mutex.synchronize do
@@ -512,6 +481,10 @@ module Git
512
481
  end
513
482
  end
514
483
 
484
+ # Clear the cached git version for all binary paths
485
+ #
486
+ # @return [void]
487
+ #
515
488
  # @api private
516
489
  def self.clear_git_version_cache
517
490
  @git_version_cache_mutex.synchronize do
@@ -543,35 +516,135 @@ module Git
543
516
  cached_git_version(path) { run_git_version(path) }
544
517
  end
545
518
 
519
+ # Return the version of the git binary
520
+ #
521
+ # @param path [String] the path to the git binary
522
+ #
523
+ # @return [Git::Version] the parsed git version
524
+ #
525
+ # @raise [Git::UnexpectedResultError] if the version output cannot be parsed
526
+ #
527
+ # @raise [Git::FailedError] if the git binary exits with a non-zero status
528
+ #
529
+ # @raise [Git::Error] if the binary is not found or fails to launch
530
+ #
546
531
  # @api private
532
+ #
547
533
  def self.run_git_version(path)
548
534
  output = Git::Commands::Version.new(Git::ExecutionContext::Global.new(binary_path: path)).call.stdout
549
535
  Git::Version.parse(output)
550
536
  end
551
537
  private_class_method :run_git_version
552
538
 
539
+ # Get or set a git config value
540
+ #
541
+ # @overload legacy_config_set_get_list(name, value, global:)
542
+ #
543
+ # Set the value of a git configuration option
544
+ #
545
+ # @param name [String] the name of the git configuration value to set
546
+ #
547
+ # @param value [String, Boolean] the value to set
548
+ #
549
+ # @param global [Boolean] true to use the global git configuration, false for the
550
+ # local repo config
551
+ #
552
+ # @return [Git::CommandLine::Result] the result of the git config command
553
+ #
554
+ # @overload legacy_config_set_get_list(name, global:)
555
+ #
556
+ # Get the value of a git configuration option
557
+ #
558
+ # @param name [String] the name of the git configuration value to get
559
+ #
560
+ # @param global [Boolean] true to use the global git configuration, false for the
561
+ # local repo config
562
+ #
563
+ # @return [String] the value of the git configuration option
564
+ #
565
+ # @overload legacy_config_set_get_list(global:)
566
+ #
567
+ # Get all git configuration options
568
+ #
569
+ # @param global [Boolean] true to use the global git configuration, false for the
570
+ # local repo config
571
+ #
572
+ # @return [Hash{String => String}] all git configuration options
573
+ #
574
+ # @raise [Git::FailedError] if the git config command fails
575
+ #
553
576
  # @api private
554
- def self.run_config_utility(name, value, global:)
555
- context = Git::ExecutionContext::Global.new
556
- options = global ? { global: true } : {}
557
-
558
- return Git::Commands::ConfigOptionSyntax::Set.new(context).call(name, value, **options) if !name.nil? && !value.nil?
559
- return run_config_get(context, name, options) if name
577
+ #
578
+ def self.legacy_config_set_get_list(name, value, global:)
579
+ if !name.nil? && !value.nil?
580
+ legacy_config_set(name, value, global:)
581
+ elsif !name.nil?
582
+ legacy_config_get(name, global:)
583
+ else
584
+ legacy_config_list(global:)
585
+ end
586
+ end
587
+ private_class_method :legacy_config_set_get_list
560
588
 
561
- output = Git::Commands::ConfigOptionSyntax::List.new(context).call(**options).stdout
562
- parse_config_list(output.split("\n"))
589
+ # Set the value of a git configuration option
590
+ #
591
+ # @param name [String] the name of the git configuration value to set
592
+ #
593
+ # @param value [String, Boolean] the value to set
594
+ #
595
+ # @param global [Boolean] whether to use the global git configuration
596
+ #
597
+ # @api private
598
+ #
599
+ def self.legacy_config_set(name, value, global:)
600
+ options = global ? { global: true } : {}
601
+ Git::Commands::ConfigOptionSyntax::Set.new(execution_context).call(name, value, **options)
563
602
  end
564
- private_class_method :run_config_utility
603
+ private_class_method :legacy_config_set
565
604
 
566
- def self.run_config_get(context, name, options)
567
- result = Git::Commands::ConfigOptionSyntax::Get.new(context).call(name, **options)
605
+ # Get the value of a git configuration option
606
+ #
607
+ # @param name [String] the name of the git configuration option
608
+ #
609
+ # @param global [Boolean] whether to use the global git configuration
610
+ #
611
+ # @return [String] the value of the git configuration option
612
+ #
613
+ # @api private
614
+ #
615
+ def self.legacy_config_get(name, global:)
616
+ options = global ? { global: true } : {}
617
+ result = Git::Commands::ConfigOptionSyntax::Get.new(execution_context).call(name, **options)
568
618
  raise Git::FailedError, result if result.status.exitstatus != 0
569
619
 
570
620
  result.stdout
571
621
  end
572
- private_class_method :run_config_get
622
+ private_class_method :legacy_config_get
623
+
624
+ # Get a list of all git configuration options
625
+ #
626
+ # @param global [Boolean] true to use the global git configuration, false for the
627
+ # local repo config
628
+ #
629
+ # @return [Hash{String => String}] all git configuration options
630
+ #
631
+ # @api private
632
+ #
633
+ def self.legacy_config_list(global:)
634
+ options = global ? { global: true } : {}
635
+ output = Git::Commands::ConfigOptionSyntax::List.new(execution_context).call(**options).stdout
636
+ parse_config_list(output.split("\n"))
637
+ end
638
+ private_class_method :legacy_config_list
573
639
 
640
+ # Parse the output of `git config --list` into a hash
641
+ #
642
+ # @param lines [Array<String>] the lines of output from `git config --list`
643
+ #
644
+ # @return [Hash{String => String}] the parsed git configuration options
645
+ #
574
646
  # @api private
647
+ #
575
648
  def self.parse_config_list(lines)
576
649
  lines.each_with_object({}) do |line, hsh|
577
650
  key, value = line.split('=', 2)
@@ -598,10 +671,23 @@ module Git
598
671
  # The +:local+, +:worktree+, and +:blob+ scopes require an active git
599
672
  # repository and are therefore not valid at the Git module level.
600
673
  #
674
+ # @param options_to_check [Hash{Symbol => Object}] the scope options to check
675
+ #
676
+ # If any of the options listed in +REPOSITORY_SPECIFIC_SCOPES+ are present and
677
+ # truthy, an +ArgumentError+ will be raised.
678
+ #
679
+ # @option options_to_check [Object] :local truthy value requests local scope
680
+ #
681
+ # @option options_to_check [Object] :worktree truthy value requests worktree scope
682
+ #
683
+ # @option options_to_check [Object] :blob truthy value requests blob scope
684
+ #
685
+ # @raise [ArgumentError] if a repository-specific scope is requested
686
+ #
601
687
  # @api private
602
688
  #
603
- def self.assert_valid_scope!(**opts)
604
- invalid = REPOSITORY_SPECIFIC_SCOPES.select { |s| opts[s] }
689
+ def self.assert_valid_scope!(**options_to_check)
690
+ invalid = REPOSITORY_SPECIFIC_SCOPES.select { |s| options_to_check[s] }
605
691
  return if invalid.empty?
606
692
 
607
693
  raise ArgumentError, "#{invalid.join(', ')} scope requires a repository"