git 4.1.0 → 4.3.0
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 +34 -6
- data/.github/pull_request_template.md +12 -3
- data/.github/workflows/continuous_integration.yml +1 -1
- data/.github/workflows/enforce_conventional_commits.yml +1 -0
- data/.github/workflows/release.yml +2 -1
- data/.release-please-manifest.json +1 -1
- data/.rubocop_todo.yml +1 -1
- data/.yardopts +4 -0
- data/AI_POLICY.md +24 -0
- data/CHANGELOG.md +41 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/CONTRIBUTING.md +35 -2
- data/GOVERNANCE.md +106 -0
- data/MAINTAINERS.md +17 -4
- data/README.md +402 -333
- data/lib/git/args_builder.rb +8 -0
- data/lib/git/base.rb +55 -4
- data/lib/git/fsck_object.rb +48 -0
- data/lib/git/fsck_result.rb +121 -0
- data/lib/git/lib.rb +79 -3
- data/lib/git/log.rb +6 -1
- data/lib/git/path.rb +7 -1
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +2 -0
- metadata +8 -3
data/README.md
CHANGED
|
@@ -7,21 +7,30 @@
|
|
|
7
7
|
|
|
8
8
|
[](https://badge.fury.io/rb/git)
|
|
9
9
|
[](https://rubydoc.info/gems/git/)
|
|
10
|
-
[](https://rubydoc.info/gems/git/file/CHANGELOG.md)
|
|
12
|
+
[](https://github.com/ruby-git/ruby-git/actions?query=workflow%3ACI)
|
|
14
|
+
[](https://conventionalcommits.org)
|
|
16
|
+
[](AI_POLICY.md)
|
|
13
17
|
|
|
14
18
|
- [Summary](#summary)
|
|
15
19
|
- [Install](#install)
|
|
16
|
-
- [
|
|
20
|
+
- [Quick Start](#quick-start)
|
|
21
|
+
- [Examples](#examples)
|
|
22
|
+
- [Configuration](#configuration)
|
|
23
|
+
- [Read Operations](#read-operations)
|
|
24
|
+
- [Write Operations](#write-operations)
|
|
25
|
+
- [Index and Tree Operations](#index-and-tree-operations)
|
|
17
26
|
- [Errors Raised By This Gem](#errors-raised-by-this-gem)
|
|
18
27
|
- [Specifying And Handling Timeouts](#specifying-and-handling-timeouts)
|
|
19
28
|
- [Deprecations](#deprecations)
|
|
20
|
-
- [
|
|
21
|
-
- [Ruby
|
|
22
|
-
- [Git
|
|
23
|
-
- [License](#license)
|
|
29
|
+
- [Project Policies](#project-policies)
|
|
30
|
+
- [Ruby Version Support Policy](#ruby-version-support-policy)
|
|
31
|
+
- [Git Version Support Policy](#git-version-support-policy)
|
|
24
32
|
- [📢 Project Announcements 📢](#-project-announcements-)
|
|
33
|
+
- [2026-01-07: AI Policy Introduced](#2026-01-07-ai-policy-introduced)
|
|
25
34
|
- [2025-07-09: Architectural Redesign](#2025-07-09-architectural-redesign)
|
|
26
35
|
- [2025-07-07: We Now Use RuboCop](#2025-07-07-we-now-use-rubocop)
|
|
27
36
|
- [2025-06-06: Default Branch Rename](#2025-06-06-default-branch-rename)
|
|
@@ -34,11 +43,15 @@ command line.
|
|
|
34
43
|
|
|
35
44
|
Get started by obtaining a repository object by:
|
|
36
45
|
|
|
37
|
-
- opening an existing working copy with
|
|
38
|
-
|
|
39
|
-
-
|
|
46
|
+
- opening an existing working copy with
|
|
47
|
+
[Git.open](https://rubydoc.info/gems/git/Git#open-class_method)
|
|
48
|
+
- initializing a new repository with
|
|
49
|
+
[Git.init](https://rubydoc.info/gems/git/Git#init-class_method)
|
|
50
|
+
- cloning a repository with
|
|
51
|
+
[Git.clone](https://rubydoc.info/gems/git/Git#clone-class_method)
|
|
40
52
|
|
|
41
|
-
Methods that can be called on a repository object are documented in
|
|
53
|
+
Methods that can be called on a repository object are documented in
|
|
54
|
+
[Git::Base](https://rubydoc.info/gems/git/Git/Base)
|
|
42
55
|
|
|
43
56
|
## Install
|
|
44
57
|
|
|
@@ -66,169 +79,92 @@ to install version 1.x:
|
|
|
66
79
|
gem install git --version "~> 1.19"
|
|
67
80
|
```
|
|
68
81
|
|
|
69
|
-
##
|
|
70
|
-
|
|
71
|
-
**Git::Base** - The object returned from a `Git.open` or `Git.clone`. Most major actions are called from this object.
|
|
72
|
-
|
|
73
|
-
**Git::Object** - The base object for your tree, blob and commit objects, returned from `@git.gtree` or `@git.object` calls. the `Git::AbstractObject` will have most of the calls in common for all those objects.
|
|
82
|
+
## Quick Start
|
|
74
83
|
|
|
75
|
-
|
|
84
|
+
All functionality for this gem starts with the top-level
|
|
85
|
+
[`Git`](https://rubydoc.info/gems/git/Git) module. This module can be used to run
|
|
86
|
+
non-repo scoped `git` commands such as `config`.
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
`Git
|
|
79
|
-
|
|
88
|
+
The `Git` module also has factory methods such as `open`, `clone`, and `init` which
|
|
89
|
+
return a [`Git::Base`](https://rubydoc.info/gems/git/Git/Base) object. The
|
|
90
|
+
`Git::Base` object is used to run repo-specific `git` commands such as `add`,
|
|
91
|
+
`commit`, `push`, and `log`.
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
**Git::Remote**- A reference to a remote repository that is tracked by this repository.
|
|
84
|
-
|
|
85
|
-
**Git::Log** - An Enumerable object that references all the `Git::Object::Commit`
|
|
86
|
-
objects that encompass your log query, which can be constructed through methods on
|
|
87
|
-
the `Git::Log object`, like:
|
|
93
|
+
Clone, read status, and log:
|
|
88
94
|
|
|
89
95
|
```ruby
|
|
90
|
-
git
|
|
91
|
-
.max_count(:all)
|
|
92
|
-
.object('README.md')
|
|
93
|
-
.since('10 years ago')
|
|
94
|
-
.between('v1.0.7', 'HEAD')
|
|
95
|
-
.map { |commit| commit.sha }
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
A maximum of 30 commits are returned if `max_count` is not called. To get all commits
|
|
99
|
-
that match the log query, call `max_count(:all)`.
|
|
100
|
-
|
|
101
|
-
Note that `git.log.all` adds the `--all` option to the underlying `git log` command.
|
|
102
|
-
This asks for the logs of all refs (basically all commits reachable by HEAD,
|
|
103
|
-
branches, and tags). This does not control the maximum number of commits returned. To
|
|
104
|
-
control how many commits are returned, you should call `max_count`.
|
|
105
|
-
|
|
106
|
-
**Git::Worktrees** - Enumerable object that holds `Git::Worktree objects`.
|
|
107
|
-
|
|
108
|
-
## Errors Raised By This Gem
|
|
109
|
-
|
|
110
|
-
The git gem will only raise an `ArgumentError` or an error that is a subclass of
|
|
111
|
-
`Git::Error`. It does not explicitly raise any other types of errors.
|
|
112
|
-
|
|
113
|
-
It is recommended to rescue `Git::Error` to catch any runtime error raised by
|
|
114
|
-
this gem unless you need more specific error handling.
|
|
96
|
+
require 'git'
|
|
115
97
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
rescue Git::Error => e
|
|
120
|
-
puts "An error occurred: #{e.message}"
|
|
121
|
-
end
|
|
98
|
+
repo = Git.clone('https://github.com/ruby-git/ruby-git.git', 'ruby-git')
|
|
99
|
+
repo.status.changed.each { |f| puts "changed: #{f.path}" }
|
|
100
|
+
repo.log(5).each { |c| puts c.message }
|
|
122
101
|
```
|
|
123
102
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
## Specifying And Handling Timeouts
|
|
127
|
-
|
|
128
|
-
The timeout feature was added in git gem version `2.0.0`.
|
|
129
|
-
|
|
130
|
-
A timeout for git command line operations can be set either globally or for specific
|
|
131
|
-
method calls that accept a `:timeout` parameter.
|
|
132
|
-
|
|
133
|
-
The timeout value must be a real, non-negative `Numeric` value that specifies a
|
|
134
|
-
number of seconds a `git` command will be given to complete before being sent a KILL
|
|
135
|
-
signal. This library may hang if the `git` command does not terminate after receiving
|
|
136
|
-
the KILL signal.
|
|
137
|
-
|
|
138
|
-
When a command times out, it is killed by sending it the `SIGKILL` signal and a
|
|
139
|
-
`Git::TimeoutError` is raised. This error derives from the `Git::SignaledError` and
|
|
140
|
-
`Git::Error`.
|
|
141
|
-
|
|
142
|
-
If the timeout value is `0` or `nil`, no timeout will be enforced.
|
|
143
|
-
|
|
144
|
-
If a method accepts a `:timeout` parameter and a receives a non-nil value, the value
|
|
145
|
-
of this parameter will override the global timeout value. In this context, a value of
|
|
146
|
-
`nil` (which is usually the default) will use the global timeout value and a value of
|
|
147
|
-
`0` will turn off timeout enforcement for that method call no matter what the global
|
|
148
|
-
value is.
|
|
149
|
-
|
|
150
|
-
To set a global timeout, use the `Git.config` object:
|
|
103
|
+
Open an existing repo and commit:
|
|
151
104
|
|
|
152
105
|
```ruby
|
|
153
|
-
|
|
154
|
-
Git.config.timeout = 1.5 # can be any real, non-negative Numeric interpreted as number of seconds
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
The global timeout can be overridden for a specific method if the method accepts a
|
|
158
|
-
`:timeout` parameter:
|
|
106
|
+
require 'git'
|
|
159
107
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
Git.clone(repo_url, timeout: 0) # Do not enforce a timeout
|
|
165
|
-
Git.clone(repo_url, timeout: 10.5) # Timeout after 10.5 seconds raising Git::SignaledError
|
|
108
|
+
repo = Git.open('/path/to/repo')
|
|
109
|
+
repo.add(all: true)
|
|
110
|
+
repo.commit('chore: update files')
|
|
111
|
+
repo.push
|
|
166
112
|
```
|
|
167
113
|
|
|
168
|
-
|
|
114
|
+
Initialize a new repo and make the first commit:
|
|
169
115
|
|
|
170
116
|
```ruby
|
|
171
|
-
|
|
172
|
-
Git.clone(repo_url, timeout: 10)
|
|
173
|
-
rescue Git::TimeoutError => e
|
|
174
|
-
e.result.tap do |r|
|
|
175
|
-
r.class #=> Git::CommandLineResult
|
|
176
|
-
r.status #=> #<Process::Status: pid 62173 SIGKILL (signal 9)>
|
|
177
|
-
r.status.timeout? #=> true
|
|
178
|
-
r.git_cmd # The git command ran as an array of strings
|
|
179
|
-
r.stdout # The command's output to stdout until it was terminated
|
|
180
|
-
r.stderr # The command's output to stderr until it was terminated
|
|
181
|
-
end
|
|
182
|
-
end
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Deprecations
|
|
186
|
-
|
|
187
|
-
This gem uses ActiveSupport's deprecation mechanism to report deprecation warnings.
|
|
188
|
-
|
|
189
|
-
You can silence deprecation warnings by adding this line to your source code:
|
|
117
|
+
require 'git'
|
|
190
118
|
|
|
191
|
-
|
|
192
|
-
|
|
119
|
+
repo = Git.init('my_project')
|
|
120
|
+
repo.add(all: true)
|
|
121
|
+
repo.commit('initial commit')
|
|
193
122
|
```
|
|
194
123
|
|
|
195
|
-
See [the Active Support Deprecation
|
|
196
|
-
documentation](https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html)
|
|
197
|
-
for more details.
|
|
198
|
-
|
|
199
|
-
If deprecation warnings are silenced, you should reenable them before upgrading the
|
|
200
|
-
git gem to the next major version. This will make it easier to identify changes
|
|
201
|
-
needed for the upgrade.
|
|
202
|
-
|
|
203
124
|
## Examples
|
|
204
125
|
|
|
205
|
-
|
|
126
|
+
Beyond the basics covered in Quick Start, these examples show the full range of
|
|
127
|
+
options and variations for each operation.
|
|
206
128
|
|
|
207
|
-
|
|
129
|
+
### Configuration
|
|
130
|
+
|
|
131
|
+
Configure the `git` command line:
|
|
208
132
|
|
|
209
133
|
```ruby
|
|
210
|
-
|
|
134
|
+
# Global config (in ~/.gitconfig)
|
|
135
|
+
settings = Git.global_config # returns a Hash
|
|
136
|
+
username = Git.global_config('user.email')
|
|
137
|
+
Git.global_config('user.email', 'user@example.com')
|
|
138
|
+
|
|
139
|
+
# Repository config
|
|
140
|
+
repo = Git.open('path/to/repo')
|
|
141
|
+
settings = repo.config # returns a Hash
|
|
142
|
+
username = repo.config('user.email')
|
|
143
|
+
repo.config('user.email', 'anotheruser@example.com')
|
|
211
144
|
```
|
|
212
145
|
|
|
213
|
-
|
|
146
|
+
Configure the git gem:
|
|
214
147
|
|
|
215
148
|
```ruby
|
|
216
149
|
Git.configure do |config|
|
|
217
|
-
|
|
218
|
-
config.
|
|
219
|
-
|
|
220
|
-
# If you need to use a custom SSH script
|
|
221
|
-
config.git_ssh = '/path/to/ssh/script'
|
|
150
|
+
config.binary_path = '/usr/local/bin/git'
|
|
151
|
+
config.git_ssh = 'ssh -i ~/.ssh/id_rsa'
|
|
222
152
|
end
|
|
223
|
-
```
|
|
224
153
|
|
|
225
|
-
|
|
154
|
+
# or
|
|
155
|
+
|
|
156
|
+
Git.config.binary_path = '/usr/local/bin/git'
|
|
157
|
+
Git.config.git_ssh = 'ssh -i ~/.ssh/id_rsa'
|
|
158
|
+
```
|
|
226
159
|
|
|
227
160
|
**How SSH configuration is determined:**
|
|
228
161
|
|
|
229
|
-
- If `git_ssh` is not specified in the API call, the global config (`Git.configure {
|
|
230
|
-
|
|
231
|
-
- If `git_ssh` is
|
|
162
|
+
- If `git_ssh` is not specified in the API call, the global config (`Git.configure {
|
|
163
|
+
|c| c.git_ssh = ... }`) is used.
|
|
164
|
+
- If `git_ssh: nil` is specified, SSH is disabled for that instance (no SSH key or
|
|
165
|
+
script will be used).
|
|
166
|
+
- If `git_ssh` is a non-empty string, it is used for that instance (overriding the
|
|
167
|
+
global config).
|
|
232
168
|
|
|
233
169
|
You can also specify a custom SSH script on a per-repository basis:
|
|
234
170
|
|
|
@@ -244,43 +180,46 @@ git = Git.clone('git@github.com:user/repo.git', 'local-dir',
|
|
|
244
180
|
git = Git.init('new-repo', git_ssh: 'ssh -i /path/to/private_key')
|
|
245
181
|
```
|
|
246
182
|
|
|
247
|
-
This is especially useful in multi-threaded applications where different repositories
|
|
183
|
+
This is especially useful in multi-threaded applications where different repositories
|
|
184
|
+
require different SSH credentials.
|
|
185
|
+
|
|
186
|
+
### Read Operations
|
|
248
187
|
|
|
249
|
-
Here are the operations that need read permission only
|
|
188
|
+
Here are the operations that need read permission only:
|
|
250
189
|
|
|
251
190
|
```ruby
|
|
252
|
-
|
|
191
|
+
repo = Git.open(working_dir, :log => Logger.new(STDOUT))
|
|
253
192
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
193
|
+
repo.index
|
|
194
|
+
repo.index.readable?
|
|
195
|
+
repo.index.writable?
|
|
196
|
+
repo.repo
|
|
197
|
+
repo.dir
|
|
259
198
|
|
|
260
199
|
# ls-tree with recursion into subtrees (list files)
|
|
261
|
-
|
|
200
|
+
repo.ls_tree("HEAD", recursive: true)
|
|
262
201
|
|
|
263
202
|
# log - returns a Git::Log object, which is an Enumerator of Git::Commit objects
|
|
264
203
|
# default configuration returns a max of 30 commits
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
204
|
+
repo.log
|
|
205
|
+
repo.log(200) # 200 most recent commits
|
|
206
|
+
repo.log.since('2 weeks ago') # default count of commits since 2 weeks ago.
|
|
207
|
+
repo.log(200).since('2 weeks ago') # commits since 2 weeks ago, limited to 200.
|
|
208
|
+
repo.log.between('v2.5', 'v2.6')
|
|
209
|
+
repo.log.each {|l| puts l.sha }
|
|
210
|
+
repo.gblob('v2.5:Makefile').log.since('2 weeks ago')
|
|
272
211
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
212
|
+
repo.object('HEAD^').to_s # git show / git rev-parse
|
|
213
|
+
repo.object('HEAD^').contents
|
|
214
|
+
repo.object('v2.5:Makefile').size
|
|
215
|
+
repo.object('v2.5:Makefile').sha
|
|
277
216
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
217
|
+
repo.gtree(treeish)
|
|
218
|
+
repo.gblob(treeish)
|
|
219
|
+
repo.gcommit(treeish)
|
|
281
220
|
|
|
282
221
|
|
|
283
|
-
commit =
|
|
222
|
+
commit = repo.gcommit('1cc8667014381')
|
|
284
223
|
|
|
285
224
|
commit.gtree
|
|
286
225
|
commit.parent.sha
|
|
@@ -292,60 +231,74 @@ commit.committer.name
|
|
|
292
231
|
commit.date.strftime("%m-%d-%y")
|
|
293
232
|
commit.message
|
|
294
233
|
|
|
295
|
-
tree =
|
|
234
|
+
tree = repo.gtree("HEAD^{tree}")
|
|
296
235
|
|
|
297
236
|
tree.blobs
|
|
298
237
|
tree.subtrees
|
|
299
238
|
tree.children # blobs and subtrees
|
|
300
239
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
240
|
+
repo.rev_parse('v2.0.0:README.md')
|
|
241
|
+
|
|
242
|
+
repo.branches # returns Git::Branch objects
|
|
243
|
+
repo.branches.local
|
|
244
|
+
repo.current_branch
|
|
245
|
+
repo.branches.remote
|
|
246
|
+
repo.branches[:main].gcommit
|
|
247
|
+
repo.branches['origin/main'].gcommit
|
|
248
|
+
|
|
249
|
+
repo.grep('hello') # implies HEAD
|
|
250
|
+
repo.blob('v2.5:Makefile').grep('hello')
|
|
251
|
+
repo.tag('v2.5').grep('hello', 'docs/')
|
|
252
|
+
repo.describe()
|
|
253
|
+
repo.describe('0djf2aa')
|
|
254
|
+
repo.describe('HEAD', {:all => true, :tags => true})
|
|
255
|
+
|
|
256
|
+
repo.diff(commit1, commit2).size
|
|
257
|
+
repo.diff(commit1, commit2).stats
|
|
258
|
+
repo.diff(commit1, commit2).name_status
|
|
259
|
+
repo.gtree('v2.5').diff('v2.6').insertions
|
|
260
|
+
repo.diff('gitsearch1', 'v2.5').path('lib/')
|
|
261
|
+
repo.diff('gitsearch1', 'v2.5').path('lib/', 'docs/', 'README.md') # multiple paths
|
|
262
|
+
repo.diff('gitsearch1', repo.gtree('v2.5'))
|
|
263
|
+
repo.diff('gitsearch1', 'v2.5').path('docs/').patch
|
|
264
|
+
repo.gtree('v2.5').diff('v2.6').patch
|
|
265
|
+
|
|
266
|
+
repo.gtree('v2.5').diff('v2.6').each do |file_diff|
|
|
328
267
|
puts file_diff.path
|
|
329
268
|
puts file_diff.patch
|
|
330
269
|
puts file_diff.blob(:src).contents
|
|
331
270
|
end
|
|
332
271
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
272
|
+
repo.worktrees # returns Git::Worktree objects
|
|
273
|
+
repo.worktrees.count
|
|
274
|
+
repo.worktrees.each do |worktree|
|
|
336
275
|
worktree.dir
|
|
337
276
|
worktree.gcommit
|
|
338
277
|
worktree.to_s
|
|
339
278
|
end
|
|
340
279
|
|
|
341
|
-
|
|
342
|
-
|
|
280
|
+
# Check repository integrity with fsck
|
|
281
|
+
result = repo.fsck
|
|
282
|
+
result.dangling.each { |obj| puts "dangling #{obj.type}: #{obj.sha}" }
|
|
283
|
+
result.missing.each { |obj| puts "missing #{obj.type}: #{obj.sha}" }
|
|
284
|
+
|
|
285
|
+
# Check if repository has any issues
|
|
286
|
+
puts "Repository is clean" if result.empty?
|
|
287
|
+
|
|
288
|
+
# fsck with options
|
|
289
|
+
result = repo.fsck(unreachable: true, strict: true)
|
|
290
|
+
|
|
291
|
+
# Suppress dangling object output
|
|
292
|
+
result = repo.fsck(dangling: false)
|
|
293
|
+
|
|
294
|
+
repo.config('user.name') # returns 'Scott Chacon'
|
|
295
|
+
repo.config # returns whole config hash
|
|
343
296
|
|
|
344
297
|
# Configuration can be set when cloning using the :config option.
|
|
345
298
|
# This option can be an single configuration String or an Array
|
|
346
299
|
# if multiple config items need to be set.
|
|
347
300
|
#
|
|
348
|
-
|
|
301
|
+
repo = Git.clone(
|
|
349
302
|
git_uri, destination_path,
|
|
350
303
|
:config => [
|
|
351
304
|
'core.sshCommand=ssh -i /home/user/.ssh/id_rsa',
|
|
@@ -353,11 +306,11 @@ g = Git.clone(
|
|
|
353
306
|
]
|
|
354
307
|
)
|
|
355
308
|
|
|
356
|
-
|
|
309
|
+
repo.tags # returns array of Git::Tag objects
|
|
357
310
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
311
|
+
repo.show()
|
|
312
|
+
repo.show('HEAD')
|
|
313
|
+
repo.show('v2.8', 'README.md')
|
|
361
314
|
|
|
362
315
|
Git.ls_remote('https://github.com/ruby-git/ruby-git.git') # returns a hash containing the available references of the repo.
|
|
363
316
|
Git.ls_remote('/path/to/local/repo')
|
|
@@ -366,207 +319,316 @@ Git.ls_remote() # same as Git.ls_remote('.')
|
|
|
366
319
|
Git.default_branch('https://github.com/ruby-git/ruby-git') #=> 'main'
|
|
367
320
|
```
|
|
368
321
|
|
|
322
|
+
### Write Operations
|
|
323
|
+
|
|
369
324
|
And here are the operations that will need to write to your git repository.
|
|
370
325
|
|
|
371
326
|
```ruby
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
327
|
+
repo = Git.init # default is the current directory
|
|
328
|
+
repo = Git.init('project')
|
|
329
|
+
repo = Git.init(
|
|
330
|
+
'/home/schacon/proj',
|
|
331
|
+
{ :repository => '/opt/git/proj.git', :index => '/tmp/index'}
|
|
332
|
+
)
|
|
377
333
|
|
|
378
334
|
# Clone from a git url
|
|
379
335
|
git_url = 'https://github.com/ruby-git/ruby-git.git'
|
|
380
|
-
|
|
381
|
-
g = Git.clone(git_url)
|
|
336
|
+
repo = Git.clone(git_url)
|
|
382
337
|
|
|
383
338
|
# Clone into /tmp/clone/ruby-git-clean
|
|
384
339
|
name = 'ruby-git-clean'
|
|
385
340
|
path = '/tmp/clone'
|
|
386
|
-
|
|
387
|
-
|
|
341
|
+
repo = Git.clone(git_url, name, :path => path)
|
|
342
|
+
repo.dir #=> /tmp/clone/ruby-git-clean
|
|
388
343
|
|
|
389
|
-
|
|
390
|
-
|
|
344
|
+
repo.config('user.name', 'Scott Chacon')
|
|
345
|
+
repo.config('user.email', 'email@email.com')
|
|
391
346
|
|
|
392
347
|
# Clone can take a filter to tell the serve to send a partial clone
|
|
393
|
-
|
|
348
|
+
repo = Git.clone(git_url, name, :path => path, :filter => 'tree:0')
|
|
394
349
|
|
|
395
350
|
# Clone can control single-branch behavior (nil default keeps current git behavior)
|
|
396
|
-
|
|
351
|
+
repo = Git.clone(git_url, name, :path => path, :depth => 1, :single_branch => false)
|
|
397
352
|
|
|
398
353
|
# Clone can take an optional logger
|
|
399
|
-
logger = Logger.new
|
|
400
|
-
|
|
354
|
+
logger = Logger.new(STDOUT)
|
|
355
|
+
repo = Git.clone(git_url, 'my-repo', :log => logger)
|
|
401
356
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
357
|
+
repo.add # git add -- "."
|
|
358
|
+
repo.add(:all=>true) # git add --all -- "."
|
|
359
|
+
repo.add('file_path') # git add -- "file_path"
|
|
360
|
+
repo.add(['file_path_1', 'file_path_2']) # git add -- "file_path_1" "file_path_2"
|
|
406
361
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
362
|
+
repo.remove() # git rm -f -- "."
|
|
363
|
+
repo.remove('file.txt') # git rm -f -- "file.txt"
|
|
364
|
+
repo.remove(['file.txt', 'file2.txt']) # git rm -f -- "file.txt" "file2.txt"
|
|
365
|
+
repo.remove('file.txt', :recursive => true) # git rm -f -r -- "file.txt"
|
|
366
|
+
repo.remove('file.txt', :cached => true) # git rm -f --cached -- "file.txt"
|
|
412
367
|
|
|
413
|
-
|
|
414
|
-
|
|
368
|
+
repo.commit('message')
|
|
369
|
+
repo.commit_all('message')
|
|
415
370
|
|
|
416
371
|
# Sign a commit using the gpg key configured in the user.signingkey config setting
|
|
417
|
-
|
|
418
|
-
|
|
372
|
+
repo.config('user.signingkey', '0A46826A')
|
|
373
|
+
repo.commit('message', gpg_sign: true)
|
|
419
374
|
|
|
420
375
|
# Sign a commit using a specified gpg key
|
|
421
376
|
key_id = '0A46826A'
|
|
422
|
-
|
|
377
|
+
repo.commit('message', gpg_sign: key_id)
|
|
423
378
|
|
|
424
379
|
# Skip signing a commit (overriding any global gpgsign setting)
|
|
425
|
-
|
|
380
|
+
repo.commit('message', no_gpg_sign: true)
|
|
426
381
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
end
|
|
382
|
+
repo = Git.clone(git_url, 'myrepo')
|
|
383
|
+
repo.chdir do
|
|
384
|
+
File.write('test-file', 'blahblahblah')
|
|
385
|
+
repo.status.changed.each do |file|
|
|
386
|
+
puts file.blob(:index).contents
|
|
387
|
+
end
|
|
433
388
|
end
|
|
434
389
|
|
|
435
|
-
|
|
436
|
-
|
|
390
|
+
repo.reset # defaults to HEAD
|
|
391
|
+
repo.reset_hard(Git::Commit)
|
|
437
392
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
393
|
+
repo.branch('new_branch') # creates new or fetches existing
|
|
394
|
+
repo.branch('new_branch').checkout
|
|
395
|
+
repo.branch('new_branch').delete
|
|
396
|
+
repo.branch('existing_branch').checkout
|
|
397
|
+
repo.branch('main').contains?('existing_branch')
|
|
443
398
|
|
|
444
399
|
# delete remote branch
|
|
445
|
-
|
|
400
|
+
repo.push('origin', 'remote_branch_name', force: true, delete: true)
|
|
446
401
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
402
|
+
repo.checkout('new_branch')
|
|
403
|
+
repo.checkout('new_branch', new_branch: true, start_point: 'main')
|
|
404
|
+
repo.checkout(repo.branch('new_branch'))
|
|
450
405
|
|
|
451
|
-
|
|
452
|
-
|
|
406
|
+
repo.branch(name).merge(branch2)
|
|
407
|
+
repo.branch(branch2).merge # merges HEAD with branch2
|
|
453
408
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
409
|
+
repo.branch(name).in_branch(message) { # add files } # auto-commits
|
|
410
|
+
repo.merge('new_branch')
|
|
411
|
+
repo.merge('new_branch', 'merge commit message', no_ff: true)
|
|
412
|
+
repo.merge('origin/remote_branch')
|
|
413
|
+
repo.merge(repo.branch('main'))
|
|
414
|
+
repo.merge([branch1, branch2])
|
|
460
415
|
|
|
461
|
-
|
|
416
|
+
repo.merge_base('branch1', 'branch2')
|
|
462
417
|
|
|
463
|
-
r =
|
|
464
|
-
r =
|
|
418
|
+
r = repo.add_remote(name, uri) # Git::Remote
|
|
419
|
+
r = repo.add_remote(name, Git::Base) # Git::Remote
|
|
465
420
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
421
|
+
repo.remotes # array of Git::Remotes
|
|
422
|
+
repo.remote(name).fetch
|
|
423
|
+
repo.remote(name).remove
|
|
424
|
+
repo.remote(name).merge
|
|
425
|
+
repo.remote(name).merge(branch)
|
|
471
426
|
|
|
472
|
-
|
|
473
|
-
|
|
427
|
+
repo.remote_set_branches('origin', '*', add: true) # append additional fetch refspecs
|
|
428
|
+
repo.remote_set_branches('origin', 'feature', 'release/*') # replace fetch refspecs
|
|
474
429
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
430
|
+
repo.fetch
|
|
431
|
+
repo.fetch(repo.remotes.first)
|
|
432
|
+
repo.fetch('origin', {:ref => 'some/ref/head'} )
|
|
433
|
+
repo.fetch(all: true, force: true, depth: 2)
|
|
434
|
+
repo.fetch('origin', {:'update-head-ok' => true})
|
|
480
435
|
|
|
481
|
-
|
|
482
|
-
|
|
436
|
+
repo.pull
|
|
437
|
+
repo.pull(Git::Repo, Git::Branch) # fetch and a merge
|
|
483
438
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
439
|
+
repo.add_tag('tag_name') # returns Git::Tag
|
|
440
|
+
repo.add_tag('tag_name', 'object_reference')
|
|
441
|
+
repo.add_tag('tag_name', 'object_reference', {:options => 'here'})
|
|
442
|
+
repo.add_tag('tag_name', {:options => 'here'})
|
|
488
443
|
|
|
489
|
-
|
|
490
|
-
:a | :annotate
|
|
491
|
-
:d
|
|
492
|
-
:f
|
|
493
|
-
:m | :message
|
|
494
|
-
:s
|
|
444
|
+
repo.delete_tag('tag_name')
|
|
495
445
|
|
|
496
|
-
|
|
446
|
+
repo.repack
|
|
497
447
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
g.push
|
|
501
|
-
g.push(g.remote('name'))
|
|
448
|
+
repo.push
|
|
449
|
+
repo.push(repo.remote('name'))
|
|
502
450
|
|
|
503
451
|
# delete remote branch
|
|
504
|
-
|
|
452
|
+
repo.push('origin', 'remote_branch_name', force: true, delete: true)
|
|
505
453
|
|
|
506
454
|
# push all branches to remote at one time
|
|
507
|
-
|
|
455
|
+
repo.push('origin', all: true)
|
|
508
456
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
457
|
+
repo.worktree('/tmp/new_worktree').add
|
|
458
|
+
repo.worktree('/tmp/new_worktree', 'branch1').add
|
|
459
|
+
repo.worktree('/tmp/new_worktree').remove
|
|
460
|
+
repo.worktrees.prune
|
|
513
461
|
```
|
|
514
462
|
|
|
463
|
+
### Index and Tree Operations
|
|
464
|
+
|
|
515
465
|
Some examples of more low-level index and tree operations
|
|
516
466
|
|
|
517
467
|
```ruby
|
|
518
|
-
|
|
468
|
+
repo.with_temp_index do
|
|
519
469
|
|
|
520
|
-
|
|
521
|
-
|
|
470
|
+
repo.read_tree(tree3) # calls self.index.read_tree
|
|
471
|
+
repo.read_tree(tree1, :prefix => 'hi/')
|
|
522
472
|
|
|
523
|
-
c =
|
|
473
|
+
c = repo.commit_tree('message')
|
|
524
474
|
# or #
|
|
525
|
-
t =
|
|
526
|
-
c =
|
|
475
|
+
t = repo.write_tree
|
|
476
|
+
c = repo.commit_tree(t, :message => 'message', :parents => [sha1, sha2])
|
|
527
477
|
|
|
528
|
-
|
|
529
|
-
|
|
478
|
+
repo.branch('branch_name').update_ref(c)
|
|
479
|
+
repo.update_ref(branch, c)
|
|
530
480
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
481
|
+
repo.with_temp_working do # new blank working directory
|
|
482
|
+
repo.checkout
|
|
483
|
+
repo.checkout(another_index)
|
|
484
|
+
repo.commit # commits to temp_index
|
|
535
485
|
end
|
|
536
486
|
end
|
|
537
487
|
|
|
538
|
-
|
|
539
|
-
|
|
488
|
+
repo.set_index('/path/to/index')
|
|
540
489
|
|
|
541
|
-
|
|
490
|
+
repo.with_index(path) do
|
|
542
491
|
# calls set_index, then switches back after
|
|
543
492
|
end
|
|
544
493
|
|
|
545
|
-
|
|
494
|
+
repo.with_working(dir) do
|
|
546
495
|
# calls set_working, then switches back after
|
|
547
496
|
end
|
|
548
497
|
|
|
549
|
-
|
|
550
|
-
|
|
498
|
+
repo.with_temp_working(dir) do
|
|
499
|
+
repo.checkout_index(:prefix => dir, :path_limiter => path)
|
|
551
500
|
# do file work
|
|
552
|
-
|
|
501
|
+
repo.commit # commits to index
|
|
553
502
|
end
|
|
554
503
|
```
|
|
555
504
|
|
|
556
|
-
##
|
|
505
|
+
## Errors Raised By This Gem
|
|
557
506
|
|
|
558
|
-
|
|
507
|
+
The git gem will only raise an `ArgumentError` or an error that is a subclass of
|
|
508
|
+
`Git::Error`. It does not explicitly raise any other types of errors.
|
|
559
509
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
- The latest version of Truffle Ruby on Linus
|
|
510
|
+
It is recommended to rescue `Git::Error` to catch any runtime error raised by this
|
|
511
|
+
gem unless you need more specific error handling.
|
|
563
512
|
|
|
564
|
-
|
|
565
|
-
|
|
513
|
+
```ruby
|
|
514
|
+
begin
|
|
515
|
+
# some git operation
|
|
516
|
+
rescue Git::Error => e
|
|
517
|
+
puts "An error occurred: #{e.message}"
|
|
518
|
+
end
|
|
519
|
+
```
|
|
566
520
|
|
|
567
|
-
|
|
521
|
+
See [`Git::Error`](https://rubydoc.info/gems/git/Git/Error) for more information.
|
|
568
522
|
|
|
569
|
-
##
|
|
523
|
+
## Specifying And Handling Timeouts
|
|
524
|
+
|
|
525
|
+
The timeout feature was added in git gem version `2.0.0`.
|
|
526
|
+
|
|
527
|
+
A timeout for git command line operations can be set either globally or for specific
|
|
528
|
+
method calls that accept a `:timeout` parameter.
|
|
529
|
+
|
|
530
|
+
The timeout value must be a real, non-negative `Numeric` value that specifies a
|
|
531
|
+
number of seconds a `git` command will be given to complete before being sent a KILL
|
|
532
|
+
signal. This library may hang if the `git` command does not terminate after receiving
|
|
533
|
+
the KILL signal.
|
|
534
|
+
|
|
535
|
+
When a command times out, it is killed by sending it the `SIGKILL` signal and a
|
|
536
|
+
`Git::TimeoutError` is raised. This error derives from the `Git::SignaledError` and
|
|
537
|
+
`Git::Error`.
|
|
538
|
+
|
|
539
|
+
If the timeout value is `0` or `nil`, no timeout will be enforced.
|
|
540
|
+
|
|
541
|
+
If a method accepts a `:timeout` parameter and a receives a non-nil value, the value
|
|
542
|
+
of this parameter will override the global timeout value. In this context, a value of
|
|
543
|
+
`nil` (which is usually the default) will use the global timeout value and a value of
|
|
544
|
+
`0` will turn off timeout enforcement for that method call no matter what the global
|
|
545
|
+
value is.
|
|
546
|
+
|
|
547
|
+
To set a global timeout, use the `Git.config` object:
|
|
548
|
+
|
|
549
|
+
```ruby
|
|
550
|
+
Git.config.timeout = nil # a value of nil or 0 means no timeout is enforced
|
|
551
|
+
Git.config.timeout = 1.5 # can be any real, non-negative Numeric interpreted as number of seconds
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
The global timeout can be overridden for a specific method if the method accepts a
|
|
555
|
+
`:timeout` parameter:
|
|
556
|
+
|
|
557
|
+
```ruby
|
|
558
|
+
repo_url = 'https://github.com/ruby-git/ruby-git.git'
|
|
559
|
+
Git.clone(repo_url) # Use the global timeout value
|
|
560
|
+
Git.clone(repo_url, timeout: nil) # Also uses the global timeout value
|
|
561
|
+
Git.clone(repo_url, timeout: 0) # Do not enforce a timeout
|
|
562
|
+
Git.clone(repo_url, timeout: 10.5) # Timeout after 10.5 seconds raising Git::SignaledError
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
If the command takes too long, a `Git::TimeoutError` will be raised:
|
|
566
|
+
|
|
567
|
+
```ruby
|
|
568
|
+
begin
|
|
569
|
+
Git.clone(repo_url, timeout: 10)
|
|
570
|
+
rescue Git::TimeoutError => e
|
|
571
|
+
e.result.tap do |r|
|
|
572
|
+
r.class #=> Git::CommandLineResult
|
|
573
|
+
r.status #=> #<Process::Status: pid 62173 SIGKILL (signal 9)>
|
|
574
|
+
r.status.timeout? #=> true
|
|
575
|
+
r.git_cmd # The git command ran as an array of strings
|
|
576
|
+
r.stdout # The command's output to stdout until it was terminated
|
|
577
|
+
r.stderr # The command's output to stderr until it was terminated
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
## Deprecations
|
|
583
|
+
|
|
584
|
+
This gem uses ActiveSupport's deprecation mechanism to report deprecation warnings.
|
|
585
|
+
|
|
586
|
+
You can silence deprecation warnings by adding this line to your source code:
|
|
587
|
+
|
|
588
|
+
```ruby
|
|
589
|
+
Git::Deprecation.behavior = :silence
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
See [the Active Support Deprecation
|
|
593
|
+
documentation](https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html)
|
|
594
|
+
for more details.
|
|
595
|
+
|
|
596
|
+
If deprecation warnings are silenced, you should reenable them before upgrading the
|
|
597
|
+
git gem to the next major version. This will make it easier to identify changes
|
|
598
|
+
needed for the upgrade.
|
|
599
|
+
|
|
600
|
+
## Project Policies
|
|
601
|
+
|
|
602
|
+
These documents set expectations for behavior, contribution workflows, AI-assisted
|
|
603
|
+
changes, decision making, maintainer roles, and licensing. Please review them before
|
|
604
|
+
opening issues or pull requests.
|
|
605
|
+
|
|
606
|
+
| Document | Description |
|
|
607
|
+
| -------- | ----------- |
|
|
608
|
+
| [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) | We follow the Ruby community Code of Conduct; expect respectful, harassment-free participation and report concerns to maintainers. |
|
|
609
|
+
| [CONTRIBUTING](CONTRIBUTING.md) | How to report issues, submit PRs with Conventional Commits, meet coding/testing standards, and follow the Code of Conduct. |
|
|
610
|
+
| [AI_POLICY](AI_POLICY.md) | AI-assisted contributions are welcome. Contributors are expected to read and apply the AI Policy, and ensure any AI-assisted work meets our quality, security, and licensing standards. |
|
|
611
|
+
| [Ruby version support policy](#ruby-version-support-policy) | Supported Ruby runtimes and platforms; bump decisions and CI coverage expectations. |
|
|
612
|
+
| [Git version support policy](#git-version-support-policy) | Minimum supported git version and how version bumps are communicated and enforced. |
|
|
613
|
+
| [GOVERNANCE](GOVERNANCE.md) | Principles-first governance defining maintainer/project lead roles, least-privilege access, consensus/majority decisions, and nomination/emeritus steps. |
|
|
614
|
+
| [MAINTAINERS](MAINTAINERS.md) | Lists active maintainers (Project Lead noted) and emeritus alumni with links; see governance for role scope. |
|
|
615
|
+
| [LICENSE](LICENSE) | MIT License terms for using, modifying, and redistributing this project. |
|
|
616
|
+
|
|
617
|
+
### Ruby Version Support Policy
|
|
618
|
+
|
|
619
|
+
This gem is expected to function correctly on:
|
|
620
|
+
|
|
621
|
+
- All [non-EOL versions](https://www.ruby-lang.org/en/downloads/branches/) of the MRI
|
|
622
|
+
Ruby on Mac, Linux, and Windows
|
|
623
|
+
- The latest version of JRuby 9.4+ on Linux
|
|
624
|
+
- The latest version of TruffleRuby 24+ on Linux
|
|
625
|
+
|
|
626
|
+
It is this project's intent to support the latest version of JRuby on Windows once
|
|
627
|
+
the [process_executer](https://github.com/main-branch/process_executer) gem properly
|
|
628
|
+
supports subprocess status reporting on JRuby for Windows (see
|
|
629
|
+
[main-branch/process_executer#156](https://github.com/main-branch/process_executer/issues/156)).
|
|
630
|
+
|
|
631
|
+
### Git Version Support Policy
|
|
570
632
|
|
|
571
633
|
This gem requires git version 2.28.0 or greater as specified in the gemspec. This
|
|
572
634
|
requirement reflects:
|
|
@@ -585,12 +647,18 @@ gem as new git features are adopted or as maintaining backward compatibility bec
|
|
|
585
647
|
impractical. Such changes will be clearly documented in the CHANGELOG and release
|
|
586
648
|
notes.
|
|
587
649
|
|
|
588
|
-
##
|
|
650
|
+
## 📢 Project Announcements 📢
|
|
589
651
|
|
|
590
|
-
|
|
591
|
-
details.
|
|
652
|
+
### 2026-01-07: AI Policy Introduced
|
|
592
653
|
|
|
593
|
-
|
|
654
|
+
We have adopted a formal [AI Policy](AI_POLICY.md) to clarify expectations for
|
|
655
|
+
AI-assisted contributions. Please review it before opening a PR to ensure your
|
|
656
|
+
changes are fully understood, meet our quality bar, and respect licensing
|
|
657
|
+
requirements.
|
|
658
|
+
|
|
659
|
+
We chose a principles-based policy to respect contributors’ time and expertise. It’s
|
|
660
|
+
quick to read, easy to remember, and avoids unnecessary policy overhead while still
|
|
661
|
+
setting clear expectations.
|
|
594
662
|
|
|
595
663
|
### 2025-07-09: Architectural Redesign
|
|
596
664
|
|
|
@@ -643,11 +711,12 @@ rake rubocop
|
|
|
643
711
|
RuboCop is also run as part of the default rake task (by running `rake`) that is run
|
|
644
712
|
in our Continuous Integration workflow.
|
|
645
713
|
|
|
646
|
-
Going forward, any PRs that have any Robocop offenses will not be merged. In
|
|
647
|
-
|
|
648
|
-
|
|
714
|
+
Going forward, any PRs that have any Robocop offenses will not be merged. In certain
|
|
715
|
+
rare cases, it might be acceptable to disable a RuboCop check for the most limited
|
|
716
|
+
scope possible.
|
|
649
717
|
|
|
650
|
-
If you have a problem fixing a RuboCop offense, don't be afraid to ask a
|
|
718
|
+
If you have a problem fixing a RuboCop offense, don't be afraid to ask a
|
|
719
|
+
contributor.
|
|
651
720
|
|
|
652
721
|
### 2025-06-06: Default Branch Rename
|
|
653
722
|
|