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,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,93 @@
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
+ # @return [Hash{String => String}] a mapping of file paths to status codes
65
+ #
40
66
  def fetch_path_status
41
- @fetch_path_status ||= @base.lib.diff_path_status(
42
- @from, @to, { path_limiter: @path_limiter }
43
- )
67
+ @fetch_path_status ||= @base.diff_name_status(@from, @to, path_limiter: @path_limiter).to_h
68
+ end
69
+
70
+ # Sets up legacy (base, from, to, path_limiter) instance state
71
+ #
72
+ # @param base [Git::Repository] the git object
73
+ #
74
+ # @param from [String] the first commit or object to compare
75
+ #
76
+ # @param to [String, nil] the second commit or object to compare
77
+ #
78
+ # @param path_limiter [String, Pathname, Array, nil] path(s) to limit the diff
79
+ #
80
+ # @return [void]
81
+ #
82
+ # @raise [ArgumentError] when `from` or `to` starts with `"-"`
83
+ #
84
+ def initialize_legacy(base, from, to, path_limiter)
85
+ [from, to].compact.each do |arg|
86
+ raise ArgumentError, "Invalid argument: '#{arg}'" if arg.start_with?('-')
87
+ end
88
+ @base = base
89
+ @from = from
90
+ @to = to
91
+ @path_limiter = path_limiter
44
92
  end
45
93
  end
46
94
  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
@@ -1,8 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Git
4
- # Provides access to the statistics of a diff between two commits,
5
- # including insertions, deletions, and file-level details.
4
+ # Lazy diff statistics for a comparison between two trees
5
+ #
6
+ # Supports comparing (1) two commits, (2) a commit against the working tree,
7
+ # or (3) the index against the working tree.
8
+ #
9
+ # @example Get the insertion and deletion counts
10
+ # stats = repo.diff_stats
11
+ # stats.insertions #=> 3
12
+ # stats.deletions #=> 1
13
+ #
14
+ # @api public
6
15
  class DiffStats
7
16
  # @private
8
17
  def initialize(base, from, to, path_limiter = nil)
@@ -15,45 +24,73 @@ module Git
15
24
  @from = from
16
25
  @to = to
17
26
  @path_limiter = path_limiter
18
- @stats = nil
27
+ @fetch_stats = nil
19
28
  end
20
29
 
21
- # Returns the total number of lines deleted.
30
+ # Returns the total number of lines deleted
31
+ #
32
+ # @example Get the deletion count
33
+ # stats = repo.diff_stats
34
+ # stats.deletions #=> 5
35
+ #
36
+ # @return [Integer] the total deletion count
22
37
  def deletions
23
38
  fetch_stats[:total][:deletions]
24
39
  end
25
40
 
26
- # Returns the total number of lines inserted.
41
+ # Returns the total number of lines inserted
42
+ #
43
+ # @example Get the insertion count
44
+ # stats = repo.diff_stats
45
+ # stats.insertions #=> 3
46
+ #
47
+ # @return [Integer] the total insertion count
27
48
  def insertions
28
49
  fetch_stats[:total][:insertions]
29
50
  end
30
51
 
31
- # Returns the total number of lines changed (insertions + deletions).
52
+ # Returns the total number of lines changed (insertions + deletions)
53
+ #
54
+ # @example Get the total changed-line count
55
+ # stats = repo.diff_stats
56
+ # stats.lines #=> 8
57
+ #
58
+ # @return [Integer] the total changed-line count
32
59
  def lines
33
60
  fetch_stats[:total][:lines]
34
61
  end
35
62
 
36
- # Returns a hash of statistics for each file in the diff.
63
+ # Returns a hash of statistics for each file in the diff
64
+ #
65
+ # @example Get per-file statistics
66
+ # stats = repo.diff_stats
67
+ # stats.files #=> { "lib/foo.rb" => { insertions: 3, deletions: 1 } }
37
68
  #
38
- # @return [Hash<String, {insertions: Integer, deletions: Integer}>]
69
+ # @return [Hash{String=>Hash{Symbol=>Integer}}]
70
+ # per-file statistics keyed by file path
39
71
  def files
