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,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Immutable value object representing stats for a single file from git numstat output
5
+ #
6
+ # @api public
7
+ #
8
+ # @!attribute [r] path
9
+ # @return [String] the file path (destination path for renames)
10
+ #
11
+ # @!attribute [r] src_path
12
+ # @return [String, nil] the source path for renamed files, nil otherwise
13
+ #
14
+ # @!attribute [r] insertions
15
+ # @return [Integer] number of lines inserted
16
+ #
17
+ # @!attribute [r] deletions
18
+ # @return [Integer] number of lines deleted
19
+ #
20
+ DiffFileNumstatInfo = Data.define(:path, :src_path, :insertions, :deletions) do
21
+ # Check if this file was renamed
22
+ #
23
+ # @return [Boolean] true if the file was renamed
24
+ #
25
+ def renamed?
26
+ !src_path.nil?
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'file_ref'
4
+
5
+ module Git
6
+ # Immutable value object representing a single file's patch information
7
+ #
8
+ # DiffFilePatchInfo encapsulates the parsed data from a unified diff for one file,
9
+ # including source and destination file references, the patch text, change type,
10
+ # and line statistics.
11
+ #
12
+ # @api public
13
+ #
14
+ # @example A modified file patch
15
+ # patch_info = Git::DiffFilePatchInfo.new(
16
+ # src: Git::FileRef.new(mode: '100644', sha: 'abc1234', path: 'lib/foo.rb'),
17
+ # dst: Git::FileRef.new(mode: '100644', sha: 'def5678', path: 'lib/foo.rb'),
18
+ # patch: "diff --git a/lib/foo.rb b/lib/foo.rb\n...",
19
+ # status: :modified,
20
+ # similarity: nil,
21
+ # binary: false,
22
+ # insertions: 10,
23
+ # deletions: 5
24
+ # )
25
+ #
26
+ # @example A new file patch (src is nil)
27
+ # patch_info = Git::DiffFilePatchInfo.new(
28
+ # src: nil,
29
+ # dst: Git::FileRef.new(mode: '100644', sha: 'abc1234', path: 'lib/new.rb'),
30
+ # patch: "diff --git a/lib/new.rb b/lib/new.rb\nnew file mode 100644\n...",
31
+ # status: :added,
32
+ # similarity: nil,
33
+ # binary: false,
34
+ # insertions: 20,
35
+ # deletions: 0
36
+ # )
37
+ #
38
+ # @!attribute [r] src
39
+ # @return [FileRef, nil] the source file reference, or nil for new/added files
40
+ #
41
+ # @!attribute [r] dst
42
+ # @return [FileRef, nil] the destination file reference, or nil for deleted files
43
+ #
44
+ # @!attribute [r] patch
45
+ # @return [String] the full unified diff patch text for this file
46
+ #
47
+ # @!attribute [r] status
48
+ # @return [Symbol] the change status (:added, :modified, :deleted, :renamed, :copied, :type_changed)
49
+ #
50
+ # @!attribute [r] similarity
51
+ # @return [Integer, nil] similarity percentage for renames/copies (0-100), nil otherwise
52
+ #
53
+ # @!attribute [r] binary
54
+ # @return [Boolean] whether this is a binary file
55
+ #
56
+ # @!attribute [r] insertions
57
+ # @return [Integer] number of lines inserted
58
+ #
59
+ # @!attribute [r] deletions
60
+ # @return [Integer] number of lines deleted
61
+ #
62
+ DiffFilePatchInfo = Data.define(
63
+ :src,
64
+ :dst,
65
+ :patch,
66
+ :status,
67
+ :similarity,
68
+ :binary,
69
+ :insertions,
70
+ :deletions
71
+ ) do
72
+ # Get the primary file path
73
+ #
74
+ # Returns the destination path if it exists, otherwise the source path.
75
+ # This is the "current" or "canonical" path for the file.
76
+ #
77
+ # @return [String] the file path
78
+ #
79
+ def path
80
+ dst&.path || src&.path
81
+ end
82
+
83
+ # Get the source file path
84
+ #
85
+ # Returns the source path if it exists. Useful for renames/copies to see
86
+ # where the file came from.
87
+ #
88
+ # @return [String, nil] the source file path, or nil if no source (added files)
89
+ #
90
+ def src_path
91
+ src&.path
92
+ end
93
+
94
+ # Check if this is a binary file
95
+ #
96
+ # @return [Boolean] true if the file is binary
97
+ #
98
+ def binary?
99
+ binary
100
+ end
101
+
102
+ # Check if this file was renamed
103
+ #
104
+ # @return [Boolean]
105
+ #
106
+ def renamed?
107
+ status == :renamed
108
+ end
109
+
110
+ # Check if this file was copied
111
+ #
112
+ # @return [Boolean]
113
+ #
114
+ def copied?
115
+ status == :copied
116
+ end
117
+
118
+ # Check if this file was added
119
+ #
120
+ # @return [Boolean]
121
+ #
122
+ def added?
123
+ status == :added
124
+ end
125
+
126
+ # Check if this file was deleted
127
+ #
128
+ # @return [Boolean]
129
+ #
130
+ def deleted?
131
+ status == :deleted
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'file_ref'
4
+
5
+ module Git
6
+ # Immutable value object representing status info for a single file from git raw diff output
7
+ #
8
+ # Contains the source and destination file references, change status,
9
+ # similarity percentage (for renames/copies), and line change statistics.
10
+ #
11
+ # @api public
12
+ #
13
+ # @example A modified file
14
+ # info = Git::DiffFileRawInfo.new(
15
+ # src: Git::FileRef.new(mode: '100644', sha: 'abc1234', path: 'lib/foo.rb'),
16
+ # dst: Git::FileRef.new(mode: '100644', sha: 'def5678', path: 'lib/foo.rb'),
17
+ # status: :modified,
18
+ # similarity: nil,
19
+ # insertions: 5,
20
+ # deletions: 2,
21
+ # binary: false
22
+ # )
23
+ #
24
+ # @example A new file (src is nil)
25
+ # info = Git::DiffFileRawInfo.new(
26
+ # src: nil,
27
+ # dst: Git::FileRef.new(mode: '100644', sha: 'abc1234', path: 'lib/new.rb'),
28
+ # status: :added,
29
+ # similarity: nil,
30
+ # insertions: 10,
31
+ # deletions: 0,
32
+ # binary: false
33
+ # )
34
+ #
35
+ # @example A renamed file
36
+ # info = Git::DiffFileRawInfo.new(
37
+ # src: Git::FileRef.new(mode: '100644', sha: 'abc1234', path: 'lib/old.rb'),
38
+ # dst: Git::FileRef.new(mode: '100644', sha: 'def5678', path: 'lib/new.rb'),
39
+ # status: :renamed,
40
+ # similarity: 95,
41
+ # insertions: 2,
42
+ # deletions: 1,
43
+ # binary: false
44
+ # )
45
+ #
46
+ # @!attribute [r] src
47
+ # @return [FileRef, nil] the source file reference, or nil for new/added files
48
+ #
49
+ # @!attribute [r] dst
50
+ # @return [FileRef, nil] the destination file reference, or nil for deleted files
51
+ #
52
+ # @!attribute [r] status
53
+ # @return [Symbol] the change status (:added, :modified, :deleted, :renamed, :copied, :type_changed)
54
+ #
55
+ # @!attribute [r] similarity
56
+ # @return [Integer, nil] similarity percentage for renames/copies (0-100), nil otherwise
57
+ #
58
+ # @!attribute [r] insertions
59
+ # @return [Integer] number of lines inserted
60
+ #
61
+ # @!attribute [r] deletions
62
+ # @return [Integer] number of lines deleted
63
+ #
64
+ # @!attribute [r] binary
65
+ # @return [Boolean] whether this is a binary file
66
+ #
67
+ DiffFileRawInfo = Data.define(:src, :dst, :status, :similarity, :insertions, :deletions, :binary) do
68
+ # Get the primary file path
69
+ #
70
+ # Returns the destination path if it exists, otherwise the source path.
71
+ # This is the "current" or "canonical" path for the file.
72
+ #
73
+ # @return [String] the file path
74
+ #
75
+ def path
76
+ dst&.path || src&.path
77
+ end
78
+
79
+ # Get the source path (for renames/copies)
80
+ #
81
+ # @return [String, nil] the source path, or nil if file was added
82
+ #
83
+ def src_path
84
+ src&.path
85
+ end
86
+
87
+ # Check if this is a binary file
88
+ #
89
+ # @return [Boolean] true if the file is binary
90
+ #
91
+ def binary?
92
+ binary
93
+ end
94
+
95
+ # Check if this file was renamed
96
+ #
97
+ # @return [Boolean]
98
+ #
99
+ def renamed?
100
+ status == :renamed
101
+ end
102
+
103
+ # Check if this file was copied
104
+ #
105
+ # @return [Boolean]
106
+ #
107
+ def copied?
108
+ status == :copied
109
+ end
110
+
111
+ # Check if this file was added
112
+ #
113
+ # @return [Boolean]
114
+ #
115
+ def added?
116
+ status == :added
117
+ end
118
+
119
+ # Check if this file was deleted
120
+ #
121
+ # @return [Boolean]
122
+ #
123
+ def deleted?
124
+ status == :deleted
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Immutable value object representing a single file's diff information
5
+ #
6
+ # FileDiffInfo encapsulates the parsed data from a unified diff for one file,
7
+ # including the file path, patch text, mode changes, object identifiers, and type.
8
+ #
9
+ # This class is used by DiffInfo for the legacy diff API.
10
+ #
11
+ # @api public
12
+ #
13
+ # @example Create a FileDiffInfo from parsed diff output
14
+ # diff_info = Git::FileDiffInfo.new(
15
+ # path: 'lib/example.rb',
16
+ # patch: "diff --git a/lib/example.rb b/lib/example.rb\n...",
17
+ # mode: '100644',
18
+ # src: 'abc1234',
19
+ # dst: 'def5678',
20
+ # type: 'modified',
21
+ # binary: false
22
+ # )
23
+ #
24
+ # @!attribute [r] path
25
+ # @return [String] the file path relative to the repository root
26
+ #
27
+ # @!attribute [r] patch
28
+ # @return [String] the full unified diff patch text for this file
29
+ #
30
+ # @!attribute [r] mode
31
+ # @return [String] the file mode (e.g., '100644' for regular files)
32
+ #
33
+ # @!attribute [r] src
34
+ # @return [String] the source blob object identifier
35
+ #
36
+ # @!attribute [r] dst
37
+ # @return [String] the destination blob object identifier
38
+ #
39
+ # @!attribute [r] type
40
+ # @return [String] the type of change: 'modified', 'new', 'deleted', 'renamed'
41
+ #
42
+ # @!attribute [r] binary
43
+ # @return [Boolean] whether this is a binary file
44
+ #
45
+ FileDiffInfo = Data.define(
46
+ :path,
47
+ :patch,
48
+ :mode,
49
+ :src,
50
+ :dst,
51
+ :type,
52
+ :binary
53
+ ) do
54
+ # Check if this is a binary file
55
+ #
56
+ # @return [Boolean] true if the file is binary
57
+ #
58
+ def binary?
59
+ binary
60
+ end
61
+ end
62
+
63
+ # Immutable value object representing diff statistics and optional file patches
64
+ #
65
+ # DiffInfo encapsulates the parsed output from various git commands that produce
66
+ # diff statistics (like `git stash show --stat`). When patches are requested,
67
+ # it also includes the full patch information for each file.
68
+ #
69
+ # The stats hash structure:
70
+ # - `:total` - Hash with `:insertions`, `:deletions`, `:lines`, `:files` keys
71
+ # - `:files` - Hash mapping file paths to `{ insertions:, deletions: }` hashes
72
+ #
73
+ # @api public
74
+ #
75
+ # @example Create a DiffInfo from parsed stats output
76
+ # info = Git::DiffInfo.new(
77
+ # stats: {
78
+ # total: { insertions: 10, deletions: 5, lines: 15, files: 2 },
79
+ # files: {
80
+ # 'lib/foo.rb' => { insertions: 8, deletions: 3 },
81
+ # 'lib/bar.rb' => { insertions: 2, deletions: 2 }
82
+ # }
83
+ # },
84
+ # file_patches: []
85
+ # )
86
+ #
87
+ # @example Access statistics
88
+ # info.insertions # => 10
89
+ # info.deletions # => 5
90
+ # info.lines # => 15
91
+ # info.file_count # => 2
92
+ #
93
+ # @!attribute [r] stats
94
+ # @return [Hash] the statistics hash with :total and :files keys
95
+ #
96
+ # @!attribute [r] file_patches
97
+ # @return [Array<FileDiffInfo>] array of file diff info objects (empty if patch not requested)
98
+ #
99
+ DiffInfo = Data.define(:stats, :file_patches) do
100
+ # @!method insertions
101
+ # @return [Integer] total number of lines inserted
102
+
103
+ # @!method deletions
104
+ # @return [Integer] total number of lines deleted
105
+
106
+ # @!method lines
107
+ # @return [Integer] total number of lines changed (insertions + deletions)
108
+
109
+ # @!method file_count
110
+ # @return [Integer] number of files changed
111
+
112
+ # Total number of lines inserted
113
+ #
114
+ # @return [Integer]
115
+ #
116
+ def insertions
117
+ stats[:total][:insertions]
118
+ end
119
+
120
+ # Total number of lines deleted
121
+ #
122
+ # @return [Integer]
123
+ #
124
+ def deletions
125
+ stats[:total][:deletions]
126
+ end
127
+
128
+ # Total number of lines changed (insertions + deletions)
129
+ #
130
+ # @return [Integer]
131
+ #
132
+ def lines
133
+ stats[:total][:lines]
134
+ end
135
+
136
+ # Number of files changed
137
+ #
138
+ # @return [Integer]
139
+ #
140
+ def file_count
141
+ stats[:total][:files]
142
+ end
143
+
144
+ # Per-file statistics hash
145
+ #
146
+ # @return [Hash<String, Hash>] mapping file paths to `{ insertions:, deletions: }` hashes
147
+ #
148
+ def file_stats
149
+ stats[:files]
150
+ end
151
+
152
+ # Check if patch information is available
153
+ #
154
+ # @return [Boolean] true if file_patches were loaded
155
+ #
156
+ def patches?
157
+ !file_patches.empty?
158
+ end
159
+
160
+ # Get patch info for a specific file
161
+ #
162
+ # @param path [String] the file path
163
+ # @return [FileDiffInfo, nil] the diff info or nil if not found
164
+ #
165
+ def patch_for(path)
166
+ file_patches.find { |p| p.path == path }
167
+ end
168
+ end
169
+ end
@@ -2,45 +2,104 @@
2
2
 
