git 4.3.2 → 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 -40
  45. data/CONTRIBUTING.md +694 -53
  46. data/README.md +17 -5
  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 +486 -410
  52. data/lib/git/branch.rb +380 -43
  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 +224 -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 +114 -18
  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 +349 -20
  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,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/base'
4
+
5
+ module Git
6
+ module Commands
7
+ # Implements the `git write-tree` command
8
+ #
9
+ # Creates a tree object using the current index and prints the SHA-1 name of
10
+ # the new tree object to standard output. The index must be in a fully
11
+ # merged state.
12
+ #
13
+ # @example Write the current index as a tree object
14
+ # write_tree = Git::Commands::WriteTree.new(execution_context)
15
+ # result = write_tree.call
16
+ # sha = result.stdout # => "abc123..."
17
+ #
18
+ # @example Write only a subdirectory as a tree object
19
+ # write_tree = Git::Commands::WriteTree.new(execution_context)
20
+ # result = write_tree.call(prefix: 'lib/')
21
+ #
22
+ # @example Allow missing objects in the object database
23
+ # write_tree = Git::Commands::WriteTree.new(execution_context)
24
+ # result = write_tree.call(missing_ok: true)
25
+ #
26
+ # @note `arguments` block audited against https://git-scm.com/docs/git-write-tree/2.53.0
27
+ #
28
+ # @see https://git-scm.com/docs/git-write-tree git-write-tree
29
+ #
30
+ # @see Git::Commands
31
+ #
32
+ # @api private
33
+ #
34
+ class WriteTree < Git::Commands::Base
35
+ arguments do
36
+ literal 'write-tree'
37
+ flag_option :missing_ok
38
+ value_option :prefix, inline: true
39
+ end
40
+
41
+ # @!method call(*, **)
42
+ #
43
+ # @overload call(**options)
44
+ #
45
+ # Execute the `git write-tree` command
46
+ #
47
+ # @param options [Hash] command options
48
+ #
49
+ # @option options [Boolean, nil] :missing_ok (nil) disable the check that
50
+ # all objects referenced by the directory exist in the object
51
+ # database
52
+ #
53
+ # @option options [String] :prefix (nil) write a tree object that
54
+ # represents a subdirectory
55
+ #
56
+ # The prefix path should end with `/` (e.g., `'lib/'`).
57
+ #
58
+ # @return [Git::CommandLineResult] the result of calling `git write-tree`
59
+ #
60
+ # @raise [ArgumentError] if unsupported options are provided
61
+ #
62
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
63
+ #
64
+ # @api public
65
+ #
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'commands/base'
4
+ require_relative 'commands/add'
5
+ require_relative 'commands/am'
6
+ require_relative 'commands/apply'
7
+ require_relative 'commands/archive'
8
+ require_relative 'commands/branch'
9
+ require_relative 'commands/cat_file'
10
+ require_relative 'commands/checkout'
11
+ require_relative 'commands/checkout_index'
12
+ require_relative 'commands/clean'
13
+ require_relative 'commands/clone'
14
+ require_relative 'commands/commit'
15
+ require_relative 'commands/commit_tree'
16
+ require_relative 'commands/config_option_syntax'
17
+ require_relative 'commands/describe'
18
+ require_relative 'commands/diff'
19
+ require_relative 'commands/diff_files'
20
+ require_relative 'commands/diff_index'
21
+ require_relative 'commands/fetch'
22
+ require_relative 'commands/fsck'
23
+ require_relative 'commands/gc'
24
+ require_relative 'commands/grep'
25
+ require_relative 'commands/init'
26
+ require_relative 'commands/log'
27
+ require_relative 'commands/ls_files'
28
+ require_relative 'commands/ls_remote'
29
+ require_relative 'commands/ls_tree'
30
+ require_relative 'commands/maintenance'
31
+ require_relative 'commands/merge'
32
+ require_relative 'commands/merge_base'
33
+ require_relative 'commands/mv'
34
+ require_relative 'commands/name_rev'
35
+ require_relative 'commands/pull'
36
+ require_relative 'commands/push'
37
+ require_relative 'commands/read_tree'
38
+ require_relative 'commands/remote'
39
+ require_relative 'commands/repack'
40
+ require_relative 'commands/reset'
41
+ require_relative 'commands/rev_parse'
42
+ require_relative 'commands/revert'
43
+ require_relative 'commands/rm'
44
+ require_relative 'commands/show'
45
+ require_relative 'commands/show_ref'
46
+ require_relative 'commands/stash'
47
+ require_relative 'commands/status'
48
+ require_relative 'commands/symbolic_ref'
49
+ require_relative 'commands/tag'
50
+ require_relative 'commands/update_ref'
51
+ require_relative 'commands/version'
52
+ require_relative 'commands/worktree'
53
+ require_relative 'commands/write_tree'
54
+
55
+ module Git
56
+ # Internal command layer for the git gem
57
+ #
58
+ # Each git operation is represented by a class within this namespace. Command
59
+ # classes define the CLI contract via the {Arguments} DSL, bind caller-supplied
60
+ # parameters to command-line flags and operands, execute the subprocess through
61
+ # {Git::CommandLine}, and return a raw {Git::CommandLineResult}.
62
+ #
63
+ # Commands do **not** parse output — that responsibility belongs to the
64
+ # {Git::Parsers} layer, orchestrated by the facade ({Git::Lib} / future
65
+ # {Git::Repository}).
66
+ #
67
+ # All classes in this namespace are internal (`@api private`). End users
68
+ # should interact with the public API on {Git::Base} instead.
69
+ #
70
+ # ## Architecture
71
+ #
72
+ # ```
73
+ # Git::Base (public API)
74
+ # └── Git::Lib / Git::Repository (facade — orchestrates commands + parsers)
75
+ # └── Git::Commands::* (defines CLI API, binds args, executes)
76
+ # └── Git::CommandLine (subprocess execution)
77
+ # ```
78
+ #
79
+ # Simple commands inherit from {Commands::Base} and only need an `arguments`
80
+ # block. Multi-mode git commands (e.g., `git branch`, `git stash`) are split
81
+ # into namespace modules with one class per mode.
82
+ #
83
+ # @api private
84
+ #
85
+ # @see Git::Commands::Base
86
+ # @see Git::Commands::Arguments
87
+ #
88
+ module Commands; end
89
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Value object representing a detached HEAD state
5
+ #
6
+ # When HEAD points directly to a commit rather than a branch reference,
7
+ # the repository is in a "detached HEAD" state. This object captures
8
+ # that state along with the commit SHA that HEAD points to.
9
+ #
10
+ # This class shares a minimal interface with {Git::BranchInfo} to allow
11
+ # polymorphic usage where appropriate:
12
+ # - `short_name` - returns 'HEAD'
13
+ # - `target_oid` - returns the commit SHA
14
+ # - `to_s` - returns 'HEAD'
15
+ # - `detached?` - returns true
16
+ #
17
+ # @example Detecting detached HEAD state
18
+ # head = repo.show_current
19
+ # if head.detached?
20
+ # puts "HEAD detached at #{head.target_oid[0, 7]}"
21
+ # else
22
+ # puts "On branch #{head.short_name}"
23
+ # end
24
+ #
25
+ # @example Polymorphic usage
26
+ # head = repo.show_current
27
+ # puts "Checked out: #{head.short_name}" # Works for both types
28
+ # system("git log #{head.short_name}") # Works for both types
29
+ #
30
+ # @see Git::BranchInfo for the branch counterpart
31
+ # @see Git::Commands::Branch::ShowCurrent for the command that produces this
32
+ #
33
+ # @api public
34
+ #
35
+ # @!attribute [r] target_oid
36
+ #
37
+ # The commit object ID (SHA) that HEAD points to
38
+ #
39
+ # @return [String] the full 40-character object ID
40
+ #
41
+ DetachedHeadInfo = Data.define(:target_oid) do
42
+ # @return [Boolean] always true for DetachedHeadInfo
43
+ def detached? = true
44
+
45
+ # @return [Boolean] always false for DetachedHeadInfo (detached HEAD always has a commit)
46
+ def unborn? = false
47
+
48
+ # @return [String] always 'HEAD'
49
+ def short_name = 'HEAD'
50
+
51
+ # @return [String] always 'HEAD'
52
+ def to_s = 'HEAD'
53
+ end
54
+ end
data/lib/git/diff.rb CHANGED
@@ -4,10 +4,37 @@ require_relative 'diff_path_status'
4
4
  require_relative 'diff_stats'
