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
data/lib/git/status.rb CHANGED
@@ -12,37 +12,112 @@ module Git
12
12
  class Status
13
13
  include Enumerable
14
14
 
15
- # @param base [Git::Base] The base git object
15
+ # Create a new Status for the given repository
16
+ #
17
+ # @param base [Git::Base, Git::Repository] the git object backing this status
18
+ #
16
19
  def initialize(base)
17
20
  @base = base
18
21
  # The factory returns a hash of file paths to StatusFile objects.
19
22
  @files = StatusFileFactory.new(base).construct_files
20
23
  end
21
24
 
22
- # File status collections, memoized for performance.
25
+ # Return files modified in the index and/or working tree
26
+ #
27
+ # Includes both staged modifications (index vs HEAD) and unstaged modifications
28
+ # (working tree vs index).
29
+ #
30
+ # @return [Hash{String => Git::Status::StatusFile}] changed files keyed by path
31
+ #
23
32
  def changed = @changed ||= select_files { |f| f.type == 'M' }
33
+
34
+ # Return files added to the index that are not yet in HEAD
35
+ #
36
+ # @return [Hash{String => Git::Status::StatusFile}] added files keyed by path
37
+ #
24
38
  def added = @added ||= select_files { |f| f.type == 'A' }
39
+
40
+ # Return files deleted from the index
41
+ #
42
+ # @return [Hash{String => Git::Status::StatusFile}] deleted files keyed by path
43
+ #
25
44
  def deleted = @deleted ||= select_files { |f| f.type == 'D' }
26
- # This works with `true` or `nil`
45
+
46
+ # Return files present in the working tree but not tracked by git
47
+ #
48
+ # @return [Hash{String => Git::Status::StatusFile}] untracked files keyed by path
49
+ #
27
50
  def untracked = @untracked ||= select_files(&:untracked)
28
51
 
29
- # Predicate methods to check the status of a specific file.
52
+ # Return `true` if `file` has been modified in the index or working tree
53
+ #
54
+ # @param file [String] the repository-relative path to check
55
+ #
56
+ # @return [Boolean] `true` if the file has been modified
57
+ #
30
58
  def changed?(file) = file_in_collection?(:changed, file)
59
+
60
+ # Return `true` if `file` has been added to the index
61
+ #
62
+ # @param file [String] the repository-relative path to check
63
+ #
64
+ # @return [Boolean] `true` if the file has been added
65
+ #
31
66
  def added?(file) = file_in_collection?(:added, file)
67
+
68
+ # Return `true` if `file` has been deleted from the index
69
+ #
70
+ # @param file [String] the repository-relative path to check
71
+ #
72
+ # @return [Boolean] `true` if the file has been deleted
73
+ #
32
74
  def deleted?(file) = file_in_collection?(:deleted, file)
75
+
76
+ # Return `true` if `file` is not tracked by git
77
+ #
78
+ # @param file [String] the repository-relative path to check
79
+ #
80
+ # @return [Boolean] `true` if the file is untracked
81
+ #
33
82
  def untracked?(file) = file_in_collection?(:untracked, file)
34
83
 
35
- # Access a status file by path, or iterate over all status files.
84
+ # Return the {Git::Status::StatusFile} for the given path
85
+ #
86
+ # @param file [String] the repository-relative path
87
+ #
88
+ # @return [Git::Status::StatusFile, nil] the status file, or `nil` if not found
89
+ #
36
90
  def [](file) = @files[file]
91
+
92
+ # Iterate over all status files
93
+ #
94
+ # @yield [file] each {Git::Status::StatusFile} in the repository
95
+ #
96
+ # @yieldparam file [Git::Status::StatusFile] a single file's status
97
+ #
98
+ # @return [Enumerator<Git::Status::StatusFile>] if no block is given
99
+ #
100
+ # @return [Array<Git::Status::StatusFile>] if a block is given
101
+ #
37
102
  def each(&) = @files.values.each(&)
38
103
 
39
- # Returns a formatted string representation of the status.
104
+ # Return a formatted multi-line string representation of the status
105
+ #
106
+ # @return [String] one indented block per file showing its SHA, mode, type,
107
+ # stage, and untracked flag
108
+ #
40
109
  def pretty
41
110
  map { |file| pretty_file(file) }.join << "\n"
42
111
  end
43
112
 
44
113
  private
45
114
 
