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