5
5
 
6
6
  module Git
7
- # object that holds the diff between two commits
7
+ # Diff between two commits or between a commit and the working tree
8
+ #
9
+ # @example Diff between two commits
10
+ # diff = repo.diff('HEAD~1', 'HEAD')
11
+ # diff.size # => 3
12
+ # diff.insertions # => 20
13
+ # diff.deletions # => 5
14
+ #
15
+ # @example Limit diff to a specific path
16
+ # diff = repo.diff('HEAD~1', 'HEAD').path('lib/')
17
+ #
18
+ # @api public
19
+ #
8
20
  class Diff
9
21
  include Enumerable
10
22
 
23
+ # Creates a new Diff
24
+ #
25
+ # @example
26
+ # diff = Git::Diff.new(base, 'HEAD~1', 'HEAD')
27
+ #
28
+ # @param base [Git::Base, Git::Repository] the git repository
29
+ #
30
+ # @param from [String, nil] the starting commit ref, or `nil` to compare
31
+ # from the index
32
+ #
33
+ # @param to [String, nil] the ending commit ref, or `nil` to compare to
34
+ # the working tree
35
+ #
36
+ # @return [void]
37
+ #
11
38
  def initialize(base, from = nil, to = nil)
12
39
  @base = base
13
40
  @from = from&.to_s
