git 4.4.0 → 5.0.0.beta.1

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 (280) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +67 -2705
  3. data/.github/pull_request_template.md +3 -1
  4. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  5. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  6. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  7. data/.github/skills/command-implementation/SKILL.md +229 -0
  8. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  9. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  10. data/.github/skills/dependency-management/SKILL.md +72 -0
  11. data/.github/skills/development-workflow/SKILL.md +506 -0
  12. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  13. data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
  14. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  15. data/.github/skills/facade-implementation/SKILL.md +260 -0
  16. data/.github/skills/facade-test-conventions/SKILL.md +380 -0
  17. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  18. data/.github/skills/make-skill-template/SKILL.md +176 -0
  19. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  20. data/.github/skills/project-context/SKILL.md +313 -0
  21. data/.github/skills/pull-request-review/SKILL.md +168 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  23. data/.github/skills/release-management/SKILL.md +125 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  25. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  26. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  27. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  28. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  29. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  30. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  31. data/.github/skills/test-debugging/SKILL.md +160 -0
  32. data/.github/skills/yard-documentation/SKILL.md +793 -0
  33. data/.github/workflows/continuous_integration.yml +3 -2
  34. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  35. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  36. data/.github/workflows/release.yml +3 -4
  37. data/.gitignore +8 -0
  38. data/.husky/pre-commit +13 -0
  39. data/.release-please-manifest.json +1 -1
  40. data/.rspec +3 -0
  41. data/.rubocop.yml +7 -3
  42. data/.rubocop_todo.yml +23 -5
  43. data/.yardopts +1 -0
  44. data/CHANGELOG.md +0 -53
  45. data/CONTRIBUTING.md +694 -53
  46. data/README.md +17 -17
  47. data/Rakefile +61 -9
  48. data/commitlint.test +4 -0
  49. data/git.gemspec +14 -8
  50. data/lib/git/args_builder.rb +0 -8
  51. data/lib/git/base.rb +488 -412
  52. data/lib/git/branch.rb +117 -59
  53. data/lib/git/branch_delete_failure.rb +31 -0
  54. data/lib/git/branch_delete_result.rb +63 -0
  55. data/lib/git/branch_info.rb +178 -0
  56. data/lib/git/branches.rb +130 -24
  57. data/lib/git/command_line/base.rb +245 -0
  58. data/lib/git/command_line/capturing.rb +249 -0
  59. data/lib/git/command_line/result.rb +96 -0
  60. data/lib/git/command_line/streaming.rb +194 -0
  61. data/lib/git/command_line.rb +43 -322
  62. data/lib/git/command_line_result.rb +4 -88
  63. data/lib/git/commands/add.rb +131 -0
  64. data/lib/git/commands/am/abort.rb +43 -0
  65. data/lib/git/commands/am/apply.rb +252 -0
  66. data/lib/git/commands/am/continue.rb +43 -0
  67. data/lib/git/commands/am/quit.rb +43 -0
  68. data/lib/git/commands/am/retry.rb +47 -0
  69. data/lib/git/commands/am/show_current_patch.rb +64 -0
  70. data/lib/git/commands/am/skip.rb +42 -0
  71. data/lib/git/commands/am.rb +33 -0
  72. data/lib/git/commands/apply.rb +237 -0
  73. data/lib/git/commands/archive/list_formats.rb +46 -0
  74. data/lib/git/commands/archive.rb +140 -0
  75. data/lib/git/commands/arguments.rb +3510 -0
  76. data/lib/git/commands/base.rb +403 -0
  77. data/lib/git/commands/branch/copy.rb +94 -0
  78. data/lib/git/commands/branch/create.rb +173 -0
  79. data/lib/git/commands/branch/delete.rb +80 -0
  80. data/lib/git/commands/branch/list.rb +162 -0
  81. data/lib/git/commands/branch/move.rb +94 -0
  82. data/lib/git/commands/branch/set_upstream.rb +86 -0
  83. data/lib/git/commands/branch/show_current.rb +49 -0
  84. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  85. data/lib/git/commands/branch.rb +34 -0
  86. data/lib/git/commands/cat_file/batch.rb +364 -0
  87. data/lib/git/commands/cat_file/filtered.rb +105 -0
  88. data/lib/git/commands/cat_file/raw.rb +210 -0
  89. data/lib/git/commands/cat_file.rb +49 -0
  90. data/lib/git/commands/checkout/branch.rb +151 -0
  91. data/lib/git/commands/checkout/files.rb +115 -0
  92. data/lib/git/commands/checkout.rb +38 -0
  93. data/lib/git/commands/checkout_index.rb +105 -0
  94. data/lib/git/commands/clean.rb +100 -0
  95. data/lib/git/commands/clone.rb +240 -0
  96. data/lib/git/commands/commit.rb +272 -0
  97. data/lib/git/commands/commit_tree.rb +100 -0
  98. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  99. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  100. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  101. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  102. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  103. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  104. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
  105. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  106. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  107. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  108. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  109. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  110. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  111. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  112. data/lib/git/commands/config_option_syntax.rb +56 -0
  113. data/lib/git/commands/describe.rb +155 -0
  114. data/lib/git/commands/diff.rb +656 -0
  115. data/lib/git/commands/diff_files.rb +518 -0
  116. data/lib/git/commands/diff_index.rb +496 -0
  117. data/lib/git/commands/fetch.rb +352 -0
  118. data/lib/git/commands/fsck.rb +136 -0
  119. data/lib/git/commands/gc.rb +132 -0
  120. data/lib/git/commands/grep.rb +338 -0
  121. data/lib/git/commands/init.rb +99 -0
  122. data/lib/git/commands/log.rb +632 -0
  123. data/lib/git/commands/ls_files.rb +191 -0
  124. data/lib/git/commands/ls_remote.rb +155 -0
  125. data/lib/git/commands/ls_tree.rb +131 -0
  126. data/lib/git/commands/maintenance/register.rb +75 -0
  127. data/lib/git/commands/maintenance/run.rb +104 -0
  128. data/lib/git/commands/maintenance/start.rb +66 -0
  129. data/lib/git/commands/maintenance/stop.rb +55 -0
  130. data/lib/git/commands/maintenance/unregister.rb +79 -0
  131. data/lib/git/commands/maintenance.rb +31 -0
  132. data/lib/git/commands/merge/abort.rb +44 -0
  133. data/lib/git/commands/merge/continue.rb +44 -0
  134. data/lib/git/commands/merge/quit.rb +46 -0
  135. data/lib/git/commands/merge/start.rb +245 -0
  136. data/lib/git/commands/merge.rb +28 -0
  137. data/lib/git/commands/merge_base.rb +86 -0
  138. data/lib/git/commands/mv.rb +77 -0
  139. data/lib/git/commands/name_rev.rb +114 -0
  140. data/lib/git/commands/pull.rb +377 -0
  141. data/lib/git/commands/push.rb +246 -0
  142. data/lib/git/commands/read_tree.rb +149 -0
  143. data/lib/git/commands/remote/add.rb +91 -0
  144. data/lib/git/commands/remote/get_url.rb +66 -0
  145. data/lib/git/commands/remote/list.rb +54 -0
  146. data/lib/git/commands/remote/prune.rb +61 -0
  147. data/lib/git/commands/remote/remove.rb +52 -0
  148. data/lib/git/commands/remote/rename.rb +69 -0
  149. data/lib/git/commands/remote/set_branches.rb +63 -0
  150. data/lib/git/commands/remote/set_head.rb +82 -0
  151. data/lib/git/commands/remote/set_url.rb +71 -0
  152. data/lib/git/commands/remote/set_url_add.rb +61 -0
  153. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  154. data/lib/git/commands/remote/show.rb +71 -0
  155. data/lib/git/commands/remote/update.rb +72 -0
  156. data/lib/git/commands/remote.rb +42 -0
  157. data/lib/git/commands/repack.rb +277 -0
  158. data/lib/git/commands/reset.rb +147 -0
  159. data/lib/git/commands/rev_parse.rb +297 -0
  160. data/lib/git/commands/revert/abort.rb +45 -0
  161. data/lib/git/commands/revert/continue.rb +57 -0
  162. data/lib/git/commands/revert/quit.rb +47 -0
  163. data/lib/git/commands/revert/skip.rb +44 -0
  164. data/lib/git/commands/revert/start.rb +153 -0
  165. data/lib/git/commands/revert.rb +29 -0
  166. data/lib/git/commands/rm.rb +114 -0
  167. data/lib/git/commands/show.rb +632 -0
  168. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  169. data/lib/git/commands/show_ref/exists.rb +78 -0
  170. data/lib/git/commands/show_ref/list.rb +145 -0
  171. data/lib/git/commands/show_ref/verify.rb +120 -0
  172. data/lib/git/commands/show_ref.rb +42 -0
  173. data/lib/git/commands/stash/apply.rb +75 -0
  174. data/lib/git/commands/stash/branch.rb +65 -0
  175. data/lib/git/commands/stash/clear.rb +41 -0
  176. data/lib/git/commands/stash/create.rb +58 -0
  177. data/lib/git/commands/stash/drop.rb +67 -0
  178. data/lib/git/commands/stash/list.rb +39 -0
  179. data/lib/git/commands/stash/pop.rb +78 -0
  180. data/lib/git/commands/stash/push.rb +103 -0
  181. data/lib/git/commands/stash/show.rb +149 -0
  182. data/lib/git/commands/stash/store.rb +63 -0
  183. data/lib/git/commands/stash.rb +38 -0
  184. data/lib/git/commands/status.rb +169 -0
  185. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  186. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  187. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  188. data/lib/git/commands/symbolic_ref.rb +38 -0
  189. data/lib/git/commands/tag/create.rb +139 -0
  190. data/lib/git/commands/tag/delete.rb +55 -0
  191. data/lib/git/commands/tag/list.rb +143 -0
  192. data/lib/git/commands/tag/verify.rb +71 -0
  193. data/lib/git/commands/tag.rb +26 -0
  194. data/lib/git/commands/update_ref/batch.rb +140 -0
  195. data/lib/git/commands/update_ref/delete.rb +92 -0
  196. data/lib/git/commands/update_ref/update.rb +106 -0
  197. data/lib/git/commands/update_ref.rb +42 -0
  198. data/lib/git/commands/version.rb +52 -0
  199. data/lib/git/commands/worktree/add.rb +140 -0
  200. data/lib/git/commands/worktree/list.rb +64 -0
  201. data/lib/git/commands/worktree/lock.rb +58 -0
  202. data/lib/git/commands/worktree/management_base.rb +51 -0
  203. data/lib/git/commands/worktree/move.rb +66 -0
  204. data/lib/git/commands/worktree/prune.rb +67 -0
  205. data/lib/git/commands/worktree/remove.rb +63 -0
  206. data/lib/git/commands/worktree/repair.rb +76 -0
  207. data/lib/git/commands/worktree/unlock.rb +47 -0
  208. data/lib/git/commands/worktree.rb +43 -0
  209. data/lib/git/commands/write_tree.rb +68 -0
  210. data/lib/git/commands.rb +89 -0
  211. data/lib/git/detached_head_info.rb +54 -0
  212. data/lib/git/diff.rb +297 -7
  213. data/lib/git/diff_file_numstat_info.rb +29 -0
  214. data/lib/git/diff_file_patch_info.rb +134 -0
  215. data/lib/git/diff_file_raw_info.rb +127 -0
  216. data/lib/git/diff_info.rb +169 -0
  217. data/lib/git/diff_path_status.rb +78 -19
  218. data/lib/git/diff_result.rb +32 -0
  219. data/lib/git/diff_stats.rb +59 -14
  220. data/lib/git/dirstat_info.rb +86 -0
  221. data/lib/git/errors.rb +65 -2
  222. data/lib/git/execution_context/global.rb +56 -0
  223. data/lib/git/execution_context/repository.rb +147 -0
  224. data/lib/git/execution_context.rb +482 -0
  225. data/lib/git/file_ref.rb +74 -0
  226. data/lib/git/fsck_object.rb +9 -9
  227. data/lib/git/fsck_result.rb +1 -1
  228. data/lib/git/lib.rb +1606 -1028
  229. data/lib/git/log.rb +15 -2
  230. data/lib/git/object.rb +92 -22
  231. data/lib/git/parsers/branch.rb +224 -0
  232. data/lib/git/parsers/cat_file.rb +111 -0
  233. data/lib/git/parsers/diff.rb +585 -0
  234. data/lib/git/parsers/fsck.rb +133 -0
  235. data/lib/git/parsers/grep.rb +42 -0
  236. data/lib/git/parsers/ls_tree.rb +58 -0
  237. data/lib/git/parsers/stash.rb +208 -0
  238. data/lib/git/parsers/tag.rb +257 -0
  239. data/lib/git/remote.rb +133 -9
  240. data/lib/git/repository/branching.rb +572 -0
  241. data/lib/git/repository/committing.rb +191 -0
  242. data/lib/git/repository/configuring.rb +156 -0
  243. data/lib/git/repository/diffing.rb +775 -0
  244. data/lib/git/repository/inspecting.rb +153 -0
  245. data/lib/git/repository/logging.rb +247 -0
  246. data/lib/git/repository/merging.rb +295 -0
  247. data/lib/git/repository/object_operations.rb +1101 -0
  248. data/lib/git/repository/path_resolver.rb +207 -0
  249. data/lib/git/repository/remote_operations.rb +753 -0
  250. data/lib/git/repository/shared_private.rb +51 -0
  251. data/lib/git/repository/staging.rb +390 -0
  252. data/lib/git/repository/stashing.rb +107 -0
  253. data/lib/git/repository/status_operations.rb +180 -0
  254. data/lib/git/repository/worktree_operations.rb +159 -0
  255. data/lib/git/repository.rb +264 -1
  256. data/lib/git/stash.rb +85 -4
  257. data/lib/git/stash_info.rb +104 -0
  258. data/lib/git/stashes.rb +130 -13
  259. data/lib/git/status.rb +226 -18
  260. data/lib/git/tag_delete_failure.rb +31 -0
  261. data/lib/git/tag_delete_result.rb +63 -0
  262. data/lib/git/tag_info.rb +105 -0
  263. data/lib/git/version.rb +109 -2
  264. data/lib/git/version_constraint.rb +81 -0
  265. data/lib/git/worktree.rb +120 -5
  266. data/lib/git/worktrees.rb +107 -7
  267. data/lib/git.rb +117 -56
  268. data/redesign/1_architecture_existing.md +54 -18
  269. data/redesign/2_architecture_redesign.md +365 -46
  270. data/redesign/3_architecture_implementation.md +1451 -54
  271. data/tasks/gem_tasks.rake +4 -0
  272. data/tasks/npm_tasks.rake +7 -0
  273. data/tasks/rspec.rake +48 -0
  274. data/tasks/test.rake +13 -1
  275. data/tasks/yard.rake +34 -7
  276. metadata +348 -19
  277. data/lib/git/index.rb +0 -6
  278. data/lib/git/path.rb +0 -38
  279. data/lib/git/working_directory.rb +0 -6
  280. /data/{release-please-config.json → .release-please-config.json} +0 -0
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Represents a git version constraint with minimum and upper bound versions
5
+ #
6
+ # Used by {Git::Commands::Base.requires_git_version} to declare version requirements
7
+ # and by {Git::VersionError} to report constraint violations.
8
+ #
9
+ # @example Minimum version only
10
+ # constraint = Git::VersionConstraint.new(min: Git::Version.parse('2.30.0'))
11
+ # constraint.too_old?(Git::Version.parse('2.28.0')) #=> true
12
+ # constraint.too_new?(Git::Version.parse('2.28.0')) #=> false
13
+ #
14
+ # @example Upper bound only
15
+ # constraint = Git::VersionConstraint.new(before: Git::Version.parse('2.50.0'))
16
+ # constraint.too_old?(Git::Version.parse('2.51.0')) #=> false
17
+ # constraint.too_new?(Git::Version.parse('2.51.0')) #=> true
18
+ #
19
+ # @example Both bounds
20
+ # constraint = Git::VersionConstraint.new(
21
+ # min: Git::Version.parse('2.30.0'),
22
+ # before: Git::Version.parse('2.50.0')
23
+ # )
24
+ # constraint.satisfied_by?(Git::Version.parse('2.40.0')) #=> true
25
+ #
26
+ # @api public
27
+ #
28
+ VersionConstraint = Data.define(:min, :before) do
29
+ # @param min [Git::Version, nil] minimum version (inclusive)
30
+ # @param before [Git::Version, nil] upper bound version (exclusive)
31
+ def initialize(min: nil, before: nil)
32
+ super
33
+ end
34
+
35
+ # Check if the given version is too old (below the minimum)
36
+ #
37
+ # @param version [Git::Version] the version to check
38
+ #
39
+ # @return [Boolean] true if version is below the minimum, false otherwise
40
+ #
41
+ def too_old?(version)
42
+ return false unless min
43
+
44
+ version < min
45
+ end
46
+
47
+ # Check if the given version is too new (at or past the upper bound)
48
+ #
49
+ # @param version [Git::Version] the version to check
50
+ #
51
+ # @return [Boolean] true if version is at or past the upper bound, false otherwise
52
+ #
53
+ def too_new?(version)
54
+ return false unless before
55
+
56
+ version >= before
57
+ end
58
+
59
+ # Check if the given version satisfies this constraint
60
+ #
61
+ # @param version [Git::Version] the version to check
62
+ #
63
+ # @return [Boolean] true if the version satisfies the constraint
64
+ #
65
+ def satisfied_by?(version)
66
+ !too_old?(version) && !too_new?(version)
67
+ end
68
+
69
+ # Return a human-readable representation of this constraint
70
+ #
71
+ # @return [String] the constraint in git version range form
72
+ #
73
+ def to_s
74
+ return ">= #{min}, < #{before}" if min && before
75
+ return ">= #{min}" if min
76
+ return "< #{before}" if before
77
+
78
+ 'any version'
79
+ end
80
+ end
81
+ end
data/lib/git/worktree.rb CHANGED
@@ -1,12 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'git/path'
3
+ require 'git/base'
4
4
 
