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
@@ -0,0 +1,501 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'git/command_line'
5
+
6
+ module Git
7
+ # Base class for execution contexts that run git commands
8
+ #
9
+ # An execution context bundles three concerns that together describe *how* and
10
+ # *where* a git command runs:
11
+ #
12
+ # 1. **Repository scope** — the public accessors `git_dir`, `git_work_dir`,
13
+ # `git_index_file`, and `git_ssh` identify which repository git targets and
14
+ # which SSH wrapper to use. Their values are translated into `GIT_*` environment
15
+ # variable overrides by the private `env_overrides` method. A `nil` value
16
+ # unsets the variable (see `Process.spawn` semantics).
17
+ #
18
+ # 2. **CLI global options** — the private `global_opts` method returns the array
19
+ # of git flags prepended to every invocation: `--git-dir` / `--work-tree` when
20
+ # those attributes are set, plus the static options in {STATIC_GLOBAL_OPTS} that
21
+ # ensure deterministic, script-friendly output.
22
+ #
23
+ # 3. **Execution defaults** — {COMMAND_CAPTURING_ARG_DEFAULTS} and
24
+ # {COMMAND_STREAMING_ARG_DEFAULTS} define the default values for I/O, encoding,
25
+ # and behavioral options (`in:`, `out:`, `normalize:`, `timeout:`, etc.) accepted
26
+ # by {#command_capturing} and {#command_streaming}.
27
+ #
28
+ # Subclasses override the repository-scope accessors to supply context-specific
29
+ # values. The `env_overrides` and `global_opts` methods are implemented here and
30
+ # call those accessors, so subclasses do not need to override them directly.
31
+ #
32
+ # Concrete subclasses:
33
+ # - {Git::ExecutionContext::Repository} — for repository-bound commands (`add`, `commit`, …)
34
+ # - {Git::ExecutionContext::Global} — for commands that do not require an existing repository
35
+ # (`init`, `clone`, `version`)
36
+ #
37
+ # @example Using a concrete subclass
38
+ # context = Git::ExecutionContext::Global.new(binary_path: '/usr/local/bin/git2')
39
+ # context.binary_path #=> "/usr/local/bin/git2"
40
+ #
41
+ # @api private
42
+ #
43
+ class ExecutionContext
44
+ # Default keyword arguments accepted by {#command_capturing}.
45
+ #
46
+ # Derived from {Git::CommandLine::Capturing::RUN_OPTION_DEFAULTS} with two
47
+ # overrides: `normalize: true` and `chomp: true` so callers receive clean
48
+ # UTF-8 strings by default. New options added to the CommandLine layer are
49
+ # automatically accepted here without requiring a coordinated edit.
50
+ #
51
+ # `timeout: nil` is intentional — the global timeout from {Git.config} is
52
+ # applied at call-time so that changes to the config are respected.
53
+ #
54
+ COMMAND_CAPTURING_ARG_DEFAULTS =
55
+ Git::CommandLine::Capturing::RUN_OPTION_DEFAULTS
56
+ .merge(normalize: true, chomp: true)
57
+ .freeze
58
+
59
+ # Default keyword arguments accepted by {#command_streaming}.
60
+ #
61
+ # Identical to {Git::CommandLine::Streaming::RUN_OPTION_DEFAULTS}. Defined
62
+ # here so callers interact with a stable constant on this class, and so that
63
+ # new options added to the CommandLine layer are automatically accepted.
64
+ #
65
+ COMMAND_STREAMING_ARG_DEFAULTS =
66
+ Git::CommandLine::Streaming::RUN_OPTION_DEFAULTS.dup.freeze
67
+
68
+ # Static git global options applied to every invocation.
69
+ #
70
+ # These ensure deterministic, script-friendly output regardless of the
71
+ # user's local git configuration.
72
+ #
73
+ STATIC_GLOBAL_OPTS = %w[
74
+ -c core.quotePath=true
75
+ -c core.editor=false
76
+ -c color.ui=false
77
+ -c color.advice=false
78
+ -c color.diff=false
79
+ -c color.grep=false
80
+ -c color.push=false
81
+ -c color.remote=false
82
+ -c color.showBranch=false
83
+ -c color.status=false
84
+ -c color.transport=false
85
+ ].freeze
86
+
87
+ # Creates a new execution context
88
+ #
89
+ # @param binary_path [String, :use_global_config] path to the git binary
90
+ #
91
+ # Give `:use_global_config` (the default) to use `Git::Config.instance.binary_path`.
92
+ #
93
+ # Passing `nil` raises `ArgumentError` — there is no "unset the
94
+ # binary" semantic.
95
+ #
96
+ # @param git_ssh [String, nil, :use_global_config] the SSH wrapper path
97
+ #
98
+ # Give `nil` to unset `GIT_SSH`, or `:use_global_config` (default)
99
+ # to use `Git::Config.instance.git_ssh`.
100
+ #
101
+ # @param logger [Logger, nil] the logger to use in the CommandLine layer
102
+ #
103
+ # Give `nil` to use a null logger (`Logger.new(nil)`).
104
+ #
105
+ # @raise [NotImplementedError] if called directly on {Git::ExecutionContext}
106
+ # rather than a subclass
107
+ #
108
+ # @raise [ArgumentError] if `binary_path` is `nil`
109
+ #
110
+ def initialize(binary_path: :use_global_config, git_ssh: :use_global_config, logger: nil)
111
+ if instance_of?(Git::ExecutionContext)
112
+ raise NotImplementedError, 'Git::ExecutionContext is an abstract base class'
113
+ end
114
+ raise ArgumentError, 'binary_path must not be nil' if binary_path.nil?
115
+
116
+ @binary_path = binary_path
117
+ @git_ssh = git_ssh
118
+ @logger = logger || Logger.new(nil)
119
+ end
120
+
121
+ # Returns the `GIT_DIR` path for this context
122
+ #
123
+ # `nil` means `GIT_DIR` will be explicitly **unset** in the child process
124
+ # (per `Process.spawn` semantics — unset is not the same as inherited).
125
+ # Subclasses override this to supply a repository-specific path.
126
+ #
127
+ # @example Base class returns nil; subclasses return the actual path
128
+ # context = Git::ExecutionContext::Global.new
129
+ # context.git_dir #=> nil
130
+ #
131
+ # @return [String, nil] the `GIT_DIR` path, or `nil` to unset the variable
132
+ #
133
+ def git_dir = nil
134
+
135
+ # Returns the `GIT_WORK_TREE` path for this context
136
+ #
137
+ # `nil` means `GIT_WORK_TREE` will be explicitly **unset** in the child process.
138
+ #
139
+ # @example Base class returns nil; subclasses return the actual path
140
+ # context = Git::ExecutionContext::Global.new
141
+ # context.git_work_dir #=> nil
142
+ #
143
+ # @return [String, nil] the `GIT_WORK_TREE` path, or `nil` to unset the variable
144
+ #
145
+ def git_work_dir = nil
146
+
147
+ # Returns the `GIT_INDEX_FILE` path for this context
148
+ #
149
+ # `nil` means `GIT_INDEX_FILE` will be explicitly **unset** in the child process.
150
+ #
151
+ # @example Base class returns nil; subclasses return the actual path
152
+ # context = Git::ExecutionContext::Global.new
153
+ # context.git_index_file #=> nil
154
+ #
155
+ # @return [String, nil] the `GIT_INDEX_FILE` path, or `nil` to unset the variable
156
+ #
157
+ def git_index_file = nil
158
+
159
+ # Returns the resolved git binary path for this context
160
+ #
161
+ # `:use_global_config` is resolved to `Git::Config.instance.binary_path` each time a
162
+ # command method is called, so runtime changes to
163
+ # `Git.configure { |c| c.binary_path = ... }`
164
+ # are reflected per command invocation.
165
+ #
166
+ # @example With the default sentinel (resolves from Git::Config.instance at call-time)
167
+ # context = Git::ExecutionContext::Global.new
168
+ # context.binary_path #=> "git"
169
+ #
170
+ # @example With an explicit path
171
+ # context = Git::ExecutionContext::Global.new(binary_path: '/usr/local/bin/git2')
172
+ # context.binary_path #=> "/usr/local/bin/git2"
173
+ #
174
+ # @return [String] the resolved git binary path
175
+ #
176
+ def binary_path
177
+ return Git::Config.instance.binary_path if @binary_path == :use_global_config
178
+
179
+ @binary_path
180
+ end
181
+
182
+ # Returns the resolved `GIT_SSH` wrapper path for this context
183
+ #
184
+ # `:use_global_config` is resolved to `Git::Config.instance.git_ssh` each time a
185
+ # command method is called, so runtime changes to
186
+ # `Git.configure { |c| c.git_ssh = ... }`
187
+ # are reflected per command invocation. `nil` means the variable will be
188
+ # explicitly unset.
189
+ #
190
+ # @example With the default sentinel (resolves from Git::Config.instance at call-time)
191
+ # context = Git::ExecutionContext::Global.new
192
+ # context.git_ssh #=> nil
193
+ #
194
+ # @example With an explicit path
195
+ # context = Git::ExecutionContext::Global.new(git_ssh: '/usr/bin/ssh-wrapper')
196
+ # context.git_ssh #=> "/usr/bin/ssh-wrapper"
197
+ #
198
+ # @return [String, nil] the resolved `GIT_SSH` wrapper path, or `nil` to unset
199
+ #
200
+ def git_ssh
201
+ return Git::Config.instance.git_ssh if @git_ssh == :use_global_config
202
+
203
+ @git_ssh
204
+ end
205
+
206
+ # Returns the logger used by this context
207
+ #
208
+ # @example
209
+ # context = Git::ExecutionContext::Repository.new(git_dir: '/repo/.git', logger: my_logger)
210
+ # context.logger #=> my_logger
211
+ #
212
+ # @return [Logger] the logger instance; never `nil`
213
+ #
214
+ # @api private
215
+ #
216
+ attr_reader :logger
217
+
218
+ # Runs a git command and returns the result
219
+ #
220
+ # By default, raises {Git::FailedError} if the command exits with a non-zero
221
+ # status. Pass `raise_on_failure: false` to suppress this behavior.
222
+ #
223
+ # @overload command_capturing(*args, **options_hash)
224
+ #
225
+ # Runs a git command and returns the result
226
+ #
227
+ # Args should exclude the 'git' command itself and global options. Remember to
228
+ # splat the arguments if given as an array.
229
+ #
230
+ # @example Run git log
231
+ # result = command_capturing('log', '--pretty=oneline')
232
+ # result.stdout #=> "abc123 First commit\ndef456 Second commit\n"
233
+ #
234
+ # @example Using an array of arguments
235
+ # args = ['log', '--pretty=oneline']
236
+ # result = command_capturing(*args)
237
+ #
238
+ # @example Suppress raising on failure
239
+ # result = command_capturing('show', 'nonexistent', raise_on_failure: false)
240
+ # result.status.success? #=> false
241
+ #
242
+ # @param args [Array<String>] the command and its arguments
243
+ #
244
+ # @param options_hash [Hash] the options to pass to the command
245
+ #
246
+ # @option options_hash [IO, nil] :in the IO object to use as stdin, or nil to
247
+ # inherit the parent process stdin
248
+ #
249
+ # Must be a real IO object with a file descriptor.
250
+ #
251
+ # @option options_hash [IO, String, #write, nil] :out the destination for
252
+ # captured stdout
253
+ #
254
+ # @option options_hash [IO, String, #write, nil] :err the destination for
255
+ # captured stderr
256
+ #
257
+ # @option options_hash [Boolean, nil] :normalize (true) normalize the output
258
+ # encoding to UTF-8
259
+ #
260
+ # @option options_hash [Boolean, nil] :chomp (true) remove trailing newlines
261
+ # from the output
262
+ #
263
+ # @option options_hash [Boolean, nil] :merge (false) merge stdout and stderr
264
+ # into a single output
265
+ #
266
+ # @option options_hash [String, nil] :chdir the directory to run the command in
267
+ #
268
+ # @option options_hash [Hash] :env additional environment variable overrides
269
+ # for this command
270
+ #
271
+ # @option options_hash [Boolean, nil] :raise_on_failure (true) whether to raise on
272
+ # non-zero exit
273
+ #
274
+ # @option options_hash [Numeric, nil] :timeout the maximum seconds to wait for
275
+ # the command to complete
276
+ #
277
+ # If timeout is nil, the global timeout from {Git::Config} is used.
278
+ #
279
+ # If timeout is zero, the timeout will not be enforced.
280
+ #
281
+ # If the command times out, it is killed via a `SIGKILL` signal and
282
+ # `Git::TimeoutError` is raised.
283
+ #
284
+ # If the command does not respond to SIGKILL, it will hang this method.
285
+ #
286
+ # @return [Git::CommandLineResult] the result of the command
287
+ #
288
+ # @raise [ArgumentError] if an unknown option is passed
289
+ #
290
+ # @raise [Git::FailedError] if the command failed (when raise_on_failure is
291
+ # true)
292
+ #
293
+ # @raise [Git::SignaledError] if the command was signaled
294
+ #
295
+ # @raise [Git::TimeoutError] if the command times out
296
+ #
297
+ # @raise [Git::ProcessIOError] if an exception was raised while collecting
298
+ # subprocess output
299
+ #
300
+ # The exception's `result` attribute is a {Git::CommandLineResult} which will
301
+ # contain the result of the command including the exit status, stdout, and stderr.
302
+ #
303
+ # @note Individual command classes (under {Git::Commands}) can selectively expose
304
+ # `:timeout` and `:env` and other options to their callers by declaring them as
305
+ # execution options in their Arguments DSL definition and forwarding them to
306
+ # this method. See {Git::Commands::Clone#call} for an example of a command that
307
+ # exposes `:timeout`.
308
+ #
309
+ # @see Git::CommandLine::Capturing#run
310
+ #
311
+ def command_capturing(*, **options_hash)
312
+ options_hash = COMMAND_CAPTURING_ARG_DEFAULTS.merge(options_hash)
313
+ options_hash[:timeout] ||= Git.config.timeout
314
+
315
+ extra_options = options_hash.keys - COMMAND_CAPTURING_ARG_DEFAULTS.keys
316
+ raise ArgumentError, "Unknown options: #{extra_options.join(', ')}" if extra_options.any?
317
+
318
+ env = options_hash.delete(:env)
319
+ raise_on_failure = options_hash.delete(:raise_on_failure)
320
+ command_line_capturing.run(*, raise_on_failure: raise_on_failure, env: env, **options_hash)
321
+ end
322
+
323
+ # Runs a git command using the streaming (non-capturing) execution path
324
+ #
325
+ # Unlike {#command_capturing}, stdout is NOT buffered in memory. It is
326
+ # written only to the IO object provided via the `out:` option. Stderr is
327
+ # captured internally via a StringIO for error diagnostics.
328
+ #
329
+ # Use this entry point when you want to stream large output (e.g. blob
330
+ # content from cat-file) without creating memory pressure.
331
+ #
332
+ # @overload command_streaming(*args, **options_hash)
333
+ #
334
+ # Streams a git command's output to the provided IO object
335
+ #
336
+ # @example Stream blob content to a file
337
+ # File.open('blob.bin', 'wb') do |f|
338
+ # command_streaming('cat-file', 'blob', 'HEAD:large_file.bin', out: f)
339
+ # end
340
+ #
341
+ # @param args [Array<String>] the git command and its arguments
342
+ #
343
+ # @param options_hash [Hash] the options to pass to the command
344
+ #
345
+ # @option options_hash [IO, nil] :in the IO object to use as stdin, or nil to
346
+ # inherit the parent process stdin
347
+ #
348
+ # Must be a real IO object with a file descriptor.
349
+ #
350
+ # @option options_hash [#write, nil] :out destination for streamed stdout
351
+ #
352
+ # @option options_hash [#write, nil] :err an optional additional destination
353
+ # to receive stderr output in real time
354
+ #
355
+ # Stderr is always captured internally; when `err:` is supplied, writes are
356
+ # teed to both the internal buffer and this destination. `result.stderr`
357
+ # always reflects the internal capture.
358
+ #
359
+ # @option options_hash [String, nil] :chdir the directory to run the command in
360
+ #
361
+ # @option options_hash [Hash] :env additional environment variable overrides
362
+ # for this command
363
+ #
364
+ # @option options_hash [Boolean, nil] :raise_on_failure (true) whether to raise on
365
+ # non-zero exit
366
+ #
367
+ # @option options_hash [Numeric, nil] :timeout
368
+ # the maximum seconds to wait for the command to complete
369
+ #
370
+ # If timeout is nil, the global timeout from {Git::Config} is used.
371
+ #
372
+ # If timeout is zero, the timeout will not be enforced.
373
+ #
374
+ # If the command times out, it is killed via a `SIGKILL` signal and
375
+ # `Git::TimeoutError` is raised.
376
+ #
377
+ # If the command does not respond to SIGKILL, it will hang this method.
378
+ #
379
+ # @return [Git::CommandLineResult] the result of the command
380
+ #
381
+ # `result.stdout` will always be `''` — stdout was streamed to `out:`.
382
+ #
383
+ # `result.stderr` contains any stderr output captured for diagnostics.
384
+ #
385
+ # @raise [ArgumentError] if an unknown option is passed
386
+ #
387
+ # @raise [Git::FailedError] if the command failed (when raise_on_failure is true)
388
+ #
389
+ # @raise [Git::SignaledError] if the command was signaled
390
+ #
391
+ # @raise [Git::TimeoutError] if the command times out
392
+ #
393
+ # @raise [Git::ProcessIOError] if an exception was raised while collecting
394
+ # subprocess output
395
+ #
396
+ # @see Git::CommandLine::Streaming#run
397
+ #
398
+ def command_streaming(*, **options_hash)
399
+ options_hash = COMMAND_STREAMING_ARG_DEFAULTS.merge(options_hash)
400
+ options_hash[:timeout] ||= Git.config.timeout
401
+
402
+ extra_options = options_hash.keys - COMMAND_STREAMING_ARG_DEFAULTS.keys
403
+ raise ArgumentError, "Unknown options: #{extra_options.join(', ')}" if extra_options.any?
404
+
405
+ env = options_hash.delete(:env)
406
+ raise_on_failure = options_hash.delete(:raise_on_failure)
407
+ command_line_streaming.run(*, raise_on_failure: raise_on_failure, env: env, **options_hash)
408
+ end
409
+
410
+ # Returns the installed git version
411
+ #
412
+ # The result is memoized per instance. Accepts an optional timeout used
413
+ # only when the version has not yet been fetched for this context.
414
+ #
415
+ # @param timeout [Numeric, nil] seconds to wait for `git version`; `nil`
416
+ # falls back to the global {Git::Config} timeout; `0` disables the timeout
417
+ # entirely (the command runs until it completes or the process is killed)
418
+ #
419
+ # @return [Git::Version] the installed git version
420
+ #
421
+ # @raise [Git::UnexpectedResultError] if the version string cannot be parsed
422
+ #
423
+ def git_version(timeout: nil)
424
+ @git_version ||= begin
425
+ call_opts = timeout.nil? ? {} : { timeout: timeout }
426
+ Git::Version.parse(Git::Commands::Version.new(self).call(**call_opts).stdout)
427
+ end
428
+ end
429
+
430
+ # Returns a Hash of environment variable overrides for this context
431
+ #
432
+ # Builds the standard git environment from the public accessor methods
433
+ # ({#git_dir}, {#git_work_dir}, {#git_index_file}, {#git_ssh}), then
434
+ # merges any per-call `additional_overrides` on top.
435
+ #
436
+ # Per `Process.spawn` semantics, a value of `nil` unsets the variable.
437
+ #
438
+ # @param additional_overrides [Hash<String, String|nil>] per-call overrides
439
+ #
440
+ # @return [Hash<String, String|nil>] the merged environment variable overrides
441
+ #
442
+ # @api private
443
+ #
444
+ def env_overrides(**additional_overrides)
445
+ {
446
+ 'GIT_DIR' => git_dir,
447
+ 'GIT_WORK_TREE' => git_work_dir,
448
+ 'GIT_INDEX_FILE' => git_index_file,
449
+ 'GIT_SSH' => git_ssh,
450
+ 'GIT_EDITOR' => 'true',
451
+ 'LC_ALL' => 'en_US.UTF-8'
452
+ }.merge(additional_overrides)
453
+ end
454
+
455
+ private
456
+
457
+ # Returns the Array of git global option strings for this context
458
+ #
459
+ # Prepends `--git-dir` and `--work-tree` when the corresponding attributes
460
+ # are set, then appends {STATIC_GLOBAL_OPTS}.
461
+ #
462
+ # @return [Array<String>] the global options to prepend to every git invocation
463
+ #
464
+ def global_opts
465
+ [].tap do |opts|
466
+ opts << "--git-dir=#{git_dir}" unless git_dir.nil?
467
+ opts << "--work-tree=#{git_work_dir}" unless git_work_dir.nil?
468
+ opts.concat(STATIC_GLOBAL_OPTS)
469
+ end
470
+ end
471
+
472
+ # Creates a {Git::CommandLine::Capturing} instance for the current invocation.
473
+ #
474
+ # A new instance is created per call so that {#binary_path} — resolved from
475
+ # `Git::Config.instance` when set to `:use_global_config` — and {#env_overrides}
476
+ # — including {#git_ssh} resolution for `:use_global_config` — reflect the
477
+ # state of {Git::Config.instance} at the time of each command invocation.
478
+ #
479
+ # @return [Git::CommandLine::Capturing] the capturing command line instance
480
+ #
481
+ def command_line_capturing
482
+ Git::CommandLine::Capturing.new(env_overrides, binary_path, global_opts, @logger)
483
+ end
484
+
485
+ # Creates a {Git::CommandLine::Streaming} instance for the current invocation.
486
+ #
487
+ # A new instance is created per call so that {#binary_path} — resolved from
488
+ # `Git::Config.instance` when set to `:use_global_config` — and {#env_overrides}
489
+ # — including {#git_ssh} resolution for `:use_global_config` — reflect the
490
+ # state of {Git::Config.instance} at the time of each command invocation.
491
+ #
492
+ # @return [Git::CommandLine::Streaming] the streaming command line instance
493
+ #
494
+ def command_line_streaming
495
+ Git::CommandLine::Streaming.new(env_overrides, binary_path, global_opts, @logger)
496
+ end
497
+ end
498
+ end
499
+
500
+ require 'git/execution_context/global'
501
+ require 'git/execution_context/repository'
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ # Immutable value object representing a reference to a file at a specific point in time
5
+ #
6
+ # FileRef encapsulates the mode, SHA, and path of a file as it exists on one side
7
+ # of a diff. This is used to represent either the source (before) or destination
8
+ # (after) state of a file in a diff operation.
9
+ #
10
+ # When a file doesn't exist on a side of the diff (e.g., src for new files,
11
+ # dst for deleted files), the entire FileRef should be nil rather than having
12
+ # a FileRef with nil attributes.
13
+ #
14
+ # @api public
15
+ #
16
+ # @example A modified file's source reference
17
+ # src = Git::FileRef.new(mode: '100644', sha: 'abc1234', path: 'lib/foo.rb')
18
+ #
19
+ # @example A new file (src would be nil, not a FileRef)
20
+ # # src = nil
21
+ # dst = Git::FileRef.new(mode: '100644', sha: 'def5678', path: 'lib/new_file.rb')
22
+ #
23
+ # @!attribute [r] mode
24
+ # @return [String] the file mode (e.g., '100644' for regular file, '100755' for executable,
25
+ # '120000' for symlink)
26
+ #
27
+ # @!attribute [r] sha
28
+ # @return [String] the blob SHA (object identifier)
29
+ #
30
+ # @!attribute [r] path
31
+ # @return [String] the file path relative to repository root
32
+ #
33
+ FileRef = Data.define(:mode, :sha, :path) do
34
+ # Check if this is a regular file (not executable, symlink, etc.)
35
+ #
36
+ # @return [Boolean] true if mode is 100644
37
+ #
38
+ def regular_file?
39
+ mode == '100644'
40
+ end
41
+
42
+ # Check if this is an executable file
43
+ #
44
+ # @return [Boolean] true if mode is 100755
45
+ #
46
+ def executable?
47
+ mode == '100755'
48
+ end
49
+
50
+ # Check if this is a symbolic link
51
+ #
52
+ # @return [Boolean] true if mode is 120000
53
+ #
54
+ def symlink?
55
+ mode == '120000'
56
+ end
57
+
58
+ # Return the mode as an integer (parsed as octal)
59
+ #
60
+ # Useful for bit operations on file permissions.
61
+ #
62
+ # @return [Integer] the mode as an integer
63
+ #
64
+ # @example Check file permissions
65
+ # ref.mode_bits & 0o777 # => 0o644 (420 decimal)
66
+ #
67
+ # @example Check if group writable
68
+ # (ref.mode_bits & 0o020) != 0
69
+ #
70
+ def mode_bits
71
+ mode.to_i(8)
72
+ end
73
+ end
74
+ end
@@ -13,9 +13,9 @@ module Git
13
13
  # @return [Symbol] one of :commit, :tree, :blob, or :tag
