git 1.19.1 → 5.5.0

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 (265) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +5 -1
  3. data/AI_POLICY.md +24 -0
  4. data/CHANGELOG.md +689 -0
  5. data/CODE_OF_CONDUCT.md +25 -0
  6. data/CONTRIBUTING.md +1175 -97
  7. data/GOVERNANCE.md +106 -0
  8. data/LICENSE +1 -1
  9. data/MAINTAINERS.md +17 -4
  10. data/README.md +476 -320
  11. data/UPGRADING.md +1138 -0
  12. data/git.gemspec +124 -36
  13. data/lib/git/author.rb +39 -7
  14. data/lib/git/author_info.rb +66 -0
  15. data/lib/git/branch.rb +615 -65
  16. data/lib/git/branch_delete_failure.rb +34 -0
  17. data/lib/git/branch_delete_result.rb +66 -0
  18. data/lib/git/branch_info.rb +237 -0
  19. data/lib/git/branches.rb +167 -44
  20. data/lib/git/command_line/base.rb +247 -0
  21. data/lib/git/command_line/capturing.rb +308 -0
  22. data/lib/git/command_line/result.rb +88 -0
  23. data/lib/git/command_line/streaming.rb +236 -0
  24. data/lib/git/command_line.rb +52 -0
  25. data/lib/git/commands/add.rb +139 -0
  26. data/lib/git/commands/am/abort.rb +43 -0
  27. data/lib/git/commands/am/apply.rb +263 -0
  28. data/lib/git/commands/am/continue.rb +43 -0
  29. data/lib/git/commands/am/quit.rb +43 -0
  30. data/lib/git/commands/am/retry.rb +49 -0
  31. data/lib/git/commands/am/show_current_patch.rb +64 -0
  32. data/lib/git/commands/am/skip.rb +42 -0
  33. data/lib/git/commands/am.rb +33 -0
  34. data/lib/git/commands/apply.rb +242 -0
  35. data/lib/git/commands/archive/list_formats.rb +46 -0
  36. data/lib/git/commands/archive.rb +145 -0
  37. data/lib/git/commands/arguments.rb +4521 -0
  38. data/lib/git/commands/base.rb +502 -0
  39. data/lib/git/commands/branch/copy.rb +102 -0
  40. data/lib/git/commands/branch/create.rb +177 -0
  41. data/lib/git/commands/branch/delete.rb +88 -0
  42. data/lib/git/commands/branch/list.rb +178 -0
  43. data/lib/git/commands/branch/move.rb +102 -0
  44. data/lib/git/commands/branch/set_upstream.rb +86 -0
  45. data/lib/git/commands/branch/show_current.rb +49 -0
  46. data/lib/git/commands/branch/unset_upstream.rb +53 -0
  47. data/lib/git/commands/branch.rb +34 -0
  48. data/lib/git/commands/cat_file/batch.rb +385 -0
  49. data/lib/git/commands/cat_file/filtered.rb +105 -0
  50. data/lib/git/commands/cat_file/raw.rb +271 -0
  51. data/lib/git/commands/cat_file.rb +49 -0
  52. data/lib/git/commands/checkout/branch.rb +153 -0
  53. data/lib/git/commands/checkout/files.rb +116 -0
  54. data/lib/git/commands/checkout.rb +38 -0
  55. data/lib/git/commands/checkout_index.rb +106 -0
  56. data/lib/git/commands/clean.rb +102 -0
  57. data/lib/git/commands/clone.rb +241 -0
  58. data/lib/git/commands/commit.rb +273 -0
  59. data/lib/git/commands/commit_tree.rb +101 -0
  60. data/lib/git/commands/config_option_syntax/add.rb +86 -0
  61. data/lib/git/commands/config_option_syntax/get.rb +121 -0
  62. data/lib/git/commands/config_option_syntax/get_all.rb +118 -0
  63. data/lib/git/commands/config_option_syntax/get_color.rb +95 -0
  64. data/lib/git/commands/config_option_syntax/get_color_bool.rb +96 -0
  65. data/lib/git/commands/config_option_syntax/get_regexp.rb +119 -0
  66. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +111 -0
  67. data/lib/git/commands/config_option_syntax/list.rb +111 -0
  68. data/lib/git/commands/config_option_syntax/remove_section.rb +79 -0
  69. data/lib/git/commands/config_option_syntax/rename_section.rb +83 -0
  70. data/lib/git/commands/config_option_syntax/replace_all.rb +109 -0
  71. data/lib/git/commands/config_option_syntax/set.rb +119 -0
  72. data/lib/git/commands/config_option_syntax/unset.rb +92 -0
  73. data/lib/git/commands/config_option_syntax/unset_all.rb +94 -0
  74. data/lib/git/commands/config_option_syntax.rb +56 -0
  75. data/lib/git/commands/describe.rb +156 -0
  76. data/lib/git/commands/diff.rb +657 -0
  77. data/lib/git/commands/diff_files.rb +519 -0
  78. data/lib/git/commands/diff_index.rb +499 -0
  79. data/lib/git/commands/fetch.rb +354 -0
  80. data/lib/git/commands/fsck.rb +138 -0
  81. data/lib/git/commands/gc.rb +134 -0
  82. data/lib/git/commands/grep.rb +339 -0
  83. data/lib/git/commands/init.rb +101 -0
  84. data/lib/git/commands/log.rb +634 -0
  85. data/lib/git/commands/ls_files.rb +195 -0
  86. data/lib/git/commands/ls_remote.rb +161 -0
  87. data/lib/git/commands/ls_tree.rb +135 -0
  88. data/lib/git/commands/maintenance/register.rb +77 -0
  89. data/lib/git/commands/maintenance/run.rb +109 -0
  90. data/lib/git/commands/maintenance/start.rb +71 -0
  91. data/lib/git/commands/maintenance/stop.rb +60 -0
  92. data/lib/git/commands/maintenance/unregister.rb +84 -0
  93. data/lib/git/commands/maintenance.rb +31 -0
  94. data/lib/git/commands/merge/abort.rb +44 -0
  95. data/lib/git/commands/merge/continue.rb +44 -0
  96. data/lib/git/commands/merge/quit.rb +46 -0
  97. data/lib/git/commands/merge/start.rb +250 -0
  98. data/lib/git/commands/merge.rb +28 -0
  99. data/lib/git/commands/merge_base.rb +91 -0
  100. data/lib/git/commands/mv.rb +82 -0
  101. data/lib/git/commands/name_rev.rb +119 -0
  102. data/lib/git/commands/pull.rb +382 -0
  103. data/lib/git/commands/push.rb +251 -0
  104. data/lib/git/commands/read_tree.rb +154 -0
  105. data/lib/git/commands/remote/add.rb +96 -0
  106. data/lib/git/commands/remote/get_url.rb +68 -0
  107. data/lib/git/commands/remote/list.rb +56 -0
  108. data/lib/git/commands/remote/prune.rb +63 -0
  109. data/lib/git/commands/remote/remove.rb +52 -0
  110. data/lib/git/commands/remote/rename.rb +76 -0
  111. data/lib/git/commands/remote/set_branches.rb +70 -0
  112. data/lib/git/commands/remote/set_head.rb +89 -0
  113. data/lib/git/commands/remote/set_url.rb +78 -0
  114. data/lib/git/commands/remote/set_url_add.rb +70 -0
  115. data/lib/git/commands/remote/set_url_delete.rb +71 -0
  116. data/lib/git/commands/remote/show.rb +77 -0
  117. data/lib/git/commands/remote/update.rb +79 -0
  118. data/lib/git/commands/remote.rb +42 -0
  119. data/lib/git/commands/repack.rb +281 -0
  120. data/lib/git/commands/reset.rb +154 -0
  121. data/lib/git/commands/rev_parse.rb +304 -0
  122. data/lib/git/commands/revert/abort.rb +45 -0
  123. data/lib/git/commands/revert/continue.rb +62 -0
  124. data/lib/git/commands/revert/quit.rb +47 -0
  125. data/lib/git/commands/revert/skip.rb +44 -0
  126. data/lib/git/commands/revert/start.rb +158 -0
  127. data/lib/git/commands/revert.rb +29 -0
  128. data/lib/git/commands/rm.rb +113 -0
  129. data/lib/git/commands/show.rb +632 -0
  130. data/lib/git/commands/show_ref/exclude_existing.rb +119 -0
  131. data/lib/git/commands/show_ref/exists.rb +80 -0
  132. data/lib/git/commands/show_ref/list.rb +149 -0
  133. data/lib/git/commands/show_ref/verify.rb +122 -0
  134. data/lib/git/commands/show_ref.rb +42 -0
  135. data/lib/git/commands/stash/apply.rb +81 -0
  136. data/lib/git/commands/stash/branch.rb +67 -0
  137. data/lib/git/commands/stash/clear.rb +43 -0
  138. data/lib/git/commands/stash/create.rb +60 -0
  139. data/lib/git/commands/stash/drop.rb +73 -0
  140. data/lib/git/commands/stash/list.rb +43 -0
  141. data/lib/git/commands/stash/pop.rb +87 -0
  142. data/lib/git/commands/stash/push.rb +112 -0
  143. data/lib/git/commands/stash/show.rb +158 -0
  144. data/lib/git/commands/stash/store.rb +72 -0
  145. data/lib/git/commands/stash.rb +38 -0
  146. data/lib/git/commands/status.rb +174 -0
  147. data/lib/git/commands/symbolic_ref/delete.rb +72 -0
  148. data/lib/git/commands/symbolic_ref/read.rb +99 -0
  149. data/lib/git/commands/symbolic_ref/update.rb +79 -0
  150. data/lib/git/commands/symbolic_ref.rb +38 -0
  151. data/lib/git/commands/tag/create.rb +142 -0
  152. data/lib/git/commands/tag/delete.rb +57 -0
  153. data/lib/git/commands/tag/list.rb +146 -0
  154. data/lib/git/commands/tag/verify.rb +71 -0
  155. data/lib/git/commands/tag.rb +26 -0
  156. data/lib/git/commands/update_ref/batch.rb +145 -0
  157. data/lib/git/commands/update_ref/delete.rb +90 -0
  158. data/lib/git/commands/update_ref/update.rb +103 -0
  159. data/lib/git/commands/update_ref.rb +42 -0
  160. data/lib/git/commands/version.rb +60 -0
  161. data/lib/git/commands/worktree/add.rb +139 -0
  162. data/lib/git/commands/worktree/list.rb +64 -0
  163. data/lib/git/commands/worktree/lock.rb +58 -0
  164. data/lib/git/commands/worktree/management_base.rb +51 -0
  165. data/lib/git/commands/worktree/move.rb +66 -0
  166. data/lib/git/commands/worktree/prune.rb +67 -0
  167. data/lib/git/commands/worktree/remove.rb +63 -0
  168. data/lib/git/commands/worktree/repair.rb +76 -0
  169. data/lib/git/commands/worktree/unlock.rb +47 -0
  170. data/lib/git/commands/worktree.rb +43 -0
  171. data/lib/git/commands/write_tree.rb +68 -0
  172. data/lib/git/commands.rb +88 -0
  173. data/lib/git/config.rb +72 -5
  174. data/lib/git/config_entry_info.rb +106 -0
  175. data/lib/git/configuring.rb +795 -0
  176. data/lib/git/detached_head_info.rb +57 -0
  177. data/lib/git/diff.rb +437 -86
  178. data/lib/git/diff_file_numstat_info.rb +31 -0
  179. data/lib/git/diff_file_patch_info.rb +136 -0
  180. data/lib/git/diff_file_raw_info.rb +129 -0
  181. data/lib/git/diff_info.rb +162 -0
  182. data/lib/git/diff_path_status.rb +107 -0
  183. data/lib/git/diff_result.rb +34 -0
  184. data/lib/git/diff_stats.rb +111 -0
  185. data/lib/git/dirstat_info.rb +102 -0
  186. data/lib/git/encoding_utils.rb +32 -1
  187. data/lib/git/errors.rb +285 -0
  188. data/lib/git/escaped_path.rb +57 -5
  189. data/lib/git/execution_context/global.rb +31 -0
  190. data/lib/git/execution_context/repository.rb +151 -0
  191. data/lib/git/execution_context.rb +559 -0
  192. data/lib/git/factories.rb +813 -0
  193. data/lib/git/file_ref.rb +77 -0
  194. data/lib/git/fsck_object.rb +56 -0
  195. data/lib/git/fsck_result.rb +132 -0
  196. data/lib/git/log.rb +306 -90
  197. data/lib/git/object.rb +563 -141
  198. data/lib/git/parsers/branch.rb +240 -0
  199. data/lib/git/parsers/cat_file.rb +111 -0
  200. data/lib/git/parsers/config_entry.rb +110 -0
  201. data/lib/git/parsers/diff.rb +792 -0
  202. data/lib/git/parsers/fsck.rb +144 -0
  203. data/lib/git/parsers/grep.rb +42 -0
  204. data/lib/git/parsers/ls_remote.rb +79 -0
  205. data/lib/git/parsers/ls_tree.rb +58 -0
  206. data/lib/git/parsers/remote.rb +162 -0
  207. data/lib/git/parsers/stash.rb +292 -0
  208. data/lib/git/parsers/status.rb +251 -0
  209. data/lib/git/parsers/tag.rb +341 -0
  210. data/lib/git/parsers/worktree.rb +185 -0
  211. data/lib/git/path_resolver.rb +206 -0
  212. data/lib/git/remote.rb +165 -12
  213. data/lib/git/remote_info.rb +203 -0
  214. data/lib/git/repository/branching.rb +964 -0
  215. data/lib/git/repository/committing.rb +246 -0
  216. data/lib/git/repository/context_helpers.rb +293 -0
  217. data/lib/git/repository/diffing.rb +785 -0
  218. data/lib/git/repository/inspecting.rb +252 -0
  219. data/lib/git/repository/logging.rb +410 -0
  220. data/lib/git/repository/maintenance.rb +65 -0
  221. data/lib/git/repository/merging.rb +451 -0
  222. data/lib/git/repository/object_operations.rb +1551 -0
  223. data/lib/git/repository/remote_operations.rb +984 -0
  224. data/lib/git/repository/shared_private.rb +120 -0
  225. data/lib/git/repository/staging.rb +587 -0
  226. data/lib/git/repository/stashing.rb +623 -0
  227. data/lib/git/repository/status_operations.rb +249 -0
  228. data/lib/git/repository/worktree_operations.rb +339 -0
  229. data/lib/git/repository.rb +484 -2
  230. data/lib/git/stash.rb +109 -12
  231. data/lib/git/stash_info.rb +102 -0
  232. data/lib/git/stashes.rb +169 -26
  233. data/lib/git/status.rb +308 -122
  234. data/lib/git/status_file_info.rb +258 -0
  235. data/lib/git/status_info.rb +189 -0
  236. data/lib/git/tag_delete_failure.rb +34 -0
  237. data/lib/git/tag_delete_result.rb +66 -0
  238. data/lib/git/tag_info.rb +99 -0
  239. data/lib/git/url.rb +15 -8
  240. data/lib/git/version.rb +113 -2
  241. data/lib/git/version_constraint.rb +85 -0
  242. data/lib/git/worktree.rb +150 -8
  243. data/lib/git/worktree_info.rb +128 -0
  244. data/lib/git/worktrees.rb +118 -13
  245. data/lib/git.rb +632 -234
  246. metadata +369 -54
  247. data/.github/stale.yml +0 -25
  248. data/.github/workflows/continuous_integration.yml +0 -49
  249. data/.gitignore +0 -10
  250. data/Dockerfile.changelog-rs +0 -12
  251. data/Gemfile +0 -5
  252. data/ISSUE_TEMPLATE.md +0 -15
  253. data/PULL_REQUEST_TEMPLATE.md +0 -9
  254. data/RELEASING.md +0 -70
  255. data/Rakefile +0 -60
  256. data/lib/git/base/factory.rb +0 -99
  257. data/lib/git/base.rb +0 -711
  258. data/lib/git/command_line_result.rb +0 -86
  259. data/lib/git/failed_error.rb +0 -53
  260. data/lib/git/git_execute_error.rb +0 -7
  261. data/lib/git/index.rb +0 -5
  262. data/lib/git/lib.rb +0 -1328
  263. data/lib/git/path.rb +0 -31
  264. data/lib/git/signaled_error.rb +0 -50
  265. data/lib/git/working_directory.rb +0 -4