115
+ # Format a single file's status as an indented multi-line string
116
+ #
117
+ # @param file [Git::Status::StatusFile] the file to format
118
+ #
119
+ # @return [String] the formatted status block for this file
120
+ #
46
121
  def pretty_file(file)
47
122
  <<~FILE
48
123
  #{file.path}
@@ -54,10 +129,26 @@ module Git
54
129
  FILE
55
130
  end
56
131
 
132
+ # Return a hash of files for which the block returns a truthy value
133
+ #
134
+ # @yield [file] each {Git::Status::StatusFile} in the repository
135
+ #
136
+ # @yieldparam file [Git::Status::StatusFile] a single file's status
137
+ #
138
+ # @return [Hash{String => Git::Status::StatusFile}] matching files keyed by path
139
+ #
57
140
  def select_files(&block)
58
141
  @files.select { |_path, file| block.call(file) }
59
142
  end
60
143
 
144
+ # Return `true` if `file_path` exists in the named status collection
145
+ #
146
+ # @param collection_name [Symbol] the collection to check (e.g. `:changed`)
147
+ #
148
+ # @param file_path [String] the repository-relative path to look up
149
+ #
150
+ # @return [Boolean] `true` if the path is present in the collection
151
+ #
61
152
  def file_in_collection?(collection_name, file_path)
62
153
  collection = public_send(collection_name)
63
154
  if ignore_case?
@@ -67,12 +158,25 @@ module Git
67
158
  end
68
159
  end
69
160
 
161
+ # Return a memoized set of downcased keys for the named collection
162
+ #
163
+ # @param collection_name [Symbol] the collection whose keys to downcase
164
+ #
165
+ # @return [Set<String>] the lowercased path keys
166
+ #
70
167
  def downcased_keys(collection_name)
71
168
  @_downcased_keys ||= {}
72
169
  @_downcased_keys[collection_name] ||=
73
170
  public_send(collection_name).keys.to_set(&:downcase)
74
171
  end
75
172
 
173
+ # Return `true` when git is configured to ignore filename case
174
+ #
175
+ # Reads `core.ignoreCase` from the repository config. Returns `false` if
176
+ # the config value is absent or if reading it raises {Git::FailedError}.
177
+ #
178
+ # @return [Boolean] `true` when `core.ignoreCase` is `"true"`
179
+ #
76
180
  def ignore_case?
77
181
  return @_ignore_case if defined?(@_ignore_case)
78
182
 
@@ -80,13 +184,65 @@ module Git
80
184
  rescue Git::FailedError
81
185
  @_ignore_case = false
82
186
  end
187
+ end
188
+ end
83
189
 
190
+ module Git
191
+ class Status
84
192
  # Represents a single file's status in the git repository. Each instance
85
193
  # holds information about a file's state in the index and working tree.
194
+ #
195
+ # @api public
196
+ #
86
197
  class StatusFile
87
- attr_reader :path, :type, :stage, :mode_index, :mode_repo,
88
- :sha_index, :sha_repo, :untracked
198
+ # The repository-relative file path
199
+ #
200
+ # @return [String] the path
201
+ attr_reader :path
89
202
 
203
+ # The change type for this file
204
+ #
205
+ # @return [String, nil] `"M"` for modified, `"A"` for added, `"D"` for deleted,
206
+ # or `nil` when not applicable
207
+ attr_reader :type
208
+
209
+ # The merge stage for this file
210
+ #
211
+ # @return [String, nil] `"0"` for normal entries, or a non-zero value during
212
+ # a merge conflict
213
+ attr_reader :stage
214
+
215
+ # The file mode recorded in the index
216
+ #
217
+ # @return [String, nil] the octal file mode (e.g. `"100644"`), or `nil`
218
+ attr_reader :mode_index
219
+
220
+ # The file mode recorded in HEAD
221
+ #
222
+ # @return [String, nil] the octal file mode (e.g. `"100644"`), or `nil`
223
+ attr_reader :mode_repo
224
+
225
+ # The SHA of the index version of this file
226
+ #
227
+ # @return [String, nil] the SHA-1 hex digest, or `nil` if unavailable
228
+ attr_reader :sha_index
229
+
230
+ # The SHA of the HEAD version of this file
231
+ #
232
+ # @return [String, nil] the SHA-1 hex digest, or `nil` if unavailable
233
+ attr_reader :sha_repo
234
+
235
+ # Whether this file is untracked
236
+ #
237
+ # @return [Boolean, nil] `true` when the file is not tracked by git
238
+ attr_reader :untracked
239
+
240
+ # Initialize a new StatusFile with the given git object and data hash
241
+ #
242
+ # @param base [Git::Base, Git::Repository] the git object
243
+ #
244
+ # @param hash [Hash] raw status data for this file
245
+ #
90
246
  def initialize(base, hash)
