git 5.0.0.beta.3 → 5.0.0.beta.5

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 (269) 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 +79 -12
  38. data/Rakefile +3 -21
  39. data/UPGRADING.md +268 -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 +123 -63
  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 +21 -7
  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 +434 -313
  255. data/redesign/3_architecture_implementation.md +59 -29
  256. data/redesign/Phase 4 - Step B.md +921 -0
  257. data/redesign/Phase 4 - Step C.md +833 -0
  258. data/redesign/c1a-public-api-scope.tsv +256 -0
  259. data/redesign/phase-4-step-b-test-audit.tsv +485 -0
  260. data/redesign/reverse_dependencies.sql +44 -0
  261. data/tasks/yard.rake +24 -17
  262. metadata +25 -57
  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/tasks/test.rake +0 -25
  268. /data/.github/{skills → skills-deprecated}/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -0
  269. /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,216 +95,178 @@ 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.config_get(name)`, `Git.config_set(name, value)`, or `Git.config_list` 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.config_get(name), Git.config_set(name, value), or Git.config_list 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
+ # Compatibility shim for code that monkeypatches the `Git::Base` class from
114
+ # versions prior to 5.0.0.
106
115
  #
107
- # @example Read the configured binary path
108
- # Git.config.binary_path #=> "git"
116
+ # `Git::Base` is a module included in {Git::Repository}, so any instance
117
+ # methods added to `Git::Base` are automatically available on
118
+ # {Git::Repository} instances. A deprecation warning is emitted for each
119
+ # method added, encouraging migration to {Git::Repository}.
109
120
  #
110
- # @return [Git::Config] the singleton config object
121
+ # @example Monkeypatching Git::Base (deprecated)
122
+ # module Git::Base
123
+ # def my_helper = "hello"
124
+ # end
125
+ # Git.open('.').my_helper # => "hello"
111
126
  #
112
- def self.config
113
- Git::Config.instance
114
- end
127
+ # @deprecated Define instance methods on {Git::Repository} instead.
128
+ #
129
+ # @api public
130
+ Base = Module.new do
131
+ # Emit a deprecation warning each time a method is defined in Git::Base so
132
+ # that authors of monkeypatches are nudged toward Git::Repository.
133
+ def self.method_added(method_name)
134
+ Git::Deprecation.warn(
135
+ 'Monkeypatching Git::Base is deprecated and will be removed in v6.0.0. ' \
136
+ "Define #{method_name} in Git::Repository instead."
137
+ )
138
+ super
139
+ end
115
140
 
116
- # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
117
- # Use `Git.config_get(name, global: true)`, `Git.config_set(name, value, global: true)`, or
118
- # `Git.config_list(global: true)` instead.
119
- def global_config(name = nil, value = nil)
120
- Git::Deprecation.warn(
121
- 'Git#global_config is deprecated and will be removed in v6.0.0. ' \
122
- 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
123
- 'or Git.config_list(global: true) instead.'
124
- )
125
- Git.global_config(name, value)
141
+ # Raise a clear error when legacy code calls Git::Base.new directly.
142
+ def self.new(...)
143
+ raise NoMethodError,
144
+ 'Git::Base.new is not supported. Use Git.open, Git.clone, or Git.init instead.'
145
+ end
126
146
  end
127
147
 
128
- # Open a bare repository
129
- #
130
- # Opens a bare repository located in the `git_dir` directory.
131
- # Since there is no working copy, you can not checkout or commit
132
- # but you can do most read operations.
133
- #
134
- # @see https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository
135
- # What is a bare repository?
136
- #
137
- # @example Open a bare repository and retrieve the first commit SHA
138
- # repository = Git.bare('ruby-git.git')
139
- # puts repository.log[0].sha #=> "64c6fa011d3287bab9158049c85f3e85718854a0"
140
- #
141
- # @param [Pathname] git_dir The path to the bare repository directory
142
- # containing an initialized Git repository. If a relative path is given, it
143
- # is converted to an absolute path using
144
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
145
- #
146
- # @param [Hash] options The options for this command (see list of valid
147
- # options below)
148
- #
149
- # @option options [String, nil] :git_ssh An optional custom SSH command
148
+ Repository.include(Base)
149
+
150
+ # Intercept the first lookup of the deprecated `Git::CommandLineResult` constant
150
151
  #
151
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
152
- # - If nil, disables SSH for this instance.
153
- # - If a non-empty string, uses that value for this instance.
152
+ # When `name` is `:CommandLineResult`, caches and returns {Git::CommandLine::Result}
153
+ # after emitting a deprecation warning. Calls `super` for any other unknown constant,
154
+ # preserving normal Ruby `NameError` behavior.
154
155
  #
155
- # @option options [Logger] :log A logger to use for Git operations. Git commands
156
- # are logged at the `:info` level. Additional logging is done at the `:debug`
157
- # level.
156
+ # @param name [Symbol] the name of the missing constant
158
157
  #
159
- # @return [Git::Repository] an object that can execute git commands in the context
160
- # of the bare repository.
158
+ # @return [Class] the resolved constant value
161
159
  #
162
- def self.bare(git_dir, options = {})
163
- Git::Repository.bare(git_dir, options)
160
+ # @api private
161
+ def self.const_missing(name)
162
+ return super unless name == :CommandLineResult
163
+
164
+ # Cache the constant first so subsequent accesses are zero-cost even if
165
+ # the deprecation behavior raises (e.g. in the test suite).
166
+ const_set(:CommandLineResult, Git::CommandLine::Result)
167
+ Git::Deprecation.warn(
168
+ 'Git::CommandLineResult is deprecated and will be removed in v6.0.0. ' \
169
+ 'Use Git::CommandLine::Result instead.'
170
+ )
171
+ Git::CommandLine::Result
164
172
  end
165
173
 
166
- # Clone a repository into an empty or newly created directory
174
+ # Gets or sets local git configuration options
167
175
  #
168
- # @see https://git-scm.com/docs/git-clone git clone
169
- # @see https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a GIT URLs
176
+ # @overload config(name, value)
177
+ # Set the value for the git named configuration option
170
178
  #
171
- # @param repository_url [URI, Pathname] The (possibly remote) repository url to clone
172
- # from. See [GIT URLS](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a)
173
- # for more information.
174
- #
175
- # @param directory [Pathname, nil] The directory to clone into
179
+ # @param name [String] the name of the git configuration option
176
180
  #
177
- # If `directory` is a relative path it is relative to the `:chdir` option if
178
- # given. If `:chdir` is not given, `directory` is relative to the current
179
- # working directory.
181
+ # @param value [String, Boolean] the value to set for the git configuration option
180
182
  #
181
- # If `nil`, `directory` will be set to the basename of the last component of
182
- # the path from the `repository_url`. For example, for the URL:
183
- # `https://github.com/org/repo.git`, `directory` will be set to `repo`.
183
+ # @return [Git::CommandLine::Result] the result of the git configuration command
184
184
  #
