git 4.3.2 → 5.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +67 -2705
  3. data/.github/pull_request_template.md +3 -1
  4. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  5. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  6. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  7. data/.github/skills/command-implementation/SKILL.md +229 -0
  8. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  9. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  10. data/.github/skills/dependency-management/SKILL.md +72 -0
  11. data/.github/skills/development-workflow/SKILL.md +506 -0
  12. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  13. data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
  14. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  15. data/.github/skills/facade-implementation/SKILL.md +260 -0
  16. data/.github/skills/facade-test-conventions/SKILL.md +380 -0
  17. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  18. data/.github/skills/make-skill-template/SKILL.md +176 -0
  19. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  20. data/.github/skills/project-context/SKILL.md +313 -0
  21. data/.github/skills/pull-request-review/SKILL.md +168 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  23. data/.github/skills/release-management/SKILL.md +125 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  25. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  26. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  27. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  28. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  29. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  30. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  31. data/.github/skills/test-debugging/SKILL.md +160 -0
  32. data/.github/skills/yard-documentation/SKILL.md +793 -0
  33. data/.github/workflows/continuous_integration.yml +3 -2
  34. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  35. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  36. data/.github/workflows/release.yml +3 -4
  37. data/.gitignore +8 -0
  38. data/.husky/pre-commit +13 -0
  39. data/.release-please-manifest.json +1 -1
  40. data/.rspec +3 -0
  41. data/.rubocop.yml +7 -3
  42. data/.rubocop_todo.yml +23 -5
  43. data/.yardopts +1 -0
  44. data/CHANGELOG.md +0 -40
  45. data/CONTRIBUTING.md +694 -53
  46. data/README.md +17 -5
  47. data/Rakefile +61 -9
  48. data/commitlint.test +4 -0
  49. data/git.gemspec +14 -8
  50. data/lib/git/args_builder.rb +0 -8
  51. data/lib/git/base.rb +486 -410
  52. data/lib/git/branch.rb +380 -43
  53. data/lib/git/branch_delete_failure.rb +31 -0
  54. data/lib/git/branch_delete_result.rb +63 -0
  55. data/lib/git/branch_info.rb +178 -0
  56. data/lib/git/branches.rb +130 -24
  57. data/lib/git/command_line/base.rb +245 -0
  58. data/lib/git/command_line/capturing.rb +249 -0
  59. data/lib/git/command_line/result.rb +96 -0
  60. data/lib/git/command_line/streaming.rb +194 -0
  61. data/lib/git/command_line.rb +43 -322
  62. data/lib/git/command_line_result.rb +4 -88
  63. data/lib/git/commands/add.rb +131 -0
  64. data/lib/git/commands/am/abort.rb +43 -0
  65. data/lib/git/commands/am/apply.rb +252 -0
  66. data/lib/git/commands/am/continue.rb +43 -0
  67. data/lib/git/commands/am/quit.rb +43 -0
  68. data/lib/git/commands/am/retry.rb +47 -0
  69. data/lib/git/commands/am/show_current_patch.rb +64 -0
  70. data/lib/git/commands/am/skip.rb +42 -0
  71. data/lib/git/commands/am.rb +33 -0
  72. data/lib/git/commands/apply.rb +237 -0
  73. data/lib/git/commands/archive/list_formats.rb +46 -0
  74. data/lib/git/commands/archive.rb +140 -0
  75. data/lib/git/commands/arguments.rb +3510 -0
  76. data/lib/git/commands/base.rb +403 -0
  77. data/lib/git/commands/branch/copy.rb +94 -0
  78. data/lib/git/commands/branch/create.rb +173 -0
  79. data/lib/git/commands/branch/delete.rb +80 -0
  80. data/lib/git/commands/branch/list.rb +162 -0
  81. data/lib/git/commands/branch/move.rb +94 -0
  82. data/lib/git/commands/branch/set_upstream.rb +86 -0
  83. data/lib/git/commands/branch/show_current.rb +49 -0
  84. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  85. data/lib/git/commands/branch.rb +34 -0
  86. data/lib/git/commands/cat_file/batch.rb +364 -0
  87. data/lib/git/commands/cat_file/filtered.rb +105 -0
  88. data/lib/git/commands/cat_file/raw.rb +210 -0
  89. data/lib/git/commands/cat_file.rb +49 -0
  90. data/lib/git/commands/checkout/branch.rb +151 -0
  91. data/lib/git/commands/checkout/files.rb +115 -0
  92. data/lib/git/commands/checkout.rb +38 -0
  93. data/lib/git/commands/checkout_index.rb +105 -0
  94. data/lib/git/commands/clean.rb +100 -0
  95. data/lib/git/commands/clone.rb +240 -0
  96. data/lib/git/commands/commit.rb +272 -0
  97. data/lib/git/commands/commit_tree.rb +100 -0
  98. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  99. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  100. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  101. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  102. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  103. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  104. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
  105. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  106. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  107. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  108. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  109. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  110. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  111. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  112. data/lib/git/commands/config_option_syntax.rb +56 -0
  113. data/lib/git/commands/describe.rb +155 -0
  114. data/lib/git/commands/diff.rb +656 -0
  115. data/lib/git/commands/diff_files.rb +518 -0
  116. data/lib/git/commands/diff_index.rb +496 -0
  117. data/lib/git/commands/fetch.rb +352 -0
  118. data/lib/git/commands/fsck.rb +136 -0
  119. data/lib/git/commands/gc.rb +132 -0
  120. data/lib/git/commands/grep.rb +338 -0
  121. data/lib/git/commands/init.rb +99 -0
  122. data/lib/git/commands/log.rb +632 -0
  123. data/lib/git/commands/ls_files.rb +191 -0
  124. data/lib/git/commands/ls_remote.rb +155 -0
  125. data/lib/git/commands/ls_tree.rb +131 -0
  126. data/lib/git/commands/maintenance/register.rb +75 -0
  127. data/lib/git/commands/maintenance/run.rb +104 -0
  128. data/lib/git/commands/maintenance/start.rb +66 -0
  129. data/lib/git/commands/maintenance/stop.rb +55 -0
  130. data/lib/git/commands/maintenance/unregister.rb +79 -0
  131. data/lib/git/commands/maintenance.rb +31 -0
  132. data/lib/git/commands/merge/abort.rb +44 -0
  133. data/lib/git/commands/merge/continue.rb +44 -0
  134. data/lib/git/commands/merge/quit.rb +46 -0
  135. data/lib/git/commands/merge/start.rb +245 -0
  136. data/lib/git/commands/merge.rb +28 -0
  137. data/lib/git/commands/merge_base.rb +86 -0
  138. data/lib/git/commands/mv.rb +77 -0
  139. data/lib/git/commands/name_rev.rb +114 -0
  140. data/lib/git/commands/pull.rb +377 -0
  141. data/lib/git/commands/push.rb +246 -0
  142. data/lib/git/commands/read_tree.rb +149 -0
  143. data/lib/git/commands/remote/add.rb +91 -0
  144. data/lib/git/commands/remote/get_url.rb +66 -0
  145. data/lib/git/commands/remote/list.rb +54 -0
  146. data/lib/git/commands/remote/prune.rb +61 -0
  147. data/lib/git/commands/remote/remove.rb +52 -0
  148. data/lib/git/commands/remote/rename.rb +69 -0
  149. data/lib/git/commands/remote/set_branches.rb +63 -0
  150. data/lib/git/commands/remote/set_head.rb +82 -0
  151. data/lib/git/commands/remote/set_url.rb +71 -0
  152. data/lib/git/commands/remote/set_url_add.rb +61 -0
  153. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  154. data/lib/git/commands/remote/show.rb +71 -0
  155. data/lib/git/commands/remote/update.rb +72 -0
  156. data/lib/git/commands/remote.rb +42 -0
  157. data/lib/git/commands/repack.rb +277 -0
  158. data/lib/git/commands/reset.rb +147 -0
  159. data/lib/git/commands/rev_parse.rb +297 -0
  160. data/lib/git/commands/revert/abort.rb +45 -0
  161. data/lib/git/commands/revert/continue.rb +57 -0
  162. data/lib/git/commands/revert/quit.rb +47 -0
  163. data/lib/git/commands/revert/skip.rb +44 -0
  164. data/lib/git/commands/revert/start.rb +153 -0
  165. data/lib/git/commands/revert.rb +29 -0
  166. data/lib/git/commands/rm.rb +114 -0
  167. data/lib/git/commands/show.rb +632 -0
  168. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  169. data/lib/git/commands/show_ref/exists.rb +78 -0
  170. data/lib/git/commands/show_ref/list.rb +145 -0
  171. data/lib/git/commands/show_ref/verify.rb +120 -0
  172. data/lib/git/commands/show_ref.rb +42 -0
  173. data/lib/git/commands/stash/apply.rb +75 -0
  174. data/lib/git/commands/stash/branch.rb +65 -0
  175. data/lib/git/commands/stash/clear.rb +41 -0
  176. data/lib/git/commands/stash/create.rb +58 -0
  177. data/lib/git/commands/stash/drop.rb +67 -0
  178. data/lib/git/commands/stash/list.rb +39 -0
  179. data/lib/git/commands/stash/pop.rb +78 -0
  180. data/lib/git/commands/stash/push.rb +103 -0
  181. data/lib/git/commands/stash/show.rb +149 -0
  182. data/lib/git/commands/stash/store.rb +63 -0
  183. data/lib/git/commands/stash.rb +38 -0
  184. data/lib/git/commands/status.rb +169 -0
  185. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  186. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  187. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  188. data/lib/git/commands/symbolic_ref.rb +38 -0
  189. data/lib/git/commands/tag/create.rb +139 -0
  190. data/lib/git/commands/tag/delete.rb +55 -0
  191. data/lib/git/commands/tag/list.rb +143 -0
  192. data/lib/git/commands/tag/verify.rb +71 -0
  193. data/lib/git/commands/tag.rb +26 -0
  194. data/lib/git/commands/update_ref/batch.rb +140 -0
  195. data/lib/git/commands/update_ref/delete.rb +92 -0
  196. data/lib/git/commands/update_ref/update.rb +106 -0
  197. data/lib/git/commands/update_ref.rb +42 -0
  198. data/lib/git/commands/version.rb +52 -0
  199. data/lib/git/commands/worktree/add.rb +140 -0
  200. data/lib/git/commands/worktree/list.rb +64 -0
  201. data/lib/git/commands/worktree/lock.rb +58 -0
  202. data/lib/git/commands/worktree/management_base.rb +51 -0
  203. data/lib/git/commands/worktree/move.rb +66 -0
  204. data/lib/git/commands/worktree/prune.rb +67 -0
  205. data/lib/git/commands/worktree/remove.rb +63 -0
  206. data/lib/git/commands/worktree/repair.rb +76 -0
  207. data/lib/git/commands/worktree/unlock.rb +47 -0
  208. data/lib/git/commands/worktree.rb +43 -0
  209. data/lib/git/commands/write_tree.rb +68 -0
  210. data/lib/git/commands.rb +89 -0
  211. data/lib/git/detached_head_info.rb +54 -0
  212. data/lib/git/diff.rb +297 -7
  213. data/lib/git/diff_file_numstat_info.rb +29 -0
  214. data/lib/git/diff_file_patch_info.rb +134 -0
  215. data/lib/git/diff_file_raw_info.rb +127 -0
  216. data/lib/git/diff_info.rb +169 -0
  217. data/lib/git/diff_path_status.rb +78 -19
  218. data/lib/git/diff_result.rb +32 -0
  219. data/lib/git/diff_stats.rb +59 -14
  220. data/lib/git/dirstat_info.rb +86 -0
  221. data/lib/git/errors.rb +65 -2
  222. data/lib/git/execution_context/global.rb +56 -0
  223. data/lib/git/execution_context/repository.rb +147 -0
  224. data/lib/git/execution_context.rb +482 -0
  225. data/lib/git/file_ref.rb +74 -0
  226. data/lib/git/fsck_object.rb +9 -9
  227. data/lib/git/fsck_result.rb +1 -1
  228. data/lib/git/lib.rb +1606 -1028
  229. data/lib/git/log.rb +15 -2
  230. data/lib/git/object.rb +92 -22
  231. data/lib/git/parsers/branch.rb +224 -0
  232. data/lib/git/parsers/cat_file.rb +111 -0
  233. data/lib/git/parsers/diff.rb +585 -0
  234. data/lib/git/parsers/fsck.rb +133 -0
  235. data/lib/git/parsers/grep.rb +42 -0
  236. data/lib/git/parsers/ls_tree.rb +58 -0
  237. data/lib/git/parsers/stash.rb +208 -0
  238. data/lib/git/parsers/tag.rb +257 -0
  239. data/lib/git/remote.rb +133 -9
  240. data/lib/git/repository/branching.rb +572 -0
  241. data/lib/git/repository/committing.rb +191 -0
  242. data/lib/git/repository/configuring.rb +156 -0
  243. data/lib/git/repository/diffing.rb +775 -0
  244. data/lib/git/repository/inspecting.rb +153 -0
  245. data/lib/git/repository/logging.rb +247 -0
  246. data/lib/git/repository/merging.rb +295 -0
  247. data/lib/git/repository/object_operations.rb +1101 -0
  248. data/lib/git/repository/path_resolver.rb +207 -0
  249. data/lib/git/repository/remote_operations.rb +753 -0
  250. data/lib/git/repository/shared_private.rb +51 -0
  251. data/lib/git/repository/staging.rb +390 -0
  252. data/lib/git/repository/stashing.rb +107 -0
  253. data/lib/git/repository/status_operations.rb +180 -0
  254. data/lib/git/repository/worktree_operations.rb +159 -0
  255. data/lib/git/repository.rb +264 -1
  256. data/lib/git/stash.rb +85 -4
  257. data/lib/git/stash_info.rb +104 -0
  258. data/lib/git/stashes.rb +130 -13
  259. data/lib/git/status.rb +224 -18
  260. data/lib/git/tag_delete_failure.rb +31 -0
  261. data/lib/git/tag_delete_result.rb +63 -0
  262. data/lib/git/tag_info.rb +105 -0
  263. data/lib/git/version.rb +109 -2
  264. data/lib/git/version_constraint.rb +81 -0
  265. data/lib/git/worktree.rb +120 -5
  266. data/lib/git/worktrees.rb +107 -7
  267. data/lib/git.rb +114 -18
  268. data/redesign/1_architecture_existing.md +54 -18
  269. data/redesign/2_architecture_redesign.md +365 -46
  270. data/redesign/3_architecture_implementation.md +1451 -54
  271. data/tasks/gem_tasks.rake +4 -0
  272. data/tasks/npm_tasks.rake +7 -0
  273. data/tasks/rspec.rake +48 -0
  274. data/tasks/test.rake +13 -1
  275. data/tasks/yard.rake +34 -7
  276. metadata +349 -20
  277. data/lib/git/index.rb +0 -6
  278. data/lib/git/path.rb +0 -38
  279. data/lib/git/working_directory.rb +0 -6
  280. /data/{release-please-config.json → .release-please-config.json} +0 -0
