git 4.3.2 → 5.0.0.beta.2

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 (297) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +73 -2705
  3. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
  4. data/.github/pull_request_template.md +3 -1
  5. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  6. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  7. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  8. data/.github/skills/command-implementation/SKILL.md +229 -0
  9. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  10. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  11. data/.github/skills/dependency-management/SKILL.md +72 -0
  12. data/.github/skills/development-workflow/SKILL.md +506 -0
  13. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  14. data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
  15. data/.github/skills/extract-facade-from-base-lib/SKILL.md +600 -0
  16. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  17. data/.github/skills/facade-implementation/SKILL.md +274 -0
  18. data/.github/skills/facade-test-conventions/SKILL.md +394 -0
  19. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  20. data/.github/skills/make-skill-template/SKILL.md +176 -0
  21. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  22. data/.github/skills/project-context/SKILL.md +313 -0
  23. data/.github/skills/pull-request-review/SKILL.md +168 -0
  24. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  25. data/.github/skills/release-management/SKILL.md +125 -0
  26. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  27. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  28. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  29. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  30. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  31. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  32. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  33. data/.github/skills/test-debugging/SKILL.md +160 -0
  34. data/.github/skills/yard-documentation/SKILL.md +793 -0
  35. data/.github/workflows/continuous_integration.yml +3 -2
  36. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  37. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  38. data/.github/workflows/release.yml +3 -4
  39. data/.gitignore +8 -0
  40. data/.husky/pre-commit +13 -0
  41. data/.release-please-manifest.json +1 -1
  42. data/.rspec +3 -0
  43. data/.rubocop.yml +12 -3
  44. data/.rubocop_todo.yml +23 -5
  45. data/.yardopts +1 -0
  46. data/CHANGELOG.md +0 -40
  47. data/CONTRIBUTING.md +694 -53
  48. data/README.md +67 -15
  49. data/Rakefile +61 -9
  50. data/UPGRADING.md +141 -0
  51. data/commitlint.test +4 -0
  52. data/git.gemspec +18 -7
  53. data/lib/git/args_builder.rb +0 -8
  54. data/lib/git/branch.rb +369 -43
  55. data/lib/git/branch_delete_failure.rb +31 -0
  56. data/lib/git/branch_delete_result.rb +63 -0
  57. data/lib/git/branch_info.rb +178 -0
  58. data/lib/git/branches.rb +122 -24
  59. data/lib/git/command_line/base.rb +255 -0
  60. data/lib/git/command_line/capturing.rb +251 -0
  61. data/lib/git/command_line/result.rb +96 -0
  62. data/lib/git/command_line/streaming.rb +196 -0
  63. data/lib/git/command_line.rb +43 -322
  64. data/lib/git/command_line_result.rb +4 -88
  65. data/lib/git/commands/add.rb +131 -0
  66. data/lib/git/commands/am/abort.rb +43 -0
  67. data/lib/git/commands/am/apply.rb +252 -0
  68. data/lib/git/commands/am/continue.rb +43 -0
  69. data/lib/git/commands/am/quit.rb +43 -0
  70. data/lib/git/commands/am/retry.rb +47 -0
  71. data/lib/git/commands/am/show_current_patch.rb +64 -0
  72. data/lib/git/commands/am/skip.rb +42 -0
  73. data/lib/git/commands/am.rb +33 -0
  74. data/lib/git/commands/apply.rb +237 -0
  75. data/lib/git/commands/archive/list_formats.rb +46 -0
  76. data/lib/git/commands/archive.rb +140 -0
  77. data/lib/git/commands/arguments.rb +3510 -0
  78. data/lib/git/commands/base.rb +404 -0
  79. data/lib/git/commands/branch/copy.rb +94 -0
  80. data/lib/git/commands/branch/create.rb +173 -0
  81. data/lib/git/commands/branch/delete.rb +80 -0
  82. data/lib/git/commands/branch/list.rb +162 -0
  83. data/lib/git/commands/branch/move.rb +94 -0
  84. data/lib/git/commands/branch/set_upstream.rb +86 -0
  85. data/lib/git/commands/branch/show_current.rb +49 -0
  86. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  87. data/lib/git/commands/branch.rb +34 -0
  88. data/lib/git/commands/cat_file/batch.rb +369 -0
  89. data/lib/git/commands/cat_file/filtered.rb +105 -0
  90. data/lib/git/commands/cat_file/raw.rb +216 -0
  91. data/lib/git/commands/cat_file.rb +49 -0
  92. data/lib/git/commands/checkout/branch.rb +151 -0
  93. data/lib/git/commands/checkout/files.rb +115 -0
  94. data/lib/git/commands/checkout.rb +38 -0
  95. data/lib/git/commands/checkout_index.rb +105 -0
  96. data/lib/git/commands/clean.rb +100 -0
  97. data/lib/git/commands/clone.rb +240 -0
  98. data/lib/git/commands/commit.rb +272 -0
  99. data/lib/git/commands/commit_tree.rb +100 -0
  100. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  101. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  102. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  103. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  104. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  105. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  106. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +107 -0
  107. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  108. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  109. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  110. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  111. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  112. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  113. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  114. data/lib/git/commands/config_option_syntax.rb +56 -0
  115. data/lib/git/commands/describe.rb +155 -0
  116. data/lib/git/commands/diff.rb +656 -0
  117. data/lib/git/commands/diff_files.rb +518 -0
  118. data/lib/git/commands/diff_index.rb +496 -0
  119. data/lib/git/commands/fetch.rb +352 -0
  120. data/lib/git/commands/fsck.rb +136 -0
  121. data/lib/git/commands/gc.rb +132 -0
  122. data/lib/git/commands/grep.rb +338 -0
  123. data/lib/git/commands/init.rb +99 -0
  124. data/lib/git/commands/log.rb +632 -0
  125. data/lib/git/commands/ls_files.rb +191 -0
  126. data/lib/git/commands/ls_remote.rb +155 -0
  127. data/lib/git/commands/ls_tree.rb +131 -0
  128. data/lib/git/commands/maintenance/register.rb +75 -0
  129. data/lib/git/commands/maintenance/run.rb +104 -0
  130. data/lib/git/commands/maintenance/start.rb +66 -0
  131. data/lib/git/commands/maintenance/stop.rb +55 -0
  132. data/lib/git/commands/maintenance/unregister.rb +79 -0
  133. data/lib/git/commands/maintenance.rb +31 -0
  134. data/lib/git/commands/merge/abort.rb +44 -0
  135. data/lib/git/commands/merge/continue.rb +44 -0
  136. data/lib/git/commands/merge/quit.rb +46 -0
  137. data/lib/git/commands/merge/start.rb +245 -0
  138. data/lib/git/commands/merge.rb +28 -0
  139. data/lib/git/commands/merge_base.rb +86 -0
  140. data/lib/git/commands/mv.rb +77 -0
  141. data/lib/git/commands/name_rev.rb +114 -0
  142. data/lib/git/commands/pull.rb +377 -0
  143. data/lib/git/commands/push.rb +246 -0
  144. data/lib/git/commands/read_tree.rb +149 -0
  145. data/lib/git/commands/remote/add.rb +91 -0
  146. data/lib/git/commands/remote/get_url.rb +66 -0
  147. data/lib/git/commands/remote/list.rb +54 -0
  148. data/lib/git/commands/remote/prune.rb +61 -0
  149. data/lib/git/commands/remote/remove.rb +52 -0
  150. data/lib/git/commands/remote/rename.rb +69 -0
  151. data/lib/git/commands/remote/set_branches.rb +63 -0
  152. data/lib/git/commands/remote/set_head.rb +82 -0
  153. data/lib/git/commands/remote/set_url.rb +71 -0
  154. data/lib/git/commands/remote/set_url_add.rb +61 -0
  155. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  156. data/lib/git/commands/remote/show.rb +71 -0
  157. data/lib/git/commands/remote/update.rb +72 -0
  158. data/lib/git/commands/remote.rb +42 -0
  159. data/lib/git/commands/repack.rb +277 -0
  160. data/lib/git/commands/reset.rb +147 -0
  161. data/lib/git/commands/rev_parse.rb +297 -0
  162. data/lib/git/commands/revert/abort.rb +45 -0
  163. data/lib/git/commands/revert/continue.rb +57 -0
  164. data/lib/git/commands/revert/quit.rb +47 -0
  165. data/lib/git/commands/revert/skip.rb +44 -0
  166. data/lib/git/commands/revert/start.rb +153 -0
  167. data/lib/git/commands/revert.rb +29 -0
  168. data/lib/git/commands/rm.rb +114 -0
  169. data/lib/git/commands/show.rb +632 -0
  170. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  171. data/lib/git/commands/show_ref/exists.rb +78 -0
  172. data/lib/git/commands/show_ref/list.rb +145 -0
  173. data/lib/git/commands/show_ref/verify.rb +120 -0
  174. data/lib/git/commands/show_ref.rb +42 -0
  175. data/lib/git/commands/stash/apply.rb +75 -0
  176. data/lib/git/commands/stash/branch.rb +65 -0
  177. data/lib/git/commands/stash/clear.rb +41 -0
  178. data/lib/git/commands/stash/create.rb +58 -0
  179. data/lib/git/commands/stash/drop.rb +67 -0
  180. data/lib/git/commands/stash/list.rb +39 -0
  181. data/lib/git/commands/stash/pop.rb +78 -0
  182. data/lib/git/commands/stash/push.rb +103 -0
  183. data/lib/git/commands/stash/show.rb +149 -0
  184. data/lib/git/commands/stash/store.rb +63 -0
  185. data/lib/git/commands/stash.rb +38 -0
  186. data/lib/git/commands/status.rb +169 -0
  187. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  188. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  189. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  190. data/lib/git/commands/symbolic_ref.rb +38 -0
  191. data/lib/git/commands/tag/create.rb +139 -0
  192. data/lib/git/commands/tag/delete.rb +55 -0
  193. data/lib/git/commands/tag/list.rb +143 -0
  194. data/lib/git/commands/tag/verify.rb +71 -0
  195. data/lib/git/commands/tag.rb +26 -0
  196. data/lib/git/commands/update_ref/batch.rb +140 -0
  197. data/lib/git/commands/update_ref/delete.rb +92 -0
  198. data/lib/git/commands/update_ref/update.rb +106 -0
  199. data/lib/git/commands/update_ref.rb +42 -0
  200. data/lib/git/commands/version.rb +57 -0
  201. data/lib/git/commands/worktree/add.rb +140 -0
  202. data/lib/git/commands/worktree/list.rb +64 -0
  203. data/lib/git/commands/worktree/lock.rb +58 -0
  204. data/lib/git/commands/worktree/management_base.rb +51 -0
  205. data/lib/git/commands/worktree/move.rb +66 -0
  206. data/lib/git/commands/worktree/prune.rb +67 -0
  207. data/lib/git/commands/worktree/remove.rb +63 -0
  208. data/lib/git/commands/worktree/repair.rb +76 -0
  209. data/lib/git/commands/worktree/unlock.rb +47 -0
  210. data/lib/git/commands/worktree.rb +43 -0
  211. data/lib/git/commands/write_tree.rb +68 -0
  212. data/lib/git/commands.rb +87 -0
  213. data/lib/git/config.rb +17 -0
  214. data/lib/git/config_entry_info.rb +106 -0
  215. data/lib/git/configuring.rb +665 -0
  216. data/lib/git/deprecation.rb +9 -0
  217. data/lib/git/detached_head_info.rb +54 -0
  218. data/lib/git/diff.rb +293 -7
  219. data/lib/git/diff_file_numstat_info.rb +29 -0
  220. data/lib/git/diff_file_patch_info.rb +134 -0
  221. data/lib/git/diff_file_raw_info.rb +127 -0
  222. data/lib/git/diff_info.rb +169 -0
  223. data/lib/git/diff_path_status.rb +67 -19
  224. data/lib/git/diff_result.rb +32 -0
  225. data/lib/git/diff_stats.rb +51 -14
  226. data/lib/git/dirstat_info.rb +86 -0
  227. data/lib/git/errors.rb +65 -2
  228. data/lib/git/execution_context/global.rb +31 -0
  229. data/lib/git/execution_context/repository.rb +136 -0
  230. data/lib/git/execution_context.rb +501 -0
  231. data/lib/git/file_ref.rb +74 -0
  232. data/lib/git/fsck_object.rb +9 -9
  233. data/lib/git/fsck_result.rb +1 -1
  234. data/lib/git/log.rb +9 -2
  235. data/lib/git/object.rb +90 -27
  236. data/lib/git/parsers/branch.rb +224 -0
  237. data/lib/git/parsers/cat_file.rb +111 -0
  238. data/lib/git/parsers/config_entry.rb +110 -0
  239. data/lib/git/parsers/diff.rb +585 -0
  240. data/lib/git/parsers/fsck.rb +133 -0
  241. data/lib/git/parsers/grep.rb +42 -0
  242. data/lib/git/parsers/ls_remote.rb +79 -0
  243. data/lib/git/parsers/ls_tree.rb +58 -0
  244. data/lib/git/parsers/stash.rb +208 -0
  245. data/lib/git/parsers/tag.rb +257 -0
  246. data/lib/git/remote.rb +120 -9
  247. data/lib/git/repository/branching.rb +743 -0
  248. data/lib/git/repository/committing.rb +187 -0
  249. data/lib/git/repository/configuring.rb +351 -0
  250. data/lib/git/repository/context_helpers.rb +264 -0
  251. data/lib/git/repository/diffing.rb +775 -0
  252. data/lib/git/repository/factories.rb +682 -0
  253. data/lib/git/repository/inspecting.rb +252 -0
  254. data/lib/git/repository/logging.rb +247 -0
  255. data/lib/git/repository/maintenance.rb +65 -0
  256. data/lib/git/repository/merging.rb +357 -0
  257. data/lib/git/repository/object_operations.rb +1199 -0
  258. data/lib/git/repository/path_resolver.rb +207 -0
  259. data/lib/git/repository/remote_operations.rb +898 -0
  260. data/lib/git/repository/shared_private.rb +51 -0
  261. data/lib/git/repository/staging.rb +554 -0
  262. data/lib/git/repository/stashing.rb +143 -0
  263. data/lib/git/repository/status_operations.rb +201 -0
  264. data/lib/git/repository/worktree_operations.rb +159 -0
  265. data/lib/git/repository.rb +203 -1
  266. data/lib/git/stash.rb +78 -4
  267. data/lib/git/stash_info.rb +104 -0
  268. data/lib/git/stashes.rb +125 -13
  269. data/lib/git/status.rb +215 -18
  270. data/lib/git/tag_delete_failure.rb +31 -0
  271. data/lib/git/tag_delete_result.rb +63 -0
  272. data/lib/git/tag_info.rb +105 -0
  273. data/lib/git/version.rb +109 -2
  274. data/lib/git/version_constraint.rb +81 -0
  275. data/lib/git/worktree.rb +108 -6
  276. data/lib/git/worktrees.rb +94 -7
  277. data/lib/git.rb +260 -61
  278. data/redesign/1_architecture_existing.md +54 -18
  279. data/redesign/2_architecture_redesign.md +365 -46
  280. data/redesign/3_architecture_implementation.md +1497 -63
  281. data/redesign/Phase 4 - Step A.md +360 -0
  282. data/redesign/beta_release.md +107 -0
  283. data/redesign/c1c2_audit.md +566 -0
  284. data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
  285. data/redesign/config_design.rb +501 -0
  286. data/tasks/gem_tasks.rake +4 -0
  287. data/tasks/npm_tasks.rake +7 -0
  288. data/tasks/rspec.rake +48 -0
  289. data/tasks/test.rake +13 -1
  290. data/tasks/yard.rake +34 -7
  291. metadata +365 -22
  292. data/lib/git/base.rb +0 -1128
  293. data/lib/git/index.rb +0 -6
  294. data/lib/git/lib.rb +0 -2277
  295. data/lib/git/path.rb +0 -38
  296. data/lib/git/working_directory.rb +0 -6
  297. /data/{release-please-config.json → .release-please-config.json} +0 -0