40
72
  fetch_stats[:files]
41
73
  end
42
74
 
43
- # Returns a hash of the total statistics for the diff.
75
+ # Returns a hash of the total statistics for the diff
44
76
  #
45
- # @return [{insertions: Integer, deletions: Integer, lines: Integer, files: Integer}]
77
+ # @example Get total statistics
78
+ # stats = repo.diff_stats
79
+ # stats.total #=> { insertions: 3, deletions: 1, lines: 4, files: 1 }
80
+ #
81
+ # @return [Hash{Symbol=>Integer}]
82
+ # aggregate statistics for the entire diff
46
83
  def total
47
84
  fetch_stats[:total]
48
85
  end
49
86
 
50
87
  private
51
88
 
52
- # Lazily fetches and caches the stats from the git lib.
89
+ # Lazily fetches and caches the stats from the git lib
90
+ #
91
+ # @return [Hash] the fetched stats hash
53
92
  def fetch_stats
54
- @fetch_stats ||= @base.lib.diff_stats(
55
- @from, @to, { path_limiter: @path_limiter }
56
- )
93
+ @fetch_stats ||= @base.diff_numstat(@from, @to, path_limiter: @path_limiter)
57
94
  end
58
95
  end
59
96
  end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Immutable value object representing a single directory's contribution to a diff
5
+ #
6
+ # @api public
7
+ #
8
+ # @example
9
+ # info = Git::DirstatEntry.new(directory: 'lib/commands/', percentage: 45.2)
10
+ # info.directory #=> "lib/commands/"
11
+ # info.percentage #=> 45.2
12
+ #
13
+ # @!attribute [r] directory
14
+ # @return [String] the directory path (always ends with '/')
15
+ #
16
+ # @!attribute [r] percentage
17
+ # @return [Float] the percentage of changes in this directory (0.0-100.0)
18
+ #
19
+ DirstatEntry = Data.define(:directory, :percentage)
20
+
21
+ # Immutable result object from git --dirstat output
22
+ #
23
+ # Contains the list of directories and their contribution percentages to the diff.
24
+ #
25
+ # @api public
26
+ #
27
+ # @example
28
+ # dirstat = Git::DirstatInfo.new(
29
+ # entries: [
30
+ # Git::DirstatEntry.new(directory: 'lib/commands/', percentage: 45.2),
31
+ # Git::DirstatEntry.new(directory: 'spec/unit/', percentage: 30.1)
32
+ # ]
33
+ # )
34
+ # dirstat.entries.first.directory #=> "lib/commands/"
35
+ # dirstat['lib/commands/'] #=> 45.2
36
+ # dirstat.to_h #=> { "lib/commands/" => 45.2, "spec/unit/" => 30.1 }
37
+ #
38
+ # @!attribute [r] entries
39
+ # @return [Array<DirstatEntry>] directory statistics in order from git output
40
+ #
41
+ DirstatInfo = Data.define(:entries) do
42
+ # Look up percentage by directory path
43
+ #
44
+ # @param directory [String] the directory path
45
+ # @return [Float, nil] the percentage or nil if not found
46
+ #
47
+ def [](directory)
48
+ entries.find { |e| e.directory == directory }&.percentage
49
+ end
50
+
51
+ # Convert to a Hash mapping directory to percentage
52
+ #
53
+ # @return [Hash<String, Float>]
54
+ #
55
+ def to_h
56
+ entries.to_h { |e| [e.directory, e.percentage] }
57
+ end
58
+
59
+ # Number of directories in the dirstat
60
+ #
61
+ # @return [Integer]
62
+ #
63
+ def size
64
+ entries.size
65
+ end
66
+
67
+ # Check if dirstat is empty
68
+ #
69
+ # @return [Boolean]
70
+ #
71
+ def empty?
72
+ entries.empty?
73
+ end
74
+
75
+ # Iterate over entries
76
+ #
77
+ # @yield [DirstatEntry] each entry
78
+ # @return [Enumerator] if no block given
79
+ #
80
+ def each(&block)
81
+ entries.each(&block)
82
+ end
83
+
84
+ include Enumerable
85
+ end
86
+ end