@@ -16,7 +43,18 @@ module Git
16
43
  @path = nil
17
44
  @full_diff_files = nil
18
45
  end
19
- attr_reader :from, :to
46
+
47
+ # The starting commit ref
48
+ #
49
+ # @return [String, nil] the starting commit ref, or `nil` if not set
50
+ #
51
+ attr_reader :from
52
+
53
+ # The ending commit ref
54
+ #
55
+ # @return [String, nil] the ending commit ref, or `nil` if not set
56
+ #
57
+ attr_reader :to
20
58
 
21
59
  # Limits the diff to the specified path(s)
22
60
  #
@@ -33,8 +71,11 @@ module Git
33
71
  # @example Remove path filtering (show all files)
34
72
  # diff.path # or diff.path(nil)
35
73
  #
36
- # @param paths [String, Pathname] one or more paths to filter the diff. Pass no arguments to remove filtering.
74
+ # @param paths [String, Pathname] one or more paths to filter the diff;
75
+ # pass no arguments to remove filtering
76
+ #
37
77
  # @return [self] returns self for method chaining
78
+ #
38
79
  # @raise [ArgumentError] if any path is an Array (use splatted arguments instead)
39
80
  #
40
81
  def path(*paths)
@@ -53,21 +94,69 @@ module Git
53
94
  self
54
95
  end
55
96
 
97
+ # Returns the full diff output as a string
98
+ #
99
+ # @example
100
+ # diff.patch # => "diff --git a/file.rb b/file.rb\n..."
101
+ #
102
+ # @return [String] the raw output of `git diff`
103
+ #
56
104
  def patch
57
- @base.lib.diff_full(@from, @to, { path_limiter: @path })
105
+ if @base.respond_to?(:diff_full)
106
+ @base.diff_full(@from, @to, path_limiter: @path)
107
+ else
108
+ @base.lib.diff_full(@from, @to, { path_limiter: @path })
109
+ end
58
110
  end
59
111
  alias to_s patch
60
112
 
113
+ # Returns the diff file info for the given path
114
+ #
115
+ # @example
116
+ # diff['lib/git.rb'] # => #<Git::Diff::DiffFile ...>
117
+ #
118
+ # @param key [String] the file path to look up
119
+ #
120
+ # @return [Git::Diff::DiffFile] the diff file object for the given path
121
+ #
61
122
  def [](key)
62
123
  process_full
63
124
  @full_diff_files.assoc(key)[1]
64
125
  end
65
126
 
