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
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/commands/rev_parse'
4
+ require 'git/errors'
5
+ require 'git/execution_context'
6
+ require 'git/execution_context/global'
7
+
8
+ module Git
9
+ # Resolves and normalizes the filesystem paths that locate a Git repository
10
+ #
11
+ # `PathResolver` is the single home for the path-resolution logic used by the
12
+ # `Git::Repository` factory class methods ({Git::Repository.open} and
13
+ # {Git::Repository.bare}). It computes the absolute working-directory,
14
+ # repository (`.git`), and index paths from the caller-supplied values,
15
+ # following the same rules Git itself uses (including gitdir-pointer files for
16
+ # submodules and linked worktrees).
17
+ #
18
+ # @api private
19
+ #
20
+ module PathResolver
21
+ module_function
22
+
23
+ # Resolve and normalize the paths that locate a Git repository
24
+ #
25
+ # Returns a new hash containing the resolved absolute paths for:
26
+ # * `:working_directory` — the working tree root (`nil` for bare repos)
27
+ # * `:repository` — the `.git` directory
28
+ # * `:index` — the index file
29
+ #
30
+ # This method does not mutate any inputs.
31
+ #
32
+ # @example Resolve paths for a working tree
33
+ # Git::PathResolver.resolve_paths(working_directory: '/repo')
34
+ # #=> { working_directory: '/repo', repository: '/repo/.git', index: '/repo/.git/index' }
35
+ #
36
+ # @param working_directory [String, nil] the working directory path
37
+ #
38
+ # @param repository [String, nil] the repository (`.git`) directory path
39
+ #
40
+ # @param index [String, nil] the index file path
41
+ #
42
+ # @param bare [Boolean] whether this is a bare repository
43
+ #
44
+ # @return [Hash{Symbol => (String, nil)}] a hash with `:working_directory`,
45
+ # `:repository`, and `:index` keys
46
+ #
47
+ def resolve_paths(working_directory: nil, repository: nil, index: nil, bare: false)
48
+ working_dir = resolve_working_directory(working_directory, bare: bare)
49
+ # For bare repos, use working_directory as the default repository location
50
+ repo_path = resolve_repository(repository, working_dir, bare: bare, bare_default: working_directory)
51
+ index_path = resolve_index(index, repo_path)
52
+
53
+ {
54
+ working_directory: working_dir,
55
+ repository: repo_path,
56
+ index: index_path
57
+ }
58
+ end
59
+
60
+ # Find the root of the working tree that contains `working_dir`
61
+ #
62
+ # Runs `git rev-parse --show-toplevel` from `working_dir` to locate the
63
+ # top-level directory of the working tree.
64
+ #
65
+ # @example Find the worktree root from a subdirectory
66
+ # Git::PathResolver.root_of_worktree('/repo/subdir') #=> '/repo'
67
+ #
68
+ # @param working_dir [String] a path inside the working tree
69
+ #
70
+ # @param binary_path [String, :use_global_config] path to the git binary
71
+ #
72
+ # Controls which git binary is invoked during root detection. Defaults to
73
+ # `:use_global_config`, which resolves to `Git.config.binary_path`.
74
+ #
75
+ # @param git_ssh [String, nil, :use_global_config] the SSH wrapper path
76
+ #
77
+ # Forwarded as `GIT_SSH`. Defaults to `:use_global_config`.
78
+ #
79
+ # @return [String] the absolute path to the root of the working tree
80
+ #
81
+ # @raise [ArgumentError] if `working_dir` does not exist, is not a
82
+ # directory, or is not inside a git working tree
83
+ #
84
+ # Also raised if the git binary cannot be found.
85
+ #
86
+ def root_of_worktree(working_dir, binary_path: :use_global_config, git_ssh: :use_global_config)
87
+ raise ArgumentError, "'#{working_dir}' does not exist or is not a directory" unless Dir.exist?(working_dir)
88
+
89
+ execute_rev_parse_toplevel(working_dir, binary_path: binary_path, git_ssh: git_ssh)
90
+ end
91
+
92
+ # Run `git rev-parse --show-toplevel` from `working_dir` and return stdout
93
+ #
94
+ # @param working_dir [String] a path inside the working tree
95
+ #
96
+ # @param binary_path [String, :use_global_config] path to the git binary
97
+ #
98
+ # @param git_ssh [String, nil, :use_global_config] the SSH wrapper path
99
+ #
100
+ # @return [String] the top-level directory reported by git
101
+ #
102
+ # @raise [ArgumentError] if the git binary is not found or `working_dir` is
103
+ # not inside a git working tree
104
+ #
105
+ # @api private
106
+ #
107
+ def execute_rev_parse_toplevel(working_dir, binary_path: :use_global_config, git_ssh: :use_global_config)
108
+ execution_context = Git::ExecutionContext::Global.new(binary_path: binary_path, git_ssh: git_ssh)
109
+ Git::Commands::RevParse.new(execution_context).call(
110
+ show_toplevel: true, chdir: File.expand_path(working_dir)
111
+ ).stdout
112
+ rescue Errno::ENOENT
113
+ raise ArgumentError, 'Failed to find the root of the worktree: git binary not found'
114
+ rescue Git::FailedError
115
+ raise ArgumentError, "'#{working_dir}' is not in a git working tree"
116
+ end
117
+ private_class_method :execute_rev_parse_toplevel
118
+
119
+ # Resolve the working directory path
120
+ #
121
+ # @param path [String, nil] the working directory path or `nil`
122
+ #
123
+ # @param bare [Boolean] whether this is a bare repository
124
+ #
125
+ # @return [String, nil] the absolute path, or `nil` for bare repos
126
+ #
127
+ # @api private
128
+ #
129
+ def resolve_working_directory(path, bare:)
130
+ return nil if bare
131
+
132
+ File.expand_path(path || Dir.pwd)
133
+ end
134
+ private_class_method :resolve_working_directory
135
+
136
+ # Resolve the repository (`.git`) directory path
137
+ #
138
+ # Handles the gitdir-pointer file case for submodules and linked worktrees.
139
+ #
140
+ # @param path [String, nil] the repository path or `nil`
141
+ #
142
+ # @param working_dir [String, nil] the working directory used for relative
143
+ # path resolution
144
+ #
145
+ # @param bare [Boolean] whether this is a bare repository
146
+ #
147
+ # @param bare_default [String, nil] for bare repos, used as the default when
148
+ # `path` is `nil`
149
+ #
150
+ # @return [String] the absolute path to the repository
151
+ #
152
+ # @api private
153
+ #
154
+ def resolve_repository(path, working_dir, bare:, bare_default: nil)
155
+ initial_path = if bare
156
+ File.expand_path(path || bare_default || Dir.pwd)
157
+ else
158
+ File.expand_path(path || '.git', working_dir)
159
+ end
160
+
161
+ resolve_gitdir_pointer(initial_path)
162
+ end
163
+ private_class_method :resolve_repository
164
+
165
+ # Resolve gitdir-pointer files used by submodules and linked worktrees
166
+ #
167
+ # If `path` points to a file containing `"gitdir: <path>"`, returns the
168
+ # resolved target path. Otherwise returns `path` unchanged.
169
+ #
170
+ # @param path [String] the path to check
171
+ #
172
+ # @return [String] the resolved absolute path
173
+ #
174
+ # Relative pointer targets are resolved from the directory containing the
175
+ # pointer file itself, matching git's pointer-file semantics.
176
+ #
177
+ # @api private
178
+ #
179
+ def resolve_gitdir_pointer(path)
180
+ return path unless File.file?(path)
181
+
182
+ gitdir_content = File.read(path).strip
183
+ return path unless gitdir_content.start_with?('gitdir: ')
184
+
185
+ gitdir_path = gitdir_content.sub(/\Agitdir: /, '')
186
+ File.expand_path(gitdir_path, File.dirname(path))
187
+ end
188
+ private_class_method :resolve_gitdir_pointer
189
+
190
+ # Resolve the index file path
191
+ #
192
+ # @param path [String, nil] the index path or `nil`
193
+ #
194
+ # @param repository [String] the repository directory used for relative
195
+ # path resolution
196
+ #
197
+ # @return [String] the absolute path to the index file
198
+ #
199
+ # @api private
200
+ #
201
+ def resolve_index(path, repository)
202
+ File.expand_path(path || 'index', repository)
203
+ end
204
+ private_class_method :resolve_index
205
+ end
206
+ end
data/lib/git/remote.rb CHANGED
@@ -1,38 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git/branch'
4
+ require 'git/branch_info'
5
+
1
6
  module Git