91
247
  @base = base
92
248
  @path = hash[:path]
@@ -99,24 +255,51 @@ module Git
99
255
  @untracked = hash[:untracked]
100
256
  end
101
257
 
102
- # Returns a Git::Object::Blob for either the index or repo version of the file.
258
+ # Return a blob object for the index or repo version of this file
259
+ #
260
+ # @param type [Symbol] `:index` (default) for the index version, or
261
+ # `:repo` for the HEAD version
262
+ #
263
+ # @return [Git::Object::Blob, nil] the blob object, or `nil` if no SHA
264
+ # is available for the requested version
265
+ #
103
266
  def blob(type = :index)
104
267
  sha = type == :repo ? sha_repo : (sha_index || sha_repo)
105
268
  @base.object(sha) if sha
106
269
  end
107
270
  end
271
+ end
272
+ end
108
273
 
274
+ module Git
275
+ class Status
109
276
  # A factory class responsible for fetching git status data and building
110
277
  # a hash of StatusFile objects.
278
+ #
111
279
  # @api private
280
+ #
112
281
  class StatusFileFactory
282
+ # Create a new factory backed by the given git object
283
+ #
284
+ # When `base` is a {Git::Repository} (which exposes `#diff_index`
285
+ # directly), it is used as the data provider. When `base` is a
286
+ # {Git::Base} (the legacy path), `base.lib` is used instead.
287
+ #
288
+ # @param base [Git::Base, Git::Repository] the git object used as the
289
+ # status data provider
290
+ #
113
291
  def initialize(base)
114
292
  @base = base
115
- @lib = base.lib
293
+ # When base is Git::Repository (which exposes #diff_index directly),
294
+ # use it as the data provider. Otherwise use base.lib (legacy path).
295
+ @provider = base.respond_to?(:diff_index) ? base : base.lib
116
296
  end
117
297
 
118
- # Gathers all status data and builds a hash of file paths to
119
- # StatusFile objects.
298
+ # Gather all status data and build a hash of file paths to StatusFile objects
299
+ #
300
+ # @return [Hash{String => Git::Status::StatusFile}] file paths mapped to
301
+ # their status objects
302
+ #
120
303
  def construct_files
121
304
  files_data = fetch_all_files_data
122
305
  files_data.transform_values do |data|
@@ -126,33 +309,56 @@ module Git
126
309
 
127
310
  private
128
311
 
129
- # Fetches and merges status information from multiple git commands.
312
+ # Fetch and merge status information from multiple git commands
313
+ #
314
+ # @return [Hash{String => Hash}] raw per-file status data keyed by path
315
+ #
130
316
  def fetch_all_files_data
131
- files = @lib.ls_files # Start with files tracked in the index.
317
+ files = @provider.ls_files # Start with files tracked in the index.
132
318
  merge_untracked_files(files)
133
319
  merge_modified_files(files)
134
320
  merge_head_diffs(files)
135
321
  files
136
322
  end
137
323
 
324
+ # Merge untracked working-tree files into `files`
325
+ #
326
+ # @param files [Hash] the in-progress files hash to update in place
327
+ #
328
+ # @return [void]
329
+ #
138
330
  def merge_untracked_files(files)
139
- @lib.untracked_files.each do |file|
331
+ @provider.untracked_files.each do |file|
140
332
  files[file] = { path: file, untracked: true }
141
333
  end
142
334
  end
143
335
 
336
+ # Merge index-versus-working-tree diff data into `files`
337
+ #
338
+ # @param files [Hash] the in-progress files hash to update in place
339
+ #
340
+ # @return [void]
341
+ #
144
342
  def merge_modified_files(files)
145
343
  # Merge changes between the index and the working directory.
146
- @lib.diff_files.each do |path, data|
344
+ @provider.diff_files.each do |path, data|
147
345
  (files[path] ||= {}).merge!(data)
148
346
  end
149
347
  end
150
348
 
