git 4.3.2 → 5.0.0.beta.2

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 (297) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +73 -2705
  3. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
  4. data/.github/pull_request_template.md +3 -1
  5. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  6. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  7. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  8. data/.github/skills/command-implementation/SKILL.md +229 -0
  9. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  10. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  11. data/.github/skills/dependency-management/SKILL.md +72 -0
  12. data/.github/skills/development-workflow/SKILL.md +506 -0
  13. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  14. data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
  15. data/.github/skills/extract-facade-from-base-lib/SKILL.md +600 -0
  16. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  17. data/.github/skills/facade-implementation/SKILL.md +274 -0
  18. data/.github/skills/facade-test-conventions/SKILL.md +394 -0
  19. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  20. data/.github/skills/make-skill-template/SKILL.md +176 -0
  21. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  22. data/.github/skills/project-context/SKILL.md +313 -0
  23. data/.github/skills/pull-request-review/SKILL.md +168 -0
  24. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  25. data/.github/skills/release-management/SKILL.md +125 -0
  26. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  27. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  28. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  29. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  30. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  31. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  32. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  33. data/.github/skills/test-debugging/SKILL.md +160 -0
  34. data/.github/skills/yard-documentation/SKILL.md +793 -0
  35. data/.github/workflows/continuous_integration.yml +3 -2
  36. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  37. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  38. data/.github/workflows/release.yml +3 -4
  39. data/.gitignore +8 -0
  40. data/.husky/pre-commit +13 -0
  41. data/.release-please-manifest.json +1 -1
  42. data/.rspec +3 -0
  43. data/.rubocop.yml +12 -3
  44. data/.rubocop_todo.yml +23 -5
  45. data/.yardopts +1 -0
  46. data/CHANGELOG.md +0 -40
  47. data/CONTRIBUTING.md +694 -53
  48. data/README.md +67 -15
  49. data/Rakefile +61 -9
  50. data/UPGRADING.md +141 -0
  51. data/commitlint.test +4 -0
  52. data/git.gemspec +18 -7
  53. data/lib/git/args_builder.rb +0 -8
  54. data/lib/git/branch.rb +369 -43
  55. data/lib/git/branch_delete_failure.rb +31 -0
  56. data/lib/git/branch_delete_result.rb +63 -0
  57. data/lib/git/branch_info.rb +178 -0
  58. data/lib/git/branches.rb +122 -24
  59. data/lib/git/command_line/base.rb +255 -0
  60. data/lib/git/command_line/capturing.rb +251 -0
  61. data/lib/git/command_line/result.rb +96 -0
  62. data/lib/git/command_line/streaming.rb +196 -0
  63. data/lib/git/command_line.rb +43 -322
  64. data/lib/git/command_line_result.rb +4 -88
  65. data/lib/git/commands/add.rb +131 -0
  66. data/lib/git/commands/am/abort.rb +43 -0
  67. data/lib/git/commands/am/apply.rb +252 -0
  68. data/lib/git/commands/am/continue.rb +43 -0
  69. data/lib/git/commands/am/quit.rb +43 -0
  70. data/lib/git/commands/am/retry.rb +47 -0
  71. data/lib/git/commands/am/show_current_patch.rb +64 -0
  72. data/lib/git/commands/am/skip.rb +42 -0
  73. data/lib/git/commands/am.rb +33 -0
  74. data/lib/git/commands/apply.rb +237 -0
  75. data/lib/git/commands/archive/list_formats.rb +46 -0
  76. data/lib/git/commands/archive.rb +140 -0
  77. data/lib/git/commands/arguments.rb +3510 -0
  78. data/lib/git/commands/base.rb +404 -0
  79. data/lib/git/commands/branch/copy.rb +94 -0
  80. data/lib/git/commands/branch/create.rb +173 -0
  81. data/lib/git/commands/branch/delete.rb +80 -0
  82. data/lib/git/commands/branch/list.rb +162 -0
  83. data/lib/git/commands/branch/move.rb +94 -0
  84. data/lib/git/commands/branch/set_upstream.rb +86 -0
  85. data/lib/git/commands/branch/show_current.rb +49 -0
  86. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  87. data/lib/git/commands/branch.rb +34 -0
  88. data/lib/git/commands/cat_file/batch.rb +369 -0
  89. data/lib/git/commands/cat_file/filtered.rb +105 -0
  90. data/lib/git/commands/cat_file/raw.rb +216 -0
  91. data/lib/git/commands/cat_file.rb +49 -0
  92. data/lib/git/commands/checkout/branch.rb +151 -0
  93. data/lib/git/commands/checkout/files.rb +115 -0
  94. data/lib/git/commands/checkout.rb +38 -0
  95. data/lib/git/commands/checkout_index.rb +105 -0
  96. data/lib/git/commands/clean.rb +100 -0
  97. data/lib/git/commands/clone.rb +240 -0
  98. data/lib/git/commands/commit.rb +272 -0
  99. data/lib/git/commands/commit_tree.rb +100 -0
  100. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  101. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  102. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  103. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  104. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  105. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  106. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +107 -0
  107. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  108. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  109. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  110. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  111. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  112. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  113. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  114. data/lib/git/commands/config_option_syntax.rb +56 -0
  115. data/lib/git/commands/describe.rb +155 -0
  116. data/lib/git/commands/diff.rb +656 -0
  117. data/lib/git/commands/diff_files.rb +518 -0
  118. data/lib/git/commands/diff_index.rb +496 -0
  119. data/lib/git/commands/fetch.rb +352 -0
  120. data/lib/git/commands/fsck.rb +136 -0
  121. data/lib/git/commands/gc.rb +132 -0
  122. data/lib/git/commands/grep.rb +338 -0
  123. data/lib/git/commands/init.rb +99 -0
  124. data/lib/git/commands/log.rb +632 -0
  125. data/lib/git/commands/ls_files.rb +191 -0
  126. data/lib/git/commands/ls_remote.rb +155 -0
  127. data/lib/git/commands/ls_tree.rb +131 -0
  128. data/lib/git/commands/maintenance/register.rb +75 -0
  129. data/lib/git/commands/maintenance/run.rb +104 -0
  130. data/lib/git/commands/maintenance/start.rb +66 -0
  131. data/lib/git/commands/maintenance/stop.rb +55 -0
  132. data/lib/git/commands/maintenance/unregister.rb +79 -0
  133. data/lib/git/commands/maintenance.rb +31 -0
  134. data/lib/git/commands/merge/abort.rb +44 -0
  135. data/lib/git/commands/merge/continue.rb +44 -0
  136. data/lib/git/commands/merge/quit.rb +46 -0
  137. data/lib/git/commands/merge/start.rb +245 -0
  138. data/lib/git/commands/merge.rb +28 -0
  139. data/lib/git/commands/merge_base.rb +86 -0
  140. data/lib/git/commands/mv.rb +77 -0
  141. data/lib/git/commands/name_rev.rb +114 -0
  142. data/lib/git/commands/pull.rb +377 -0
  143. data/lib/git/commands/push.rb +246 -0
  144. data/lib/git/commands/read_tree.rb +149 -0
  145. data/lib/git/commands/remote/add.rb +91 -0
  146. data/lib/git/commands/remote/get_url.rb +66 -0
  147. data/lib/git/commands/remote/list.rb +54 -0
  148. data/lib/git/commands/remote/prune.rb +61 -0
  149. data/lib/git/commands/remote/remove.rb +52 -0
  150. data/lib/git/commands/remote/rename.rb +69 -0
  151. data/lib/git/commands/remote/set_branches.rb +63 -0
  152. data/lib/git/commands/remote/set_head.rb +82 -0
  153. data/lib/git/commands/remote/set_url.rb +71 -0
  154. data/lib/git/commands/remote/set_url_add.rb +61 -0
  155. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  156. data/lib/git/commands/remote/show.rb +71 -0
  157. data/lib/git/commands/remote/update.rb +72 -0
  158. data/lib/git/commands/remote.rb +42 -0
  159. data/lib/git/commands/repack.rb +277 -0
  160. data/lib/git/commands/reset.rb +147 -0
  161. data/lib/git/commands/rev_parse.rb +297 -0
  162. data/lib/git/commands/revert/abort.rb +45 -0
  163. data/lib/git/commands/revert/continue.rb +57 -0
  164. data/lib/git/commands/revert/quit.rb +47 -0
  165. data/lib/git/commands/revert/skip.rb +44 -0
  166. data/lib/git/commands/revert/start.rb +153 -0
  167. data/lib/git/commands/revert.rb +29 -0
  168. data/lib/git/commands/rm.rb +114 -0
  169. data/lib/git/commands/show.rb +632 -0
  170. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  171. data/lib/git/commands/show_ref/exists.rb +78 -0
  172. data/lib/git/commands/show_ref/list.rb +145 -0
  173. data/lib/git/commands/show_ref/verify.rb +120 -0
  174. data/lib/git/commands/show_ref.rb +42 -0
  175. data/lib/git/commands/stash/apply.rb +75 -0
  176. data/lib/git/commands/stash/branch.rb +65 -0
  177. data/lib/git/commands/stash/clear.rb +41 -0
  178. data/lib/git/commands/stash/create.rb +58 -0
  179. data/lib/git/commands/stash/drop.rb +67 -0
  180. data/lib/git/commands/stash/list.rb +39 -0
  181. data/lib/git/commands/stash/pop.rb +78 -0
  182. data/lib/git/commands/stash/push.rb +103 -0
  183. data/lib/git/commands/stash/show.rb +149 -0
  184. data/lib/git/commands/stash/store.rb +63 -0
  185. data/lib/git/commands/stash.rb +38 -0
  186. data/lib/git/commands/status.rb +169 -0
  187. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  188. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  189. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  190. data/lib/git/commands/symbolic_ref.rb +38 -0
  191. data/lib/git/commands/tag/create.rb +139 -0
  192. data/lib/git/commands/tag/delete.rb +55 -0
  193. data/lib/git/commands/tag/list.rb +143 -0
  194. data/lib/git/commands/tag/verify.rb +71 -0
  195. data/lib/git/commands/tag.rb +26 -0
  196. data/lib/git/commands/update_ref/batch.rb +140 -0
  197. data/lib/git/commands/update_ref/delete.rb +92 -0
  198. data/lib/git/commands/update_ref/update.rb +106 -0
  199. data/lib/git/commands/update_ref.rb +42 -0
  200. data/lib/git/commands/version.rb +57 -0
  201. data/lib/git/commands/worktree/add.rb +140 -0
  202. data/lib/git/commands/worktree/list.rb +64 -0
  203. data/lib/git/commands/worktree/lock.rb +58 -0
  204. data/lib/git/commands/worktree/management_base.rb +51 -0
  205. data/lib/git/commands/worktree/move.rb +66 -0
  206. data/lib/git/commands/worktree/prune.rb +67 -0
  207. data/lib/git/commands/worktree/remove.rb +63 -0
  208. data/lib/git/commands/worktree/repair.rb +76 -0
  209. data/lib/git/commands/worktree/unlock.rb +47 -0
  210. data/lib/git/commands/worktree.rb +43 -0
  211. data/lib/git/commands/write_tree.rb +68 -0
  212. data/lib/git/commands.rb +87 -0
  213. data/lib/git/config.rb +17 -0
  214. data/lib/git/config_entry_info.rb +106 -0
  215. data/lib/git/configuring.rb +665 -0
  216. data/lib/git/deprecation.rb +9 -0
  217. data/lib/git/detached_head_info.rb +54 -0
  218. data/lib/git/diff.rb +293 -7
  219. data/lib/git/diff_file_numstat_info.rb +29 -0
  220. data/lib/git/diff_file_patch_info.rb +134 -0
  221. data/lib/git/diff_file_raw_info.rb +127 -0
  222. data/lib/git/diff_info.rb +169 -0
  223. data/lib/git/diff_path_status.rb +67 -19
  224. data/lib/git/diff_result.rb +32 -0
  225. data/lib/git/diff_stats.rb +51 -14
  226. data/lib/git/dirstat_info.rb +86 -0
  227. data/lib/git/errors.rb +65 -2
  228. data/lib/git/execution_context/global.rb +31 -0
  229. data/lib/git/execution_context/repository.rb +136 -0
  230. data/lib/git/execution_context.rb +501 -0
  231. data/lib/git/file_ref.rb +74 -0
  232. data/lib/git/fsck_object.rb +9 -9
  233. data/lib/git/fsck_result.rb +1 -1
  234. data/lib/git/log.rb +9 -2
  235. data/lib/git/object.rb +90 -27
  236. data/lib/git/parsers/branch.rb +224 -0
  237. data/lib/git/parsers/cat_file.rb +111 -0
  238. data/lib/git/parsers/config_entry.rb +110 -0
  239. data/lib/git/parsers/diff.rb +585 -0
  240. data/lib/git/parsers/fsck.rb +133 -0
  241. data/lib/git/parsers/grep.rb +42 -0
  242. data/lib/git/parsers/ls_remote.rb +79 -0
  243. data/lib/git/parsers/ls_tree.rb +58 -0
  244. data/lib/git/parsers/stash.rb +208 -0
  245. data/lib/git/parsers/tag.rb +257 -0
  246. data/lib/git/remote.rb +120 -9
  247. data/lib/git/repository/branching.rb +743 -0
  248. data/lib/git/repository/committing.rb +187 -0
  249. data/lib/git/repository/configuring.rb +351 -0
  250. data/lib/git/repository/context_helpers.rb +264 -0
  251. data/lib/git/repository/diffing.rb +775 -0
  252. data/lib/git/repository/factories.rb +682 -0
  253. data/lib/git/repository/inspecting.rb +252 -0
  254. data/lib/git/repository/logging.rb +247 -0
  255. data/lib/git/repository/maintenance.rb +65 -0
  256. data/lib/git/repository/merging.rb +357 -0
  257. data/lib/git/repository/object_operations.rb +1199 -0
  258. data/lib/git/repository/path_resolver.rb +207 -0
  259. data/lib/git/repository/remote_operations.rb +898 -0
  260. data/lib/git/repository/shared_private.rb +51 -0
  261. data/lib/git/repository/staging.rb +554 -0
  262. data/lib/git/repository/stashing.rb +143 -0
  263. data/lib/git/repository/status_operations.rb +201 -0
  264. data/lib/git/repository/worktree_operations.rb +159 -0
  265. data/lib/git/repository.rb +203 -1
  266. data/lib/git/stash.rb +78 -4
  267. data/lib/git/stash_info.rb +104 -0
  268. data/lib/git/stashes.rb +125 -13
  269. data/lib/git/status.rb +215 -18
  270. data/lib/git/tag_delete_failure.rb +31 -0
  271. data/lib/git/tag_delete_result.rb +63 -0
  272. data/lib/git/tag_info.rb +105 -0
  273. data/lib/git/version.rb +109 -2
  274. data/lib/git/version_constraint.rb +81 -0
  275. data/lib/git/worktree.rb +108 -6
  276. data/lib/git/worktrees.rb +94 -7
  277. data/lib/git.rb +260 -61
  278. data/redesign/1_architecture_existing.md +54 -18
  279. data/redesign/2_architecture_redesign.md +365 -46
  280. data/redesign/3_architecture_implementation.md +1497 -63
  281. data/redesign/Phase 4 - Step A.md +360 -0
  282. data/redesign/beta_release.md +107 -0
  283. data/redesign/c1c2_audit.md +566 -0
  284. data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
  285. data/redesign/config_design.rb +501 -0
  286. data/tasks/gem_tasks.rake +4 -0
  287. data/tasks/npm_tasks.rake +7 -0
  288. data/tasks/rspec.rake +48 -0
  289. data/tasks/test.rake +13 -1
  290. data/tasks/yard.rake +34 -7
  291. metadata +365 -22
  292. data/lib/git/base.rb +0 -1128
  293. data/lib/git/index.rb +0 -6
  294. data/lib/git/lib.rb +0 -2277
  295. data/lib/git/path.rb +0 -38
  296. data/lib/git/working_directory.rb +0 -6
  297. /data/{release-please-config.json → .release-please-config.json} +0 -0