185
- # If the last component of the path is `.git`, the next-to-last component of
186
- # the path is used. For example, for the URL `/Users/me/foo/.git`, `directory`
187
- # will be set to `foo`.
188
- #
189
- # @param [Hash] options The options for this command (see list of valid
190
- # options below)
185
+ # @overload config(name)
186
+ # Get the value for the git named configuration option
191
187
  #
192
- # @option options [Boolean] :bare Make a bare Git repository. See
193
- # [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
188
+ # @param name [String] the name of the git configuration option
194
189
  #
195
- # @option options [String] :branch The name of a branch or tag to checkout
196
- # instead of the default branch.
190
+ # @return [String] the value of the git configuration option
197
191
  #
198
- # @option options [Array, String] :config A list of configuration options to
199
- # set on the newly created repository.
192
+ # @overload config()
193
+ # List all git configuration options
200
194
  #
201
- # @option options [Integer] :depth Create a shallow clone with a history
202
- # truncated to the specified number of commits.
195
+ # @return [Hash{String => String}] a hash of all git configuration options
203
196
  #
204
- # @option options [String] :filter Request that the server send a partial
205
- # clone according to the given filter
197
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
198
+ # Use `Git.config_get(name)`, `Git.config_set(name, value)`, or `Git.config_list` instead.
206
199
  #
207
- # @option options [Boolean, nil] :single_branch Control whether the clone
208
- # limits fetch refspecs to a single branch.
209
- # - If nil (default), no flag is passed and the Git default is used.
210
- # - If true, `--single-branch` is passed to limit the refspec to the
211
- # checkout branch.
212
- # - If false, `--no-single-branch` is passed to broaden the refspec (useful
213
- # for shallow clones that should include all branches).
200
+ def config(name = nil, value = nil)
201
+ Git::Deprecation.warn(
202
+ 'Git#config is deprecated and will be removed in v6.0.0. ' \
203
+ 'Use Git.config_get(name), Git.config_set(name, value), or Git.config_list instead.'
204
+ )
205
+ Git.__send__(:legacy_config_set_get_list, name, value, global: false)
206
+ end
207
+
208
+ # Configures the gem by yielding {Git::Config.instance} to the block
214
209
  #
215
- # @option options [String, nil] :git_ssh An optional custom SSH command
210
+ # @example Set the global git binary path
211
+ # Git.configure { |c| c.binary_path = '/usr/local/bin/git' }
216
212
  #