5
5
  module Git
6
6
  # A worktree in a Git repository
7
+ #
8
+ # Represents a single linked or main worktree. Constructed by
9
+ # {Git::Repository::WorktreeOperations#worktree} or populated by
10
+ # {Git::Worktrees}.
11
+ #
12
+ # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy form)
13
+ # as the `base` argument. The `is_a?(Git::Base)` guard routes git operations
14
+ # through the facade repository and will be removed when {Git::Base} is
15
+ # deleted in Phase 4.
16
+ #
17
+ # @example Add and remove a linked worktree
18
+ # worktree = repo.worktree('/path/to/new-worktree')
19
+ # worktree.add
20
+ # worktree.remove
21
+ #
22
+ # @api public
23
+ #
7
24
  class Worktree
8
- attr_accessor :full, :dir
25
+ # Full worktree descriptor including the optional commitish
26
+ #
27
+ # @return [String] the filesystem path, space-separated with the commitish
28
+ # when one was given at construction time
29
+ #
30
+ attr_accessor :full
9
31
 
32
+ # Filesystem path of this worktree
33
+ #
34
+ # @return [String] the filesystem path of the worktree directory
35
+ #
36
+ attr_accessor :dir
37
+
38
+ # Creates a new Worktree object
39
+ #
40
+ # @param base [Git::Base, Git::Repository] the repository that owns this
41
+ # worktree
42
+ #
43
+ # @param dir [String] filesystem path of the worktree
44
+ #
45
+ # @param gcommit [String, nil] commitish associated with the worktree;
46
+ # when non-nil it is appended to {#full}
47
+ #
48
+ # @return [void]
49
+ #
10
50
  def initialize(base, dir, gcommit = nil)