data/lib/git.rb CHANGED
@@ -3,36 +3,64 @@
3
3
  require 'active_support'
4
4
  require 'active_support/deprecation'
5
5
 
6
+ require 'git/deprecation'
7
+ require 'git/version'
8
+
6
9
  module Git
7
- Deprecation = ActiveSupport::Deprecation.new('5.0.0', 'Git')
10
+ # Minimum git version required by this gem
11
+ #
12
+ # Commands and features may require newer versions, but this is the absolute
13
+ # minimum supported version for the gem as a whole.
14
+ #
15
+ # @return [Git::Version]
16
+ #
17
+ # @api public
18
+ #
19
+ MINIMUM_GIT_VERSION = Version.parse('2.28.0')
8
20
  end
9
21
 
10
22
  require 'git/author'
11
- require 'git/base'
12
23
  require 'git/branch'
24
+ require 'git/branch_info'
13
25
  require 'git/branches'
14
26
  require 'git/command_line_result'
15
27
  require 'git/command_line'
28
+ require 'process_executer'
16
29
  require 'git/config'
30
+ require 'git/config_entry_info'
31
+ require 'git/parsers/config_entry'
32
+ require 'git/configuring'
17
33
  require 'git/diff'
34
+ require 'git/diff_file_numstat_info'
35
+ require 'git/diff_file_patch_info'
36
+ require 'git/diff_file_raw_info'
37
+ require 'git/diff_info'
38
+ require 'git/parsers/diff'
39
+ require 'git/diff_result'
40
+ require 'git/dirstat_info'
18
41
  require 'git/encoding_utils'