217
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
218
- # - If nil, disables SSH for this instance.
219
- # - If a non-empty string, uses that value for this instance.
213
+ # @return [void]
220
214
  #
221
- # @option options [Logger] :log A logger to use for Git operations. Git
222
- # commands are logged at the `:info` level. Additional logging is done
223
- # at the `:debug` level.
215
+ # @yield [config] yields the singleton config object
224
216
  #
225
- # @option options [Boolean] :mirror Set up a mirror of the source repository.
217
+ # @yieldparam config [Git::Config] the singleton config object
226
218
  #
227
- # @option options [String] :origin Use the value instead `origin` to track
228
- # the upstream repository.
219
+ # @yieldreturn [void]
229
220
  #
230
- # @option options [Pathname] :chdir Run `git clone` from within this directory.
221
+ def self.configure
222
+ yield Git::Config.instance
223
+ nil
224
+ end
225
+
226
+ # Returns the process-wide {Git::Config} singleton
231
227
  #
232
- # The `directory` parameter (or the repository basename when `directory` is nil)
233
- # is resolved relative to `:chdir`, just as if you had `cd`'d into it before
234
- # running `git clone`. The returned path is the join of `:chdir` and the
235
- # cloned directory path.
228
+ # @example Read the configured binary path
229
+ # Git.config.binary_path #=> "git"
236
230
  #
237
- # @option options [Pathname] :path Deprecated use `:chdir` instead.
231
+ # @return [Git::Config] the singleton config object
238
232
  #
239
- # @option options [Boolean] :recursive After the clone is created, initialize
240
- # all submodules within, using their default settings.
233
+ def self.config
234
+ Git::Config.instance
235
+ end
236
+
237
+ # Gets or sets global git configuration options
241
238
  #
242
- # @example Clone into the default directory `ruby-git`
243
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git')
239
+ # @overload global_config(name, value)
240
+ # Set the value for the git named configuration option
244
241
  #
245
- # @example Clone and then checkout the `development` branch
246
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', branch: 'development')
242
+ # @param name [String] the name of the git configuration option
247
243
  #
248
- # @example Clone into a different directory `my-ruby-git`
249
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', 'my-ruby-git')
244
+ # @param value [String, Boolean] the value to set for the git configuration option
250
245
  #
251
- # @example Clone into a specific parent directory
252
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', chdir: '/path/to/projects')
253
- # # clones into /path/to/projects/ruby-git
246
+ # @return [Git::CommandLine::Result] the result of the git configuration command
254
247
  #
255
- # @example Create a bare repository in the directory `ruby-git.git`
256
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', bare: true)
248
+ # @overload global_config(name)
249
+ # Get the value for the git named configuration option
257
250
  #
258
- # @example Clone a repository and set a single config option
259
- # git = Git.clone(
260
- # 'https://github.com/ruby-git/ruby-git.git',
261
- # config: 'submodule.recurse=true'
262
- # )
251
+ # @param name [String] the name of the git configuration option
263
252
  #
264
- # @example Clone a repository and set multiple config options
265
- # git = Git.clone(
266
- # 'https://github.com/ruby-git/ruby-git.git',
267
- # config: ['user.name=John Doe', 'user.email=john@doe.com']
268
- # )
253
+ # @return [String] the value of the git configuration option
269
254
  #
270
- # @example Clone using a specific SSH key
271
- # git = Git.clone(
272
- # 'git@github.com:ruby-git/ruby-git.git',
273
- # 'local-dir',
274
- # git_ssh: 'ssh -i /path/to/private_key'
275
- # )
255
+ # @overload global_config()
256
+ # List all git configuration options
276
257
  #
277
- # @return [Git::Repository] an object that can execute git commands in the context
278
- # of the cloned local working copy or cloned repository.
258
+ # @return [Hash{String => String}] a hash of all git configuration options
279
259
  #
280
- def self.clone(repository_url, directory = nil, options = {})
281
- Git::Repository.clone(repository_url, directory, options)
260
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
261
+ # Use `Git.config_get(name, global: true)`, `Git.config_set(name, value, global: true)`, or
262
+ # `Git.config_list(global: true)` instead.
263
+ def global_config(name = nil, value = nil)
264
+ Git::Deprecation.warn(
265
+ 'Git#global_config is deprecated and will be removed in v6.0.0. ' \
266
+ 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
267
+ 'or Git.config_list(global: true) instead.'
268
+ )
269
+ Git.__send__(:legacy_config_set_get_list, name, value, global: true)
282
270
  end
283
271
 
284
272
  # Returns the name of the default branch of the given repository
@@ -301,15 +289,16 @@ module Git
301
289
  # @example with the logging option
302
290
  # logger = Logger.new(STDOUT, level: Logger::INFO)
303
291
  # Git.default_branch('.', log: logger) # => 'master'
