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/lib/git/branch.rb CHANGED
@@ -1,109 +1,558 @@
1
- require 'git/path'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'branch_info'
2
4
 
3
5
  module Git
4
- class Branch < Path
5
- attr_accessor :full, :remote, :name
6
+ # Represents a Git branch
7
+ #
8
+ # Branch objects provide access to branch metadata and operations like checkout,
9
+ # delete, and merge. They should be obtained via {Git::Repository#branch} or
10
+ # {Git::Repository#branches}, not constructed directly.
11
+ #
12
+ # @example Getting a branch
13
+ # git = Git.open('.')
14
+ # branch = git.branch('main')
15
+ # branch.checkout
16
+ #
17
+ # @example Listing branches
18
+ # git.branches.each { |b| puts b.name }
19
+ #
20
+ # @deprecated Use {Git::Repository::Branching#branch_list} and the
21
+ # name-based branch operations on {Git::Repository} instead
22
+ #
23
+ # {Git::Repository::Branching#branch_list} returns immutable
24
+ # {Git::BranchInfo} value objects. Operations that lived on this class are
25
+ # called on the repository with the branch name instead (for example
26
+ # {Git::Repository::Branching#checkout} and
27
+ # {Git::Repository::Branching#branch_delete}). Every operation on a
28
+ # `Git::Branch` emits a deprecation warning; the `full`, `name`, `remote`,
29
+ # `to_s`, and `to_a` readers do not.
30
+ #
31
+ # @api public
32
+ #
33
+ class Branch
34
+ # The full refname of this branch
35
+ #
36
+ # For local branches this is the short name (e.g. `'main'`). For
37
+ # remote-tracking branches obtained via {Git::Repository#branches} this includes
38
+ # the `remotes/` prefix (e.g. `'remotes/origin/main'`). Branches constructed
39
+ # by {Git::Remote#branch} use the `<remote>/<branch>` form (e.g.
40
+ # `'origin/main'`) which does **not** populate {#remote}.
41
+ #
42
+ # @example Local and remote-tracking branch full refnames
43
+ # git.branch('main').full #=> 'main'
44
+ # git.branch('remotes/origin/main').full #=> 'remotes/origin/main'
45
+ #
46
+ # @return [String] the full refname
47
+ #
48
+ attr_accessor :full
6
49
 
7
- def initialize(base, name)
8
- @full = name
50
+ # The remote for this branch, or `nil` for local or bare-name remote-tracking branches
51
+ #
52
+ # Set to a {Git::Remote} object only when this branch was initialized with a
53
+ # `remotes/<remote>/` or `refs/remotes/<remote>/` prefix. `nil` for local
54
+ # branches and for remote-tracking branches in `<remote>/<branch>` form
55
+ # (such as those returned by {Git::Remote#branch}).
56
+ #
57
+ # @example Local and remote-tracking branches
58
+ # git.branch('main').remote #=> nil
59
+ # git.branch('remotes/origin/main').remote #=> #<Git::Remote 'origin'>
60
+ # git.remote('origin').branch('main').remote #=> nil # uses 'origin/main' form
61
+ #
62
+ # @return [Git::Remote, nil] the remote object, or `nil`
63
+ #
64
+ attr_accessor :remote
65
+
66
+ # The short branch name without the remote prefix
67
+ #
68
+ # For both local and remote-tracking branches this is the bare branch
69
+ # name (e.g. `'main'` rather than `'remotes/origin/main'`).
70
+ #
71
+ # @example Local and remote-tracking branch short names
72
+ # git.branch('main').name #=> 'main'
73
+ # git.branch('remotes/origin/main').name #=> 'main'
74
+ #
75
+ # @return [String] the short branch name
76
+ #
77
+ attr_accessor :name
78
+
79
+ # Initialize a new Branch object
80
+ #
81
+ # @param base [Git::Repository] the git repository
82
+ #
83
+ # @param branch_info_or_name [Git::BranchInfo, String] branch info object or name string
84
+ #
85
+ # Passing a BranchInfo is preferred; String support is for backward compatibility.
86
+ #
87
+ # @note Use {Git::Repository#branch} or {Git::Repository#branches} instead of constructing directly
88
+ #
89
+ # @api private
90
+ #
91
+ def initialize(base, branch_info_or_name)
9
92
  @base = base