2
- class Remote < Path
7
+ # A remote in a Git repository
8
+ #
9
+ # Remote objects provide access to remote metadata and operations like fetch,
10
+ # merge, and remove. This class and `Git::Repository#remote`, which returns
11
+ # it, are both deprecated: read remote configuration through
12
+ # {Git::Repository::RemoteOperations#remote_list} and call the
13
+ # repository-level operations with the remote name instead.
14
+ #
15
+ # @example Reading a remote and fetching from it without Git::Remote
16
+ # git = Git.open('.')
17
+ # origin = git.remote_list.find { |r| r.name == 'origin' } #=> Git::RemoteInfo
18
+ # origin.url.first
19
+ # git.fetch(origin.name)
20
+ #
21
+ # @deprecated Use {Git::Repository::RemoteOperations#remote_list} and the
22
+ # repository-level remote operations instead
23
+ #
24
+ # {Git::Repository::RemoteOperations#remote_list} returns immutable
25
+ # {Git::RemoteInfo} value objects. Operations that lived on this class are
26
+ # called on the repository with the remote name instead (for example
27
+ # {Git::Repository::RemoteOperations#fetch} and
28
+ # {Git::Repository::RemoteOperations#remote_remove}). Constructing a
29
+ # `Git::Remote` emits a deprecation warning.
30
+ #
31
+ # @api public
32
+ #
33
+ class Remote
34
+ # The name of this remote (e.g. `'origin'`)
35
+ #
36
+ # @return [String] the remote name
37
+ #
38
+ attr_accessor :name
39
+
40
+ # The URL of this remote
41
+ #
42
+ # @return [String, nil] the remote URL
43
+ #
44
+ attr_accessor :url
3
45
 
