git 4.0.6 → 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.
data/README.md CHANGED
@@ -7,20 +7,30 @@
7
7
 
8
8
  [![Gem Version](https://badge.fury.io/rb/git.svg)](https://badge.fury.io/rb/git)
9
9
  [![Documentation](https://img.shields.io/badge/Documentation-Latest-green)](https://rubydoc.info/gems/git/)
10
- [![Change Log](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://rubydoc.info/gems/git/file/CHANGELOG.md)
11
- [![Build Status](https://github.com/ruby-git/ruby-git/workflows/CI/badge.svg?branch=main)](https://github.com/ruby-git/ruby-git/actions?query=workflow%3ACI)
12
- [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
10
+ [![Change
11
+ Log](https://img.shields.io/badge/CHANGELOG-Latest-green)](https://rubydoc.info/gems/git/file/CHANGELOG.md)
12
+ [![Build
13
+ Status](https://github.com/ruby-git/ruby-git/workflows/CI/badge.svg?branch=main)](https://github.com/ruby-git/ruby-git/actions?query=workflow%3ACI)
14
+ [![Conventional
15
+ Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
16
+ [![AI Policy](https://img.shields.io/badge/AI%20Policy-Required-blue)](AI_POLICY.md)
13
17
 
14
18
  - [Summary](#summary)
15
19
  - [Install](#install)
16
- - [Major Objects](#major-objects)
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
- - [Examples](#examples)
21
- - [Ruby version support policy](#ruby-version-support-policy)
22
- - [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)
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
- * opening an existing working copy with [Git.open](https://rubydoc.info/gems/git/Git#open-class_method)
37
- * initializing a new repository with [Git.init](https://rubydoc.info/gems/git/Git#init-class_method)
38
- * cloning a repository with [Git.clone](https://rubydoc.info/gems/git/Git#clone-class_method)
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 [Git::Base](https://rubydoc.info/gems/git/Git/Base)
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
- ## Major Objects
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
- **Git::Diff** - returns from a `@git.diff` command. It is an Enumerable that returns `Git::Diff:DiffFile` objects from which you can get per file patches and insertion/deletion statistics. You can also get total statistics from the Git::Diff object directly.
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
- **Git::Status** - returns from a `@git.status` command. It is an Enumerable that returns
77
- `Git:Status::StatusFile` objects for each object in git, which includes files in the working
78
- directory, in the index and in the repository. Similar to running 'git status' on the command line to determine untracked and changed files.
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
- **Git::Branches** - Enumerable object that holds `Git::Branch objects`. You can call .local or .remote on it to filter to just your local or remote branches.
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.log
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
- The git gem will only raise an `ArgumentError` or an error that is a subclass of
110
- `Git::Error`. It does not explicitly raise any other types of errors.
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
- It is recommended to rescue `Git::Error` to catch any runtime error raised by
113
- this gem unless you need more specific error handling.
103
+ Open an existing repo and commit:
114
104
 
115
105
  ```ruby
116
- begin
117
- # some git operation
118
- rescue Git::Error => e
119
- puts "An error occurred: #{e.message}"
120
- end
121
- ```
106
+ require 'git'
122
107
 
123
- See [`Git::Error`](https://rubydoc.info/gems/git/Git/Error) for more information.
108
+ repo = Git.open('/path/to/repo')
109
+ repo.add(all: true)
110
+ repo.commit('chore: update files')
111
+ repo.push
112
+ ```
124
113
 
125
- ## Specifying And Handling Timeouts
114
+ Initialize a new repo and make the first commit:
126
115
 
127
- The timeout feature was added in git gem version `2.0.0`.
128
-
129
- A timeout for git command line operations can be set either globally or for specific
130
- method calls that accept a `:timeout` parameter.
116
+ ```ruby
117
+ require 'git'
131
118
 
132
- The timeout value must be a real, non-negative `Numeric` value that specifies a
133
- number of seconds a `git` command will be given to complete before being sent a KILL
134
- signal. This library may hang if the `git` command does not terminate after receiving
135
- the KILL signal.
119
+ repo = Git.init('my_project')
120
+ repo.add(all: true)
121
+ repo.commit('initial commit')
122
+ ```
136
123
 
137
- When a command times out, it is killed by sending it the `SIGKILL` signal and a
138
- `Git::TimeoutError` is raised. This error derives from the `Git::SignaledError` and
139
- `Git::Error`.
124
+ ## Examples
140
125
 
141
- If the timeout value is `0` or `nil`, no timeout will be enforced.
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
- If a method accepts a `:timeout` parameter and a receives a non-nil value, the value
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
- To set a global timeout, use the `Git.config` object:
131
+ Configure the `git` command line:
150
132
 
151
133
  ```ruby
152
- Git.config.timeout = nil # a value of nil or 0 means no timeout is enforced
153
- Git.config.timeout = 1.5 # can be any real, non-negative Numeric interpreted as number of seconds
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
- The global timeout can be overridden for a specific method if the method accepts a
157
- `:timeout` parameter:
146
+ Configure the git gem:
158
147
 
159
148
  ```ruby
160
- repo_url = 'https://github.com/ruby-git/ruby-git.git'
161
- Git.clone(repo_url) # Use the global timeout value
162
- Git.clone(repo_url, timeout: nil) # Also uses the global timeout value
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
-
184
- ## Deprecations
185
-
186
- This gem uses ActiveSupport's deprecation mechanism to report deprecation warnings.
187
153
 
188
- You can silence deprecation warnings by adding this line to your source code:
154
+ # or
189
155
 
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
- See [the Active Support Deprecation
195
- documentation](https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html)
196
- for more details.
160
+ **How SSH configuration is determined:**
197
161
 
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
203
-
204
- Here are a bunch of examples of how to use the Ruby/Git package.
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
- Require the 'git' gem.
169
+ You can also specify a custom SSH script on a per-repository basis:
207
170
 
208
171
  ```ruby
209
- require 'git'
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
- ```ruby
215
- Git.configure do |config|
216
- # If you want to use a custom git binary
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
- # If you need to use a custom SSH script
220
- config.git_ssh = '/path/to/ssh/script'
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
- _NOTE: Another way to specify where is the `git` binary is through the environment variable `GIT_PATH`_
183
+ This is especially useful in multi-threaded applications where different repositories
184
+ require different SSH credentials.
185
+
186
+ ### Read Operations
225
187
 
226
- Here are the operations that need read permission only.
188
+ Here are the operations that need read permission only:
227
189
 
228
190
  ```ruby
229
- g = Git.open(working_dir, :log => Logger.new(STDOUT))
191
+ repo = Git.open(working_dir, :log => Logger.new(STDOUT))
230
192
 
231
- g.index
232
- g.index.readable?
233
- g.index.writable?
234
- g.repo
235
- g.dir
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
- g.ls_tree("HEAD", recursive: true)
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
- g.log
243
- g.log(200) # 200 most recent commits
244
- g.log.since('2 weeks ago') # default count of commits since 2 weeks ago.
245
- g.log(200).since('2 weeks ago') # commits since 2 weeks ago, limited to 200.
246
- g.log.between('v2.5', 'v2.6')
247
- g.log.each {|l| puts l.sha }
248
- g.gblob('v2.5:Makefile').log.since('2 weeks ago')
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
- g.object('HEAD^').to_s # git show / git rev-parse
251
- g.object('HEAD^').contents
252
- g.object('v2.5:Makefile').size
253
- g.object('v2.5:Makefile').sha
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
- g.gtree(treeish)
256
- g.gblob(treeish)
257
- g.gcommit(treeish)
217
+ repo.gtree(treeish)
218
+ repo.gblob(treeish)
219
+ repo.gcommit(treeish)
258
220
 
259
221
 
260
- commit = g.gcommit('1cc8667014381')
222
+ commit = repo.gcommit('1cc8667014381')
261
223
 
262
224
  commit.gtree
263
225
  commit.parent.sha
@@ -269,59 +231,74 @@ commit.committer.name
269
231
  commit.date.strftime("%m-%d-%y")
270
232
  commit.message
271
233
 
272
- tree = g.gtree("HEAD^{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
- g.rev_parse('v2.0.0:README.md')
279
-
280
- g.branches # returns Git::Branch objects
281
- g.branches.local
282
- g.current_branch
283
- g.branches.remote
284
- g.branches[:main].gcommit
285
- g.branches['origin/main'].gcommit
286
-
287
- g.grep('hello') # implies HEAD
288
- g.blob('v2.5:Makefile').grep('hello')
289
- g.tag('v2.5').grep('hello', 'docs/')
290
- g.describe()
291
- g.describe('0djf2aa')
292
- g.describe('HEAD', {:all => true, :tags => true})
293
-
294
- g.diff(commit1, commit2).size
295
- g.diff(commit1, commit2).stats
296
- g.diff(commit1, commit2).name_status
297
- g.gtree('v2.5').diff('v2.6').insertions
298
- g.diff('gitsearch1', 'v2.5').path('lib/')
299
- g.diff('gitsearch1', @git.gtree('v2.5'))
300
- g.diff('gitsearch1', 'v2.5').path('docs/').patch
301
- g.gtree('v2.5').diff('v2.6').patch
302
-
303
- g.gtree('v2.5').diff('v2.6').each do |file_diff|
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
- g.worktrees # returns Git::Worktree objects
310
- g.worktrees.count
311
- g.worktrees.each do |worktree|
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
- g.config('user.name') # returns 'Scott Chacon'
318
- g.config # returns whole config hash
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
319
296
 
320
297
  # Configuration can be set when cloning using the :config option.
321
298
  # This option can be an single configuration String or an Array
322
299
  # if multiple config items need to be set.
323
300
  #
324
- g = Git.clone(
301
+ repo = Git.clone(
325
302
  git_uri, destination_path,
326
303
  :config => [
327
304
  'core.sshCommand=ssh -i /home/user/.ssh/id_rsa',
@@ -329,11 +306,11 @@ g = Git.clone(
329
306
  ]
330
307
  )
331
308
 
332
- g.tags # returns array of Git::Tag objects
309
+ repo.tags # returns array of Git::Tag objects
333
310
 
334
- g.show()
335
- g.show('HEAD')
336
- g.show('v2.8', 'README.md')
311
+ repo.show()
312
+ repo.show('HEAD')
313
+ repo.show('v2.8', 'README.md')
337
314
 
338
315
  Git.ls_remote('https://github.com/ruby-git/ruby-git.git') # returns a hash containing the available references of the repo.
339
316
  Git.ls_remote('/path/to/local/repo')
@@ -342,207 +319,347 @@ Git.ls_remote() # same as Git.ls_remote('.')
342
319
  Git.default_branch('https://github.com/ruby-git/ruby-git') #=> 'main'
343
320
  ```
344
321
 
322
+ ### Write Operations
323
+
345
324
  And here are the operations that will need to write to your git repository.
346
325
 
347
326
  ```ruby
348
- g = Git.init
349
- Git.init('project')
350
- Git.init('/home/schacon/proj',
351
- { :repository => '/opt/git/proj.git',
352
- :index => '/tmp/index'} )
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
+ )
353
333
 
354
334
  # Clone from a git url
355
335
  git_url = 'https://github.com/ruby-git/ruby-git.git'
356
- # Clone into the ruby-git directory
357
- g = Git.clone(git_url)
336
+ repo = Git.clone(git_url)
358
337
 
359
338
  # Clone into /tmp/clone/ruby-git-clean
360
339
  name = 'ruby-git-clean'
361
340
  path = '/tmp/clone'
362
- g = Git.clone(git_url, name, :path => path)
363
- g.dir #=> /tmp/clone/ruby-git-clean
341
+ repo = Git.clone(git_url, name, :path => path)
342
+ repo.dir #=> /tmp/clone/ruby-git-clean
364
343
 
365
- g.config('user.name', 'Scott Chacon')
366
- g.config('user.email', 'email@email.com')
344
+ repo.config('user.name', 'Scott Chacon')
345
+ repo.config('user.email', 'email@email.com')
367
346
 
368
347
  # Clone can take a filter to tell the serve to send a partial clone
369
- g = Git.clone(git_url, name, :path => path, :filter => 'tree:0')
348
+ repo = Git.clone(git_url, name, :path => path, :filter => 'tree:0')
349
+
350
+ # Clone can control single-branch behavior (nil default keeps current git behavior)
351
+ repo = Git.clone(git_url, name, :path => path, :depth => 1, :single_branch => false)
370
352
 
371
353
  # Clone can take an optional logger
372
- logger = Logger.new
373
- g = Git.clone(git_url, NAME, :log => logger)
354
+ logger = Logger.new(STDOUT)
355
+ repo = Git.clone(git_url, 'my-repo', :log => logger)
374
356
 
375
- g.add # git add -- "."
376
- g.add(:all=>true) # git add --all -- "."
377
- g.add('file_path') # git add -- "file_path"
378
- g.add(['file_path_1', 'file_path_2']) # git add -- "file_path_1" "file_path_2"
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"
379
361
 
380
- g.remove() # git rm -f -- "."
381
- g.remove('file.txt') # git rm -f -- "file.txt"
382
- g.remove(['file.txt', 'file2.txt']) # git rm -f -- "file.txt" "file2.txt"
383
- g.remove('file.txt', :recursive => true) # git rm -f -r -- "file.txt"
384
- g.remove('file.txt', :cached => true) # git rm -f --cached -- "file.txt"
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"
385
367
 
386
- g.commit('message')
387
- g.commit_all('message')
368
+ repo.commit('message')
369
+ repo.commit_all('message')
388
370
 
389
371
  # Sign a commit using the gpg key configured in the user.signingkey config setting
390
- g.config('user.signingkey', '0A46826A')
391
- g.commit('message', gpg_sign: true)
372
+ repo.config('user.signingkey', '0A46826A')
373
+ repo.commit('message', gpg_sign: true)
392
374
 
393
375
  # Sign a commit using a specified gpg key
394
376
  key_id = '0A46826A'
395
- g.commit('message', gpg_sign: key_id)
377
+ repo.commit('message', gpg_sign: key_id)
396
378
 
397
379
  # Skip signing a commit (overriding any global gpgsign setting)
398
- g.commit('message', no_gpg_sign: true)
380
+ repo.commit('message', no_gpg_sign: true)
399
381
 
400
- g = Git.clone(repo, 'myrepo')
401
- g.chdir do
402
- new_file('test-file', 'blahblahblah')
403
- g.status.changed.each do |file|
404
- puts file.blob(:index).contents
405
- 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
406
388
  end
407
389
 
408
- g.reset # defaults to HEAD
409
- g.reset_hard(Git::Commit)
390
+ repo.reset # defaults to HEAD
391
+ repo.reset_hard(Git::Commit)
410
392
 
411
- g.branch('new_branch') # creates new or fetches existing
412
- g.branch('new_branch').checkout
413
- g.branch('new_branch').delete
414
- g.branch('existing_branch').checkout
415
- g.branch('main').contains?('existing_branch')
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')
416
398
 
417
399
  # delete remote branch
418
- g.push('origin', 'remote_branch_name', force: true, delete: true)
400
+ repo.push('origin', 'remote_branch_name', force: true, delete: true)
419
401
 
420
- g.checkout('new_branch')
421
- g.checkout('new_branch', new_branch: true, start_point: 'main')
422
- g.checkout(g.branch('new_branch'))
402
+ repo.checkout('new_branch')
403
+ repo.checkout('new_branch', new_branch: true, start_point: 'main')
404
+ repo.checkout(repo.branch('new_branch'))
423
405
 
424
- g.branch(name).merge(branch2)
425
- g.branch(branch2).merge # merges HEAD with branch2
406
+ repo.branch(name).merge(branch2)
407
+ repo.branch(branch2).merge # merges HEAD with branch2
426
408
 
427
- g.branch(name).in_branch(message) { # add files } # auto-commits
428
- g.merge('new_branch')
429
- g.merge('new_branch', 'merge commit message', no_ff: true)
430
- g.merge('origin/remote_branch')
431
- g.merge(g.branch('main'))
432
- g.merge([branch1, branch2])
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])
433
415
 
434
- g.merge_base('branch1', 'branch2')
416
+ repo.merge_base('branch1', 'branch2')
435
417
 
436
- r = g.add_remote(name, uri) # Git::Remote
437
- r = g.add_remote(name, Git::Base) # Git::Remote
418
+ r = repo.add_remote(name, uri) # Git::Remote
419
+ r = repo.add_remote(name, Git::Base) # Git::Remote
438
420
 
439
- g.remotes # array of Git::Remotes
440
- g.remote(name).fetch
441
- g.remote(name).remove
442
- g.remote(name).merge
443
- g.remote(name).merge(branch)
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)
444
426
 
445
- g.fetch
446
- g.fetch(g.remotes.first)
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})
427
+ repo.remote_set_branches('origin', '*', add: true) # append additional fetch refspecs
428
+ repo.remote_set_branches('origin', 'feature', 'release/*') # replace fetch refspecs
450
429
 
451
- g.pull
452
- g.pull(Git::Repo, Git::Branch) # fetch and a merge
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})
453
435
 
454
- g.add_tag('tag_name') # returns Git::Tag
455
- g.add_tag('tag_name', 'object_reference')
456
- g.add_tag('tag_name', 'object_reference', {:options => 'here'})
457
- g.add_tag('tag_name', {:options => 'here'})
436
+ repo.pull
437
+ repo.pull(Git::Repo, Git::Branch) # fetch and a merge
458
438
 
459
- Options:
460
- :a | :annotate
461
- :d
462
- :f
463
- :m | :message
464
- :s
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'})
465
443
 
466
- g.delete_tag('tag_name')
444
+ repo.delete_tag('tag_name')
467
445
 
468
- g.repack
446
+ repo.repack
469
447
 
470
- g.push
471
- g.push(g.remote('name'))
448
+ repo.push
449
+ repo.push(repo.remote('name'))
472
450
 
473
451
  # delete remote branch
474
- g.push('origin', 'remote_branch_name', force: true, delete: true)
452
+ repo.push('origin', 'remote_branch_name', force: true, delete: true)
475
453
 
476
454
  # push all branches to remote at one time
477
- g.push('origin', all: true)
455
+ repo.push('origin', all: true)
478
456
 
479
- g.worktree('/tmp/new_worktree').add
480
- g.worktree('/tmp/new_worktree', 'branch1').add
481
- g.worktree('/tmp/new_worktree').remove
482
- g.worktrees.prune
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
483
461
  ```
484
462
 
463
+ ### Index and Tree Operations
464
+
485
465
  Some examples of more low-level index and tree operations
486
466
 
487
467
  ```ruby
488
- g.with_temp_index do
468
+ repo.with_temp_index do
489
469
 
490
- g.read_tree(tree3) # calls self.index.read_tree
491
- g.read_tree(tree1, :prefix => 'hi/')
470
+ repo.read_tree(tree3) # calls self.index.read_tree
471
+ repo.read_tree(tree1, :prefix => 'hi/')
492
472
 
493
- c = g.commit_tree('message')
473
+ c = repo.commit_tree('message')
494
474
  # or #
495
- t = g.write_tree
496
- c = g.commit_tree(t, :message => 'message', :parents => [sha1, sha2])
475
+ t = repo.write_tree
476
+ c = repo.commit_tree(t, :message => 'message', :parents => [sha1, sha2])
497
477
 
498
- g.branch('branch_name').update_ref(c)
499
- g.update_ref(branch, c)
478
+ repo.branch('branch_name').update_ref(c)
479
+ repo.update_ref(branch, c)
500
480
 
501
- g.with_temp_working do # new blank working directory
502
- g.checkout
503
- g.checkout(another_index)
504
- g.commit # commits to temp_index
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
505
485
  end
506
486
  end
507
487
 
508
- g.set_index('/path/to/index')
509
-
488
+ repo.set_index('/path/to/index')
510
489
 
511
- g.with_index(path) do
490
+ repo.with_index(path) do
512
491
  # calls set_index, then switches back after
513
492
  end
514
493
 
515
- g.with_working(dir) do
494
+ repo.with_working(dir) do
516
495
  # calls set_working, then switches back after
517
496
  end
518
497
 
519
- g.with_temp_working(dir) do
520
- g.checkout_index(:prefix => dir, :path_limiter => path)
498
+ repo.with_temp_working(dir) do
499
+ repo.checkout_index(:prefix => dir, :path_limiter => path)
521
500
  # do file work
522
- g.commit # commits to index
501
+ repo.commit # commits to index
523
502
  end
524
503
  ```
525
504
 
526
- ## Ruby version support policy
505
+ ## Errors Raised By This Gem
527
506
 
528
- This gem will be expected to function correctly on:
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.
529
509
 
530
- - All non-EOL versions of the MRI Ruby on Mac, Linux, and Windows
531
- - The latest version of JRuby on Linux
532
- - 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.
533
512
 
534
- It is this project's intent to support the latest version of JRuby on Windows
535
- once the following JRuby bug is fixed:
513
+ ```ruby
514
+ begin
515
+ # some git operation
516
+ rescue Git::Error => e
517
+ puts "An error occurred: #{e.message}"
518
+ end
519
+ ```
536
520
 
537
- jruby/jruby#7515
521
+ See [`Git::Error`](https://rubydoc.info/gems/git/Git/Error) for more information.
538
522
 
539
- ## License
523
+ ## Specifying And Handling Timeouts
540
524
 
541
- Licensed under MIT License Copyright (c) 2008 Scott Chacon. See LICENSE for further
542
- details.
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
632
+
633
+ This gem requires git version 2.28.0 or greater as specified in the gemspec. This
634
+ requirement reflects:
635
+
636
+ - The minimum git version necessary to support all features provided by this gem
637
+ - A reasonable balance between supporting older systems and leveraging modern git
638
+ capabilities
639
+ - The practical limitations of testing across multiple git versions in CI
640
+
641
+ Git 2.28.0 was released on July 27, 2020. While this gem may work with earlier
642
+ versions of git, compatibility with versions prior to 2.28.0 is not tested or
643
+ guaranteed. Users on older git versions should upgrade to at least 2.28.0.
644
+
645
+ The supported git version may be increased in future major or minor releases of this
646
+ gem as new git features are adopted or as maintaining backward compatibility becomes
647
+ impractical. Such changes will be clearly documented in the CHANGELOG and release
648
+ notes.
543
649
 
544
650
  ## 📢 Project Announcements 📢
545
651
 
652
+ ### 2026-01-07: AI Policy Introduced
653
+
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.
662
+
546
663
  ### 2025-07-09: Architectural Redesign
547
664
 
548
665
  The git gem is undergoing a significant architectural redesign for the upcoming
@@ -594,11 +711,12 @@ rake rubocop
594
711
  RuboCop is also run as part of the default rake task (by running `rake`) that is run
595
712
  in our Continuous Integration workflow.
596
713
 
597
- Going forward, any PRs that have any Robocop offenses will not be merged. In
598
- certain rare cases, it might be acceptable to disable a RuboCop check for the most
599
- limited scope possible.
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.
600
717
 
601
- If you have a problem fixing a RuboCop offense, don't be afraid to ask a contributor.
718
+ If you have a problem fixing a RuboCop offense, don't be afraid to ask a
719
+ contributor.
602
720
 
603
721
  ### 2025-06-06: Default Branch Rename
604
722