14
14
  attr_reader :type
15
15
 
16
- # The SHA-1 hash of the object
17
- # @return [String] the 40-character SHA-1 hash
18
- attr_reader :sha
16
+ # The object identifier (OID) of the object
17
+ # @return [String] the 40-character object identifier
18
+ attr_reader :oid
19
19
 
20
20
  # A warning or error message associated with this object
21
21
  # @return [String, nil] the message, or nil if no message
@@ -28,21 +28,21 @@ module Git
28
28
  # Create a new FsckObject
29
29
  #
30
30
  # @param type [Symbol] the object type (:commit, :tree, :blob, or :tag)
31
- # @param sha [String] the 40-character SHA-1 hash
31
+ # @param oid [String] the 40-character object identifier
32
32
  # @param message [String, nil] optional warning/error message
33
33
  # @param name [String, nil] optional name from --name-objects (e.g., "HEAD~2^2:src/")
34
34
  #
35
- def initialize(type:, sha:, message: nil, name: nil)
35
+ def initialize(type:, oid:, message: nil, name: nil)
36
36
  @type = type
37
- @sha = sha
37
+ @oid = oid
38
38
  @message = message
39
39
  @name = name
40
40
  end
41
41
 
42
- # Returns the SHA as the string representation
43
- # @return [String] the SHA-1 hash
42
+ # Returns the OID as the string representation
43
+ # @return [String] the object identifier
44
44
  def to_s