11
51
  @full = dir
12
52
  @full += " #{gcommit}" unless gcommit.nil?
@@ -15,25 +55,100 @@ module Git
15
55
  @gcommit = gcommit
16
56
  end
17
57
 
58
+ # Returns the commit (or commitish string) associated with this worktree
59
+ #
60
+ # When a commitish string was supplied at construction time (e.g. by
61
+ # {Git::Worktrees} which passes the raw SHA from `git worktree list`), that
62
+ # string is returned as-is. Otherwise the value is lazily resolved on first
63
+ # call via `worktree_repository.gcommit(@full)` and the result is memoized.
64
+ #
65
+ # @example When resolved lazily (no commitish at construction)
66
+ # worktree = repo.worktree('/path/to/wt')
67
+ # worktree.gcommit # => #<Git::Object::Commit ...>
68
+ #
69
+ # @example When the commitish was given at construction
70
+ # worktree = repo.worktrees['/path/to/wt']
71
+ # worktree.gcommit # => "4bef5ab8c9..." (raw SHA string)
72
+ #
73
+ # @return [Git::Object::Commit, String] a commit object when lazily
74
+ # resolved, or the raw commitish string when pre-set at construction
75
+ #
76
+ # @raise [Git::FailedError] if git must resolve the commit and exits with a
77
+ # non-zero exit status
78
+ #
18
79
  def gcommit
