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.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +67 -2705
- data/.github/pull_request_template.md +3 -1
- data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
- data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
- data/.github/skills/command-implementation/REFERENCE.md +993 -0
- data/.github/skills/command-implementation/SKILL.md +229 -0
- data/.github/skills/command-test-conventions/SKILL.md +660 -0
- data/.github/skills/command-yard-documentation/SKILL.md +426 -0
- data/.github/skills/dependency-management/SKILL.md +72 -0
- data/.github/skills/development-workflow/SKILL.md +506 -0
- data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
- data/.github/skills/facade-implementation/REFERENCE.md +840 -0
- data/.github/skills/facade-implementation/SKILL.md +260 -0
- data/.github/skills/facade-test-conventions/SKILL.md +380 -0
- data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
- data/.github/skills/make-skill-template/SKILL.md +176 -0
- data/.github/skills/pr-readiness-review/SKILL.md +185 -0
- data/.github/skills/project-context/SKILL.md +313 -0
- data/.github/skills/pull-request-review/SKILL.md +168 -0
- data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
- data/.github/skills/release-management/SKILL.md +125 -0
- data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
- data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
- data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
- data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
- data/.github/skills/reviewing-skills/SKILL.md +189 -0
- data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
- data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
- data/.github/skills/test-debugging/SKILL.md +160 -0
- data/.github/skills/yard-documentation/SKILL.md +793 -0
- data/.github/workflows/continuous_integration.yml +3 -2
- data/.github/workflows/enforce_conventional_commits.yml +1 -1
- data/.github/workflows/experimental_continuous_integration.yml +2 -2
- data/.github/workflows/release.yml +3 -4
- data/.gitignore +8 -0
- data/.husky/pre-commit +13 -0
- data/.release-please-manifest.json +1 -1
- data/.rspec +3 -0
- data/.rubocop.yml +7 -3
- data/.rubocop_todo.yml +23 -5
- data/.yardopts +1 -0
- data/CHANGELOG.md +0 -40
- data/CONTRIBUTING.md +694 -53
- data/README.md +17 -5
- data/Rakefile +61 -9
- data/commitlint.test +4 -0
- data/git.gemspec +14 -8
- data/lib/git/args_builder.rb +0 -8
- data/lib/git/base.rb +486 -410
- data/lib/git/branch.rb +380 -43
- data/lib/git/branch_delete_failure.rb +31 -0
- data/lib/git/branch_delete_result.rb +63 -0
- data/lib/git/branch_info.rb +178 -0
- data/lib/git/branches.rb +130 -24
- data/lib/git/command_line/base.rb +245 -0
- data/lib/git/command_line/capturing.rb +249 -0
- data/lib/git/command_line/result.rb +96 -0
- data/lib/git/command_line/streaming.rb +194 -0
- data/lib/git/command_line.rb +43 -322
- data/lib/git/command_line_result.rb +4 -88
- data/lib/git/commands/add.rb +131 -0
- data/lib/git/commands/am/abort.rb +43 -0
- data/lib/git/commands/am/apply.rb +252 -0
- data/lib/git/commands/am/continue.rb +43 -0
- data/lib/git/commands/am/quit.rb +43 -0
- data/lib/git/commands/am/retry.rb +47 -0
- data/lib/git/commands/am/show_current_patch.rb +64 -0
- data/lib/git/commands/am/skip.rb +42 -0
- data/lib/git/commands/am.rb +33 -0
- data/lib/git/commands/apply.rb +237 -0
- data/lib/git/commands/archive/list_formats.rb +46 -0
- data/lib/git/commands/archive.rb +140 -0
- data/lib/git/commands/arguments.rb +3510 -0
- data/lib/git/commands/base.rb +403 -0
- data/lib/git/commands/branch/copy.rb +94 -0
- data/lib/git/commands/branch/create.rb +173 -0
- data/lib/git/commands/branch/delete.rb +80 -0
- data/lib/git/commands/branch/list.rb +162 -0
- data/lib/git/commands/branch/move.rb +94 -0
- data/lib/git/commands/branch/set_upstream.rb +86 -0
- data/lib/git/commands/branch/show_current.rb +49 -0
- data/lib/git/commands/branch/unset_upstream.rb +57 -0
- data/lib/git/commands/branch.rb +34 -0
- data/lib/git/commands/cat_file/batch.rb +364 -0
- data/lib/git/commands/cat_file/filtered.rb +105 -0
- data/lib/git/commands/cat_file/raw.rb +210 -0
- data/lib/git/commands/cat_file.rb +49 -0
- data/lib/git/commands/checkout/branch.rb +151 -0
- data/lib/git/commands/checkout/files.rb +115 -0
- data/lib/git/commands/checkout.rb +38 -0
- data/lib/git/commands/checkout_index.rb +105 -0
- data/lib/git/commands/clean.rb +100 -0
- data/lib/git/commands/clone.rb +240 -0
- data/lib/git/commands/commit.rb +272 -0
- data/lib/git/commands/commit_tree.rb +100 -0
- data/lib/git/commands/config_option_syntax/add.rb +83 -0
- data/lib/git/commands/config_option_syntax/get.rb +117 -0
- data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
- data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
- data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
- data/lib/git/commands/config_option_syntax/list.rb +107 -0
- data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
- data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
- data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
- data/lib/git/commands/config_option_syntax/set.rb +114 -0
- data/lib/git/commands/config_option_syntax/unset.rb +89 -0
- data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
- data/lib/git/commands/config_option_syntax.rb +56 -0
- data/lib/git/commands/describe.rb +155 -0
- data/lib/git/commands/diff.rb +656 -0
- data/lib/git/commands/diff_files.rb +518 -0
- data/lib/git/commands/diff_index.rb +496 -0
- data/lib/git/commands/fetch.rb +352 -0
- data/lib/git/commands/fsck.rb +136 -0
- data/lib/git/commands/gc.rb +132 -0
- data/lib/git/commands/grep.rb +338 -0
- data/lib/git/commands/init.rb +99 -0
- data/lib/git/commands/log.rb +632 -0
- data/lib/git/commands/ls_files.rb +191 -0
- data/lib/git/commands/ls_remote.rb +155 -0
- data/lib/git/commands/ls_tree.rb +131 -0
- data/lib/git/commands/maintenance/register.rb +75 -0
- data/lib/git/commands/maintenance/run.rb +104 -0
- data/lib/git/commands/maintenance/start.rb +66 -0
- data/lib/git/commands/maintenance/stop.rb +55 -0
- data/lib/git/commands/maintenance/unregister.rb +79 -0
- data/lib/git/commands/maintenance.rb +31 -0
- data/lib/git/commands/merge/abort.rb +44 -0
- data/lib/git/commands/merge/continue.rb +44 -0
- data/lib/git/commands/merge/quit.rb +46 -0
- data/lib/git/commands/merge/start.rb +245 -0
- data/lib/git/commands/merge.rb +28 -0
- data/lib/git/commands/merge_base.rb +86 -0
- data/lib/git/commands/mv.rb +77 -0
- data/lib/git/commands/name_rev.rb +114 -0
- data/lib/git/commands/pull.rb +377 -0
- data/lib/git/commands/push.rb +246 -0
- data/lib/git/commands/read_tree.rb +149 -0
- data/lib/git/commands/remote/add.rb +91 -0
- data/lib/git/commands/remote/get_url.rb +66 -0
- data/lib/git/commands/remote/list.rb +54 -0
- data/lib/git/commands/remote/prune.rb +61 -0
- data/lib/git/commands/remote/remove.rb +52 -0
- data/lib/git/commands/remote/rename.rb +69 -0
- data/lib/git/commands/remote/set_branches.rb +63 -0
- data/lib/git/commands/remote/set_head.rb +82 -0
- data/lib/git/commands/remote/set_url.rb +71 -0
- data/lib/git/commands/remote/set_url_add.rb +61 -0
- data/lib/git/commands/remote/set_url_delete.rb +64 -0
- data/lib/git/commands/remote/show.rb +71 -0
- data/lib/git/commands/remote/update.rb +72 -0
- data/lib/git/commands/remote.rb +42 -0
- data/lib/git/commands/repack.rb +277 -0
- data/lib/git/commands/reset.rb +147 -0
- data/lib/git/commands/rev_parse.rb +297 -0
- data/lib/git/commands/revert/abort.rb +45 -0
- data/lib/git/commands/revert/continue.rb +57 -0
- data/lib/git/commands/revert/quit.rb +47 -0
- data/lib/git/commands/revert/skip.rb +44 -0
- data/lib/git/commands/revert/start.rb +153 -0
- data/lib/git/commands/revert.rb +29 -0
- data/lib/git/commands/rm.rb +114 -0
- data/lib/git/commands/show.rb +632 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
- data/lib/git/commands/show_ref/exists.rb +78 -0
- data/lib/git/commands/show_ref/list.rb +145 -0
- data/lib/git/commands/show_ref/verify.rb +120 -0
- data/lib/git/commands/show_ref.rb +42 -0
- data/lib/git/commands/stash/apply.rb +75 -0
- data/lib/git/commands/stash/branch.rb +65 -0
- data/lib/git/commands/stash/clear.rb +41 -0
- data/lib/git/commands/stash/create.rb +58 -0
- data/lib/git/commands/stash/drop.rb +67 -0
- data/lib/git/commands/stash/list.rb +39 -0
- data/lib/git/commands/stash/pop.rb +78 -0
- data/lib/git/commands/stash/push.rb +103 -0
- data/lib/git/commands/stash/show.rb +149 -0
- data/lib/git/commands/stash/store.rb +63 -0
- data/lib/git/commands/stash.rb +38 -0
- data/lib/git/commands/status.rb +169 -0
- data/lib/git/commands/symbolic_ref/delete.rb +68 -0
- data/lib/git/commands/symbolic_ref/read.rb +95 -0
- data/lib/git/commands/symbolic_ref/update.rb +76 -0
- data/lib/git/commands/symbolic_ref.rb +38 -0
- data/lib/git/commands/tag/create.rb +139 -0
- data/lib/git/commands/tag/delete.rb +55 -0
- data/lib/git/commands/tag/list.rb +143 -0
- data/lib/git/commands/tag/verify.rb +71 -0
- data/lib/git/commands/tag.rb +26 -0
- data/lib/git/commands/update_ref/batch.rb +140 -0
- data/lib/git/commands/update_ref/delete.rb +92 -0
- data/lib/git/commands/update_ref/update.rb +106 -0
- data/lib/git/commands/update_ref.rb +42 -0
- data/lib/git/commands/version.rb +52 -0
- data/lib/git/commands/worktree/add.rb +140 -0
- data/lib/git/commands/worktree/list.rb +64 -0
- data/lib/git/commands/worktree/lock.rb +58 -0
- data/lib/git/commands/worktree/management_base.rb +51 -0
- data/lib/git/commands/worktree/move.rb +66 -0
- data/lib/git/commands/worktree/prune.rb +67 -0
- data/lib/git/commands/worktree/remove.rb +63 -0
- data/lib/git/commands/worktree/repair.rb +76 -0
- data/lib/git/commands/worktree/unlock.rb +47 -0
- data/lib/git/commands/worktree.rb +43 -0
- data/lib/git/commands/write_tree.rb +68 -0
- data/lib/git/commands.rb +89 -0
- data/lib/git/detached_head_info.rb +54 -0
- data/lib/git/diff.rb +297 -7
- data/lib/git/diff_file_numstat_info.rb +29 -0
- data/lib/git/diff_file_patch_info.rb +134 -0
- data/lib/git/diff_file_raw_info.rb +127 -0
- data/lib/git/diff_info.rb +169 -0
- data/lib/git/diff_path_status.rb +78 -19
- data/lib/git/diff_result.rb +32 -0
- data/lib/git/diff_stats.rb +59 -14
- data/lib/git/dirstat_info.rb +86 -0
- data/lib/git/errors.rb +65 -2
- data/lib/git/execution_context/global.rb +56 -0
- data/lib/git/execution_context/repository.rb +147 -0
- data/lib/git/execution_context.rb +482 -0
- data/lib/git/file_ref.rb +74 -0
- data/lib/git/fsck_object.rb +9 -9
- data/lib/git/fsck_result.rb +1 -1
- data/lib/git/lib.rb +1606 -1028
- data/lib/git/log.rb +15 -2
- data/lib/git/object.rb +92 -22
- data/lib/git/parsers/branch.rb +224 -0
- data/lib/git/parsers/cat_file.rb +111 -0
- data/lib/git/parsers/diff.rb +585 -0
- data/lib/git/parsers/fsck.rb +133 -0
- data/lib/git/parsers/grep.rb +42 -0
- data/lib/git/parsers/ls_tree.rb +58 -0
- data/lib/git/parsers/stash.rb +208 -0
- data/lib/git/parsers/tag.rb +257 -0
- data/lib/git/remote.rb +133 -9
- data/lib/git/repository/branching.rb +572 -0
- data/lib/git/repository/committing.rb +191 -0
- data/lib/git/repository/configuring.rb +156 -0
- data/lib/git/repository/diffing.rb +775 -0
- data/lib/git/repository/inspecting.rb +153 -0
- data/lib/git/repository/logging.rb +247 -0
- data/lib/git/repository/merging.rb +295 -0
- data/lib/git/repository/object_operations.rb +1101 -0
- data/lib/git/repository/path_resolver.rb +207 -0
- data/lib/git/repository/remote_operations.rb +753 -0
- data/lib/git/repository/shared_private.rb +51 -0
- data/lib/git/repository/staging.rb +390 -0
- data/lib/git/repository/stashing.rb +107 -0
- data/lib/git/repository/status_operations.rb +180 -0
- data/lib/git/repository/worktree_operations.rb +159 -0
- data/lib/git/repository.rb +264 -1
- data/lib/git/stash.rb +85 -4
- data/lib/git/stash_info.rb +104 -0
- data/lib/git/stashes.rb +130 -13
- data/lib/git/status.rb +224 -18
- data/lib/git/tag_delete_failure.rb +31 -0
- data/lib/git/tag_delete_result.rb +63 -0
- data/lib/git/tag_info.rb +105 -0
- data/lib/git/version.rb +109 -2
- data/lib/git/version_constraint.rb +81 -0
- data/lib/git/worktree.rb +120 -5
- data/lib/git/worktrees.rb +107 -7
- data/lib/git.rb +114 -18
- data/redesign/1_architecture_existing.md +54 -18
- data/redesign/2_architecture_redesign.md +365 -46
- data/redesign/3_architecture_implementation.md +1451 -54
- data/tasks/gem_tasks.rake +4 -0
- data/tasks/npm_tasks.rake +7 -0
- data/tasks/rspec.rake +48 -0
- data/tasks/test.rake +13 -1
- data/tasks/yard.rake +34 -7
- metadata +349 -20
- data/lib/git/index.rb +0 -6
- data/lib/git/path.rb +0 -38
- data/lib/git/working_directory.rb +0 -6
- /data/{release-please-config.json → .release-please-config.json} +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/command_line'
|
|
4
|
+
|
|
5
|
+
module Git
|
|
6
|
+
module CommandLine
|
|
7
|
+
# Executes a git command and captures both stdout and stderr in memory
|
|
8
|
+
#
|
|
9
|
+
# {Git::CommandLine::Capturing} is the buffering strategy: it calls
|
|
10
|
+
# `ProcessExecuter.run_with_capture`, which reads all subprocess output into
|
|
11
|
+
# `String` objects before returning. Use this class (via
|
|
12
|
+
# {Git::Lib#command_capturing}) for the vast majority of git subcommands whose
|
|
13
|
+
# output fits comfortably in memory.
|
|
14
|
+
#
|
|
15
|
+
# {Git::CommandLine::Streaming} is the complementary strategy for commands
|
|
16
|
+
# (such as `cat-file -p <blob>`) whose stdout may be too large to buffer.
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# capturing = Git::CommandLine::Capturing.new(
|
|
20
|
+
# {}, '/usr/bin/git', %w[--git-dir /repo/.git], Logger.new($stdout)
|
|
21
|
+
# )
|
|
22
|
+
# result = capturing.run('log', '--oneline', '-5')
|
|
23
|
+
# result.stdout # => "abc1234 Initial commit\n..."
|
|
24
|
+
# result.stderr # => ""
|
|
25
|
+
#
|
|
26
|
+
# @see Git::Lib#command_capturing
|
|
27
|
+
#
|
|
28
|
+
# @see Git::CommandLine::Streaming
|
|
29
|
+
#
|
|
30
|
+
class Capturing < Git::CommandLine::Base
|
|
31
|
+
# Default options accepted by {#run}
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
34
|
+
RUN_OPTION_DEFAULTS = {
|
|
35
|
+
in: nil,
|
|
36
|
+
out: nil,
|
|
37
|
+
err: nil,
|
|
38
|
+
chdir: nil,
|
|
39
|
+
timeout: nil,
|
|
40
|
+
raise_on_failure: true,
|
|
41
|
+
env: {},
|
|
42
|
+
normalize: false,
|
|
43
|
+
chomp: false,
|
|
44
|
+
merge: false
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# Execute a git command, capture stdout and stderr, and return the result
|
|
48
|
+
#
|
|
49
|
+
# Non-option command-line arguments to pass to git. If you collect the
|
|
50
|
+
# arguments in an array, splat the array into the parameter list.
|
|
51
|
+
#
|
|
52
|
+
# NORMALIZATION
|
|
53
|
+
#
|
|
54
|
+
# The command output is returned as a Unicode string containing the binary
|
|
55
|
+
# output from the command. If the binary output is not valid UTF-8, the
|
|
56
|
+
# output will cause problems because the encoding will be invalid.
|
|
57
|
+
#
|
|
58
|
+
# Normalization is a process that tries to convert the binary output to a
|
|
59
|
+
# valid UTF-8 string. It uses the `rchardet` gem to detect the encoding of
|
|
60
|
+
# the binary output and then converts it to UTF-8.
|
|
61
|
+
#
|
|
62
|
+
# Normalization is not enabled by default. Pass `normalize: true` to enable
|
|
63
|
+
# it. When enabled, normalization is applied to both stdout and stderr in
|
|
64
|
+
# the returned result object, regardless of the `out:` or `err:` options.
|
|
65
|
+
# Only the captured in-memory strings are normalized; any external IO you
|
|
66
|
+
# provide will receive the raw subprocess output.
|
|
67
|
+
#
|
|
68
|
+
# @example Run a command and return the output
|
|
69
|
+
# result = capturing.run('version')
|
|
70
|
+
# result.stdout #=> "git version 2.39.1\n"
|
|
71
|
+
#
|
|
72
|
+
# @example The args array should be splatted into the parameter list
|
|
73
|
+
# args = %w[log -n 1 --oneline]
|
|
74
|
+
# result = capturing.run(*args)
|
|
75
|
+
# result.stdout #=> "f5baa11 beginning of Ruby/Git project\n"
|
|
76
|
+
#
|
|
77
|
+
# @example Run a command and return the chomped output
|
|
78
|
+
# result = capturing.run('version', chomp: true)
|
|
79
|
+
# result.stdout #=> "git version 2.39.1"
|
|
80
|
+
#
|
|
81
|
+
# @example Run a command without normalizing the output
|
|
82
|
+
# capturing.run('version', normalize: false) #=> "git version 2.39.1\n"
|
|
83
|
+
#
|
|
84
|
+
# @example Capture stdout in a temporary file
|
|
85
|
+
# require 'tempfile'
|
|
86
|
+
# Tempfile.create('git') do |file|
|
|
87
|
+
# capturing.run('version', out: file)
|
|
88
|
+
# file.rewind
|
|
89
|
+
# file.read #=> "git version 2.39.1\n"
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# @example Capture stderr in a StringIO object
|
|
93
|
+
# require 'stringio'
|
|
94
|
+
# stderr = StringIO.new
|
|
95
|
+
# begin
|
|
96
|
+
# capturing.run('log', 'nonexistent-branch', err: stderr)
|
|
97
|
+
# rescue Git::FailedError => e
|
|
98
|
+
# stderr.string #=> "unknown revision or path not in the working tree.\n"
|
|
99
|
+
# end
|
|
100
|
+
#
|
|
101
|
+
# @param options_hash [Hash] the options to pass to the command
|
|
102
|
+
#
|
|
103
|
+
# @option options_hash [IO, nil] :in the IO object to use as stdin for the
|
|
104
|
+
# command, or nil to inherit the parent process stdin. Must be a real IO
|
|
105
|
+
# object with a file descriptor (not StringIO).
|
|
106
|
+
#
|
|
107
|
+
# @option options_hash [#write, nil] :out the object to write stdout to, or
|
|
108
|
+
# nil to capture stdout in the returned result.
|
|
109
|
+
#
|
|
110
|
+
# If this is a `StringIO` object, `stdout_writer.string` will be returned.
|
|
111
|
+
#
|
|
112
|
+
# In general, only specify a `stdout_writer` when you want to redirect
|
|
113
|
+
# stdout to a file or other `#write`-responding object. The default
|
|
114
|
+
# behaviour returns the command output.
|
|
115
|
+
#
|
|
116
|
+
# @option options_hash [#write, nil] :err the object to write stderr to, or
|
|
117
|
+
# nil to capture stderr in the returned result.
|
|
118
|
+
#
|
|
119
|
+
# @option options_hash [Boolean] :normalize (false) whether to normalize the
|
|
120
|
+
# encoding of stdout and stderr output
|
|
121
|
+
#
|
|
122
|
+
# @option options_hash [Boolean] :chomp (false) whether to chomp both stdout
|
|
123
|
+
# and stderr output
|
|
124
|
+
#
|
|
125
|
+
# @option options_hash [Boolean] :merge (false) whether to merge stdout and
|
|
126
|
+
# stderr in the returned string
|
|
127
|
+
#
|
|
128
|
+
# @option options_hash [String, nil] :chdir the directory to run the command in
|
|
129
|
+
#
|
|
130
|
+
# @option options_hash [Numeric, nil] :timeout the maximum seconds to wait for
|
|
131
|
+
# the command to complete. Zero means no timeout. A timeout kills the
|
|
132
|
+
# process via `SIGKILL` and raises {Git::TimeoutError}.
|
|
133
|
+
#
|
|
134
|
+
# @option options_hash [Boolean] :raise_on_failure (true) whether to raise
|
|
135
|
+
# {Git::FailedError} on non-zero exit status.
|
|
136
|
+
# {Git::TimeoutError} and {Git::SignaledError} are always raised regardless.
|
|
137
|
+
#
|
|
138
|
+
# @option options_hash [Hash] :env ({}) additional environment variable
|
|
139
|
+
# overrides for this command. String keys map to String values (to set) or
|
|
140
|
+
# `nil` (to unset).
|
|
141
|
+
#
|
|
142
|
+
# @return [Git::CommandLineResult] the result of the command
|
|
143
|
+
#
|
|
144
|
+
# @raise [ArgumentError] if `args` contains an array or an unknown option is
|
|
145
|
+
# passed
|
|
146
|
+
#
|
|
147
|
+
# @raise [Git::SignaledError] if the command was terminated by an uncaught signal
|
|
148
|
+
#
|
|
149
|
+
# @raise [Git::FailedError] if the command returned a non-zero exit status
|
|
150
|
+
#
|
|
151
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting
|
|
152
|
+
# subprocess output
|
|
153
|
+
#
|
|
154
|
+
# @raise [Git::TimeoutError] if the command times out
|
|
155
|
+
#
|
|
156
|
+
def run(*, **options_hash)
|
|
157
|
+
options = merge_and_validate_options(RUN_OPTION_DEFAULTS, options_hash)
|
|
158
|
+
|
|
159
|
+
result = execute(*, **options)
|
|
160
|
+
process_result(result, options)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
# @return [ProcessExecuter::ResultWithCapture] the process result with captured output
|
|
166
|
+
#
|
|
167
|
+
# @api private
|
|
168
|
+
def execute(*args, **options_hash)
|
|
169
|
+
git_cmd = build_git_cmd(args)
|
|
170
|
+
options = execute_options(**options_hash)
|
|
171
|
+
run_process_executer do
|
|
172
|
+
ProcessExecuter.run_with_capture(merged_env(options_hash), *git_cmd, **options)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Build the ProcessExecuter options hash for a capturing run
|
|
177
|
+
#
|
|
178
|
+
# @return [Hash]
|
|
179
|
+
#
|
|
180
|
+
# @api private
|
|
181
|
+
def execute_options(**options_hash)
|
|
182
|
+
chdir = options_hash[:chdir] || :not_set
|
|
183
|
+
timeout_after = options_hash[:timeout]
|
|
184
|
+
merge_output = options_hash[:merge] || false
|
|
185
|
+
|
|
186
|
+
{ chdir:, timeout_after:, merge_output:, raise_errors: false }.tap do |options|
|
|
187
|
+
redirect_options(options_hash).each { |k, v| options[k] = v }
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Extract non-nil redirect options (`:in`, `:out`, `:err`) from options_hash
|
|
192
|
+
#
|
|
193
|
+
# @return [Hash]
|
|
194
|
+
#
|
|
195
|
+
# @api private
|
|
196
|
+
def redirect_options(options_hash)
|
|
197
|
+
%i[in out err].filter_map do |key|
|
|
198
|
+
val = options_hash[key]
|
|
199
|
+
[key, val] unless val.nil?
|
|
200
|
+
end.to_h
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Post-process and return the stdout/stderr strings from the captured result,
|
|
204
|
+
# then log and raise on failure if required.
|
|
205
|
+
#
|
|
206
|
+
# @param result [ProcessExecuter::ResultWithCapture] the raw result
|
|
207
|
+
#
|
|
208
|
+
# @param options [Hash] the merged run options
|
|
209
|
+
#
|
|
210
|
+
# @return [Git::CommandLineResult]
|
|
211
|
+
#
|
|
212
|
+
# @raise [Git::FailedError] if the command failed and raise_on_failure is true
|
|
213
|
+
#
|
|
214
|
+
# @raise [Git::SignaledError] if the command was signaled
|
|
215
|
+
#
|
|
216
|
+
# @raise [Git::TimeoutError] if the command timed out
|
|
217
|
+
#
|
|
218
|
+
# @api private
|
|
219
|
+
def process_result(result, options)
|
|
220
|
+
command = result.command
|
|
221
|
+
processed_out, processed_err = post_process_output(result, options[:normalize], options[:chomp])
|
|
222
|
+
log_result(result, command, processed_out, processed_err)
|
|
223
|
+
command_line_result(
|
|
224
|
+
command, result, processed_out, processed_err, options[:timeout], options[:raise_on_failure]
|
|
225
|
+
)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Normalize and/or chomp the raw stdout and stderr strings.
|
|
229
|
+
#
|
|
230
|
+
# @param result [ProcessExecuter::ResultWithCapture] the raw result
|
|
231
|
+
#
|
|
232
|
+
# @param normalize [Boolean]
|
|
233
|
+
#
|
|
234
|
+
# @param chomp [Boolean]
|
|
235
|
+
#
|
|
236
|
+
# @return [Array<String>] two-element array: [processed_stdout, processed_stderr]
|
|
237
|
+
#
|
|
238
|
+
# @api private
|
|
239
|
+
def post_process_output(result, normalize, chomp)
|
|
240
|
+
[result.stdout, result.stderr].map do |raw_output|
|
|
241
|
+
output = raw_output.dup
|
|
242
|
+
output = output.lines.map { |l| Git::EncodingUtils.normalize_encoding(l) }.join if normalize
|
|
243
|
+
output.chomp! if chomp
|
|
244
|
+
output
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Git
|
|
4
|
+
module CommandLine
|
|
5
|
+
# The result of running a git command
|
|
6
|
+
#
|
|
7
|
+
# This object stores the Git command executed and its status, stdout, and stderr.
|
|
8
|
+
#
|
|
9
|
+
# @api public
|
|
10
|
+
#
|
|
11
|
+
class Result
|
|
12
|
+
# Create a Result object
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# git_cmd = %w[git version]
|
|
16
|
+
# status = instance_double(ProcessExecuter::Result)
|
|
17
|
+
# stdout = "git version 2.39.1\n"
|
|
18
|
+
# stderr = ""
|
|
19
|
+
# result = Git::CommandLine::Result.new(git_cmd, status, stdout, stderr)
|
|
20
|
+
#
|
|
21
|
+
# @param git_cmd [Array<String>] the git command that was executed
|
|
22
|
+
#
|
|
23
|
+
# @param status [ProcessExecuter::Result] the process result object returned
|
|
24
|
+
# by `ProcessExecuter.run` or `ProcessExecuter.run_with_capture`.
|
|
25
|
+
# Responds to `timed_out?`, `signaled?`, and `success?`.
|
|
26
|
+
#
|
|
27
|
+
# @param stdout [String] the processed stdout of the process
|
|
28
|
+
#
|
|
29
|
+
# @param stderr [String] the processed stderr of the process
|
|
30
|
+
#
|
|
31
|
+
def initialize(git_cmd, status, stdout, stderr)
|
|
32
|
+
@git_cmd = git_cmd
|
|
33
|
+
@status = status
|
|
34
|
+
@stdout = stdout
|
|
35
|
+
@stderr = stderr
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @attribute [r] git_cmd
|
|
39
|
+
#
|
|
40
|
+
# The git command that was executed
|
|
41
|
+
#
|
|
42
|
+
# @example
|
|
43
|
+
# git_cmd = %w[git version]
|
|
44
|
+
# result = Git::CommandLine::Result.new(git_cmd, nil, '', '')
|
|
45
|
+
# result.git_cmd #=> ["git", "version"]
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<String>]
|
|
48
|
+
#
|
|
49
|
+
attr_reader :git_cmd
|
|
50
|
+
|
|
51
|
+
# @attribute [r] status
|
|
52
|
+
#
|
|
53
|
+
# The process result object returned by ProcessExecuter
|
|
54
|
+
#
|
|
55
|
+
# In practice this is a `ProcessExecuter::ResultWithCapture` (from
|
|
56
|
+
# {Git::CommandLine::Capturing}) or a `ProcessExecuter::Result` (from
|
|
57
|
+
# {Git::CommandLine::Streaming}). Both respond to `success?`, `timed_out?`,
|
|
58
|
+
# and `signaled?`.
|
|
59
|
+
#
|
|
60
|
+
# @example
|
|
61
|
+
# status = instance_double(ProcessExecuter::Result, success?: true)
|
|
62
|
+
# result = Git::CommandLine::Result.new(%w[git version], status, '', '')
|
|
63
|
+
# result.status == status #=> true
|
|
64
|
+
#
|
|
65
|
+
# @return [ProcessExecuter::Result]
|
|
66
|
+
#
|
|
67
|
+
attr_reader :status
|
|
68
|
+
|
|
69
|
+
# @attribute [r] stdout
|
|
70
|
+
#
|
|
71
|
+
# The output of the process
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# stdout = "git version 2.39.1\n"
|
|
75
|
+
# result = Git::CommandLine::Result.new([], nil, stdout, '')
|
|
76
|
+
# result.stdout #=> "git version 2.39.1\n"
|
|
77
|
+
#
|
|
78
|
+
# @return [String]
|
|
79
|
+
#
|
|
80
|
+
attr_reader :stdout
|
|
81
|
+
|
|
82
|
+
# @attribute [r] stderr
|
|
83
|
+
#
|
|
84
|
+
# The error output of the process
|
|
85
|
+
#
|
|
86
|
+
# @example
|
|
87
|
+
# stderr = "Tag not found\n"
|
|
88
|
+
# result = Git::CommandLine::Result.new([], nil, '', stderr)
|
|
89
|
+
# result.stderr #=> "Tag not found\n"
|
|
90
|
+
#
|
|
91
|
+
# @return [String]
|
|
92
|
+
#
|
|
93
|
+
attr_reader :stderr
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'git/command_line'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
module Git
|
|
7
|
+
module CommandLine
|
|
8
|
+
# Executes a git command in streaming mode without buffering stdout in memory
|
|
9
|
+
#
|
|
10
|
+
# {Git::CommandLine::Streaming} is the non-buffering strategy: it calls
|
|
11
|
+
# `ProcessExecuter.run` and streams stdout directly to the caller-supplied `out:`
|
|
12
|
+
# IO object. Stderr is always captured internally in a `StringIO` for error
|
|
13
|
+
# diagnostics and is available as `result.stderr`.
|
|
14
|
+
#
|
|
15
|
+
# Use this class (via {Git::Lib#command_streaming}) for commands such as
|
|
16
|
+
# `cat-file -p <blob>` whose stdout may be too large to buffer in memory.
|
|
17
|
+
#
|
|
18
|
+
# {Git::CommandLine::Capturing} is the complementary strategy for the common case
|
|
19
|
+
# where buffering stdout is acceptable.
|
|
20
|
+
#
|
|
21
|
+
# @example Stream a blob to a file
|
|
22
|
+
# streaming = Git::CommandLine::Streaming.new(
|
|
23
|
+
# {}, '/usr/bin/git', %w[--git-dir /repo/.git], Logger.new($stdout)
|
|
24
|
+
# )
|
|
25
|
+
# File.open('/tmp/blob', 'wb') do |f|
|
|
26
|
+
# streaming.run('cat-file', 'blob', sha, out: f)
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# @see Git::Lib#command_streaming
|
|
30
|
+
#
|
|
31
|
+
# @see Git::CommandLine::Capturing
|
|
32
|
+
#
|
|
33
|
+
class Streaming < Git::CommandLine::Base
|
|
34
|
+
# Default options accepted by {#run}
|
|
35
|
+
#
|
|
36
|
+
# @api private
|
|
37
|
+
RUN_OPTION_DEFAULTS = {
|
|
38
|
+
in: nil,
|
|
39
|
+
out: nil,
|
|
40
|
+
err: nil,
|
|
41
|
+
chdir: nil,
|
|
42
|
+
timeout: nil,
|
|
43
|
+
raise_on_failure: true,
|
|
44
|
+
env: {}
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# Execute a git command in streaming mode and return the result
|
|
48
|
+
#
|
|
49
|
+
# Unlike {Git::CommandLine::Capturing#run}, this method does **not** buffer
|
|
50
|
+
# stdout in memory. Stdout is written only to the IO object provided via the
|
|
51
|
+
# `out:` option. Stderr is captured internally via a `StringIO` for error
|
|
52
|
+
# diagnostics.
|
|
53
|
+
#
|
|
54
|
+
# Use this entry point for commands that stream large content (e.g. blobs)
|
|
55
|
+
# where capturing stdout in memory would be unacceptable.
|
|
56
|
+
#
|
|
57
|
+
# @example Stream a blob to a file
|
|
58
|
+
# file = File.open('/tmp/blob', 'wb')
|
|
59
|
+
# streaming.run('cat-file', 'blob', sha, out: file)
|
|
60
|
+
#
|
|
61
|
+
# @param options_hash [Hash] the options to pass to the command
|
|
62
|
+
#
|
|
63
|
+
# @option options_hash [IO, nil] :in the IO object to use as stdin for the
|
|
64
|
+
# command, or nil to inherit the parent process stdin. Must be a real IO
|
|
65
|
+
# object with a file descriptor (not StringIO).
|
|
66
|
+
#
|
|
67
|
+
# @option options_hash [#write, nil] :out the IO/object to stream stdout into.
|
|
68
|
+
# Stdout is NOT buffered in the returned result; this is the only way to
|
|
69
|
+
# read it.
|
|
70
|
+
#
|
|
71
|
+
# @option options_hash [#write, nil] :err an optional additional destination to
|
|
72
|
+
# receive stderr output in real time (e.g. `$stderr` or a `File`). Stderr is
|
|
73
|
+
# always captured internally in a `StringIO` for error diagnostics. When
|
|
74
|
+
# `err:` is provided, writes are teed to both the internal buffer and this
|
|
75
|
+
# destination. `result.stderr` always reflects what was captured in the
|
|
76
|
+
# internal buffer, regardless of whether `err:` is supplied.
|
|
77
|
+
#
|
|
78
|
+
# @option options_hash [String, nil] :chdir the directory to run the command in
|
|
79
|
+
#
|
|
80
|
+
# @option options_hash [Numeric, nil] :timeout the maximum seconds to wait for
|
|
81
|
+
# the command to complete. Zero means no timeout. A timeout kills the
|
|
82
|
+
# process via `SIGKILL` and raises {Git::TimeoutError}.
|
|
83
|
+
#
|
|
84
|
+
# @option options_hash [Boolean] :raise_on_failure (true) whether to raise
|
|
85
|
+
# {Git::FailedError} on non-zero exit status.
|
|
86
|
+
# {Git::TimeoutError} and {Git::SignaledError} are always raised regardless.
|
|
87
|
+
#
|
|
88
|
+
# @option options_hash [Hash] :env ({}) additional environment variable
|
|
89
|
+
# overrides for this command. String keys map to String values (to set) or
|
|
90
|
+
# `nil` (to unset).
|
|
91
|
+
#
|
|
92
|
+
# @return [Git::CommandLineResult] the result of the command
|
|
93
|
+
#
|
|
94
|
+
# `result.stdout` will always be `''` (empty) — stdout was streamed to `out:`.
|
|
95
|
+
# `result.stderr` contains any stderr output captured for diagnostics.
|
|
96
|
+
#
|
|
97
|
+
# @raise [ArgumentError] if `args` contains an array or an unknown option is
|
|
98
|
+
# passed
|
|
99
|
+
#
|
|
100
|
+
# @raise [Git::SignaledError] if the command was terminated by an uncaught signal
|
|
101
|
+
#
|
|
102
|
+
# @raise [Git::FailedError] if the command returned a non-zero exit status
|
|
103
|
+
#
|
|
104
|
+
# @raise [Git::ProcessIOError] if an exception was raised while collecting
|
|
105
|
+
# subprocess output
|
|
106
|
+
#
|
|
107
|
+
# @raise [Git::TimeoutError] if the command times out
|
|
108
|
+
#
|
|
109
|
+
def run(*, **options_hash)
|
|
110
|
+
options = merge_and_validate_options(RUN_OPTION_DEFAULTS, options_hash)
|
|
111
|
+
|
|
112
|
+
internal_err = StringIO.new
|
|
113
|
+
# Tee stderr to the caller-provided destination (if any) AND the internal
|
|
114
|
+
# StringIO. This ensures result.stderr is always available even when err:
|
|
115
|
+
# is a non-StringIO IO object.
|
|
116
|
+
err_dest = options[:err] ? build_stderr_tee(internal_err, options[:err]) : internal_err
|
|
117
|
+
result = execute(*, err_io: err_dest, **options)
|
|
118
|
+
process_result(result, internal_err, options)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
# @return [ProcessExecuter::Result] the result of running the command (non-capturing)
|
|
124
|
+
#
|
|
125
|
+
# @api private
|
|
126
|
+
def execute(*args, err_io:, **options_hash)
|
|
127
|
+
git_cmd = build_git_cmd(args)
|
|
128
|
+
options = execute_options(err_io:, **options_hash)
|
|
129
|
+
run_process_executer do
|
|
130
|
+
ProcessExecuter.run(merged_env(options_hash), *git_cmd, **options)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Build the ProcessExecuter options hash for a streaming run
|
|
135
|
+
#
|
|
136
|
+
# @return [Hash]
|
|
137
|
+
#
|
|
138
|
+
# @api private
|
|
139
|
+
def execute_options(err_io:, **options_hash)
|
|
140
|
+
chdir = options_hash[:chdir] || :not_set
|
|
141
|
+
timeout_after = options_hash[:timeout]
|
|
142
|
+
|
|
143
|
+
{ chdir:, timeout_after:, raise_errors: false, err: err_io }.tap do |options|
|
|
144
|
+
options[:in] = options_hash[:in] unless options_hash[:in].nil?
|
|
145
|
+
options[:out] = options_hash[:out] unless options_hash[:out].nil?
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Build a tee writer that forwards #write calls to two destinations simultaneously.
|
|
150
|
+
#
|
|
151
|
+
# Used to capture stderr in an internal StringIO while also streaming to a
|
|
152
|
+
# caller-provided destination.
|
|
153
|
+
#
|
|
154
|
+
# @param primary [StringIO] the internal capture buffer
|
|
155
|
+
#
|
|
156
|
+
# @param secondary [#write] the caller-supplied destination
|
|
157
|
+
#
|
|
158
|
+
# @return [#write] an object whose #write method delegates to both destinations
|
|
159
|
+
#
|
|
160
|
+
# @api private
|
|
161
|
+
def build_stderr_tee(primary, secondary)
|
|
162
|
+
::Object.new.tap do |tee|
|
|
163
|
+
tee.define_singleton_method(:write) do |data|
|
|
164
|
+
primary.write(data)
|
|
165
|
+
secondary.write(data)
|
|
166
|
+
data.bytesize
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Process the result of a streaming command and return a Git::CommandLineResult
|
|
172
|
+
#
|
|
173
|
+
# Constructs stdout as `''` (not captured) and stderr from the internal StringIO.
|
|
174
|
+
#
|
|
175
|
+
# @param result [ProcessExecuter::Result] the raw process result
|
|
176
|
+
#
|
|
177
|
+
# @param err_io [StringIO] the internal StringIO that captured stderr
|
|
178
|
+
#
|
|
179
|
+
# @param options [Hash] the merged run options
|
|
180
|
+
#
|
|
181
|
+
# @return [Git::CommandLineResult]
|
|
182
|
+
#
|
|
183
|
+
# @api private
|
|
184
|
+
def process_result(result, err_io, options)
|
|
185
|
+
command = result.command
|
|
186
|
+
stderr = err_io.string
|
|
187
|
+
log_result(result, command, '', stderr)
|
|
188
|
+
command_line_result(
|
|
189
|
+
command, result, '', stderr, options[:timeout], options[:raise_on_failure]
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|