git 4.3.2 → 5.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +67 -2705
  3. data/.github/pull_request_template.md +3 -1
  4. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  5. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  6. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  7. data/.github/skills/command-implementation/SKILL.md +229 -0
  8. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  9. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  10. data/.github/skills/dependency-management/SKILL.md +72 -0
  11. data/.github/skills/development-workflow/SKILL.md +506 -0
  12. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  13. data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
  14. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  15. data/.github/skills/facade-implementation/SKILL.md +260 -0
  16. data/.github/skills/facade-test-conventions/SKILL.md +380 -0
  17. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  18. data/.github/skills/make-skill-template/SKILL.md +176 -0
  19. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  20. data/.github/skills/project-context/SKILL.md +313 -0
  21. data/.github/skills/pull-request-review/SKILL.md +168 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  23. data/.github/skills/release-management/SKILL.md +125 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  25. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  26. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  27. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  28. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  29. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  30. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  31. data/.github/skills/test-debugging/SKILL.md +160 -0
  32. data/.github/skills/yard-documentation/SKILL.md +793 -0
  33. data/.github/workflows/continuous_integration.yml +3 -2
  34. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  35. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  36. data/.github/workflows/release.yml +3 -4
  37. data/.gitignore +8 -0
  38. data/.husky/pre-commit +13 -0
  39. data/.release-please-manifest.json +1 -1
  40. data/.rspec +3 -0
  41. data/.rubocop.yml +7 -3
  42. data/.rubocop_todo.yml +23 -5
  43. data/.yardopts +1 -0
  44. data/CHANGELOG.md +0 -40
  45. data/CONTRIBUTING.md +694 -53
  46. data/README.md +17 -5
  47. data/Rakefile +61 -9
  48. data/commitlint.test +4 -0
  49. data/git.gemspec +14 -8
  50. data/lib/git/args_builder.rb +0 -8
  51. data/lib/git/base.rb +486 -410
  52. data/lib/git/branch.rb +380 -43
  53. data/lib/git/branch_delete_failure.rb +31 -0
  54. data/lib/git/branch_delete_result.rb +63 -0
  55. data/lib/git/branch_info.rb +178 -0
  56. data/lib/git/branches.rb +130 -24
  57. data/lib/git/command_line/base.rb +245 -0
  58. data/lib/git/command_line/capturing.rb +249 -0
  59. data/lib/git/command_line/result.rb +96 -0
  60. data/lib/git/command_line/streaming.rb +194 -0
  61. data/lib/git/command_line.rb +43 -322
  62. data/lib/git/command_line_result.rb +4 -88
  63. data/lib/git/commands/add.rb +131 -0
  64. data/lib/git/commands/am/abort.rb +43 -0
  65. data/lib/git/commands/am/apply.rb +252 -0
  66. data/lib/git/commands/am/continue.rb +43 -0
  67. data/lib/git/commands/am/quit.rb +43 -0
  68. data/lib/git/commands/am/retry.rb +47 -0
  69. data/lib/git/commands/am/show_current_patch.rb +64 -0
  70. data/lib/git/commands/am/skip.rb +42 -0
  71. data/lib/git/commands/am.rb +33 -0
  72. data/lib/git/commands/apply.rb +237 -0
  73. data/lib/git/commands/archive/list_formats.rb +46 -0
  74. data/lib/git/commands/archive.rb +140 -0
  75. data/lib/git/commands/arguments.rb +3510 -0
  76. data/lib/git/commands/base.rb +403 -0
  77. data/lib/git/commands/branch/copy.rb +94 -0
  78. data/lib/git/commands/branch/create.rb +173 -0
  79. data/lib/git/commands/branch/delete.rb +80 -0
  80. data/lib/git/commands/branch/list.rb +162 -0
  81. data/lib/git/commands/branch/move.rb +94 -0
  82. data/lib/git/commands/branch/set_upstream.rb +86 -0
  83. data/lib/git/commands/branch/show_current.rb +49 -0
  84. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  85. data/lib/git/commands/branch.rb +34 -0
  86. data/lib/git/commands/cat_file/batch.rb +364 -0
  87. data/lib/git/commands/cat_file/filtered.rb +105 -0
  88. data/lib/git/commands/cat_file/raw.rb +210 -0
  89. data/lib/git/commands/cat_file.rb +49 -0
  90. data/lib/git/commands/checkout/branch.rb +151 -0
  91. data/lib/git/commands/checkout/files.rb +115 -0
  92. data/lib/git/commands/checkout.rb +38 -0
  93. data/lib/git/commands/checkout_index.rb +105 -0
  94. data/lib/git/commands/clean.rb +100 -0
  95. data/lib/git/commands/clone.rb +240 -0
  96. data/lib/git/commands/commit.rb +272 -0
  97. data/lib/git/commands/commit_tree.rb +100 -0
  98. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  99. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  100. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  101. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  102. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  103. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  104. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
  105. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  106. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  107. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  108. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  109. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  110. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  111. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  112. data/lib/git/commands/config_option_syntax.rb +56 -0
  113. data/lib/git/commands/describe.rb +155 -0
  114. data/lib/git/commands/diff.rb +656 -0
  115. data/lib/git/commands/diff_files.rb +518 -0
  116. data/lib/git/commands/diff_index.rb +496 -0
  117. data/lib/git/commands/fetch.rb +352 -0
  118. data/lib/git/commands/fsck.rb +136 -0
  119. data/lib/git/commands/gc.rb +132 -0
  120. data/lib/git/commands/grep.rb +338 -0
  121. data/lib/git/commands/init.rb +99 -0
  122. data/lib/git/commands/log.rb +632 -0
  123. data/lib/git/commands/ls_files.rb +191 -0
  124. data/lib/git/commands/ls_remote.rb +155 -0
  125. data/lib/git/commands/ls_tree.rb +131 -0
  126. data/lib/git/commands/maintenance/register.rb +75 -0
  127. data/lib/git/commands/maintenance/run.rb +104 -0
  128. data/lib/git/commands/maintenance/start.rb +66 -0
  129. data/lib/git/commands/maintenance/stop.rb +55 -0
  130. data/lib/git/commands/maintenance/unregister.rb +79 -0
  131. data/lib/git/commands/maintenance.rb +31 -0
  132. data/lib/git/commands/merge/abort.rb +44 -0
  133. data/lib/git/commands/merge/continue.rb +44 -0
  134. data/lib/git/commands/merge/quit.rb +46 -0
  135. data/lib/git/commands/merge/start.rb +245 -0
  136. data/lib/git/commands/merge.rb +28 -0
  137. data/lib/git/commands/merge_base.rb +86 -0
  138. data/lib/git/commands/mv.rb +77 -0
  139. data/lib/git/commands/name_rev.rb +114 -0
  140. data/lib/git/commands/pull.rb +377 -0
  141. data/lib/git/commands/push.rb +246 -0
  142. data/lib/git/commands/read_tree.rb +149 -0
  143. data/lib/git/commands/remote/add.rb +91 -0
  144. data/lib/git/commands/remote/get_url.rb +66 -0
  145. data/lib/git/commands/remote/list.rb +54 -0
  146. data/lib/git/commands/remote/prune.rb +61 -0
  147. data/lib/git/commands/remote/remove.rb +52 -0
  148. data/lib/git/commands/remote/rename.rb +69 -0
  149. data/lib/git/commands/remote/set_branches.rb +63 -0
  150. data/lib/git/commands/remote/set_head.rb +82 -0
  151. data/lib/git/commands/remote/set_url.rb +71 -0
  152. data/lib/git/commands/remote/set_url_add.rb +61 -0
  153. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  154. data/lib/git/commands/remote/show.rb +71 -0
  155. data/lib/git/commands/remote/update.rb +72 -0
  156. data/lib/git/commands/remote.rb +42 -0
  157. data/lib/git/commands/repack.rb +277 -0
  158. data/lib/git/commands/reset.rb +147 -0
  159. data/lib/git/commands/rev_parse.rb +297 -0
  160. data/lib/git/commands/revert/abort.rb +45 -0
  161. data/lib/git/commands/revert/continue.rb +57 -0
  162. data/lib/git/commands/revert/quit.rb +47 -0
  163. data/lib/git/commands/revert/skip.rb +44 -0
  164. data/lib/git/commands/revert/start.rb +153 -0
  165. data/lib/git/commands/revert.rb +29 -0
  166. data/lib/git/commands/rm.rb +114 -0
  167. data/lib/git/commands/show.rb +632 -0
  168. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  169. data/lib/git/commands/show_ref/exists.rb +78 -0
  170. data/lib/git/commands/show_ref/list.rb +145 -0
  171. data/lib/git/commands/show_ref/verify.rb +120 -0
  172. data/lib/git/commands/show_ref.rb +42 -0
  173. data/lib/git/commands/stash/apply.rb +75 -0
  174. data/lib/git/commands/stash/branch.rb +65 -0
  175. data/lib/git/commands/stash/clear.rb +41 -0
  176. data/lib/git/commands/stash/create.rb +58 -0
  177. data/lib/git/commands/stash/drop.rb +67 -0
  178. data/lib/git/commands/stash/list.rb +39 -0
  179. data/lib/git/commands/stash/pop.rb +78 -0
  180. data/lib/git/commands/stash/push.rb +103 -0
  181. data/lib/git/commands/stash/show.rb +149 -0
  182. data/lib/git/commands/stash/store.rb +63 -0
  183. data/lib/git/commands/stash.rb +38 -0
  184. data/lib/git/commands/status.rb +169 -0
  185. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  186. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  187. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  188. data/lib/git/commands/symbolic_ref.rb +38 -0
  189. data/lib/git/commands/tag/create.rb +139 -0
  190. data/lib/git/commands/tag/delete.rb +55 -0
  191. data/lib/git/commands/tag/list.rb +143 -0
  192. data/lib/git/commands/tag/verify.rb +71 -0
  193. data/lib/git/commands/tag.rb +26 -0
  194. data/lib/git/commands/update_ref/batch.rb +140 -0
  195. data/lib/git/commands/update_ref/delete.rb +92 -0
  196. data/lib/git/commands/update_ref/update.rb +106 -0
  197. data/lib/git/commands/update_ref.rb +42 -0
  198. data/lib/git/commands/version.rb +52 -0
  199. data/lib/git/commands/worktree/add.rb +140 -0
  200. data/lib/git/commands/worktree/list.rb +64 -0
  201. data/lib/git/commands/worktree/lock.rb +58 -0
  202. data/lib/git/commands/worktree/management_base.rb +51 -0
  203. data/lib/git/commands/worktree/move.rb +66 -0
  204. data/lib/git/commands/worktree/prune.rb +67 -0
  205. data/lib/git/commands/worktree/remove.rb +63 -0
  206. data/lib/git/commands/worktree/repair.rb +76 -0
  207. data/lib/git/commands/worktree/unlock.rb +47 -0
  208. data/lib/git/commands/worktree.rb +43 -0
  209. data/lib/git/commands/write_tree.rb +68 -0
  210. data/lib/git/commands.rb +89 -0
  211. data/lib/git/detached_head_info.rb +54 -0
  212. data/lib/git/diff.rb +297 -7
  213. data/lib/git/diff_file_numstat_info.rb +29 -0
  214. data/lib/git/diff_file_patch_info.rb +134 -0
  215. data/lib/git/diff_file_raw_info.rb +127 -0
  216. data/lib/git/diff_info.rb +169 -0
  217. data/lib/git/diff_path_status.rb +78 -19
  218. data/lib/git/diff_result.rb +32 -0
  219. data/lib/git/diff_stats.rb +59 -14
  220. data/lib/git/dirstat_info.rb +86 -0
  221. data/lib/git/errors.rb +65 -2
  222. data/lib/git/execution_context/global.rb +56 -0
  223. data/lib/git/execution_context/repository.rb +147 -0
  224. data/lib/git/execution_context.rb +482 -0
  225. data/lib/git/file_ref.rb +74 -0
  226. data/lib/git/fsck_object.rb +9 -9
  227. data/lib/git/fsck_result.rb +1 -1
  228. data/lib/git/lib.rb +1606 -1028
  229. data/lib/git/log.rb +15 -2
  230. data/lib/git/object.rb +92 -22
  231. data/lib/git/parsers/branch.rb +224 -0
  232. data/lib/git/parsers/cat_file.rb +111 -0
  233. data/lib/git/parsers/diff.rb +585 -0
  234. data/lib/git/parsers/fsck.rb +133 -0
  235. data/lib/git/parsers/grep.rb +42 -0
  236. data/lib/git/parsers/ls_tree.rb +58 -0
  237. data/lib/git/parsers/stash.rb +208 -0
  238. data/lib/git/parsers/tag.rb +257 -0
  239. data/lib/git/remote.rb +133 -9
  240. data/lib/git/repository/branching.rb +572 -0
  241. data/lib/git/repository/committing.rb +191 -0
  242. data/lib/git/repository/configuring.rb +156 -0
  243. data/lib/git/repository/diffing.rb +775 -0
  244. data/lib/git/repository/inspecting.rb +153 -0
  245. data/lib/git/repository/logging.rb +247 -0
  246. data/lib/git/repository/merging.rb +295 -0
  247. data/lib/git/repository/object_operations.rb +1101 -0
  248. data/lib/git/repository/path_resolver.rb +207 -0
  249. data/lib/git/repository/remote_operations.rb +753 -0
  250. data/lib/git/repository/shared_private.rb +51 -0
  251. data/lib/git/repository/staging.rb +390 -0
  252. data/lib/git/repository/stashing.rb +107 -0
  253. data/lib/git/repository/status_operations.rb +180 -0
  254. data/lib/git/repository/worktree_operations.rb +159 -0
  255. data/lib/git/repository.rb +264 -1
  256. data/lib/git/stash.rb +85 -4
  257. data/lib/git/stash_info.rb +104 -0
  258. data/lib/git/stashes.rb +130 -13
  259. data/lib/git/status.rb +224 -18
  260. data/lib/git/tag_delete_failure.rb +31 -0
  261. data/lib/git/tag_delete_result.rb +63 -0
  262. data/lib/git/tag_info.rb +105 -0
  263. data/lib/git/version.rb +109 -2
  264. data/lib/git/version_constraint.rb +81 -0
  265. data/lib/git/worktree.rb +120 -5
  266. data/lib/git/worktrees.rb +107 -7
  267. data/lib/git.rb +114 -18
  268. data/redesign/1_architecture_existing.md +54 -18
  269. data/redesign/2_architecture_redesign.md +365 -46
  270. data/redesign/3_architecture_implementation.md +1451 -54
  271. data/tasks/gem_tasks.rake +4 -0
  272. data/tasks/npm_tasks.rake +7 -0
  273. data/tasks/rspec.rake +48 -0
  274. data/tasks/test.rake +13 -1
  275. data/tasks/yard.rake +34 -7
  276. metadata +349 -20
  277. data/lib/git/index.rb +0 -6
  278. data/lib/git/path.rb +0 -38
  279. data/lib/git/working_directory.rb +0 -6
  280. /data/{release-please-config.json → .release-please-config.json} +0 -0