304
- # I, [2022-04-13T16:01:33.221596 #18415] INFO -- : git '-c' 'core.quotePath=true'
305
- # '-c' 'color.ui=false' ls-remote '--symref' '--' '.' 'HEAD' 2>&1
292
+ # # Logs the executed git command to STDOUT, for example:
293
+ # # I, [2022-04-13T16:01:33.221596 #18415] INFO -- : git '-c' 'core.quotePath=true'
294
+ # # '-c' 'color.ui=false' ls-remote '--symref' '--' '.' 'HEAD' 2>&1
306
295
  #
307
296
  # @param repository [URI, Pathname, String] The (possibly remote) repository to get the default branch name for
308
297
  #
309
298
  # See [GIT URLS](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a)
310
299
  # for more information.
311
300
  #
312
- # @param [Hash] options The options for this command (see list of valid
301
+ # @param options [Hash] The options for this command (see list of valid
313
302
  # options below)
314
303
  #
315
304
  # @option options [Logger] :log A logger to use for Git operations. Git
@@ -324,94 +313,64 @@ module Git
324
313
  Git::Parsers::LsRemote.parse_default_branch(output)
325
314
  end
326
315
 
327
- # Export the current HEAD (or a branch, if <tt>options[:branch]</tt>
328
- # is specified) into the +name+ directory, then remove all traces of git from the
329
- # directory.
316
+ # Clone a repository into `directory` then remove its `.git` directory
330
317
  #
331
- # See +clone+ for options. Does not obey the <tt>:remote</tt> option,
332
- # since the .git info will be deleted anyway; always uses the default
333
- # remote, 'origin.'
334
- def self.export(repository, name, options = {})
335
- options.delete(:remote)
336
- repo = clone(repository, name, { depth: 1 }.merge(options))
337
- repo.checkout("origin/#{options[:branch]}") if options[:branch]
338
- FileUtils.rm_r File.join(repo.dir.to_s, '.git')
339
- end
340
-
341
- # Same as g.config, but forces it to be at the global level
342
- #
343
- # g.config('user.name', 'Scott Chacon') # sets value
344
- # g.config('user.email', 'email@email.com') # sets value
345
- # g.config('user.name') # returns 'Scott Chacon'
346
- # g.config # returns whole config hash
347
- def self.global_config(name = nil, value = nil)
348
- run_config_utility(name, value, global: true)
349
- end
350
-
351
- # Create an empty Git repository or reinitialize an existing Git repository
318
+ # Exports the current HEAD (or the specific branch given in <tt>options[:branch]</tt>)
319
+ # into the given `directory`. It then removes all traces of git from the directory.
352
320
  #
353
- # @param [Pathname] directory If the `:bare` option is NOT given or is not
354
- # `true`, the repository will be created in `"#{directory}/.git"`.
355
- # Otherwise, the repository is created in `"#{directory}"`.
321
+ # Takes the same options as {Git.clone} except that `:remote` is silently ignored
322
+ # and `:depth` defaults to 1.
356
323
  #
357
- # All directories along the path to `directory` are created if they do not exist.
324
+ # @param repository_url [String, URI, Pathname] the repository to export from
358
325
  #
359
- # A relative path is referenced from the current working directory of the process
360
- # and converted to an absolute path using
361
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
362
- #
363
- # @param [Hash] options The options for this command (see list of valid
364
- # options below)
326
+ # @param directory [String, Pathname, nil] the directory to export into; defaults to the
327
+ # repository basename
365
328
  #
366
- # @option options [Boolean] :bare Instead of creating a repository at
367
- # `"#{directory}/.git"`, create a bare repository at `"#{directory}"`.
368
- # See [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
329
+ # @param options [Hash] options forwarded to {Git.clone} (`:remote` is ignored;
330
+ # `:depth` defaults to 1)
369
331
  #
370
- # @option options [String] :initial_branch Use the specified name for the
371
- # initial branch in the newly created repository.
332
+ # @option options [String] :branch the branch or tag to export instead of HEAD
372
333
  #
373
- # @option options [Pathname] :repository the path to put the newly initialized
374
- # Git repository. The default for non-bare repository is `"#{directory}/.git"`.
375
- #
376
- # A relative path is referenced from the current working directory of the process
377
- # and converted to an absolute path using
378
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
379
- #
380
- # @option options [Pathname] :separate_git_dir Alias for `:repository`.
381
- #
382
- # @option options [String, nil] :git_ssh An optional custom SSH command
334
+ # @return [void]
383
335
  #
