git 1.19.1 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -1
  3. data/AI_POLICY.md +24 -0
  4. data/CHANGELOG.md +689 -0
  5. data/CODE_OF_CONDUCT.md +25 -0
  6. data/CONTRIBUTING.md +1175 -97
  7. data/GOVERNANCE.md +106 -0
  8. data/LICENSE +1 -1
  9. data/MAINTAINERS.md +17 -4
  10. data/README.md +476 -320
  11. data/UPGRADING.md +1138 -0
  12. data/git.gemspec +124 -36
  13. data/lib/git/author.rb +39 -7
  14. data/lib/git/author_info.rb +66 -0
  15. data/lib/git/branch.rb +615 -65
  16. data/lib/git/branch_delete_failure.rb +34 -0
  17. data/lib/git/branch_delete_result.rb +66 -0
  18. data/lib/git/branch_info.rb +237 -0
  19. data/lib/git/branches.rb +167 -44
  20. data/lib/git/command_line/base.rb +247 -0
  21. data/lib/git/command_line/capturing.rb +308 -0
  22. data/lib/git/command_line/result.rb +88 -0
  23. data/lib/git/command_line/streaming.rb +236 -0
  24. data/lib/git/command_line.rb +52 -0
  25. data/lib/git/commands/add.rb +139 -0
  26. data/lib/git/commands/am/abort.rb +43 -0
  27. data/lib/git/commands/am/apply.rb +263 -0
  28. data/lib/git/commands/am/continue.rb +43 -0
  29. data/lib/git/commands/am/quit.rb +43 -0
  30. data/lib/git/commands/am/retry.rb +49 -0
  31. data/lib/git/commands/am/show_current_patch.rb +64 -0
  32. data/lib/git/commands/am/skip.rb +42 -0
  33. data/lib/git/commands/am.rb +33 -0
  34. data/lib/git/commands/apply.rb +242 -0
  35. data/lib/git/commands/archive/list_formats.rb +46 -0
  36. data/lib/git/commands/archive.rb +145 -0
  37. data/lib/git/commands/arguments.rb +4521 -0
  38. data/lib/git/commands/base.rb +502 -0
  39. data/lib/git/commands/branch/copy.rb +102 -0
  40. data/lib/git/commands/branch/create.rb +177 -0
  41. data/lib/git/commands/branch/delete.rb +88 -0
  42. data/lib/git/commands/branch/list.rb +178 -0
  43. data/lib/git/commands/branch/move.rb +102 -0
  44. data/lib/git/commands/branch/set_upstream.rb +86 -0
  45. data/lib/git/commands/branch/show_current.rb +49 -0
  46. data/lib/git/commands/branch/unset_upstream.rb +53 -0
  47. data/lib/git/commands/branch.rb +34 -0
  48. data/lib/git/commands/cat_file/batch.rb +385 -0
  49. data/lib/git/commands/cat_file/filtered.rb +105 -0
  50. data/lib/git/commands/cat_file/raw.rb +271 -0
  51. data/lib/git/commands/cat_file.rb +49 -0
  52. data/lib/git/commands/checkout/branch.rb +153 -0
  53. data/lib/git/commands/checkout/files.rb +116 -0
  54. data/lib/git/commands/checkout.rb +38 -0
  55. data/lib/git/commands/checkout_index.rb +106 -0
  56. data/lib/git/commands/clean.rb +102 -0
  57. data/lib/git/commands/clone.rb +241 -0
  58. data/lib/git/commands/commit.rb +273 -0
  59. data/lib/git/commands/commit_tree.rb +101 -0
  60. data/lib/git/commands/config_option_syntax/add.rb +86 -0
  61. data/lib/git/commands/config_option_syntax/get.rb +121 -0
  62. data/lib/git/commands/config_option_syntax/get_all.rb +118 -0
  63. data/lib/git/commands/config_option_syntax/get_color.rb +95 -0
  64. data/lib/git/commands/config_option_syntax/get_color_bool.rb +96 -0
  65. data/lib/git/commands/config_option_syntax/get_regexp.rb +119 -0
  66. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +111 -0
  67. data/lib/git/commands/config_option_syntax/list.rb +111 -0
  68. data/lib/git/commands/config_option_syntax/remove_section.rb +79 -0
  69. data/lib/git/commands/config_option_syntax/rename_section.rb +83 -0
  70. data/lib/git/commands/config_option_syntax/replace_all.rb +109 -0
  71. data/lib/git/commands/config_option_syntax/set.rb +119 -0
  72. data/lib/git/commands/config_option_syntax/unset.rb +92 -0
  73. data/lib/git/commands/config_option_syntax/unset_all.rb +94 -0
  74. data/lib/git/commands/config_option_syntax.rb +56 -0
  75. data/lib/git/commands/describe.rb +156 -0
  76. data/lib/git/commands/diff.rb +657 -0
  77. data/lib/git/commands/diff_files.rb +519 -0
  78. data/lib/git/commands/diff_index.rb +499 -0
  79. data/lib/git/commands/fetch.rb +354 -0
  80. data/lib/git/commands/fsck.rb +138 -0
  81. data/lib/git/commands/gc.rb +134 -0
  82. data/lib/git/commands/grep.rb +339 -0
  83. data/lib/git/commands/init.rb +101 -0
  84. data/lib/git/commands/log.rb +634 -0
  85. data/lib/git/commands/ls_files.rb +195 -0
  86. data/lib/git/commands/ls_remote.rb +161 -0
  87. data/lib/git/commands/ls_tree.rb +135 -0
  88. data/lib/git/commands/maintenance/register.rb +77 -0
  89. data/lib/git/commands/maintenance/run.rb +109 -0
  90. data/lib/git/commands/maintenance/start.rb +71 -0
  91. data/lib/git/commands/maintenance/stop.rb +60 -0
  92. data/lib/git/commands/maintenance/unregister.rb +84 -0
  93. data/lib/git/commands/maintenance.rb +31 -0
  94. data/lib/git/commands/merge/abort.rb +44 -0
  95. data/lib/git/commands/merge/continue.rb +44 -0
  96. data/lib/git/commands/merge/quit.rb +46 -0
  97. data/lib/git/commands/merge/start.rb +250 -0
  98. data/lib/git/commands/merge.rb +28 -0
  99. data/lib/git/commands/merge_base.rb +91 -0
  100. data/lib/git/commands/mv.rb +82 -0
  101. data/lib/git/commands/name_rev.rb +119 -0
  102. data/lib/git/commands/pull.rb +382 -0
  103. data/lib/git/commands/push.rb +251 -0
  104. data/lib/git/commands/read_tree.rb +154 -0
  105. data/lib/git/commands/remote/add.rb +96 -0
  106. data/lib/git/commands/remote/get_url.rb +68 -0
  107. data/lib/git/commands/remote/list.rb +56 -0
  108. data/lib/git/commands/remote/prune.rb +63 -0
  109. data/lib/git/commands/remote/remove.rb +52 -0
  110. data/lib/git/commands/remote/rename.rb +76 -0
  111. data/lib/git/commands/remote/set_branches.rb +70 -0
  112. data/lib/git/commands/remote/set_head.rb +89 -0
  113. data/lib/git/commands/remote/set_url.rb +78 -0
  114. data/lib/git/commands/remote/set_url_add.rb +70 -0
  115. data/lib/git/commands/remote/set_url_delete.rb +71 -0
  116. data/lib/git/commands/remote/show.rb +77 -0
  117. data/lib/git/commands/remote/update.rb +79 -0
  118. data/lib/git/commands/remote.rb +42 -0
  119. data/lib/git/commands/repack.rb +281 -0
  120. data/lib/git/commands/reset.rb +154 -0
  121. data/lib/git/commands/rev_parse.rb +304 -0
  122. data/lib/git/commands/revert/abort.rb +45 -0
  123. data/lib/git/commands/revert/continue.rb +62 -0
  124. data/lib/git/commands/revert/quit.rb +47 -0
  125. data/lib/git/commands/revert/skip.rb +44 -0
  126. data/lib/git/commands/revert/start.rb +158 -0
  127. data/lib/git/commands/revert.rb +29 -0
  128. data/lib/git/commands/rm.rb +113 -0
  129. data/lib/git/commands/show.rb +632 -0
  130. data/lib/git/commands/show_ref/exclude_existing.rb +119 -0
  131. data/lib/git/commands/show_ref/exists.rb +80 -0
  132. data/lib/git/commands/show_ref/list.rb +149 -0
  133. data/lib/git/commands/show_ref/verify.rb +122 -0
  134. data/lib/git/commands/show_ref.rb +42 -0
  135. data/lib/git/commands/stash/apply.rb +81 -0
  136. data/lib/git/commands/stash/branch.rb +67 -0
  137. data/lib/git/commands/stash/clear.rb +43 -0
  138. data/lib/git/commands/stash/create.rb +60 -0
  139. data/lib/git/commands/stash/drop.rb +73 -0
  140. data/lib/git/commands/stash/list.rb +43 -0
  141. data/lib/git/commands/stash/pop.rb +87 -0
  142. data/lib/git/commands/stash/push.rb +112 -0
  143. data/lib/git/commands/stash/show.rb +158 -0
  144. data/lib/git/commands/stash/store.rb +72 -0
  145. data/lib/git/commands/stash.rb +38 -0
  146. data/lib/git/commands/status.rb +174 -0
  147. data/lib/git/commands/symbolic_ref/delete.rb +72 -0
  148. data/lib/git/commands/symbolic_ref/read.rb +99 -0
  149. data/lib/git/commands/symbolic_ref/update.rb +79 -0
  150. data/lib/git/commands/symbolic_ref.rb +38 -0
  151. data/lib/git/commands/tag/create.rb +142 -0
  152. data/lib/git/commands/tag/delete.rb +57 -0
  153. data/lib/git/commands/tag/list.rb +146 -0
  154. data/lib/git/commands/tag/verify.rb +71 -0
  155. data/lib/git/commands/tag.rb +26 -0
  156. data/lib/git/commands/update_ref/batch.rb +145 -0
  157. data/lib/git/commands/update_ref/delete.rb +90 -0
  158. data/lib/git/commands/update_ref/update.rb +103 -0
  159. data/lib/git/commands/update_ref.rb +42 -0
  160. data/lib/git/commands/version.rb +60 -0
  161. data/lib/git/commands/worktree/add.rb +139 -0
  162. data/lib/git/commands/worktree/list.rb +64 -0
  163. data/lib/git/commands/worktree/lock.rb +58 -0
  164. data/lib/git/commands/worktree/management_base.rb +51 -0
  165. data/lib/git/commands/worktree/move.rb +66 -0
  166. data/lib/git/commands/worktree/prune.rb +67 -0
  167. data/lib/git/commands/worktree/remove.rb +63 -0
  168. data/lib/git/commands/worktree/repair.rb +76 -0
  169. data/lib/git/commands/worktree/unlock.rb +47 -0
  170. data/lib/git/commands/worktree.rb +43 -0
  171. data/lib/git/commands/write_tree.rb +68 -0
  172. data/lib/git/commands.rb +88 -0
  173. data/lib/git/config.rb +72 -5
  174. data/lib/git/config_entry_info.rb +106 -0
  175. data/lib/git/configuring.rb +795 -0
  176. data/lib/git/detached_head_info.rb +57 -0
  177. data/lib/git/diff.rb +437 -86
  178. data/lib/git/diff_file_numstat_info.rb +31 -0
  179. data/lib/git/diff_file_patch_info.rb +136 -0
  180. data/lib/git/diff_file_raw_info.rb +129 -0
  181. data/lib/git/diff_info.rb +162 -0
  182. data/lib/git/diff_path_status.rb +107 -0
  183. data/lib/git/diff_result.rb +34 -0
  184. data/lib/git/diff_stats.rb +111 -0
  185. data/lib/git/dirstat_info.rb +102 -0
  186. data/lib/git/encoding_utils.rb +32 -1
  187. data/lib/git/errors.rb +285 -0
  188. data/lib/git/escaped_path.rb +57 -5
  189. data/lib/git/execution_context/global.rb +31 -0
  190. data/lib/git/execution_context/repository.rb +151 -0
  191. data/lib/git/execution_context.rb +559 -0
  192. data/lib/git/factories.rb +813 -0
  193. data/lib/git/file_ref.rb +77 -0
  194. data/lib/git/fsck_object.rb +56 -0
  195. data/lib/git/fsck_result.rb +132 -0
  196. data/lib/git/log.rb +306 -90
  197. data/lib/git/object.rb +563 -141
  198. data/lib/git/parsers/branch.rb +240 -0
  199. data/lib/git/parsers/cat_file.rb +111 -0
  200. data/lib/git/parsers/config_entry.rb +110 -0
  201. data/lib/git/parsers/diff.rb +792 -0
  202. data/lib/git/parsers/fsck.rb +144 -0
  203. data/lib/git/parsers/grep.rb +42 -0
  204. data/lib/git/parsers/ls_remote.rb +79 -0
  205. data/lib/git/parsers/ls_tree.rb +58 -0
  206. data/lib/git/parsers/remote.rb +162 -0
  207. data/lib/git/parsers/stash.rb +292 -0
  208. data/lib/git/parsers/status.rb +251 -0
  209. data/lib/git/parsers/tag.rb +341 -0
  210. data/lib/git/parsers/worktree.rb +185 -0
  211. data/lib/git/path_resolver.rb +206 -0
  212. data/lib/git/remote.rb +165 -12
  213. data/lib/git/remote_info.rb +203 -0
  214. data/lib/git/repository/branching.rb +964 -0
  215. data/lib/git/repository/committing.rb +246 -0
  216. data/lib/git/repository/context_helpers.rb +293 -0
  217. data/lib/git/repository/diffing.rb +785 -0
  218. data/lib/git/repository/inspecting.rb +252 -0
  219. data/lib/git/repository/logging.rb +410 -0
  220. data/lib/git/repository/maintenance.rb +65 -0
  221. data/lib/git/repository/merging.rb +451 -0
  222. data/lib/git/repository/object_operations.rb +1551 -0
  223. data/lib/git/repository/remote_operations.rb +984 -0
  224. data/lib/git/repository/shared_private.rb +120 -0
  225. data/lib/git/repository/staging.rb +587 -0
  226. data/lib/git/repository/stashing.rb +623 -0
  227. data/lib/git/repository/status_operations.rb +249 -0
  228. data/lib/git/repository/worktree_operations.rb +339 -0
  229. data/lib/git/repository.rb +484 -2
  230. data/lib/git/stash.rb +109 -12
  231. data/lib/git/stash_info.rb +102 -0
  232. data/lib/git/stashes.rb +169 -26
  233. data/lib/git/status.rb +308 -122
  234. data/lib/git/status_file_info.rb +258 -0
  235. data/lib/git/status_info.rb +189 -0
  236. data/lib/git/tag_delete_failure.rb +34 -0
  237. data/lib/git/tag_delete_result.rb +66 -0
  238. data/lib/git/tag_info.rb +99 -0
  239. data/lib/git/url.rb +15 -8
  240. data/lib/git/version.rb +113 -2
  241. data/lib/git/version_constraint.rb +85 -0
  242. data/lib/git/worktree.rb +150 -8
  243. data/lib/git/worktree_info.rb +128 -0
  244. data/lib/git/worktrees.rb +118 -13
  245. data/lib/git.rb +632 -234
  246. metadata +369 -54
  247. data/.github/stale.yml +0 -25
  248. data/.github/workflows/continuous_integration.yml +0 -49
  249. data/.gitignore +0 -10
  250. data/Dockerfile.changelog-rs +0 -12
  251. data/Gemfile +0 -5
  252. data/ISSUE_TEMPLATE.md +0 -15
  253. data/PULL_REQUEST_TEMPLATE.md +0 -9
  254. data/RELEASING.md +0 -70
  255. data/Rakefile +0 -60
  256. data/lib/git/base/factory.rb +0 -99
  257. data/lib/git/base.rb +0 -711
  258. data/lib/git/command_line_result.rb +0 -86
  259. data/lib/git/failed_error.rb +0 -53
  260. data/lib/git/git_execute_error.rb +0 -7
  261. data/lib/git/index.rb +0 -5
  262. data/lib/git/lib.rb +0 -1328
  263. data/lib/git/path.rb +0 -31
  264. data/lib/git/signaled_error.rb +0 -50
  265. data/lib/git/working_directory.rb +0 -4