19
42
  require 'git/errors'
20
43
  require 'git/escaped_path'
44
+ require 'git/execution_context'
45
+ require 'git/execution_context/global'
46
+ require 'git/file_ref'
21
47
  require 'git/fsck_object'
22
48
  require 'git/fsck_result'
23
- require 'git/index'
24
- require 'git/lib'
49
+ require 'git/parsers/ls_remote'
50
+ require 'git/version_constraint'
51
+ require 'git/commands'
25
52
  require 'git/log'
26
53
  require 'git/object'
27
- require 'git/path'
28
54
  require 'git/remote'
29
55
  require 'git/repository'
30
56
  require 'git/status'
31
57
  require 'git/stash'
58
+ require 'git/stash_info'
32
59
  require 'git/stashes'
60
+ require 'git/tag_delete_failure'
61
+ require 'git/tag_delete_result'
62
+ require 'git/tag_info'
33
63
  require 'git/url'
34
- require 'git/version'
35
- require 'git/working_directory'
36
64
  require 'git/worktree'
37
65
  require 'git/worktrees'
38
66
 
@@ -43,35 +71,56 @@ require 'git/worktrees'
43
71
  #
44
72
  # @author Scott Chacon (mailto:schacon@gmail.com)
45
73
  #
46
- module Git # rubocop:disable Style/OneClassPerFile
47
- # g.config('user.name', 'Scott Chacon') # sets value
48
- # g.config('user.email', 'email@email.com') # sets value
49
- # g.config('user.name') # returns 'Scott Chacon'
50
- # g.config # returns whole config hash
74
+ module Git
75
+ extend Git::Configuring
76
+
77
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
78
+ # Use `Git.open(Dir.pwd).config(...)` instead.
51
79
  def config(name = nil, value = nil)
