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
data/lib/git/base.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'logger'
4
- require 'open3'
4
+ require 'pathname'
5
+ require 'git/repository/path_resolver'
5
6
 
6
7
  module Git
7
8
  # The main public interface for interacting with Git commands
@@ -15,17 +16,22 @@ module Git
15
16
  class Base
16
17
  # (see Git.bare)
17
18
  def self.bare(git_dir, options = {})
18
- normalize_paths(options, default_repository: git_dir, bare: true)
19
- new(options)
19
+ paths = Git::Repository::PathResolver.resolve_paths(repository: git_dir, bare: true)
20
+ new(options.merge(paths))
20
21
  end
21
22
 
22
23
  # (see Git.clone)
23
24
  def self.clone(repository_url, directory, options = {})
24
25
  lib_options = {}
25
26
  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)
27
+ clone_result = Git::Lib.new(lib_options, options[:log]).clone(repository_url, directory, options)
28
+ bare = options[:bare] || options[:mirror]
29
+ paths = Git::Repository::PathResolver.resolve_paths(
30
+ working_directory: clone_result[:working_directory],
31
+ repository: clone_result[:repository],
32
+ bare: bare
33
+ )
34
+ new(options.merge(paths))
29
35
  end
30
36
 
31
37
  # (see Git.default_branch)
@@ -40,84 +46,32 @@ module Git
40
46
  @config ||= Config.new
41
47
  end
42
48
 
49
+ # @deprecated Use {Git.git_version} instead, which returns a {Git::Version} (not an Array).
50
+ # For the legacy array shape, call: `Git.git_version.to_a`
51
+ #
43
52
  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'
53
+ Git::Deprecation.warn(
54
+ 'Git::Base.binary_version is deprecated and will be removed in 6.0. ' \
55
+ 'Use Git.git_version instead, which returns a Git::Version ' \
56
+ '(not an Array). For the legacy array shape, call: Git.git_version.to_a'
57
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)
58
+ Git.git_version(binary_path).to_a
96
59
  end
97
60
 
61
+ # Find the root of the working tree that contains `working_dir`
62
+ #
63
+ # Delegates to {Git::Repository::PathResolver.root_of_worktree}, using the
64
+ # global config for `binary_path` and `git_ssh`.
65
+ #
66
+ # @param working_dir [String] a path inside the working tree
67
+ #
68
+ # @return [String] the absolute path to the root of the working tree
69
+ #
70
+ # @raise [ArgumentError] if `working_dir` does not exist or is not inside a
71
+ # git working tree
72
+ #
98
73
  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
74
+ Git::Repository::PathResolver.root_of_worktree(working_dir)
121
75
  end
122
76
 
123
77
  # (see Git.open)
@@ -126,9 +80,13 @@ module Git
126
80
 
127
81
  working_dir = root_of_worktree(working_dir) unless options[:repository]
128
82
 
129
- normalize_paths(options, default_working_directory: working_dir)
83
+ paths = Git::Repository::PathResolver.resolve_paths(
84
+ working_directory: working_dir,
85
+ repository: options[:repository],
86
+ index: options[:index]
87
+ )
130
88
 
131
- new(options)
89
+ new(options.merge(paths))
132
90
  end
133
91
 
134
92
  # Create an object that executes Git commands in the context of a working
@@ -137,51 +95,100 @@ module Git
137
95
  # @param [Hash] options The options for this command (see list of valid
138
96
  # options below)
139
97
  #
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.
98
+ # @option options [Pathname] :working_directory the path to the root of the working
99
+ # directory or `nil` if executing commands on a bare repository
142
100
  #
143
101
  # @option options [Pathname] :repository used to specify a non-standard path to
144
- # the repository directory. The default is `"#{working_dir}/.git"`.
102
+ # the repository directory
103
+ #
104
+ # The default is `"<working_directory>/.git"`.
145
105
  #
146
106
  # @option options [Pathname] :index used to specify a non-standard path to an
147
- # index file. The default is `"#{working_dir}/.git/index"`
107
+ # index file
108
+ #
109
+ # The default is `"<working_directory>/.git/index"`
110
+ #
111
+ # @option options [Logger] :log A logger to use for Git operations
148
112
  #
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
113
+ # Git commands are logged at the `:info` level. Additional logging is done
151
114
  # at the `:debug` level.
152
115
  #
153
- # @option options [String, nil] :git_ssh Path to a custom SSH executable or script.
116
+ # @option options [String, nil] :git_ssh Path to a custom SSH executable or script
117
+ #
154
118
  # Controls how SSH is configured for this {Git::Base} instance:
155
119
  # - If this option is not provided, the global Git::Base.config.git_ssh setting is used.
156
120
  # - If this option is explicitly set to nil, SSH is disabled for this instance.
157
121
  # - If this option is a non-empty String, that value is used as the SSH command for
158
122
  # this instance, overriding the global Git::Base.config.git_ssh setting.
159
123
  #
160
- # @return [Git::Base] an object that can execute git commands in the context
161
- # of the opened working copy or bare repository
124
+ # @option options [String, :use_global_config] :binary_path Path to the git binary
125
+ #
126
+ # Controls which git binary is used for commands routed through
127
+ # {Git::ExecutionContext} (i.e., commands already migrated to
128
+ # +Git::Commands::*+ classes). Commands still delegating through +Git::Lib+
129
+ # continue to use the global `Git::Base.config.binary_path` setting.
130
+ #
131
+ # This limitation will be resolved when the architectural migration to
132
+ # +Git::Repository+ is complete.
133
+ #
134
+ # - If this option is not provided, the global Git::Base.config.binary_path setting is used.
135
+ # - If this option is a String, that value is used as the git binary path for
136
+ # migrated commands, overriding the global Git::Base.config.binary_path setting.
137
+ # - Passing `nil` raises ArgumentError — there is no "unset the binary" semantic.
138
+ #
139
+ # @return [Git::Base] an object that can execute git commands on a working copy or
140
+ # bare repository
141
+ #
142
+ # @raise [ArgumentError] if `binary_path` is `nil`
162
143
  #