4
- attr_accessor :name, :url, :fetch_opts
46
+ # The fetch refspec for this remote
47
+ #
48
+ # @return [String, nil] the fetch options string
49
+ #
50
+ attr_accessor :fetch_opts
5
51
 
52
+ # Initialize a new Remote object
53
+ #
54
+ # @param base [Git::Repository] the git repository
55
+ #
56
+ # @param name [String] the remote name (e.g. `'origin'`)
57
+ #
58
+ # @note Do not construct directly. `Git::Repository#remote` is deprecated as
59
+ # well; use {Git::Repository::RemoteOperations#remote_list} and the
60
+ # repository-level remote operations instead.
61
+ #
62
+ # @api private
63
+ #
6
64
  def initialize(base, name)
65
+ Git::Deprecation.warn(
66
+ 'Git::Remote is deprecated and will be removed in v6.0.0. ' \
67
+ 'Use Git::Repository#remote_list and the repository-level remote operations instead.'
68
+ )
7
69
  @base = base
8
- config = @base.lib.config_remote(name)
70
+ # config_remote is deprecated too; silence it so one Git::Remote.new emits one warning
71
+ config = Git::Deprecation.silence { remote_repository.config_remote(name) }
9
72
  @name = name
10
73
  @url = config['url']
11
74
  @fetch_opts = config['fetch']
12
75
  end
13
76
 
14
- def fetch(opts={})
15
- @base.fetch(@name, opts)
77
+ # Fetches from this remote
78
+ #
79
+ # @example Fetch from origin
80
+ # git.remote('origin').fetch
81
+ #
82
+ # @param opts [Hash] options for the fetch command
83
+ #
84
+ # @option opts [Boolean, nil] :tags (nil) fetch all tags from the remote
85
+ # (`--tags`)
86
+ #
87
+ # @option opts [Boolean, nil] :prune (nil) remove remote-tracking references
88
+ # that no longer exist on the remote (`--prune`)
89
+ #
90
+ # @option opts [Boolean, nil] :prune_tags (nil) remove local tags that no
91
+ # longer exist on the remote (`--prune-tags`)
92
+ #
93
+ # @option opts [Boolean, nil] :force (nil) override the fast-forward check
94
+ # when using explicit refspecs (`--force`)
95
+ #
96
+ # @option opts [Boolean, nil] :update_head_ok (nil) allow `git fetch` to
97
+ # update the branch pointed to by `HEAD` (`--update-head-ok`)
98
+ #
99
+ # @option opts [Boolean, nil] :unshallow (nil) convert a shallow clone into a
100
+ # full repository (`--unshallow`)
101
+ #
102
+ # @option opts [String, Integer, nil] :depth (nil) limit history to N commits
103
+ # from each branch tip (`--depth=N`)
104
+ #
105
+ # @option opts [String, Array<String>, nil] :ref (nil) one or more refspecs to
106
+ # fetch as positional arguments after the remote name
107
+ #
108
+ # @return [String] git's stdout from the fetch
109
+ #
110
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
111
+ #
112
+ def fetch(opts = {})
113
+ remote_repository.fetch(@name, opts)
16
114
  end