52
- lib = Git::Lib.new
53
- if name && value
54
- # set value
55
- lib.config_set(name, value)
56
- elsif name
57
- # return value
58
- lib.config_get(name)
59
- else
60
- # return hash
61
- lib.config_list
62
- end
80
+ Git::Deprecation.warn(
81
+ 'Git#config is deprecated and will be removed in v6.0.0. ' \
82
+ 'Use Git.open(Dir.pwd).config(...) instead.'
83
+ )
84
+ Git.__send__(:run_config_utility, name, value, global: false)
63
85
  end
64
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
93
+ #
94
+ # @yieldparam config [Git::Config] the singleton config object
95
+ #
96
+ # @yieldreturn [void]
97
+ #
98
+ # @return [void]
99
+ #
65
100
  def self.configure
66
- yield Base.config
101
+ yield Git::Config.instance
102
+ nil
67
103
  end
68
104
 
105
+ # Returns the process-wide {Git::Config} singleton
106
+ #
107
+ # @example Read the configured binary path
108
+ # Git.config.binary_path #=> "git"
109
+ #
110
+ # @return [Git::Config] the singleton config object
111
+ #
69
112
  def self.config
70
- Base.config
113
+ Git::Config.instance
71
114
  end
72
115
 
116
+ # @deprecated Mixing in the `Git` module is deprecated and will be removed in v6.0.0.
117
+ # Use `Git.global_config(...)` instead.
73
118
  def global_config(name = nil, value = nil)
74
- self.class.global_config(name, value)
119
+ Git::Deprecation.warn(
120
+ 'Git#global_config is deprecated and will be removed in v6.0.0. ' \
121
+ 'Use Git.global_config(...) instead.'
122
+ )
123
+ Git.global_config(name, value)
75
124
  end
76
125
 
77
126
  # Open a bare repository
@@ -105,11 +154,11 @@ module Git # rubocop:disable Style/OneClassPerFile
105
154
  # are logged at the `:info` level. Additional logging is done at the `:debug`
106
155
  # level.
107
156
  #
108
- # @return [Git::Base] an object that can execute git commands in the context
157
+ # @return [Git::Repository] an object that can execute git commands in the context
109
158
  # of the bare repository.
110
159
  #
111
160
  def self.bare(git_dir, options = {})
112
- Base.bare(git_dir, options)
161
+ Git::Repository.bare(git_dir, options)
113
162
  end
114
163
 
115
164
  # Clone a repository into an empty or newly created directory