163
144
  def initialize(options = {})
164
- options = default_paths(options)
165
145
  setup_logger(options[:log])
166
146
  @git_ssh = options.key?(:git_ssh) ? options[:git_ssh] : :use_global_config
147
+ if options.key?(:binary_path)
148
+ raise ArgumentError, 'binary_path must not be nil' if options[:binary_path].nil?
149
+
150
+ @binary_path = options[:binary_path]
151
+ else
152
+ @binary_path = :use_global_config
153
+ end
167
154
  initialize_components(options)
168
155
  end
169
156
 
170
- # Update the index from the current worktree to prepare the for the next commit
157
+ # Update the index from the current worktree to prepare for the next commit
171
158
  #
172
- # @example
173
- # lib.add('path/to/file')
174
- # lib.add(['path/to/file1','path/to/file2'])
175
- # lib.add(all: true)
159
+ # @overload add(paths = '.', **options)
176
160
  #
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
161
+ # @example Stage all changed files
162
+ # git.add
179
163
  #
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
164
+ # @example Stage a specific file
165
+ # git.add('path/to/file.rb')
182
166
  #
183
- def add(paths = '.', **options)
184
- lib.add(paths, options)
167
+ # @example Stage multiple files
168
+ # git.add(['path/to/file1.rb', 'path/to/file2.rb'])
169
+ #
170
+ # @example Stage all changes including deletions
171
+ # git.add(all: true)
172
+ #
173
+ # @param paths [String, Array<String>] a file or files to add (relative to
174
+ # the worktree root); defaults to `'.'` (all files)
175
+ #
176
+ # @param options [Hash] options for the add command
177
+ #
178
+ # @option options [Boolean, nil] :all (nil) add, modify, and remove index
179
+ # entries to match the worktree
180
+ #
181
+ # @option options [Boolean, nil] :force (nil) allow adding otherwise ignored
182
+ # files
183
+ #
184
+ # @return [String] git's stdout from the add
185
+ #
186
+ # @raise [ArgumentError] if unsupported options are provided
187
+ #
188
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
189
+ #
190
+ def add(paths = '.', **)
191
+ facade_repository.add(paths, **)
185
192
  end
186
193
 
187
194
  # adds a new remote to this repository
@@ -195,9 +202,7 @@ module Git
195
202
  # :fetch => true
196
203
  # :track => <branch_name>
197
204
  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)
205
+ facade_repository.add_remote(name, url, opts)
201
206
  end
202
207
 
203
208
  # changes current working directory for a block
@@ -209,42 +214,46 @@ module Git
209
214
  # @git.add
210
215
  # @git.commit('message')
211
216
  # end
212
- def chdir # :yields: the Git::Path
217
+ def chdir # :yields: the working directory Pathname
213
218
  Dir.chdir(dir.to_s) do
214
- yield dir.to_s
219
+ yield dir
215
220
  end
216
221
  end
217
222
 
218
223
  # g.config('user.name', 'Scott Chacon') # sets value
219
224
  # 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
225
+ # g.config('user.email', 'email@email.com', file: 'path/to/custom/config') # sets value in file
221
226
  # g.config('user.name') # returns 'Scott Chacon'
222
227
  # g.config # returns whole config hash
223
228
  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
229
+ facade_repository.config(name, value, options)
234
230
  end
235
231
 
236
- # returns a reference to the working directory
237
- # @git.dir.path
238
- # @git.dir.writeable?
232
+ # Returns a reference to the working directory
233
+ #
234
+ # @example
235
+ # @git.dir.to_s
236
+ # @git.dir.writable?
237
+ #
238
+ # @return [Pathname] the working directory path
239
+ #
239
240
  def dir
240
241
  @working_directory
241
242
  end
242
243
 
243
- # returns reference to the git index file
244
+ # Returns a reference to the git index file
245
+ #
246
+ # @return [Pathname] the index file path
247
+ #
244
248
  attr_reader :index
245
249
 
246
- # returns reference to the git repository directory
247
- # @git.dir.path
250
+ # Returns a reference to the git repository directory
251
+ #
252
+ # @example
253
+ # @git.repo.to_s
254
+ #
255
+ # @return [Pathname] the repository directory path
256
+ #
248
257
  def repo
249
258
  @repository
250
259
  end
@@ -259,40 +268,44 @@ module Git
259
268
  .sum { |file| File.stat(file).size.to_i }
260
269
  end
261
270
 
262
- def set_index(index_file, check = nil, must_exist: nil)
271
+ private
272
+
273
+ def deprecate_check_argument(check, must_exist)
263
274
  unless check.nil?
264
275
  Git::Deprecation.warn(
265
276
  'The "check" argument is deprecated and will be removed in a future version. ' \
266
277
  'Use "must_exist:" instead.'
267
278
  )
268
279
  end
269
-
270
280
  # 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:)
281
+ must_exist.nil? && check.nil? ? true : must_exist | check
275
282
  end
276
283
 
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
- )
284
+ def validate_path(path, must_exist)
285
+ Pathname.new(File.expand_path(path.to_s)).tap do |expanded_path|
286
+ raise ArgumentError, "path does not exist: #{expanded_path}" if must_exist && !expanded_path.exist?
283
287
  end
288
+ end
284
289
 
285
- # default is true
286
- must_exist = must_exist.nil? && check.nil? ? true : must_exist | check
290
+ public
291
+
292
+ def set_index(index_file, check = nil, must_exist: nil)
293
+ must_exist = deprecate_check_argument(check, must_exist)
294
+ @lib = nil
295
+ @facade_repository = nil
296
+ @index = validate_path(index_file, must_exist)
297
+ end
287
298
 
299
+ def set_working(work_dir, check = nil, must_exist: nil)
300
+ must_exist = deprecate_check_argument(check, must_exist)
288
301
  @lib = nil
289
- @working_directory = Git::WorkingDirectory.new(work_dir.to_s, must_exist:)
302
+ @facade_repository = nil
303
+ @working_directory = validate_path(work_dir, must_exist)
290
304
  end