data/UPGRADING.md ADDED
@@ -0,0 +1,1138 @@
1
+ # Upgrading the `git` Gem
2
+
3
+ This document covers breaking changes and migration steps when upgrading the
4
+ `git` gem to a new major version. Each section describes what changed and how
5
+ to update your code when upgrading from the preceding major version.
6
+
7
+ - [Upgrading to v6.0.0](#upgrading-to-v600)
8
+ - [Upgrading to v5.x](#upgrading-to-v5x)
9
+ - [Overview](#overview)
10
+ - [Breaking changes](#breaking-changes)
11
+ - [`Git::Base` removed](#gitbase-removed)
12
+ - [Return type of `Git.open`, `Git.clone`, `Git.init`, `Git.bare`](#return-type-of-gitopen-gitclone-gitinit-gitbare)
13
+ - [Unsupported options raise `ArgumentError`](#unsupported-options-raise-argumenterror)
14
+ - [`Git::Lib` removed](#gitlib-removed)
15
+ - [`Git::Log#object` is not a path limiter](#gitlogobject-is-not-a-path-limiter)
16
+ - [`Git::CommandLineResult` deprecated](#gitcommandlineresult-deprecated)
17
+ - [Deprecated methods](#deprecated-methods)
18
+ - [Facade method renames](#facade-method-renames)
19
+ - [`Git::Repository` method renames](#gitrepository-method-renames)
20
+ - [`Git::Repository` option renames](#gitrepository-option-renames)
21
+ - [v4.x-style configuration methods](#v4x-style-configuration-methods)
22
+ - [`Git` module mixin deprecations](#git-module-mixin-deprecations)
23
+ - [Module-level `Git` function deprecations](#module-level-git-function-deprecations)
24
+ - [`Git::Author` deprecated](#gitauthor-deprecated)
25
+ - [`Git::Branch#stashes` deprecated](#gitbranchstashes-deprecated)
26
+ - [Legacy stash API deprecated](#legacy-stash-api-deprecated)
27
+ - [`Git::Repository#remotes` deprecated](#gitrepositoryremotes-deprecated)
28
+ - [`Git::Remote` deprecated](#gitremote-deprecated)
29
+ - [`Git::Commands::CatFile::Raw` `allow_unknown_type` option deprecated](#gitcommandscatfileraw-allow_unknown_type-option-deprecated)
30
+ - [`Git::Branch` and `Git::Branches` deprecated](#gitbranch-and-gitbranches-deprecated)
31
+ - [`Git::Object::Tag` deprecated](#gitobjecttag-deprecated)
32
+ - [`Git::Status` deprecated](#gitstatus-deprecated)
33
+ - [`Git::Worktree` and `Git::Worktrees` deprecated](#gitworktree-and-gitworktrees-deprecated)
34
+ - [`Git.clone` option renames](#gitclone-option-renames)
35
+ - [`Git::Log` Enumerable interface deprecated](#gitlog-enumerable-interface-deprecated)
36
+ - [`Git::Object::Commit#set_commit` deprecated](#gitobjectcommitset_commit-deprecated)
37
+ - [`Git.export` `:remote` option deprecated](#gitexport-remote-option-deprecated)
38
+
39
+ ## Upgrading to v6.0.0
40
+
41
+ v6.0.0 is not yet released. This section will be completed when it ships.
42
+
43
+ v6.0.0 removes the APIs deprecated during v5.x under the project's
44
+ [deprecation policy](README.md#deprecation-policy).
45
+ [Issue 1717](https://github.com/ruby-git/ruby-git/issues/1717) tracks its scope.
46
+
47
+ To prepare:
48
+
49
+ 1. Upgrade to the latest v5.x release.
50
+ 2. Set `GIT_DEPRECATION_BEHAVIOR=raise` (or `Git::Deprecation.behavior = :raise`) in
51
+ your test suite and, if possible, staging.
52
+ 3. Fix each deprecation using the entries under
53
+ [Deprecated methods](#deprecated-methods) until the suite is clean.
54
+ 4. Upgrade to v6.0.0.
55
+
56
+ ---
57
+
58
+ ## Upgrading to v5.x
59
+
60
+ ### Overview
61
+
62
+ v5.0.0 delivers a new internal architecture while keeping the v4.x API working
63
+ for the vast majority of users. Most v4.x code requires **no changes** to run on
64
+ v5.x.
65
+
66
+ The new architecture introduces a layered design (`Git::Commands`,
67
+ `Git::Repository`, and associated parsers). Compatibility shims — deprecated
68
+ forwarding methods that map old call patterns to the new API — ensure that v4.x
69
+ code continues to work. These shims emit deprecation warnings that tell you
70
+ exactly what to change and what will be eliminated in v6.0.0.
71
+
72
+ Hard breaks are limited to a small number of things that had no safe migration
73
+ path. These are described in the [Breaking changes](#breaking-changes) section,
74
+ followed by [Deprecated methods](#deprecated-methods) that still work in v5.x
75
+ but are removed in v6.0.0.
76
+
77
+ For information on how to suppress or configure deprecation warnings, see the
78
+ [Deprecations](README.md#deprecations) section of the README.
79
+
80
+ **Changes at a glance:**
81
+
82
+ | Change | Type | Impact | Action required |
83
+ |--------|------|--------|-----------------|
84
+ | `Git::Base` removed | Hard break | High for code that references it by name | Replace with `Git::Repository` (returned by `Git.open` etc.) |
85
+ | `Git::Lib` removed | Hard break | High for `.lib.*` callers | Use the equivalent method directly on the repo object (see table below) |
86
+ | `Git.open` etc. return `Git::Repository` (not `Git::Base`) | Hard break | Low for most callers; breaks `is_a?(Git::Base)` | Update type checks and update `be_a(Git::Base)` in tests |
87
+ | Unsupported options now raise `ArgumentError` | Behavior change | Medium for code passing unknown or misspelled options | Check option names against the documented API |
88
+ | `Git::Log#object` is not a path limiter | Behavior change | Medium for code that used `object(path)` to filter logs by path | Use `Git::Log#path` for path filtering |
89
+ | `Git::CommandLineResult` deprecated | Deprecation (removed in v6.0.0) | Low; only affects code that references the constant by name | Use `Git::CommandLine::Result` instead |
90
+
91
+ ---
92
+
93
+ ### Breaking changes
94
+
95
+ #### `Git::Base` removed
96
+
97
+ `Git::Base` — the class previously returned by `Git.open`, `Git.clone`,
98
+ `Git.init`, and `Git.bare` — is removed in v5.0.0. The replacement is
99
+ `Git::Repository`, which is returned by all four entry points and exposes the
100
+ same public API.
101
+
102
+ **Code that must be updated:**
103
+
104
+ ```ruby
105
+ # v4.x — explicit Git::Base reference (raises NameError in v5.x)
106
+ repo = Git::Base.new(working_directory: '/path/to/repo')
107
+
108
+ # v5.x — use the entry-point methods; do not construct Git::Repository directly
109
+ repo = Git.open('/path/to/repo')
110
+ ```
111
+
112
+ ```ruby
113
+ # v4.x — type-checking against Git::Base (raises NameError in v5.x because Git::Base is removed)
114
+ raise unless repo.is_a?(Git::Base)
115
+
116
+ # v5.x — check against Git::Repository
117
+ raise unless repo.is_a?(Git::Repository)
118
+ ```
119
+
120
+ ```ruby
121
+ # v4.x — requiring the internal file (raises LoadError in v5.x)
122
+ require 'git/base'
123
+
124
+ # v5.x — the public entry point is git itself; no internal require needed
125
+ require 'git'
126
+ ```
127
+
128
+ **Public API is preserved:** `Git::Repository` provides every method that
129
+ `Git::Base` did. Code that simply calls methods on the object returned by
130
+ `Git.open` (e.g., `repo.commit`, `repo.status`, `repo.add`) requires no
131
+ changes.
132
+
133
+ **Monkeypatching `Git::Base` is deprecated:** v5.x includes a temporary
134
+ compatibility shim for applications that define instance methods on `Git::Base`.
135
+ Those methods are made available on `Git::Repository` instances, but each method
136
+ definition emits a deprecation warning and this shim will be removed in v6.0.0.
137
+
138
+ Move custom repository helpers to an application-owned extension module and
139
+ include or prepend that module into `Git::Repository` during application setup:
140
+
141
+ ```ruby
142
+ # Deprecated in v5.x and will be removed in v6.0.0
143
+ module Git::Base
144
+ def worktree_clean?
145
+ status.changed.empty?
146
+ end
147
+ end
148
+
149
+ # v5.x — keep the extension in application-owned code
150
+ module MyAppGitRepositoryExtensions
151
+ def worktree_clean?
152
+ status.changed.empty?
153
+ end
154
+ end
155
+
156
+ Git::Repository.include(MyAppGitRepositoryExtensions)
157
+ ```
158
+
159
+ ---
160
+
161
+ #### Return type of `Git.open`, `Git.clone`, `Git.init`, `Git.bare`
162
+
163
+ `Git.open`, `Git.clone`, `Git.init`, and `Git.bare` now return
164
+ `Git::Repository` instead of `Git::Base`.
165
+
166
+ For most callers this is transparent — the returned object responds to the same
167
+ methods. Code that explicitly checks `is_a?(Git::Base)` or `be_a(Git::Base)` in
168
+ tests must be updated:
169
+
170
+ ```ruby
171
+ # v4.x
172
+ expect(Git.open(repo_path)).to be_a(Git::Base)
173
+
174
+ # v5.x
175
+ expect(Git.open(repo_path)).to be_a(Git::Repository)
176
+ ```
177
+
178
+ `Git::Repository` does not define `.open`, `.bare`, `.clone`, or `.init` class
179
+ methods. Always use `Git.open`, `Git.bare`, `Git.clone`, and `Git.init` to
180
+ construct a repository object.
181
+
182
+ ---
183
+
184
+ #### Unsupported options raise `ArgumentError`
185
+
186
+ v5.x validates options more strictly for factory methods and command APIs.
187
+ Unknown options that were silently ignored in v4.x may now raise
188
+ `ArgumentError`. Check option names against the documented API when upgrading,
189
+ especially for calls that pass keyword options through helper methods or shared
190
+ option hashes.
191
+
192
+ For example, `Git.clone` supports `log:`, not `logger:`. A misspelled or
193
+ unsupported option that v4.x ignored must be corrected:
194
+
195
+ ```ruby
196
+ # v4.x — silently ignored; did not configure clone logging
197
+ Git.clone(url, path, logger: logger)
198
+
199
+ # v5.x — use the documented option name
200
+ Git.clone(url, path, log: logger)
201
+ ```
202
+
203
+ ---
204
+
205
+ #### `Git::Lib` removed
206
+
207
+ The object returned by `Git.open`, `Git.clone`, `Git.init`, and `Git.bare` previously
208
+ exposed a `#lib` method that gave access to `Git::Lib`, the gem's internal
209
+ implementation class. `Git::Lib` is removed in v5.0.0.
210
+
211
+ In v5.x, calling `#lib` on a repo object returns `self` with a deprecation
212
+ warning. This means `g.lib.some_method(args)` is forwarded to
213
+ `g.some_method(args)` — but only if `some_method` exists on `Git::Repository`.
214
+ Methods that were unique to `Git::Lib` and have no counterpart on
215
+ `Git::Repository` raise `NoMethodError` immediately. The `#lib` method itself
216
+ is removed in v6.0.0.
217
+
218
+ Most public behavior previously accessible via `g.lib.*` is available directly
219
+ on the repository object (`g.*`). See the tables below for every affected
220
+ method.
221
+
222
+ ##### Methods that work via the `#lib` shim (with deprecation warning)
223
+
224
+ The following v4.x `g.lib.*` call shapes are forwarded to their `Git::Repository`
225
+ counterpart by the `#lib → self` shim. They emit a deprecation warning; migrate
226
+ to the replacement shown to silence it.
227
+
228
+ > **Note — config return type change:** `g.lib.config_get(name)` returned a
229
+ > `String`; `g.lib.config_list` returned a `Hash`.
230
+ > The v5.x replacements `config_get` and `config_list` return
231
+ > `Git::ConfigEntryInfo` and `Array<Git::ConfigEntryInfo>` respectively — richer
232
+ > objects that expose `.value` (the String), `.key`, `.scope`, and `.origin`.
233
+ >
234
+ > If you only need the String value:
235
+ > - `g.config_get(name)&.value` → replaces `g.lib.config_get(name)`
236
+ > - `g.config_list.to_h { |e| [e.key, e.value] }` → replaces `g.lib.config_list`
237
+ >
238
+ > If your code was using the v4.x public `g.config(name)` API (not `g.lib.*`),
239
+ > that deprecated bridge still returns a `String` in v5.x and continues to work
240
+ > until v6.0.0.
241
+
242
+ | v4.x call | Replacement in v5.x |
243
+ |-----------|---------------------|
244
+ | `g.lib.config_get(name)` | `g.config_get(name)` — returns `Git::ConfigEntryInfo`; use `.value` for the String |
245
+ | `g.lib.config_list` | `g.config_list` — returns `Array<Git::ConfigEntryInfo>` |
246
+ | `g.lib.config_set(name, value)` | `g.config_set(name, value)` |
247
+ | `g.lib.git_version` | `g.git_version` |
248
+ | `g.lib.stash_list` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
249
+ | `g.lib.unmerged` | `g.unmerged` |
250
+ | `g.lib.change_head_branch(name)` | `g.change_head_branch(name)` |
251
+ | `g.lib.ls_remote(location, opts)` | `g.ls_remote(location, opts)` |
252
+ | `g.lib.current_branch_state` | `g.current_branch_state` |
253
+
254
+ > **Note — `current_branch_state` return type change:** `g.lib.current_branch_state`
255
+ > returned a `Git::Lib::HeadState` (a mutable `Struct`). `g.current_branch_state`
256
+ > returns a `Git::Repository::Branching::HeadState` (an immutable `Data` object).
257
+ > Both expose `.state` (`:active`, `:unborn`, or `:detached`) and `.name`. If your
258
+ > code relies on the struct being mutable or uses positional construction
259
+ > (`Git::Lib::HeadState.new(:active, 'main')`), update to keyword construction:
260
+ > `Git::Repository::Branching::HeadState.new(state: :active, name: 'main')`.
261
+
262
+ ##### Methods that raise `NoMethodError` in v5.x
263
+
264
+ These `Git::Lib` method names have no counterpart on `Git::Repository`, so
265
+ `g.lib.method_name` raises `NoMethodError` even in v5.x (the `#lib → self`
266
+ shim cannot forward them). Update call sites directly:
267
+
268
+ | v4.x call | Replacement in v5.x |
269
+ |-----------|---------------------|
270
+ | `g.lib.global_config_get(name)` | `g.config_get(name, global: true)` |
271
+ | `g.lib.global_config_list` | `g.config_list(global: true)` |
272
+ | `g.lib.global_config_set(name, value)` | `g.config_set(name, value, global: true)` |
273
+ | `g.lib.branch_current` | `g.current_branch` |
274
+ | `g.lib.parse_config(file)` | `g.config_list(file: file)` |
275
+
276
+ ##### Methods with no replacement
277
+
278
+ | v4.x call | Notes |
279
+ |-----------|-------|
280
+ | `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.branch_list`, `g.tag_list`, or `g.remote_list` instead. |
281
+
282
+ ##### Internal plumbing methods (no replacement)
283
+
284
+ The following methods were technically public on `Git::Lib` but are internal
285
+ helpers with no plausible external use. They have no replacement in v5.0.0:
286
+
287
+ - `assert_args_are_not_options`
288
+ - `assert_valid_opts`
289
+ - `cat_file_object_meta`
290
+ - `command_capturing`
291
+ - `command_streaming`
292
+ - `each_cat_file_header`
293
+ - `handle_deprecated_path_option`
294
+ - `normalize_pathspecs`
295
+ - `parse_cat_file_meta`
296
+ - `parse_config_list`
297
+ - `process_commit_data`
298
+ - `validate_pathspec_types`
299
+
300
+ ---
301
+
302
+ #### `Git::Log#object` is not a path limiter
303
+
304
+ In previous 4.x releases, some uses of `Git::Log#object(path)` could appear to
305
+ filter log output by path when combined with `#between` or other revision range
306
+ options. This relied on ambiguous `git log` argument handling and was not the
307
+ intended API for path filtering.
308
+
309
+ In v5.x, `Git::Log#object` should be treated as a revision expression. When both
310
+ `#object` and `#between` are specified, `#between` takes precedence. Code that
311
+ used `#object` to limit commits to a path should use `#path` instead.
312
+
313
+ ```ruby
314
+ # v4.x — ambiguous; could appear to filter commits touching this path
315
+ git.log(500).object('cookbooks/mycookbook').between('1.0.0', 'HEAD').execute
316
+
317
+ # v5.x — use #path for path filtering
318
+ git.log(500).path('cookbooks/mycookbook').between('1.0.0', 'HEAD').execute
319
+
320
+ # #object remains appropriate for revision expressions
321
+ git.log.object('HEAD~10..HEAD').execute
322
+ ```
323
+
324
+ ---
325
+
326
+ #### `Git::CommandLineResult` deprecated
327
+
328
+ `Git::CommandLineResult` was an alias for `Git::CommandLine::Result` introduced
329
+ for backward compatibility. It is deprecated in v5.0.0 and removed in v6.0.0.
330
+ Accessing `Git::CommandLineResult` emits a deprecation warning.
331
+
332
+ ```ruby
333
+ # v4.x
334
+ result.is_a?(Git::CommandLineResult)
335
+
336
+ # v5.x
337
+ result.is_a?(Git::CommandLine::Result)
338
+ ```
339
+
340
+ This change is only relevant if your code references `Git::CommandLineResult`
341
+ by name (typically in type checks or documentation). Code that simply uses the
342
+ result object returned by git commands is unaffected.
343
+
344
+ ---
345
+
346
+ ### Deprecated methods
347
+
348
+ The following methods are available in v5.x with deprecation warnings and are
349
+ removed in v6.0.0. Migrate to the replacement shown to silence the warnings.
350
+
351
+ #### Facade method renames
352
+
353
+ Five methods were renamed to follow the project's `noun_verb` naming convention.
354
+ The old names continue to work but emit deprecation warnings:
355
+
356
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
357
+ |-----------------------------------------------------|-------------|
358
+ | `g.add_remote(name, url, opts)` | `g.remote_add(name, url, opts)` |
359
+ | `g.remove_remote(name)` | `g.remote_remove(name)` |
360
+ | `g.set_remote_url(name, url)` | `g.remote_set_url(name, url)` |
361
+ | `g.add_tag(name, ...)` | `g.tag_add(name, ...)` |
362
+ | `g.delete_tag(name)` | `g.tag_delete(name)` |
363
+
364
+ #### `Git::Repository` method renames
365
+
366
+ Seven more `Git::Repository` methods were renamed in v5.x. The old names continue
367
+ to work but emit deprecation warnings. Each old name returns exactly what its
368
+ replacement returns, except `branches_all`.
369
+
370
+ > **Return shape change:** `g.branches_all` returns an `Array` of 4-element
371
+ > tuples `[refname, current, worktree, symref]`, where `refname` is the short
372
+ > form (`main` or `remotes/origin/main`), `current` and `worktree` are booleans,
373
+ > and `symref` is the symbolic-ref target or `nil`. `g.branch_list` returns
374
+ > `Array<Git::BranchInfo>` with `refname` (always the full ref: `refs/heads/main`
375
+ > or `refs/remotes/origin/main`), `short_name`, `remote_name`, `remote?`,
376
+ > `current?`, `other_worktree?`, `symref`, `target_oid`, and `upstream`. This
377
+ > expression reproduces the legacy tuples:
378
+ >
379
+ > ```ruby
380
+ > g.branch_list.map do |i|
381
+ > refname = i.remote? ? "remotes/#{i.remote_name}/#{i.short_name}" : i.short_name
382
+ > [refname, i.current?, i.other_worktree?, i.symref]
383
+ > end
384
+ > ```
385
+
386
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
387
+ |-----------------------------------------------------|-------------|
388
+ | `g.empty?` | `g.no_commits?` — `true` when the repository has no commits |
389
+ | `g.reset_hard` | `g.reset(nil, hard: true)` — `reset` takes the commitish positionally, so pass `nil` before the options; returns git's stdout, as `reset_hard` did |
390
+ | `g.reset_hard(commitish)` | `g.reset(commitish, hard: true)` — `reset_hard` ignored any `:hard` option passed to it and always reset with `--hard` |
391
+ | `g.conflicts { \|file, yours, theirs\| ... }` | `g.each_conflict { \|file, yours, theirs\| ... }` — same block arguments; returns the unmerged paths |
392
+ | `g.is_local_branch?(name)` | `g.local_branch?(name)` |
393
+ | `g.is_remote_branch?(name)` | `g.remote_branch?(name)` |
394
+ | `g.is_branch?(name)` | `g.branch?(name)` |
395
+ | `g.branches_all` | `g.branch_list` — returns `Array<Git::BranchInfo>`; see the return shape change above |
396
+
397
+ #### `Git::Repository` option renames
398
+
399
+ Five methods accept a v4.x option or positional argument under its old name.
400
+ The old form still works in v5.x but emits a deprecation warning and is
401
+ translated to the v5.x form shown below.
402
+
403
+ > **`clean`:** `force: 2` runs `git clean -ff`, which also removes untracked
404
+ > nested git repositories. A `false` or `nil` value for `:ff` or `:force_force`
405
+ > still warns and has no effect; a value other than `true`, `false`, or `nil`
406
+ > raises `ArgumentError`. When the deprecated key is `true` and a valid
407
+ > `:force` is also given, `:force` is raised to `2` (a `:force` already at `2`
408
+ > is unchanged). An invalid `:force` value such as `0` is passed through
409
+ > unchanged and still raises `ArgumentError`; the deprecated key does not mask
410
+ > it.
411
+
412
+ > **`diff_path_status`:** `:path_limiter` accepts the same values as `:path`
413
+ > (a `String`, a `Pathname`, or an `Array` of them). When both keys are given,
414
+ > `:path_limiter` wins and no warning is emitted.
415
+
416
+ > **`set_working` and `set_index`:** `must_exist:` defaults to `true`. When
417
+ > both the positional argument and `must_exist:` are given, they are OR'ed so
418
+ > the more restrictive value wins.
419
+
420
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
421
+ |-----------------------------------------------------|-------------|
422
+ | `g.clean(ff: true)` | `g.clean(force: 2)` |
423
+ | `g.clean(force_force: true)` | `g.clean(force: 2)` |
424
+ | `g.diff_path_status(ref1, ref2, path: p)` | `g.diff_path_status(ref1, ref2, path_limiter: p)` |
425
+ | `g.commit(message, add_all: true)` | `g.commit(message, all: true)` — runs `git commit -a` |
426
+ | `g.set_working(dir, check)` | `g.set_working(dir, must_exist: check)` |
427
+ | `g.set_index(file, check)` | `g.set_index(file, must_exist: check)` |
428
+
429
+ #### v4.x-style configuration methods
430
+
431
+ The v4.x `config` and `global_config` methods accepted varying argument shapes
432
+ to read, write, or list configuration. These are replaced by separate,
433
+ purpose-named methods. The same applies to the module-level
434
+ `Git.global_config`, which is replaced by `Git.config_get`, `Git.config_set`,
435
+ and `Git.config_list` called with `global: true`.
436
+
437
+ > **Return type change:** The v4.x `g.config(name)` and `Git.global_config(name)`
438
+ > returned a `String`; `g.config` and `Git.global_config` returned a `Hash`. The
439
+ > v5.x replacements `config_get` and `config_list` return `Git::ConfigEntryInfo`
440
+ > (or `nil` when the key is not set) and `Array<Git::ConfigEntryInfo>`
441
+ > respectively. Use `.value` to get the String value:
442
+ > - `g.config_get(name)&.value` → String or nil
443
+ > - `g.config_list.to_h { |e| [e.key, e.value] }` → Hash (key → value)
444
+ >
445
+ > The setters `g.config(name, value)` and `Git.global_config(name, value)`
446
+ > returned the raw command result; `config_set` returns `nil`.
447
+
448
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
449
+ |-----------------------------------------------------|-------------|
450
+ | `g.config(name)` | `g.config_get(name)` — returns `Git::ConfigEntryInfo`; use `.value` for the String |
451
+ | `g.config` | `g.config_list` — returns `Array<Git::ConfigEntryInfo>` |
452
+ | `g.config(name, value)` | `g.config_set(name, value)` |
453
+ | `g.global_config(name)` | `g.config_get(name, global: true)` |
454
+ | `g.global_config` | `g.config_list(global: true)` |
455
+ | `g.global_config(name, value)` | `g.config_set(name, value, global: true)` |
456
+ | `Git.global_config(name)` | `Git.config_get(name, global: true)` — returns `Git::ConfigEntryInfo` or `nil`; use `.value` for the String |
457
+ | `Git.global_config` | `Git.config_list(global: true)` — returns `Array<Git::ConfigEntryInfo>` |
458
+ | `Git.global_config(name, value)` | `Git.config_set(name, value, global: true)` |
459
+
460
+ #### `Git` module mixin deprecations
461
+
462
+ Extending or including the `Git` module to call `config` and `global_config`
463
+ as bare methods is deprecated:
464
+
465
+ | Deprecated usage | Replacement |
466
+ |-----------------|-------------|
467
+ | `include Git; config(name)` | `Git.config_get(name)` |
468
+ | `include Git; config(name, value)` | `Git.config_set(name, value)` |
469
+ | `include Git; config` | `Git.config_list` |
470
+ | `include Git; global_config(name)` | `Git.config_get(name, global: true)` |
471
+ | `include Git; global_config(name, value)` | `Git.config_set(name, value, global: true)` |
472
+ | `include Git; global_config` | `Git.config_list(global: true)` |
473
+
474
+ `Git.config_get`, `Git.config_set`, and `Git.config_list` run `git config` in
475
+ the current directory, which is what the mixin `config` method did. The
476
+ return types differ as described under
477
+ [v4.x-style configuration methods](#v4x-style-configuration-methods).
478
+
479
+ #### Module-level `Git` function deprecations
480
+
481
+ Two module-level functions on `Git` accept a legacy call shape or return a
482
+ legacy type that is deprecated:
483
+
484
+ - `Git.ls_remote` defaults its repository argument to `'.'`. Passing `nil`
485
+ explicitly still works but warns; omit the argument or pass `'.'`. The
486
+ options hash is positional, so when you pass options you must also pass the
487
+ repository: `Git.ls_remote('.', opts)`, not `Git.ls_remote(opts)`.
488
+ - `Git.binary_version` is replaced by `Git.git_version`, which keeps the
489
+ optional binary path argument.
490
+
491
+ > **Return type change:** `Git.binary_version` returned an `Array<Integer>` of
492
+ > `[major, minor, patch]`. `Git.git_version` returns a `Git::Version`, which
493
+ > supports comparison and exposes `major`, `minor`, and `patch`.
494
+ > `Git.git_version.to_a` reproduces the legacy array. The return value of
495
+ > `Git.ls_remote` is unchanged.
496
+
497
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
498
+ |-----------------------------------------------------|-------------|
499
+ | `Git.ls_remote(nil)` | `Git.ls_remote` or `Git.ls_remote('.')` |
500
+ | `Git.ls_remote(nil, opts)` | `Git.ls_remote('.', opts)` |
501
+ | `Git.binary_version` | `Git.git_version` — returns `Git::Version`; use `.to_a` for the `[major, minor, patch]` Array |
502
+ | `Git.binary_version(binary_path)` | `Git.git_version(binary_path)` |
503
+
504
+ #### `Git::Author` deprecated
505
+
506
+ Starting in v5.3.0, methods that return author, committer, or tagger data —
507
+ `Git::Object::Commit#author`, `Git::Object::Commit#committer`,
508
+ `Git::Object::Tag#tagger`, and `Git::TagInfo#tagger` — return an immutable
509
+ `Git::AuthorInfo` value object instead of the mutable `Git::Author`.
510
+
511
+ `Git::AuthorInfo` exposes the same `name`, `email`, and `date` readers, so code
512
+ that only reads these attributes needs no changes. Code that mutated a
513
+ `Git::Author` (via `name=`, `email=`, or `date=`) must be updated:
514
+ `Git::AuthorInfo` is frozen, and `#with` returns a modified copy rather than
515
+ updating in place (e.g. `info = info.with(name: 'New Name')`).
516
+
517
+ Constructing `Git::Author` directly emits a deprecation warning naming
518
+ `Git::AuthorInfo` as the replacement. The class is removed in v6.0.0.
519
+
520
+ | Deprecated usage | Replacement |
521
+ |-----------------|-------------|
522
+ | `Git::Author.new('Name <email> 1627849923 +0200')` | `Git::AuthorInfo.parse('Name <email> 1627849923 +0200')` |
523
+ | `author.name = 'New Name'` | `author = author.with(name: 'New Name')` (returns a new object) |
524
+
525
+ #### `Git::Branch#stashes` deprecated
526
+
527
+ `Git::Branch#stashes` ignores the branch it is called on and returns every stash
528
+ in the repository, so `g.branch('feature').stashes` and `g.branch('main').stashes`
529
+ return the same entries. Call `Git::Repository#stash_infos` instead; it is the
530
+ query `Git::Branch#stashes` was already running.
531
+
532
+ > **Return type change:** `Git::Branch#stashes` returns a `Git::Stashes`
533
+ > collection of `Git::Stash` objects. `g.stash_infos` returns an array of
534
+ > `Git::StashInfo` values. Both are newest first, so indexes carry over unchanged.
535
+ > `Git::Stash#message` strips the `WIP on <branch>:` or `On <branch>:` prefix;
536
+ > `Git::StashInfo#message` keeps the full message and exposes the branch name as
537
+ > `Git::StashInfo#branch`.
538
+
539
+ `Git::Stashes` also exposes `save`, `apply`, and `clear`. Those map to the
540
+ repository's `stash_push`, `stash_apply`, and `stash_clear`. `Git::Stashes#apply(i)`
541
+ already passed `i` to git as `stash@{i}` (`0` = newest), and `g.stash_apply(i)` does
542
+ the same, so that index needs no conversion. The `Git::Stashes` class is deprecated
543
+ as well; [Legacy stash API deprecated](#legacy-stash-api-deprecated) maps each of
544
+ its methods.
545
+
546
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
547
+ |-----------------------------------------------------|-------------|
548
+ | `g.branch(name).stashes` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
549
+ | `g.branch(name).stashes.each { \|s\| puts s.message }` | `g.stash_infos.each { \|info\| puts info.message }` |
550
+ | `g.branch(name).stashes.all` (`[index, message]` pairs, oldest first) | `g.stash_infos.reverse` — see the ordering note in [Legacy stash API deprecated](#legacy-stash-api-deprecated) |
551
+ | `g.branch(name).stashes.size` | `g.stash_infos.size` |
552
+ | `g.branch(name).stashes[i].message` (`0` = newest, `i` coerced with `to_i`) | `g.stash_infos[i.to_i].message` |
553
+ | `g.branch(name).stashes.save(message)` | `g.stash_push(message: message)` |
554
+ | `g.branch(name).stashes.apply` | `g.stash_apply` |
555
+ | `g.branch(name).stashes.apply(i)` (`0` = newest) | `g.stash_apply(i)` |
556
+ | `g.branch(name).stashes.clear` | `g.stash_clear` — returns git's stdout (normally `""`, which is truthy) where `Git::Stashes#clear` returned `nil` |
557
+
558
+ #### Legacy stash API deprecated
559
+
560
+ Starting in v5.4.0, the stash methods on `Git::Repository` are built around the
561
+ immutable `Git::StashInfo` value object. `g.stash_infos` returns every entry as a
562
+ `Git::StashInfo`, and `stash_push`, `stash_pop`, `stash_drop`, `stash_show`,
563
+ `stash_branch`, `stash_create`, and `stash_store` each map onto the `git stash`
564
+ subcommand of the same name. Every method that takes a stash (`stash_apply`,
565
+ `stash_pop`, `stash_drop`, `stash_show`, `stash_branch`) accepts a `Git::StashInfo`,
566
+ a `stash@{N}` name, an Integer index (`0` = newest), or `nil` for the newest entry.
567
+
568
+ The legacy methods and classes are deprecated and removed in v6.0.0:
569
+ `Git::Repository#stashes_all`, `Git::Repository#stash_save`,
570
+ `Git::Repository#stash_list`, `Git::Stash`, and `Git::Stashes`. Constructing a
571
+ `Git::Stash` or `Git::Stashes` emits one warning per object.
572
+
573
+ > **Ordering flip:** `g.stashes_all` returns entries **oldest first** with a
574
+ > sequential index of its own (`0` is the oldest). `g.stash_infos` returns entries
575
+ > **newest first**, the order `git stash list` uses, and `Git::StashInfo#index` is
576
+ > git's own `stash@{N}` number (`0` is the newest). `g.stashes_all.first` is
577
+ > `g.stash_infos.last`. Code that reads an entry by position must reverse the
578
+ > array or the index.
579
+
580
+ > **Message difference:** `g.stashes_all` strips the `WIP on <branch>:` or
581
+ > `On <branch>:` prefix from each message. `Git::StashInfo#message` keeps the full
582
+ > message git stores, and `Git::StashInfo#branch` holds the branch name. A stash
583
+ > created from a detached HEAD has the branch `"(no branch)"`, the label git writes
584
+ > in its message. `branch` is `nil` only when the message has no branch prefix at
585
+ > all, as for a `stash_store` entry with a custom message.
586
+
587
+ `g.stash_save(message)` returned `true` when it created a stash and `false` when
588
+ there were no local changes to save. `g.stash_push(message: message)` returns the
589
+ new `Git::StashInfo`, or `nil` when there were no local changes, so a truthiness
590
+ check such as `if g.stash_push(message: 'WIP')` still works.
591
+
592
+ `g.stash_list` returned the `git stash list` text as a String. Build that text from
593
+ `g.stash_infos` if you need it. In v6.0.0, `stash_list` returns
594
+ `Array<Git::StashInfo>`, the same value as `stash_infos`, and `stash_infos` stays as
595
+ a permanent alias. Move String callers of `stash_list` to `stash_infos` before
596
+ upgrading so the return type change cannot go unnoticed.
597
+
598
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
599
+ |-----------------------------------------------------|-------------|
600
+ | `g.stashes_all` | `g.stash_infos` — returns `Array<Git::StashInfo>`, newest first |
601
+ | `g.stashes_all.each { \|index, message\| ... }` | `g.stash_infos.reverse_each.with_index { \|info, index\| ... info.message }` |
602
+ | `g.stashes_all[i]` (`0` = oldest) | `g.stash_infos.reverse[i]` |
603
+ | `g.stashes_all.last` | `g.stash_infos.first` |
604
+ | `g.stash_save(message)` | `g.stash_push(message: message)` — returns `Git::StashInfo` or `nil` |
605
+ | `g.stash_list` (String) | `g.stash_infos.map { \|s\| "#{s.name}: #{s.message}" }.join("\n")` |
606
+ | `Git::Stash.new(g, message)` | `info = g.stash_push(message: message)` |
607
+ | `Git::Stash.new(g, message, existing: true)` | `message` — `existing: true` only wrapped the String and never looked an entry up; code that needs a real entry picks one from `g.stash_infos` by index or name |
608
+ | `stash.save` | `info = g.stash_push(message: message)` |
609
+ | `stash.saved?` | `!info.nil?` — check the value `stash_push` returned rather than pushing again |
610
+ | `stash.message` / `stash.to_s` | `info.message` — keeps the branch prefix; see the note above |
611
+ | `Git::Stashes.new(g)` | `g.stash_infos` |
612
+ | `stashes.all` (`[index, message]` pairs, oldest first) | `g.stash_infos.reverse` — see the ordering note above |
613
+ | `stashes.each { \|s\| ... }` (newest first) | `g.stash_infos.each { \|info\| ... }` |
614
+ | `stashes[i]` (`0` = newest, `i` coerced with `to_i`) | `g.stash_infos[i.to_i]` |
615
+ | `stashes.size` | `g.stash_infos.size` |
616
+ | `stashes.save(message)` | `g.stash_push(message: message)` |
617
+ | `stashes.apply` / `stashes.apply(i)` | `g.stash_apply` / `g.stash_apply(i)` |
618
+ | `stashes.clear` | `g.stash_clear` — returns git's stdout (normally `""`, which is truthy) where `Git::Stashes#clear` returned `nil` |
619
+
620
+ #### `Git::Repository#remotes` deprecated
621
+
622
+ `Git::Repository#remotes` is deprecated in favor of `Git::Repository#remote_list`
623
+ and is removed in v6.0.0. Its return value is unchanged. Calling `remotes` emits
624
+ one deprecation warning for itself plus one `Git::Remote` constructor warning for
625
+ each remote it returns (see the `Git::Remote` deprecation below), so a repository
626
+ with N remotes produces N + 1 warnings per call.
627
+
628
+ > **Return type change:** `remotes` returns `Array<Git::Remote>` — mutable
629
+ > objects with `name`, `url`, and `fetch_opts` accessors and `fetch`, `merge`,
630
+ > `branch`, and `remove` operations. `remote_list` returns
631
+ > `Array<Git::RemoteInfo>` — immutable value objects read from the repository's
632
+ > git config, with fields such as `name`, `url`, `push_url`, `fetch`, and `push`.
633
+ > Because a remote may carry more than one URL or refspec, `url`, `push_url`,
634
+ > `fetch`, and `push` are always frozen `Array<String>`. When a remote has more
635
+ > than one URL, git fetches from the first; the legacy `Git::Remote#url` returned
636
+ > the last one configured, so use `r.url.last` to reproduce that exact value.
637
+ > Likewise, `Git::Remote#fetch_opts` returned only the last configured fetch
638
+ > refspec, while `fetch` holds all of them. Operations that lived on
639
+ > `Git::Remote` are called on the repository with the remote name instead.
640
+ >
641
+ > **Order change:** `remotes` lists remotes in the order `git remote` prints
642
+ > them, while `remote_list` keeps the order in which remotes first appear in the
643
+ > config. When the legacy order matters, iterate `g.remote_names` (the same
644
+ > `git remote` order) or sort `g.remote_list` explicitly.
645
+
646
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
647
+ |-----------------------------------------------------|-------------|
648
+ | `g.remotes` | `g.remote_list` — returns `Array<Git::RemoteInfo>` |
649
+ | `g.remotes.map(&:name)` | `g.remote_list.map(&:name)` or `g.remote_names` |
650
+ | `g.remotes.map(&:to_s)` | `g.remote_list.map(&:name)` — `Git::RemoteInfo#to_s` is not the name |
651
+ | `g.remotes.map(&:url)` | `g.remote_list.map { \|r\| r.url.first }` — `url` is an `Array<String>` |
652
+ | `g.remotes.map(&:fetch_opts)` | `g.remote_list.map { \|r\| r.fetch.last }` — `fetch` holds every refspec |
653
+ | `g.remotes.each(&:fetch)` | `g.remote_names.each { \|name\| g.fetch(name) }` — same order as `remotes` |
654
+ | `remote.fetch` | `g.fetch(remote.name)` |
655
+ | `remote.fetch(opts)` | `g.fetch(remote.name, opts)` — same options hash |
656
+ | `remote.merge` | `g.merge("#{remote.name}/#{g.current_branch}")` |
657
+ | `remote.merge(branch)` | `g.merge("#{remote.name}/#{branch}")` |
658
+ | `remote.branch` | `g.branch_list("#{remote.name}/#{g.current_branch}").first` — returns a `Git::BranchInfo` |
659
+ | `remote.branch(name)` | `g.branch_list("#{remote.name}/#{name}").first` — returns a `Git::BranchInfo` |
660
+ | `remote.remove` | `g.remote_remove(remote.name)` |
661
+
662
+ #### `Git::Remote` deprecated
663
+
664
+ `Git::Remote`, `Git::Repository#remote`, and `Git::Repository#config_remote` are
665
+ deprecated and are removed in v6.0.0. Read a remote's configuration through
666
+ `Git::Repository#remote_list`, which returns one `Git::RemoteInfo` value object per
667
+ remote, and call the repository-level operations (`fetch`, `merge`, `branch_list`,
668
+ `remote_remove`) with the remote name. Return values are unchanged. Constructing a
669
+ `Git::Remote` directly emits one deprecation warning, and so does calling
670
+ `g.config_remote`. Calling `g.remote` emits two: one for `Git::Repository#remote`
671
+ and one for the `Git::Remote` it constructs. Likewise `g.remotes` emits one warning
672
+ for itself plus one per `Git::Remote` it returns (N + 1 for N remotes). The extra
673
+ warnings from `g.remote` and `g.remotes` are expected, not a bug.
674
+
675
+ > **Return type changes:** `Git::RemoteInfo#url` and `Git::RemoteInfo#fetch` are
676
+ > frozen `Array<String>` because a remote may carry more than one URL or fetch
677
+ > refspec. The legacy `Git::Remote#url` and `Git::Remote#fetch_opts` returned only
678
+ > the last configured value, so `r.url.last` and `r.fetch.last` reproduce them
679
+ > exactly; `r.url.first` is the URL git actually fetches from.
680
+ > `config_remote` returned a flat `Hash{String => String}` in which a repeated
681
+ > `url` or `fetch` key overwrote the earlier value, so it could not report every
682
+ > configured URL or refspec; `remote_list` keeps all of them. In the other
683
+ > direction, `Git::RemoteInfo` models only the remote variables git defines and
684
+ > drops any other `remote.<name>.*` key, while `config_remote` returned every key.
685
+ > Code that reads custom keys should filter `g.config_list` instead (see the
686
+ > table); that yields the same `Hash{String => String}` as `config_remote`.
687
+ > `Git::Remote#branch` returned a `Git::Branch`. Its replacement,
688
+ > `g.branch_list("#{name}/#{branch}").first`, returns a `Git::BranchInfo` value
689
+ > object, or `nil` when the remote-tracking branch does not exist.
690
+
691
+ In the table, `name` is the remote name (`g.remote` defaults it to `'origin'`).
692
+
693
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
694
+ |-----------------------------------------------------|-------------|
695
+ | `g.remote` | `g.remote_list.find { \|r\| r.name == 'origin' }` — returns a `Git::RemoteInfo`; the deprecated call emits two warnings |
696
+ | `g.remote(name)` | `g.remote_list.find { \|r\| r.name == name }` — returns a `Git::RemoteInfo`; the deprecated call emits two warnings |
697
+ | `g.config_remote(name)` for `url`, `fetch`, and the other modeled fields | `g.remote_list.find { \|r\| r.name == name }` — a `Git::RemoteInfo`, not a `Hash` |
698
+ | `g.config_remote(name)` for every key, including custom ones | `g.config_list.select { \|e\| e.key.start_with?("remote.#{name}.") }.to_h { \|e\| [e.key.delete_prefix("remote.#{name}."), e.value] }` — the same `Hash{String => String}` |
699
+ | `remote.name`, `remote.to_s` | `g.remote_list.find { \|r\| r.name == name }.name` or `g.remote_names` |
700
+ | `remote.url` | `g.remote_list.find { \|r\| r.name == name }.url` — `Array<String>`; `.first` for the single-URL case |
701
+ | `remote.fetch_opts` | `g.remote_list.find { \|r\| r.name == name }.fetch` — `Array<String>` of refspecs |
702
+ | `remote.fetch` | `g.fetch(name)` |
703
+ | `remote.fetch(opts)` | `g.fetch(name, opts)` — same option keys |
704
+ | `remote.merge` | `g.merge("#{name}/#{g.current_branch}")` |
705
+ | `remote.merge(branch)` | `g.merge("#{name}/#{branch}")` |
706
+ | `remote.branch` | `g.branch_list("#{name}/#{g.current_branch}").first` — returns a `Git::BranchInfo` |
707
+ | `remote.branch(branch)` | `g.branch_list("#{name}/#{branch}").first` — returns a `Git::BranchInfo` |
708
+ | `remote.remove` | `g.remote_remove(name)` |
709
+
710
+ #### `Git::Commands::CatFile::Raw` `allow_unknown_type` option deprecated
711
+
712
+ The `allow_unknown_type:` option of `Git::Commands::CatFile::Raw` is deprecated
713
+ and is removed in v6.0.0. Passing it emits a deprecation warning; the
714
+ `--allow-unknown-type` flag still reaches git unchanged until the option is
715
+ removed.
716
+
717
+ There is no replacement. Git 2.50 removed the unknown-type feature, so on git
718
+ 2.50 and later `--allow-unknown-type` is an accepted no-op and the option has no
719
+ effect. On git 2.28 through 2.49 the flag still lets `t: true` and `s: true`
720
+ report the type and size of an object whose type git does not recognize, but
721
+ that behavior is dropped together with the option. The class is internal
722
+ (`@api private`) and no `Git::Repository` method passes the option, so only code
723
+ that constructs the command class directly is affected.
724
+
725
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
726
+ |-----------------------------------------------------|-------------|
727
+ | `Git::Commands::CatFile::Raw.new(ctx).call(sha, t: true, allow_unknown_type: true)` | `Git::Commands::CatFile::Raw.new(ctx).call(sha, t: true)` |
728
+ | `Git::Commands::CatFile::Raw.new(ctx).call(sha, s: true, allow_unknown_type: true)` | `Git::Commands::CatFile::Raw.new(ctx).call(sha, s: true)` |
729
+
730
+ #### `Git::Branch` and `Git::Branches` deprecated
731
+
732
+ `Git::Branch`, `Git::Branches`, `Git::Repository#branch`, and
733
+ `Git::Repository#branches` are deprecated and are removed in v6.0.0. Read branch
734
+ data through `Git::Repository#branch_list`, which returns one `Git::BranchInfo`
735
+ value object per local and remote-tracking branch, and call the repository-level
736
+ operations (`checkout`, `branch_new`, `branch_delete`, `merge`, `merge_into`,
737
+ `in_branch`, and so on) with the branch name. Calling `g.branch` or `g.branches`,
738
+ constructing a `Git::Branches`, and calling any operation on a `Git::Branch` each
739
+ emit a deprecation warning; their return values are unchanged. The `full`,
740
+ `name`, `remote`, `to_s`, and `to_a` readers on `Git::Branch` do not warn.
741
+
742
+ > **Return shape change:** `Git::Branch` exposes `full` (`main` or
743
+ > `remotes/origin/main`), `name`, and `remote` (a `Git::Remote`, or `nil`).
744
+ > `Git::BranchInfo` exposes `refname` (always the full ref: `refs/heads/main` or
745
+ > `refs/remotes/origin/main`), `short_name` (`main` for both), `remote_name` (a
746
+ > `String`, or `nil`), `remote?`, `current?`, `target_oid`, `upstream`,
747
+ > `worktree_path`, and `symref`. `Git::BranchInfo#to_s` is the full ref, not the
748
+ > `remotes/origin/main` form `Git::Branch#to_s` returned. `branch_list` takes
749
+ > `git branch --list` patterns: `'main'` matches the local branch and
750
+ > `'origin/main'` matches the remote-tracking branch. The `remotes/origin/main`
751
+ > and `refs/...` forms that `g.branches[...]` accepted match nothing.
752
+ >
753
+ > **`checkout` no longer creates the branch:** `g.branch('x').checkout` created
754
+ > `x` when it did not exist, ignoring any error from that attempt, and then
755
+ > checked it out. `g.checkout('x')` does not create a missing local branch,
756
+ > with one exception that is git's own: when exactly one remote has a branch
757
+ > named `x`, git creates a local tracking branch from it (its default guess
758
+ > behavior). Otherwise the checkout fails. To reproduce create-or-checkout,
759
+ > call `g.branch_new('x') unless g.local_branch?('x')` and then
760
+ > `g.checkout('x')`. Use `g.checkout('x', new_branch: true)` only when `x` is
761
+ > known not to exist; like `g.branch_new('x')`, it fails when `x` already
762
+ > exists. Likewise `g.branch('x').create` ignored every error, while
763
+ > `g.branch_new('x')` raises `Git::FailedError` when `x` already exists.
764
+ >
765
+ > **`in_branch` and `merge_into` differences:**
766
+ >
767
+ > 1. **Branch creation.** `g.branch('x').in_branch { ... }` created `x` if it did
768
+ > not exist. `g.in_branch('x') { ... }` raises `ArgumentError` unless `x` is an
769
+ > existing local branch, so call `g.branch_new('x')` first. A commit SHA, tag,
770
+ > or remote-tracking name is also rejected before any checkout.
771
+ > 2. **Detached HEAD.** `Git::Branch#in_branch` recorded the literal `HEAD` and
772
+ > could not restore a detached HEAD to its original commit. `g.in_branch` and
773
+ > `g.merge_into` record the SHA and restore it.
774
+ > 3. **Unborn HEAD.** Both new methods raise `Git::Error` before checking anything
775
+ > out when HEAD is on a branch with no commits. The old methods failed later,
776
+ > mid-flow.
777
+ > 4. **Merge overload.** `g.branch('main').merge('feature')` returned stdout from
778
+ > the final restore checkout and ran a hard reset after the merge.
779
+ > `g.merge_into('main', 'feature')` returns the merge's stdout and does no
780
+ > reset. It also rejects the `:no_commit` option; callers who need
781
+ > `--no-commit` use `checkout` and `merge` directly.
782
+ > 5. **Remote-tracking receivers.** Called on a remote-tracking `Git::Branch`,
783
+ > `in_branch` and `merge(branch)` checked out the remote-tracking ref,
784
+ > detaching HEAD, and any commit made there was left dangling. `g.in_branch`
785
+ > and `g.merge_into` take an existing local branch only. Create one from the
786
+ > remote-tracking ref first, with
787
+ > `g.branch_new(name, "remotes/#{remote}/#{name}")`, and pass that branch.
788
+
789
+ In the table, `name` is the branch name (`g.branch` defaults it to the current
790
+ branch), `remote` is the remote name of a remote-tracking branch, `b` is a
791
+ `Git::Branch`, and `info` is the `Git::BranchInfo` that replaces it. Where a
792
+ row says to pass `info.refname` for a remote-tracking branch, `b.full` (the
793
+ `remotes/<remote>/<name>` form) works too; the shorter `"#{remote}/#{name}"`
794
+ can resolve a local branch of that name and is only used where git expects it
795
+ (`branch_delete` with `remotes: true`).
796
+
797
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
798
+ |-----------------------------------------------------|-------------|
799
+ | `g.branch(name)` | `g.branch_list(name).first` for a local branch, or `g.branch_list("#{remote}/#{name}").find(&:remote?)` for a remote-tracking one — a `Git::BranchInfo`, or `nil` when the branch does not exist; the `remotes/` and `refs/` forms match nothing |
800
+ | `g.branch` | `g.branch_list(g.current_branch).first` — `nil` when HEAD is detached or unborn; use `g.current_branch_state` there |
801
+ | `g.branches` | `g.branch_list` — returns `Array<Git::BranchInfo>` |
802
+ | `g.branches[name]` | `g.branch_list(name).first`, or `g.branch_list("#{remote}/#{name}").find(&:remote?)` for a remote-tracking branch |
803
+ | `g.branches.local` | `g.branch_list.reject(&:remote?)` |
804
+ | `g.branches.remote` | `g.branch_list.select(&:remote?)` |
805
+ | `g.branches.size` | `g.branch_list.size` |
806
+ | `g.branches.each { \|b\| ... }` | `g.branch_list.each { \|info\| ... }` |
807
+ | `g.branches.to_s` | `g.branch_list.map { \|i\| "#{i.current? ? '* ' : ' '}#{i.refname}\n" }.join` — full refs, not `remotes/...` |
808
+ | `b.full`, `b.to_s` | `info.refname` — `refs/remotes/origin/main` rather than `remotes/origin/main` |
809
+ | `b.to_a` | `[info.refname]` |
810
+ | `b.name` | `info.short_name` |
811
+ | `b.remote` | `info.remote_name` — a `String`, or `nil` for a local branch |
812
+ | `b.gcommit` | `g.gcommit(name)` — pass `info.refname` for a remote-tracking branch |
813
+ | `b.checkout` | `g.checkout(name)` — does not create the branch (see above); pass `info.refname` for a remote-tracking branch |
814
+ | `b.create` | `g.branch_new(name)` — raises when the branch already exists |
815
+ | `b.delete` (local) | `g.branch_delete(name)` |
816
+ | `b.delete` (remote-tracking) | `g.branch_delete("#{remote}/#{name}", remotes: true)` |
817
+ | `b.current` | `g.current_branch == name` |
818
+ | `b.contains?(commit)` | `!g.branch_contains(commit, name).empty?` |
819
+ | `b.merge` | `g.merge(name)` |
820
+ | `b.merge(branch, message)` | `g.merge_into(name, branch, message)` — local `b` only; see the differences above |
821
+ | `b.update_ref(commit)` (local) | `g.update_ref(name, commit)` |
822
+ | `b.update_ref(commit)` (remote-tracking) | `g.update_ref("remotes/#{remote}/#{name}", commit)` |
823
+ | `b.archive(file, opts)` | `g.archive(name, file, opts)` — pass `info.refname` for a remote-tracking branch |
824
+ | `b.in_branch(message) { ... }` | `g.in_branch(name, message) { ... }` — local `b` only; see the differences above |
825
+ | `b.stashes` | `g.stash_infos` — see [`Git::Branch#stashes` deprecated](#gitbranchstashes-deprecated) |
826
+
827
+ #### `Git::Object::Tag` deprecated
828
+
829
+ `Git::Object::Tag`, `Git::Repository#tag`, `Git::Repository#tags`, and
830
+ `Git::Repository#tag_add` are deprecated and are removed in v6.0.0. Read tag data
831
+ through `Git::Repository#tag_list`, which returns one `Git::TagInfo` value object per
832
+ tag, create tags with `Git::Repository#tag_create`, which returns the new tag's
833
+ `Git::TagInfo`, and call the repository-level operations (`archive`, `log`, `diff`,
834
+ `cat_file_contents`, and so on) with the tag's object ID,
835
+ `info.oid || info.target_oid`, which is the object a `Git::Object::Tag` pinned when
836
+ it was constructed. Calling `g.tag`, `g.tags`, or
837
+ `g.tag_add`, and constructing a `Git::Object::Tag`, each emit one deprecation
838
+ warning; their return values are unchanged. `g.add_tag` already warned, pointing at
839
+ `g.tag_add`, and now emits two warnings for a creation call, one for itself and one
840
+ for the `g.tag_add` it calls; `g.add_tag(name, d: true)` emits three, adding the
841
+ `:d`/`:delete` warning described below. The readers on a `Git::Object::Tag` do not
842
+ warn.
843
+
844
+ > **Return shape change:** `Git::Object::Tag` exposes `name`, `sha`, `objectish`,
845
+ > `annotated?`, `message`, and `tagger`. `Git::TagInfo` exposes `name`, `oid`,
846
+ > `target_oid`, `objecttype`, `annotated?`, `lightweight?`, `message`, and
847
+ > `tagger`. `name` and `annotated?` are unchanged. `tagger` keeps the same `name`
848
+ > and `email`, but `tagger.date` differs: `t.tagger.date` is a `Time` in the
849
+ > process's local zone, while `info.tagger.date` keeps the UTC offset recorded in
850
+ > the tag object. Both name the same instant. `message` differs for an annotated
851
+ > tag created with an empty message (`message: ''`): `t.message` returns `""` and
852
+ > `info.message` returns `nil`, the same value a lightweight tag has. `t.sha` and
853
+ > `t.objectish` are the tag object's ID for an annotated tag and
854
+ > the tagged object's ID for a lightweight tag. `Git::TagInfo` separates the two:
855
+ > `oid` is the tag object's ID (`nil` for a lightweight tag) and `target_oid` is
856
+ > the ID of the object the tag points to (set for both kinds), so
857
+ > `info.oid || info.target_oid` reproduces `t.sha`. The target is usually a
858
+ > commit, but a tag can point at any git object, and `info.objecttype` reports
859
+ > which kind (`tag` for an annotated tag, or the target's own type such as
860
+ > `commit` or `blob` for a lightweight one).
861
+ >
862
+ > **Missing tags:** `g.tag(name)` raises `Git::UnexpectedResultError` when no tag
863
+ > has that name. `g.tag_list(name).first` returns `nil`.
864
+ >
865
+ > **Deleting through `tag_add`:** `g.tag_add(name, d: true)`, which was already
866
+ > deprecated, deletes the tag and emits a second warning pointing at
867
+ > `g.tag_delete`. `g.tag_create` rejects `:d` and `:delete` with `ArgumentError`.
868
+ >
869
+ > **Extra positional arguments:** `g.tag_add(name, target, extra)` ignores
870
+ > `extra` and tags `target`. `g.tag_create` raises `ArgumentError` when more than
871
+ > one positional argument follows the name.
872
+ >
873
+ > **Object identity:** every `Git::Object::Tag` resolves its tag to an object ID
874
+ > when it is constructed and runs `size`, `contents`, `grep`, `diff`, `log`, and
875
+ > `archive` against that ID, so moving or deleting the tag afterwards does not
876
+ > redirect an existing object. `Git::Object::Tag.new(g, sha, name)` uses the
877
+ > supplied `sha` as that ID; the other forms look it up from the ref. `annotated?`,
878
+ > `message`, and `tagger` always read the ref `name`. `Git::TagInfo` describes the
879
+ > ref only: `g.tag_list(name).first` returns whatever `name` points at now, or
880
+ > `nil` once the tag is deleted. Keep the same identity by passing `id` (see the
881
+ > table) rather than `name` to the operation replacements; they accept any object.
882
+ > To read an annotated tag object by ID without going through its ref, use
883
+ > `g.cat_file_tag(id)`, which returns the tag object's `object`, `type`, `tag`,
884
+ > `tagger`, and `message`.
885
+
886
+ In the table, `name` is the tag name, `t` is a `Git::Object::Tag`, `info` is the
887
+ `Git::TagInfo` that replaces it, and `id` is `info.oid || info.target_oid` (or the
888
+ `sha` given to the three-argument constructor), the object `t` pinned.
889
+
890
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
891
+ |-----------------------------------------------------|-------------|
892
+ | `g.tag(name)` | `g.tag_list(name).first` — a `Git::TagInfo`, or `nil` when the tag does not exist |
893
+ | `g.tags` | `g.tag_list` — returns `Array<Git::TagInfo>` |
894
+ | `g.tags.map(&:name)` | `g.tag_list.map(&:name)` |
895
+ | `g.tag_add(name, opts)` | `g.tag_create(name, opts)` — returns a `Git::TagInfo` |
896
+ | `g.tag_add(name, target, opts)` | `g.tag_create(name, target, opts)` |
897
+ | `g.tag_add(name, d: true)` | `g.tag_delete(name)` |
898
+ | `g.add_tag(name, opts)`, `g.add_tag(name, target, opts)` | `g.tag_create(name, ...)` — its warning names `g.tag_add`, which is deprecated too; go straight to `g.tag_create` |
899
+ | `g.add_tag(name, d: true)` | `g.tag_delete(name)` — `g.tag_create` rejects `:d`; see the deletion note above |
900
+ | `Git::Object::Tag.new(g, name)` | `g.tag_list(name).first` |
901
+ | `Git::Object::Tag.new(g, sha, name)` | `g.tag_list(name).first` — reads the ref rather than `sha`; use `sha` as `id` for the operations below, or read the object with `g.cat_file_tag(sha)`; see the object identity note above |
902
+ | `Git::Object.new(g, name, nil, true)` | `g.tag_list(name).first` — its warning names `Git::Object::Tag.new`, which is deprecated too |
903
+ | `t.name` | `info.name` |
904
+ | `t.sha`, `t.objectish`, `t.to_s` | `info.oid \|\| info.target_oid` — see the return shape change above |
905
+ | `t.annotated?` | `info.annotated?` |
906
+ | `t.message` | `info.message` — `nil` rather than `""` for an annotated tag with an empty message |
907
+ | `t.tagger` | `info.tagger` — `date` keeps the recorded UTC offset; see the return shape change above |
908
+ | `t.tag?` | not needed; every `Git::TagInfo` is a tag |
909
+ | `t.size` | `g.cat_file_size(id)` — `id` rather than `name` keeps this and the operations below on the object `t` pinned; see the object identity note above |
910
+ | `t.contents` | `g.cat_file_contents(id)` |
911
+ | `t.contents { \|file\| ... }` | `g.cat_file_contents(id) { \|file\| ... }` — streams to a temporary file instead of buffering the object |
912
+ | `t.contents_array` | `g.cat_file_contents(id).split("\n")` |
913
+ | `t.grep(string, path, opts)` | `g.grep(string, path, opts.merge(object: id))` |
914
+ | `t.diff(other)` | `g.diff(id, other)` |
915
+ | `t.log(count)` | `g.log(count).object(id)` |
916
+ | `t.archive(file, opts)` | `g.archive(id, file, opts)` |
917
+
918
+ #### `Git::Status` deprecated
919
+
920
+ Starting in v5.4.0, `Git::Status`, `Git::Status::StatusFile`, and
921
+ `Git::Repository#status` are deprecated and will be removed in v6.0.0. Read the
922
+ index and working tree state through `Git::Repository#status_info`, which
923
+ returns an immutable `Git::StatusInfo` holding one `Git::StatusFileInfo` per
924
+ path that `git status --porcelain=v2` reports. Calling `g.status` emits one
925
+ deprecation warning, and so does constructing a `Git::Status` directly.
926
+
927
+ `Git::StatusInfo` keeps the `changed`, `added`, `deleted`, and `untracked`
928
+ readers and the `changed?`, `added?`, `deleted?`, and `untracked?` predicates,
929
+ so code that only uses those can change `status` to `status_info` and needs
930
+ no other edit, subject to the category differences below. The readers now
931
+ return `Hash{String => Git::StatusFileInfo}`, and a new `unmerged` reader
932
+ lists conflicted paths, which `Git::Status` did not report. The predicates
933
+ still compare paths case-insensitively when `core.ignoreCase` is `true`.
934
+ `Git::StatusInfo` is not `Enumerable`; iterate `status_info.files`, an
935
+ `Array<Git::StatusFileInfo>` in git's output order.
936
+
937
+ The categories are derived differently. `Git::Status` gave each file one
938
+ `type`, and `changed` held only files whose type was `M`, so `changed`,
939
+ `added`, and `deleted` were disjoint: a file staged as new and then modified
940
+ in the working tree was only `added`. `Git::StatusInfo` derives the
941
+ categories from both status characters, so `changed` also includes type
942
+ changes (`T`), and one path can be in more than one category: that same file
943
+ (`AM`) is in both `added` and `changed`, and a file modified in the index and
944
+ then deleted from the working tree (`MD`) is in both `changed` and `deleted`.
945
+ Code that relied on the sets being disjoint should test `index_status` and
946
+ `worktree_status` directly.
947
+
948
+ `Git::StatusInfo` holds only the paths `git status` reports. `Git::Status`
949
+ also held an entry for every clean tracked file, seeded from `git ls-files`,
950
+ so `status[path]` returned a `Git::Status::StatusFile` with a `nil` type for an
951
+ unchanged path and `status.each` yielded one. `status_info[path]` returns `nil`
952
+ for a clean path and `status_info.files` omits it. Code that inspected clean
953
+ files should read `g.ls_files`, which still returns the index mode and SHA of
954
+ every tracked path.
955
+
956
+ `Git::StatusFileInfo` replaces the single `type` character with the two status
957
+ characters of the porcelain v2 format, `index_status` (HEAD versus index) and
958
+ `worktree_status` (index versus working tree), plus the `changed?`, `added?`,
959
+ `deleted?`, `renamed?`, `unmerged?`, `untracked?`, and `ignored?` predicates:
960
+ `added?` is true when `index_status` is `A`, `deleted?` when either status is
961
+ `D`, and `changed?` when either status is `M` or `T`. It holds no repository
962
+ reference, so `blob` is gone; fetch the object through the repository instead.
963
+ `stage` is gone too: an unmerged entry carries its stage 1, 2, and 3 modes and
964
+ SHAs in `unmerged_stages`, and every other entry is at stage 0.
965
+
966
+ > **Field renames:** the legacy mode and SHA readers were named for the wrong
967
+ > sides. `sha_index` and `mode_index` held the working-tree side of the diff:
968
+ > the index blob when the working tree matched the index, and an all-zero SHA
969
+ > when it did not. `sha_repo` and `mode_repo` held the side git compared the
970
+ > working tree against: the index in a repository with no commits, and HEAD
971
+ > once a commit exists (the factory applied `git diff-index HEAD` last). The
972
+ > new names follow git: `sha_head` and `mode_head` are the HEAD side,
973
+ > `sha_index` and `mode_index` are the index (staged) side, and
974
+ > `mode_worktree` is the working-tree mode. There is no working-tree SHA
975
+ > because `git status` does not compute one; `worktree_status` says whether
976
+ > the working tree differs from the index.
977
+
978
+ In the table, `g` is a `Git::Repository`, `status` is the `Git::Status` from
979
+ `g.status`, `file` is a `Git::Status::StatusFile`, and `info` is the
980
+ `Git::StatusFileInfo` that replaces it.
981
+
982
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
983
+ |-----------------------------------------------------|-------------|
984
+ | `g.status` | `g.status_info` — returns a `Git::StatusInfo` |
985
+ | `Git::Status.new(g)` | `g.status_info` |
986
+ | `status.changed`, `status.added`, `status.deleted`, `status.untracked` | same names on `g.status_info` — now `Hash{String => Git::StatusFileInfo}` keyed by path |
987
+ | `status.changed?(path)`, `status.added?(path)`, `status.deleted?(path)`, `status.untracked?(path)` | same names on `g.status_info` |
988
+ | `status[path]` | `g.status_info[path]` — a `Git::StatusFileInfo`, or `nil`; `nil` for a clean tracked path, which `status[path]` reported (see above) |
989
+ | `status.each { \|file\| ... }` | `g.status_info.files.each { \|info\| ... }` — does not yield clean tracked paths (see above) |
990
+ | `status.pretty` | no replacement; format `g.status_info.files` yourself |
991
+ | `file.path` | `info.path` |
992
+ | `file.type` | `info.index_status` and `info.worktree_status`, or the `info.changed?`, `info.added?`, and `info.deleted?` predicates |
993
+ | `file.untracked` | `info.untracked?` |
994
+ | `file.stage` | gone; `info.unmerged?` and `info.unmerged_stages` describe conflicted entries |
995
+ | `file.sha_repo` | `info.sha_head`, or `info.sha_index` in a repository with no commits |
996
+ | `file.mode_repo` | `info.mode_head`, or `info.mode_index` in a repository with no commits |
997
+ | `file.sha_index` | `info.sha_index` for the staged blob; `info.worktree_status` says whether the working tree differs from it |
998
+ | `file.mode_index` | `info.mode_worktree` |
999
+ | `file.blob` | `g.object(info.sha_index)` when `info.sha_index` is set and not all zeros — it is `nil` for untracked, ignored, and unmerged entries and all zeros when the path is not in the index; legacy `blob` returned `nil` without a lookup when no SHA was available and fell back to `sha_repo` when `sha_index` was `nil`. For an unmerged entry read a stage instead: `g.object(info.unmerged_stages[2][:sha])` |
1000
+ | `file.blob(:repo)` | `g.object(info.sha_head)` when `info.sha_head` is set and not all zeros — it is `nil` for untracked, ignored, and unmerged entries and all zeros when the path is not in HEAD |
1001
+
1002
+ #### `Git::Worktree` and `Git::Worktrees` deprecated
1003
+
1004
+ `Git::Worktree`, `Git::Worktrees`, `Git::Repository#worktree`,
1005
+ `Git::Repository#worktrees`, and `Git::Repository#worktrees_all` are deprecated
1006
+ and are removed in v6.0.0. Read worktree data through
1007
+ `Git::Repository#worktree_list`, which returns one `Git::WorktreeInfo` value
1008
+ object per worktree, and call the repository-level operations (`worktree_add`,
1009
+ `worktree_remove`, `worktree_move`, `worktree_lock`, `worktree_unlock`,
1010
+ `worktree_repair`, and `worktree_prune`) with the worktree path or its
1011
+ `Git::WorktreeInfo`. Return values are unchanged. Calling `g.worktree`,
1012
+ `g.worktrees`, or `g.worktrees_all`, constructing a `Git::Worktrees`, and calling
1013
+ `gcommit`, `add`, or `remove` on a `Git::Worktree` each emit a deprecation
1014
+ warning; the `dir`, `full`, `to_s`, and `to_a` readers on `Git::Worktree` do not.
1015
+ `g.worktrees` emits two warnings, one for itself and one for the `Git::Worktrees`
1016
+ it constructs, and `g.worktree(dir).add` emits one for `g.worktree` and one for
1017
+ `add`.
1018
+
1019
+ > **Return shape change:** `worktrees_all` returns `[directory, sha]` pairs and
1020
+ > omits the main worktree of a bare repository, which has no checked-out commit.
1021
+ > `worktree_list` returns `Git::WorktreeInfo` objects with `path`, `head`,
1022
+ > `branch` (the full refname, such as `refs/heads/main`, or `nil` when detached
1023
+ > or bare), `bare?`, `detached?`, `locked?` with `lock_reason`, and `prunable?`
1024
+ > with `prune_reason`. It includes the bare main worktree, with `head` and
1025
+ > `branch` set to `nil`. `Git::WorktreeInfo#to_s` is the path, so an entry can be
1026
+ > passed to any method that takes a worktree path.
1027
+ >
1028
+ > **`gcommit` return type:** `Git::Worktree#gcommit` returned a
1029
+ > `Git::Object::Commit` for a worktree obtained from `g.worktree(dir)` and a raw
1030
+ > SHA `String` for one obtained from `g.worktrees`. `info.head` is always a
1031
+ > `String` (or `nil` for a bare main worktree); call `g.gcommit(info.head)` for
1032
+ > the commit object.
1033
+ >
1034
+ > **`full` and `to_s`:** `Git::Worktree#full` and `#to_s` append the commitish
1035
+ > given at construction to the path, so entries from `g.worktrees` read
1036
+ > `"/path/to/wt <sha>"`. `Git::WorktreeInfo#to_s` is the path alone.
1037
+
1038
+ In the table, `dir` is the worktree path, `wt` is a `Git::Worktree`, and `info`
1039
+ is the `Git::WorktreeInfo` that replaces it.
1040
+
1041
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1042
+ |-----------------------------------------------------|-------------|
1043
+ | `g.worktrees_all` | `g.worktree_list.map { \|w\| [w.path, w.head] }` — includes a bare main worktree as `[path, nil]`; add `.reject(&:bare?)` before `map` to omit it as `worktrees_all` did |
1044
+ | `g.worktrees` | `g.worktree_list` — returns `Array<Git::WorktreeInfo>`; the deprecated call emits two warnings |
1045
+ | `g.worktrees[dir]` | `g.worktree_list.find { \|w\| w.path == dir }` — `nil` when not found; `dir` is the path as git reports it (absolute, with symlinks resolved), as before |
1046
+ | `g.worktrees.size` | `g.worktree_list.size` |
1047
+ | `g.worktrees.each { \|wt\| ... }` | `g.worktree_list.each { \|info\| ... }` |
1048
+ | `g.worktrees.to_s` | `g.worktree_list.map { \|w\| "#{w.path} #{w.head}\n" }.join` |
1049
+ | `g.worktrees.prune` | `g.worktree_prune` |
1050
+ | `g.worktree(dir).add` | `g.worktree_add(dir)` |
1051
+ | `g.worktree(dir, commitish).add` | `g.worktree_add(dir, commitish)` |
1052
+ | `g.worktree(dir).remove` | `g.worktree_remove(dir)` — or `g.worktree_remove(info)` |
1053
+ | `wt.gcommit` | `info.head` — always a `String`, or `nil` for a bare main worktree; `g.gcommit(info.head)` for the commit object |
1054
+ | `wt.dir` | `info.path` |
1055
+ | `wt.full`, `wt.to_s` | `info.path` — or `"#{info.path} #{info.head}"` for the descriptor that entries from `g.worktrees` produced |
1056
+ | `wt.to_a` | `[info.path]` |
1057
+
1058
+ #### `Git.clone` option renames
1059
+
1060
+ Three `Git.clone` options were renamed in v5.x. The v4.x names still work. Each
1061
+ deprecated option present on a call emits its own deprecation warning, so a call
1062
+ that uses two of them warns twice. Each value is passed through to the
1063
+ replacement option, except that `:path` is dropped when `:chdir` is also given.
1064
+
1065
+ > **Precedence and value notes:**
1066
+ > - `:path` and `:chdir` both run `git clone` from inside the given directory.
1067
+ > When both are given, `:chdir` wins and `:path` is dropped.
1068
+ > - `:recursive` carries its value over to `:recurse_submodules` unchanged.
1069
+ > `:recurse_submodules` also accepts a pathspec `String` or `Array<String>`
1070
+ > to initialize only a subset of submodules, which `:recursive` never did.
1071
+ > - `:remote` and `:origin` have the same effect (`git clone --origin name`).
1072
+
1073
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1074
+ |-----------------------------------------------------|-------------|
1075
+ | `Git.clone(url, dir, path: p)` | `Git.clone(url, dir, chdir: p)` |
1076
+ | `Git.clone(url, dir, recursive: true)` | `Git.clone(url, dir, recurse_submodules: true)` — or a pathspec `String` or `Array<String>` for a subset of submodules |
1077
+ | `Git.clone(url, dir, remote: name)` | `Git.clone(url, dir, origin: name)` |
1078
+
1079
+ #### `Git::Log` Enumerable interface deprecated
1080
+
1081
+ `Git::Log` is a query builder. Calling `each`, `size`, `to_s`, `first`, `last`, or
1082
+ `[]` directly on it runs the query and emits a deprecation warning; those methods
1083
+ are removed in v6.0.0. Call `Git::Log#execute` instead. It runs the query and
1084
+ returns a `Git::Log::Result`, which includes `Enumerable` and provides the same
1085
+ six methods. The chainable query methods on `Git::Log` (`since`, `author`,
1086
+ `between`, `path`, `max_count`, and so on) are unchanged.
1087
+
1088
+ `Git::Log` includes `Enumerable`, so every `Enumerable` method called on the
1089
+ builder (`map`, `select`, `count`, `to_a`, `include?`, and so on) goes through the
1090
+ deprecated `each` and emits its warning. Move those calls to the result as well,
1091
+ not only the six named methods.
1092
+
1093
+ > **Snapshot results:** `execute` returns a snapshot. The builder re-runs
1094
+ > `git log` only when a query method (`since`, `max_count`, and so on) has been
1095
+ > called since the last run, even with the same value as before, so calling
1096
+ > `execute` twice on an untouched builder returns equal results without a second
1097
+ > `git log`. Keep the result object when a chain of operations needs the same
1098
+ > commits rather than calling `g.log` again, which builds a new query.
1099
+
1100
+ In the table, `g` is a `Git::Repository`.
1101
+
1102
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1103
+ |-----------------------------------------------------|-------------|
1104
+ | `g.log.each { \|c\| ... }` | `g.log.execute.each { \|c\| ... }` |
1105
+ | `g.log.size` | `g.log.execute.size` |
1106
+ | `g.log.to_s` | `g.log.execute.to_s` — commits joined with newlines, as before |
1107
+ | `g.log.first`, `g.log.last` | `g.log.execute.first`, `g.log.execute.last` |
1108
+ | `g.log[i]`, `g.log[range]` | `g.log.execute[i]`, `g.log.execute[range]` |
1109
+ | any other `Enumerable` method on the log (`map`, `select`, `count`, `to_a`, `include?`, ...) | the same method on `g.log.execute` |
1110
+
1111
+ #### `Git::Object::Commit#set_commit` deprecated
1112
+
1113
+ `Git::Object::Commit#set_commit` is deprecated and is removed in v6.0.0. Call
1114
+ `from_data` instead; it takes the same parsed commit data hash and has the same
1115
+ effect.
1116
+
1117
+ | Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
1118
+ |-----------------------------------------------------|-------------|
1119
+ | `commit.set_commit(data)` | `commit.from_data(data)` |
1120
+
1121
+ #### `Git.export` `:remote` option deprecated
1122
+
1123
+ `Git.export` has always dropped a `:remote` option before calling `Git.clone`
1124
+ without telling the caller. Passing it now emits a deprecation warning. The option
1125
+ is still dropped, so the export itself is unchanged, and it will be removed in a
1126
+ future major release. Once it is removed, passing `:remote` raises `ArgumentError`
1127
+ like any other unsupported option (see [Unsupported options raise
1128
+ `ArgumentError`](#unsupported-options-raise-argumenterror)).
1129
+
1130
+ There is no replacement option. `:remote` renamed the clone's remote, and
1131
+ `Git.export` deletes the `.git` directory before returning, so the name was never
1132
+ observable in the result. Delete the option from the call.
1133
+
1134
+ | Deprecated call (works in v5.x, removed in a future major release) | Replacement |
1135
+ |--------------------------------------------------------------------|-------------|
1136
+ | `Git.export(url, dir, remote: name)` | `Git.export(url, dir)` |
1137
+
1138
+ ---