@@ -123,9 +172,9 @@ module Git # rubocop:disable Style/OneClassPerFile
123
172
  #
124
173
  # @param directory [Pathname, nil] The directory to clone into
125
174
  #
126
- # If `directory` is a relative directory it is relative to the `path` option if
127
- # given. If `path` is not given, `directory` is relative to the current working
128
- # directory.
175
+ # If `directory` is a relative path it is relative to the `:chdir` option if
176
+ # given. If `:chdir` is not given, `directory` is relative to the current
177
+ # working directory.
129
178
  #
130
179
  # If `nil`, `directory` will be set to the basename of the last component of
131
180
  # the path from the `repository_url`. For example, for the URL:
@@ -176,9 +225,14 @@ module Git # rubocop:disable Style/OneClassPerFile
176
225
  # @option options [String] :origin Use the value instead `origin` to track
177
226
  # the upstream repository.
178
227
  #
179
- # @option options [Pathname] :path The directory to clone into. May be used
180
- # as an alternative to the `directory` parameter. If specified, the
181
- # `path` option is used instead of the `directory` parameter.
228
+ # @option options [Pathname] :chdir Run `git clone` from within this directory.
229
+ #
230
+ # The `directory` parameter (or the repository basename when `directory` is nil)
231
+ # is resolved relative to `:chdir`, just as if you had `cd`'d into it before
232
+ # running `git clone`. The returned path is the join of `:chdir` and the
233
+ # cloned directory path.
234
+ #
235
+ # @option options [Pathname] :path Deprecated — use `:chdir` instead.
182
236
  #
183
237
  # @option options [Boolean] :recursive After the clone is created, initialize
184
238
  # all submodules within, using their default settings.
@@ -191,8 +245,10 @@ module Git # rubocop:disable Style/OneClassPerFile
191
245
  #
192
246
  # @example Clone into a different directory `my-ruby-git`
193
247
  # git = Git.clone('https://github.com/ruby-git/ruby-git.git', 'my-ruby-git')
194
- # # or:
195
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', path: 'my-ruby-git')
248
+ #
249
+ # @example Clone into a specific parent directory
250
+ # git = Git.clone('https://github.com/ruby-git/ruby-git.git', chdir: '/path/to/projects')
251
+ # # clones into /path/to/projects/ruby-git
196
252
  #
197
253
  # @example Create a bare repository in the directory `ruby-git.git`
198
254
  # git = Git.clone('https://github.com/ruby-git/ruby-git.git', bare: true)
@@ -216,13 +272,11 @@ module Git # rubocop:disable Style/OneClassPerFile
216
272
  # git_ssh: 'ssh -i /path/to/private_key'
217
273
  # )
218
274
  #
219
- # @return [Git::Base] an object that can execute git commands in the context
275
+ # @return [Git::Repository] an object that can execute git commands in the context
220
276
  # of the cloned local working copy or cloned repository.
221
277
  #
222
278
  def self.clone(repository_url, directory = nil, options = {})
223
- clone_to_options = options.slice(:bare, :mirror)
224
- directory ||= Git::URL.clone_to(repository_url, **clone_to_options)
225
- Base.clone(repository_url, directory, options)
279
+ Git::Repository.clone(repository_url, directory, options)
226
280
  end
227
281
 
228
282
  # Returns the name of the default branch of the given repository
@@ -263,7 +317,9 @@ module Git # rubocop:disable Style/OneClassPerFile
263
317
  # @return [String] the name of the default branch
264
318
  #
265
319
  def self.default_branch(repository, options = {})
266
- 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)
267
323
  end
268
324
 
269
325
  # Export the current HEAD (or a branch, if <tt>options[:branch]</tt>
@@ -287,17 +343,7 @@ module Git # rubocop:disable Style/OneClassPerFile
287
343
  # g.config('user.name') # returns 'Scott Chacon'
288
344
  # g.config # returns whole config hash
289
345
  def self.global_config(name = nil, value = nil)
290
- lib = Git::Lib.new(nil, nil)
291
- if name && value
292
- # set value
293
- lib.global_config_set(name, value)
294
- elsif name
295
- # return value
296
- lib.global_config_get(name)
297
- else
298
- # return hash
299
- lib.global_config_list
300
- end
346
+ run_config_utility(name, value, global: true)
301
347
  end
302
348
 
303
349
  # Create an empty Git repository or reinitialize an existing Git repository
@@ -329,17 +375,23 @@ module Git # rubocop:disable Style/OneClassPerFile
329
375
  # and converted to an absolute path using
330
376
  # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
331
377
  #
378
+ # @option options [Pathname] :separate_git_dir Alias for `:repository`.
379
+ #
332
380
  # @option options [String, nil] :git_ssh An optional custom SSH command
333
381
  #
334
382
  # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
335
383
  # - If nil, disables SSH for this instance.
336
384
  # - If a non-empty string, uses that value for this instance.
337
385
  #
386
+ # @option options [String, :use_global_config] :binary_path path to the git
387
+ # binary; defaults to `Git::Config.instance.binary_path` when not specified.
388
+ # Raises `ArgumentError` if set to `nil`.
389
+ #
338
390
  # @option options [Logger] :log A logger to use for Git operations. Git
339
391
  # commands are logged at the `:info` level. Additional logging is done
340
392
  # at the `:debug` level.
341
393
  #
342
- # @return [Git::Base] an object that can execute git commands in the context
394
+ # @return [Git::Repository] an object that can execute git commands in the context
343
395
  # of the newly initialized repository
344
396
  #
345
397
  # @example Initialize a repository in the current directory