19
- @gcommit ||= @base.gcommit(@full)
80
+ @gcommit ||= worktree_repository.gcommit(@full)
20
81
  @gcommit
21
82
  end
22
83
 
84
+ # Creates this worktree on disk
85
+ #
86
+ # Runs `git worktree add` for {#dir}, optionally at the commitish passed
87
+ # at construction time.
88
+ #
89
+ # @example Add a worktree
90
+ # worktree = repo.worktree('/path/to/new-worktree')
91
+ # worktree.add
92
+ #
93
+ # @return [String] stdout from the git command
94
+ #
95
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
96
+ #
23
97
  def add
24
- @base.lib.worktree_add(@dir, @gcommit)
98
+ worktree_repository.worktree_add(@dir, @gcommit)
25
99
  end
26
100
 
101
+ # Removes this worktree from disk
102
+ #
103
+ # Runs `git worktree remove` for {#dir}.
104
+ #
105
+ # @example Remove a worktree
106
+ # worktree.remove
107
+ #
108
+ # @return [String] stdout from the git command (typically empty)
109
+ #
110
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
111
+ #
27
112
  def remove
28
- @base.lib.worktree_remove(@dir)
113
+ worktree_repository.worktree_remove(@dir)
29
114
  end
30
115
 
116
+ # Returns an array containing the full worktree descriptor
117
+ #
118
+ # @example Get the descriptor array
119
+ # worktree.to_a # => ["/path/to/worktree"]
120
+ #
121
+ # @return [Array<String>] array containing the full worktree descriptor
122
+ #
31
123
  def to_a