3
3
  module Git
4
4
  # The files and their status (e.g., added, modified, deleted) between two commits
5
+ #
6
+ # @example Iterate over path statuses
7
+ # git = Git.open('/path/to/repo')
8
+ # git.diff_path_status.each { |path, status| puts "#{path}: #{status}" }
9
+ #
10
+ # @api public
11
+ #
5
12
  class DiffPathStatus
6
13
  include Enumerable
7
14
 
8
15
  # @private
9
- def initialize(base, from, to, path_limiter = nil)
10
- # Eagerly check for invalid arguments
11
- [from, to].compact.each do |arg|
12
- raise ArgumentError, "Invalid argument: '#{arg}'" if arg.start_with?('-')
16
+ def initialize(base_or_hash, from = nil, to = nil, path_limiter = nil)
17
+ if base_or_hash.is_a?(Hash)
18
+ # New form: pre-fetched hash passed directly from Git::Repository::Diffing
19
+ @fetch_path_status = base_or_hash
20
+ else
21
+ # Legacy form: (base, from, to, path_limiter)
22
+ # Used by Git::Diff#path_status_provider and direct instantiation in tests.
23
+ initialize_legacy(base_or_hash, from, to, path_limiter)
13
24
  end
14
-
15
- @base = base
16
- @from = from
17
- @to = to
18
- @path_limiter = path_limiter
19
- @path_status = nil
20
25
  end