291
305
 
292
306
  # returns +true+ if the branch exists locally
293
307
  def local_branch?(branch)
294
- branch_names = branches.local.map(&:name)
295
- branch_names.include?(branch)
308
+ facade_repository.local_branch?(branch)
296
309
  end
297
310
 
298
311
  def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
@@ -305,8 +318,7 @@ module Git
305
318
 
306
319
  # returns +true+ if the branch exists remotely
307
320
  def remote_branch?(branch)
308
- branch_names = branches.remote.map(&:name)
309
- branch_names.include?(branch)
321
+ facade_repository.remote_branch?(branch)
310
322
  end
311
323
 
312
324
  def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
@@ -319,8 +331,7 @@ module Git
319
331
 
320
332
  # returns +true+ if the branch exists
321
333
  def branch?(branch)
322
- branch_names = branches.map(&:name)
323
- branch_names.include?(branch)
334
+ facade_repository.branch?(branch)
324
335
  end
325
336
 
326
337
  def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
@@ -338,7 +349,17 @@ module Git
338
349
  @lib ||= Git::Lib.new(self, @logger)
339
350
  end
340
351
 
341
- # Returns the per-instance git_ssh configuration value.
352
+ # Returns the {Git::Repository} facade for this repository
353
+ #
354
+ # @return [Git::Repository]
355
+ # @api private
356
+ def facade_repository
357
+ @facade_repository ||= Git::Repository.new(
358
+ execution_context: Git::ExecutionContext::Repository.from_base(self, logger: @logger)
359
+ )
360
+ end
361
+
362
+ # Returns the per-instance git_ssh configuration value
342
363
  #
343
364
  # This may be:
344
365
  # * a [String] path when an explicit git_ssh command has been configured
@@ -349,6 +370,16 @@ module Git
349
370
  # @api private
350
371
  attr_reader :git_ssh
351
372
 
373
+ # Returns the per-instance git binary path configuration value
374
+ #
375
+ # This may be:
376
+ # * a [String] path when an explicit binary path has been configured
377
+ # * the Symbol `:use_global_config` when this instance is using the global config
378
+ #
379
+ # @return [String, Symbol] the binary_path configuration value for this instance
380
+ # @api private
381
+ attr_reader :binary_path
382
+
352
383
  # Run a grep for 'string' on the HEAD of the git repository
353
384
  #
354
385
  # @example Limit grep's scope by calling grep() from a specific object:
@@ -367,9 +398,9 @@ module Git
367
398
  # of paths to limit the search to or nil for no limit
368
399
  # @param opts [Hash] options to pass to the underlying `git grep` command
369
400
  #
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
401
+ # @option opts [Boolean, nil] :ignore_case (nil) ignore case when matching
402
+ # @option opts [Boolean, nil] :invert_match (nil) select non-matching lines
403
+ # @option opts [Boolean, nil] :extended_regexp (nil) use extended regular expressions
373
404
  # @option opts [String] :object (HEAD) the object to search from
374
405
  #
375
406
  # @return [Hash<String, Array>] a hash of arrays
@@ -382,30 +413,38 @@ module Git
382
413
  # ```
383
414
  #
384
415
  def grep(string, path_limiter = nil, opts = {})
385
- object('HEAD').grep(string, path_limiter, opts)
416
+ opts = opts.merge(object: facade_repository.rev_parse('HEAD')) unless opts.key?(:object)
417
+ facade_repository.grep(string, path_limiter, opts)
386
418
  end
387
419
 
388
420
  # 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
421
+ # @return [Array<String>] the list of ignored files relative to the root of the worktree
390
422
  #
391
423
  def ignored_files
392
- lib.ignored_files
424
+ facade_repository.ignored_files
393
425
  end
394
426
 
395
427
  # removes file(s) from the git repository
396
428
  def rm(path = '.', opts = {})
397
- lib.rm(path, opts)
429
+ facade_repository.rm(path, opts)
398
430
  end
399
431
 
400
432
  alias remove rm
401
433
 
402
434
  # resets the working directory to the provided commitish
403
435
  def reset(commitish = nil, opts = {})
404
- lib.reset(commitish, opts)
436
+ facade_repository.reset(commitish, **opts)
405
437
  end
406
438
 
407
439
  # resets the working directory to the commitish with '--hard'
440
+ #
441
+ # @deprecated Use {#reset} with `hard: true` instead.
442
+ #
408
443
  def reset_hard(commitish = nil, opts = {})
444
+ Git::Deprecation.warn(
445
+ 'Git::Base#reset_hard is deprecated and will be removed in a future version. ' \
446
+ 'Use Git::Base#reset(commitish, hard: true) instead.'
447
+ )
409
448
  opts = { hard: true }.merge(opts)
410
449
  lib.reset(commitish, opts)
411
450
  end
@@ -418,7 +457,7 @@ module Git
418
457
  # :ff
419
458
  #
420
459
  def clean(opts = {})
421
- lib.clean(opts)
460
+ facade_repository.clean(opts)
422
461
  end
423
462
 
424
463
  # returns the most recent tag that is reachable from a commit
@@ -447,7 +486,7 @@ module Git
447
486
  # :no_edit
448
487
  #
449
488
  def revert(commitish = nil, opts = {})
450
- lib.revert(commitish, opts)
489
+ facade_repository.revert(commitish, **opts)
451
490
  end
452
491
 
453
492
  # commits all pending changes in the index file to the git repository
@@ -459,69 +498,71 @@ module Git
459
498
  # :author
460
499
  #
461
500
  def commit(message, opts = {})
462
- lib.commit(message, opts)
501
+ facade_repository.commit(message, **opts)
463
502
  end
464
503
 
465
504
  # commits all pending changes in the index file to the git repository,
466
505
  # but automatically adds all modified files without having to explicitly
467
506
  # calling @git.add() on them.
468
507
  def commit_all(message, opts = {})