32
124
  [@full]
33
125
  end
34
126
 
127
+ # Returns the full worktree descriptor as a string
128
+ #
129
+ # @example Get the descriptor string
130
+ # worktree.to_s # => "/path/to/worktree"
131
+ #
132
+ # @return [String] the full worktree descriptor (path and optional commitish)
133
+ #
35
134
  def to_s
36
135
  @full
37
136
  end
137
+
138
+ private
139
+
140
+ # Resolves the {Git::Repository} for worktree operations
141
+ #
142
+ # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
143
+ # The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
144
+ # in Phase 4.
145
+ #
146
+ # @return [Git::Repository] the repository used for worktree operations
147
+ #
148
+ # @api private
149
+ #
150
+ def worktree_repository
151
+ @base.is_a?(Git::Base) ? @base.facade_repository : @base
152
+ end
38
153
  end
39
154
  end
data/lib/git/worktrees.rb CHANGED
@@ -1,47 +1,147 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'git/base'
4
+
3
5
  module Git
4
- # object that holds all the available worktrees
6
+ # Collection of all Git worktrees in a repository
7
+ #
8
+ # Wraps every linked and main worktree and provides enumeration and
9
+ # path-based lookup.
10
+ #
11
+ # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy
12
+ # form) as the `base` argument. The `is_a?(Git::Base)` guard routes git
13
+ # operations through the facade repository and will be removed when
14
+ # {Git::Base} is deleted in Phase 4.
15
+ #
16
+ # @example Enumerate all worktrees
17
+ # worktrees = repo.worktrees
18
+ # worktrees.each { |wt| puts wt.dir }
19
+ #
20
+ # @api public
21
+ #
5
22
  class Worktrees
6
23
  include Enumerable
7
24
 
25
+ # Creates a new Worktrees collection populated from the given repository
26
+ #
27
+ # @param base [Git::Base, Git::Repository] the repository to enumerate
28
+ # worktrees from
29
+ #
30
+ # @return [void]
31
+ #
32
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
33
+ #
8
34
  def initialize(base)
9
35
  @worktrees = {}
10
36
 
11
37
  @base = base
12
38
 
13
- # Array contains [dir, git_hash]
14
- @base.lib.worktrees_all.each do |w|
39
+ worktree_repository.worktrees_all.each do |w|
15
40
  @worktrees[w[0]] = Git::Worktree.new(@base, w[0], w[1])
16
41
  end
17
42
  end
18
43
 
19
- # array like methods
20
-
44
+ # Returns the number of worktrees in the collection
45
+ #
46
+ # @example Count all worktrees
47
+ # repo.worktrees.size # => 2
48
+ #
49
+ # @return [Integer] the total number of worktrees
50
+ #
21
51
  def size
22
52
  @worktrees.size
23
53
  end
24
54
 
55
+ # Iterates over every worktree in the collection
56
+ #
57
+ # @overload each
58
+ #
59
+ # @example Get an enumerator over all worktrees
60
+ # enum = repo.worktrees.each
61
+ #
62
+ # @return [Enumerator<Git::Worktree>] an enumerator over all worktrees
63
+ #
64
+ # @overload each(&block)
65
+ #
66
+ # @example Print every worktree path
67
+ # repo.worktrees.each { |wt| puts wt.dir }
68
+ #
69
+ # @return [Array<Git::Worktree>] the full list of worktrees
70
+ #
71
+ # @yield [worktree] passes each worktree to the block
72
+ #
73
+ # @yieldparam worktree [Git::Worktree] a worktree in the repository
74
+ #
75
+ # @yieldreturn [void]
76
+ #
25
77
  def each(&)