21
26
 
22
- # Iterates over each file's status.
27
+ # Iterates over each file path and its status code
28
+ #
29
+ # @example Print each path and status
30
+ # diff_path_status.each { |path, status| puts "#{path}: #{status}" }
31
+ #
32
+ # @yield [path, status] each file path with its git status code
33
+ #
34
+ # @yieldparam path [String] the file path relative to the repository root
35
+ #
36
+ # @yieldparam status [String] the git status code (e.g. `"M"`, `"A"`, `"D"`)
37
+ #
38
+ # @yieldreturn [void]
39
+ #
40
+ # @return [Enumerator, Hash{String => String}] an `Enumerator` when no block
41
+ # is given; the name-status hash when a block is given
23
42
  #
24
- # @yield [path, status]
25
43
  def each(&)
44
+ return to_enum(__method__) unless block_given?
45
+
26
46
  fetch_path_status.each(&)
27
47
  end
28
48
 
29
- # Returns the name-status report as a Hash.
49
+ # Returns the name-status report as a hash
50
+ #
51
+ # @example Basic usage
52
+ # diff_path_status.to_h #=> { "README.md" => "M", "lib/foo.rb" => "A" }
53
+ #
54
+ # @return [Hash{String => String}] a mapping of file paths to their status codes
30
55
  #