469
- opts = { add_all: true }.merge(opts)
470
- lib.commit(message, opts)
508
+ facade_repository.commit_all(message, **opts)
471
509
  end
472
510
 
473
511
  # checks out a branch as the new git working directory
474
512
  def checkout(*, **)
475
- lib.checkout(*, **)
513
+ facade_repository.checkout(*, **)
476
514
  end
477
515
 
478
516
  # checks out an old version of a file
479
517
  def checkout_file(version, file)
480
- lib.checkout_file(version, file)
518
+ facade_repository.checkout_file(version, file)
481
519
  end
482
520
 
483
521
  # fetches changes from a remote branch - this does not modify the working directory,
484
522
  # it just gets the changes from the remote if there are any
485
523
  def fetch(remote = 'origin', opts = {})
486
- if remote.is_a?(Hash)
487
- opts = remote
488
- remote = nil
489
- end
490
- lib.fetch(remote, opts)
524
+ facade_repository.fetch(remote, opts)
491
525
  end
492
526
 
493
527
  # Push changes to a remote repository
494
528
  #
495
529
  # @overload push(remote = nil, branch = nil, options = {})
530
+ #
496
531
  # @param remote [String] the remote repository to push to
532
+ #
497
533
  # @param branch [String] the branch to push
534
+ #
498
535
  # @param options [Hash] options to pass to the push command
499
536
  #
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
537
+ # @option options [Boolean, nil] :mirror (nil) push all refs under refs/heads/, refs/tags/ and refs/remotes/
538
+ #
539
+ # @option options [Boolean, nil] :delete (nil) delete refs that don't exist on the remote
505
540
  #
506
- # @return [Void]
541
+ # @option options [Boolean, nil] :force (nil) force updates
542
+ #
543
+ # @option options [Boolean, nil] :tags (nil) push all refs under refs/tags/
544
+ #
545
+ # @option options [String, Array<String>] :push_option (nil) push options to transmit
546
+ #
547
+ # @return [String] the stdout output from the push command
507
548
  #
508
549
  # @raise [Git::FailedError] if the push fails
509
550
  # @raise [ArgumentError] if a branch is given without a remote
510
551
  #
511
552
  def push(*, **)
512
- lib.push(*, **)
553
+ facade_repository.push(*, **)
513
554
  end
514
555
 
515
556
  # merges one or more branches into the current working branch
516
557
  #
517
558
  # you can specify more than one branch to merge by passing an array of branches
518
559
  def merge(branch, message = 'merge', opts = {})
519
- lib.merge(branch, message, opts)
560
+ facade_repository.merge(branch, message, opts)
520
561
  end
521
562
 
522
563
  # iterates over the files which are unmerged
523
- def each_conflict(&) # :yields: file, your_version, their_version
524
- lib.conflicts(&)
564
+ def each_conflict(&)
565
+ facade_repository.each_conflict(&)
525
566
  end
526
567
 
527
568
  # Pulls the given branch from the given remote into the current branch
@@ -530,7 +571,7 @@ module Git
530
571
  # @param branch [String] the branch to pull from
531
572
  # @param opts [Hash] options to pass to the pull command
532
573
  #
533
- # @option opts [Boolean] :allow_unrelated_histories (false) Merges histories of
574
+ # @option opts [Boolean, nil] :allow_unrelated_histories (nil) merges histories of
534
575
  # two projects that started their lives independently
535
576
  # @example pulls from origin/master
536
577
  # @git.pull
@@ -539,17 +580,17 @@ module Git
539
580
  # @example pulls from upstream/develop
540
581
  # @git.pull('upstream', 'develop')
541
582
  #
542
- # @return [Void]
583
+ # @return [String] the stdout output from the pull command
543
584
  #
544
585
  # @raise [Git::FailedError] if the pull fails
545
586
  # @raise [ArgumentError] if a branch is given without a remote
546
587
  def pull(remote = nil, branch = nil, opts = {})
547
- lib.pull(remote, branch, opts)
588
+ facade_repository.pull(remote, branch, opts)
548
589
  end
549
590
 
550
591
  # returns an array of Git:Remote objects
551
592
  def remotes
552
- lib.remotes.map { |r| Git::Remote.new(self, r) }
593
+ facade_repository.remotes
553
594
  end
554
595
 
555
596
  # sets the url for a remote
@@ -558,9 +599,7 @@ module Git
558
599
  # @git.set_remote_url('scotts_git', 'git://repo.or.cz/rubygit.git')
559
600
  #
560
601
  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)
602
+ facade_repository.set_remote_url(name, url)
564
603
  end
565
604
 
566
605
  # Configures which branches are fetched for a remote
@@ -592,24 +631,19 @@ module Git
592
631
  # the underlying git command fails
593
632
  #
594
633
  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
634
+ facade_repository.remote_set_branches(name, *branches, add: add)
601
635
  end
602
636
 
603
637
  # removes a remote from this repository
604
638
  #
605
639
  # @git.remove_remote('scott_git')
606
640
  def remove_remote(name)
607
- lib.remote_remove(name)
641
+ facade_repository.remove_remote(name)
608
642
  end
609
643
 
610
- # returns an array of all Git::Tag objects for this repository
644
+ # returns an array of all Git::Object::Tag objects for this repository
611
645
  def tags
612
- lib.tags.map { |r| tag(r) }
646
+ facade_repository.tags
613
647
  end
614
648
 
615
649
  # Create a new git tag
@@ -620,29 +654,53 @@ module Git
620
654
  # repo.add_tag('tag_name', {:options => 'here'})
621
655
  #
622
656
  # @param [String] name The name of the tag to add
623
- # @param [Hash] options Opstions to pass to `git tag`.
657
+ # @param [Hash] options Options to pass to `git tag`.
624
658
  # 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)