data/lib/git.rb CHANGED
@@ -1,34 +1,101 @@
1
- # Add the directory containing this file to the start of the load path if it
2
- # isn't there already.
3
- $:.unshift(File.dirname(__FILE__)) unless
4
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/deprecation'
4
+
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
+ module Git
10
+ # The deprecation instance used to emit deprecation warnings for the Git gem
11
+ #
12
+ # @api public
13
+ Deprecation = ActiveSupport::Deprecation.new('6.0.0', 'Git')
14
+
15
+ # Configure a deprecation instance from a GIT_DEPRECATION_BEHAVIOR value
16
+ #
17
+ # @param deprecation [ActiveSupport::Deprecation] the deprecation instance to configure
18
+ #
19
+ # @param behavior [String, nil] the desired behavior name (e.g. `'silence'`); when
20
+ # `nil` the deprecation instance is left unchanged
21
+ #
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))
46
+ end
5
47
 
6
48
  require 'git/author'
7
- require 'git/base'
49
+ require 'git/author_info'
8
50
  require 'git/branch'
51
+ require 'git/branch_info'
9
52
  require 'git/branches'
10
- require 'git/command_line_result'
53
+ require 'git/command_line'
54
+ require 'process_executer'
11
55
  require 'git/config'
56
+ require 'git/config_entry_info'
57
+ require 'git/parsers/config_entry'
58
+ require 'git/configuring'
59
+ require 'git/factories'
12
60
  require 'git/diff'