@@ -357,9 +409,24 @@ module Git # rubocop:disable Style/OneClassPerFile
357
409
  # @see https://git-scm.com/docs/git-init git init
358
410
  #
359
411
  def self.init(directory = '.', options = {})
360
- Base.init(directory, options)
412
+ Git::Repository.init(directory, options)
361
413
  end
362
414
 
415
+ # Option keys accepted by {.ls_remote}
416
+ #
417
+ # Parser-incompatible options such as `:get_url` and `:symref` are intentionally
418
+ # excluded because {Git::Parsers::LsRemote.parse_output} cannot handle the
419
+ # non-standard output formats those flags produce.
420
+ #
421
+ # @return [Array<Symbol>]
422
+ #
423
+ # @api private
424
+ #
425
+ LS_REMOTE_ALLOWED_OPTS = %i[
426
+ branches b heads h tags t refs upload_pack quiet q exit_code sort server_option o timeout
427
+ ].freeze
428
+ private_constant :LS_REMOTE_ALLOWED_OPTS
429
+
363
430
  # returns a Hash containing information about the references
364
431
  # of the target repository
365
432
  #
@@ -369,7 +436,15 @@ module Git # rubocop:disable Style/OneClassPerFile
369
436
  # @param [String|NilClass] location the target repository location or nil for '.'
370
437
  # @return [{String=>Hash}] the available references of the target repo.
371
438
  def self.ls_remote(location = nil, options = {})
372
- Git::Lib.new.ls_remote(location, options)
439
+ options = options.dup
440
+ log = options.delete(:log)
441
+ unknown = options.keys - LS_REMOTE_ALLOWED_OPTS
442
+ raise ArgumentError, "Unknown options: #{unknown.join(', ')}" unless unknown.empty?
443
+
444
+ context = Git::ExecutionContext::Global.new(logger: log)
445
+ repository = location || '.'
446
+ output_lines = Git::Commands::LsRemote.new(context).call(repository, **options).stdout.split("\n")
447
+ Git::Parsers::LsRemote.parse_output(output_lines)
373
448
  end
374
449
 
375
450
  # Open a an existing Git working directory
@@ -419,21 +494,145 @@ module Git # rubocop:disable Style/OneClassPerFile
419
494
  # commands are logged at the `:info` level. Additional logging is done
420
495
  # at the `:debug` level.
421
496
  #
422
- # @return [Git::Base] an object that can execute git commands in the context
497
+ # @return [Git::Repository] an object that can execute git commands in the context
423
498
  # of the opened working copy
424
499
  #
425
500
  def self.open(working_dir, options = {})
426
- Base.open(working_dir, options)
501
+ Git::Repository.open(working_dir, options)
502
+ end
503
+
504
+ # Thread-safe cache for git versions, keyed by binary path.
505
+ @git_version_cache_mutex = Mutex.new
506
+ @git_version_cache = {}
507
+
508
+ # @api private
509
+ def self.cached_git_version(binary_path, &block)
510
+ @git_version_cache_mutex.synchronize do
511
+ @git_version_cache[binary_path] ||= block.call
512
+ end
513
+ end
514
+
515
+ # @api private
516
+ def self.clear_git_version_cache
517
+ @git_version_cache_mutex.synchronize do
518
+ @git_version_cache.clear
519
+ end
520
+ end
521
+
522
+ # Return the version of a git binary as a {Git::Version}
523
+ #
524
+ # @example Default binary
525
+ # Git.git_version #=> #<Git::Version 2.42.0>
526
+ #
527
+ # @example Explicit binary path
528
+ # Git.git_version('/opt/homebrew/bin/git') #=> #<Git::Version 2.42.0>
529
+ #
530
+ # @param binary_path [String, nil] path to the git binary; defaults to
531
+ # `Git::Config.instance.binary_path`
532
+ #
533
+ # @return [Git::Version] the parsed git version
534
+ #
535
+ # @raise [Git::UnexpectedResultError] if the version output cannot be parsed
536
+ #
537
+ # @raise [Git::FailedError] if the git binary exits with a non-zero status
538
+ #
539
+ # @raise [Git::Error] if the binary is not found or fails to launch
540
+ #
541
+ def self.git_version(binary_path = nil)
542
+ path = binary_path || Git::Config.instance.binary_path
543
+ cached_git_version(path) { run_git_version(path) }
544
+ end
545
+
546
+ # @api private
547
+ def self.run_git_version(path)
548
+ output = Git::Commands::Version.new(Git::ExecutionContext::Global.new(binary_path: path)).call.stdout
549
+ Git::Version.parse(output)
550
+ end
551
+ private_class_method :run_git_version
552
+
553
+ # @api private
554
+ def self.run_config_utility(name, value, global:)
555
+ context = Git::ExecutionContext::Global.new
556
+ options = global ? { global: true } : {}
557
+
558
+ return Git::Commands::ConfigOptionSyntax::Set.new(context).call(name, value, **options) if !name.nil? && !value.nil?
559
+ return run_config_get(context, name, options) if name
560
+
561
+ output = Git::Commands::ConfigOptionSyntax::List.new(context).call(**options).stdout
562
+ parse_config_list(output.split("\n"))
563
+ end
564
+ private_class_method :run_config_utility
565
+
566
+ def self.run_config_get(context, name, options)
567
+ result = Git::Commands::ConfigOptionSyntax::Get.new(context).call(name, **options)
568
+ raise Git::FailedError, result if result.status.exitstatus != 0
569
+
570
+ result.stdout
571
+ end
572
+ private_class_method :run_config_get
573
+
574
+ # @api private
575
+ def self.parse_config_list(lines)
576
+ lines.each_with_object({}) do |line, hsh|
577
+ key, value = line.split('=', 2)
578
+ hsh[key] = value || ''
579
+ end
427
580
  end