10
93
  @gcommit = nil
11
94
  @stashes = nil
12
- @remote, @name = parse_name(name)
95
+
96
+ initialize_from_argument(branch_info_or_name)
13
97
  end
14
98
 
99
+ # Returns the commit at the tip of this branch
100
+ #
101
+ # The result is memoized after the first call.
102
+ #
103
+ # @example Get the tip commit
104
+ # git.branch('main').gcommit #=> #<Git::Object ...>
105
+ #
106
+ # @return [Git::Object] the commit at the tip of this branch
107
+ #
108
+ # @deprecated Use {Git::Repository::ObjectOperations#gcommit} with the branch name instead
109
+ #
110
+ # Pass the branch name for a local branch, or `"remotes/#{remote}/#{name}"`
111
+ # (the value of {#full}) for a remote-tracking branch; the shorter
112
+ # `"#{remote}/#{name}"` can resolve a local branch of that name.
113
+ #
114
+ # @see Git::Repository::ObjectOperations#gcommit
115
+ #
15
116
  def gcommit
16
- @gcommit ||= @base.gcommit(@full)
117
+ Git::Deprecation.warn(
118
+ 'Git::Branch#gcommit is deprecated and will be removed in v6.0.0. ' \
119
+ 'Use Git::Repository#gcommit(name) or, for a remote-tracking branch, ' \
120
+ 'Git::Repository#gcommit("remotes/remote/name") instead.'
121
+ )
122
+ @gcommit ||= branch_repository.gcommit(@full)
17
123
  @gcommit
18
124
  end
19
125
 
126
+ # Returns the stash list for this repository
127
+ #
128
+ # This method ignores the branch receiver and returns every stash in the
129
+ # repository, so `git.branch('feature').stashes` and
130
+ # `git.branch('main').stashes` return the same entries. It is deprecated and
131
+ # will be removed in v6.0.0.
132
+ #
133
+ # The result is memoized after the first call.
134
+ #
135
+ # @example Iterate over stash entries (deprecated)
136
+ # git.branch('main').stashes.each { |s| puts s }
137
+ #
138
+ # @example The replacement
139
+ # repo.stash_infos.each { |info| puts info.message }
140
+ #
141
+ # @return [Git::Stashes] the stash list
142
+ #
143
+ # @deprecated Use {Git::Repository#stash_infos} instead
144
+ #
145
+ # @see Git::Repository#stash_infos
146
+ #
20
147
  def stashes
21
- @stashes ||= Git::Stashes.new(@base)
148
+ Git::Deprecation.warn(
149
+ 'Git::Branch#stashes is deprecated and will be removed in v6.0.0. ' \
150
+ 'It ignores the branch and returns all repository stashes. ' \
151
+ 'Use Git::Repository#stash_infos instead.'
152
+ )
153
+ # Git::Stashes is deprecated too; silence it so one stashes call emits one warning
154
+ @stashes ||= Git::Deprecation.silence { Git::Stashes.new(branch_repository) }
22
155
  end
23
156
 
157
+ # Checks out this branch, attempting to create it first if it does not already exist
158
+ #
159
+ # Branch creation is attempted via {#check_if_create}; any error from that
160
+ # step is silently ignored and the checkout proceeds regardless.
161
+ #
162
+ # **Note:** for remote-tracking branches (where {#remote} is not `nil`),
163
+ # `check_if_create` will attempt to create a *local* branch named {#name}
164
+ # as a side-effect before checking out {#full} (which typically results in
165
+ # a detached HEAD). This is a known limitation; see
166
+ # [ruby-git#1280](https://github.com/ruby-git/ruby-git/issues/1280).
167
+ #
168
+ # @example Check out a branch
169
+ # git = Git.open('.')
170
+ # git.branch('main').checkout
171
+ #
172
+ # @return [String] git's stdout from the checkout
173
+ #
174
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
175
+ #
176
+ # @deprecated Use {Git::Repository::Branching#checkout} with the branch name instead
177
+ #
178
+ # {Git::Repository::Branching#checkout} does not create a missing local
179
+ # branch, apart from the guess git makes on its own: with no `:no_guess`
180
+ # option, git creates a tracking branch when exactly one remote has a
181
+ # branch of that name. To reproduce the create-or-checkout behavior of
182
+ # this method, call {Git::Repository::Branching#branch_new} when
183
+ # {Git::Repository::Branching#local_branch?} is false, then
184
+ # {Git::Repository::Branching#checkout}. Pass `"remotes/#{remote}/#{name}"`
185
+ # (the value of {#full}) for a remote-tracking branch; the shorter
186
+ # `"#{remote}/#{name}"` can resolve a local branch of that name.
187
+ #
188
+ # @see Git::Repository::Branching#checkout
189
+ #
190
+ # @see Git::Repository::Branching#branch_new
191
+ #
24
192
  def checkout