@@ -0,0 +1,482 @@
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::Base.config.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) to use `Git::Base.config.git_ssh`.
99
+ #
100
+ # @param logger [Logger, nil] the logger to use in the CommandLine layer
101
+ #
102
+ # Give `nil` to use a null logger (`Logger.new(nil)`).
103
+ #
104
+ # @raise [NotImplementedError] if called directly on {Git::ExecutionContext} rather than a subclass
105
+ #
106
+ # @raise [ArgumentError] if `binary_path` is `nil`
107
+ #
108
+ def initialize(binary_path: :use_global_config, git_ssh: :use_global_config, logger: nil)
109
+ if instance_of?(Git::ExecutionContext)
110
+ raise NotImplementedError, 'Git::ExecutionContext is an abstract base class'
111
+ end
112
+ raise ArgumentError, 'binary_path must not be nil' if binary_path.nil?
113
+
114
+ @binary_path = binary_path
115
+ @git_ssh = git_ssh
116
+ @logger = logger || Logger.new(nil)
117
+ end
118
+
119
+ # Returns the `GIT_DIR` path for this context
120
+ #
121
+ # `nil` means `GIT_DIR` will be explicitly **unset** in the child process
122
+ # (per `Process.spawn` semantics — unset is not the same as inherited).
123
+ # Subclasses override this to supply a repository-specific path.
124
+ #
125
+ # @example Base class returns nil; subclasses return the actual path
126
+ # context = Git::ExecutionContext::Global.new
127
+ # context.git_dir #=> nil
128
+ #
129
+ # @return [String, nil] the `GIT_DIR` path, or `nil` to unset the variable
130
+ #
131
+ def git_dir = nil
132
+
133
+ # Returns the `GIT_WORK_TREE` path for this context
134
+ #
135
+ # `nil` means `GIT_WORK_TREE` will be explicitly **unset** in the child process.
136
+ #
137
+ # @example Base class returns nil; subclasses return the actual path
138
+ # context = Git::ExecutionContext::Global.new
139
+ # context.git_work_dir #=> nil
140
+ #
141
+ # @return [String, nil] the `GIT_WORK_TREE` path, or `nil` to unset the variable
142
+ #
143
+ def git_work_dir = nil
144
+
145
+ # Returns the `GIT_INDEX_FILE` path for this context
146
+ #
147
+ # `nil` means `GIT_INDEX_FILE` will be explicitly **unset** in the child process.
148
+ #
149
+ # @example Base class returns nil; subclasses return the actual path
150
+ # context = Git::ExecutionContext::Global.new
151
+ # context.git_index_file #=> nil
152
+ #
153
+ # @return [String, nil] the `GIT_INDEX_FILE` path, or `nil` to unset the variable
154
+ #
155
+ def git_index_file = nil
156
+
157
+ # Returns the resolved git binary path for this context
158
+ #
159
+ # `:use_global_config` is resolved to `Git::Base.config.binary_path` each time a
160
+ # command method is called, so runtime changes to `Git::Base.config.binary_path`
161
+ # are reflected per command invocation.
162
+ #
163
+ # @example With the default sentinel (resolves from Git::Base.config at call-time)
164
+ # context = Git::ExecutionContext::Global.new
165
+ # context.binary_path #=> "git"
166
+ #
167
+ # @example With an explicit path
168
+ # context = Git::ExecutionContext::Global.new(binary_path: '/usr/local/bin/git2')
169
+ # context.binary_path #=> "/usr/local/bin/git2"
170
+ #
171
+ # @return [String] the resolved git binary path
172
+ #
173
+ def binary_path
174
+ return Git::Base.config.binary_path if @binary_path == :use_global_config
175
+
176
+ @binary_path
177
+ end
178
+
179
+ # Returns the resolved `GIT_SSH` wrapper path for this context
180
+ #
181
+ # `:use_global_config` is resolved to `Git::Base.config.git_ssh` each time a
182
+ # command method is called, so runtime changes to `Git::Base.config.git_ssh`
183
+ # are reflected per command invocation. `nil` means the variable will be
184
+ # explicitly unset.
185
+ #
186
+ # @example With the default sentinel (resolves from Git::Base.config at call-time)
187
+ # context = Git::ExecutionContext::Global.new
188
+ # context.git_ssh #=> nil
189
+ #
190
+ # @example With an explicit path
191
+ # context = Git::ExecutionContext::Global.new(git_ssh: '/usr/bin/ssh-wrapper')
192
+ # context.git_ssh #=> "/usr/bin/ssh-wrapper"
193
+ #
194
+ # @return [String, nil] the resolved `GIT_SSH` wrapper path, or `nil` to unset
195
+ #
196
+ def git_ssh
197
+ return Git::Base.config.git_ssh if @git_ssh == :use_global_config
198
+
199
+ @git_ssh
200
+ end
201
+
202
+ # Runs a git command and returns the result
203
+ #
204
+ # By default, raises {Git::FailedError} if the command exits with a non-zero
205
+ # status. Pass `raise_on_failure: false` to suppress this behavior.
206
+ #
207
+ # @overload command_capturing(*args, **options_hash)
208
+ #
209
+ # Runs a git command and returns the result
210
+ #
211
+ # Args should exclude the 'git' command itself and global options. Remember to
212
+ # splat the arguments if given as an array.
213
+ #
214
+ # @example Run git log
215
+ # result = command_capturing('log', '--pretty=oneline')
216
+ # result.stdout #=> "abc123 First commit\ndef456 Second commit\n"
217
+ #
218
+ # @example Using an array of arguments
219
+ # args = ['log', '--pretty=oneline']
220
+ # result = command_capturing(*args)
221
+ #
222
+ # @example Suppress raising on failure
223
+ # result = command_capturing('show', 'nonexistent', raise_on_failure: false)
224
+ # result.status.success? #=> false
225
+ #
226
+ # @param args [Array<String>] the command and its arguments
227
+ #
228
+ # @param options_hash [Hash] the options to pass to the command
229
+ #
230
+ # @option options_hash [IO, nil] :in the IO object to use as stdin, or nil to
231
+ # inherit the parent process stdin
232
+ #
233
+ # Must be a real IO object with a file descriptor.
234
+ #
235
+ # @option options_hash [IO, String, #write, nil] :out the destination for
236
+ # captured stdout
237
+ #
238
+ # @option options_hash [IO, String, #write, nil] :err the destination for
239
+ # captured stderr
240
+ #
241
+ # @option options_hash [Boolean, nil] :normalize (true) normalize the output
242
+ # encoding to UTF-8
243
+ #
244
+ # @option options_hash [Boolean, nil] :chomp (true) remove trailing newlines
245
+ # from the output
246
+ #
247
+ # @option options_hash [Boolean, nil] :merge (false) merge stdout and stderr
248
+ # into a single output
249
+ #
250
+ # @option options_hash [String, nil] :chdir the directory to run the command in
251
+ #
252
+ # @option options_hash [Hash] :env additional environment variable overrides
253
+ # for this command
254
+ #
255
+ # @option options_hash [Boolean, nil] :raise_on_failure (true) whether to raise on
256
+ # non-zero exit
257
+ #
258
+ # @option options_hash [Numeric, nil] :timeout the maximum seconds to wait for
259
+ # the command to complete
260
+ #
261
+ # If timeout is nil, the global timeout from {Git::Config} is used.
262
+ #
263
+ # If timeout is zero, the timeout will not be enforced.
264
+ #
265
+ # If the command times out, it is killed via a `SIGKILL` signal and
266
+ # `Git::TimeoutError` is raised.
267
+ #
268
+ # If the command does not respond to SIGKILL, it will hang this method.
269
+ #
270
+ # @return [Git::CommandLineResult] the result of the command
271
+ #
272
+ # @raise [ArgumentError] if an unknown option is passed
273
+ #
274
+ # @raise [Git::FailedError] if the command failed (when raise_on_failure is
275
+ # true)
276
+ #
277
+ # @raise [Git::SignaledError] if the command was signaled
278
+ #
279
+ # @raise [Git::TimeoutError] if the command times out
280
+ #
281
+ # @raise [Git::ProcessIOError] if an exception was raised while collecting
282
+ # subprocess output
283
+ #
284
+ # The exception's `result` attribute is a {Git::CommandLineResult} which will
285
+ # contain the result of the command including the exit status, stdout, and stderr.
286
+ #
287
+ # @note Individual command classes (under {Git::Commands}) can selectively expose
288
+ # `:timeout` and `:env` and other options to their callers by declaring them as
289
+ # execution options in their Arguments DSL definition and forwarding them to
290
+ # this method. See {Git::Commands::Clone#call} for an example of a command that
291
+ # exposes `:timeout`.
292
+ #
293
+ # @see Git::CommandLine::Capturing#run
294
+ #
295
+ def command_capturing(*, **options_hash)
296
+ options_hash = COMMAND_CAPTURING_ARG_DEFAULTS.merge(options_hash)
297
+ options_hash[:timeout] ||= Git.config.timeout
298
+
299
+ extra_options = options_hash.keys - COMMAND_CAPTURING_ARG_DEFAULTS.keys
300
+ raise ArgumentError, "Unknown options: #{extra_options.join(', ')}" if extra_options.any?
301
+
302
+ env = options_hash.delete(:env)
303
+ raise_on_failure = options_hash.delete(:raise_on_failure)
304
+ command_line_capturing.run(*, raise_on_failure: raise_on_failure, env: env, **options_hash)
305
+ end
306
+
307
+ # Runs a git command using the streaming (non-capturing) execution path
308
+ #
309
+ # Unlike {#command_capturing}, stdout is NOT buffered in memory. It is
310
+ # written only to the IO object provided via the `out:` option. Stderr is
311
+ # captured internally via a StringIO for error diagnostics.
312
+ #
313
+ # Use this entry point when you want to stream large output (e.g. blob
314
+ # content from cat-file) without creating memory pressure.
315
+ #
316
+ # @overload command_streaming(*args, **options_hash)
317
+ #
318
+ # Streams a git command's output to the provided IO object
319
+ #
320
+ # @example Stream blob content to a file
321
+ # File.open('blob.bin', 'wb') do |f|
322
+ # command_streaming('cat-file', 'blob', 'HEAD:large_file.bin', out: f)
323
+ # end
324
+ #
325
+ # @param args [Array<String>] the git command and its arguments
326
+ #
327
+ # @param options_hash [Hash] the options to pass to the command
328
+ #
329
+ # @option options_hash [IO, nil] :in the IO object to use as stdin, or nil to
330
+ # inherit the parent process stdin
331
+ #
332
+ # Must be a real IO object with a file descriptor.
333
+ #
334
+ # @option options_hash [#write, nil] :out destination for streamed stdout
335
+ #
336
+ # @option options_hash [#write, nil] :err an optional additional destination
337
+ # to receive stderr output in real time
338
+ #
339
+ # Stderr is always captured internally; when `err:` is supplied, writes are
340
+ # teed to both the internal buffer and this destination. `result.stderr`
341
+ # always reflects the internal capture.
342
+ #
343
+ # @option options_hash [String, nil] :chdir the directory to run the command in
344
+ #
345
+ # @option options_hash [Hash] :env additional environment variable overrides
346
+ # for this command
347
+ #
348
+ # @option options_hash [Boolean, nil] :raise_on_failure (true) whether to raise on
349
+ # non-zero exit
350
+ #
351
+ # @option options_hash [Numeric, nil] :timeout
352
+ # the maximum seconds to wait for the command to complete
353
+ #
354
+ # If timeout is nil, the global timeout from {Git::Config} is used.
355
+ #
356
+ # If timeout is zero, the timeout will not be enforced.
357
+ #
358
+ # If the command times out, it is killed via a `SIGKILL` signal and
359
+ # `Git::TimeoutError` is raised.
360
+ #
361
+ # If the command does not respond to SIGKILL, it will hang this method.
362
+ #
363
+ # @return [Git::CommandLineResult] the result of the command
364
+ #
365
+ # `result.stdout` will always be `''` — stdout was streamed to `out:`.
366
+ #
367
+ # `result.stderr` contains any stderr output captured for diagnostics.
368
+ #
369
+ # @raise [ArgumentError] if an unknown option is passed
370
+ #
371
+ # @raise [Git::FailedError] if the command failed (when raise_on_failure is true)
372
+ #
373
+ # @raise [Git::SignaledError] if the command was signaled
374
+ #
375
+ # @raise [Git::TimeoutError] if the command times out
376
+ #
377
+ # @raise [Git::ProcessIOError] if an exception was raised while collecting
378
+ # subprocess output
379
+ #
380
+ # @see Git::CommandLine::Streaming#run
381
+ #
382
+ def command_streaming(*, **options_hash)
383
+ options_hash = COMMAND_STREAMING_ARG_DEFAULTS.merge(options_hash)
384
+ options_hash[:timeout] ||= Git.config.timeout
385
+
386
+ extra_options = options_hash.keys - COMMAND_STREAMING_ARG_DEFAULTS.keys
387
+ raise ArgumentError, "Unknown options: #{extra_options.join(', ')}" if extra_options.any?
388
+
389
+ env = options_hash.delete(:env)
390
+ raise_on_failure = options_hash.delete(:raise_on_failure)
391
+ command_line_streaming.run(*, raise_on_failure: raise_on_failure, env: env, **options_hash)
392
+ end
393
+
394
+ # Returns the installed git version
395
+ #
396
+ # The result is memoized per instance.
397
+ #
398
+ # @example Get the installed git version
399
+ # context = Git::ExecutionContext::Global.new
400
+ # context.git_version #=> #<Git::Version 2.42.0>
401
+ #
402
+ # @return [Git::Version] the installed git version
403
+ #
404
+ # @raise [Git::UnexpectedResultError] if the version string cannot be parsed
405
+ #
406
+ def git_version
407
+ @git_version ||= begin
408
+ output = Git::Commands::Version.new(self).call.stdout
409
+ Git::Version.parse(output)
410
+ end
411
+ end
412
+
413
+ private
414
+
415
+ # Returns a Hash of environment variable overrides for this context
416
+ #
417
+ # Builds the standard git environment from the public accessor methods
418
+ # ({#git_dir}, {#git_work_dir}, {#git_index_file}, {#git_ssh}), then
419
+ # merges any per-call `additional_overrides` on top.
420
+ #
421
+ # Per `Process.spawn` semantics, a value of `nil` unsets the variable.
422
+ #
423
+ # @param additional_overrides [Hash<String, String|nil>] per-call overrides
424
+ #
425
+ # @return [Hash<String, String|nil>] the merged environment variable overrides
426
+ #
427
+ def env_overrides(**additional_overrides)
428
+ {
429
+ 'GIT_DIR' => git_dir,
430
+ 'GIT_WORK_TREE' => git_work_dir,
431
+ 'GIT_INDEX_FILE' => git_index_file,
432
+ 'GIT_SSH' => git_ssh,
433
+ 'GIT_EDITOR' => 'true',
434
+ 'LC_ALL' => 'en_US.UTF-8'
435
+ }.merge(additional_overrides)
436
+ end
437
+
438
+ # Returns the Array of git global option strings for this context
439
+ #
440
+ # Prepends `--git-dir` and `--work-tree` when the corresponding attributes
441
+ # are set, then appends {STATIC_GLOBAL_OPTS}.
442
+ #
443
+ # @return [Array<String>] the global options to prepend to every git invocation
444
+ #
445
+ def global_opts
446
+ [].tap do |opts|
447
+ opts << "--git-dir=#{git_dir}" unless git_dir.nil?
448
+ opts << "--work-tree=#{git_work_dir}" unless git_work_dir.nil?
449
+ opts.concat(STATIC_GLOBAL_OPTS)
450
+ end
451
+ end
452
+
453
+ # Creates a {Git::CommandLine::Capturing} instance for the current invocation.
454
+ #
455
+ # A new instance is created per call so that {#binary_path} — resolved from
456
+ # `Git::Base.config` when set to `:use_global_config` — and {#env_overrides}
457
+ # — including {#git_ssh} resolution for `:use_global_config` — reflect the
458
+ # state of `Git::Base.config` at the time of each command invocation.
459
+ #
460
+ # @return [Git::CommandLine::Capturing] the capturing command line instance
461
+ #
462
+ def command_line_capturing
463
+ Git::CommandLine::Capturing.new(env_overrides, binary_path, global_opts, @logger)
464
+ end
465
+
466
+ # Creates a {Git::CommandLine::Streaming} instance for the current invocation.
467
+ #
468
+ # A new instance is created per call so that {#binary_path} — resolved from
469
+ # `Git::Base.config` when set to `:use_global_config` — and {#env_overrides}
470
+ # — including {#git_ssh} resolution for `:use_global_config` — reflect the
471
+ # state of `Git::Base.config` at the time of each command invocation.
472
+ #
473
+ # @return [Git::CommandLine::Streaming] the streaming command line instance
474
+ #
475
+ def command_line_streaming
476
+ Git::CommandLine::Streaming.new(env_overrides, binary_path, global_opts, @logger)
477
+ end
478
+ end
479
+ end
480
+
481
+ require 'git/execution_context/global'
482
+ 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