61
+ require 'git/diff_file_numstat_info'
62
+ require 'git/diff_file_patch_info'
63
+ require 'git/diff_file_raw_info'
64
+ require 'git/diff_info'
65
+ require 'git/parsers/diff'
66
+ require 'git/diff_result'
67
+ require 'git/dirstat_info'
13
68
  require 'git/encoding_utils'
69
+ require 'git/errors'
14
70
  require 'git/escaped_path'
15
- require 'git/failed_error'
16
- require 'git/git_execute_error'
17
- require 'git/index'
18
- require 'git/lib'
71
+ require 'git/execution_context'
72
+ require 'git/execution_context/global'
73
+ require 'git/file_ref'
74
+ require 'git/fsck_object'
75
+ require 'git/fsck_result'
76
+ require 'git/parsers/ls_remote'
77
+ require 'git/parsers/remote'
78
+ require 'git/parsers/status'
79
+ require 'git/version_constraint'
80
+ require 'git/commands'
19
81
  require 'git/log'
20
82
  require 'git/object'
21
- require 'git/path'
22
83
  require 'git/remote'
84
+ require 'git/remote_info'
23
85
  require 'git/repository'
24
- require 'git/signaled_error'
25
86
  require 'git/status'
87
+ require 'git/status_file_info'
88
+ require 'git/status_info'
26
89
  require 'git/stash'
90
+ require 'git/stash_info'
27
91
  require 'git/stashes'
92
+ require 'git/tag_delete_failure'
93
+ require 'git/tag_delete_result'
94
+ require 'git/tag_info'
28
95
  require 'git/url'
29
96
  require 'git/version'
30
- require 'git/working_directory'
31
97
  require 'git/worktree'
98
+ require 'git/worktree_info'
32
99
  require 'git/worktrees'
33
100
 
34
101
  # The Git module provides the basic functions to open a git
@@ -36,161 +103,179 @@ require 'git/worktrees'
36
103
  # open a bare repository, initialize a new repo or clone an
37
104
  # existing remote repository.
38
105
  #
39
- # @author Scott Chacon (mailto:schacon@gmail.com)
40
- #
41
106
  module Git
42
- #g.config('user.name', 'Scott Chacon') # sets value
43
- #g.config('user.email', 'email@email.com') # sets value
44
- #g.config('user.name') # returns 'Scott Chacon'
45
- #g.config # returns whole config hash
46
- def config(name = nil, value = nil)
47
- lib = Git::Lib.new
48
- if(name && value)
49
- # set value
50
- lib.config_set(name, value)
51
- elsif (name)
52
- # return value
53
- lib.config_get(name)
54
- else
55
- # return hash
56
- lib.config_list
57
- end
58
- end
107
+ extend Git::Configuring
108
+ extend Git::Factories
59
109
 
60
- def self.configure
61
- yield Base.config
62
- end
110
+ # Minimum git version required by this gem
111
+ #
112
+ # Commands and features may require newer versions, but this is the absolute
113
+ # minimum supported version for the gem as a whole.
114
+ #
115
+ # @return [Git::Version]
116
+ #
117
+ # @api public
118
+ #
119
+ MINIMUM_GIT_VERSION = Version.parse('2.28.0')
63
120
 