193
+ Git::Deprecation.warn(
194
+ 'Git::Branch#checkout is deprecated and will be removed in v6.0.0. ' \
195
+ 'Use Git::Repository#checkout(name) or, for a remote-tracking branch, ' \
196
+ 'Git::Repository#checkout("remotes/remote/name") instead. Git::Repository#checkout does not ' \
197
+ 'create a missing local branch (beyond the guess git makes from a unique remote-tracking ' \
198
+ 'branch); call Git::Repository#branch_new first unless Git::Repository#local_branch? is true.'
199
+ )
25
200
  check_if_create
26
- @base.checkout(@full)
201
+ branch_repository.checkout(@full)
27
202
  end
28
203
 
204
+ # Archives this branch and writes the result to a file
205
+ #
206
+ # @example Archive to a tar file
207
+ # git.branch('main').archive('/tmp/main.tar', format: 'tar')
208
+ #
209
+ # @example Archive to a zip file
210
+ # git.branch('main').archive('/tmp/main.zip', format: 'zip')
211
+ #
212
+ # @example Archive a remote branch to a tgz file
213
+ # git.remote('origin').branch('main').archive('/tmp/main.tgz', format: 'tgz')
214
+ #
215
+ # @param file [String] path to the destination archive file
216
+ #
217
+ # @param opts [Hash] archive options (see {Git::Repository#archive})
218
+ #
219
+ # @option opts [String] :format ('zip') archive format for this wrapper:
220
+ # `'tar'`, `'zip'`, or `'tgz'`
221
+ #
222
+ # @option opts [String] :prefix (nil) prefix prepended to every filename
223
+ # in the archive
224
+ #
225
+ # @option opts [String] :path (nil) path within the tree to include in the
226
+ # archive
227
+ #
228
+ # @option opts [String] :remote (nil) retrieve the archive from a remote
229
+ # repository
230
+ #
231
+ # @option opts [Boolean, nil] :add_gzip (nil) apply gzip compression after
232
+ # writing the archive; set automatically when `format: 'tgz'` is given
233
+ #
234
+ # @return [String] the path to the written archive file
235
+ #
236
+ # @raise [ArgumentError] when archive options or destination path are invalid
237
+ #
238
+ # @raise [Git::FailedError] if `git archive` fails
239
+ #
240
+ # @deprecated Use {Git::Repository::ObjectOperations#archive} with the branch name instead
241
+ #
242
+ # Pass the branch name for a local branch, or `"remotes/#{remote}/#{name}"`
243
+ # (the value of {#full}) for a remote-tracking branch; the shorter
244
+ # `"#{remote}/#{name}"` can resolve a local branch of that name.
245
+ #
246
+ # @see Git::Repository::ObjectOperations#archive
247
+ #
29
248
  def archive(file, opts = {})
30
- @base.lib.archive(@full, file, opts)
249
+ Git::Deprecation.warn(
250
+ 'Git::Branch#archive is deprecated and will be removed in v6.0.0. ' \
251
+ 'Use Git::Repository#archive(name, file, opts) or, for a remote-tracking branch, ' \
252
+ 'Git::Repository#archive("remotes/remote/name", file, opts) instead.'
253
+ )
254
+ branch_repository.archive(@full, file, opts)
31
255
  end
32
256
 