659
+ # @option options [Boolean, nil] :annotate (nil) make an unsigned, annotated tag object
660
+ # @option options [Boolean, nil] :a (nil) an alias for the `:annotate` option
661
+ # @option options [Boolean, nil] :d (nil) delete existing tag with the given name —
662
+ # deprecated; use {#delete_tag} instead (alias: `:delete`)
663
+ # @option options [Boolean, nil] :delete (nil) delete existing tag with the given name —
664
+ # deprecated; use {#delete_tag} instead (alias: `:d`)
665
+ # @option options [Boolean, nil] :f (nil) replace an existing tag with the given name (instead of failing)
629
666
  # @option options [String] :message Use the given tag message
630
667
  # @option options [String] :m An alias for the `:message` option
631
- # @option options [boolean] :s Make a GPG-signed tag.
668
+ # @option options [Boolean, nil] :s (nil) make a GPG-signed tag
632
669
  #
633
670
  def add_tag(name, *options)
634
- lib.tag(name, *options)
635
- tag(name)
671
+ facade_repository.add_tag(name, *options)
636
672
  end
637
673
 
638
674
  # deletes a tag
639
675
  def delete_tag(name)
640
- lib.tag(name, { d: true })
676
+ facade_repository.delete_tag(name)
641
677
  end
642
678
 
643
- # creates an archive file of the given tree-ish
679
+ # Creates an archive of the given tree-ish and writes it to a file
680
+ #
681
+ # @api public
682
+ #
683
+ # @param treeish [String] the commit, tag, branch, or tree to archive
684
+ #
685
+ # @param file [String, nil] destination file path; a temp file is created
686
+ # if `nil`
687
+ #
688
+ # @param opts [Hash] archive options (see {Git::Repository::ObjectOperations#archive})
689
+ #
690
+ # @return [String] the path to the written archive file
691
+ #
692
+ # @raise [ArgumentError] if unsupported options are provided
693
+ #
694
+ # @raise [ArgumentError] if `file` is an existing directory
695
+ #
696
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
697
+ #
698
+ # @example Archive HEAD to a zip file
699
+ # git.archive('HEAD', '/tmp/release.zip', format: 'zip')
700
+ # #=> "/tmp/release.zip"
701
+ #
644
702
  def archive(treeish, file = nil, opts = {})
645
- object(treeish).archive(file, opts)
703
+ facade_repository.archive(treeish, file, opts)
646
704
  end
647
705
 
648
706
  # repacks the repository
@@ -665,14 +723,14 @@ module Git
665
723
  # references in the refs namespace, and all reflogs.
666
724
  # @param [Hash] options options to pass to the underlying `git fsck` command
667
725
  #
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
726
+ # @option options [Boolean, nil] :unreachable (nil) print unreachable objects
727
+ # @option options [Boolean, nil] :strict (nil) enable strict checking
728
+ # @option options [Boolean, nil] :connectivity_only (nil) check only connectivity (faster)
729
+ # @option options [Boolean, nil] :root (nil) report root nodes
730
+ # @option options [Boolean, nil] :tags (nil) report tags
731
+ # @option options [Boolean, nil] :cache (nil) consider objects in the index
732
+ # @option options [Boolean, nil] :no_reflogs (nil) do not consider reflogs
733
+ # @option options [Boolean, nil] :lost_found (nil) write dangling objects to .git/lost-found
676
734
  # (note: this modifies the repository by creating files)
677
735
  # @option options [Boolean, nil] :dangling print dangling objects (true/false/nil for default)
678
736
  # @option options [Boolean, nil] :full check objects in alternate pools (true/false/nil for default)
@@ -683,10 +741,10 @@ module Git
683
741
  #
684
742
  # @example Check repository integrity
685
743
  # result = git.fsck
686
- # result.dangling.each { |obj| puts "#{obj.type}: #{obj.sha}" }
744
+ # result.dangling.each { |obj| puts "#{obj.type}: #{obj.oid}" }
687
745
  #
688
746
  # @example Check with strict mode and suppress dangling output
689
- # result = git.fsck(strict: true, dangling: false)
747
+ # result = git.fsck(strict: true, no_dangling: true)
690
748
  #
691
749
  # @example Check if repository has any issues
692
750
  # result = git.fsck
@@ -694,14 +752,14 @@ module Git
694
752
  #
695
753
  # @example List root commits
696
754
  # result = git.fsck(root: true)
697
- # result.root.each { |obj| puts obj.sha }
755
+ # result.root.each { |obj| puts obj.oid }
698
756
  #
699
757
  # @example Check specific objects
700
758
  # result = git.fsck('abc1234', 'def5678')
701
759
  #
702
760
  # rubocop:disable Style/ArgumentsForwarding
703
761
  def fsck(*objects, **opts)
704
- lib.fsck(*objects, **opts)
762
+ facade_repository.fsck(*objects, **opts)
705
763
  end
706
764
  # rubocop:enable Style/ArgumentsForwarding
707
765
 
@@ -721,7 +779,7 @@ module Git
721
779
  # @param [String|NilClass] path the path of the file to be shown
722
780
  # @return [String] the object information
723
781
  def show(objectish = nil, path = nil)
724
- lib.show(objectish, path)
782
+ facade_repository.show(objectish, path)
725
783
  end
726
784
 
727
785
  ## LOWER LEVEL INDEX OPERATIONS ##
@@ -750,7 +808,7 @@ module Git
750
808
  end
751
809
 
752
810
  def checkout_index(opts = {})
753
- lib.checkout_index(opts)
811
+ facade_repository.checkout_index(opts)
754
812
  end
755
813
 
756
814
  def read_tree(treeish, opts = {})
@@ -758,23 +816,22 @@ module Git
758
816
  end
759
817
 
760
818
  def write_tree
761
- lib.write_tree
819
+ facade_repository.write_tree
762
820
  end
763
821
 
764
822
  def write_and_commit_tree(opts = {})
765
- tree = write_tree
766
- commit_tree(tree, opts)
823
+ Git::Object::Commit.new(self, facade_repository.write_and_commit_tree(**opts))
767
824
  end
768
825
 
769
826
  def update_ref(branch, commit)
770
- branch(branch).update_ref(commit)
827
+ facade_repository.update_ref(branch, commit)
771
828
  end
772
829
 