@@ -0,0 +1,753 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/config_option_syntax'
4
+ require 'git/commands/fetch'
5
+ require 'git/commands/pull'
6
+ require 'git/commands/push'
7
+ require 'git/commands/remote'
8
+ require 'git/remote'
9
+
10
+ require 'git/repository/shared_private'
11
+
12
+ module Git
13
+ class Repository
14
+ # Mixin that adds remote operation facade methods to {Git::Repository}
15
+ #
16
+ # Included by {Git::Repository}.
17
+ #
18
+ # @api public
19
+ #
20
+ module RemoteOperations
21
+ # Key normalizations for {#fetch} options
22
+ #
23
+ # Maps dash-style option keys (which the 4.x `Git::Lib#fetch` accepted)
24
+ # to their canonical underscore-style equivalents.
25
+ #
26
+ # @return [Hash{Symbol => Symbol}]
27
+ #
28
+ # @api private
29
+ #
30
+ FETCH_KEY_NORMALIZATIONS = { 'update-head-ok': :update_head_ok, 'prune-tags': :prune_tags }.freeze
31
+ private_constant :FETCH_KEY_NORMALIZATIONS
32
+
33
+ # Option keys accepted by {#fetch}
34
+ #
35
+ # Derived from the 4.x `FETCH_OPTION_MAP` in `Git::Lib`.
36
+ #
37
+ # @return [Array<Symbol>]
38
+ #
39
+ # @api private
40
+ #
41
+ FETCH_ALLOWED_OPTS = %i[all tags t prune p prune_tags P force f update_head_ok u unshallow depth ref].freeze
42
+ private_constant :FETCH_ALLOWED_OPTS
43
+
44
+ # Download objects and refs from a remote repository
45
+ #
46
+ # Fetches branches and/or tags from one or more other repositories, along
47
+ # with the objects necessary to complete their histories. The local
48
+ # tracking references are updated but the working directory is not
49
+ # modified.
50
+ #
51
+ # @example Fetch from the default remote
52
+ # repo.fetch
53
+ #
54
+ # @example Fetch from a named remote
55
+ # repo.fetch('upstream')
56
+ #
57
+ # @example Fetch all remotes at once
58
+ # repo.fetch(all: true)
59
+ #
60
+ # @example Fetch and prune deleted remote branches
61
+ # repo.fetch('origin', prune: true)
62
+ #
63
+ # @example Fetch a specific refspec
64
+ # repo.fetch('origin', ref: 'refs/heads/main:refs/remotes/origin/main')
65
+ #
66
+ # @example Fetch multiple refspecs
67
+ # repo.fetch('origin', ref: ['refs/heads/main', 'refs/heads/develop'])
68
+ #
69
+ # @example Fetch and include all tags
70
+ # repo.fetch('origin', tags: true)
71
+ #
72
+ # @overload fetch(remote = 'origin', opts = {})
73
+ #
74
+ # @param remote [String, Hash, nil] the remote name or URL to fetch from
75
+ #
76
+ # When a Hash is given it is treated as `opts` and `remote` defaults to
77
+ # `nil` (which omits the remote positional argument and lets git use the
78
+ # configured default).
79
+ #
80
+ # @param opts [Hash] options for the fetch command
81
+ #
82
+ # @option opts [Boolean, nil] :all (nil) fetch from all configured remotes
83
+ # (`--all`)
84
+ #
85
+ # @option opts [Boolean, nil] :tags (nil) fetch all tags from the remote
86
+ # (`--tags`)
87
+ #
88
+ # Alias: `:t`
89
+ #
90
+ # @option opts [Boolean, nil] :prune (nil) remove remote-tracking references
91
+ # that no longer exist on the remote (`--prune`)
92
+ #
93
+ # Alias: `:p`
94
+ #
95
+ # @option opts [Boolean, nil] :prune_tags (nil) remove local tags that no
96
+ # longer exist on the remote (`--prune-tags`)
97
+ #
98
+ # Alias: `:P`. The legacy dash-style key `:'prune-tags'` is also accepted
99
+ # and normalized automatically.
100
+ #
101
+ # @option opts [Boolean, nil] :force (nil) override the fast-forward check
102
+ # when using explicit refspecs (`--force`)
103
+ #
104
+ # Alias: `:f`
105
+ #
106
+ # @option opts [Boolean, nil] :update_head_ok (nil) allow `git fetch` to
107
+ # update the branch pointed to by `HEAD` (`--update-head-ok`)
108
+ #
109
+ # Alias: `:u`. The legacy dash-style key `:'update-head-ok'` is also
110
+ # accepted and normalized automatically.
111
+ #
112
+ # @option opts [Boolean, nil] :unshallow (nil) convert a shallow clone into a
113
+ # full repository (`--unshallow`)
114
+ #
115
+ # @option opts [String, Integer] :depth (nil) limit history to N commits
116
+ # from each branch tip (`--depth=N`)
117
+ #
118
+ # @option opts [String, Array<String>] :ref (nil) one or more refspecs to
119
+ # fetch; forwarded as positional arguments after the remote name. An
120
+ # explicit `remote` is required when `:ref` is given.
121
+ #
122
+ # @return [String] the merged stdout from the fetch command
123
+ #
124
+ # @raise [ArgumentError] when unsupported option keys are provided or `:ref`
125
+ # is supplied without an explicit remote
126
+ #
127
+ # @raise [Git::FailedError] when git exits with a non-zero status
128
+ #
129
+ def fetch(remote = 'origin', opts = {})
130
+ remote, opts = Private.resolve_fetch_target(remote, opts)
131
+
132
+ opts = Private.normalize_fetch_keys(opts)
133
+ SharedPrivate.assert_valid_opts!(FETCH_ALLOWED_OPTS, **opts)
134
+
135
+ opts = opts.dup
136
+ refspecs = Array(opts.delete(:ref)).compact
137
+ positionals = [*([remote] if remote), *refspecs]
138
+
139
+ Git::Commands::Fetch.new(@execution_context).call(*positionals, **opts, merge: true).stdout
140
+ end
141
+
142
+ # Option keys accepted by {#pull}
143
+ #
144
+ # Derived from the 4.x `PULL_OPTION_MAP` in `Git::Lib`.
145
+ #
146
+ # @return [Array<Symbol>]
147
+ #
148
+ # @api private
149
+ #
150
+ PULL_ALLOWED_OPTS = %i[allow_unrelated_histories].freeze
151
+ private_constant :PULL_ALLOWED_OPTS
152
+
153
+ # Incorporate changes from a remote repository into the current branch
154
+ #
155
+ # Fetches from the given remote and merges into the current branch. In its
156
+ # default mode, `git pull` is shorthand for `git fetch` followed by
157
+ # `git merge FETCH_HEAD`. The merge editor is suppressed (`--no-edit`) and
158
+ # progress output is silenced (`--no-progress`) by default.
159
+ #
160
+ # @example Pull from the default remote and branch
161
+ # repo.pull
162
+ #
163
+ # @example Pull from a named remote
164
+ # repo.pull('upstream')
165
+ #
166
+ # @example Pull a specific branch from a remote
167
+ # repo.pull('origin', 'main')
168
+ #
169
+ # @example Pull allowing unrelated histories
170
+ # repo.pull('origin', 'main', allow_unrelated_histories: true)
171
+ #
172
+ # @overload pull(remote = nil, branch = nil, opts = {})
173
+ #
174
+ # @param remote [String, nil] the remote name or URL to pull from
175
+ #
176
+ # When nil, git uses the tracking remote for the current branch.
177
+ #
178
+ # @param branch [String, nil] the remote branch name to pull
179
+ #
180
+ # When nil, git uses the tracking branch for the current branch.
181
+ # A branch may not be specified without also specifying a remote.
182
+ #
183
+ # @param opts [Hash] options for the pull command
184
+ #
185
+ # @option opts [Boolean, nil] :allow_unrelated_histories (nil) allow merging
186
+ # histories that do not share a common ancestor
187
+ # (`--allow-unrelated-histories`)
188
+ #
189
+ # @return [String] the stdout from the pull command
190
+ #
191
+ # @raise [ArgumentError] when a branch is given without a remote, or when
192
+ # unsupported option keys are provided
193
+ #
194
+ # @raise [Git::FailedError] when git exits with a non-zero status
195
+ #
196
+ def pull(remote = nil, branch = nil, opts = {})
197
+ raise ArgumentError, 'You must specify a remote if a branch is specified' if remote.nil? && !branch.nil?
198
+
199
+ SharedPrivate.assert_valid_opts!(PULL_ALLOWED_OPTS, **opts)
200
+ positional_args = [remote, branch].compact
201
+ Git::Commands::Pull
202
+ .new(@execution_context)
203
+ .call(*positional_args, no_edit: true, no_progress: true, **opts)
204
+ .stdout
205
+ end
206
+
207
+ # Option keys accepted by {#push}
208
+ #
209
+ # Derived from the 4.x `PUSH_OPTION_MAP` in `Git::Lib`.
210
+ #
211
+ # @return [Array<Symbol>]
212
+ #
213
+ # @api private
214
+ #
215
+ PUSH_ALLOWED_OPTS = %i[mirror delete force f push_option all tags].freeze
216
+ private_constant :PUSH_ALLOWED_OPTS
217
+
218
+ # Push refs to a remote repository
219
+ #
220
+ # @example Push using the current branch's default remote and push configuration
221
+ # repo.push
222
+ #
223
+ # @example Push to a named remote
224
+ # repo.push('origin')
225
+ #
226
+ # @example Force-push the current branch to a named remote
227
+ # repo.push('origin', force: true)
228
+ #
229
+ # @example Push a specific branch to a named remote
230
+ # repo.push('origin', 'main')
231
+ #
232
+ # @example Push a branch and all tags to a named remote
233
+ # repo.push('origin', 'main', tags: true)
234
+ #
235
+ # @example Push all branches to a named remote
236
+ # repo.push('origin', all: true)
237
+ #
238
+ # @example Mirror all refs to a named remote
239
+ # repo.push('origin', mirror: true)
240
+ #
241
+ # @overload push(options = {})
242
+ # Push using the current branch's default remote and push configuration
243
+ #
244
+ # @param options [Hash] push options (see option list below)
245
+ #
246
+ # @option options [Boolean, nil] :all (nil) push all branches (`--all`)
247
+ #
248
+ # @option options [Boolean, nil] :mirror (nil) push all refs under
249
+ # `refs/` to the remote (`--mirror`)
250
+ #
251
+ # When `:tags` is also given, the separate tags push is suppressed.
252
+ #
253
+ # @option options [Boolean, nil] :tags (nil) push all refs under
254
+ # `refs/tags/` in a second `git push` invocation (`--tags`)
255
+ #
256
+ # When `:mirror` is also given, the tags push is suppressed because
257
+ # `--mirror` already includes tags.
258
+ #
259
+ # @option options [Boolean, nil] :force (nil) force updates,
260
+ # overriding the fast-forward check (`--force`)
261
+ #
262
+ # Alias: `:f`
263
+ #
264
+ # @option options [Boolean, nil] :delete (nil) delete the named refs
265
+ # from the remote (`--delete`)
266
+ #
267
+ # @option options [String, Array<String>] :push_option (nil) one or
268
+ # more server-side push option values (`--push-option=<value>`,
269
+ # repeatable)
270
+ #
271
+ # @return [String] the stdout from the push command
272
+ #
273
+ # @overload push(remote, options = {})
274
+ # Push to the given remote using the current branch's default push configuration
275
+ #
276
+ # @param remote [String] the remote name or URL to push to
277
+ #
278
+ # @param options [Hash] push options (see option list below)
279
+ #
280
+ # @option options [Boolean, nil] :all (nil) push all branches (`--all`)
281
+ #
282
+ # @option options [Boolean, nil] :mirror (nil) push all refs under
283
+ # `refs/` to the remote (`--mirror`)
284
+ #
285
+ # When `:tags` is also given, the separate tags push is suppressed.
286
+ #
287
+ # @option options [Boolean, nil] :tags (nil) push all refs under
288
+ # `refs/tags/` in a second `git push` invocation (`--tags`)
289
+ #
290
+ # When `:mirror` is also given, the tags push is suppressed because
291
+ # `--mirror` already includes tags.
292
+ #
293
+ # @option options [Boolean, nil] :force (nil) force updates,
294
+ # overriding the fast-forward check (`--force`)
295
+ #
296
+ # Alias: `:f`
297
+ #
298
+ # @option options [Boolean, nil] :delete (nil) delete the named refs
299
+ # from the remote (`--delete`)
300
+ #
301
+ # @option options [String, Array<String>] :push_option (nil) one or
302
+ # more server-side push option values (`--push-option=<value>`,
303
+ # repeatable)
304
+ #
305
+ # @return [String] the stdout from the push command
306
+ #
307
+ # @overload push(remote, branch, options = {})
308
+ # Push a branch or refspec to the given remote
309
+ #
310
+ # @param remote [String] the remote name or URL to push to
311
+ #
312
+ # @param branch [String] the branch name or refspec to push
313
+ #
314
+ # @param options [Hash] push options (see option list below)
315
+ #
316
+ # @option options [Boolean, nil] :all (nil) push all branches (`--all`)
317
+ #
318
+ # @option options [Boolean, nil] :mirror (nil) push all refs under
319
+ # `refs/` to the remote (`--mirror`)
320
+ #
321
+ # When `:tags` is also given, the separate tags push is suppressed.
322
+ #
323
+ # @option options [Boolean, nil] :tags (nil) push all refs under
324
+ # `refs/tags/` in a second `git push` invocation (`--tags`)
325
+ #
326
+ # When `:mirror` is also given, the tags push is suppressed because
327
+ # `--mirror` already includes tags.
328
+ #
329
+ # @option options [Boolean, nil] :force (nil) force updates,
330
+ # overriding the fast-forward check (`--force`)
331
+ #
332
+ # Alias: `:f`
333
+ #
334
+ # @option options [Boolean, nil] :delete (nil) delete the named refs
335
+ # from the remote (`--delete`)
336
+ #
337
+ # @option options [String, Array<String>] :push_option (nil) one or
338
+ # more server-side push option values (`--push-option=<value>`,
339
+ # repeatable)
340
+ #
341
+ # @return [String] the stdout from the push command
342
+ #
343
+ # @raise [ArgumentError] if `remote` is nil when `branch` is given
344
+ #
345
+ # @overload push(remote, branch, tags)
346
+ # Backward-compatible shorthand for `push(remote, branch, tags: tags)`
347
+ #
348
+ # @param remote [String] the remote name or URL to push to
349
+ #
350
+ # @param branch [String] the branch name or refspec to push
351
+ #
352
+ # @param tags [Boolean] whether to push all tags; equivalent to `tags: tags`
353
+ #
354
+ # @return [String] the stdout from the push command
355
+ #
356
+ # @raise [ArgumentError] if a branch is given and remote is nil
357
+ #
358
+ # @raise [ArgumentError] when unsupported option keys are provided
359
+ #
360
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
361
+ #
362
+ def push(remote = nil, branch = nil, opts = nil)
363
+ remote, branch, opts = Private.normalize_push_args(remote, branch, opts)
364
+ SharedPrivate.assert_valid_opts!(PUSH_ALLOWED_OPTS, **opts)
365
+ raise ArgumentError, 'remote is required if branch is specified' if !remote && branch
366
+
367
+ first_result = Private.push_refs(@execution_context, remote, branch, opts)
368
+ return first_result.stdout unless Private.push_tags_separately?(opts)
369
+
370
+ Private.push_tags(@execution_context, remote, opts).stdout
371
+ end
372
+
373
+ # Option keys accepted by {#add_remote}
374
+ #
375
+ # Derived from the 4.x `REMOTE_ADD_OPTION_MAP` in `Git::Lib`.
376
+ ADD_REMOTE_ALLOWED_OPTS = %i[fetch track].freeze
377
+ private_constant :ADD_REMOTE_ALLOWED_OPTS
378
+
379
+ # Register a new remote in the local repository
380
+ #
381
+ # Associates `name` with `url` and optionally fetches immediately or
382
+ # configures which branches are tracked.
383
+ #
384
+ # @example Add a remote
385
+ # repo.add_remote('upstream', 'https://github.com/user/repo.git')
386
+ #
387
+ # @example Add a remote and fetch immediately
388
+ # repo.add_remote('upstream', 'https://github.com/user/repo.git', fetch: true)
389
+ #
390
+ # @example Add a remote tracking a specific branch
391
+ # repo.add_remote('upstream', 'https://github.com/user/repo.git', track: 'main')
392
+ #
393
+ # @param name [String] the name for the new remote
394
+ #
395
+ # @param url [String, Git::Base] the URL of the remote repository
396
+ #
397
+ # A {Git::Base} instance is accepted for local references and converted
398
+ # to `url.repo.to_s`.
399
+ #
400
+ # @param opts [Hash] options for adding the remote
401
+ #
402
+ # @option opts [Boolean, nil] :fetch (nil) fetch from the remote immediately
403
+ # after adding it (`-f`)
404
+ #
405
+ # The deprecated alias `:with_fetch` is accepted and normalized
406
+ # automatically.
407
+ #
408
+ # @option opts [String, nil] :track (nil) track only the given branch during
409
+ # fetch (`-t`)
410
+ #
411
+ # @return [Git::Remote] the newly added remote
412
+ #
413
+ # @raise [ArgumentError] when unsupported option keys are provided
414
+ #
415
+ # @raise [Git::FailedError] when git exits with a non-zero status
416
+ #
417
+ def add_remote(name, url, opts = {})
418
+ url = url.repo.to_s if url.is_a?(Git::Base)
419
+ opts = Private.normalize_add_remote_keys(opts)
420
+ SharedPrivate.assert_valid_opts!(ADD_REMOTE_ALLOWED_OPTS, **opts)
421
+ Git::Commands::Remote::Add.new(@execution_context).call(name, url, **opts)
422
+
423
+ Git::Remote.new(self, name)
424
+ end
425
+
426
+ # Removes a remote from this repository
427
+ #
428
+ # Deletes the remote named `name` along with its associated configuration,
429
+ # tracking references, and remote-tracking branches.
430
+ #
431
+ # @example Remove a remote named 'upstream'
432
+ # repo.remove_remote('upstream')
433
+ #
434
+ # @param name [String] the name of the remote to remove
435
+ #
436
+ # @return [Git::CommandLineResult] the result of calling `git remote remove`
437
+ #
438
+ # @raise [Git::FailedError] when git exits with a non-zero status
439
+ #
440
+ def remove_remote(name)
441
+ Git::Commands::Remote::Remove.new(@execution_context).call(name)
442
+ end
443
+
444
+ # Sets the URL for an existing remote
445
+ #
446
+ # Replaces the fetch URL configured for the remote named `name`.
447
+ #
448
+ # @example Set the URL for a remote
449
+ # repo.set_remote_url('origin', 'https://github.com/user/repo.git')
450
+ #
451
+ # @example Set the URL from a local repository reference
452
+ # source = Git.open('/path/to/source')
453
+ # repo.set_remote_url('origin', source)
454
+ #
455
+ # @param name [String] the name of the remote to update
456
+ #
457
+ # @param url [String, Git::Base] the new URL for the remote
458
+ #
459
+ # A {Git::Base} instance is accepted for local references and converted
460
+ # to `url.repo.to_s`.
461
+ #
462
+ # @return [Git::Remote] the updated remote
463
+ #
464
+ # @raise [Git::FailedError] when git exits with a non-zero status
465
+ #
466
+ def set_remote_url(name, url)
467
+ url = url.repo.to_s if url.is_a?(Git::Base)
468
+ Git::Commands::Remote::SetUrl.new(@execution_context).call(name, url)
469
+
470
+ Git::Remote.new(self, name)
471
+ end
472
+
473
+ # Configures which branches are fetched for a remote
474
+ #
475
+ # Uses `git remote set-branches` to set or append fetch refspecs. When the
476
+ # `add:` option is `false`, the `--add` flag is not passed to the git
477
+ # command and the tracked branch list is replaced.
478
+ #
479
+ # @example Replace fetched branches with a single glob pattern
480
+ # repo.remote_set_branches('origin', 'feature/*')
481
+ #
482
+ # @example Append a glob pattern to existing fetched branches
483
+ # repo.remote_set_branches('origin', 'release/*', add: true)
484
+ #
485
+ # @example Configure multiple explicit branches
486
+ # repo.remote_set_branches('origin', 'main', 'development', 'hotfix')
487
+ #
488
+ # @param name [String] the remote name (for example, `"origin"`)
489
+ #
490
+ # @param branches [Array<String>] branch names or globs (for example, `'*'`)
491
+ #
492
+ # @param add [Boolean] when `true`, append to existing refspecs instead of
493
+ # replacing them
494
+ #
495
+ # @return [void]
496
+ #
497
+ # @raise [ArgumentError] when no branches are provided
498
+ #
499
+ # @raise [Git::FailedError] when git exits with a non-zero status
500
+ #
501
+ def remote_set_branches(name, *branches, add: false)
502
+ branch_list = branches.flatten
503
+ raise ArgumentError, 'branches are required' if branch_list.empty?
504
+
505
+ Git::Commands::Remote::SetBranches.new(@execution_context).call(name, *branch_list, add: add)
506
+
507
+ nil
508
+ end
509
+
510
+ # Return the git configuration entries for a named remote
511
+ #
512
+ # Reads `git config --list` and returns all entries whose keys begin with
513
+ # `remote.<name>.`, with the `remote.<name>.` prefix stripped. This
514
+ # typically yields at least `"url"` and `"fetch"` for a configured remote.
515
+ #
516
+ # @example Retrieve the config for the 'origin' remote
517
+ # repo.config_remote('origin')
518
+ # #=> {
519
+ # # 'url' => 'https://github.com/user/repo.git',
520
+ # # 'fetch' => '+refs/heads/*:refs/remotes/origin/*'
521
+ # # }
522
+ #
523
+ # @param name [String] the name of the remote (e.g. `"origin"`)
524
+ #
525
+ # @return [Hash{String => String}] configuration entries for the remote,
526
+ # keyed without the `remote.<name>.` prefix
527
+ #
528
+ # Returns an empty hash when no entries are found.
529
+ #
530
+ # @raise [Git::FailedError] when git exits with a non-zero status
531
+ #
532
+ def config_remote(name)
533
+ prefix = "remote.#{name}."
534
+ Private.config_list(@execution_context).each_with_object({}) do |(key, value), hsh|
535
+ hsh[key.delete_prefix(prefix)] = value if key.start_with?(prefix)
536
+ end
537
+ end
538
+
539
+ # Returns a {Git::Remote} object for the named remote
540
+ #
541
+ # @example Get the default 'origin' remote
542
+ # repo.remote #=> #<Git::Remote 'origin'>
543
+ #
544
+ # @example Get a named remote
545
+ # repo.remote('upstream') #=> #<Git::Remote 'upstream'>
546
+ #
547
+ # @param name [String] the remote name (defaults to `'origin'`)
548
+ #
549
+ # @return [Git::Remote] the remote object
550
+ #
551
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
552
+ #
553
+ def remote(name = 'origin')
554
+ Git::Remote.new(self, name)
555
+ end
556
+
557
+ # Returns all configured remotes as {Git::Remote} objects
558
+ #
559
+ # @example List all remotes
560
+ # repo.remotes #=> [#<Git::Remote 'origin'>, #<Git::Remote 'upstream'>]
561
+ #
562
+ # @return [Array<Git::Remote>] one {Git::Remote} for each configured remote
563
+ #
564
+ # Returns an empty array when no remotes are configured.
565
+ #
566
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
567
+ #
568
+ def remotes
569
+ result = Git::Commands::Remote::List.new(@execution_context).call
570
+ result.stdout.split("\n").map { |name| Git::Remote.new(self, name) }
571
+ end
572
+
573
+ # Helpers private to the `RemoteOperations` topic module
574
+ #
575
+ # @api private
576
+ #
577
+ module Private
578
+ module_function
579
+
580
+ # Resolve the (remote, opts) pair for {#fetch}, supporting the hash-only form
581
+ #
582
+ # `fetch` may be called as `fetch(remote, opts)` or `fetch(opts)`. When a bare
583
+ # options hash is passed the remote is treated as nil. A `:ref` is only
584
+ # meaningful with an explicit remote, so requesting one without a remote (it
585
+ # would otherwise be silently promoted to the remote-name slot) is rejected.
586
+ #
587
+ # @param remote [String, Hash, nil] the remote name, or an options hash
588
+ # @param opts [Hash] the options hash when remote is given positionally
589
+ #
590
+ # @return [Array(String, Hash), Array(nil, Hash)] the resolved remote and opts
591
+ #
592
+ # @raise [ArgumentError] when :ref is supplied without an explicit remote
593
+ #
594
+ # @api private
595
+ #
596
+ def resolve_fetch_target(remote, opts)
597
+ if remote.is_a?(Hash)
598
+ opts = remote
599
+ remote = nil
600
+ end
601
+
602
+ raise ArgumentError, ':ref requires an explicit remote' if remote.nil? && opts.key?(:ref)
603
+
604
+ [remote, opts]
605
+ end
606
+
607
+ # Normalize dash-style option keys to their underscore equivalents
608
+ #
609
+ # Converts any key in {FETCH_KEY_NORMALIZATIONS} from its dash-style symbol
610
+ # form (e.g., `:'update-head-ok'`) to the canonical underscore-style form
611
+ # (e.g., `:update_head_ok`). Unrecognized keys are returned unchanged.
612
+ #
613
+ # @param opts [Hash] the raw options hash passed by the caller
614
+ #
615
+ # @return [Hash] a new hash with all applicable keys normalized
616
+ #
617
+ # @api private
618
+ #
619
+ def normalize_fetch_keys(opts)
620
+ opts.transform_keys do |k|
621
+ sym = k.is_a?(Symbol) ? k : k.to_sym
622
+ FETCH_KEY_NORMALIZATIONS.fetch(sym, sym)
623
+ end
624
+ end
625
+
626
+ # Normalize the flexible argument list accepted by {RemoteOperations#push}
627
+ #
628
+ # Handles three call forms:
629
+ # - `push(opts)` — Hash promoted from `remote` position
630
+ # - `push(remote, opts)` — Hash promoted from `branch` position
631
+ # - `push(remote, branch, true|false)` — Boolean `opts` converted to
632
+ # `{ tags: opts }` for backward compatibility
633
+ #
634
+ # @param remote [String, Hash, nil] remote name, URL, or opts hash
635
+ # @param branch [String, Hash, nil] branch/refspec, or opts hash
636
+ # @param opts [Hash, Boolean, nil] options hash or legacy Boolean shorthand
637
+ #
638
+ # @return [Array(String|nil, String|nil, Hash)] normalized [remote, branch, opts]
639
+ #
640
+ # @api private
641
+ #
642
+ def normalize_push_args(remote, branch, opts)
643
+ if branch.is_a?(Hash)
644
+ opts = branch
645
+ branch = nil
646
+ elsif remote.is_a?(Hash)
647
+ opts = remote
648
+ remote = nil
649
+ end
650
+
651
+ opts ||= {}
652
+
653
+ # Backwards compatibility for `push(remote, branch, true)` to push tags
654
+ # without requiring the caller to use keyword arguments
655
+
656
+ opts = { tags: opts } if [true, false].include?(opts)
657
+ [remote, branch, opts]
658
+ end
659
+
660
+ # Issue the refs push (first push when `:tags` is given separately)
661
+ #
662
+ # Strips `:tags` from the options so that only refs — not tags — are pushed
663
+ # in this first call. Tags are pushed in a separate call when
664
+ # {push_tags_separately?} is true.
665
+ #
666
+ # @param execution_context [Git::ExecutionContext::Repository] the repository execution context
667
+ # @param remote [String, nil] remote name or URL
668
+ # @param branch [String, nil] branch or refspec
669
+ # @param opts [Hash] push options (`:tags` key will be stripped)
670
+ #
671
+ # @return [Git::CommandLineResult]
672
+ #
673
+ # @api private
674
+ #
675
+ def push_refs(execution_context, remote, branch, opts)
676
+ positionals = [remote, branch].compact
677
+ Git::Commands::Push.new(execution_context).call(*positionals, **opts.except(:tags))
678
+ end
679
+
680
+ # Return true when tags must be pushed in a second separate invocation
681
+ #
682
+ # Tags are pushed separately when `:tags` is truthy AND `:mirror` is not set.
683
+ # When `:mirror` is set, the mirror push already includes all refs and tags,
684
+ # so a second tags-only call would be redundant.
685
+ #
686
+ # @param opts [Hash] the normalized push options
687
+ #
688
+ # @return [Boolean]
689
+ #
690
+ # @api private
691
+ #
692
+ def push_tags_separately?(opts)
693
+ opts[:tags] && !opts[:mirror]
694
+ end
695
+
696
+ # Issue the tags push (second push when `:tags` is requested without `:mirror`)
697
+ #
698
+ # @param execution_context [Git::ExecutionContext::Repository] the repository execution context
699
+ # @param remote [String, nil] remote name or URL
700
+ # @param opts [Hash] push options (`:tags` key included to emit `--tags`)
701
+ #
702
+ # @return [Git::CommandLineResult]
703
+ #
704
+ # @api private
705
+ #
706
+ def push_tags(execution_context, remote, opts)
707
+ Git::Commands::Push.new(execution_context).call(*[remote].compact, **opts)
708
+ end
709
+
710
+ # Normalize deprecated {#add_remote} option keys to their canonical equivalents
711
+ #
712
+ # Renames the deprecated `:with_fetch` key to `:fetch`, removing it from
713
+ # the copy. When both keys are present, `:with_fetch` takes precedence.
714
+ #
715
+ # @param opts [Hash] the raw options hash passed by the caller
716
+ #
717
+ # @return [Hash] a new hash with all applicable keys normalized
718
+ #
719
+ # @api private
720
+ #
721
+ def normalize_add_remote_keys(opts)
722
+ normalized = opts.dup
723
+ normalized[:fetch] = normalized.delete(:with_fetch) if normalized.key?(:with_fetch)
724
+ normalized
725
+ end
726
+
727
+ # Retrieve all config entries as a flat hash
728
+ #
729
+ # Runs `git config --list` and parses each `key=value` line into a hash.
730
+ # When no value is present for a key, the value defaults to an empty string.
731
+ #
732
+ # @param execution_context [Git::ExecutionContext::Repository] the
733
+ # execution context for the repository
734
+ #
735
+ # @return [Hash{String => String}] all visible config entries, keyed by
736
+ # their full dotted key names
737
+ #
738
+ # For example, `"remote.origin.url"` is a valid key.
739
+ #
740
+ # @api private
741
+ #
742
+ def config_list(execution_context)
743
+ lines = Git::Commands::ConfigOptionSyntax::List.new(execution_context).call.stdout.split("\n")
744
+ lines.each_with_object({}) do |line, hsh|
745
+ key, value = line.split('=', 2)
746
+ hsh[key] = value || ''
747
+ end
748
+ end
749
+ end
750
+ private_constant :Private
751
+ end
752
+ end
753
+ end