64
- def self.config
65
- return Base.config
66
- end
121
+ # Compatibility shim for code that monkeypatches the `Git::Base` class from
122
+ # versions prior to 5.0.0.
123
+ #
124
+ # `Git::Base` is a module included in {Git::Repository}, so any instance
125
+ # methods added to `Git::Base` are automatically available on
126
+ # {Git::Repository} instances. A deprecation warning is emitted for each
127
+ # method added, encouraging migration to an application-owned extension module.
128
+ #
129
+ # @example Monkeypatching Git::Base (deprecated)
130
+ # module Git::Base
131
+ # def my_helper = "hello"
132
+ # end
133
+ # Git.open('.').my_helper # => "hello"
134
+ #
135
+ # @deprecated Move custom methods to an application-owned extension module and
136
+ # include or prepend it into {Git::Repository}.
137
+ #
138
+ # @api public
139
+ Base = Module.new do
140
+ # Emit a deprecation warning each time a method is defined in Git::Base so
141
+ # that authors of monkeypatches are nudged toward application-owned extensions.
142
+ def self.method_added(method_name)
143
+ Git::Deprecation.warn(
144
+ 'Monkeypatching Git::Base is deprecated and will be removed in v6.0.0. ' \
145
+ "Move #{method_name} to an application-owned extension module for Git::Repository."
146
+ )
147
+ super
148
+ end
67
149
 
68
- def global_config(name = nil, value = nil)
69
- self.class.global_config(name, value)
150
+ # Raise a clear error when legacy code calls Git::Base.new directly.
151
+ def self.new(...)
152
+ raise NoMethodError,
153
+ 'Git::Base.new is not supported. Use Git.open, Git.clone, or Git.init instead.'
154
+ end
70
155
  end
71
156
 
72
- # Open a bare repository
157
+ Repository.include(Base)
158
+
159
+ # Intercept the first lookup of the deprecated `Git::CommandLineResult` constant
73
160
  #
74
- # Opens a bare repository located in the `git_dir` directory.
75
- # Since there is no working copy, you can not checkout or commit
76
- # but you can do most read operations.
161
+ # When `name` is `:CommandLineResult`, caches and returns {Git::CommandLine::Result}
162
+ # after emitting a deprecation warning. Calls `super` for any other unknown constant,
163
+ # preserving normal Ruby `NameError` behavior.
77
164
  #
78
- # @see https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository
79
- # What is a bare repository?
165
+ # @param name [Symbol] the name of the missing constant
80
166
  #
81
- # @example Open a bare repository and retrieve the first commit SHA
82
- # repository = Git.bare('ruby-git.git')
83
- # puts repository.log[0].sha #=> "64c6fa011d3287bab9158049c85f3e85718854a0"
167
+ # @return [Class] the resolved constant value
84
168
  #
85
- # @param [Pathname] git_dir The path to the bare repository directory
86
- # containing an initialized Git repository. If a relative path is given, it
87
- # is converted to an absolute path using
88
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
169
+ # @api private
170
+ def self.const_missing(name)
171
+ return super unless name == :CommandLineResult
172
+
173
+ # Cache the constant first so subsequent accesses are zero-cost even if
174
+ # the deprecation behavior raises (e.g. in the test suite).
175
+ const_set(:CommandLineResult, Git::CommandLine::Result)
176
+ Git::Deprecation.warn(
177
+ 'Git::CommandLineResult is deprecated and will be removed in v6.0.0. ' \
178
+ 'Use Git::CommandLine::Result instead.'
179
+ )
180
+ Git::CommandLine::Result
181
+ end
182
+
183
+ # Gets or sets local git configuration options
89
184
  #
90
- # @param [Hash] options The options for this command (see list of valid
91
- # options below)
185
+ # @overload config(name, value)
186
+ # Set the value for the git named configuration option
92
187
  #
93
- # @option options [Logger] :log A logger to use for Git operations. Git commands
94
- # are logged at the `:info` level. Additional logging is done at the `:debug`
95
- # level.
188
+ # @param name [String] the name of the git configuration option
96
189
  #
97
- # @return [Git::Base] an object that can execute git commands in the context
98
- # of the bare repository.
190
+ # @param value [String, Boolean] the value to set for the git configuration option
99
191
  #
100
- def self.bare(git_dir, options = {})
101
- Base.bare(git_dir, options)
102
- end
103
-
104
- # Clone a repository into an empty or newly created directory
192
+ # @return [Git::CommandLine::Result] the result of the git configuration command
105
193
  #
106
- # @see https://git-scm.com/docs/git-clone git clone
107
- # @see https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a GIT URLs
194
+ # @overload config(name)
195
+ # Get the value for the git named configuration option
108
196
  #
109
- # @param repository_url [URI, Pathname] The (possibly remote) repository url to clone
110
- # from. See [GIT URLS](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a)
111
- # for more information.
197
+ # @param name [String] the name of the git configuration option
112
198
  #
113
- # @param directory [Pathname, nil] The directory to clone into
199
+ # @return [String] the value of the git configuration option
114
200
  #
115
- # If `directory` is a relative directory it is relative to the `path` option if
116
- # given. If `path` is not given, `directory` is relative to the current working
117
- # directory.
201
+ # @overload config()
202
+ # List all git configuration options
118
203
  #
119
- # If `nil`, `directory` will be set to the basename of the last component of
120
- # the path from the `repository_url`. For example, for the URL:
121
- # `https://github.com/org/repo.git`, `directory` will be set to `repo`.
204
+ # @return [Hash{String => String}] a hash of all git configuration options
122
205
  #
123
- # If the last component of the path is `.git`, the next-to-last component of
124
- # the path is used. For example, for the URL `/Users/me/foo/.git`, `directory`
125
- # will be set to `foo`.
206
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
207
+ # Use `Git.config_get(name)`, `Git.config_set(name, value)`, or `Git.config_list` instead.
126
208
  #
127
- # @param [Hash] options The options for this command (see list of valid
128
- # options below)
209
+ def config(name = nil, value = nil)
210
+ Git::Deprecation.warn(
211
+ 'Git#config is deprecated and will be removed in v6.0.0. ' \
212
+ 'Use Git.config_get(name), Git.config_set(name, value), or Git.config_list instead.'
213
+ )
214
+ Git.__send__(:legacy_config_set_get_list, name, value, global: false)
215
+ end
216
+
217
+ # Configures the gem by yielding {Git::Config.instance} to the block
129
218
  #
130
- # @option options [Boolean] :bare Make a bare Git repository. See
131
- # [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
219
+ # @example Set the global git binary path
220
+ # Git.configure { |c| c.binary_path = '/usr/local/bin/git' }
132
221
  #
133
- # @option options [String] :branch The name of a branch or tag to checkout
134
- # instead of the default branch.
222
+ # @return [void]
135
223
  #
136
- # @option options [Array, String] :config A list of configuration options to
137
- # set on the newly created repository.
224
+ # @yield [config] yields the singleton config object
138
225
  #
139
- # @option options [Integer] :depth Create a shallow clone with a history
140
- # truncated to the specified number of commits.
226
+ # @yieldparam config [Git::Config] the singleton config object
141
227
  #
142
- # @option options [String] :filter Request that the server send a partial
143
- # clone according to the given filter
228
+ # @yieldreturn [void]
144
229
  #
145
- # @option options [Logger] :log A logger to use for Git operations. Git
146
- # commands are logged at the `:info` level. Additional logging is done
147
- # at the `:debug` level.
230
+ def self.configure
231
+ yield Git::Config.instance
232
+ nil
233
+ end
234
+
235
+ # Returns the process-wide {Git::Config} singleton
148
236
  #