33
- # g.branch('new_branch').in_branch do
34
- # # create new file
35
- # # do other stuff
36
- # return true # auto commits and switches back
37
- # end
257
+ # Checks out this branch for the duration of a block, then restores the original branch
258
+ #
259
+ # If the block returns a truthy value, all pending changes are committed with the
260
+ # given message before switching back to the original branch. If the block returns
261
+ # a falsy value, a hard reset is performed before switching back.
262
+ #
263
+ # **Note:** the restore checkout is not wrapped in `ensure`. If the block,
264
+ # the commit, or the reset raises an exception, the repository will be left
265
+ # checked out on this branch rather than restored to the original.
266
+ #
267
+ # @example Commit a new file on a feature branch
268
+ # git.branch('feature').in_branch('Add README') do
269
+ # File.write('README.md', '# Hello')
270
+ # git.add('README.md')
271
+ # true # commit and return to original branch
272
+ # end
273
+ #
274
+ # @param message [String] commit message used when the block returns truthy
275
+ #
276
+ # @return [String] git's stdout from the final checkout back to the original branch
277
+ #
278
+ # @raise [Git::FailedError] if any of the underlying git operations (checkout, commit, reset) fail
279
+ #
280
+ # @yield Executes the block with this branch checked out
281
+ #
282
+ # @yieldreturn [Object] return a truthy value to commit all changes, a falsy value to hard-reset
283
+ #
284
+ # @deprecated Use {Git::Repository::Branching#in_branch} with the branch name instead
285
+ #
286
+ # {Git::Repository::Branching#in_branch} does not create the branch and
287
+ # restores a detached HEAD to its original commit.
288
+ # It takes an existing local branch, so a remote-tracking `Git::Branch` has
289
+ # no direct replacement: this method checked out the remote-tracking ref,
290
+ # detaching HEAD. Create a local branch from that ref with
291
+ # {Git::Repository::Branching#branch_new} first.
292
+ #
293
+ # @see Git::Repository::Branching#in_branch
294
+ #
38
295
  def in_branch(message = 'in branch work')
39
- old_current = @base.lib.branch_current
40
- checkout
41
- if yield
42
- @base.commit_all(message)
43
- else
44
- @base.reset_hard
45
- end
46
- @base.checkout(old_current)
296
+ Git::Deprecation.warn(
297
+ 'Git::Branch#in_branch is deprecated and will be removed in v6.0.0. ' \
298
+ 'Use Git::Repository#in_branch(name, message) instead. It takes an existing local ' \
299
+ 'branch; for a remote-tracking branch, create a local branch from it first.'
300
+ )
301
+ old_current = branch_repository.current_branch
302
+ # checkout is deprecated too; silence it so one in_branch call emits one warning
303
+ Git::Deprecation.silence { checkout }
304
+ yield ? branch_repository.commit_all(message) : branch_repository.reset(nil, hard: true)
305
+ branch_repository.checkout(old_current)
47
306
  end
48
307
 
308
+ # Creates this branch if it does not already exist
309
+ #
310
+ # Silently ignores any error raised during branch creation (including the case
311
+ # where the branch already exists).
312
+ #
313
+ # @example Create a new branch
314
+ # git.branch('feature').create
315
+ #
316
+ # @return [nil]
317
+ #
318
+ # @deprecated Use {Git::Repository::Branching#branch_new} instead
319
+ #
320
+ # {Git::Repository::Branching#branch_new} raises {Git::FailedError} when
321
+ # the branch already exists rather than ignoring the error.
322
+ #
323
+ # @see Git::Repository::Branching#branch_new
324
+ #
49
325
  def create
326
+ Git::Deprecation.warn(
327
+ 'Git::Branch#create is deprecated and will be removed in v6.0.0. ' \
328
+ 'Use Git::Repository#branch_new instead.'
329
+ )
50
330
  check_if_create
51
331
  end
52
332
 