773
830
  def ls_files(location = nil)
774
- lib.ls_files(location)
831
+ facade_repository.ls_files(location)
775
832
  end
776
833
 
777
- def with_working(work_dir) # :yields: the Git::WorkingDirectory
834
+ def with_working(work_dir) # :yields: the working directory Pathname
778
835
  return_value = false
779
836
  old_working = @working_directory
780
837
  set_working(work_dir)
@@ -802,29 +859,58 @@ module Git
802
859
  # git.rev_parse('v2.4:/doc/index.html')
803
860
  #
804
861
  def rev_parse(objectish)
805
- lib.rev_parse(objectish)
862
+ facade_repository.rev_parse(objectish)
806
863
  end
807
864
 
808
865
  # For backwards compatibility
809
866
  alias revparse rev_parse
810
867
 
811
- def ls_tree(objectish, opts = {})
812
- lib.ls_tree(objectish, opts)
868
+ # Returns the number of entries in a git tree object
869
+ #
870
+ # @example Count recursive entries in the HEAD tree
871
+ # git.tree_depth('HEAD^{tree}') #=> 42
872
+ #
873
+ # @param objectish [String] the tree-ish object to recurse into
874
+ #
875
+ # @return [Integer] the number of entries in the recursive tree listing
876
+ #
877
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
878
+ #
879
+ # @see Git::Repository::ObjectOperations#tree_depth
880
+ #
881
+ def tree_depth(objectish)
882
+ facade_repository.tree_depth(objectish)
813
883
  end
814
884
 
815
- # Returns the contents of a git object
885
+ # Lists the objects in a git tree object
886
+ #
887
+ # @example List all top-level objects
888
+ # git.ls_tree('HEAD')
889
+ # # => { 'blob' => { 'README.md' => { mode: '100644', sha: '...' } }, ... }
890
+ #
891
+ # @param objectish [String] the tree-ish object to list
816
892
  #
817
- # Uses `git cat-file -p` to pretty-print the contents of the given object.
893
+ # @param opts [Hash] additional options
818
894
  #
819
- # @param objectish [String] a SHA, branch name, tag, or other revision reference
820
- # to the git object
895
+ # @option opts [Boolean, nil] :recursive (nil) recurse into subtrees
821
896
  #
822
- # @return [String] the contents of the object
897
+ # @option opts [String, Array<String>] :path (nil) limit the listing to
898
+ # the given path or array of paths
823
899
  #
824
- # @see https://git-scm.com/docs/git-cat-file git-cat-file
900
+ # @return [Hash<String, Hash<String, Hash>>] a three-level Hash keyed by
901
+ # object type (`'blob'`, `'tree'`, `'commit'`), then by filename, then
902
+ # holding `:mode` and `:sha` values
825
903
  #
904
+ # @raise [ArgumentError] when unsupported options are provided
905
+ #
906
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
907
+ #
908
+ def ls_tree(objectish, opts = {})
909
+ facade_repository.ls_tree(objectish, opts)
910
+ end
911
+
826
912
  def cat_file(objectish)
827
- lib.cat_file_contents(objectish)
913
+ lib.cat_file(objectish)
828
914
  end
829
915
 
830
916
  # The name of the branch HEAD refers to or 'HEAD' if detached
@@ -836,59 +922,84 @@ module Git
836
922
  # @return [String] the name of the branch HEAD refers to or 'HEAD' if detached
837
923
  #
838
924
  def current_branch
839
- lib.branch_current
925
+ facade_repository.current_branch
840
926
  end
841
927
 
842
928
  # @return [Git::Branch] an object for branch_name
843
929
  def branch(branch_name = current_branch)
844
- Git::Branch.new(self, branch_name)
930
+ facade_repository.branch(branch_name)
845
931
  end
846
932
 
847
933
  # @return [Git::Branches] a collection of all the branches in the repository.
848
934
  # Each branch is represented as a {Git::Branch}.
849
935
  def branches
850
- Git::Branches.new(self)
936
+ facade_repository.branches
851
937
  end
852
938
 
853
- # returns a Git::Worktree object for dir, commitish
939
+ # Returns a {Git::Worktree} object for the given path and optional commitish
940
+ #
941
+ # @example Create a worktree object for an existing path
942
+ # worktree = repo.worktree('/path/to/worktree')
943
+ #
944
+ # @param dir [String] filesystem path of the worktree
945
+ #
946
+ # @param commitish [String, nil] branch, tag, or commit to check out
947
+ #
948
+ # @return [Git::Worktree] worktree object for the given path
949
+ #
854
950
  def worktree(dir, commitish = nil)
855
- Git::Worktree.new(self, dir, commitish)
951
+ facade_repository.worktree(dir, commitish)
856
952
  end
857
953
 
858
- # returns a Git::worktrees object of all the Git::Worktrees
859
- # objects for this repo
954
+ # Returns a {Git::Worktrees} collection of all worktrees in the repository
955
+ #
956
+ # @example List paths for all worktrees
957
+ # repo.worktrees.each { |wt| puts wt.dir }
958
+ #
959
+ # @return [Git::Worktrees] all linked and main worktrees
960
+ #
961
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
962
+ #
860
963
  def worktrees
861
- Git::Worktrees.new(self)
964
+ facade_repository.worktrees
862
965
  end
863
966
 
864
967
  # @return [Git::Object::Commit] a commit object
865
968
  def commit_tree(tree = nil, opts = {})
866
- Git::Object::Commit.new(self, lib.commit_tree(tree, opts))
969
+ Git::Object::Commit.new(self, facade_repository.commit_tree(tree, **opts))
867
970
  end
868
971
 
869
972
  # @return [Git::Diff] a Git::Diff object
870
973
  def diff(objectish = 'HEAD', obj2 = nil)
871
- Git::Diff.new(self, objectish, obj2)
974
+ facade_repository.diff(objectish, obj2)
872
975
  end
873
976
 
874
977
  # @return [Git::Object] a Git object
875
978
  def gblob(objectish)