149
- # @option options [Boolean] :mirror Set up a mirror of the source repository.
237
+ # @example Read the configured binary path
238
+ # Git.config.binary_path #=> "git"
150
239
  #
151
- # @option options [String] :origin Use the value instead `origin` to track
152
- # the upstream repository.
240
+ # @return [Git::Config] the singleton config object
153
241
  #
154
- # @option options [Pathname] :path The directory to clone into. May be used
155
- # as an alternative to the `directory` parameter. If specified, the
156
- # `path` option is used instead of the `directory` parameter.
242
+ def self.config
243
+ Git::Config.instance
244
+ end
245
+
246
+ # Gets or sets global git configuration options
247
+ #
248
+ # @overload global_config(name, value)
249
+ # Set the value for the git named configuration option
157
250
  #
158
- # @option options [Boolean] :recursive After the clone is created, initialize
159
- # all submodules within, using their default settings.
251
+ # @param name [String] the name of the git configuration option
160
252
  #
161
- # @example Clone into the default directory `ruby-git`
162
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git')
253
+ # @param value [String, Boolean] the value to set for the git configuration option
163
254
  #
164
- # @example Clone and then checkout the `development` branch
165
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', branch: 'development')
255
+ # @return [Git::CommandLine::Result] the result of the git configuration command
166
256
  #
167
- # @example Clone into a different directory `my-ruby-git`
168
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', 'my-ruby-git')
169
- # # or:
170
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', path: 'my-ruby-git')
257
+ # @overload global_config(name)
258
+ # Get the value for the git named configuration option
171
259
  #
172
- # @example Create a bare repository in the directory `ruby-git.git`
173
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', bare: true)
260
+ # @param name [String] the name of the git configuration option
174
261
  #
175
- # @example Clone a repository and set a single config option
176
- # git = Git.clone(
177
- # 'https://github.com/ruby-git/ruby-git.git',
178
- # config: 'submodule.recurse=true'
179
- # )
262
+ # @return [String] the value of the git configuration option
180
263
  #
181
- # @example Clone a repository and set multiple config options
182
- # git = Git.clone(
183
- # 'https://github.com/ruby-git/ruby-git.git',
184
- # config: ['user.name=John Doe', 'user.email=john@doe.com']
185
- # )
264
+ # @overload global_config()
265
+ # List all git configuration options
186
266
  #
187
- # @return [Git::Base] an object that can execute git commands in the context
188
- # of the cloned local working copy or cloned repository.
267
+ # @return [Hash{String => String}] a hash of all git configuration options
189
268
  #
190
- def self.clone(repository_url, directory = nil, options = {})
191
- clone_to_options = options.select { |key, _value| %i[bare mirror].include?(key) }
192
- directory ||= Git::URL.clone_to(repository_url, **clone_to_options)
193
- Base.clone(repository_url, directory, options)
269
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
270
+ # Use `Git.config_get(name, global: true)`, `Git.config_set(name, value, global: true)`, or
271
+ # `Git.config_list(global: true)` instead.
272
+ def global_config(name = nil, value = nil)
273
+ Git::Deprecation.warn(
274
+ 'Git#global_config is deprecated and will be removed in v6.0.0. ' \
275
+ 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
276
+ 'or Git.config_list(global: true) instead.'
277
+ )
278
+ Git.__send__(:legacy_config_set_get_list, name, value, global: true)
194
279
  end
195
280
 
196
281
  # Returns the name of the default branch of the given repository
@@ -213,14 +298,16 @@ module Git
213
298
  # @example with the logging option
214
299
  # logger = Logger.new(STDOUT, level: Logger::INFO)
215
300
  # Git.default_branch('.', log: logger) # => 'master'
216
- # I, [2022-04-13T16:01:33.221596 #18415] INFO -- : git '-c' 'core.quotePath=true' '-c' 'color.ui=false' ls-remote '--symref' '--' '.' 'HEAD' 2>&1
301
+ # # Logs the executed git command to STDOUT, for example:
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
217
304
  #
218
305
  # @param repository [URI, Pathname, String] The (possibly remote) repository to get the default branch name for
219
306
  #
220
307
  # See [GIT URLS](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a)
221
308
  # for more information.
222
309
  #
223
- # @param [Hash] options The options for this command (see list of valid
310
+ # @param options [Hash] The options for this command (see list of valid
224
311
  # options below)
225
312
  #
226
313
  # @option options [Logger] :log A logger to use for Git operations. Git
@@ -230,154 +317,465 @@ module Git
230
317
  # @return [String] the name of the default branch
231
318
  #
232
319
  def self.default_branch(repository, options = {})
233
- Base.repository_default_branch(repository, options)
320
+ context = Git::ExecutionContext::Global.new(logger: options[:log])
321
+ output = Git::Commands::LsRemote.new(context).call(repository, 'HEAD', symref: true).stdout
322
+ Git::Parsers::LsRemote.parse_default_branch(output)
234
323
  end
235
324
 
236
- # Export the current HEAD (or a branch, if <tt>options[:branch]</tt>
237
- # is specified) into the +name+ directory, then remove all traces of git from the
238
- # directory.
239
- #
240
- # See +clone+ for options. Does not obey the <tt>:remote</tt> option,
241
- # since the .git info will be deleted anyway; always uses the default
242
- # remote, 'origin.'
243
- def self.export(repository, name, options = {})
244
- options.delete(:remote)
245
- repo = clone(repository, name, {:depth => 1}.merge(options))
246
- repo.checkout("origin/#{options[:branch]}") if options[:branch]
325
+ # Clone a repository into `directory` then remove its `.git` directory
326
+ #
327
+ # Exports the current HEAD (or the specific branch given in <tt>options[:branch]</tt>)
328
+ # into the given `directory`. It then removes all traces of git from the directory.
329
+ #
330
+ # Takes the same options as {Git.clone} except that `:depth` defaults to 1 and
331
+ # `:remote` is ignored with a deprecation warning.
332
+ #
333
+ # @param repository_url [String, URI, Pathname] the repository to export from
334
+ #
335
+ # @param directory [String, Pathname, nil] the directory to export into; defaults to the
336
+ # repository basename
337
+ #
338
+ # @param options [Hash] options forwarded to {Git.clone} (`:depth` defaults to 1)
339
+ #
340
+ # @option options [String] :remote deprecated and ignored; delete it from the call.
341
+ # Passing it emits a deprecation warning and it will be removed in a future
342
+ # major release.
343
+ #
344
+ # @option options [String] :branch the branch or tag to export instead of HEAD.
345
+ # Give the short name (`main`, `v1.0.0`); a full ref path such as
346
+ # `refs/tags/v1.0.0` or a commit SHA is not accepted. Use `:revision` to
347
+ # export a SHA.
348
+ #
349
+ # @return [void]
350
+ #
351
+ def self.export(repository_url, directory = nil, options = {})
352
+ if options.key?(:remote)
353
+ Git::Deprecation.warn(
354
+ 'The :remote option to Git.export is ignored, is deprecated, and will be removed in a future ' \
355
+ 'major release. Delete it from the call.'
356
+ )
357
+ options.delete(:remote)
358
+ end
359
+ repo = clone(repository_url, directory, { depth: 1 }.merge(options))
247
360
  FileUtils.rm_r File.join(repo.dir.to_s, '.git')