26
78
  @worktrees.values.each(&)
27
79
  end
28
80
 
81
+ # Returns the worktree with the given path
82
+ #
83
+ # Supports lookup by the filesystem path of the worktree directory or by
84
+ # the full worktree descriptor (path and optional commitish).
85
+ #
86
+ # @example Look up a worktree by path
87
+ # repo.worktrees['/path/to/linked-worktree']
88
+ #
89
+ # @param worktree_name [#to_s] the path (or full descriptor) of the
90
+ # worktree to retrieve
91
+ #
92
+ # @return [Git::Worktree, nil] the matching worktree, or `nil` if not found
93
+ #
29
94
  def [](worktree_name)
30
95
  @worktrees.values.each_with_object(@worktrees) do |worktree, worktrees|
31
96
  worktrees[worktree.full] ||= worktree
32
97
  end[worktree_name.to_s]
33
98
  end
34
99
 
100
+ # Returns a string listing all worktrees, one per line
101
+ #
102
+ # @example Display all worktrees
103
+ # puts repo.worktrees.to_s
104
+ #
105
+ # @return [String] a newline-separated listing of worktree descriptors
106
+ #
35
107
  def to_s
36
- out = ''
108
+ out = +''
37
109
  @worktrees.each_value do |b|
38
110
  out << b.to_s << "\n"
39
111
  end
40
112
  out
41
113
  end
42
114
 
115
+ # Removes stale administrative files for worktrees that no longer exist
116
+ #
117
+ # Runs `git worktree prune` to clean up any lingering worktree metadata
118
+ # for linked worktrees whose directories have been deleted.
119
+ #
120
+ # @example Prune stale worktree metadata
121
+ # repo.worktrees.prune
122
+ #
123
+ # @return [String] stdout from the git command (typically empty)
124
+ #
125
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
126
+ #
43
127
  def prune
44
- @base.lib.worktree_prune
128
+ worktree_repository.worktree_prune
129
+ end
130
+
131
+ private
132
+
133
+ # Resolves the {Git::Repository} for this collection of worktrees
134
+ #
135
+ # Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
136
+ # The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
137
+ # in Phase 4.
138
+ #
139
+ # @return [Git::Repository] the repository used to enumerate worktrees
140
+ #
141
+ # @api private
142
+ #
143
+ def worktree_repository
144
+ @base.is_a?(Git::Base) ? @base.facade_repository : @base
45
145
  end
46
146
  end
47
147
  end
data/lib/git.rb CHANGED
@@ -1,57 +1,63 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support'
3
4
  require 'active_support/deprecation'
4
5
 
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
- #
6
+ require 'git/version'
7
+
9
8
  module Git
10
- # The deprecation instance used to emit deprecation warnings for the Git gem
11
- #
12
- # @api public
13
9
  Deprecation = ActiveSupport::Deprecation.new('5.0.0', 'Git')
14
10
 
15
- if (behavior = ENV.fetch('GIT_DEPRECATION_BEHAVIOR', nil))
16
- behavior = behavior.strip
17
- allowed_behaviors = ActiveSupport::Deprecation::DEFAULT_BEHAVIORS.keys.map(&:to_s)
18
-
19
- unless allowed_behaviors.include?(behavior)
20
- raise ArgumentError,
21
- "Invalid GIT_DEPRECATION_BEHAVIOR=#{behavior.inspect}; " \
22
- "expected one of: #{allowed_behaviors.join(', ')}"
23
- end
24
-
25
- Deprecation.behavior = behavior.to_sym
26
- end
11
+ # Minimum git version required by this gem
12
+ #
13
+ # Commands and features may require newer versions, but this is the absolute
14
+ # minimum supported version for the gem as a whole.
15
+ #
16
+ # @return [Git::Version]
17
+ #
18
+ # @api public
19
+ #
20
+ MINIMUM_GIT_VERSION = Version.parse('2.28.0')
27
21
  end
28
22
 
29
23
  require 'git/author'
30
- require 'git/base'
31
24
  require 'git/branch'
25
+ require 'git/branch_info'
32
26
  require 'git/branches'
33
27
  require 'git/command_line_result'
34
28
  require 'git/command_line'
29
+ require 'git/commands/init'
35
30
  require 'git/config'
36
31
  require 'git/diff'
32
+ require 'git/diff_file_numstat_info'
33
+ require 'git/diff_file_patch_info'
34
+ require 'git/diff_file_raw_info'
35
+ require 'git/diff_info'
36
+ require 'git/parsers/diff'
37
+ require 'git/diff_result'
38
+ require 'git/dirstat_info'
37
39
  require 'git/encoding_utils'
38
40
  require 'git/errors'
39
41
  require 'git/escaped_path'
42
+ require 'git/execution_context'
43
+ require 'git/file_ref'
40
44
  require 'git/fsck_object'
41
45
  require 'git/fsck_result'
42
- require 'git/index'
46
+ require 'git/version_constraint'
43
47
  require 'git/lib'
44
48
  require 'git/log'
45
49
  require 'git/object'