data/lib/git/base.rb DELETED
@@ -1,1128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'logger'
4
- require 'open3'
5
-
6
- module Git
7
- # The main public interface for interacting with Git commands
8
- #
9
- # Instead of creating a Git::Base directly, obtain a Git::Base instance by
10
- # calling one of the follow {Git} class methods: {Git.open}, {Git.init},
11
- # {Git.clone}, or {Git.bare}.
12
- #
13
- # @api public
14
- #
15
- class Base
16
- # (see Git.bare)
17
- def self.bare(git_dir, options = {})
18
- normalize_paths(options, default_repository: git_dir, bare: true)
19
- new(options)
20
- end
21
-
22
- # (see Git.clone)
23
- def self.clone(repository_url, directory, options = {})
24
- lib_options = {}
25
- lib_options[:git_ssh] = options[:git_ssh] if options.key?(:git_ssh)
26
- new_options = Git::Lib.new(lib_options, options[:log]).clone(repository_url, directory, options)
27
- normalize_paths(new_options, bare: options[:bare] || options[:mirror])
28
- new(new_options)
29
- end
30
-
31
- # (see Git.default_branch)
32
- def self.repository_default_branch(repository, options = {})
33
- Git::Lib.new(nil, options[:log]).repository_default_branch(repository)
34
- end
35
-
36
- # Returns (and initialize if needed) a Git::Config instance
37
- #
38
- # @return [Git::Config] the current config instance.
39
- def self.config
40
- @config ||= Config.new
41
- end
42
-
43
- def self.binary_version(binary_path)
44
- result, status = execute_git_version(binary_path)
45
-
46
- raise "Failed to get git version: #{status}\n#{result}" unless status.success?
47
-
48
- parse_version_string(result)
49
- end
50
-
51
- private_class_method def self.execute_git_version(binary_path)
52
- Open3.capture2e(
53
- binary_path,
54
- '-c', 'core.quotePath=true',
55
- '-c', 'color.ui=false',
56
- 'version'
57
- )
58
- rescue Errno::ENOENT
59
- raise "Failed to get git version: #{binary_path} not found"
60
- end
61
-
62
- private_class_method def self.parse_version_string(raw_string)
63
- version_match = raw_string.match(/\d+(\.\d+)+/)
64
- return [0, 0, 0] unless version_match
65
-
66
- version_parts = version_match[0].split('.').map(&:to_i)
67
- version_parts.fill(0, version_parts.length...3)
68
- end
69
-
70
- # (see Git.init)
71
- def self.init(directory = '.', options = {})
72
- normalize_paths(options, default_working_directory: directory, default_repository: directory,
73
- bare: options[:bare])
74
-
75
- init_options = {
76
- bare: options[:bare],
77
- initial_branch: options[:initial_branch]
78
- }
79
-
80
- directory = options[:bare] ? options[:repository] : options[:working_directory]
81
- FileUtils.mkdir_p(directory)
82
-
83
- # TODO: this dance seems awkward: this creates a Git::Lib so we can call
84
- # init so we can create a new Git::Base which in turn (ultimately)
85
- # creates another/different Git::Lib.
86
- #
87
- # TODO: maybe refactor so this Git::Bare.init does this:
88
- # self.new(opts).init(init_opts) and move all/some of this code into
89
- # Git::Bare#init. This way the init method can be called on any
90
- # repository you have a Git::Base instance for. This would not
91
- # change the existing interface (other than adding to it).
92
- #
93
- Git::Lib.new(options).init(init_options)
94
-
95
- new(options)
96
- end
97
-
98
- def self.root_of_worktree(working_dir)
99
- raise ArgumentError, "'#{working_dir}' does not exist" unless Dir.exist?(working_dir)
100
-
101
- result, status = execute_rev_parse_toplevel(working_dir)
102
- process_rev_parse_result(result, status, working_dir)
103
- end
104
-
105
- private_class_method def self.execute_rev_parse_toplevel(working_dir)
106
- Open3.capture2e(
107
- Git::Base.config.binary_path,
108
- '-c', 'core.quotePath=true',
109
- '-c', 'color.ui=false',
110
- 'rev-parse', '--show-toplevel',
111
- chdir: File.expand_path(working_dir)
112
- )
113
- rescue Errno::ENOENT
114
- raise ArgumentError, 'Failed to find the root of the worktree: git binary not found'
115
- end
116
-
117
- private_class_method def self.process_rev_parse_result(result, status, working_dir)
118
- raise ArgumentError, "'#{working_dir}' is not in a git working tree" unless status.success?
119
-
120
- result.chomp
121
- end
122
-
123
- # (see Git.open)
124
- def self.open(working_dir, options = {})
125
- raise ArgumentError, "'#{working_dir}' is not a directory" unless Dir.exist?(working_dir)
126
-
127
- working_dir = root_of_worktree(working_dir) unless options[:repository]
128
-
129
- normalize_paths(options, default_working_directory: working_dir)
130
-
131
- new(options)
132
- end
133
-
134
- # Create an object that executes Git commands in the context of a working
135
- # copy or a bare repository.
136
- #
137
- # @param [Hash] options The options for this command (see list of valid
138
- # options below)
139
- #
140
- # @option options [Pathname] :working_dir the path to the root of the working
141
- # directory. Should be `nil` if executing commands on a bare repository.
142
- #
143
- # @option options [Pathname] :repository used to specify a non-standard path to
144
- # the repository directory. The default is `"#{working_dir}/.git"`.
145
- #
146
- # @option options [Pathname] :index used to specify a non-standard path to an
147
- # index file. The default is `"#{working_dir}/.git/index"`
148
- #
149
- # @option options [Logger] :log A logger to use for Git operations. Git
150
- # commands are logged at the `:info` level. Additional logging is done
151
- # at the `:debug` level.
152
- #
153
- # @option options [String, nil] :git_ssh Path to a custom SSH executable or script.
154
- # Controls how SSH is configured for this {Git::Base} instance:
155
- # - If this option is not provided, the global Git::Base.config.git_ssh setting is used.
156
- # - If this option is explicitly set to nil, SSH is disabled for this instance.
157
- # - If this option is a non-empty String, that value is used as the SSH command for
158
- # this instance, overriding the global Git::Base.config.git_ssh setting.
159
- #
160
- # @return [Git::Base] an object that can execute git commands in the context
161
- # of the opened working copy or bare repository
162
- #
163
- def initialize(options = {})
164
- options = default_paths(options)
165
- setup_logger(options[:log])
166
- @git_ssh = options.key?(:git_ssh) ? options[:git_ssh] : :use_global_config
167
- initialize_components(options)
168
- end
169
-
170
- # Update the index from the current worktree to prepare the for the next commit
171
- #
172
- # @example
173
- # lib.add('path/to/file')
174
- # lib.add(['path/to/file1','path/to/file2'])
175
- # lib.add(all: true)
176
- #
177
- # @param [String, Array<String>] paths a file or files to be added to the repository (relative to the worktree root)
178
- # @param [Hash] options
179
- #
180
- # @option options [Boolean] :all Add, modify, and remove index entries to match the worktree
181
- # @option options [Boolean] :force Allow adding otherwise ignored files
182
- #
183
- def add(paths = '.', **options)
184
- lib.add(paths, options)
185
- end
186
-
187
- # adds a new remote to this repository
188
- # url can be a git url or a Git::Base object if it's a local reference
189
- #
190
- # @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
191
- # @git.fetch('scotts_git')
192
- # @git.merge('scotts_git/master')
193
- #
194
- # Options:
195
- # :fetch => true
196
- # :track => <branch_name>
197
- def add_remote(name, url, opts = {})
198
- url = url.repo.to_s if url.is_a?(Git::Base)
199
- lib.remote_add(name, url, opts)
200
- Git::Remote.new(self, name)
201
- end
202
-
203
- # changes current working directory for a block
204
- # to the git working directory
205
- #
206
- # example
207
- # @git.chdir do
208
- # # write files
209
- # @git.add
210
- # @git.commit('message')
211
- # end
212
- def chdir # :yields: the Git::Path
213
- Dir.chdir(dir.to_s) do
214
- yield dir.to_s
215
- end
216
- end
217
-
218
- # g.config('user.name', 'Scott Chacon') # sets value
219
- # g.config('user.email', 'email@email.com') # sets value
220
- # g.config('user.email', 'email@email.com', file: 'path/to/custom/config) # sets value in file
221
- # g.config('user.name') # returns 'Scott Chacon'
222
- # g.config # returns whole config hash
223
- def config(name = nil, value = nil, options = {})
224
- if name && value
225
- # set value
226
- lib.config_set(name, value, options)
227
- elsif name
228
- # return value
229
- lib.config_get(name)
230
- else
231
- # return hash
232
- lib.config_list
233
- end
234
- end
235
-
236
- # returns a reference to the working directory
237
- # @git.dir.path
238
- # @git.dir.writeable?
239
- def dir
240
- @working_directory
241
- end
242
-
243
- # returns reference to the git index file
244
- attr_reader :index
245
-
246
- # returns reference to the git repository directory
247
- # @git.dir.path
248
- def repo
249
- @repository
250
- end
251
-
252
- # returns the repository size in bytes
253
- def repo_size
254
- all_files = Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH)
255
-
256
- all_files.reject { |file| file.include?('..') }
257
- .map { |file| File.expand_path(file) }
258
- .uniq
259
- .sum { |file| File.stat(file).size.to_i }
260
- end
261
-
262
- def set_index(index_file, check = nil, must_exist: nil)
263
- unless check.nil?
264
- Git::Deprecation.warn(
265
- 'The "check" argument is deprecated and will be removed in a future version. ' \
266
- 'Use "must_exist:" instead.'
267
- )
268
- end
269
-
270
- # default is true
271
- must_exist = must_exist.nil? && check.nil? ? true : must_exist | check
272
-
273
- @lib = nil
274
- @index = Git::Index.new(index_file.to_s, must_exist:)
275
- end
276
-
277
- def set_working(work_dir, check = nil, must_exist: nil)
278
- unless check.nil?
279
- Git::Deprecation.warn(
280
- 'The "check" argument is deprecated and will be removed in a future version. ' \
281
- 'Use "must_exist:" instead.'
282
- )
283
- end
284
-
285
- # default is true
286
- must_exist = must_exist.nil? && check.nil? ? true : must_exist | check
287
-
288
- @lib = nil
289
- @working_directory = Git::WorkingDirectory.new(work_dir.to_s, must_exist:)
290
- end
291
-
292
- # returns +true+ if the branch exists locally
293
- def local_branch?(branch)
294
- branch_names = branches.local.map(&:name)
295
- branch_names.include?(branch)
296
- end
297
-
298
- def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
299
- Git::Deprecation.warn(
300
- 'Git::Base#is_local_branch? is deprecated and will be removed in a future version. ' \
301
- 'Use Git::Base#local_branch? instead.'
302
- )
303
- local_branch?(branch)
304
- end
305
-
306
- # returns +true+ if the branch exists remotely
307
- def remote_branch?(branch)
308
- branch_names = branches.remote.map(&:name)
309
- branch_names.include?(branch)
310
- end
311
-
312
- def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
313
- Git::Deprecation.warn(
314
- 'Git::Base#is_remote_branch? is deprecated and will be removed in a future version. ' \
315
- 'Use Git::Base#remote_branch? instead.'
316
- )
317
- remote_branch?(branch)
318
- end
319
-
320
- # returns +true+ if the branch exists
321
- def branch?(branch)
322
- branch_names = branches.map(&:name)
323
- branch_names.include?(branch)
324
- end
325
-
326
- def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
327
- Git::Deprecation.warn(
328
- 'Git::Base#is_branch? is deprecated and will be removed in a future version. ' \
329
- 'Use Git::Base#branch? instead.'
330
- )
331
- branch?(branch)
332
- end
333
-
334
- # this is a convenience method for accessing the class that wraps all the
335
- # actual 'git' forked system calls. At some point I hope to replace the Git::Lib
336
- # class with one that uses native methods or libgit C bindings
337
- def lib
338
- @lib ||= Git::Lib.new(self, @logger)
339
- end
340
-
341
- # Returns the per-instance git_ssh configuration value.
342
- #
343
- # This may be:
344
- # * a [String] path when an explicit git_ssh command has been configured
345
- # * the Symbol `:use_global_config` when this instance is using the global config
346
- # * `nil` when SSH has been explicitly disabled for this instance
347
- #
348
- # @return [String, Symbol, nil] the git_ssh configuration value for this instance
349
- # @api private
350
- attr_reader :git_ssh
351
-
352
- # Run a grep for 'string' on the HEAD of the git repository
353
- #
354
- # @example Limit grep's scope by calling grep() from a specific object:
355
- # git.object("v2.3").grep('TODO')
356
- #
357
- # @example Using grep results:
358
- # git.grep("TODO").each do |sha, arr|
359
- # puts "in blob #{sha}:"
360
- # arr.each do |line_no, match_string|
361
- # puts "\t line #{line_no}: '#{match_string}'"
362
- # end
363
- # end
364
- #
365
- # @param string [String] the string to search for
366
- # @param path_limiter [String, Pathname, Array<String, Pathname>] a path or array
367
- # of paths to limit the search to or nil for no limit
368
- # @param opts [Hash] options to pass to the underlying `git grep` command
369
- #
370
- # @option opts [Boolean] :ignore_case (false) ignore case when matching
371
- # @option opts [Boolean] :invert_match (false) select non-matching lines
372
- # @option opts [Boolean] :extended_regexp (false) use extended regular expressions
373
- # @option opts [String] :object (HEAD) the object to search from
374
- #
375
- # @return [Hash<String, Array>] a hash of arrays
376
- # ```Ruby
377
- # {
378
- # 'tree-ish1' => [[line_no1, match_string1], ...],
379
- # 'tree-ish2' => [[line_no1, match_string1], ...],
380
- # ...
381
- # }
382
- # ```
383
- #
384
- def grep(string, path_limiter = nil, opts = {})
385
- object('HEAD').grep(string, path_limiter, opts)
386
- end
387
-
388
- # List the files in the worktree that are ignored by git
389
- # @return [Array<String>] the list of ignored files relative to teh root of the worktree
390
- #
391
- def ignored_files
392
- lib.ignored_files
393
- end
394
-
395
- # removes file(s) from the git repository
396
- def rm(path = '.', opts = {})
397
- lib.rm(path, opts)
398
- end
399
-
400
- alias remove rm
401
-
402
- # resets the working directory to the provided commitish
403
- def reset(commitish = nil, opts = {})
404
- lib.reset(commitish, opts)
405
- end
406
-
407
- # resets the working directory to the commitish with '--hard'
408
- def reset_hard(commitish = nil, opts = {})
409
- opts = { hard: true }.merge(opts)
410
- lib.reset(commitish, opts)
411
- end
412
-
413
- # cleans the working directory
414
- #
415
- # options:
416
- # :force
417
- # :d
418
- # :ff
419
- #
420
- def clean(opts = {})
421
- lib.clean(opts)
422
- end
423
-
424
- # returns the most recent tag that is reachable from a commit
425
- #
426
- # options:
427
- # :all
428
- # :tags
429
- # :contains
430
- # :debug
431
- # :exact_match
432
- # :dirty
433
- # :abbrev
434
- # :candidates
435
- # :long
436
- # :always
437
- # :match
438
- #
439
- def describe(committish = nil, opts = {})
440
- lib.describe(committish, opts)
441
- end
442
-
443
- # reverts the working directory to the provided commitish.
444
- # Accepts a range, such as comittish..HEAD
445
- #
446
- # options:
447
- # :no_edit
448
- #
449
- def revert(commitish = nil, opts = {})
450
- lib.revert(commitish, opts)
451
- end
452
-
453
- # commits all pending changes in the index file to the git repository
454
- #
455
- # options:
456
- # :all
457
- # :allow_empty
458
- # :amend
459
- # :author
460
- #
461
- def commit(message, opts = {})
462
- lib.commit(message, opts)
463
- end
464
-
465
- # commits all pending changes in the index file to the git repository,
466
- # but automatically adds all modified files without having to explicitly
467
- # calling @git.add() on them.
468
- def commit_all(message, opts = {})
469
- opts = { add_all: true }.merge(opts)
470
- lib.commit(message, opts)
471
- end
472
-
473
- # checks out a branch as the new git working directory
474
- def checkout(*, **)
475
- lib.checkout(*, **)
476
- end
477
-
478
- # checks out an old version of a file
479
- def checkout_file(version, file)
480
- lib.checkout_file(version, file)
481
- end
482
-
483
- # fetches changes from a remote branch - this does not modify the working directory,
484
- # it just gets the changes from the remote if there are any
485
- def fetch(remote = 'origin', opts = {})
486
- if remote.is_a?(Hash)
487
- opts = remote
488
- remote = nil
489
- end
490
- lib.fetch(remote, opts)
491
- end
492
-
493
- # Push changes to a remote repository
494
- #
495
- # @overload push(remote = nil, branch = nil, options = {})
496
- # @param remote [String] the remote repository to push to
497
- # @param branch [String] the branch to push
498
- # @param options [Hash] options to pass to the push command
499
- #
500
- # @option opts [Boolean] :mirror (false) Push all refs under refs/heads/, refs/tags/ and refs/remotes/
501
- # @option opts [Boolean] :delete (false) Delete refs that don't exist on the remote
502
- # @option opts [Boolean] :force (false) Force updates
503
- # @option opts [Boolean] :tags (false) Push all refs under refs/tags/
504
- # @option opts [Array, String] :push_options (nil) Push options to transmit
505
- #
506
- # @return [Void]
507
- #
508
- # @raise [Git::FailedError] if the push fails
509
- # @raise [ArgumentError] if a branch is given without a remote
510
- #
511
- def push(*, **)
512
- lib.push(*, **)
513
- end
514
-
515
- # merges one or more branches into the current working branch
516
- #
517
- # you can specify more than one branch to merge by passing an array of branches
518
- def merge(branch, message = 'merge', opts = {})
519
- lib.merge(branch, message, opts)
520
- end
521
-
522
- # iterates over the files which are unmerged
523
- def each_conflict(&) # :yields: file, your_version, their_version
524
- lib.conflicts(&)
525
- end
526
-
527
- # Pulls the given branch from the given remote into the current branch
528
- #
529
- # @param remote [String] the remote repository to pull from
530
- # @param branch [String] the branch to pull from
531
- # @param opts [Hash] options to pass to the pull command
532
- #
533
- # @option opts [Boolean] :allow_unrelated_histories (false) Merges histories of
534
- # two projects that started their lives independently
535
- # @example pulls from origin/master
536
- # @git.pull
537
- # @example pulls from upstream/master
538
- # @git.pull('upstream')
539
- # @example pulls from upstream/develop
540
- # @git.pull('upstream', 'develop')
541
- #
542
- # @return [Void]
543
- #
544
- # @raise [Git::FailedError] if the pull fails
545
- # @raise [ArgumentError] if a branch is given without a remote
546
- def pull(remote = nil, branch = nil, opts = {})
547
- lib.pull(remote, branch, opts)
548
- end
549
-
550
- # returns an array of Git:Remote objects
551
- def remotes
552
- lib.remotes.map { |r| Git::Remote.new(self, r) }
553
- end
554
-
555
- # sets the url for a remote
556
- # url can be a git url or a Git::Base object if it's a local reference
557
- #
558
- # @git.set_remote_url('scotts_git', 'git://repo.or.cz/rubygit.git')
559
- #
560
- def set_remote_url(name, url)
561
- url = url.repo.to_s if url.is_a?(Git::Base)
562
- lib.remote_set_url(name, url)
563
- Git::Remote.new(self, name)
564
- end
565
-
566
- # Configures which branches are fetched for a remote
567
- #
568
- # Uses `git remote set-branches` to set or append fetch refspecs. When the `add:`
569
- # option is not given, the `--add` option is not passed to the git command
570
- #
571
- # @example Replace fetched branches with a single glob pattern
572
- # git = Git.open('/path/to/repo')
573
- # # Only fetch branches matching "feature/*" from origin
574
- # git.remote_set_branches('origin', 'feature/*')
575
- #
576
- # @example Append a glob pattern to existing fetched branches
577
- # git = Git.open('/path/to/repo')
578
- # # Keep existing fetch refspecs and add all release branches
579
- # git.remote_set_branches('origin', 'release/*', add: true)
580
- #
581
- # @example Configure multiple explicit branches
582
- # git = Git.open('/path/to/repo')
583
- # git.remote_set_branches('origin', 'main', 'development', 'hotfix')
584
- #
585
- # @param name [String] the remote name (for example, "origin")
586
- # @param branches [Array<String>] branch names or globs (for example, '*')
587
- # @param add [Boolean] when true, append to existing refspecs instead of replacing them
588
- #
589
- # @return [nil]
590
- #
591
- # @raise [ArgumentError] if no branches are provided @raise [Git::FailedError] if
592
- # the underlying git command fails
593
- #
594
- def remote_set_branches(name, *branches, add: false)
595
- branch_list = branches.flatten
596
- raise ArgumentError, 'branches are required' if branch_list.empty?
597
-
598
- lib.remote_set_branches(name, branch_list, add: add)
599
-
600
- nil
601
- end
602
-
603
- # removes a remote from this repository
604
- #
605
- # @git.remove_remote('scott_git')
606
- def remove_remote(name)
607
- lib.remote_remove(name)
608
- end
609
-
610
- # returns an array of all Git::Tag objects for this repository
611
- def tags
612
- lib.tags.map { |r| tag(r) }
613
- end
614
-
615
- # Create a new git tag
616
- #
617
- # @example
618
- # repo.add_tag('tag_name', object_reference)
619
- # repo.add_tag('tag_name', object_reference, {:options => 'here'})
620
- # repo.add_tag('tag_name', {:options => 'here'})
621
- #
622
- # @param [String] name The name of the tag to add
623
- # @param [Hash] options Opstions to pass to `git tag`.
624
- # See [git-tag](https://git-scm.com/docs/git-tag) for more details.
625
- # @option options [boolean] :annotate Make an unsigned, annotated tag object
626
- # @option options [boolean] :a An alias for the `:annotate` option
627
- # @option options [boolean] :d Delete existing tag with the given names.
628
- # @option options [boolean] :f Replace an existing tag with the given name (instead of failing)
629
- # @option options [String] :message Use the given tag message
630
- # @option options [String] :m An alias for the `:message` option
631
- # @option options [boolean] :s Make a GPG-signed tag.
632
- #
633
- def add_tag(name, *options)
634
- lib.tag(name, *options)
635
- tag(name)
636
- end
637
-
638
- # deletes a tag
639
- def delete_tag(name)
640
- lib.tag(name, { d: true })
641
- end
642
-
643
- # creates an archive file of the given tree-ish
644
- def archive(treeish, file = nil, opts = {})
645
- object(treeish).archive(file, opts)
646
- end
647
-
648
- # repacks the repository
649
- def repack
650
- lib.repack
651
- end
652
-
653
- def gc
654
- lib.gc
655
- end
656
-
657
- # Verifies the connectivity and validity of objects in the database
658
- #
659
- # Runs `git fsck` to check repository integrity and identify dangling,
660
- # missing, or unreachable objects.
661
- #
662
- # @overload fsck(objects = [], options = {})
663
- # @param objects [Array<String>] specific objects to treat as heads for unreachability trace.
664
- # If no objects are given, git fsck defaults to using the index file, all SHA-1
665
- # references in the refs namespace, and all reflogs.
666
- # @param [Hash] options options to pass to the underlying `git fsck` command
667
- #
668
- # @option options [Boolean] :unreachable print unreachable objects
669
- # @option options [Boolean] :strict enable strict checking
670
- # @option options [Boolean] :connectivity_only check only connectivity (faster)
671
- # @option options [Boolean] :root report root nodes
672
- # @option options [Boolean] :tags report tags
673
- # @option options [Boolean] :cache consider objects in the index
674
- # @option options [Boolean] :no_reflogs do not consider reflogs
675
- # @option options [Boolean] :lost_found write dangling objects to .git/lost-found
676
- # (note: this modifies the repository by creating files)
677
- # @option options [Boolean, nil] :dangling print dangling objects (true/false/nil for default)
678
- # @option options [Boolean, nil] :full check objects in alternate pools (true/false/nil for default)
679
- # @option options [Boolean, nil] :name_objects name objects by refs (true/false/nil for default)
680
- # @option options [Boolean, nil] :references check refs database consistency (true/false/nil for default)
681
- #
682
- # @return [Git::FsckResult] categorized objects flagged by fsck
683
- #
684
- # @example Check repository integrity
685
- # result = git.fsck
686
- # result.dangling.each { |obj| puts "#{obj.type}: #{obj.sha}" }
687
- #
688
- # @example Check with strict mode and suppress dangling output
689
- # result = git.fsck(strict: true, dangling: false)
690
- #
691
- # @example Check if repository has any issues
692
- # result = git.fsck
693
- # puts "Repository is clean" if result.empty?
694
- #
695
- # @example List root commits
696
- # result = git.fsck(root: true)
697
- # result.root.each { |obj| puts obj.sha }
698
- #
699
- # @example Check specific objects
700
- # result = git.fsck('abc1234', 'def5678')
701
- #
702
- # rubocop:disable Style/ArgumentsForwarding
703
- def fsck(*objects, **opts)
704
- lib.fsck(*objects, **opts)
705
- end
706
- # rubocop:enable Style/ArgumentsForwarding
707
-
708
- def apply(file)
709
- return unless File.exist?(file)
710
-
711
- lib.apply(file)
712
- end
713
-
714
- def apply_mail(file)
715
- lib.apply_mail(file) if File.exist?(file)
716
- end
717
-
718
- # Shows objects
719
- #
720
- # @param [String|NilClass] objectish the target object reference (nil == HEAD)
721
- # @param [String|NilClass] path the path of the file to be shown
722
- # @return [String] the object information
723
- def show(objectish = nil, path = nil)
724
- lib.show(objectish, path)
725
- end
726
-
727
- ## LOWER LEVEL INDEX OPERATIONS ##
728
-
729
- def with_index(new_index) # :yields: new_index
730
- old_index = @index
731
- set_index(new_index, false)
732
- return_value = yield @index
733
- set_index(old_index)
734
- return_value
735
- end
736
-
737
- def with_temp_index(&)
738
- # Workaround for JRUBY, since they handle the TempFile path different.
739
- # MUST be improved to be safer and OS independent.
740
- if RUBY_PLATFORM == 'java'
741
- temp_path = "/tmp/temp-index-#{(0...15).map { ('a'..'z').to_a[rand(26)] }.join}"
742
- else
743
- tempfile = Tempfile.new('temp-index')
744
- temp_path = tempfile.path
745
- tempfile.close
746
- tempfile.unlink
747
- end
748
-
749
- with_index(temp_path, &)
750
- end
751
-
752
- def checkout_index(opts = {})
753
- lib.checkout_index(opts)
754
- end
755
-
756
- def read_tree(treeish, opts = {})
757
- lib.read_tree(treeish, opts)
758
- end
759
-
760
- def write_tree
761
- lib.write_tree
762
- end
763
-
764
- def write_and_commit_tree(opts = {})
765
- tree = write_tree
766
- commit_tree(tree, opts)
767
- end
768
-
769
- def update_ref(branch, commit)
770
- branch(branch).update_ref(commit)
771
- end
772
-
773
- def ls_files(location = nil)
774
- lib.ls_files(location)
775
- end
776
-
777
- def with_working(work_dir) # :yields: the Git::WorkingDirectory
778
- return_value = false
779
- old_working = @working_directory
780
- set_working(work_dir)
781
- Dir.chdir work_dir do
782
- return_value = yield @working_directory
783
- end
784
- set_working(old_working)
785
- return_value
786
- end
787
-
788
- def with_temp_working(&)
789
- tempfile = Tempfile.new('temp-workdir')
790
- temp_dir = tempfile.path
791
- tempfile.close
792
- tempfile.unlink
793
- Dir.mkdir(temp_dir, 0o700)
794
- with_working(temp_dir, &)
795
- end
796
-
797
- # runs git rev-parse to convert the objectish to a full sha
798
- #
799
- # @example
800
- # git.rev_parse("HEAD^^")
801
- # git.rev_parse('v2.4^{tree}')
802
- # git.rev_parse('v2.4:/doc/index.html')
803
- #
804
- def rev_parse(objectish)
805
- lib.rev_parse(objectish)
806
- end
807
-
808
- # For backwards compatibility
809
- alias revparse rev_parse
810
-
811
- def ls_tree(objectish, opts = {})
812
- lib.ls_tree(objectish, opts)
813
- end
814
-
815
- # Returns the contents of a git object
816
- #
817
- # Uses `git cat-file -p` to pretty-print the contents of the given object.
818
- #
819
- # @param objectish [String] a SHA, branch name, tag, or other revision reference
820
- # to the git object
821
- #
822
- # @return [String] the contents of the object
823
- #
824
- # @see https://git-scm.com/docs/git-cat-file git-cat-file
825
- #
826
- def cat_file(objectish)
827
- lib.cat_file_contents(objectish)
828
- end
829
-
830
- # The name of the branch HEAD refers to or 'HEAD' if detached
831
- #
832
- # Returns one of the following:
833
- # * The branch name that HEAD refers to (even if it is an unborn branch)
834
- # * 'HEAD' if in a detached HEAD state
835
- #
836
- # @return [String] the name of the branch HEAD refers to or 'HEAD' if detached
837
- #
838
- def current_branch
839
- lib.branch_current
840
- end
841
-
842
- # @return [Git::Branch] an object for branch_name
843
- def branch(branch_name = current_branch)
844
- Git::Branch.new(self, branch_name)
845
- end
846
-
847
- # @return [Git::Branches] a collection of all the branches in the repository.
848
- # Each branch is represented as a {Git::Branch}.
849
- def branches
850
- Git::Branches.new(self)
851
- end
852
-
853
- # returns a Git::Worktree object for dir, commitish
854
- def worktree(dir, commitish = nil)
855
- Git::Worktree.new(self, dir, commitish)
856
- end
857
-
858
- # returns a Git::worktrees object of all the Git::Worktrees
859
- # objects for this repo
860
- def worktrees
861
- Git::Worktrees.new(self)
862
- end
863
-
864
- # @return [Git::Object::Commit] a commit object
865
- def commit_tree(tree = nil, opts = {})
866
- Git::Object::Commit.new(self, lib.commit_tree(tree, opts))
867
- end
868
-
869
- # @return [Git::Diff] a Git::Diff object
870
- def diff(objectish = 'HEAD', obj2 = nil)
871
- Git::Diff.new(self, objectish, obj2)
872
- end
873
-
874
- # @return [Git::Object] a Git object
875
- def gblob(objectish)
876
- Git::Object.new(self, objectish, 'blob')
877
- end
878
-
879
- # @return [Git::Object] a Git object
880
- def gcommit(objectish)
881
- Git::Object.new(self, objectish, 'commit')
882
- end
883
-
884
- # @return [Git::Object] a Git object
885
- def gtree(objectish)
886
- Git::Object.new(self, objectish, 'tree')
887
- end
888
-
889
- # @return [Git::Log] a log with the specified number of commits
890
- def log(count = 30)
891
- Git::Log.new(self, count)
892
- end
893
-
894
- # returns a Git::Object of the appropriate type
895
- # you can also call @git.gtree('tree'), but that's
896
- # just for readability. If you call @git.gtree('HEAD') it will
897
- # still return a Git::Object::Commit object.
898
- #
899
- # object calls a method that will run a rev-parse
900
- # on the objectish and determine the type of the object and return
901
- # an appropriate object for that type
902
- #
903
- # @return [Git::Object] an instance of the appropriate type of Git::Object
904
- def object(objectish)
905
- Git::Object.new(self, objectish)
906
- end
907
-
908
- # @return [Git::Remote] a remote of the specified name
909
- def remote(remote_name = 'origin')
910
- Git::Remote.new(self, remote_name)
911
- end
912
-
913
- # @return [Git::Status] a status object
914
- def status
915
- Git::Status.new(self)
916
- end
917
-
918
- # @return [Git::Object::Tag] a tag object
919
- def tag(tag_name)
920
- Git::Object::Tag.new(self, tag_name)
921
- end
922
-
923
- # Find as good common ancestors as possible for a merge
924
- # example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
925
- #
926
- # @return [Array<Git::Object::Commit>] a collection of common ancestors
927
- def merge_base(*)
928
- shas = lib.merge_base(*)
929
- shas.map { |sha| gcommit(sha) }
930
- end
931
-
932
- # Returns a Git::Diff::Stats object for accessing diff statistics.
933
- #
934
- # @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
935
- # @param obj2 [String, nil] The second commit or object to compare.
936
- # @param opts [Hash] Options to filter the diff.
937
- # @option opts [String, Pathname, Array<String, Pathname>] :path_limiter Limit stats to specified path(s).
938
- # @return [Git::DiffStats]
939
- def diff_stats(objectish = 'HEAD', obj2 = nil, opts = {})
940
- Git::DiffStats.new(self, objectish, obj2, opts[:path_limiter])
941
- end
942
-
943
- # Returns a Git::Diff::PathStatus object for accessing the name-status report.
944
- #
945
- # @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
946
- # @param obj2 [String, nil] The second commit or object to compare.
947
- # @param opts [Hash] Options to filter the diff.
948
- # @option opts [String, Pathname, Array<String, Pathname>] :path_limiter Limit status to specified path(s).
949
- # @option opts [String, Pathname, Array<String, Pathname>] :path (deprecated) Legacy alias for :path_limiter.
950
- # @return [Git::DiffPathStatus]
951
- def diff_path_status(objectish = 'HEAD', obj2 = nil, opts = {})
952
- path_limiter = if opts.key?(:path_limiter)
953
- opts[:path_limiter]
954
- elsif opts.key?(:path)
955
- Git::Deprecation.warn(
956
- 'Git::Base#diff_path_status :path option is deprecated. Use :path_limiter instead.'
957
- )
958
- opts[:path]
959
- end
960
-
961
- Git::DiffPathStatus.new(self, objectish, obj2, path_limiter)
962
- end
963
-
964
- # Provided for backwards compatibility
965
- alias diff_name_status diff_path_status
966
-
967
- private
968
-
969
- # Sets default paths in the options hash for direct `Git::Base.new` calls
970
- #
971
- # Factory methods like `Git.open` pre-populate these options by calling
972
- # `normalize_paths`, making this a fallback. It avoids mutating the
973
- # original options hash by returning a new one.
974
- #
975
- # @param options [Hash] the original options hash
976
- # @return [Hash] a new options hash with defaults applied
977
- def default_paths(options)
978
- return options unless (working_dir = options[:working_directory])
979
-
980
- options.dup.tap do |opts|
981
- opts[:repository] ||= File.join(working_dir, '.git')
982
- opts[:index] ||= File.join(opts[:repository], 'index')
983
- end
984
- end
985
-
986
- # Initializes the logger from the provided options
987
- # @param log_option [Logger, nil] The logger instance from options.
988
- def setup_logger(log_option)
989
- @logger = log_option || Logger.new(nil)
990
- @logger.info('Starting Git')
991
- end
992
-
993
- # Initializes the core git objects based on the provided options
994
- # @param options [Hash] The processed options hash.
995
- def initialize_components(options)
996
- @working_directory = Git::WorkingDirectory.new(options[:working_directory]) if options[:working_directory]
997
- @repository = Git::Repository.new(options[:repository]) if options[:repository]
998
- @index = Git::Index.new(options[:index], must_exist: false) if options[:index]
999
- end
1000
-
1001
- # Normalize options before they are sent to Git::Base.new
1002
- #
1003
- # Updates the options parameter by setting appropriate values for the following keys:
1004
- # * options[:working_directory]
1005
- # * options[:repository]
1006
- # * options[:index]
1007
- #
1008
- # All three values will be set to absolute paths. An exception is that
1009
- # :working_directory will be set to nil if bare is true.
1010
- #
1011
- private_class_method def self.normalize_paths(
1012
- options, default_working_directory: nil, default_repository: nil, bare: false
1013
- )
1014
- normalize_working_directory(options, default: default_working_directory, bare: bare)
1015
- normalize_repository(options, default: default_repository, bare: bare)
1016
- normalize_index(options)
1017
- end
1018
-
1019
- # Normalize options[:working_directory]
1020
- #
1021
- # If working with a bare repository, set to `nil`.
1022
- # Otherwise, set to the first non-nil value of:
1023
- # 1. `options[:working_directory]`,
1024
- # 2. the `default` parameter, or
1025
- # 3. the current working directory
1026
- #
1027
- # Finally, if options[:working_directory] is a relative path, convert it to an absoluite
1028
- # path relative to the current directory.
1029
- #
1030
- private_class_method def self.normalize_working_directory(options, default:, bare: false)
1031
- working_directory =
1032
- if bare
1033
- nil
1034
- else
1035
- File.expand_path(options[:working_directory] || default || Dir.pwd)
1036
- end
1037
-
1038
- options[:working_directory] = working_directory
1039
- end
1040
-
1041
- # Normalize options[:repository]
1042
- #
1043
- # If working with a bare repository, set to the first non-nil value out of:
1044
- # 1. `options[:repository]`
1045
- # 2. the `default` parameter
1046
- # 3. the current working directory
1047
- #
1048
- # Otherwise, set to the first non-nil value of:
1049
- # 1. `options[:repository]`
1050
- # 2. `.git`
1051
- #
1052
- # Next, if options[:repository] refers to a *file* and not a *directory*, set
1053
- # options[:repository] to the contents of that file. This is the case when
1054
- # working with a submodule or a secondary working tree (created with git worktree
1055
- # add). In these cases the repository is actually contained/nested within the
1056
- # parent's repository directory.
1057
- #
1058
- # Finally, if options[:repository] is a relative path, convert it to an absolute
1059
- # path relative to:
1060
- # 1. the current directory if working with a bare repository or
1061
- # 2. the working directory if NOT working with a bare repository
1062
- #
1063
- private_class_method def self.normalize_repository(options, default:, bare: false)
1064
- initial_path = initial_repository_path(options, default: default, bare: bare)
1065
- final_path = resolve_gitdir_if_present(initial_path, options[:working_directory])
1066
- options[:repository] = final_path
1067
- end
1068
-
1069
- # Determines the initial, potential path to the repository directory
1070
- #
1071
- # This path is considered 'initial' because it is not guaranteed to be the
1072
- # final repository location. For features like submodules or worktrees,
1073
- # this path may point to a text file containing a `gitdir:` pointer to the
1074
- # actual repository directory elsewhere. This initial path must be
1075
- # subsequently resolved.
1076
- #
1077
- # @api private
1078
- #
1079
- # @param options [Hash] The options hash, checked for `[:repository]`.
1080
- #
1081
- # @param default [String] A fallback path if `options[:repository]` is not set.
1082
- #
1083
- # @param bare [Boolean] Whether the repository is bare, which changes path resolution.
1084
- #
1085
- # @return [String] The initial, absolute path to the `.git` directory or file.
1086
- #
1087
- private_class_method def self.initial_repository_path(options, default:, bare:)
1088
- if bare
1089
- File.expand_path(options[:repository] || default || Dir.pwd)
1090
- else
1091
- File.expand_path(options[:repository] || '.git', options[:working_directory])
1092
- end
1093
- end
1094
-
1095
- # Resolves the path to the actual repository if it's a `gitdir:` pointer file.
1096
- #
1097
- # If `path` points to a file (common in submodules and worktrees), this
1098
- # method reads the `gitdir:` path from it and returns the real repository
1099
- # path. Otherwise, it returns the original path.
1100
- #
1101
- # @api private
1102
- #
1103
- # @param path [String] The initial path to the repository, which may be a pointer file.
1104
- #
1105
- # @param working_dir [String] The working directory, used as a base to resolve the path.
1106
- #
1107
- # @return [String] The final, resolved absolute path to the repository directory.
1108
- #
1109
- private_class_method def self.resolve_gitdir_if_present(path, working_dir)
1110
- return path unless File.file?(path)
1111
-
1112
- # The file contains `gitdir: <path>`, so we read the file,
1113
- # extract the path part, and expand it.
1114
- gitdir_pointer = File.read(path).sub(/\Agitdir: /, '').strip
1115
- File.expand_path(gitdir_pointer, working_dir)
1116
- end
1117
-
1118
- # Normalize options[:index]
1119
- #
1120
- # If options[:index] is a relative directory, convert it to an absolute
1121
- # directory relative to the repository directory
1122
- #
1123
- private_class_method def self.normalize_index(options)
1124
- index = File.expand_path(options[:index] || 'index', options[:repository])
1125
- options[:index] = index
1126
- end
1127
- end
1128
- end