17
115
 
18
- # merge this remote locally
19
- def merge(branch = @base.current_branch)
116
+ # Merges this remote into the given (or current) local branch
117
+ #
118
+ # @example Merge origin/main into the current branch
119
+ # git.remote('origin').merge('main')
120
+ #
121
+ # @param branch [String] the local branch to merge into (defaults to current branch)
122
+ #
123
+ # @return [String] git's stdout from the merge
124
+ #
125
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
126
+ #
127
+ def merge(branch = nil)
128
+ branch ||= remote_repository.current_branch
20
129
  remote_tracking_branch = "#{@name}/#{branch}"
21
- @base.merge(remote_tracking_branch)
130
+ remote_repository.merge(remote_tracking_branch)
22
131
  end
23
132
 
24
- def branch(branch = @base.current_branch)
25
- remote_tracking_branch = "#{@name}/#{branch}"
26
- Git::Branch.new(@base, remote_tracking_branch)
133
+ # Returns a {Git::Branch} object for the given branch on this remote
134
+ #
135
+ # @example Get the remote-tracking branch object
136
+ # git.remote('origin').branch('main') #=> #<Git::Branch 'origin/main'>
137
+ #
138
+ # @param branch [String] the branch name on this remote (defaults to current branch)
139
+ #
140
+ # @return [Git::Branch] a branch object representing `<remote>/<branch>`
141
+ #
142
+ # @deprecated Use
143
+ # `Git::Repository#branch_list("#{name}/#{branch || current_branch}").first`
144
+ # instead
145
+ #
146
+ # With no argument this method falls back to the current branch, so the
147
+ # replacement has to supply `Git::Repository#current_branch` itself. The
148
+ # replacement returns a {Git::BranchInfo} value object rather than a
149
+ # {Git::Branch}, and returns `nil` when the remote-tracking branch does
150
+ # not exist.
151
+ #
152
+ def branch(branch = nil)
153
+ branch ||= remote_repository.current_branch
154
+ Git::Branch.new(@base, "#{@name}/#{branch}")
27
155
  end
28
156
 
157
+ # Removes this remote from the repository
158
+ #
159
+ # @example Remove the upstream remote
160
+ # git.remote('upstream').remove
161
+ #
162
+ # @return [Git::CommandLine::Result] the result of `git remote remove`
163
+ #
164
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
165
+ #
29
166
  def remove
30
- @base.lib.remote_remove(@name)
167
+ remote_repository.remote_remove(@name)
31
168
  end
32
169
 
170
+ # Returns the name of this remote as a string
171
+ #
172
+ # @example Get the remote name as a string
173
+ # git.remote('origin').to_s #=> 'origin'
174
+ #
175
+ # @return [String] the remote name
176
+ #
33
177
  def to_s
34
178
  @name
35
179
  end
36
180
 
181
+ private
182
+
183
+ # @return [Git::Repository]
184
+ #
185
+ # @api private
186
+ #
187
+ def remote_repository
188
+ @base
189
+ end
37
190
  end
38
191
  end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Value object representing a configured git remote