349
+ # Merge HEAD-versus-index diff data into `files`, if commits exist
350
+ #
351
+ # @param files [Hash] the in-progress files hash to update in place
352
+ #
353
+ # @return [void]
354
+ #
151
355
  def merge_head_diffs(files)
152
- return if @lib.empty?
356
+ # Git::Repository exposes #no_commits?; Git::Lib exposes #empty?.
357
+ is_empty = @provider.respond_to?(:no_commits?) ? @provider.no_commits? : @provider.empty?
358
+ return if is_empty
153
359
 
154
360
  # Merge changes between HEAD and the index.
155
- @lib.diff_index('HEAD').each do |path, data|
361
+ @provider.diff_index('HEAD').each do |path, data|
156
362
  (files[path] ||= {}).merge!(data)
157
363
  end
158
364
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Represents a tag that failed to be deleted
5
+ #
6
+ # This is an immutable data object returned as part of {Git::TagDeleteResult}
7
+ # when one or more tags could not be deleted.
8
+ #
9
+ # @example
10
+ # failure = Git::TagDeleteFailure.new(
11
+ # name: 'nonexistent',
12
+ # error_message: "tag 'nonexistent' not found."
13
+ # )
14
+ # failure.name #=> 'nonexistent'
15
+ # failure.error_message #=> "tag 'nonexistent' not found."
16
+ #
17
+ # @see Git::TagDeleteResult
18
+ # @see Git::Commands::Tag::Delete
19
+ #
20
+ # @api public
21
+ #
22
+ # @!attribute [r] name
23
+ # The name of the tag that failed to be deleted
24
+ # @return [String]
25
+ #
26
+ # @!attribute [r] error_message
27
+ # The error message from git explaining why the tag could not be deleted
28
+ # @return [String]
29
+ #
30
+ TagDeleteFailure = Data.define(:name, :error_message)
31
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/tag_info'
4
+ require 'git/tag_delete_failure'
5
+
6
+ module Git
7
+ # Represents the result of a tag delete operation
8
+ #
9
+ # This is an immutable data object returned by {Git::Commands::Tag::Delete#call}.
10
+ # It contains information about which tags were successfully deleted and which
11
+ # failed to be deleted, along with the reason for each failure.
12
+ #
13
+ # Git's `git tag -d` command uses "best effort" semantics - it deletes as many
14
+ # tags as possible and reports errors for those that couldn't be deleted. This
15
+ # result object reflects that behavior, allowing callers to inspect both
16
+ # successes and failures.
17
+ #
18
+ # @example Successful deletion of all tags
19
+ # result = tag_delete.call('v1.0.0', 'v2.0.0')
20
+ # result.success? #=> true
21
+ # result.deleted.map(&:name) #=> ['v1.0.0', 'v2.0.0']
22
+ # result.not_deleted #=> []
23
+ #
24
+ # @example Partial failure (some tags deleted, some not found)
25
+ # result = tag_delete.call('v1.0.0', 'nonexistent', 'v2.0.0')
26
+ # result.success? #=> false
27
+ # result.deleted.map(&:name) #=> ['v1.0.0', 'v2.0.0']
28
+ # result.not_deleted.first.name #=> 'nonexistent'
29
+ # result.not_deleted.first.error_message #=> "tag 'nonexistent' not found."
30
+ #
31
+ # @see Git::TagInfo
32
+ # @see Git::TagDeleteFailure
33
+ # @see Git::Commands::Tag::Delete
34
+ #
35
+ # @api public
36
+ #
37
+ # @!attribute [r] deleted
38
+ # Tags that were successfully deleted
39
+ # @return [Array<Git::TagInfo>]
40
+ #
41
+ # @!attribute [r] not_deleted
42
+ # Tags that could not be deleted, with the reason for each failure
43
+ # @return [Array<Git::TagDeleteFailure>]
44
+ #
45
+ TagDeleteResult = Data.define(:deleted, :not_deleted) do
46
+ # Returns true if all requested tags were successfully deleted
47
+ #
48
+ # @return [Boolean] true if no tags failed to delete, false otherwise
49
+ #
50
+ # @example
51
+ # result = tag_delete.call('v1.0.0')
52
+ # if result.success?
53
+ # puts "All tags deleted successfully"
54
+ # else
55
+ # puts "Some tags could not be deleted:"
56
+ # result.not_deleted.each { |f| puts " #{f.name}: #{f.error_message}" }
57
+ # end
58
+ #
59
+ def success?
60
+ not_deleted.empty?
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/author'
4
+
5
+ module Git
6
+ # Value object representing tag metadata from git tag output
7
+ #
8
+ # This is a lightweight, immutable data structure returned by tag listing
9
+ # commands. It contains only the data parsed from git output without any
10
+ # repository context or operations.
11
+ #
12
+ # @example Annotated tag
13
+ # info = Git::TagInfo.new(
14
+ # name: 'v1.0.0',
15
+ # oid: 'abc123def456', # tag object's ID
16
+ # target_oid: 'def456abc789', # commit it points to
17
+ # objecttype: 'tag',
18
+ # tagger_name: 'John Doe',
19
+ # tagger_email: '<john@example.com>',
20
+ # tagger_date: '2024-01-15T10:30:00-08:00',
21
+ # message: 'Release version 1.0.0'
22
+ # )
23
+ # info.annotated? #=> true
24
+ # info.tagger.name #=> 'John Doe'
25
+ #
26
+ # @example Lightweight tag
27
+ # info = Git::TagInfo.new(
28
+ # name: 'v1.0.0',
29
+ # oid: nil, # no tag object exists
30
+ # target_oid: 'def456abc789', # commit ID
31
+ # objecttype: 'commit',
32
+ # tagger_name: nil,
33
+ # tagger_email: nil,
34
+ # tagger_date: nil,
35
+ # message: nil
36
+ # )
37
+ # info.lightweight? #=> true
38
+ # info.tagger #=> nil
39
+ #
40
+ # @see Git::Tag for the full-featured tag object with operations
41
+ # @see Git::Commands::Tag::List for the command that produces these
42
+ #
43
+ # @api public
44
+ #
45
+ # @!attribute [r] name
46
+ # @return [String] the tag name (e.g., 'v1.0.0')
47
+ #
48
+ # @!attribute [r] oid
49
+ # The object ID of the tag object itself.
50
+ #
51
+ # For annotated tags, this is the tag object's ID. For lightweight tags,
52
+ # this is nil because lightweight tags are not objects in the git database.
53
+ #
54
+ # @return [String, nil] the tag object's ID, or nil for lightweight tags
55
+ #
56
+ # @!attribute [r] target_oid
57
+ # The object ID of the commit this tag ultimately points to.
58
+ #
59
+ # For both annotated and lightweight tags, this is the commit ID that the
60
+ # tag resolves to (i.e., the dereferenced target).
61
+ #
62
+ # @return [String] the commit ID this tag points to
63
+ #
64
+ # @!attribute [r] objecttype
65
+ # @return [String] 'tag' for annotated tags, 'commit' for lightweight tags
66
+ #
67
+ # @!attribute [r] tagger_name
68
+ # @return [String, nil] the tagger's name, or nil for lightweight tags
69
+ #
70
+ # @!attribute [r] tagger_email
71
+ # @return [String, nil] the tagger's email, or nil for lightweight tags
72
+ #
73
+ # @!attribute [r] tagger_date
74
+ # @return [String, nil] the tag date in ISO 8601 format, or nil for lightweight tags
75
+ #
76
+ # @!attribute [r] message
77
+ # @return [String, nil] the tag message, or nil for lightweight tags
78
+ #
79
+ TagInfo = Data.define(:name, :oid, :target_oid, :objecttype, :tagger_name, :tagger_email, :tagger_date, :message) do
80
+ # @return [Boolean] true if this is an annotated tag (oid is present)
81
+ def annotated?
82
+ !oid.nil?
83
+ end
84
+
85
+ # @return [Boolean] true if this is a lightweight tag (oid is nil)
86
+ def lightweight?
87
+ oid.nil?
88
+ end
89
+
90
+ # Return the tagger as an Author object
91
+ #
92
+ # @return [Git::Author, nil] the tagger as an Author object, or nil for lightweight tags
93
+ def tagger
94
+ return nil unless annotated? && tagger_name && tagger_email
95
+
96
+ # Git::Author expects format "Name <email> timestamp timezone"
97
+ # We construct a minimal format that will parse correctly
98
+ author = Git::Author.new('')
99
+ author.name = tagger_name
100
+ # Remove angle brackets if present
101
+ author.email = tagger_email.gsub(/\A<|>\z/, '')
102
+ author
103
+ end
104
+ end
105
+ end
data/lib/git/version.rb CHANGED
@@ -2,6 +2,113 @@
2
2
 