248
361
  end
249
362
 
250
- # Same as g.config, but forces it to be at the global level
363
+ # Get or set a git global configuration value
364
+ #
365
+ # @example Set a value
366
+ # Git.global_config('user.name', 'Scott Chacon')
367
+ #
368
+ # @example Get a value
369
+ # Git.global_config('user.name') # => 'Scott Chacon'
370
+ #
371
+ # @example List all global config entries
372
+ # Git.global_config # => { 'user.name' => 'Scott Chacon', ... }
373
+ #
374
+ # @param name [String, nil] the config key to get or set; omit to list all
375
+ #
376
+ # @param value [Object, nil] the value to set; omit to get or list
377
+ #
378
+ # @return [String, Hash, Git::CommandLine::Result] the config value, all entries,
379
+ # or the result of the set command
380
+ #
381
+ # @deprecated Use {Git.config_get}, {Git.config_set}, or {Git.config_list} instead.
382
+ #
383
+ # - `Git.global_config('user.name')` → `Git.config_get('user.name', global: true)`
384
+ # - `Git.global_config('user.name', 'Bob')` → `Git.config_set('user.name', 'Bob', global: true)`
385
+ # - `Git.global_config` → `Git.config_list(global: true)`
251
386
  #
252
- #g.config('user.name', 'Scott Chacon') # sets value
253
- #g.config('user.email', 'email@email.com') # sets value
254
- #g.config('user.name') # returns 'Scott Chacon'
255
- #g.config # returns whole config hash
256
387
  def self.global_config(name = nil, value = nil)
257
- lib = Git::Lib.new(nil, nil)
258
- if(name && value)
259
- # set value
260
- lib.global_config_set(name, value)
261
- elsif (name)
262
- # return value
263
- lib.global_config_get(name)
264
- else
265
- # return hash
266
- lib.global_config_list
388
+ Git::Deprecation.warn(
389
+ 'Git.global_config is deprecated and will be removed in v6.0.0. ' \
390
+ 'Use Git.config_get(name, global: true), Git.config_set(name, value, global: true), ' \
391
+ 'or Git.config_list(global: true) instead.'
392
+ )
393
+ legacy_config_set_get_list(name, value, global: true)
394
+ end
395
+
396
+ # Option keys accepted by {.ls_remote}
397
+ #
398
+ # Parser-incompatible options such as `:get_url` and `:symref` are intentionally
399
+ # excluded because {Git::Parsers::LsRemote.parse_output} cannot handle the
400
+ # non-standard output formats those flags produce.
401
+ #
402
+ # @return [Array<Symbol>]
403
+ #
404
+ # @api private
405
+ #
406
+ LS_REMOTE_ALLOWED_OPTS = %i[
407
+ branches b heads h tags t refs upload_pack quiet q exit_code sort server_option o timeout
408
+ ].freeze
409
+ private_constant :LS_REMOTE_ALLOWED_OPTS
410
+
411
+ # Displays references available in a remote repository along with the associated commit IDs
412
+ #
413
+ # @example From a remote repository given its URL
414
+ # references = Git.ls_remote('https://github.com/user/repo.git')
415
+ #
416
+ # @example From the default remote of the current repository
417
+ # references = Git.ls_remote
418
+ #
419
+ # @example From a specific remote of the current repository
420
+ # references = Git.ls_remote('origin')
421
+ #
422
+ # @param repository [String, nil] the target repository location or the name of a remote
423
+ #
424
+ # Defaults to `'.'` (the current directory). Passing `nil` explicitly is
425
+ # deprecated and will be removed in v6.0.0; pass `'.'` or omit the argument.
426
+ #
427
+ # @param options [Hash] the options to pass to the git command
428
+ #
429
+ # @option options [Boolean, nil] :branches (nil) limit output to refs under `refs/heads/`
430
+ #
431
+ # Alias: `:b`
432
+ #
433
+ # @option options [Boolean, nil] :heads (nil) limit output to refs under `refs/heads/`
434
+ #
435
+ # Deprecated: use `:branches` instead. Kept for backward compatibility with
436
+ # older git versions where `--heads` is the only supported flag.
437
+ #
438
+ # Alias: `:h`
439
+ #
440
+ # @option options [Boolean, nil] :tags (nil) limit output to refs under `refs/tags/`
441
+ #
442
+ # Alias: `:t`
443
+ #
444
+ # @option options [Boolean, nil] :refs (nil) exclude peeled tags and pseudorefs
445
+ # like `HEAD` from the output
446
+ #
447
+ # @option options [String] :upload_pack (nil) full path to `git-upload-pack` on the
448
+ # remote host
449
+ #
450
+ # Useful when accessing repositories via SSH where the daemon does not use the
451
+ # PATH configured by the user.
452
+ #
453
+ # @option options [Boolean, nil] :quiet (nil) do not print the remote URL to stderr
454
+ #
455
+ # Alias: `:q`
456
+ #
457
+ # @option options [Boolean, nil] :exit_code (nil) exit with status `2` when no
458
+ # matching refs are found in the remote repository
459
+ #
460
+ # Without this option, the command exits `0` whenever it successfully
461
+ # communicates with the remote, even if no refs match.
462
+ #
463
+ # @option options [String] :sort (nil) sort output by the given key
464
+ #
465
+ # Prefix `-` for descending order. Supports `"version:refname"` or `"v:refname"`.
466
+ # See `git for-each-ref` for sort key documentation.
467
+ #
468
+ # @option options [String, Array<String>] :server_option (nil) transmit a string to
469
+ # the server when communicating using protocol version 2
470
+ #
471
+ # The string must not contain NUL or LF characters. Repeatable by passing an
472
+ # Array. Alias: `:o`
473
+ #
474
+ # @option options [Numeric] :timeout (nil) execution timeout in seconds
475
+ #
476
+ # @option options [Logger] :log (nil) a logger to use for Git operations
477
+ #
478
+ # Git commands are logged at the `:info` level. Additional logging is done at
479
+ # the `:debug` level.
480
+ #
481
+ # @return [Hash{String => Hash}] the available references of the target repo
482
+ #
483
+ def self.ls_remote(repository = '.', options = {})
484
+ repository = normalize_ls_remote_repository(repository)
485
+ options = options.dup
486
+ log = options.delete(:log)
487
+ unknown = options.keys - LS_REMOTE_ALLOWED_OPTS
488
+ raise ArgumentError, "Unknown options: #{unknown.join(', ')}" unless unknown.empty?
489
+
490
+ context = Git::ExecutionContext::Global.new(logger: log)
491
+ output_lines = Git::Commands::LsRemote.new(context).call(repository, **options).stdout.split("\n")
492
+ Git::Parsers::LsRemote.parse_output(output_lines)
493
+ end
494
+
495
+ # Normalize the repository argument for {.ls_remote}
496
+ #
497
+ # Returns the repository unchanged unless it is nil, in which case a
498
+ # deprecation warning is emitted and `'.'` is returned.
499
+ #
500
+ # @param repository [String, nil] the repository argument passed by the caller
501
+ #
502
+ # @return [String] the normalized repository value (`'.'` when nil was given)
503
+ #
504
+ # @api private
505
+ #
506
+ def self.normalize_ls_remote_repository(repository)
507
+ return repository unless repository.nil?
508
+
509
+ Git::Deprecation.warn(
510
+ 'Passing nil as the repository to Git.ls_remote is deprecated and will ' \
511
+ "be removed in v6.0.0. Pass '.' explicitly or omit the argument instead."
512
+ )
513
+
514
+ '.'
515
+ end
516
+ private_class_method :normalize_ls_remote_repository
517
+
518
+ # Thread-safe cache for git versions, keyed by binary path.
519
+ @git_version_cache_mutex = Mutex.new
520
+ @git_version_cache = {}
521
+
522
+ # Return the cached git version for the given binary path
523
+ #
524
+ # If it isn't already known, compute it using the given block.
525
+ #
526
+ # @param binary_path [String] the path to the git binary
527
+ #
528
+ # @return [Git::Version] the git version
529
+ #
530
+ # @yield [] compute the git version if it is not cached
531
+ #
532
+ # @yieldreturn [Git::Version] the computed git version
533
+ #
534
+ # @api private
535
+ def self.cached_git_version(binary_path, &block)
536
+ @git_version_cache_mutex.synchronize do
537
+ @git_version_cache[binary_path] ||= block.call
267
538
  end