876
- Git::Object.new(self, objectish, 'blob')
979
+ facade_repository.gblob(objectish)
877
980
  end
878
981
 
879
982
  # @return [Git::Object] a Git object
880
983
  def gcommit(objectish)
881
- Git::Object.new(self, objectish, 'commit')
984
+ facade_repository.gcommit(objectish)
882
985
  end
883
986
 
884
987
  # @return [Git::Object] a Git object
885
988
  def gtree(objectish)
886
- Git::Object.new(self, objectish, 'tree')
989
+ facade_repository.gtree(objectish)
887
990
  end
888
991
 
889
992
  # @return [Git::Log] a log with the specified number of commits
890
993
  def log(count = 30)
891
- Git::Log.new(self, count)
994
+ facade_repository.log(count)
995
+ end
996
+
997
+ # Return commits that are within the given revision range
998
+ #
999
+ # @param opts [Hash] options for the log query
1000
+ # @return [Array<Hash>] the parsed raw log output for each commit
1001
+ def full_log_commits(opts = {})
1002
+ facade_repository.full_log_commits(opts)
892
1003
  end
893
1004
 
894
1005
  # returns a Git::Object of the appropriate type
@@ -902,22 +1013,22 @@ module Git
902
1013
  #
903
1014
  # @return [Git::Object] an instance of the appropriate type of Git::Object
904
1015
  def object(objectish)
905
- Git::Object.new(self, objectish)
1016
+ facade_repository.object(objectish)
906
1017
  end
907
1018
 
908
1019
  # @return [Git::Remote] a remote of the specified name
909
1020
  def remote(remote_name = 'origin')
910
- Git::Remote.new(self, remote_name)
1021
+ facade_repository.remote(remote_name)
911
1022
  end
912
1023
 
913
1024
  # @return [Git::Status] a status object
914
1025
  def status
915
- Git::Status.new(self)
1026
+ facade_repository.status
916
1027
  end
917
1028
 
918
1029
  # @return [Git::Object::Tag] a tag object
919
1030
  def tag(tag_name)
920
- Git::Object::Tag.new(self, tag_name)
1031
+ facade_repository.tag(tag_name)
921
1032
  end
922
1033
 
923
1034
  # Find as good common ancestors as possible for a merge
@@ -925,204 +1036,169 @@ module Git
925
1036
  #
926
1037
  # @return [Array<Git::Object::Commit>] a collection of common ancestors
927
1038
  def merge_base(*)
928
- shas = lib.merge_base(*)
929
- shas.map { |sha| gcommit(sha) }
1039
+ facade_repository.merge_base(*).map { |sha| gcommit(sha) }
930
1040
  end
931
1041
 
932
- # Returns a Git::Diff::Stats object for accessing diff statistics.
1042
+ # Returns the full unified diff patch text between two commits
933
1043
  #
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.
1044
+ # @example Get the patch for the most recent commit
1045
+ # repo.diff_full #=> "diff --git a/lib/foo.rb b/lib/foo.rb\n..."
944
1046
  #
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
1047
+ # @param obj1 [String] the first commit or object to compare; defaults to
1048
+ # `'HEAD'`
970
1049
  #
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.
1050
+ # @param obj2 [String, nil] the second commit or object to compare
974
1051
  #
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
1052
+ # When `nil`, the comparison is against the index or working tree.
1002
1053
  #
1003
- # Updates the options parameter by setting appropriate values for the following keys:
1004
- # * options[:working_directory]
1005
- # * options[:repository]
1006
- # * options[:index]
1054
+ # @param opts [Hash] options to filter the diff
1007
1055
  #
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.
1056
+ # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
1057
+ # limit the diff to the given path(s)
1010
1058
  #
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]
1059
+ # @return [String] the unified diff patch output
1020
1060
  #
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
1061
+ # @note Unknown option keys are silently ignored for backward compatibility;
1062
+ # only `:path_limiter` is forwarded to the underlying command.
1026
1063
  #
1027
- # Finally, if options[:working_directory] is a relative path, convert it to an absoluite
1028
- # path relative to the current directory.
1064
+ # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
1029
1065
  #
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
1066
+ # @see Git::Repository::Diffing#diff_full
1067
+ #
1068
+ def diff_full(obj1 = 'HEAD', obj2 = nil, opts = {})
1069
+ facade_repository.diff_full(obj1, obj2, opts.slice(:path_limiter))
1039
1070
  end
1040
1071
 
1041
- # Normalize options[:repository]
1072
+ # Returns a lazy {Git::DiffStats} object for accessing diff statistics
1042
1073
  #
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
1074
+ # Compares (1) two commits, (2) a commit against the working tree, or (3) the
1075
+ # index against the working tree and constructs a lazy {Git::DiffStats} that
1076
+ # computes per-file insertion and deletion counts on demand when its accessor
1077
+ # methods are called.
1047
1078
  #
1048
- # Otherwise, set to the first non-nil value of:
1049
- # 1. `options[:repository]`
1050
- # 2. `.git`
1079
+ # **Comparing two commits**
1051
1080
  #
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.
1081
+ # When both objectish and obj2 are provided, the comparison is between those two
1082
+ # refs (commits, tags, branches, etc.).
1057
1083
  #
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
1084
+ # **Comparing a commit against the working tree**
1062
1085
  #
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
1086
+ # When only objectish is provided (and isn't nil), the comparison is between
1087
+ # objectish and the working tree; the stats reflect all changes since objectish.
1070
1088
  #
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.
1089
+ # **Comparing the index against the working tree**
1076
1090
  #
1077
- # @api private
1091
+ # When objectish is explicitly `nil` then obj2 must be omitted or `nil`. In this
1092
+ # case, the comparison is between the index and the working tree; the stats reflect
1093
+ # unstaged changes.
1078
1094
  #
1079
- # @param options [Hash] The options hash, checked for `[:repository]`.
1095
+ # @example Get working tree stats since HEAD
1096
+ # repo.diff_stats.insertions #=> 3
1080
1097
  #