581
+ private_class_method :parse_config_list
582
+
583
+ # @api private
584
+ def self.execution_context
585
+ Git::ExecutionContext::Global.new
586
+ end
587
+ private_class_method :execution_context
588
+
589
+ # Scopes that require an active repository and cannot be used at the Git module level
590
+ #
591
+ # @api private
592
+ #
593
+ REPOSITORY_SPECIFIC_SCOPES = %i[local worktree blob].freeze
594
+ private_constant :REPOSITORY_SPECIFIC_SCOPES
595
+
596
+ # Raises +ArgumentError+ when a repository-specific scope is requested.
597
+ #
598
+ # The +:local+, +:worktree+, and +:blob+ scopes require an active git
599
+ # repository and are therefore not valid at the Git module level.
600
+ #
601
+ # @api private
602
+ #
603
+ def self.assert_valid_scope!(**opts)
604
+ invalid = REPOSITORY_SPECIFIC_SCOPES.select { |s| opts[s] }
605
+ return if invalid.empty?
606
+
607
+ raise ArgumentError, "#{invalid.join(', ')} scope requires a repository"
608
+ end
609
+ private_class_method :assert_valid_scope!
428
610
 
429
611
  # Return the version of the git binary
430
612
  #
431
- # @example
432
- # Git.binary_version # => [2, 46, 0]
613
+ # @example Basic usage
614
+ # Git.binary_version # => [2, 46, 0]
615
+ #
616
+ # @param binary_path [String, nil] path to the git binary; defaults to
617
+ # `Git::Config.instance.binary_path`
433
618
  #
434
619
  # @return [Array<Integer>] the version of the git binary
435
620
  #
436
- def self.binary_version(binary_path = Git::Base.config.binary_path)
437
- Base.binary_version(binary_path)
621
+ # @deprecated Use {Git.git_version} instead, which returns a
622
+ # {Git::Version} (not an Array)
623
+ #
624
+ # For the legacy array shape, call: `Git.git_version.to_a`.
625
+ # The optional binary_path argument is preserved:
626
+ # `Git.git_version(binary_path)`.
627
+ #
628
+ def self.binary_version(binary_path = nil)
629
+ binary_path ||= Git::Config.instance.binary_path
630
+ Git::Deprecation.warn(
631
+ 'Git.binary_version is deprecated and will be removed in 6.0. ' \
632
+ 'Use Git.git_version instead, which returns a Git::Version ' \
633
+ '(not an Array). For the legacy array shape, call: Git.git_version.to_a. ' \
634
+ 'The optional binary_path argument is preserved: Git.git_version(binary_path).'
635
+ )
636
+ git_version(binary_path).to_a
438
637
  end
439
638
  end
@@ -1,6 +1,9 @@
1
1
  # Analysis of the Current Git Gem Architecture and Its Challenges
2
2
 
3
- This document provides an in-depth look at the current architecture of the `git` gem, outlining its primary components and the design challenges that have emerged over time. Understanding these challenges is the key motivation for the proposed architectural redesign.
3
+ This document provides an in-depth look at the current architecture of the `git` gem,
4
+ outlining its primary components and the design challenges that have emerged over
5
+ time. Understanding these challenges is the key motivation for the proposed
6
+ architectural redesign.
4
7
 