127
+ # Iterates over each changed file in the diff
128
+ #
129
+ # @overload each
130
+ # @example Get an enumerator
131
+ # diff.each.map(&:path) # => ["lib/git.rb", "README.md"]
132
+ #
133
+ # @return [Enumerator<Git::Diff::DiffFile>] an enumerator over the
134
+ # changed files
135
+ #
136
+ # @overload each(&block)
137
+ # @example Iterate with a block
138
+ # diff.each { |file| puts file.path }
139
+ #
140
+ # @yield [file] each changed file in the diff
141
+ #
142
+ # @yieldparam file [Git::Diff::DiffFile] a changed file
143
+ #
144
+ # @yieldreturn [void]
145
+ #
146
+ # @return [Array<Git::Diff::DiffFile>] the array of changed files
147
+ #
66
148
  def each(&)
67
149
  process_full
68
150
  @full_diff_files.map { |file| file[1] }.each(&)
69
151
  end
70
152
 
153
+ # Returns the number of changed files in the diff
154
+ #
155
+ # @example
156
+ # diff.size # => 3
157
+ #
158
+ # @return [Integer] the number of changed files
159
+ #
71
160
  def size
72
161
  stats_provider.total[:files]
73
162
  end
@@ -76,22 +165,61 @@ module Git
76
165
  # DEPRECATED METHODS
77
166
  #
78
167
 
168
+ # Returns the path-to-status hash for all changed files in the diff
169
+ #
170
+ # @example
171
+ # diff.name_status # => { "lib/git.rb" => "M", "README.md" => "A" }
172
+ #
173
+ # @return [Hash<String, String>] map of file path to git status letter
174
+ #
79
175
  def name_status
80
176
  path_status_provider.to_h
81
177
  end
82
178
 
179
+ # Returns the total number of changed lines in the diff
180
+ #
181
+ # @example
182
+ # diff.lines # => 42
183
+ #
184
+ # @return [Integer] the total number of inserted and deleted lines
185
+ #
83
186
  def lines
84
187
  stats_provider.lines
85
188
  end
86
189
 
190
+ # Returns the total number of deleted lines in the diff
191
+ #
192
+ # @example
193
+ # diff.deletions # => 10
194
+ #
195
+ # @return [Integer] the number of deleted lines
196
+ #
87
197
  def deletions
88
198
  stats_provider.deletions
89
199
  end
90
200
 
201
+ # Returns the total number of inserted lines in the diff
202
+ #
203
+ # @example
204
+ # diff.insertions # => 32
205
+ #
206
+ # @return [Integer] the number of inserted lines
207
+ #
91
208
  def insertions
92
209
  stats_provider.insertions
93
210
  end
94
211
 
212
+ # Returns a statistics hash for the diff
213
+ #
214
+ # @example
215
+ # diff.stats
216
+ # # => {
217
+ # # files: { "lib/git.rb" => { insertions: 5, deletions: 2 } },
218
+ # # total: { insertions: 5, deletions: 2, lines: 7 }
219
+ # # }
220
+ #
221
+ # @return [Hash] statistics including per-file and total insert/delete counts
222
+ #
95
223
  def stats
96
224
  {
97
225
  files: stats_provider.files,
@@ -99,13 +227,72 @@ module Git
99
227
  }
100
228
  end
101
229
 
102
- # The changes for a single file within a diff
230
+ # Information about a single changed file within a {Git::Diff}
231
+ #
232
+ # @example Access diff file information
233
+ # diff.each do |file|
234
+ # puts file.path
235
+ # puts file.binary? ? 'binary' : file.patch
236
+ # end
237
+ #
238
+ # @api public
239
+ #
103
240
  class DiffFile
104
- attr_accessor :patch, :path, :mode, :src, :dst, :type
241
+ # The raw diff patch text for this file
242
+ #
243
+ # @return [String, nil] the patch text
244
+ #
245
+ attr_accessor :patch
246
+
247
+ # The file path relative to the repository root
248
+ #
249
+ # @return [String, nil] the file path
250
+ #
251
+ attr_accessor :path
252
+
253
+ # The file mode
254
+ #
255
+ # @return [String] the octal file mode (e.g. `"100644"`)
256
+ #
257
+ attr_accessor :mode
258
+
259
+ # The source (pre-change) blob SHA
260
+ #
261
+ # @return [String] the source blob SHA
262
+ #
263
+ attr_accessor :src
264
+
265
+ # The destination (post-change) blob SHA
266
+ #
267
+ # @return [String] the destination blob SHA
268
+ #
269
+ attr_accessor :dst
270
+
271
+ # The type of change
272
+ #
273
+ # @return [String] the change type (e.g. `"modified"`, `"new"`, `"deleted"`)
274
+ #
275
+ attr_accessor :type
105
276
 