268
539
  end
269
540
 
270
- # Create an empty Git repository or reinitialize an existing Git repository
541
+ # Clear the cached git version for all binary paths
271
542
  #
272
- # @param [Pathname] directory If the `:bare` option is NOT given or is not
273
- # `true`, the repository will be created in `"#{directory}/.git"`.
274
- # Otherwise, the repository is created in `"#{directory}"`.
543
+ # @return [void]
275
544
  #
276
- # All directories along the path to `directory` are created if they do not exist.
545
+ # @api private
546
+ def self.clear_git_version_cache
547
+ @git_version_cache_mutex.synchronize do
548
+ @git_version_cache.clear
549
+ end
550
+ end
551
+
552
+ # Return the version of a git binary as a {Git::Version}
277
553
  #
278
- # A relative path is referenced from the current working directory of the process
279
- # and converted to an absolute path using
280
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
554
+ # @example Default binary
555
+ # Git.git_version #=> #<Git::Version 2.42.0>
281
556
  #
282
- # @param [Hash] options The options for this command (see list of valid
283
- # options below)
557
+ # @example Explicit binary path
558
+ # Git.git_version('/opt/homebrew/bin/git') #=> #<Git::Version 2.42.0>
284
559
  #
285
- # @option options [Boolean] :bare Instead of creating a repository at
286
- # `"#{directory}/.git"`, create a bare repository at `"#{directory}"`.
287
- # See [what is a bare repository?](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefbarerepositoryabarerepository).
560
+ # @param binary_path [String, nil] path to the git binary; defaults to
561
+ # `Git::Config.instance.binary_path`
288
562
  #
289
- # @option options [String] :initial_branch Use the specified name for the
290
- # initial branch in the newly created repository.
563
+ # @return [Git::Version] the parsed git version
291
564
  #
292
- # @option options [Pathname] :repository the path to put the newly initialized
293
- # Git repository. The default for non-bare repository is `"#{directory}/.git"`.
565
+ # @raise [Git::UnexpectedResultError] if the version output cannot be parsed
294
566
  #
295
- # A relative path is referenced from the current working directory of the process
296
- # and converted to an absolute path using
297
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
567
+ # @raise [Git::FailedError] if the git binary exits with a non-zero status
298
568
  #
299
- # @option options [Logger] :log A logger to use for Git operations. Git
300
- # commands are logged at the `:info` level. Additional logging is done
301
- # at the `:debug` level.
569
+ # @raise [Git::Error] if the binary is not found or fails to launch
570
+ #
571
+ def self.git_version(binary_path = nil)
572
+ path = binary_path || Git::Config.instance.binary_path
573
+ cached_git_version(path) { run_git_version(path) }
574
+ end
575
+
576
+ # Return the version of the git binary
577
+ #
578
+ # @param path [String] the path to the git binary
579
+ #
580
+ # @return [Git::Version] the parsed git version
581
+ #
582
+ # @raise [Git::UnexpectedResultError] if the version output cannot be parsed
583
+ #
584
+ # @raise [Git::FailedError] if the git binary exits with a non-zero status
585
+ #
586
+ # @raise [Git::Error] if the binary is not found or fails to launch
587
+ #
588
+ # @api private
589
+ #
590
+ def self.run_git_version(path)
591
+ output = Git::Commands::Version.new(Git::ExecutionContext::Global.new(binary_path: path)).call.stdout
592
+ Git::Version.parse(output)
593
+ end
594
+ private_class_method :run_git_version
595
+
596
+ # Get or set a git config value
302
597
  #
303
- # @return [Git::Base] an object that can execute git commands in the context
304
- # of the newly initialized repository
598
+ # @overload legacy_config_set_get_list(name, value, global:)
305
599
  #
306
- # @example Initialize a repository in the current directory
307
- # git = Git.init
600
+ # Set the value of a git configuration option
308
601
  #
309
- # @example Initialize a repository in some other directory
310
- # git = Git.init '~/code/ruby-git'
602
+ # @param name [String] the name of the git configuration value to set
311
603
  #
312
- # @example Initialize a bare repository
313
- # git = Git.init '~/code/ruby-git.git', bare: true
604
+ # @param value [String, Boolean] the value to set
314
605
  #
315
- # @example Initialize a repository in a non-default location (outside of the working copy)
316
- # git = Git.init '~/code/ruby-git', repository: '~/code/ruby-git.git'
606
+ # @param global [Boolean] true to use the global git configuration, false for the
607
+ # local repo config
317
608
  #
318
- # @see https://git-scm.com/docs/git-init git init
609
+ # @return [Git::CommandLine::Result] the result of the git config command
319
610
  #
320
- def self.init(directory = '.', options = {})
321
- Base.init(directory, options)
611
+ # @overload legacy_config_set_get_list(name, global:)
612
+ #
613
+ # Get the value of a git configuration option
614
+ #
615
+ # @param name [String] the name of the git configuration value to get
616
+ #
617
+ # @param global [Boolean] true to use the global git configuration, false for the
618
+ # local repo config
619
+ #
620
+ # @return [String] the value of the git configuration option
621
+ #
622
+ # @overload legacy_config_set_get_list(global:)
623
+ #
624
+ # Get 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
+ # @raise [Git::FailedError] if the git config command fails
632
+ #
633
+ # @api private
634
+ #
635
+ def self.legacy_config_set_get_list(name, value, global:)
636
+ if !name.nil? && !value.nil?
637
+ legacy_config_set(name, value, global:)
638
+ elsif !name.nil?
639
+ legacy_config_get(name, global:)
640
+ else
641
+ legacy_config_list(global:)
642
+ end
322
643
  end
644
+ private_class_method :legacy_config_set_get_list
323
645
 
324
- # returns a Hash containing information about the references
325
- # of the target repository
646
+ # Set the value of a git configuration option
326
647
  #
327
- # options
328
- # :refs
648
+ # @param name [String] the name of the git configuration value to set
329
649
  #