46
- require 'git/path'
47
50
  require 'git/remote'
48
51
  require 'git/repository'
52
+ require 'git/base'
49
53
  require 'git/status'
50
54
  require 'git/stash'
55
+ require 'git/stash_info'
51
56
  require 'git/stashes'
57
+ require 'git/tag_delete_failure'
58
+ require 'git/tag_delete_result'
59
+ require 'git/tag_info'
52
60
  require 'git/url'
53
- require 'git/version'
54
- require 'git/working_directory'
55
61
  require 'git/worktree'
56
62
  require 'git/worktrees'
57
63
 
@@ -62,29 +68,13 @@ require 'git/worktrees'
62
68
  #
63
69
  # @author Scott Chacon (mailto:schacon@gmail.com)
64
70
  #
65
- module Git # rubocop:disable Style/OneClassPerFile
66
- # Internal alias for Git::Lib, used by the gem itself after the public constant
67
- # is deprecated. Code outside the gem should not reference this constant.
68
- # @api private
69
- LibImpl = remove_const(:Lib)
70
-
71
- # @api private
72
- def self.const_missing(name)
73
- return super unless name == :Lib
74
-
75
- Git::Deprecation.warn(
76
- 'Git::Lib is deprecated and will be removed in version 5.x. ' \
77
- 'Use the #lib accessor on the object returned by Git.init, Git.open, or Git.clone instead.'
78
- )
79
- const_set(:Lib, LibImpl)
80
- end
81
-
71
+ module Git
82
72
  # g.config('user.name', 'Scott Chacon') # sets value
83
73
  # g.config('user.email', 'email@email.com') # sets value
84
74
  # g.config('user.name') # returns 'Scott Chacon'
85
75
  # g.config # returns whole config hash
86
76
  def config(name = nil, value = nil)
87
- lib = LibImpl.new
77
+ lib = Git::Lib.new
88
78
  if name && value
89
79
  # set value
90
80
  lib.config_set(name, value)
@@ -158,9 +148,9 @@ module Git # rubocop:disable Style/OneClassPerFile
158
148
  #
159
149
  # @param directory [Pathname, nil] The directory to clone into
160
150
  #
161
- # If `directory` is a relative directory it is relative to the `path` option if
162
- # given. If `path` is not given, `directory` is relative to the current working
163
- # directory.
151
+ # If `directory` is a relative path it is relative to the `:chdir` option if
152
+ # given. If `:chdir` is not given, `directory` is relative to the current
153
+ # working directory.
164
154
  #
165
155
  # If `nil`, `directory` will be set to the basename of the last component of
166
156
  # the path from the `repository_url`. For example, for the URL:
@@ -211,9 +201,14 @@ module Git # rubocop:disable Style/OneClassPerFile
211
201
  # @option options [String] :origin Use the value instead `origin` to track
212
202
  # the upstream repository.
213
203
  #
214
- # @option options [Pathname] :path The directory to clone into. May be used
215
- # as an alternative to the `directory` parameter. If specified, the
216
- # `path` option is used instead of the `directory` parameter.
204
+ # @option options [Pathname] :chdir Run `git clone` from within this directory.
205
+ #
206
+ # The `directory` parameter (or the repository basename when `directory` is nil)
207
+ # is resolved relative to `:chdir`, just as if you had `cd`'d into it before
208
+ # running `git clone`. The returned path is the join of `:chdir` and the
209
+ # cloned directory path.
210
+ #
211
+ # @option options [Pathname] :path Deprecated — use `:chdir` instead.
217
212
  #
218
213
  # @option options [Boolean] :recursive After the clone is created, initialize
219
214
  # all submodules within, using their default settings.
@@ -226,8 +221,10 @@ module Git # rubocop:disable Style/OneClassPerFile
226
221
  #
227
222
  # @example Clone into a different directory `my-ruby-git`
228
223
  # git = Git.clone('https://github.com/ruby-git/ruby-git.git', 'my-ruby-git')
229
- # # or:
230
- # git = Git.clone('https://github.com/ruby-git/ruby-git.git', path: 'my-ruby-git')
224
+ #
225
+ # @example Clone into a specific parent directory
226
+ # git = Git.clone('https://github.com/ruby-git/ruby-git.git', chdir: '/path/to/projects')
227
+ # # clones into /path/to/projects/ruby-git
231
228
  #
232
229
  # @example Create a bare repository in the directory `ruby-git.git`
233
230
  # git = Git.clone('https://github.com/ruby-git/ruby-git.git', bare: true)
@@ -255,8 +252,6 @@ module Git # rubocop:disable Style/OneClassPerFile
255
252
  # of the cloned local working copy or cloned repository.
256
253
  #
257
254
  def self.clone(repository_url, directory = nil, options = {})
258
- clone_to_options = options.slice(:bare, :mirror)
259
- directory ||= Git::URL.clone_to(repository_url, **clone_to_options)
260
255
  Base.clone(repository_url, directory, options)
261
256
  end
262
257
 
@@ -322,7 +317,7 @@ module Git # rubocop:disable Style/OneClassPerFile
322
317
  # g.config('user.name') # returns 'Scott Chacon'
323
318
  # g.config # returns whole config hash
324
319
  def self.global_config(name = nil, value = nil)