384
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
385
- # - If nil, disables SSH for this instance.
386
- # - If a non-empty string, uses that value for this instance.
336
+ def self.export(repository_url, directory = nil, options = {})
337
+ options.delete(:remote)
338
+ repo = clone(repository_url, directory, { depth: 1 }.merge(options))
339
+ repo.checkout("origin/#{options[:branch]}") if options[:branch]
340
+ FileUtils.rm_r File.join(repo.dir.to_s, '.git')
341
+ end
342
+
343
+ # Get or set a git global configuration value
387
344
  #
388
- # @option options [String, :use_global_config] :binary_path path to the git
389
- # binary; defaults to `Git::Config.instance.binary_path` when not specified.
390
- # Raises `ArgumentError` if set to `nil`.
345
+ # @example Set a value
346
+ # Git.global_config('user.name', 'Scott Chacon')
391
347
  #
392
- # @option options [Logger] :log A logger to use for Git operations. Git
393
- # commands are logged at the `:info` level. Additional logging is done
394
- # at the `:debug` level.
348
+ # @example Get a value
349
+ # Git.global_config('user.name') # => 'Scott Chacon'
395
350
  #
396
- # @return [Git::Repository] an object that can execute git commands in the context
397
- # of the newly initialized repository
351
+ # @example List all global config entries
352
+ # Git.global_config # => { 'user.name' => 'Scott Chacon', ... }
398
353
  #
399
- # @example Initialize a repository in the current directory
400
- # git = Git.init
354
+ # @param name [String, nil] the config key to get or set; omit to list all
401
355
  #
402
- # @example Initialize a repository in some other directory
403
- # git = Git.init '~/code/ruby-git'
356
+ # @param value [Object, nil] the value to set; omit to get or list
404
357
  #
405
- # @example Initialize a bare repository
406
- # git = Git.init '~/code/ruby-git.git', bare: true
358
+ # @return [String, Hash, Git::CommandLine::Result] the config value, all entries,
359
+ # or the result of the set command
407
360
  #
408
- # @example Initialize a repository in a non-default location (outside of the working copy)
409
- # git = Git.init '~/code/ruby-git', repository: '~/code/ruby-git.git'
361
+ # @deprecated Use {Git.config_get}, {Git.config_set}, or {Git.config_list} instead.
410
362
  #
411
- # @see https://git-scm.com/docs/git-init git init
363
+ # - `Git.global_config('user.name')` → `Git.config_get('user.name', global: true)`
364
+ # - `Git.global_config('user.name', 'Bob')` → `Git.config_set('user.name', 'Bob', global: true)`
365
+ # - `Git.global_config` → `Git.config_list(global: true)`
412
366
  #
413
- def self.init(directory = '.', options = {})
414
- Git::Repository.init(directory, options)
367
+ def self.global_config(name = nil, value = nil)
368
+ Git::Deprecation.warn(
369
+ 'Git.global_config is deprecated and will be removed in v6.0.0. ' \
370
+ 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
371
+ 'or Git.config_list(global: true) instead.'
372
+ )
373
+ legacy_config_set_get_list(name, value, global: true)
415
374
  end
416
375
 
417
376
  # Option keys accepted by {.ls_remote}
@@ -429,84 +388,129 @@ module Git
429
388
  ].freeze
430
389
  private_constant :LS_REMOTE_ALLOWED_OPTS
431
390
 
432
- # returns a Hash containing information about the references
433
- # of the target repository
391
+ # Displays references available in a remote repository along with the associated commit IDs
434
392
  #
435
- # options
436
- # :refs
393
+ # @example From a remote repository given its URL
394
+ # references = Git.ls_remote('https://github.com/user/repo.git')
437
395
  #
438
- # @param [String|NilClass] location the target repository location or nil for '.'
439
- # @return [{String=>Hash}] the available references of the target repo.
440
- def self.ls_remote(location = nil, options = {})
441
- options = options.dup
442
- log = options.delete(:log)
443
- unknown = options.keys - LS_REMOTE_ALLOWED_OPTS
444
- raise ArgumentError, "Unknown options: #{unknown.join(', ')}" unless unknown.empty?
445
-
446
- context = Git::ExecutionContext::Global.new(logger: log)
447
- repository = location || '.'
448
- output_lines = Git::Commands::LsRemote.new(context).call(repository, **options).stdout.split("\n")
449
- Git::Parsers::LsRemote.parse_output(output_lines)
450
- end
451
-
452
- # Open a an existing Git working directory
396
+ # @example From the default remote of the current repository
397
+ # references = Git.ls_remote
453
398
  #
454
- # Git.open will most likely be the most common way to create
455
- # a git reference, referring to an existing working directory.
399
+ # @example From a specific remote of the current repository
400
+ # references = Git.ls_remote('origin')
456
401
  #
457
- # If not provided in the options, the library will assume
458
- # the repository and index are in the default places (`.git/`, `.git/index`).
402
+ # @param repository [String, nil] the target repository location or the name of a remote
459
403
  #
