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
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/stash'
4
+ require 'git/parsers/stash'
5
+
6
+ module Git
7
+ class Repository
8
+ # Facade methods for stash operations
9
+ #
10
+ # Included by {Git::Repository}.
11
+ #
12
+ # @api public
13
+ #
14
+ module Stashing
15
+ # Returns all stash entries as an array of index and message pairs
16
+ #
17
+ # Lists all stash entries in the repository ordered from oldest to newest.
18
+ # The index is a sequential number starting from 0 for the oldest stash. The
19
+ # message is the stash description with the leading branch prefix (e.g.
20
+ # `"On main:"` or `"WIP on main:"`) stripped.
21
+ #
22
+ # @note The sequential index returned here is **not** the same as git's
23
+ # `stash@{N}` reference used by {#stash_apply}. In git, `stash@{0}` is the
24
+ # **most recent** stash, while index `0` here is the **oldest**. To apply a
25
+ # specific stash from this list, convert the entry's position to a git
26
+ # reference: `'stash@{%d}' % (total - 1 - index)`, or pass the string
27
+ # reference directly to {#stash_apply}.
28
+ #
29
+ # @example List all stashes (oldest first)
30
+ # repo.stashes_all #=> [[0, "Fix bug"], [1, "Add feature"]]
31
+ #
32
+ # @return [Array<Array(Integer, String)>] array of `[index, message]` pairs
33
+ # where index is the sequential position (0 is oldest) and message is the
34
+ # stash description with the branch prefix stripped
35
+ #
36
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
37
+ #
38
+ # @see https://git-scm.com/docs/git-stash git-stash documentation
39
+ #
40
+ def stashes_all
41
+ result = Git::Commands::Stash::List.new(@execution_context).call
42
+ stashes = Git::Parsers::Stash.parse_list(result.stdout)
43
+ stashes.reverse.each_with_index.map do |info, i|
44
+ message = info.message.sub(/^(?:WIP on|On)\s+[^:]+:\s*/, '')
45
+ [i, message]
46
+ end
47
+ end
48
+
49
+ # Returns stash entries as a formatted string matching `git stash list` output
50
+ #
51
+ # @deprecated Use {#stashes_all} instead.
52
+ #
53
+ # @example List stashes as a formatted string
54
+ # repo.stash_list #=> "stash@{0}: On main: WIP\nstash@{1}: On feature: Fix bug"
55
+ #
56
+ # @return [String] newline-joined `"stash@{n}: <full message>"` entries, or an
57
+ # empty string when there are no stashes; the format matches `git stash list`
58
+ # output
59
+ #
60
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
61
+ #
62
+ # @see #stashes_all
63
+ #
64
+ # @see https://git-scm.com/docs/git-stash git-stash documentation
65
+ #
66
+ def stash_list
67
+ Git::Deprecation.warn(
68
+ 'Git::Repository#stash_list is deprecated and will be removed in a future version. ' \
69
+ 'Use Git::Repository#stashes_all instead.'
70
+ )
71
+ result = Git::Commands::Stash::List.new(@execution_context).call
72
+ stashes = Git::Parsers::Stash.parse_list(result.stdout)
73
+ stashes.map { |info| "#{info.name}: #{info.message}" }.join("\n")
74
+ end
75
+
76
+ # Save the current working directory and index state to a new stash
77
+ #
78
+ # @param message [String] the stash message
79
+ #
80
+ # @return [Boolean] true if changes were stashed, false if there were no
81
+ # local changes to save
82
+ #
83
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
84
+ #
85
+ # @example Save current changes
86
+ # repo.stash_save('WIP: feature work')
87
+ #
88
+ # @see https://git-scm.com/docs/git-stash git-stash documentation
89
+ #
90
+ def stash_save(message) # rubocop:disable Naming/PredicateMethod
91
+ result = Git::Commands::Stash::Push.new(@execution_context).call(message: message)
92
+ !result.stdout.include?('No local changes to save')
93
+ end
94
+
95
+ # Apply a stash to the working directory
96
+ #
97
+ # Applies the changes recorded in a stash entry to the working directory
98
+ # without removing the entry from the stash list. Unlike `git stash pop`,
99
+ # the stash entry is preserved after applying.
100
+ #
101
+ # @example Apply the most recent stash
102
+ # repo.stash_apply #=> "HEAD is now at abc1234 Initial commit"
103
+ #
104
+ # @example Apply a specific stash entry by reference
105
+ # repo.stash_apply('stash@{1}') #=> "HEAD is now at abc1234 Initial commit"
106
+ #
107
+ # @param id [String, Integer, nil] the stash identifier (e.g., `'stash@{0}'`,
108
+ # `0`) or `nil` to apply the most recent stash entry. When an Integer is
109
+ # given it is passed directly to git as `stash@{N}`, where `0` is the
110
+ # **most recent** stash — the opposite order from {#stashes_all}'s
111
+ # sequential indices, where `0` is the **oldest** stash.
112
+ #
113
+ # @return [String] the output from the git stash apply command
114
+ #
115
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
116
+ #
117
+ # @see https://git-scm.com/docs/git-stash git-stash documentation
118
+ #
119
+ def stash_apply(id = nil)
120
+ Git::Commands::Stash::Apply.new(@execution_context).call(id).stdout
121
+ end
122
+
123
+ # Remove all stash entries
124
+ #
125
+ # Removes all entries from the stash list. Use with caution as this
126
+ # operation cannot be undone.
127
+ #
128
+ # @return [String] the output from the git stash clear command
129
+ # (typically empty)
130
+ #
131
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
132
+ #
133
+ # @example Clear all stashes
134
+ # repo.stash_clear #=> ""
135
+ #
136
+ # @see https://git-scm.com/docs/git-stash git-stash documentation
137
+ #
138
+ def stash_clear
139
+ Git::Commands::Stash::Clear.new(@execution_context).call.stdout
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/ls_files'
4
+ require 'git/commands/rev_parse'
5
+ require 'git/escaped_path'
6
+ require 'git/status'
7
+
8
+ module Git
9
+ class Repository
10
+ # Facade methods for repository-status operations
11
+ #
12
+ # Provides methods for querying the state of the repository: checking
13
+ # whether any commits exist, listing untracked working-tree files, and
14
+ # listing files tracked in the index.
15
+ #
16
+ # Included by {Git::Repository}.
17
+ #
18
+ # @api public
19
+ #
20
+ module StatusOperations
21
+ # Returns `true` if the repository has no commits yet
22
+ #
23
+ # Checks whether `HEAD` can be resolved to a commit object. A brand-new
24
+ # repository (or one created with `git checkout --orphan`) where no commit
25
+ # has been made yet will have no commits.
26
+ #
27
+ # @example Check whether a repository is empty
28
+ # repo.no_commits? #=> true # freshly initialized, no commits yet
29
+ # repo.no_commits? #=> false # at least one commit exists
30
+ #
31
+ # @return [Boolean] `true` when the repository has no commits, `false` otherwise
32
+ #
33
+ # @raise [Git::FailedError] if git exits with a non-zero exit status other
34
+ # than when the repository has no commits
35
+ #
36
+ def no_commits?
37
+ Git::Commands::RevParse.new(@execution_context).call('HEAD', verify: true)
38
+ false
39
+ rescue Git::FailedError => e
40
+ raise unless e.result.status.exitstatus == 128 &&
41
+ e.result.stderr == 'fatal: Needed a single revision'
42
+
43
+ true
44
+ end
45
+
46
+ # Returns `true` if the repository has no commits yet
47
+ #
48
+ # @example Check whether a repository is empty
49
+ # repo.empty? #=> true # freshly initialized, no commits yet
50
+ # repo.empty? #=> false # at least one commit exists
51
+ #
52
+ # @return [Boolean] `true` when the repository has no commits, `false` otherwise
53
+ #
54
+ # @raise [Git::FailedError] if git exits with a non-zero exit status other
55
+ # than when the repository has no commits
56
+ #
57
+ # @deprecated Use {#no_commits?} instead
58
+ #
59
+ def empty?
60
+ Git::Deprecation.warn(
61
+ 'Git::Repository#empty? is deprecated and will be removed in a future version. ' \
62
+ 'Use Git::Repository#no_commits? instead.'
63
+ )
64
+ no_commits?
65
+ end
66
+
67
+ # List all files in the working tree that are not tracked by git
68
+ #
69
+ # Runs `git ls-files --others --exclude-standard` from the working tree
70
+ # root and returns an array of repository-relative file paths. Files that
71
+ # match `.gitignore` or other standard exclusion rules are omitted.
72
+ #
73
+ # @example Get untracked files
74
+ # repo.untracked_files #=> ["new_feature.rb", "tmp/debug.log"]
75
+ #
76
+ # @example No untracked files
77
+ # repo.untracked_files #=> []
78
+ #
79
+ # @return [Array<String>] repository-relative paths of untracked,
80
+ # non-ignored files; empty when there are none
81
+ #
82
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
83
+ #
84
+ def untracked_files
85
+ Git::Commands::LsFiles.new(@execution_context).call(
86
+ others: true, exclude_standard: true, chdir: @execution_context.git_work_dir
87
+ ).stdout.split("\n").map { |f| Private.unescape_quoted_path(f) }
88
+ end
89
+
90
+ # Returns a {Git::Status} object describing the working tree and index state
91
+ #
92
+ # Constructs a {Git::Status} for this repository by collecting information from
93
+ # `git ls-files --stage`, `git ls-files --others`, `git diff-files`, and
94
+ # `git diff-index HEAD` (the last only when at least one commit exists). The
95
+ # result identifies which files have been modified, added, deleted, or are
96
+ # untracked.
97
+ #
98
+ # @example Check which files are modified
99
+ # repo.status.changed #=> { "lib/foo.rb" => <Git::Status::StatusFile ...> }
100
+ #
101
+ # @example Check for untracked files
102
+ # repo.status.untracked #=> { "new_file.rb" => <Git::Status::StatusFile ...> }
103
+ #
104
+ # @example Iterate over all status files
105
+ # repo.status.each { |file| puts "#{file.path}: #{file.type}" }
106
+ #
107
+ # @return [Git::Status] the status of the repository
108
+ #
109
+ # @raise [Git::FailedError] if any underlying git command exits with a
110
+ # non-zero exit status
111
+ #
112
+ def status
113
+ Git::Status.new(self)
114
+ end
115
+
116
+ # List all files tracked in the index
117
+ #
118
+ # Runs `git ls-files --stage` under the given `location` and returns a
119
+ # hash keyed by file path with per-file index metadata.
120
+ #
121
+ # @example List all indexed files in the working tree
122
+ # repo.ls_files
123
+ # #=> { "README.md" => { path: "README.md", mode_index: "100644",
124
+ # #=> sha_index: "abc123...", stage: "0" }, ... }
125
+ #
126
+ # @example List indexed files under a specific directory
127
+ # repo.ls_files('lib/')
128
+ # #=> { "lib/git.rb" => { path: "lib/git.rb", ... }, ... }
129
+ #
130
+ # @param location [String, nil] the path to restrict the listing to;
131
+ # defaults to `'.'` (all tracked files) when `nil`
132
+ #
133
+ # @return [Hash{String => Hash}] a hash of index entries keyed by file path
134
+ #
135
+ # Each value is a Hash with the following keys:
136
+ # * `:path` [String] the file path
137
+ # * `:mode_index` [String] the file's index mode (e.g. `"100644"`)
138
+ # * `:sha_index` [String] the file's index SHA
139
+ # * `:stage` [String] the merge stage (`"0"` for normal entries)
140
+ #
141
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
142
+ #
143
+ def ls_files(location = nil)
144
+ location ||= '.'
145
+ {}.tap do |files|
146
+ Git::Commands::LsFiles.new(@execution_context).call(location, stage: true).stdout.split("\n").each do |line|
147
+ info, file = Private.split_status_line(line)
148
+ mode, sha, stage = info.split
149
+ files[file] = { path: file, mode_index: mode, sha_index: sha, stage: stage }
150
+ end
151
+ end
152
+ end
153
+
154
+ # Private helpers local to {Git::Repository::StatusOperations}
155
+ #
156
+ # @api private
157
+ #
158
+ module Private
159
+ module_function
160
+
161
+ # Split a tab-delimited status line from `git ls-files --stage` output
162
+ #
163
+ # The output format is `<mode> <sha> <stage>\t<file>`. Quoted file paths
164
+ # (which git uses when the path contains non-ASCII or special characters)
165
+ # are unescaped before being returned. `line` is assumed to be non-empty
166
+ # because `git ls-files --stage` never emits blank lines.
167
+ #
168
+ # @param line [String] a single line of git ls-files output
169
+ #
170
+ # @return [Array<String>] the tab-delimited parts with the last part
171
+ # unescaped when it was git-quoted
172
+ #
173
+ def split_status_line(line)
174
+ parts = line.split("\t")
175
+ parts[-1] = unescape_quoted_path(parts[-1])
176
+ parts
177
+ end
178
+
179
+ # Unescape a git-quoted path
180
+ #
181
+ # Git wraps paths containing non-ASCII or special characters in
182
+ # double-quotes and octal-escapes each byte. This method strips the
183
+ # surrounding quotes and delegates unescaping to {Git::EscapedPath}.
184
+ #
185
+ # @param path [String] the path as it appears in git output
186
+ #
187
+ # @return [String] the unescaped path
188
+ #
189
+ def unescape_quoted_path(path)
190
+ if path.start_with?('"') && path.end_with?('"')
191
+ Git::EscapedPath.new(path[1..-2]).unescape
192
+ else
193
+ path
194
+ end
195
+ end
196
+ end
197
+
198
+ private_constant :Private
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/worktree'
4
+ require 'git/worktree'
5
+ require 'git/worktrees'
6
+
7
+ module Git
8
+ class Repository
9
+ # Facade methods for worktree operations
10
+ #
11
+ # Included by {Git::Repository}.
12
+ #
13
+ # @api public
14
+ #
15
+ module WorktreeOperations
16
+ # Returns all worktrees as an array of directory and SHA pairs
17
+ #
18
+ # Lists all worktrees attached to the repository, including the main
19
+ # worktree and all linked worktrees.
20
+ #
21
+ # @example List all worktrees
22
+ # repo.worktrees_all
23
+ # #=> [["/path/to/main", "4bef5ab..."], ["/tmp/worktree-1", "b8c6320..."]]
24
+ #
25
+ # @return [Array<Array(String, String)>] array of `[directory, sha]` pairs
26
+ #
27
+ # `directory` is the worktree path reported by git (absolute or relative,
28
+ # depending on repository configuration); `sha` is the full SHA of the
29
+ # checked-out HEAD commit
30
+ #
31
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
32
+ #
33
+ # @see https://git-scm.com/docs/git-worktree git-worktree documentation
34
+ #
35
+ def worktrees_all
36
+ worktree_entries = []
37
+ current_directory = ''
38
+ command_output = Git::Commands::Worktree::List.new(@execution_context).call(porcelain: true).stdout
39
+
40
+ command_output.each_line(chomp: true) do |line|
41
+ key, value = line.split(' ', 2)
42
+ current_directory = value if key == 'worktree'
43
+ worktree_entries << [current_directory, value] if key == 'HEAD'
44
+ end
45
+
46
+ worktree_entries
47
+ end
48
+
49
+ # Create a new linked worktree at the given directory
50
+ #
51
+ # @example Create a worktree at a path (auto-creates a branch)
52
+ # repo.worktree_add('/tmp/feature')
53
+ #
54
+ # @example Create a worktree and check out an existing commitish
55
+ # repo.worktree_add('/tmp/hotfix', 'main')
56
+ #
57
+ # @param dir [String] filesystem path for the new worktree
58
+ #
59
+ # @param commitish [String, nil] branch, tag, or commit to check out
60
+ #
61
+ # When `nil`, git creates a new branch named after the final path component
62
+ #
63
+ # @return [String] the output from the git worktree add command
64
+ #
65
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
66
+ #
67
+ # @see https://git-scm.com/docs/git-worktree git-worktree documentation
68
+ #
69
+ def worktree_add(dir, commitish = nil)
70
+ args = [dir]
71
+ args << commitish unless commitish.nil?
72
+
73
+ Git::Commands::Worktree::Add.new(@execution_context).call(*args).stdout
74
+ end
75
+
76
+ # Remove a linked worktree
77
+ #
78
+ # @example Remove a worktree
79
+ # repo.worktree_remove('/tmp/feature')
80
+ #
81
+ # @param dir [String] filesystem path of the worktree to remove
82
+ #
83
+ # @return [String] the output from the git worktree remove command
84
+ # (typically empty)
85
+ #
86
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
87
+ #
88
+ # @see https://git-scm.com/docs/git-worktree git-worktree documentation
89
+ #
90
+ def worktree_remove(dir)
91
+ Git::Commands::Worktree::Remove.new(@execution_context).call(dir).stdout
92
+ end
93
+
94
+ # Prune stale worktree administrative files
95
+ #
96
+ # Removes stale administrative files from `$GIT_DIR/worktrees`. A
97
+ # worktree becomes stale when its directory no longer exists on disk.
98
+ #
99
+ # @example Prune stale worktrees
100
+ # repo.worktree_prune
101
+ #
102
+ # @return [String] the output from the git worktree prune command
103
+ # (typically empty)
104
+ #
105
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
106
+ #
107
+ # @see https://git-scm.com/docs/git-worktree git-worktree documentation
108
+ #
109
+ def worktree_prune
110
+ Git::Commands::Worktree::Prune.new(@execution_context).call.stdout
111
+ end
112
+
113
+ # Return a {Git::Worktree} object for the given directory and optional commitish
114
+ #
115
+ # This is a factory method — it constructs the domain object but does not
116
+ # immediately execute any git commands.
117
+ #
118
+ # @example Get a worktree object for a new path
119
+ # wt = repo.worktree('/tmp/feature')
120
+ #
121
+ # @example Get a worktree object for a specific branch or commit
122
+ # wt = repo.worktree('/tmp/hotfix', 'main')
123
+ #
124
+ # @param dir [String] filesystem path for the worktree
125
+ #
126
+ # @param commitish [String, nil] branch, tag, or commit to associate with
127
+ # the worktree; `nil` means no commitish is specified
128
+ #
129
+ # @return [Git::Worktree] a worktree domain object for the given path
130
+ #
131
+ def worktree(dir, commitish = nil)
132
+ Git::Worktree.new(self, dir, commitish)
133
+ end
134
+
135
+ # Return a {Git::Worktrees} collection of all worktrees (main and linked)
136
+ #
137
+ # The collection is populated eagerly when this method is called (git runs
138
+ # at construction time). It is enumerable and supports indexed access by
139
+ # worktree path.
140
+ #
141
+ # @example Iterate over all worktrees
142
+ # repo.worktrees.each { |wt| puts wt.dir }
143
+ #
144
+ # @example Count worktrees
145
+ # repo.worktrees.size
146
+ #
147
+ # @example Access a specific worktree by path
148
+ # repo.worktrees['/tmp/feature']
149
+ #
150
+ # @return [Git::Worktrees] an enumerable collection of all worktrees
151
+ #
152
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
153
+ #
154
+ def worktrees
155
+ Git::Worktrees.new(self)
156
+ end
157
+ end
158
+ end
159
+ end