330
- # @param [String|NilClass] location the target repository location or nil for '.'
331
- # @return [{String=>Hash}] the available references of the target repo.
332
- def self.ls_remote(location = nil, options = {})
333
- Git::Lib.new.ls_remote(location, options)
650
+ # @param value [String, Boolean] the value to set
651
+ #
652
+ # @param global [Boolean] whether to use the global git configuration
653
+ #
654
+ # @api private
655
+ #
656
+ def self.legacy_config_set(name, value, global:)
657
+ options = global ? { global: true } : {}
658
+ Git::Commands::ConfigOptionSyntax::Set.new(execution_context).call(name, value, **options)
334
659
  end
660
+ private_class_method :legacy_config_set
335
661
 
336
- # Open a an existing Git working directory
662
+ # Get the value of a git configuration option
337
663
  #
338
- # Git.open will most likely be the most common way to create
339
- # a git reference, referring to an existing working directory.
664
+ # @param name [String] the name of the git configuration option
340
665
  #
341
- # If not provided in the options, the library will assume
342
- # the repository and index are in the default places (`.git/`, `.git/index`).
666
+ # @param global [Boolean] whether to use the global git configuration
343
667
  #
344
- # @example Open the Git working directory in the current directory
345
- # git = Git.open
668
+ # @return [String] the value of the git configuration option
346
669
  #
347
- # @example Open a Git working directory in some other directory
348
- # git = Git.open('~/Projects/ruby-git')
670
+ # @api private
349
671
  #
350
- # @example Use a logger to see what is going on
351
- # logger = Logger.new(STDOUT)
352
- # git = Git.open('~/Projects/ruby-git', log: logger)
672
+ def self.legacy_config_get(name, global:)
673
+ options = global ? { global: true } : {}
674
+ result = Git::Commands::ConfigOptionSyntax::Get.new(execution_context).call(name, **options)
675
+ raise Git::FailedError, result if result.status.exitstatus != 0
676
+
677
+ result.stdout
678
+ end
679
+ private_class_method :legacy_config_get
680
+
681
+ # Get a list of all git configuration options
353
682
  #
354
- # @example Open a working copy whose repository is in a non-standard directory
355
- # git = Git.open('~/Projects/ruby-git', repository: '~/Project/ruby-git.git')
683
+ # @param global [Boolean] true to use the global git configuration, false for the
684
+ # local repo config
356
685
  #
357
- # @param [Pathname] working_dir the path to the working directory to use
358
- # for git commands.
686
+ # @return [Hash{String => String}] all git configuration options
359
687
  #
360
- # A relative path is referenced from the current working directory of the process
361
- # and converted to an absolute path using
362
- # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
688
+ # @api private
363
689
  #
364
- # @param [Hash] options The options for this command (see list of valid
365
- # options below)
690
+ def self.legacy_config_list(global:)
691
+ options = global ? { global: true } : {}
692
+ output = Git::Commands::ConfigOptionSyntax::List.new(execution_context).call(**options).stdout
693
+ parse_config_list(output.split("\n"))
694
+ end
695
+ private_class_method :legacy_config_list
696
+
697
+ # Parse the output of `git config --list` into a hash
366
698
  #
367
- # @option options [Pathname] :repository used to specify a non-standard path to
368
- # the repository directory. The default is `"#{working_dir}/.git"`.
699
+ # @param lines [Array<String>] the lines of output from `git config --list`
369
700
  #
370
- # @option options [Pathname] :index used to specify a non-standard path to an
371
- # index file. The default is `"#{working_dir}/.git/index"`
701
+ # @return [Hash{String => String}] the parsed git configuration options
372
702
  #
373
- # @option options [Logger] :log A logger to use for Git operations. Git
374
- # commands are logged at the `:info` level. Additional logging is done
375
- # at the `:debug` level.
703
+ # @api private
704
+ #
705
+ def self.parse_config_list(lines)
706
+ lines.each_with_object({}) do |line, hsh|
707
+ key, value = line.split('=', 2)
708
+ hsh[key] = value || ''
709
+ end
710
+ end
711
+ private_class_method :parse_config_list
712
+
713
+ # @api private
714
+ def self.execution_context
715
+ Git::ExecutionContext::Global.new
716
+ end
717
+ private_class_method :execution_context
718
+
719
+ # Scopes that require an active repository and cannot be used at the Git module level
720
+ #
721
+ # @api private
722
+ #
723
+ REPOSITORY_SPECIFIC_SCOPES = %i[local worktree blob].freeze
724
+ private_constant :REPOSITORY_SPECIFIC_SCOPES
725
+
726
+ # Raises +ArgumentError+ when a repository-specific scope is requested.
727
+ #
728
+ # The +:local+, +:worktree+, and +:blob+ scopes require an active git
729
+ # repository and are therefore not valid at the Git module level.
730
+ #
731
+ # @param options_to_check [Hash{Symbol => Object}] the scope options to check
732
+ #
733
+ # If any of the options listed in +REPOSITORY_SPECIFIC_SCOPES+ are present and
734
+ # truthy, an +ArgumentError+ will be raised.
735
+ #
736
+ # @option options_to_check [Object] :local truthy value requests local scope
737
+ #
738
+ # @option options_to_check [Object] :worktree truthy value requests worktree scope
739
+ #
740
+ # @option options_to_check [Object] :blob truthy value requests blob scope
741
+ #
742
+ # @raise [ArgumentError] if a repository-specific scope is requested
743
+ #
744
+ # @api private
745
+ #
746
+ def self.assert_valid_scope!(**options_to_check)
747
+ invalid = REPOSITORY_SPECIFIC_SCOPES.select { |s| options_to_check[s] }
748
+ return if invalid.empty?
749
+
750
+ raise ArgumentError, "#{invalid.join(', ')} scope requires a repository"
751
+ end
752
+ private_class_method :assert_valid_scope!
753
+
754
+ # Return the version of the git binary
755
+ #
756
+ # @example Basic usage
757
+ # Git.binary_version # => [2, 46, 0]
758
+ #
759
+ # @param binary_path [String, nil] path to the git binary; defaults to
760
+ # `Git::Config.instance.binary_path`
761
+ #
762
+ # @return [Array<Integer>] the version of the git binary
763
+ #
764
+ # @deprecated Use {Git.git_version} instead, which returns a
765
+ # {Git::Version} (not an Array)
376
766
  #
377
- # @return [Git::Base] an object that can execute git commands in the context
378
- # of the opened working copy
767
+ # For the legacy array shape, call: `Git.git_version.to_a`.
768
+ # The optional binary_path argument is preserved:
769
+ # `Git.git_version(binary_path)`.
379
770
  #
380
- def self.open(working_dir, options = {})
381
- Base.open(working_dir, options)
771
+ def self.binary_version(binary_path = nil)
772
+ binary_path ||= Git::Config.instance.binary_path
773
+ Git::Deprecation.warn(
774
+ 'Git.binary_version is deprecated and will be removed in v6.0.0. ' \
775
+ 'Use Git.git_version instead, which returns a Git::Version ' \
776
+ '(not an Array). For the legacy array shape, call: Git.git_version.to_a. ' \
777
+ 'The optional binary_path argument is preserved: Git.git_version(binary_path).'
778
+ )
779
+ git_version(binary_path).to_a
382
780
  end
383
781
  end