45
- sha
45
+ oid
46
46
  end
47
47
  end
48
48
  end
@@ -85,7 +85,7 @@ module Git
85
85
  #
86
86
  # @example
87
87
  # result = git.fsck
88
- # result.all_objects.each { |obj| puts obj.sha }
88
+ # result.all_objects.each { |obj| puts obj.oid }
89
89
  #
90
90
  def all_objects
91
91
  dangling + missing + unreachable + warnings
data/lib/git/log.rb CHANGED
@@ -42,7 +42,8 @@ module Git
42
42
  # git = Git.open('.')
43
43
  # Git::Log.new(git)
44
44
  #
45
- # @param base [Git::Base] the git repository object
45
+ # @param base [Git::Repository] the git repository object
46
+ #
46
47
  # @param max_count [Integer, Symbol, nil] the number of commits to return, or
47
48
  # `:all` or `nil` to return all
48
49
  #
@@ -157,10 +158,16 @@ module Git
157
158
  self
158
159
  end
159
160
 
161
+ # @return [Git::Repository]
162
+ #
163
+ def log_repository
164
+ @base
165
+ end
166
+
160
167
  def run_log_if_dirty
161
168
  return unless @dirty
162
169
 
163
- log_data = @base.lib.full_log_commits(@options)
170
+ log_data = log_repository.full_log_commits(@options)
164
171
  @commits = log_data.map { |c| Git::Object::Commit.new(@base, c['sha'], c) }
165
172
  @dirty = false
166
173
  end