333
+ # Deletes this branch
334
+ #
335
+ # Remote-tracking branches (one where {#remote} is not `nil`) delete the
336
+ # local remote-tracking ref; they do not push a deletion to the remote.
337
+ #
338
+ # @example Delete a local branch
339
+ # git.branch('old-feature').delete
340
+ #
341
+ # @return [String] git's deletion output
342
+ #
343
+ # @raise [Git::Error] if the branch cannot be deleted
344
+ #
345
+ # @deprecated Use {Git::Repository::Branching#branch_delete} instead
346
+ #
347
+ # Pass the branch name for a local branch, or `"#{remote}/#{name}"` with
348
+ # `remotes: true` for a remote-tracking branch.
349
+ #
350
+ # @see Git::Repository::Branching#branch_delete
351
+ #
53
352
  def delete
54
- @base.lib.branch_delete(@name)
353
+ Git::Deprecation.warn(
354
+ 'Git::Branch#delete is deprecated and will be removed in v6.0.0. ' \
355
+ 'Use Git::Repository#branch_delete(name) or, for a remote-tracking branch, ' \
356
+ 'Git::Repository#branch_delete("remote/name", remotes: true) instead.'
357
+ )
358
+ if @remote
359
+ branch_repository.branch_delete("#{@remote.name}/#{@name}", remotes: true)
360
+ else
361
+ branch_repository.branch_delete(@name)
362
+ end
55
363
  end
56
364
 
57
- def current
58
- determine_current
365
+ # Returns true if this is the currently checked-out branch
366
+ #
367
+ # **Note:** this compares the current branch's short name against {#name}.
368
+ # For a remote-tracking branch (where {#remote} is not `nil`), {#name} is
369
+ # still the bare short name (e.g. `'main'`), so this will return `true`
370
+ # whenever the *local* branch with that name is checked out — not the
371
+ # remote-tracking ref itself.
372
+ #
373
+ # @example Check whether currently on main
374
+ # git.branch('main').current #=> true
375
+ #
376
+ # @return [Boolean] whether this branch is currently checked out
377
+ #
378
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
379
+ #
380
+ # @deprecated Compare {Git::Repository::Branching#current_branch} with the
381
+ # branch name instead
382
+ #
383
+ # @see Git::Repository::Branching#current_branch
384
+ #
385
+ def current # rubocop:disable Naming/PredicateMethod
386
+ Git::Deprecation.warn(
387
+ 'Git::Branch#current is deprecated and will be removed in v6.0.0. ' \
388
+ 'Use Git::Repository#current_branch == name instead.'
389
+ )
390
+ branch_repository.current_branch == @name
59
391
  end
60
392
 
393
+ # Returns true if this branch contains the given commit
394
+ #
395
+ # **Note:** this queries local branches by short name. For a remote-tracking
396
+ # branch (where {#remote} is not `nil`), it checks the *local* branch with
397
+ # the same {#name} rather than the remote-tracking ref, which may give an
398
+ # inaccurate result.
399
+ #
400
+ # @example Check if a commit is reachable from this branch
401
+ # git.branch('main').contains?('abc1234') #=> true
402
+ #
403
+ # @param commit [String] the commit SHA or ref to check
404
+ #
405
+ # @return [Boolean] whether this branch contains the given commit
406
+ #
407
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
408
+ #
409
+ # @deprecated Use {Git::Repository::Branching#branch_contains} with the
410
+ # commit and branch name instead
411
+ #
412
+ # {Git::Repository::Branching#branch_contains} returns the matching
413
+ # branch names as a String; test it with `empty?`.
414
+ #
415
+ # @see Git::Repository::Branching#branch_contains
416
+ #
61
417
  def contains?(commit)
62
- !@base.lib.branch_contains(commit, self.name).empty?
418
+ Git::Deprecation.warn(
419
+ 'Git::Branch#contains? is deprecated and will be removed in v6.0.0. ' \
420
+ 'Use !Git::Repository#branch_contains(commit, name).empty? instead.'
421
+ )
422
+ !branch_repository.branch_contains(commit, name).empty?
63
423
  end
64
424
 