5
+ #
6
+ # Each instance holds the parsed configuration for a single remote as read
7
+ # from the repository's git config. Multi-value fields (`:url`, `:push_url`,
8
+ # `:fetch`, `:push`) are always `Array<String>` (never `nil`; may be empty).
9
+ # Those arrays are frozen copies of the values given, so the set of URLs and
10
+ # refspecs cannot change after construction; use `with` to derive a modified
11
+ # copy. The immutability is shallow, as with any `Data` member: the strings
12
+ # inside those arrays and the scalar members are the objects the caller
13
+ # passed in, not copies. All other fields are nilable except `:name`.
14
+ #
15
+ # @example Minimal remote (fetch-only, one URL)
16
+ # info = Git::RemoteInfo.new(
17
+ # name: 'origin',
18
+ # url: ['https://github.com/ruby-git/ruby-git.git'],
19
+ # push_url: [],
20
+ # fetch: ['+refs/heads/*:refs/remotes/origin/*'],
21
+ # push: []
22
+ # )
23
+ # info.name # => "origin"
24
+ # info.url # => ["https://github.com/ruby-git/ruby-git.git"]
25
+ # info.prune # => nil
26
+ #
27
+ # @api public
28
+ #
29
+ # @!attribute [r] name
30
+ # @return [String] the name of the remote (e.g. `'origin'`)
31
+ #
32
+ # @!attribute [r] url
33
+ # @return [Array<String>] the fetch URL(s) for this remote (`remote.<name>.url`)
34
+ #
35
+ # @!attribute [r] push_url
36
+ # @return [Array<String>] the push URL(s) for this remote (`remote.<name>.pushurl`)
37
+ #
38
+ # @!attribute [r] fetch
39
+ # @return [Array<String>] the fetch refspec(s) for this remote (`remote.<name>.fetch`)
40
+ #
41
+ # @!attribute [r] push
42
+ # @return [Array<String>] the push refspec(s) for this remote (`remote.<name>.push`)
43
+ #
44
+ # @!attribute [r] mirror
45
+ # @return [Boolean, nil] `true`/`false` per `remote.<name>.mirror`, or `nil` when not set
46
+ #
47
+ # @!attribute [r] skip_default_update
48
+ # @return [Boolean, nil] `true`/`false` per `remote.<name>.skipDefaultUpdate`, or `nil` when not set
49
+ #
50
+ # @!attribute [r] tag_opt
51
+ # @return [String, nil] the tag-fetching option (`remote.<name>.tagOpt`), or `nil` when not set
52
+ #
53
+ # @!attribute [r] prune
54
+ # @return [Boolean, nil] `true`/`false` per `remote.<name>.prune`; `nil` inherits `fetch.prune`
55
+ #
56
+ # @!attribute [r] prune_tags
57
+ # @return [Boolean, nil] `true`/`false` per `remote.<name>.pruneTags`; `nil` inherits `fetch.pruneTags`
58
+ #
59
+ # @!attribute [r] receivepack
60
+ # @return [String, nil] the `git-receive-pack` path on the remote (`remote.<name>.receivepack`)
61
+ #
62
+ # @!attribute [r] uploadpack
63
+ # @return [String, nil] the `git-upload-pack` path on the remote (`remote.<name>.uploadpack`)
64
+ #
65
+ # @!attribute [r] promisor
66
+ # @return [Boolean, nil] `true`/`false` per `remote.<name>.promisor`, or `nil` when not set
67
+ #
68
+ # @!attribute [r] partial_clone_filter
69
+ # @return [String, nil] the partial-clone object filter (`remote.<name>.partialclonefilter`)
70
+ #
71
+ # @!attribute [r] vcs
72
+ # @return [String, nil] the VCS type for git-remote helpers (`remote.<name>.vcs`)
73
+ #
74
+ RemoteInfo = Data.define(
75
+ :name,
76
+ :url,
77
+ :push_url,
78
+ :fetch,
79
+ :push,
80
+ :mirror,
81
+ :skip_default_update,
82
+ :tag_opt,
83
+ :prune,
84
+ :prune_tags,
85
+ :receivepack,
86
+ :uploadpack,
87
+ :promisor,
88
+ :partial_clone_filter,
89
+ :vcs
90
+ ) do
91
+ # Create a new RemoteInfo
92
+ #
93
+ # @param name [String] the name of the remote (required)
94
+ #
95
+ # @param url [Array<String>] fetch URLs (default `[]`); stored as a frozen copy
96
+ #
97
+ # @param push_url [Array<String>] push URLs (default `[]`); stored as a frozen copy
98
+ #
99
+ # @param fetch [Array<String>] fetch refspecs (default `[]`); stored as a frozen copy
100
+ #
101
+ # @param push [Array<String>] push refspecs (default `[]`); stored as a frozen copy
102
+ #
103
+ # @param mirror [Boolean, nil] mirror flag (default `nil`)
104
+ #
105
+ # @param skip_default_update [Boolean, nil] skip-default-update flag (default `nil`)
106
+ #
107
+ # @param tag_opt [String, nil] tag-fetching option (default `nil`)
108
+ #
109
+ # @param prune [Boolean, nil] prune flag (default `nil`)
110
+ #
111
+ # @param prune_tags [Boolean, nil] prune-tags flag (default `nil`)
112
+ #
113
+ # @param receivepack [String, nil] receive-pack path (default `nil`)
114
+ #
115
+ # @param uploadpack [String, nil] upload-pack path (default `nil`)
116
+ #
117
+ # @param promisor [Boolean, nil] promisor flag (default `nil`)
118
+ #
119
+ # @param partial_clone_filter [String, nil] partial-clone filter (default `nil`)
120
+ #
121
+ # @param vcs [String, nil] VCS type (default `nil`)
122
+ #
123
+ # @return [Git::RemoteInfo]
124
+ #
125
+ def initialize( # rubocop:disable Metrics/ParameterLists
126
+ name:,
127
+ url: [],
128
+ push_url: [],
129
+ fetch: [],
130
+ push: [],
131
+ mirror: nil,
132
+ skip_default_update: nil,
133
+ tag_opt: nil,
134
+ prune: nil,
135
+ prune_tags: nil,
136
+ receivepack: nil,
137
+ uploadpack: nil,
138
+ promisor: nil,
139
+ partial_clone_filter: nil,
140
+ vcs: nil
141
+ )
142
+ super(
143
+ name:, url: Array(url).dup.freeze, push_url: Array(push_url).dup.freeze,
144
+ fetch: Array(fetch).dup.freeze, push: Array(push).dup.freeze, mirror:,
145
+ skip_default_update:, tag_opt:, prune:, prune_tags:, receivepack:, uploadpack:,
146
+ promisor:, partial_clone_filter:, vcs:
147
+ )
148
+ end
149
+
150
+ # Return a copy of this RemoteInfo with the given fields replaced
151
+ #
152
+ # Routes through {#initialize} so the multi-value fields of the copy are
153
+ # frozen copies, the same as on construction. `Data#with` bypasses
154
+ # `initialize` on Ruby 3.2, which would leave those arrays mutable.
155
+ #
156
+ # @example Replace the fetch URL
157
+ # info.with(url: ['https://example.com/other.git']).url
158
+ # # => ["https://example.com/other.git"]
159
+ #
160
+ # @param fields [Hash{Symbol => Object}] the fields to replace, keyed by
161
+ # member name
162
+ #
163
+ # @option fields [String] :name the name of the remote
164
+ #
165
+ # @option fields [Array<String>] :url fetch URLs
166
+ #
167
+ # @option fields [Array<String>] :push_url push URLs
168
+ #
169
+ # @option fields [Array<String>] :fetch fetch refspecs
170
+ #
171
+ # @option fields [Array<String>] :push push refspecs
172
+ #
173
+ # @option fields [Boolean, nil] :mirror mirror flag
174
+ #
175
+ # @option fields [Boolean, nil] :skip_default_update skip-default-update flag
176
+ #
177
+ # @option fields [String, nil] :tag_opt tag-fetching option
178
+ #
179
+ # @option fields [Boolean, nil] :prune prune flag
180
+ #
181
+ # @option fields [Boolean, nil] :prune_tags prune-tags flag
182
+ #
183
+ # @option fields [String, nil] :receivepack receive-pack path
184
+ #
185
+ # @option fields [String, nil] :uploadpack upload-pack path
186
+ #
187
+ # @option fields [Boolean, nil] :promisor promisor flag
188
+ #
189
+ # @option fields [String, nil] :partial_clone_filter partial-clone filter
190
+ #
191
+ # @option fields [String, nil] :vcs VCS type
192
+ #
193
+ # @return [Git::RemoteInfo] a new instance; `self` when no fields are given
194
+ #
195
+ # @raise [ArgumentError] if a key is not a member of this Data class
196
+ #
197
+ def with(**fields)
198
+ return self if fields.empty?
199
+
200
+ self.class.new(**to_h, **fields)
201
+ end
202
+ end
203
+ end