106
277
  @base = nil
278
+
279
+ # Regexp matching a nil blob SHA (all-zero hash of 4 to 40 hex digits)
107
280
  NIL_BLOB_REGEXP = /\A0{4,40}\z/
108
281
 
282
+ # Creates a new DiffFile from parsed diff data
283
+ #
284
+ # @example
285
+ # file = Git::Diff::DiffFile.new(base,
286
+ # patch: "diff --git ...", path: 'lib/git.rb',
287
+ # mode: '100644', src: 'abc123', dst: 'def456',
288
+ # type: 'modified', binary: false)
289
+ #
290
+ # @param base [Git::Base, Git::Repository] the git repository
291
+ #
292
+ # @param hash [Hash] the parsed diff attributes
293
+ #
294
+ # @return [void]
295
+ #
109
296
  def initialize(base, hash)
110
297
  @base = base
111
298
  @patch = hash[:patch]
@@ -117,10 +304,31 @@ module Git
117
304
  @binary = hash[:binary]
118
305
  end
119
306
 
307
+ # Returns true if this file is a binary file
308
+ #
309
+ # @example
310
+ # diff['path/to/image.png'].binary? # => true
311
+ #
312
+ # @return [Boolean] `true` if the file is binary, `false` otherwise
313
+ #
120
314
  def binary?
121
315
  !!@binary
122
316
  end
123
317
 
318
+ # Returns the blob object for this file
319
+ #
320
+ # @example Retrieve the destination blob
321
+ # file.blob # => #<Git::Object::Blob ...>
322
+ #
323
+ # @example Retrieve the source blob
324
+ # file.blob(:src) # => #<Git::Object::Blob ...>
325
+ #
326
+ # @param type [Symbol] `:src` to retrieve the source blob, or `:dst`
327
+ # (default) for the destination blob
328
+ #
329
+ # @return [Git::Object::Blob, nil] the blob object, or `nil` if the blob
330
+ # SHA is the null SHA
331
+ #
124
332
  def blob(type = :dst)
125
333
  if type == :src && !NIL_BLOB_REGEXP.match(@src)
126
334
  @base.object(@src)
@@ -132,6 +340,14 @@ module Git
132
340
 
133
341
  private
134
342
 
343
+ # Validates that no path argument is an Array
344
+ #
345
+ # @param paths [Array] the raw paths array passed to {#path}
346
+ #
347
+ # @return [void]
348
+ #
349
+ # @raise [ArgumentError] if any element of paths is an Array
350
+ #
135
351
  def validate_paths_not_arrays(paths)
136
352
  return unless paths.any?(Array)
137
353
 
@@ -140,27 +356,58 @@ module Git
140
356
  "Use path('lib/', 'docs/') not path(['lib/', 'docs/'])"
141
357
  end
142
358
 
359
+ # Triggers full diff processing if not yet done
360
+ #
361
+ # @return [void]
362
+ #
143
363
  def process_full
144
364
  return if @full_diff_files
145
365
 
146
366
  @full_diff_files = process_full_diff
147
367
  end
148
368
 
369
+ # Returns a memoized DiffPathStatus provider for this diff
370
+ #
371
+ # @return [Git::DiffPathStatus] the path status provider
372
+ #
149
373
  def path_status_provider
150
374
  @path_status_provider ||= Git::DiffPathStatus.new(@base, @from, @to, @path)
151
375
  end
152
376
 
377
+ # Returns a memoized DiffStats provider for this diff
378
+ #
379
+ # @return [Git::DiffStats] the stats provider
380
+ #
153
381
  def stats_provider
154
382
  @stats_provider ||= Git::DiffStats.new(@base, @from, @to, @path)
155
383
  end
156
384
 
385
+ # Parses the full diff output into DiffFile objects
386
+ #
387
+ # @return [Array<Array(String, Git::Diff::DiffFile)>] list of
388
+ # `[filename, DiffFile]` pairs
389
+ #
157
390
  def process_full_diff
158
391
  FullDiffParser.new(@base, patch).parse
159
392
  end
160
393
 