425
+ # Merges a branch into this branch, or merges this branch into the current branch
426
+ #
427
+ # @overload merge(branch, message = nil)
428
+ #
429
+ # Temporarily checks out this branch, merges the given branch into it,
430
+ # then restores the original branch.
431
+ #
432
+ # **Note:** if `self` is a remote-tracking branch (where {#remote} is not
433
+ # `nil`), this delegates to {#checkout} which has the detached-HEAD
434
+ # side-effect described there. The remote-tracking ref will not be updated.
435
+ #
436
+ # @example Merge a feature branch into main
437
+ # git.branch('main').merge('feature')
438
+ #
439
+ # @param branch [String] the name of the branch to merge into this one
440
+ #
441
+ # @param message [String, nil] commit message for the merge commit
442
+ #
443
+ # @return [String] git's stdout from the final checkout back to the original branch
444
+ #
445
+ # @overload merge()
446
+ #
447
+ # Merges this branch into the currently checked-out branch.
448
+ #
449
+ # @example Merge main into the current branch
450
+ # git.branch('main').merge
451
+ #
452
+ # @return [String] git's stdout from the merge command
453
+ #
454
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
455
+ #
456
+ # @deprecated Use {Git::Repository::Merging#merge_into} in place of
457
+ # `merge(branch)` and {Git::Repository::Merging#merge} with the branch
458
+ # name in place of `merge()`
459
+ #
460
+ # {Git::Repository::Merging#merge_into} returns the merge's stdout, does
461
+ # not hard-reset after the merge, and restores a detached HEAD to its
462
+ # original commit.
463
+ # It takes an existing local branch, so a remote-tracking `Git::Branch` has
464
+ # no direct replacement: `merge(branch)` checked out the remote-tracking ref,
465
+ # detaching HEAD. Create a local branch from that ref with
466
+ # {Git::Repository::Branching#branch_new} first.
467
+ #
468
+ # @see Git::Repository::Merging#merge_into
469
+ #
470
+ # @see Git::Repository::Merging#merge
471
+ #
65
472
  def merge(branch = nil, message = nil)
66
473
  if branch
67
- in_branch do
68
- @base.merge(branch, message)
69
- false
70
- end
71
- # merge a branch into this one
474
+ merge_into_this_branch(branch, message)
72
475
  else
73
- # merge this branch into the current one
74
- @base.merge(@name)
476
+ merge_into_current_branch
75
477
  end
76
478
  end
77
479
 
480
+ # Updates the git ref for this branch to point to the given commit
481
+ #
482
+ # The target ref depends on whether {#remote} is set:
483
+ # - When {#remote} is not `nil` (i.e. the branch was initialized with a
484
+ # `remotes/<remote>/` or `refs/remotes/<remote>/` prefix), updates
485
+ # `refs/remotes/<remote>/<name>`.
486
+ # - Otherwise updates `refs/heads/<name>`. Note that branches in the
487
+ # `<remote>/<branch>` form (e.g. those returned by {Git::Remote#branch})
488
+ # have `remote == nil` and therefore update `refs/heads/<remote>/<name>`,
489
+ # **not** `refs/remotes/...`.
490
+ #
491
+ # @example Advance a local branch to a new commit
492
+ # git.branch('feature').update_ref('abc1234def5678')
493
+ #
494
+ # @param commit [String] the commit SHA to point this branch at
495
+ #
496
+ # @return [Git::CommandLine::Result] the result of calling `git update-ref`
497
+ #
498
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
499
+ #
500
+ # @deprecated Use {Git::Repository::Branching#update_ref} instead
501
+ #
502
+ # Pass the branch name for a local branch, or
503
+ # `"remotes/#{remote}/#{name}"` for a remote-tracking branch.
504
+ #
505
+ # @see Git::Repository::Branching#update_ref
506
+ #
78
507
  def update_ref(commit)
508
+ Git::Deprecation.warn(
509
+ 'Git::Branch#update_ref is deprecated and will be removed in v6.0.0. ' \
510
+ 'Use Git::Repository#update_ref(name, commit) or, for a remote-tracking branch, ' \
511
+ 'Git::Repository#update_ref("remotes/remote/name", commit) instead.'
512
+ )
79
513
  if @remote
80
- @base.lib.update_ref("refs/remotes/#{@remote.name}/#{@name}", commit)
514
+ branch_repository.update_ref("remotes/#{@remote.name}/#{@name}", commit)
81
515
  else
82
- @base.lib.update_ref("refs/heads/#{@name}", commit)
516
+ branch_repository.update_ref(@name, commit)
83
517
  end
84
518
  end