460
- # @example Open the Git working directory in the current directory
461
- # git = Git.open
404
+ # Defaults to `'.'` (the current directory). Passing `nil` explicitly is
405
+ # deprecated and will be removed in v6.0.0; pass `'.'` or omit the argument.
462
406
  #
463
- # @example Open a Git working directory in some other directory
464
- # git = Git.open('~/Projects/ruby-git')
407
+ # @param options [Hash] the options to pass to the git command
465
408
  #
466
- # @example Use a logger to see what is going on
467
- # logger = Logger.new(STDOUT)
468
- # git = Git.open('~/Projects/ruby-git', log: logger)
409
+ # @option options [Boolean, nil] :branches (nil) limit output to refs under `refs/heads/`
469
410
  #
470
- # @example Open a working copy whose repository is in a non-standard directory
471
- # git = Git.open('~/Projects/ruby-git', repository: '~/Project/ruby-git.git')
411
+ # Alias: `:b`
472
412
  #
473
- # @param [Pathname] working_dir the path to the working directory to use
474
- # for git commands.
413
+ # @option options [Boolean, nil] :heads (nil) limit output to refs under `refs/heads/`
475
414
  #
476
- # A relative path is referenced from the current working directory of the process
477
- # and converted to an absolute path using
478
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
415
+ # Deprecated: use `:branches` instead. Kept for backward compatibility with
416
+ # older git versions where `--heads` is the only supported flag.
479
417
  #
480
- # @param [Hash] options The options for this command (see list of valid
481
- # options below)
418
+ # Alias: `:h`
482
419
  #
483
- # @option options [Pathname] :repository used to specify a non-standard path to
484
- # the repository directory. The default is `"#{working_dir}/.git"`.
420
+ # @option options [Boolean, nil] :tags (nil) limit output to refs under `refs/tags/`
485
421
  #
486
- # @option options [Pathname] :index used to specify a non-standard path to an
487
- # index file. The default is `"#{working_dir}/.git/index"`
422
+ # Alias: `:t`
488
423
  #
489
- # @option options [String, nil] :git_ssh An optional custom SSH command
424
+ # @option options [Boolean, nil] :refs (nil) exclude peeled tags and pseudorefs
425
+ # like `HEAD` from the output
490
426
  #
491
- # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
492
- # - If nil, disables SSH for this instance.
493
- # - If a non-empty string, uses that value for this instance.
427
+ # @option options [String] :upload_pack (nil) full path to `git-upload-pack` on the
428
+ # remote host
494
429
  #
495
- # @option options [Logger] :log A logger to use for Git operations. Git
496
- # commands are logged at the `:info` level. Additional logging is done
497
- # at the `:debug` level.
430
+ # Useful when accessing repositories via SSH where the daemon does not use the
431
+ # PATH configured by the user.
432
+ #
433
+ # @option options [Boolean, nil] :quiet (nil) do not print the remote URL to stderr
434
+ #
435
+ # Alias: `:q`
436
+ #
437
+ # @option options [Boolean, nil] :exit_code (nil) exit with status `2` when no
438
+ # matching refs are found in the remote repository
439
+ #
440
+ # Without this option, the command exits `0` whenever it successfully
441
+ # communicates with the remote, even if no refs match.
498
442
  #
499
- # @return [Git::Repository] an object that can execute git commands in the context
500
- # of the opened working copy
443
+ # @option options [String] :sort (nil) sort output by the given key
501
444
  #
502
- def self.open(working_dir, options = {})
503
- Git::Repository.open(working_dir, options)
445
+ # Prefix `-` for descending order. Supports `"version:refname"` or `"v:refname"`.
446
+ # See `git for-each-ref` for sort key documentation.
447
+ #
448
+ # @option options [String, Array<String>] :server_option (nil) transmit a string to
449
+ # the server when communicating using protocol version 2
450
+ #
451
+ # The string must not contain NUL or LF characters. Repeatable by passing an
452
+ # Array. Alias: `:o`
453
+ #
454
+ # @option options [Numeric] :timeout (nil) execution timeout in seconds
455
+ #
456
+ # @option options [Logger] :log (nil) a logger to use for Git operations
457
+ #
458
+ # Git commands are logged at the `:info` level. Additional logging is done at
459
+ # the `:debug` level.
460
+ #
461
+ # @return [Hash{String => Hash}] the available references of the target repo
462
+ #
463
+ def self.ls_remote(repository = '.', options = {})
464
+ repository = normalize_ls_remote_repository(repository)
465
+ options = options.dup
466
+ log = options.delete(:log)
467
+ unknown = options.keys - LS_REMOTE_ALLOWED_OPTS
468
+ raise ArgumentError, "Unknown options: #{unknown.join(', ')}" unless unknown.empty?
469
+
470
+ context = Git::ExecutionContext::Global.new(logger: log)
471
+ output_lines = Git::Commands::LsRemote.new(context).call(repository, **options).stdout.split("\n")
472
+ Git::Parsers::LsRemote.parse_output(output_lines)
504
473
  end
