git 5.0.0.beta.1 → 5.0.0.beta.3
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 +6 -0
- data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +188 -0
- data/.github/skills/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +22 -0
- data/.github/skills/extract-facade-from-base-lib/SKILL.md +28 -14
- data/.github/skills/facade-implementation/SKILL.md +14 -0
- data/.github/skills/facade-test-conventions/SKILL.md +14 -0
- data/.rubocop.yml +5 -0
- data/README.md +90 -22
- data/UPGRADING.md +141 -0
- data/git.gemspec +5 -0
- data/lib/git/branch.rb +7 -18
- data/lib/git/branches.rb +2 -10
- data/lib/git/command_line/base.rb +10 -0
- data/lib/git/command_line/capturing.rb +5 -3
- data/lib/git/command_line/streaming.rb +5 -3
- data/lib/git/command_line.rb +3 -3
- data/lib/git/commands/base.rb +7 -6
- data/lib/git/commands/cat_file/batch.rb +6 -1
- data/lib/git/commands/cat_file/raw.rb +7 -1
- data/lib/git/commands/config_option_syntax/get_urlmatch.rb +5 -0
- data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
- data/lib/git/commands/update_ref/batch.rb +1 -1
- data/lib/git/commands/version.rb +5 -0
- data/lib/git/commands.rb +5 -7
- data/lib/git/config.rb +17 -0
- data/lib/git/config_entry_info.rb +106 -0
- data/lib/git/configuring.rb +665 -0
- data/lib/git/deprecation.rb +9 -0
- data/lib/git/diff.rb +4 -8
- data/lib/git/diff_path_status.rb +2 -13
- data/lib/git/diff_stats.rb +1 -9
- data/lib/git/execution_context/global.rb +3 -28
- data/lib/git/execution_context/repository.rb +30 -41
- data/lib/git/execution_context.rb +43 -24
- data/lib/git/log.rb +3 -9
- data/lib/git/object.rb +14 -21
- data/lib/git/parsers/config_entry.rb +110 -0
- data/lib/git/parsers/ls_remote.rb +79 -0
- data/lib/git/remote.rb +7 -20
- data/lib/git/repository/branching.rb +183 -12
- data/lib/git/repository/committing.rb +64 -68
- data/lib/git/repository/context_helpers.rb +264 -0
- data/lib/git/repository/factories.rb +682 -0
- data/lib/git/repository/inspecting.rb +99 -0
- data/lib/git/repository/maintenance.rb +65 -0
- data/lib/git/repository/merging.rb +63 -1
- data/lib/git/repository/object_operations.rb +133 -35
- data/lib/git/repository/path_resolver.rb +1 -1
- data/lib/git/repository/remote_operations.rb +166 -21
- data/lib/git/repository/staging.rb +187 -23
- data/lib/git/repository/stashing.rb +39 -3
- data/lib/git/repository/status_operations.rb +21 -0
- data/lib/git/repository.rb +288 -128
- data/lib/git/stash.rb +2 -9
- data/lib/git/stashes.rb +2 -7
- data/lib/git/status.rb +12 -23
- data/lib/git/version.rb +2 -2
- data/lib/git/worktree.rb +2 -15
- data/lib/git/worktrees.rb +2 -15
- data/lib/git.rb +182 -77
- data/redesign/3_architecture_implementation.md +170 -112
- data/redesign/Phase 4 - Step A.md +366 -0
- data/redesign/beta_release.md +107 -0
- data/redesign/c1c2_audit.md +566 -0
- data/redesign/c1c2_bucket6_lib_orphans.md +626 -0
- data/redesign/config_design.rb +501 -0
- metadata +19 -6
- data/lib/git/base.rb +0 -1204
- data/lib/git/lib.rb +0 -2855
- data/lib/git/repository/configuring.rb +0 -156
data/UPGRADING.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Upgrading the `git` Gem
|
|
2
|
+
|
|
3
|
+
> **⚠️ Work in progress — v5.0.0 is currently in beta.**
|
|
4
|
+
> This document is updated incrementally as development continues and is not yet
|
|
5
|
+
> complete. If you encounter a compatibility problem not covered here, please
|
|
6
|
+
> [open an issue](https://github.com/ruby-git/ruby-git/issues).
|
|
7
|
+
|
|
8
|
+
- [Upgrading from v4.x to v5.x](#upgrading-from-v4x-to-v5x)
|
|
9
|
+
- [Overview](#overview)
|
|
10
|
+
- [`Git::Lib` removal](#gitlib-removal)
|
|
11
|
+
- [Methods with a direct replacement](#methods-with-a-direct-replacement)
|
|
12
|
+
- [Methods with no replacement](#methods-with-no-replacement)
|
|
13
|
+
- [Internal plumbing methods (no replacement)](#internal-plumbing-methods-no-replacement)
|
|
14
|
+
- [Deprecated methods](#deprecated-methods)
|
|
15
|
+
|
|
16
|
+
## Upgrading from v4.x to v5.x
|
|
17
|
+
|
|
18
|
+
### Overview
|
|
19
|
+
|
|
20
|
+
The primary goal of v5.0.0 is to move everyone onto a new internal architecture
|
|
21
|
+
while keeping the existing v4.x API working. The vast majority of v4.x code
|
|
22
|
+
requires no changes to run on v5.x.
|
|
23
|
+
|
|
24
|
+
The new architecture delivered in v5.0.0 is the foundation for future API
|
|
25
|
+
improvements across upcoming major releases.
|
|
26
|
+
|
|
27
|
+
The new architecture introduces a layered design (`Git::Commands`, `Git::Repository`,
|
|
28
|
+
and associated parsers). Compatibility shims — deprecated forwarding methods that map
|
|
29
|
+
old call patterns to the new API — ensure that v4.x code continues to work. These
|
|
30
|
+
shims emit deprecation warnings that tell you exactly what to change and what will be
|
|
31
|
+
eliminated in a future major release (most likely v6.0.0).
|
|
32
|
+
|
|
33
|
+
Hard breaks are limited to a small number of methods that had no safe
|
|
34
|
+
migration path — these are described in detail below.
|
|
35
|
+
|
|
36
|
+
Our intent is to make upgrading to v5.x as smooth as possible. For information
|
|
37
|
+
on how to suppress or configure deprecation warnings, see the
|
|
38
|
+
[Deprecations](README.md#deprecations) section of the README.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### `Git::Lib` removal
|
|
43
|
+
|
|
44
|
+
The object returned by `Git.open`, `Git.clone`, and `Git.init` — the object
|
|
45
|
+
you call methods on to interact with your repository — inadvertently exposed
|
|
46
|
+
a `#lib` method that gave access to `Git::Lib`, the gem's internal
|
|
47
|
+
implementation class. This was never intended to be public; it was an
|
|
48
|
+
implementation detail that leaked out. `Git::Lib` is removed in v5.0.0.
|
|
49
|
+
|
|
50
|
+
Calling `#lib` on the repository object returns `self` with a deprecation
|
|
51
|
+
warning so that existing `g.lib.*` call chains continue to work during
|
|
52
|
+
migration. The `#lib` method itself is removed in v6.0.0. Calls to methods
|
|
53
|
+
that have no replacement (see below) raise `NoMethodError` with an
|
|
54
|
+
informative message.
|
|
55
|
+
|
|
56
|
+
Most public behavior previously accessible via `g.lib.*` is available
|
|
57
|
+
directly on the repository object (i.e., `g.*`). A small number of methods
|
|
58
|
+
have no replacement — see below. The sections below list every affected method.
|
|
59
|
+
|
|
60
|
+
#### Methods with a direct replacement
|
|
61
|
+
|
|
62
|
+
All of the following `g.lib.*` calls work in v5.x but emit a deprecation
|
|
63
|
+
warning. Migrate to the replacement shown to silence the `g.lib.*` deprecation
|
|
64
|
+
warning; note that some replacements are themselves deprecated — see the table
|
|
65
|
+
notes for the final migration target.
|
|
66
|
+
|
|
67
|
+
| Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
|
|
68
|
+
|---------------------------------------------------|-------------|
|
|
69
|
+
| `g.lib.config_get(name)` | `g.config(name)` |
|
|
70
|
+
| `g.lib.config_list` | `g.config` |
|
|
71
|
+
| `g.lib.config_set(name, value)` | `g.config(name, value)` |
|
|
72
|
+
| `g.lib.git_version` | `g.git_version` |
|
|
73
|
+
| `g.lib.global_config_get(name)` | `g.global_config(name)` |
|
|
74
|
+
| `g.lib.global_config_list` | `g.global_config` |
|
|
75
|
+
| `g.lib.global_config_set(name, value)` | `g.global_config(name, value)` |
|
|
76
|
+
| `g.lib.parse_config(file)` | `g.config(file: file)` |
|
|
77
|
+
| `g.lib.stash_list` | `g.stash_list` *(also deprecated — use `g.stashes_all`)* |
|
|
78
|
+
| `g.lib.unmerged` | `g.unmerged` |
|
|
79
|
+
| `g.lib.change_head_branch(name)` | `g.change_head_branch(name)` |
|
|
80
|
+
| `g.lib.branch_current` | `g.current_branch` |
|
|
81
|
+
| `g.lib.ls_remote(location, opts)` | `g.ls_remote(location, opts)` |
|
|
82
|
+
| `g.lib.current_branch_state` | `g.current_branch_state` |
|
|
83
|
+
|
|
84
|
+
> **Note — `current_branch_state` return type change:** `g.lib.current_branch_state`
|
|
85
|
+
> returned a `Git::Lib::HeadState` (a mutable `Struct`). `g.current_branch_state`
|
|
86
|
+
> returns a `Git::Repository::Branching::HeadState` (an immutable `Data` object).
|
|
87
|
+
> Both expose `.state` (`:active`, `:unborn`, or `:detached`) and `.name`. If your
|
|
88
|
+
> code relies on the struct being mutable or uses positional construction
|
|
89
|
+
> (`Git::Lib::HeadState.new(:active, 'main')`), update to keyword construction:
|
|
90
|
+
> `Git::Repository::Branching::HeadState.new(state: :active, name: 'main')`.
|
|
91
|
+
|
|
92
|
+
#### Methods with no replacement
|
|
93
|
+
|
|
94
|
+
| v4.x call | Notes |
|
|
95
|
+
|-----------|-------|
|
|
96
|
+
| `g.lib.list_files(ref_dir)` | Walked `.git/refs/` directly. Use `g.branches`, `g.tags`, or `g.remotes` instead. |
|
|
97
|
+
|
|
98
|
+
#### Internal plumbing methods (no replacement)
|
|
99
|
+
|
|
100
|
+
The following methods were technically public on `Git::Lib` but are internal
|
|
101
|
+
helpers with no plausible external use. They have no replacement in v5.0.0:
|
|
102
|
+
|
|
103
|
+
- `assert_args_are_not_options`
|
|
104
|
+
- `assert_valid_opts`
|
|
105
|
+
- `cat_file_object_meta`
|
|
106
|
+
- `command_capturing`
|
|
107
|
+
- `command_streaming`
|
|
108
|
+
- `each_cat_file_header`
|
|
109
|
+
- `handle_deprecated_path_option`
|
|
110
|
+
- `normalize_pathspecs`
|
|
111
|
+
- `parse_cat_file_meta`
|
|
112
|
+
- `parse_config_list`
|
|
113
|
+
- `process_commit_data`
|
|
114
|
+
- `validate_pathspec_types`
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### Deprecated methods
|
|
119
|
+
|
|
120
|
+
The following methods are available in v5.x with deprecation warnings and
|
|
121
|
+
are removed in v6.0.0.
|
|
122
|
+
|
|
123
|
+
| Deprecated call (works in v5.x, removed in v6.0.0) | Replacement |
|
|
124
|
+
|---------------------------------------------------|-------------|
|
|
125
|
+
| `g.config_get(name)` | `g.config(name)` |
|
|
126
|
+
| `g.config_list` | `g.config` |
|
|
127
|
+
| `g.config_set(name, value)` | `g.config(name, value)` |
|
|
128
|
+
| `g.global_config_get(name)` | `g.global_config(name)` |
|
|
129
|
+
| `g.global_config_list` | `g.global_config` |
|
|
130
|
+
| `g.global_config_set(name, value)` | `g.global_config(name, value)` |
|
|
131
|
+
| `g.parse_config(file)` | `g.config(file: file)` |
|
|
132
|
+
| `g.stash_list` | `g.stashes_all` |
|
|
133
|
+
| `g.add_remote(name, url, opts)` | `g.remote_add(name, url, opts)` |
|
|
134
|
+
| `g.remove_remote(name)` | `g.remote_remove(name)` |
|
|
135
|
+
| `g.set_remote_url(name, url)` | `g.remote_set_url(name, url)` |
|
|
136
|
+
| `g.add_tag(name, ...)` | `g.tag_add(name, ...)` |
|
|
137
|
+
| `g.delete_tag(name)` | `g.tag_delete(name)` |
|
|
138
|
+
| `include Git; config(...)` | `Git.open(Dir.pwd).config(...)` |
|
|
139
|
+
| `include Git; global_config(...)` | `Git.global_config(...)` |
|
|
140
|
+
|
|
141
|
+
---
|
data/git.gemspec
CHANGED
|
@@ -48,6 +48,11 @@ Gem::Specification.new do |spec|
|
|
|
48
48
|
spec.add_development_dependency 'simplecov-rspec', '~> 0.4'
|
|
49
49
|
spec.add_development_dependency 'test-unit', '~> 3.7'
|
|
50
50
|
|
|
51
|
+
if RUBY_ENGINE == 'truffleruby' && Gem::Version.new(RUBY_ENGINE_VERSION) < Gem::Version.new('34.0.0')
|
|
52
|
+
# i18n 1.15+ uses Fiber.[] (Ruby 3.2 Fiber storage) which TruffleRuby < 34.0.0 does not implement
|
|
53
|
+
spec.add_development_dependency 'i18n', '< 1.15'
|
|
54
|
+
end
|
|
55
|
+
|
|
51
56
|
unless RUBY_PLATFORM == 'java' || RUBY_ENGINE == 'truffleruby'
|
|
52
57
|
spec.add_development_dependency 'irb', '~> 1.16'
|
|
53
58
|
spec.add_development_dependency 'redcarpet', '~> 3.6'
|
data/lib/git/branch.rb
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'git/base'
|
|
4
3
|
require_relative 'branch_info'
|
|
5
4
|
|
|
6
5
|
module Git
|
|
7
6
|
# Represents a Git branch
|
|
8
7
|
#
|
|
9
8
|
# Branch objects provide access to branch metadata and operations like checkout,
|
|
10
|
-
# delete, and merge. They should be obtained via {Git::
|
|
11
|
-
# {Git::
|
|
9
|
+
# delete, and merge. They should be obtained via {Git::Repository#branch} or
|
|
10
|
+
# {Git::Repository#branches}, not constructed directly.
|
|
12
11
|
#
|
|
13
12
|
# @example Getting a branch
|
|
14
13
|
# git = Git.open('.')
|
|
@@ -24,7 +23,7 @@ module Git
|
|
|
24
23
|
# The full refname of this branch
|
|
25
24
|
#
|
|
26
25
|
# For local branches this is the short name (e.g. `'main'`). For
|
|
27
|
-
# remote-tracking branches obtained via {Git::
|
|
26
|
+
# remote-tracking branches obtained via {Git::Repository#branches} this includes
|
|
28
27
|
# the `remotes/` prefix (e.g. `'remotes/origin/main'`). Branches constructed
|
|
29
28
|
# by {Git::Remote#branch} use the `<remote>/<branch>` form (e.g.
|
|
30
29
|
# `'origin/main'`) which does **not** populate {#remote}.
|
|
@@ -68,17 +67,13 @@ module Git
|
|
|
68
67
|
|
|
69
68
|
# Initialize a new Branch object
|
|
70
69
|
#
|
|
71
|
-
# @param base [Git::
|
|
72
|
-
#
|
|
73
|
-
# Accepts either a {Git::Base} (legacy) or a {Git::Repository} (new form).
|
|
74
|
-
# The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
|
|
75
|
-
# in Phase 4.
|
|
70
|
+
# @param base [Git::Repository] the git repository
|
|
76
71
|
#
|
|
77
72
|
# @param branch_info_or_name [Git::BranchInfo, String] branch info object or name string
|
|
78
73
|
#
|
|
79
74
|
# Passing a BranchInfo is preferred; String support is for backward compatibility.
|
|
80
75
|
#
|
|
81
|
-
# @note Use {Git::
|
|
76
|
+
# @note Use {Git::Repository#branch} or {Git::Repository#branches} instead of constructing directly
|
|
82
77
|
#
|
|
83
78
|
# @api private
|
|
84
79
|
#
|
|
@@ -151,7 +146,7 @@ module Git
|
|
|
151
146
|
#
|
|
152
147
|
# @param file [String] path to the destination archive file
|
|
153
148
|
#
|
|
154
|
-
# @param opts [Hash] archive options (see {Git::
|
|
149
|
+
# @param opts [Hash] archive options (see {Git::Repository#archive})
|
|
155
150
|
#
|
|
156
151
|
# @return [String] the path to the written archive file
|
|
157
152
|
#
|
|
@@ -464,18 +459,12 @@ module Git
|
|
|
464
459
|
nil
|
|
465
460
|
end
|
|
466
461
|
|
|
467
|
-
# Resolves the {Git::Repository} for this branch
|
|
468
|
-
#
|
|
469
|
-
# Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
|
|
470
|
-
# The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
|
|
471
|
-
# in Phase 4.
|
|
472
|
-
#
|
|
473
462
|
# @return [Git::Repository]
|
|
474
463
|
#
|
|
475
464
|
# @api private
|
|
476
465
|
#
|
|
477
466
|
def branch_repository
|
|
478
|
-
@base
|
|
467
|
+
@base
|
|
479
468
|
end
|
|
480
469
|
end
|
|
481
470
|
end
|
data/lib/git/branches.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'git/base'
|
|
4
|
-
|
|
5
3
|
module Git
|
|
6
4
|
# Collection of all Git branches in a repository
|
|
7
5
|
#
|
|
@@ -19,7 +17,7 @@ module Git
|
|
|
19
17
|
|
|
20
18
|
# Creates a new Branches collection populated from the given repository
|
|
21
19
|
#
|
|
22
|
-
# @param base [Git::
|
|
20
|
+
# @param base [Git::Repository] the repository to enumerate
|
|
23
21
|
# branches from
|
|
24
22
|
#
|
|
25
23
|
# @return [void]
|
|
@@ -136,18 +134,12 @@ module Git
|
|
|
136
134
|
|
|
137
135
|
private
|
|
138
136
|
|
|
139
|
-
# Resolves the {Git::Repository} for this collection of branches
|
|
140
|
-
#
|
|
141
|
-
# Accepts either a {Git::Repository} (new form) or a {Git::Base} (legacy).
|
|
142
|
-
# The `is_a?(Git::Base)` guard will be removed when {Git::Base} is deleted
|
|
143
|
-
# in Phase 4.
|
|
144
|
-
#
|
|
145
137
|
# @return [Git::Repository] the repository used to enumerate branches
|
|
146
138
|
#
|
|
147
139
|
# @api private
|
|
148
140
|
#
|
|
149
141
|
def branch_repository
|
|
150
|
-
@base
|
|
142
|
+
@base
|
|
151
143
|
end
|
|
152
144
|
|
|
153
145
|
# Indexes all supported lookup keys for a branch without mutating
|
|
@@ -156,6 +156,11 @@ module Git
|
|
|
156
156
|
#
|
|
157
157
|
# @raise [Git::ProcessIOError] in place of ProcessExecuter::ProcessIOError
|
|
158
158
|
#
|
|
159
|
+
# @raise [Git::ProcessIOError] when a timeout race causes Errno::ESRCH. On Ruby 4.0+,
|
|
160
|
+
# `Process.kill` raises `Errno::ESRCH` when the spawned process exits between the
|
|
161
|
+
# timeout firing and the kill signal being delivered. This is a race in
|
|
162
|
+
# process_executer's timeout handling and is semantically equivalent to a timeout.
|
|
163
|
+
#
|
|
159
164
|
# @return [Object] the return value of the block
|
|
160
165
|
#
|
|
161
166
|
# @api private
|
|
@@ -168,6 +173,11 @@ module Git
|
|
|
168
173
|
raise Git::Error, e.message, cause: e.cause
|
|
169
174
|
rescue ProcessExecuter::ProcessIOError => e
|
|
170
175
|
raise Git::ProcessIOError, e.message, cause: e.cause
|
|
176
|
+
rescue Errno::ESRCH => e
|
|
177
|
+
# Ruby 4.0+: Process.kill raises Errno::ESRCH when the spawned process exits
|
|
178
|
+
# in the narrow window between the timeout firing and the kill signal being sent.
|
|
179
|
+
# This is a known race in process_executer's timeout handling.
|
|
180
|
+
raise Git::ProcessIOError, "Git process no longer exists (timeout race): #{e.message}", cause: e
|
|
171
181
|
end
|
|
172
182
|
|
|
173
183
|
# Build the git command line from the available sources to send to `Process.spawn`
|
|
@@ -9,7 +9,7 @@ module Git
|
|
|
9
9
|
# {Git::CommandLine::Capturing} is the buffering strategy: it calls
|
|
10
10
|
# `ProcessExecuter.run_with_capture`, which reads all subprocess output into
|
|
11
11
|
# `String` objects before returning. Use this class (via
|
|
12
|
-
# {Git::
|
|
12
|
+
# {Git::ExecutionContext#command_capturing}) for the vast majority of git subcommands whose
|
|
13
13
|
# output fits comfortably in memory.
|
|
14
14
|
#
|
|
15
15
|
# {Git::CommandLine::Streaming} is the complementary strategy for commands
|
|
@@ -23,7 +23,7 @@ module Git
|
|
|
23
23
|
# result.stdout # => "abc1234 Initial commit\n..."
|
|
24
24
|
# result.stderr # => ""
|
|
25
25
|
#
|
|
26
|
-
# @see Git::
|
|
26
|
+
# @see Git::ExecutionContext#command_capturing
|
|
27
27
|
#
|
|
28
28
|
# @see Git::CommandLine::Streaming
|
|
29
29
|
#
|
|
@@ -149,7 +149,9 @@ module Git
|
|
|
149
149
|
# @raise [Git::FailedError] if the command returned a non-zero exit status
|
|
150
150
|
#
|
|
151
151
|
# @raise [Git::ProcessIOError] if an exception was raised while collecting
|
|
152
|
-
# subprocess output
|
|
152
|
+
# subprocess output, or (Ruby 4.0+) if a timeout-handling race causes
|
|
153
|
+
# `Errno::ESRCH` when the spawned process exits between the timeout
|
|
154
|
+
# firing and the kill signal being delivered
|
|
153
155
|
#
|
|
154
156
|
# @raise [Git::TimeoutError] if the command times out
|
|
155
157
|
#
|
|
@@ -12,7 +12,7 @@ module Git
|
|
|
12
12
|
# IO object. Stderr is always captured internally in a `StringIO` for error
|
|
13
13
|
# diagnostics and is available as `result.stderr`.
|
|
14
14
|
#
|
|
15
|
-
# Use this class (via {Git::
|
|
15
|
+
# Use this class (via {Git::ExecutionContext#command_streaming}) for commands such as
|
|
16
16
|
# `cat-file -p <blob>` whose stdout may be too large to buffer in memory.
|
|
17
17
|
#
|
|
18
18
|
# {Git::CommandLine::Capturing} is the complementary strategy for the common case
|
|
@@ -26,7 +26,7 @@ module Git
|
|
|
26
26
|
# streaming.run('cat-file', 'blob', sha, out: f)
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
29
|
-
# @see Git::
|
|
29
|
+
# @see Git::ExecutionContext#command_streaming
|
|
30
30
|
#
|
|
31
31
|
# @see Git::CommandLine::Capturing
|
|
32
32
|
#
|
|
@@ -102,7 +102,9 @@ module Git
|
|
|
102
102
|
# @raise [Git::FailedError] if the command returned a non-zero exit status
|
|
103
103
|
#
|
|
104
104
|
# @raise [Git::ProcessIOError] if an exception was raised while collecting
|
|
105
|
-
# subprocess output
|
|
105
|
+
# subprocess output, or (Ruby 4.0+) if a timeout-handling race causes
|
|
106
|
+
# `Errno::ESRCH` when the spawned process exits between the timeout
|
|
107
|
+
# firing and the kill signal being delivered
|
|
106
108
|
#
|
|
107
109
|
# @raise [Git::TimeoutError] if the command times out
|
|
108
110
|
#
|
data/lib/git/command_line.rb
CHANGED
|
@@ -14,9 +14,9 @@ module Git
|
|
|
14
14
|
# Use this for commands (e.g. `cat-file -p <blob>`) whose output may be
|
|
15
15
|
# too large to buffer.
|
|
16
16
|
#
|
|
17
|
-
# Both classes inherit from {Git::CommandLine::Base} and are
|
|
18
|
-
#
|
|
19
|
-
# {Git::
|
|
17
|
+
# Both classes inherit from {Git::CommandLine::Base} and are used internally
|
|
18
|
+
# by {Git::ExecutionContext#command_capturing} and
|
|
19
|
+
# {Git::ExecutionContext#command_streaming}.
|
|
20
20
|
#
|
|
21
21
|
# Results are returned as {Git::CommandLine::Result} objects (also accessible
|
|
22
22
|
# as {Git::CommandLineResult} for backward compatibility).
|
data/lib/git/commands/base.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'git/commands/arguments'
|
|
4
|
+
require 'git/version_constraint'
|
|
4
5
|
|
|
5
6
|
module Git
|
|
6
7
|
module Commands
|
|
@@ -173,8 +174,8 @@ module Git
|
|
|
173
174
|
end
|
|
174
175
|
end
|
|
175
176
|
|
|
176
|
-
# @param execution_context [Git::ExecutionContext
|
|
177
|
-
# {Git::
|
|
177
|
+
# @param execution_context [Git::ExecutionContext] context that provides
|
|
178
|
+
# {Git::ExecutionContext#command_capturing} and {Git::ExecutionContext#command_streaming}
|
|
178
179
|
def initialize(execution_context)
|
|
179
180
|
@execution_context = execution_context
|
|
180
181
|
end
|
|
@@ -210,7 +211,7 @@ module Git
|
|
|
210
211
|
# @raise [Git::VersionError] if the installed git version doesn't meet requirements
|
|
211
212
|
def call(*, **)
|
|
212
213
|
bound = args_definition.bind(*, **)
|
|
213
|
-
validate_version!
|
|
214
|
+
validate_version!(bound.execution_options)
|
|
214
215
|
result = execute_command(bound)
|
|
215
216
|
validate_exit_status!(result)
|
|
216
217
|
result
|
|
@@ -312,10 +313,10 @@ module Git
|
|
|
312
313
|
#
|
|
313
314
|
# Floor check always runs first and fails fast.
|
|
314
315
|
#
|
|
315
|
-
def validate_version!
|
|
316
|
+
def validate_version!(exec_opts = {})
|
|
316
317
|
return if self.class.skip_version_validation?
|
|
317
318
|
|
|
318
|
-
actual_version = @execution_context.git_version
|
|
319
|
+
actual_version = @execution_context.git_version(timeout: exec_opts[:timeout])
|
|
319
320
|
|
|
320
321
|
# Floor check: fail-fast if git is too old for the gem itself
|
|
321
322
|
validate_floor_version!(actual_version)
|
|
@@ -351,7 +352,7 @@ module Git
|
|
|
351
352
|
# the read end. The write and close happen concurrently with the block.
|
|
352
353
|
#
|
|
353
354
|
# The read end can be passed as the `in:` keyword to
|
|
354
|
-
# {Git::
|
|
355
|
+
# {Git::ExecutionContext#command_capturing} / {Git::CommandLine#run_with_capture}, connecting it directly to
|
|
355
356
|
# the spawned git process's stdin without an intermediate file or shell
|
|
356
357
|
# heredoc. This is required because `Process.spawn` only accepts real IO
|
|
357
358
|
# objects with a file descriptor — `StringIO` does not work.
|
|
@@ -103,6 +103,9 @@ module Git
|
|
|
103
103
|
# When provided, {#call} dispatches to the streaming execution path.
|
|
104
104
|
execution_option :out
|
|
105
105
|
|
|
106
|
+
# Abort the command after this many seconds.
|
|
107
|
+
execution_option :timeout
|
|
108
|
+
|
|
106
109
|
# Object names (or batch-command lines) are written to stdin, not argv.
|
|
107
110
|
# Using skip_cli: true because these values are fed via stdin — git never
|
|
108
111
|
# sees them as CLI arguments so Ruby must enforce the cross-argument
|
|
@@ -302,6 +305,8 @@ module Git
|
|
|
302
305
|
# @option options [#write, nil] :out (nil) stream stdout to this IO object
|
|
303
306
|
# instead of buffering in memory; when given, `result.stdout` will be `''`
|
|
304
307
|
#
|
|
308
|
+
# @option options [Numeric, nil] :timeout (nil) abort the command after this many seconds
|
|
309
|
+
#
|
|
305
310
|
# @return [Git::CommandLineResult] the result of calling `git cat-file`
|
|
306
311
|
#
|
|
307
312
|
# Stdout contains one metadata line per object (or `''` when `out:` is given)
|
|
@@ -311,7 +316,7 @@ module Git
|
|
|
311
316
|
# @raise [Git::FailedError] if git exits non-zero
|
|
312
317
|
def call(*objects, **)
|
|
313
318
|
bound = args_definition.bind(*objects, **)
|
|
314
|
-
validate_version!
|
|
319
|
+
validate_version!(bound.execution_options)
|
|
315
320
|
# `-Z` puts git into NUL I/O mode: input objects must be NUL-terminated.
|
|
316
321
|
# Without `-Z`, the standard newline delimiter is used.
|
|
317
322
|
delimiter = bound.Z? ? "\0" : "\n"
|
|
@@ -62,6 +62,9 @@ module Git
|
|
|
62
62
|
# When provided, {#call} dispatches to the streaming execution path.
|
|
63
63
|
execution_option :out
|
|
64
64
|
|
|
65
|
+
# Abort the command after this many seconds.
|
|
66
|
+
execution_option :timeout
|
|
67
|
+
|
|
65
68
|
end_of_options
|
|
66
69
|
|
|
67
70
|
# Expected object type — one of `commit`, `tree`, `blob`, or `tag`.
|
|
@@ -191,9 +194,12 @@ module Git
|
|
|
191
194
|
#
|
|
192
195
|
# @raise [Git::FailedError] if the object does not exist or is not of the
|
|
193
196
|
# given type
|
|
197
|
+
#
|
|
198
|
+
# @option options [Numeric, nil] :timeout (nil) abort the command after this many seconds
|
|
199
|
+
#
|
|
194
200
|
def call(*, **)
|
|
195
201
|
bound = args_definition.bind(*, **)
|
|
196
|
-
validate_version!
|
|
202
|
+
validate_version!(bound.execution_options)
|
|
197
203
|
result = execute_command(bound)
|
|
198
204
|
|
|
199
205
|
# `-e` treats exit 1 as a meaningful result (object not found), but any other
|
|
@@ -45,6 +45,9 @@ module Git
|
|
|
45
45
|
# General read options
|
|
46
46
|
flag_option :includes, negatable: true
|
|
47
47
|
|
|
48
|
+
# Output modifiers
|
|
49
|
+
flag_option :show_scope
|
|
50
|
+
|
|
48
51
|
# Operands
|
|
49
52
|
end_of_options
|
|
50
53
|
operand :name, required: true
|
|
@@ -91,6 +94,8 @@ module Git
|
|
|
91
94
|
# @option options [Boolean, nil] :no_includes (nil) disable include directives
|
|
92
95
|
# in config files (`--no-includes`)
|
|
93
96
|
#
|
|
97
|
+
# @option options [Boolean, nil] :show_scope (nil) show the scope of each config entry
|
|
98
|
+
#
|
|
94
99
|
# @return [Git::CommandLineResult] the result of calling `git config --get-urlmatch`
|
|
95
100
|
#
|
|
96
101
|
# @raise [ArgumentError] if unsupported options are provided
|
|
@@ -86,7 +86,7 @@ module Git
|
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
bound = args_definition.bind(*, exclude_existing: exclude_existing, **)
|
|
89
|
-
validate_version!
|
|
89
|
+
validate_version!(bound.execution_options)
|
|
90
90
|
stdin = Array(bound.ref).map { |r| "#{r}\n" }.join
|
|
91
91
|
with_stdin(stdin) { |reader| run_filter(bound, reader) }
|
|
92
92
|
end
|
|
@@ -118,7 +118,7 @@ module Git
|
|
|
118
118
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
119
119
|
def call(*, **)
|
|
120
120
|
bound = args_definition.bind(*, **)
|
|
121
|
-
validate_version!
|
|
121
|
+
validate_version!(bound.execution_options)
|
|
122
122
|
with_stdin(build_stdin(bound)) do |reader|
|
|
123
123
|
result = @execution_context.command_capturing(
|
|
124
124
|
*bound, in: reader, **bound.execution_options, raise_on_failure: false
|
data/lib/git/commands/version.rb
CHANGED
|
@@ -28,6 +28,7 @@ module Git
|
|
|
28
28
|
skip_version_validation
|
|
29
29
|
|
|
30
30
|
arguments do
|
|
31
|
+
execution_option :timeout
|
|
31
32
|
literal 'version'
|
|
32
33
|
flag_option :build_options
|
|
33
34
|
end
|
|
@@ -42,6 +43,10 @@ module Git
|
|
|
42
43
|
#
|
|
43
44
|
# @option options [Boolean, nil] :build_options (nil) include build options in the output
|
|
44
45
|
#
|
|
46
|
+
# @option options [Numeric, nil] :timeout (nil) the number of seconds to wait
|
|
47
|
+
# for the command to complete; if nil, uses the global timeout from
|
|
48
|
+
# {Git::Config}; if 0, no timeout is enforced
|
|
49
|
+
#
|
|
45
50
|
# @return [Git::CommandLineResult] the result of calling `git version`
|
|
46
51
|
#
|
|
47
52
|
# @raise [ArgumentError] if unsupported options are provided
|
data/lib/git/commands.rb
CHANGED
|
@@ -61,19 +61,17 @@ module Git
|
|
|
61
61
|
# {Git::CommandLine}, and return a raw {Git::CommandLineResult}.
|
|
62
62
|
#
|
|
63
63
|
# Commands do **not** parse output — that responsibility belongs to the
|
|
64
|
-
# {Git::Parsers} layer, orchestrated by the facade ({Git::
|
|
65
|
-
# {Git::Repository}).
|
|
64
|
+
# {Git::Parsers} layer, orchestrated by the facade ({Git::Repository}).
|
|
66
65
|
#
|
|
67
66
|
# All classes in this namespace are internal (`@api private`). End users
|
|
68
|
-
# should interact with the public API on {Git::
|
|
67
|
+
# should interact with the public API on {Git::Repository} instead.
|
|
69
68
|
#
|
|
70
69
|
# ## Architecture
|
|
71
70
|
#
|
|
72
71
|
# ```
|
|
73
|
-
# Git::
|
|
74
|
-
# └── Git::
|
|
75
|
-
# └── Git::
|
|
76
|
-
# └── Git::CommandLine (subprocess execution)
|
|
72
|
+
# Git::Repository (public API / facade — orchestrates commands + parsers)
|
|
73
|
+
# └── Git::Commands::* (defines CLI API, binds args, executes)
|
|
74
|
+
# └── Git::CommandLine (subprocess execution)
|
|
77
75
|
# ```
|
|
78
76
|
#
|
|
79
77
|
# Simple commands inherit from {Commands::Base} and only need an `arguments`
|
data/lib/git/config.rb
CHANGED
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
module Git
|
|
4
4
|
# The global configuration for this gem
|
|
5
5
|
class Config
|
|
6
|
+
# Returns the process-wide singleton {Git::Config} instance
|
|
7
|
+
#
|
|
8
|
+
# All calls to {Git.configure}, {Git.config}, and the {Git::ExecutionContext}
|
|
9
|
+
# classes resolve global configuration through this method.
|
|
10
|
+
#
|
|
11
|
+
# @example Read the configured binary path
|
|
12
|
+
# Git::Config.instance.binary_path #=> "git"
|
|
13
|
+
#
|
|
14
|
+
# @example Mutate the singleton (same as Git.configure { |c| ... })
|
|
15
|
+
# Git::Config.instance.binary_path = '/usr/local/bin/git'
|
|
16
|
+
#
|
|
17
|
+
# @return [Git::Config] the singleton config object
|
|
18
|
+
#
|
|
19
|
+
def self.instance
|
|
20
|
+
@instance ||= new
|
|
21
|
+
end
|
|
22
|
+
|
|
6
23
|
attr_writer :binary_path, :git_ssh, :timeout
|
|
7
24
|
|
|
8
25
|
def initialize
|