161
- # A private parser class to process the output of `git diff`
394
+ # Private parser for `git diff` output
395
+ #
396
+ # @example Parse a diff patch
397
+ # parser = Git::Diff::FullDiffParser.new(base, patch_text)
398
+ # files = parser.parse
399
+ #
162
400
  # @api private
401
+ #
163
402
  class FullDiffParser
403
+ # Creates a new FullDiffParser
404
+ #
405
+ # @param base [Git::Base, Git::Repository] the git repository
406
+ #
407
+ # @param patch_text [String] the raw `git diff` output to parse
408
+ #
409
+ # @return [void]
410
+ #
164
411
  def initialize(base, patch_text)
165
412
  @base = base
166
413
  @patch_text = patch_text
@@ -169,6 +416,11 @@ module Git
169
416
  @defaults = { mode: '', src: '', dst: '', type: 'modified', binary: false }
170
417
  end
171
418
 
419
+ # Parses the diff text into a list of filename/DiffFile pairs
420
+ #
421
+ # @return [Array<Array(String, Git::Diff::DiffFile)>] list of
422
+ # `[filename, DiffFile]` pairs
423
+ #
172
424
  def parse
173
425
  @patch_text.split("\n").each { |line| process_line(line) }
174
426
  @final_files.map { |filename, data| [filename, DiffFile.new(@base, data)] }
@@ -176,6 +428,12 @@ module Git
176
428
 
177
429
  private
178
430
 
431
+ # Dispatches a single diff line to the appropriate handler
432
+ #
433
+ # @param line [String] a line from the diff output
434
+ #
435
+ # @return [void]
436
+ #
179
437
  def process_line(line)
180
438
  if (new_file_match = line.match(%r{\Adiff --git ("?)a/(.+?)\1 ("?)b/(.+?)\3\z}))
181
439
  start_new_file(new_file_match, line)
@@ -184,12 +442,26 @@ module Git
184
442
  end
185
443
  end
186
444
 
445
+ # Starts tracking a new file from a diff header line
446
+ #
447
+ # @param match [MatchData] the regex match from the diff header line
448
+ #
449
+ # @param line [String] the original diff header line
450
+ #
451
+ # @return [void]
452
+ #
187
453
  def start_new_file(match, line)
188
454
  filename = Git::EscapedPath.new(match[2]).unescape
189
455
  @current_file_data = @defaults.merge({ patch: line, path: filename })
190
456
  @final_files[filename] = @current_file_data
191
457
  end
192
458
 
459
+ # Appends a diff line to the current file's accumulated data
460
+ #
461
+ # @param line [String] a diff line to append
462
+ #
463
+ # @return [void]
464
+ #
193
465
  def append_to_current_file(line)
194
466
  return unless @current_file_data
195
467
 
@@ -200,6 +472,12 @@ module Git
200
472
  @current_file_data[:patch] << "\n#{line}"
201
473
  end
202
474
 
475
+ # Parses an index line to extract source and destination blob SHAs
476
+ #
477
+ # @param line [String] a diff line that may be an index line
478
+ #
479
+ # @return [void]
480
+ #
203
481
  def parse_index_line(line)
204
482
  return unless (match = line.match(/^index ([0-9a-f]{4,40})\.\.([0-9a-f]{4,40})( ......)*/))
205
483
 
@@ -208,6 +486,12 @@ module Git
208
486
  @current_file_data[:mode] = match[3].strip if match[3]
209
487
  end
210
488
 
489
+ # Parses a file mode line to extract the change type and file mode
490
+ #
491
+ # @param line [String] a diff line that may be a file mode line
492
+ #
493
+ # @return [void]
494
+ #
211
495
  def parse_file_mode_line(line)
212
496
  return unless (match = line.match(/^([[:alpha:]]*?) file mode (......)/))
213
497
 
@@ -215,6 +499,12 @@ module Git
215
499
  @current_file_data[:mode] = match[2]
216
500
  end
217
501
 
502
+ # Marks the current file as binary if this is a binary diff line
503
+ #
504
+ # @param line [String] a diff line to check for the binary file marker
505
+ #
506
+ # @return [void]
507
+ #
218
508
  def check_for_binary(line)
219
509
  @current_file_data[:binary] = true if line.match?(/^Binary files /)
220
510
  end