505
474
 
475
+ # Normalize the repository argument for {.ls_remote}
476
+ #
477
+ # Returns the repository unchanged unless it is nil, in which case a
478
+ # deprecation warning is emitted and `'.'` is returned.
479
+ #
480
+ # @param repository [String, nil] the repository argument passed by the caller
481
+ #
482
+ # @return [String] the normalized repository value (`'.'` when nil was given)
483
+ #
484
+ # @api private
485
+ #
486
+ def self.normalize_ls_remote_repository(repository)
487
+ return repository unless repository.nil?
488
+
489
+ Git::Deprecation.warn(
490
+ 'Passing nil as the repository to Git.ls_remote is deprecated and will ' \
491
+ "be removed in v6.0.0. Pass '.' explicitly or omit the argument instead."
492
+ )
493
+
494
+ '.'
495
+ end
496
+ private_class_method :normalize_ls_remote_repository
497
+
506
498
  # Thread-safe cache for git versions, keyed by binary path.
507
499
  @git_version_cache_mutex = Mutex.new
508
500
  @git_version_cache = {}
509
501
 
502
+ # Return the cached git version for the given binary path
503
+ #
504
+ # If it isn't already known, compute it using the given block.
505
+ #
506
+ # @param binary_path [String] the path to the git binary
507
+ #
508
+ # @return [Git::Version] the git version
509
+ #
510
+ # @yield [] compute the git version if it is not cached
511
+ #
512
+ # @yieldreturn [Git::Version] the computed git version
513
+ #
510
514
  # @api private
511
515
  def self.cached_git_version(binary_path, &block)
512
516
  @git_version_cache_mutex.synchronize do
@@ -514,6 +518,10 @@ module Git
514
518
  end
515
519
  end
516
520
 
521
+ # Clear the cached git version for all binary paths
522
+ #
523
+ # @return [void]
524
+ #
517
525
  # @api private
518
526
  def self.clear_git_version_cache
519
527
  @git_version_cache_mutex.synchronize do
@@ -545,35 +553,135 @@ module Git
545
553
  cached_git_version(path) { run_git_version(path) }
546
554
  end
547
555
 
556
+ # Return the version of the git binary
557
+ #
558
+ # @param path [String] the path to the git binary
559
+ #
560
+ # @return [Git::Version] the parsed git version
561
+ #
562
+ # @raise [Git::UnexpectedResultError] if the version output cannot be parsed
563
+ #
564
+ # @raise [Git::FailedError] if the git binary exits with a non-zero status
565
+ #
566
+ # @raise [Git::Error] if the binary is not found or fails to launch
567
+ #
548
568
  # @api private
569
+ #
549
570
  def self.run_git_version(path)
550
571
  output = Git::Commands::Version.new(Git::ExecutionContext::Global.new(binary_path: path)).call.stdout
551
572
  Git::Version.parse(output)
552
573
  end
553
574
  private_class_method :run_git_version
554
575
 
576
+ # Get or set a git config value
577
+ #
578
+ # @overload legacy_config_set_get_list(name, value, global:)
579
+ #
580
+ # Set the value of a git configuration option
581
+ #
582
+ # @param name [String] the name of the git configuration value to set
583
+ #
584
+ # @param value [String, Boolean] the value to set
585
+ #
586
+ # @param global [Boolean] true to use the global git configuration, false for the
587
+ # local repo config
588
+ #
589
+ # @return [Git::CommandLine::Result] the result of the git config command
590
+ #
591
+ # @overload legacy_config_set_get_list(name, global:)
592
+ #
593
+ # Get the value of a git configuration option
594
+ #
595
+ # @param name [String] the name of the git configuration value to get
596
+ #
597
+ # @param global [Boolean] true to use the global git configuration, false for the
598
+ # local repo config
599
+ #
600
+ # @return [String] the value of the git configuration option
601
+ #
602
+ # @overload legacy_config_set_get_list(global:)
603
+ #
604
+ # Get all git configuration options
605
+ #
606
+ # @param global [Boolean] true to use the global git configuration, false for the
607
+ # local repo config
608
+ #
609
+ # @return [Hash{String => String}] all git configuration options
610
+ #
611
+ # @raise [Git::FailedError] if the git config command fails
612
+ #
555
613
  # @api private