325
- lib = LibImpl.new(nil, nil)
320
+ lib = Git::Lib.new(nil, nil)
326
321
  if name && value
327
322
  # set value
328
323
  lib.global_config_set(name, value)
@@ -364,6 +359,8 @@ module Git # rubocop:disable Style/OneClassPerFile
364
359
  # and converted to an absolute path using
365
360
  # [File.expand_path](https://www.rubydoc.info/stdlib/core/File.expand_path).
366
361
  #
362
+ # @option options [Pathname] :separate_git_dir Alias for `:repository`.
363
+ #
367
364
  # @option options [String, nil] :git_ssh An optional custom SSH command
368
365
  #
369
366
  # - If not specified, uses the global config (Git.configure { |c| c.git_ssh = ... }).
@@ -392,7 +389,30 @@ module Git # rubocop:disable Style/OneClassPerFile
392
389
  # @see https://git-scm.com/docs/git-init git init
393
390
  #
394
391
  def self.init(directory = '.', options = {})
395
- Base.init(directory, options)
392
+ require_relative 'git/commands/init'
393
+
394
+ options = options.dup
395
+ options[:repository] ||= options.delete(:separate_git_dir)
396
+ init_opts = options.slice(:bare, :initial_branch)
397
+ init_opts[:separate_git_dir] = options[:repository] if options.key?(:repository)
398
+ Git::Commands::Init.new(Git::Lib.new(nil, options[:log])).call(directory, **init_opts)
399
+
400
+ open_initialized_repository(directory, options)
401
+ end
402
+
403
+ # Open the repository after initialization
404
+ #
405
+ # @param directory [String] the directory containing the repository
406
+ # @param options [Hash] the options hash
407
+ # @return [Git::Base] the opened repository
408
+ # @api private
409
+ #
410
+ private_class_method def self.open_initialized_repository(directory, options)
411
+ if options[:bare]
412
+ Git.bare(options[:repository] || directory, options.slice(:log, :git_ssh).compact)
413
+ else
414
+ Git.open(directory, options.slice(:log, :git_ssh, :index, :repository).compact)
415
+ end
396
416
  end
397
417
 
398
418
  # returns a Hash containing information about the references
@@ -404,7 +424,7 @@ module Git # rubocop:disable Style/OneClassPerFile
404
424
  # @param [String|NilClass] location the target repository location or nil for '.'
405
425
  # @return [{String=>Hash}] the available references of the target repo.
406
426
  def self.ls_remote(location = nil, options = {})
407
- LibImpl.new.ls_remote(location, options)
427
+ Git::Lib.new.ls_remote(location, options)
408
428
  end
409
429
 
410
430
  # Open a an existing Git working directory
@@ -461,6 +481,37 @@ module Git # rubocop:disable Style/OneClassPerFile
461
481
  Base.open(working_dir, options)
462
482
  end
463
483
 
484
+ # Return the version of a git binary as a {Git::Version}
485
+ #
486
+ # @param binary_path [String, nil] path to the git binary; defaults to
487
+ # `Git::Base.config.binary_path`
488
+ #
489
+ # @return [Git::Version] the parsed git version
490
+ #
491
+ # @raise [Git::UnexpectedResultError] if the version output cannot be parsed
492
+ #
493
+ # @raise [Git::FailedError] if the git binary exits with a non-zero status
494
+ #
495
+ # @raise [Git::Error] if the binary is not found or fails to launch
496
+ #
497
+ # @example Default binary
498
+ # Git.git_version #=> #<Git::Version 2.42.0>
499
+ #
500
+ # @example Explicit binary path
501
+ # Git.git_version('/opt/homebrew/bin/git') #=> #<Git::Version 2.42.0>
502
+ #
503
+ def self.git_version(binary_path = nil)
504
+ path = binary_path || Git::Base.config.binary_path
505
+ Git::Lib.cached_git_version(path) { run_git_version(path) }
506
+ end
507
+
508
+ # @api private
509
+ def self.run_git_version(path)
510
+ output = Git::Commands::Version.new(Git::ExecutionContext::Global.new(binary_path: path)).call.stdout
511
+ Git::Version.parse(output)
512
+ end
513
+ private_class_method :run_git_version
514
+
464
515
  # Return the version of the git binary
465
516
  #
466
517
  # @example
@@ -468,7 +519,17 @@ module Git # rubocop:disable Style/OneClassPerFile
468
519
  #
469
520
  # @return [Array<Integer>] the version of the git binary
470
521
  #
522
+ # @deprecated Use {Git.git_version} instead, which returns a {Git::Version} (not an Array).
523
+ # For the legacy array shape, call: `Git.git_version.to_a`.
524
+ # The optional binary_path argument is preserved: `Git.git_version(binary_path)`.
525
+ #
471
526
  def self.binary_version(binary_path = Git::Base.config.binary_path)
472
- Base.binary_version(binary_path)
527
+ Git::Deprecation.warn(
528
+ 'Git.binary_version is deprecated and will be removed in 6.0. ' \
529
+ 'Use Git.git_version instead, which returns a Git::Version ' \
530
+ '(not an Array). For the legacy array shape, call: Git.git_version.to_a. ' \
531
+ 'The optional binary_path argument is preserved: Git.git_version(binary_path).'
532
+ )
533
+ git_version(binary_path).to_a
473
534
  end
474
535
  end