31
- # @return [Hash<String, String>] A hash where keys are file paths
32
- # and values are their status codes.
33
56
  def to_h
34
57
  fetch_path_status
35
58
  end
36
59
 
37
60
  private
38
61
 
39
- # Lazily fetches and caches the path status from the git lib.
62
+ # Lazily fetches and caches the path status from the git lib
63
+ #
64
+ # When constructed with a pre-fetched hash, returns it directly.
65
+ # When `@base` responds to `#diff_name_status` (e.g. {Git::Repository} or
66
+ # {Git::Base}, which defines it as an alias for `#diff_path_status`),
67
+ # delegates directly to that method. Otherwise falls back to the legacy
68
+ # `@base.lib.diff_path_status` call for duck-typed base objects that only
69
+ # expose path-status via their lib.
70
+ #
71
+ # @return [Hash{String => String}] a mapping of file paths to status codes
72
+ #
40
73
  def fetch_path_status
41
- @fetch_path_status ||= @base.lib.diff_path_status(
42
- @from, @to, { path_limiter: @path_limiter }
43
- )
74
+ @fetch_path_status ||= if @base.respond_to?(:diff_name_status)
75
+ @base.diff_name_status(@from, @to, path_limiter: @path_limiter).to_h
76
+ else
77
+ @base.lib.diff_path_status(@from, @to, { path_limiter: @path_limiter })
78
+ end
79
+ end
80
+
81
+ # Sets up legacy (base, from, to, path_limiter) instance state
82
+ #
83
+ # @param base [Git::Base, Git::Repository] the git base instance
84
+ #
85
+ # @param from [String] the first commit or object to compare
86
+ #
87
+ # @param to [String, nil] the second commit or object to compare
88
+ #
89
+ # @param path_limiter [String, Pathname, Array, nil] path(s) to limit the diff
90
+ #
91
+ # @return [void]
92
+ #
93
+ # @raise [ArgumentError] when `from` or `to` starts with `"-"`
94
+ #
95
+ def initialize_legacy(base, from, to, path_limiter)
96
+ [from, to].compact.each do |arg|
97
+ raise ArgumentError, "Invalid argument: '#{arg}'" if arg.start_with?('-')
98
+ end
99
+ @base = base
100
+ @from = from
101
+ @to = to
102
+ @path_limiter = path_limiter
44
103
  end