5
8
  - [1. Overview of the Current Architecture](#1-overview-of-the-current-architecture)
6
9
  - [2. Key Architectural Challenges](#2-key-architectural-challenges)
@@ -11,27 +14,43 @@ This document provides an in-depth look at the current architecture of the `git`
11
14
 
12
15
  ## 1. Overview of the Current Architecture
13
16
 
14
- The gem's current design is centered around three main classes: `Git`, `Git::Base`, and `Git::Lib`.
17
+ The gem's current design is centered around three main classes: `Git`, `Git::Base`,
18
+ and `Git::Lib`.
15
19
 
16
- - **`Git` (Top-Level Module)**: This module serves as the primary public entry point for creating repository objects. It contains class-level factory methods like `Git.open`, `Git.clone`, and `Git.init`. It also provides an interface for accessing global git configuration settings.
20
+ - **`Git` (Top-Level Module)**: This module serves as the primary public entry point
21
+ for creating repository objects. It contains class-level factory methods like
22
+ `Git.open`, `Git.clone`, and `Git.init`. It also provides an interface for
23
+ accessing global git configuration settings.
17
24
 
18
- **`Git::Base`**: This is the main object that users interact with after creating or opening a repository. It holds the high-level public API for most git operations (e.g., `g.commit`, `g.add`, `g.status`). It is responsible for managing the repository's state, such as the paths to the working directory and the `.git` directory.
25
+ - **`Git::Base`**: This is the main object that users interact with after creating or
26
+ opening a repository. It holds the high-level public API for most git operations
27
+ (e.g., `g.commit`, `g.add`, `g.status`). It is responsible for managing the
28
+ repository's state, such as the paths to the working directory and the `.git`
29
+ directory.
19
30
 
20
- **`Git::Lib`**: This class is intended to be the low-level wrapper around the `git` command-line tool. It contains the methods that build the specific command-line arguments and execute the `git` binary. In practice, it also contains a significant amount of logic for parsing the output of these commands.
31
+ - **`Git::Lib`**: This class is intended to be the low-level wrapper around the `git`
32
+ command-line tool. It contains the methods that build the specific command-line
33
+ arguments and execute the `git` binary. In practice, it also contains a significant
34
+ amount of logic for parsing the output of these commands.
21
35
 
22
36
  ## 2. Key Architectural Challenges
23
37
 
24
- While this structure has been functional, several significant design challenges make the codebase difficult to maintain, test, and evolve.
38
+ While this structure has been functional, several significant design challenges make
39
+ the codebase difficult to maintain, test, and evolve.
25
40
 
26
41
  ### A. Unclear Separation of Concerns
27
42
 
28
- The responsibilities between Git::Base and Git::Lib are "muddy" and overlap significantly.
43
+ The responsibilities between Git::Base and Git::Lib are "muddy" and overlap
44
+ significantly.
29
45
 
30
46
  - `Git::Base` sometimes contains logic that feels like it should be lower-level.
31
47
 
32
- - `Git::Lib`, which should ideally only be concerned with command execution, is filled with high-level logic for parsing command output into specific Ruby objects (e.g., parsing log output, diff stats, and branch lists).
48
+ - `Git::Lib`, which should ideally only be concerned with command execution, is
49
+ filled with high-level logic for parsing command output into specific Ruby objects
50
+ (e.g., parsing log output, diff stats, and branch lists).
33
51
 
34
- This blending of responsibilities makes it hard to determine where a specific piece of logic should reside, leading to an inconsistent and confusing internal structure.
52
+ This blending of responsibilities makes it hard to determine where a specific piece
53
+ of logic should reside, leading to an inconsistent and confusing internal structure.
35
54
 
36
55
  ### B. Circular Dependency
37
56
 
@@ -39,28 +58,45 @@ This is the most critical architectural flaw in the current design.
39
58
 
40
59
  - A `Git::Base` instance is created.
41
60
 
42
- - The first time a command is run, `Git::Base` lazily initializes a `Git::Lib` instance via its `.lib` accessor method.
61
+ - The first time a command is run, `Git::Base` lazily initializes a `Git::Lib`
62
+ instance via its `.lib` accessor method.
43
63
 
44
- - The `Git::Lib` constructor is passed the `Git::Base` instance (`self`) so that it can read the repository's path configuration back from the object that is creating it.
64
+ - The `Git::Lib` constructor is passed the `Git::Base` instance (`self`) so that it
65
+ can read the repository's path configuration back from the object that is creating
66
+ it.
45
67
 
46
- This creates a tight, circular coupling: `Git::Base` depends on `Git::Lib` to execute commands, but `Git::Lib` depends on `Git::Base` for its own configuration. This pattern makes the classes difficula to instantiate or test in isolation and creates a fragile system where changes in one class can have unexpected side effects in the other.
68
+ This creates a tight, circular coupling: `Git::Base` depends on `Git::Lib` to execute
69
+ commands, but `Git::Lib` depends on `Git::Base` for its own configuration. This
70
+ pattern makes the classes difficult to instantiate or test in isolation and creates a
71
+ fragile system where changes in one class can have unexpected side effects in the
72
+ other.
47
73
 
48
74
  ### C. Undefined Public API Boundary
49
75
 
50
- The gem lacks a formally defined public interface. Because `Git::Base` exposes its internal `Git::Lib` instance via the public `g.lib` accessor, many users have come to rely on `Git::Lib` and its methods as if they were part of the public API.
76
+ The gem lacks a formally defined public interface. Because `Git::Base` exposes its
77
+ internal `Git::Lib` instance via the public `g.lib` accessor, many users have come to
78
+ rely on `Git::Lib` and its methods as if they were part of the public API.
51
79
 
52
80
  This has two negative consequences:
53
81
 
54
- 1. It prevents the gem's maintainers from refactoring or changing the internal implementation of `Git::Lib` without causing breaking changes for users.
82
+ 1. It prevents the gem's maintainers from refactoring or changing the internal
83
+ implementation of `Git::Lib` without causing breaking changes for users.
55
84
 
56
- 2. It exposes complex, internal methods to users, creating a confusing and inconsistent user experience.
85
+ 2. It exposes complex, internal methods to users, creating a confusing and
86
+ inconsistent user experience.
57
87
 
58
88
  ### D. Slow and Brittle Test Suite
59
89
 
60
90
  The current testing strategy, built on `TestUnit`, suffers from two major issues:
61
91
 
62
- - **Over-reliance on Fixtures**: Most tests depend on having a complete, physical git repository on the filesystem to run against. Managing these fixtures is cumbersome.
92
+ - **Over-reliance on Fixtures**: Most tests depend on having a complete, physical git
93
+ repository on the filesystem to run against. Managing these fixtures is cumbersome.
63
94
 
64
- - **Excessive Shelling Out**: Because the logic for command execution and output parsing are tightly coupled, nearly every test must shell out to the actual `git` command-line tool.
95
+ - **Excessive Shelling Out**: Because the logic for command execution and output
96
+ parsing are tightly coupled, nearly every test must shell out to the actual `git`
97
+ command-line tool.
65
98
 
66
- This makes the test suite extremely slow, especially on non-UNIX platforms like Windows where process creation is more expensive. The slow feedback loop discourages frequent testing and makes development more difficult. The brittleness of filesystem-dependent tests also leads to flickering or unreliable test runs.
99
+ This makes the test suite extremely slow, especially on non-UNIX platforms like
100
+ Windows where process creation is more expensive. The slow feedback loop discourages
101
+ frequent testing and makes development more difficult. The brittleness of
102
+ filesystem-dependent tests also leads to flickering or unreliable test runs.