3
3
  module Git
4
4
  # The current gem version
5
- # @return [String] the current gem version.
6
- VERSION = '4.3.2'
5
+ #
6
+ # @return [String] the current gem version
7
+ VERSION = '5.0.0.beta.1'
8
+
9
+ # Represents a git version with major, minor, and patch components
10
+ #
11
+ # Git versions follow a strict major.minor.patch format. This class provides
12
+ # parsing from git command output (which may include platform suffixes) and
13
+ # comparison operations for version gating.
14
+ #
15
+ # @!attribute [r] major
16
+ # The major version number
17
+ # @return [Integer]
18
+ #
19
+ # @!attribute [r] minor
20
+ # The minor version number
21
+ # @return [Integer]
22
+ #
23
+ # @!attribute [r] patch
24
+ # The patch version number
25
+ # @return [Integer]
26
+ #
27
+ # @example Creating a version directly
28
+ # version = Git::Version.new(2, 42, 1)
29
+ # version.to_s #=> "2.42.1"
30
+ #
31
+ # @example Parsing from git version output
32
+ # Git::Version.parse('git version 2.42.1') #=> Git::Version.new(2, 42, 1)
33
+ # Git::Version.parse('2.39.2 (Apple Git-143)') #=> Git::Version.new(2, 39, 2)
34
+ #
35
+ # @example Parsing versions with platform suffixes
36
+ # Git::Version.parse('2.42.0.windows.1') #=> Git::Version.new(2, 42, 0)
37
+ #
38
+ # @example Comparing versions
39
+ # Git::Version.new(2, 42, 1) > Git::Version.new(2, 28, 0) #=> true
40
+ #
41
+ # @api public
42
+ #
43
+ Version = Data.define(:major, :minor, :patch) do
44
+ include Comparable
45
+
46
+ # Parse a version string into a Version object
47
+ #
48
+ # Handles git's version output format, stripping platform suffixes
49
+ # (like `.windows.1` or `.vfs.0`) and padding two-segment versions
50
+ # to three segments.
51
+ #
52
+ # @example Parse various version string formats
53
+ # Git::Version.parse('2.42.1') #=> Git::Version.new(2, 42, 1)
54
+ # Git::Version.parse('git version 2.42.1') #=> Git::Version.new(2, 42, 1)
55
+ # Git::Version.parse('2.42.0.windows.1') #=> Git::Version.new(2, 42, 0)
56
+ #
57
+ # @param string [String] version string to parse
58
+ #
59
+ # @return [Git::Version] the parsed version
60
+ #
61
+ # @raise [Git::UnexpectedResultError] if the string cannot be parsed as a version
62
+ #
63
+ def self.parse(string)
64
+ version_match = string&.match(/(\d+)\.(\d+)(?:\.(\d+))?/)
65
+ raise Git::UnexpectedResultError, "Invalid version: #{string.inspect}" unless version_match
66
+
67
+ major = version_match[1].to_i
68
+ minor = version_match[2].to_i
69
+ patch = (version_match[3] || '0').to_i
70
+
71
+ new(major, minor, patch)
72
+ end
73
+
74
+ # Compare this version to another
75
+ #
76
+ # @param other [Git::Version] the version to compare to
77
+ #
78
+ # @return [Integer] -1, 0, or 1
79
+ #
80
+ def <=>(other)
81
+ [major, minor, patch] <=> [other.major, other.minor, other.patch]
82
+ end
83
+
84
+ # Return the version as a dotted string
85
+ #
86
+ # @return [String] the version in "major.minor.patch" format
87
+ #
88
+ def to_s
89
+ "#{major}.#{minor}.#{patch}"
90
+ end
91
+
92
+ # Return a readable representation
93
+ #
94
+ # @return [String] inspect string
95
+ #
96
+ def inspect
97
+ "#<Git::Version #{self}>"
98
+ end
99
+
100
+ # Return the version as an array of integers
101
+ #
102
+ # Useful when legacy code expects the array shape returned by the
103
+ # deprecated {Git::Lib#current_command_version} method.
104
+ #
105
+ # @return [Array<Integer>] [major, minor, patch]
106
+ #
107
+ # @example
108
+ # Git.git_version.to_a #=> [2, 42, 0]
109
+ #
110
+ def to_a
111
+ deconstruct
112
+ end
113
+ end
7
114
  end