45
104
  end
46
105
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dirstat_info'
4
+
5
+ module Git
6
+ # Immutable result object from git diff commands
7
+ #
8
+ # Contains summary statistics and per-file information from various diff formats.
9
+ # The `files` array contains one of:
10
+ # - DiffFileNumstatInfo (from --numstat)
11
+ # - DiffFileRawInfo (from --raw)
12
+ # - DiffFilePatchInfo (from --patch)
13
+ #
14
+ # @api public
15
+ #
16
+ # @!attribute [r] files_changed
17
+ # @return [Integer] number of files changed (from --shortstat)
18
+ #
19
+ # @!attribute [r] total_insertions
20
+ # @return [Integer] total lines inserted across all files (from --shortstat)
21
+ #
22
+ # @!attribute [r] total_deletions
23
+ # @return [Integer] total lines deleted across all files (from --shortstat)
24
+ #
25
+ # @!attribute [r] files
26
+ # @return [Array<DiffFileNumstatInfo, DiffFileRawInfo, DiffFilePatchInfo>] per-file information
27
+ #
28
+ # @!attribute [r] dirstat
29
+ # @return [DirstatInfo, nil] directory statistics if requested, nil otherwise
30
+ #
31
+ DiffResult = Data.define(:files_changed, :total_insertions, :total_deletions, :files, :dirstat)
32
+ end