85
519
 
520
+ # Returns this branch as a single-element array containing its full refname
521
+ #
522
+ # @example Get branch as array
523
+ # git.branch('main').to_a #=> ['main']
524
+ #
525
+ # @return [Array<String>] a single-element array containing the full refname
526
+ #
86
527
  def to_a
87
528
  [@full]
88
529
  end
89
530
 
531
+ # Returns the full refname of this branch as a string
532
+ #
533
+ # @example Get branch as string
534
+ # git.branch('main').to_s #=> 'main'
535
+ #
536
+ # @return [String] the full refname
537
+ #
90
538
  def to_s
91
539
  @full
92
540
  end
93
541
 
94
- private
95
-
96
- def check_if_create
97
- @base.lib.branch_new(@name) rescue nil
98
- end
99
-
100
- def determine_current
101
- @base.lib.branch_current == @name
102
- end
103
-
542
+ # Regular expression for parsing branch refnames
543
+ #
544
+ # Matches full and short refnames, capturing an optional remote name and the
545
+ # branch name. Used internally to identify remote-tracking branches.
546
+ #
547
+ # @note This legacy string-constructor path does not resolve remote names
548
+ # containing `/`. Use {Git::Repository#branch_list} to build branch objects
549
+ # from remote-aware {Git::BranchInfo} values.
550
+ #
551
+ # @api private
552
+ #
104
553
  BRANCH_NAME_REGEXP = %r{
105
554
  ^
106
- # Optional 'refs/remotes/' at the beggining to specify a remote tracking branch
555
+ # Optional 'remotes/' or 'refs/remotes/' at the beginning to specify a remote tracking branch
107
556
  # with a <remote_name>. <remote_name> is nil if not present.
108
557
  (?:
109
558
  (?:(?:refs/)?remotes/)(?<remote_name>[^/]+)/
@@ -112,32 +561,133 @@ module Git
112
561
  $
113
562
  }x
114
563
 
115
- # Given a full branch name return an Array containing the remote and branch names.
564
+ private
565
+
566
+ # Dispatches initialization to the appropriate strategy
567
+ #
568
+ # @param branch_info_or_name [Git::BranchInfo, String] branch info or name string
569
+ #
570
+ # @return [nil]
571
+ #
572
+ # @api private
573
+ #
574
+ def initialize_from_argument(branch_info_or_name)
575
+ if branch_info_or_name.is_a?(Git::BranchInfo)
576
+ initialize_from_branch_info(branch_info_or_name)
577
+ else
578
+ initialize_from_name(branch_info_or_name)
579
+ end
580
+ end
581
+
582
+ # Initialize from a BranchInfo object (preferred path)
583
+ #
584
+ # @param branch_info [Git::BranchInfo] the branch info
585
+ #
586
+ # @return [nil]
587
+ #
588
+ def initialize_from_branch_info(branch_info)
589
+ @name = branch_info.short_name
590
+ remote_name = branch_info.remote_name
591
+ # Git::Remote is deprecated too; silence it so one Git::Branch call emits one warning
592
+ @remote = remote_name ? Git::Deprecation.silence { Git::Remote.new(@base, remote_name) } : nil
593
+ @full = @remote ? "remotes/#{@remote.name}/#{@name}" : @name
594
+ end
595
+
596
+ # Initialize from a string name (legacy path for backward compatibility)
597
+ #
598
+ # @param name [String] the branch name
599
+ #
600
+ # @return [nil]
601
+ #
602
+ def initialize_from_name(name)
603
+ @full = name
604
+ @remote, @name = parse_name(name)
605
+ end
606
+
607
+ # Parses a full branch name into remote and short branch name components
608
+ #
609
+ # Strips an optional `remotes/` or `refs/remotes/` prefix. Only inputs that
610
+ # begin with one of those prefixes yield a remote object; all other inputs
611
+ # (including `'origin/master'`) are treated as local branch names with a
612
+ # `nil` remote.
116
613
  #
117
- # Removes 'remotes' from the beggining of the name (if present).
118
- # Takes the second part (splittign by '/') as the remote name.
119
- # Takes the rest as the repo name (can also hold one or more '/').
614
+ # @example Local branches
615
+ # parse_name('master') #=> [nil, 'master']
616
+ # parse_name('origin/master') #=> [nil, 'origin/master']
120
617
  #
121
- # Example:
122
- # # local branches
123
- # parse_name('master') #=> [nil, 'master']
124
- # parse_name('origin/master') #=> [nil, 'origin/master']
125
- # parse_name('origin/master/v2') #=> [nil, 'origin/master']
618
+ # @example Remote-tracking branches
619
+ # parse_name('remotes/origin/master') #=> [#<Git::Remote 'origin'>, 'master']
620
+ # parse_name('refs/remotes/origin/master') #=> [#<Git::Remote 'origin'>, 'master']
126
621
  #
127
- # # remote branches
128
- # parse_name('remotes/origin/master') #=> ['origin', 'master']
129
- # parse_name('remotes/origin/master/v2') #=> ['origin', 'master/v2']
130
- # parse_name('refs/remotes/origin/master') #=> ['origin', 'master']
131
- # parse_name('refs/remotes/origin/master/v2') #=> ['origin', 'master/v2']
622
+ # @param name [String] the full branch name to parse
623
+ #
624
+ # @return [Array(Git::Remote, String)] a two-element array; the first element is
625
+ # a {Git::Remote} for remote-tracking branches or `nil` for local branches,
626
+ # and the second element is the short branch name
132
627
  #
133
- # param [String] name branch full name.
134
- # return [<Git::Remote,NilClass,String>] an Array containing the remote and branch names.
135
628
  def parse_name(name)
136
629
  # Expect this will always match
137
630
  match = name.match(BRANCH_NAME_REGEXP)
138
- remote = match[:remote_name] ? Git::Remote.new(@base, match[:remote_name]) : nil
631
+ remote_name = match[:remote_name]
632
+ # Git::Remote is deprecated too; silence it so one Git::Branch call emits one warning
633
+ remote = remote_name ? Git::Deprecation.silence { Git::Remote.new(@base, remote_name) } : nil
139
634
  branch_name = match[:branch_name]
140
- [ remote, branch_name ]
635
+ [remote, branch_name]
636
+ end
637
+
638
+ # Merges the given branch into this branch, then restores the original branch
639
+ #
640
+ # @param branch [String] the name of the branch to merge into this one
641
+ #
642
+ # @param message [String, nil] commit message for the merge commit
643
+ #
644
+ # @return [String] git's stdout from the final checkout back to the original branch
645
+ #
646
+ # @api private
647
+ #
648
+ def merge_into_this_branch(branch, message)
649
+ Git::Deprecation.warn(
650
+ 'Git::Branch#merge(branch) is deprecated and will be removed in v6.0.0. ' \
651
+ 'Use Git::Repository#merge_into(name, branch, message) instead. It takes an existing ' \
652
+ 'local branch; for a remote-tracking branch, create a local branch from it first.'
653
+ )
654
+ # in_branch is deprecated too; silence it so one merge call emits one warning.
655
+ # The falsy block value makes in_branch hard-reset instead of committing.
656
+ Git::Deprecation.silence do
657
+ in_branch { branch_repository.merge(branch, message) && false }
658
+ end
659
+ end
660
+
661
+ # Merges this branch into the currently checked-out branch
662
+ #
663
+ # @return [String] git's stdout from the merge command
664
+ #
665
+ # @api private
666
+ #
667
+ def merge_into_current_branch
668
+ Git::Deprecation.warn(
669
+ 'Git::Branch#merge with no arguments is deprecated and will be removed in v6.0.0. ' \
670
+ 'Use Git::Repository#merge(name) instead.'
671
+ )
672
+ branch_repository.merge(@name)
673
+ end
674
+
675
+ # Creates the branch if it does not already exist, ignoring errors
676
+ #
677
+ # @return [nil]
678
+ #
679
+ def check_if_create
680
+ branch_repository.branch_new(@name)
681
+ rescue StandardError
682
+ nil
683
+ end
684
+
685
+ # @return [Git::Repository]
686
+ #
687
+ # @api private
688
+ #
689
+ def branch_repository
690
+ @base
141
691
  end
142
692
  end
143
693
  end