556
- def self.run_config_utility(name, value, global:)
557
- context = Git::ExecutionContext::Global.new
558
- options = global ? { global: true } : {}
559
-
560
- return Git::Commands::ConfigOptionSyntax::Set.new(context).call(name, value, **options) if !name.nil? && !value.nil?
561
- return run_config_get(context, name, options) if name
614
+ #
615
+ def self.legacy_config_set_get_list(name, value, global:)
616
+ if !name.nil? && !value.nil?
617
+ legacy_config_set(name, value, global:)
618
+ elsif !name.nil?
619
+ legacy_config_get(name, global:)
620
+ else
621
+ legacy_config_list(global:)
622
+ end
623
+ end
624
+ private_class_method :legacy_config_set_get_list
562
625
 
563
- output = Git::Commands::ConfigOptionSyntax::List.new(context).call(**options).stdout
564
- parse_config_list(output.split("\n"))
626
+ # Set the value of a git configuration option
627
+ #
628
+ # @param name [String] the name of the git configuration value to set
629
+ #
630
+ # @param value [String, Boolean] the value to set
631
+ #
632
+ # @param global [Boolean] whether to use the global git configuration
633
+ #
634
+ # @api private
635
+ #
636
+ def self.legacy_config_set(name, value, global:)
637
+ options = global ? { global: true } : {}
638
+ Git::Commands::ConfigOptionSyntax::Set.new(execution_context).call(name, value, **options)
565
639
  end
566
- private_class_method :run_config_utility
640
+ private_class_method :legacy_config_set
567
641
 
568
- def self.run_config_get(context, name, options)
569
- result = Git::Commands::ConfigOptionSyntax::Get.new(context).call(name, **options)
642
+ # Get the value of a git configuration option
643
+ #
644
+ # @param name [String] the name of the git configuration option
645
+ #
646
+ # @param global [Boolean] whether to use the global git configuration
647
+ #
648
+ # @return [String] the value of the git configuration option
649
+ #
650
+ # @api private
651
+ #
652
+ def self.legacy_config_get(name, global:)
653
+ options = global ? { global: true } : {}
654
+ result = Git::Commands::ConfigOptionSyntax::Get.new(execution_context).call(name, **options)
570
655
  raise Git::FailedError, result if result.status.exitstatus != 0
571
656
 
572
657
  result.stdout
573
658
  end
574
- private_class_method :run_config_get
659
+ private_class_method :legacy_config_get
660
+
661
+ # Get a list of all git configuration options
662
+ #
663
+ # @param global [Boolean] true to use the global git configuration, false for the
664
+ # local repo config
665
+ #
666
+ # @return [Hash{String => String}] all git configuration options
667
+ #
668
+ # @api private
669
+ #
670
+ def self.legacy_config_list(global:)
671
+ options = global ? { global: true } : {}
672
+ output = Git::Commands::ConfigOptionSyntax::List.new(execution_context).call(**options).stdout
673
+ parse_config_list(output.split("\n"))
674
+ end
675
+ private_class_method :legacy_config_list
575
676
 
677
+ # Parse the output of `git config --list` into a hash
678
+ #
679
+ # @param lines [Array<String>] the lines of output from `git config --list`
680
+ #
681
+ # @return [Hash{String => String}] the parsed git configuration options
682
+ #
576
683
  # @api private
684
+ #
577
685
  def self.parse_config_list(lines)
578
686
  lines.each_with_object({}) do |line, hsh|
579
687
  key, value = line.split('=', 2)
@@ -600,10 +708,23 @@ module Git
600
708
  # The +:local+, +:worktree+, and +:blob+ scopes require an active git
601
709
  # repository and are therefore not valid at the Git module level.
602
710
  #
711
+ # @param options_to_check [Hash{Symbol => Object}] the scope options to check
712
+ #
713
+ # If any of the options listed in +REPOSITORY_SPECIFIC_SCOPES+ are present and
714
+ # truthy, an +ArgumentError+ will be raised.
715
+ #
716
+ # @option options_to_check [Object] :local truthy value requests local scope
717
+ #
718
+ # @option options_to_check [Object] :worktree truthy value requests worktree scope
719
+ #
720
+ # @option options_to_check [Object] :blob truthy value requests blob scope
721
+ #
722
+ # @raise [ArgumentError] if a repository-specific scope is requested
723
+ #
603
724
  # @api private
604
725
  #
605
- def self.assert_valid_scope!(**opts)
606
- invalid = REPOSITORY_SPECIFIC_SCOPES.select { |s| opts[s] }
726
+ def self.assert_valid_scope!(**options_to_check)
727
+ invalid = REPOSITORY_SPECIFIC_SCOPES.select { |s| options_to_check[s] }
607
728
  return if invalid.empty?
608
729
 
609
730
  raise ArgumentError, "#{invalid.join(', ')} scope requires a repository"