1081
- # @param default [String] A fallback path if `options[:repository]` is not set.
1098
+ # @example Compare two specific commits
1099
+ # repo.diff_stats('abc1234', 'def5678')
1082
1100
  #
1083
- # @param bare [Boolean] Whether the repository is bare, which changes path resolution.
1101
+ # @example Get unstaged stats (index vs. working tree)
1102
+ # repo.diff_stats(nil).insertions
1084
1103
  #
1085
- # @return [String] The initial, absolute path to the `.git` directory or file.
1104
+ # @example Limit stats to a sub-path
1105
+ # repo.diff_stats('HEAD~1', 'HEAD', path_limiter: 'lib/')
1086
1106
  #
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
1107
+ # @param objectish [String, nil] the first commit or object to compare; defaults to
1108
+ # `'HEAD'`; pass `nil` to compare the index against the working tree
1109
+ #
1110
+ # @param obj2 [String, nil] the second commit or object to compare
1111
+ #
1112
+ # @param opts [Hash] options to filter the diff
1113
+ #
1114
+ # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
1115
+ # limit the stats to the given path(s)
1116
+ #
1117
+ # @return [Git::DiffStats] a lazy stats object for the comparison
1118
+ #
1119
+ # @note Unknown option keys are silently ignored for backward compatibility;
1120
+ # only `:path_limiter` is forwarded to the underlying command.
1121
+ #
1122
+ # @raise [ArgumentError] if `objectish` or `obj2` starts with `"-"`
1123
+ #
1124
+ # @raise [ArgumentError] if `objectish` is `nil` but `obj2` is not
1125
+ #
1126
+ # @see Git::Repository::Diffing#diff_stats
1127
+ #
1128
+ def diff_stats(objectish = 'HEAD', obj2 = nil, opts = {})
1129
+ facade_repository.diff_stats(objectish, obj2, opts.slice(:path_limiter))
1093
1130
  end
1094
1131
 
1095
- # Resolves the path to the actual repository if it's a `gitdir:` pointer file.
1132
+ # Returns the file path status between two commits
1096
1133
  #
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.
1134
+ # @example Get all changed files between HEAD and the previous commit
1135
+ # repo.diff_path_status.to_h #=> { "README.md" => "M", "lib/foo.rb" => "A" }
1100
1136
  #
1101
- # @api private
1137
+ # @param objectish [String] the first commit or object to compare; defaults to
1138
+ # `'HEAD'`
1102
1139
  #
1103
- # @param path [String] The initial path to the repository, which may be a pointer file.
1140
+ # @param obj2 [String, nil] the second commit or object to compare
1104
1141
  #
1105
- # @param working_dir [String] The working directory, used as a base to resolve the path.
1142
+ # @param opts [Hash] options to filter the diff
1106
1143
  #
1107
- # @return [String] The final, resolved absolute path to the repository directory.
1144
+ # @option opts [String, Pathname, Array<String, Pathname>, nil] :path_limiter (nil)
1145
+ # limit the status report to specified path(s)
1108
1146
  #
1109
- private_class_method def self.resolve_gitdir_if_present(path, working_dir)
1110
- return path unless File.file?(path)
1147
+ # @option opts [String, Pathname, Array<String, Pathname>, nil] :path (nil)
1148
+ # deprecated; use `:path_limiter` instead
1149
+ #
1150
+ # @return [Git::DiffPathStatus] the name-status report for the comparison
1151
+ #
1152
+ # @raise [ArgumentError] if `objectish` or `obj2` starts with `"-"`
1153
+ #
1154
+ # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
1155
+ #
1156
+ # @see Git::Repository::Diffing#diff_path_status
1157
+ #
1158
+ def diff_path_status(objectish = 'HEAD', obj2 = nil, opts = {})
1159
+ facade_repository.diff_path_status(objectish, obj2, opts.slice(:path_limiter, :path))
1160
+ end
1111
1161
 
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)
1162
+ # Compares the index and the working directory
1163
+ #
1164
+ # @example List all files with unstaged changes
1165
+ # repo.diff_files #=> { "lib/foo.rb" => { mode_index: "100644", ... } }
1166
+ #
1167
+ # @return [Hash{String => Hash}] a hash keyed by file path; see
1168
+ # {Git::Repository::Diffing#diff_files} for the full key list
1169
+ #
1170
+ # @raise [Git::FailedError] if git exits outside the allowed range (exit code > 1)
1171
+ #
1172
+ # @see Git::Repository::Diffing#diff_files
1173
+ #
1174
+ def diff_files
1175
+ facade_repository.diff_files
1116
1176
  end
1117
1177
 
1118
- # Normalize options[:index]
1178
+ # Alias for {#diff_path_status}; provided for backward compatibility
1179
+ #
1180
+ # @return [Git::DiffPathStatus] the name-status report for the comparison
1119
1181
  #
1120
- # If options[:index] is a relative directory, convert it to an absolute
1121
- # directory relative to the repository directory
1182
+ # @deprecated Use {#diff_path_status} instead
1122
1183
  #
1123
- private_class_method def self.normalize_index(options)
1124
- index = File.expand_path(options[:index] || 'index', options[:repository])
1125
- options[:index] = index
1184
+ # @see #diff_path_status
1185
+ alias diff_name_status diff_path_status
1186
+
1187
+ private
1188
+
1189
+ # Initializes the logger from the provided options
1190
+ # @param log_option [Logger, nil] The logger instance from options.
1191
+ def setup_logger(log_option)
1192
+ @logger = log_option || Logger.new(nil)
1193
+ @logger.info('Starting Git')
1194
+ end
1195
+
1196
+ # Initializes the core git objects based on the provided options
1197
+ # @param options [Hash] The processed options hash.
1198
+ def initialize_components(options)
1199
+ @working_directory = Pathname.new(options[:working_directory]) if options[:working_directory]
1200
+ @repository = Pathname.new(options[:repository]) if options[:repository]
1201
+ @index = Pathname.new(options[:index]) if options[:index]
1126
1202
  end
1127
1203
  end
1128
1204
  end