git 1.19.1 → 4.4.5

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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.commitlintrc.yml +38 -0
  3. data/.github/copilot-instructions.md +2733 -0
  4. data/.github/pull_request_template.md +17 -0
  5. data/.github/workflows/continuous_integration.yml +92 -21
  6. data/.github/workflows/enforce_conventional_commits.yml +29 -0
  7. data/.github/workflows/experimental_continuous_integration.yml +59 -0
  8. data/.github/workflows/release.yml +53 -0
  9. data/.gitignore +5 -0
  10. data/.husky/commit-msg +1 -0
  11. data/.release-please-manifest.json +3 -0
  12. data/.rubocop.yml +55 -0
  13. data/.rubocop_todo.yml +12 -0
  14. data/.yardopts +4 -1
  15. data/AI_POLICY.md +24 -0
  16. data/CHANGELOG.md +501 -0
  17. data/CODE_OF_CONDUCT.md +25 -0
  18. data/CONTRIBUTING.md +323 -102
  19. data/GOVERNANCE.md +106 -0
  20. data/LICENSE +1 -1
  21. data/MAINTAINERS.md +17 -4
  22. data/README.md +575 -246
  23. data/Rakefile +13 -55
  24. data/git.gemspec +36 -30
  25. data/lib/git/args_builder.rb +111 -0
  26. data/lib/git/author.rb +9 -7
  27. data/lib/git/base.rb +602 -173
  28. data/lib/git/branch.rb +318 -38
  29. data/lib/git/branches.rb +21 -24
  30. data/lib/git/command_line.rb +330 -0
  31. data/lib/git/command_line_result.rb +9 -3
  32. data/lib/git/config.rb +10 -6
  33. data/lib/git/diff.rb +149 -81
  34. data/lib/git/diff_path_status.rb +46 -0
  35. data/lib/git/diff_stats.rb +59 -0
  36. data/lib/git/errors.rb +212 -0
  37. data/lib/git/escaped_path.rb +2 -2
  38. data/lib/git/fsck_object.rb +48 -0
  39. data/lib/git/fsck_result.rb +121 -0
  40. data/lib/git/index.rb +2 -1
  41. data/lib/git/lib.rb +1648 -643
  42. data/lib/git/log.rb +143 -106
  43. data/lib/git/object.rb +151 -125
  44. data/lib/git/path.rb +23 -16
  45. data/lib/git/remote.rb +5 -4
  46. data/lib/git/repository.rb +2 -2
  47. data/lib/git/stash.rb +11 -12
  48. data/lib/git/stashes.rb +16 -15
  49. data/lib/git/status.rb +104 -143
  50. data/lib/git/url.rb +3 -3
  51. data/lib/git/version.rb +3 -1
  52. data/lib/git/working_directory.rb +2 -0
  53. data/lib/git/worktree.rb +6 -5
  54. data/lib/git/worktrees.rb +6 -6
  55. data/lib/git.rb +131 -28
  56. data/package.json +10 -0
  57. data/redesign/1_architecture_existing.md +66 -0
  58. data/redesign/2_architecture_redesign.md +130 -0
  59. data/redesign/3_architecture_implementation.md +138 -0
  60. data/redesign/index.md +34 -0
  61. data/release-please-config.json +36 -0
  62. data/tasks/gem_tasks.rake +10 -0
  63. data/tasks/rubocop.rake +12 -0
  64. data/tasks/test.rake +13 -0
  65. data/tasks/test_gem.rake +12 -0
  66. data/tasks/yard.rake +23 -0
  67. metadata +114 -37
  68. data/.github/stale.yml +0 -25
  69. data/Dockerfile.changelog-rs +0 -12
  70. data/PULL_REQUEST_TEMPLATE.md +0 -9
  71. data/RELEASING.md +0 -70
  72. data/lib/git/base/factory.rb +0 -99
  73. data/lib/git/failed_error.rb +0 -53
  74. data/lib/git/git_execute_error.rb +0 -7
  75. data/lib/git/signaled_error.rb +0 -50
  76. /data/{ISSUE_TEMPLATE.md → .github/issue_template.md} +0 -0
data/README.md CHANGED
@@ -7,156 +7,219 @@
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=master)](https://github.com/ruby-git/ruby-git/actions?query=workflow%3ACI)
12
- [![Code Climate](https://codeclimate.com/github/ruby-git/ruby-git.png)](https://codeclimate.com/github/ruby-git/ruby-git)
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)
17
+
18
+ - [Summary](#summary)
19
+ - [Install](#install)
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)
26
+ - [Errors Raised By This Gem](#errors-raised-by-this-gem)
27
+ - [Specifying And Handling Timeouts](#specifying-and-handling-timeouts)
28
+ - [Deprecations](#deprecations)
29
+ - [Project Policies](#project-policies)
30
+ - [Ruby Version Support Policy](#ruby-version-support-policy)
31
+ - [Git Version Support Policy](#git-version-support-policy)
32
+ - [📢 Project Announcements 📢](#-project-announcements-)
33
+ - [2026-01-07: AI Policy Introduced](#2026-01-07-ai-policy-introduced)
34
+ - [2025-07-09: Architectural Redesign](#2025-07-09-architectural-redesign)
35
+ - [2025-07-07: We Now Use RuboCop](#2025-07-07-we-now-use-rubocop)
36
+ - [2025-06-06: Default Branch Rename](#2025-06-06-default-branch-rename)
37
+ - [2025-05-15: We've Switched to Conventional Commits](#2025-05-15-weve-switched-to-conventional-commits)
38
+
39
+ ## Summary
40
+
41
+ The [git gem](https://rubygems.org/gems/git) provides a Ruby interface to the `git`
42
+ command line.
13
43
 
14
- The [git gem](https://rubygems.org/gems/git) provides an API that can be used to
15
- create, read, and manipulate Git repositories by wrapping system calls to the `git`
16
- command line. The API can be used for working with Git in complex interactions
17
- including branching and merging, object inspection and manipulation, history, patch
18
- generation and more.
19
-
20
- ## v2.0.0 pre-release
21
-
22
- git 2.0.0 is available as a pre-release version for testing! Please give it a try.
44
+ Get started by obtaining a repository object by:
23
45
 
24
- **JRuby on Windows is not yet supported by the 2.x release line. Users running JRuby
25
- on Windows should continue to use the 1.x release line.**
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)
26
52
 
27
- The changes coming in this major release include:
53
+ Methods that can be called on a repository object are documented in
54
+ [Git::Base](https://rubydoc.info/gems/git/Git/Base)
28
55
 
29
- * Create a policy of supported Ruby versions to support only non-EOL Ruby versions
30
- * Create a policy of supported Git CLI versions (released 2020-12-25)
31
- * Update the required Ruby version to at least 3.0 (released 2020-07-27)
32
- * Update the required Git command line version to at least 2.28
33
- * Update how CLI commands are called to use the [process_executer](https://github.com/main-branch/process_executer)
34
- gem which is built on top of [Kernel.spawn](https://ruby-doc.org/3.3.0/Kernel.html#method-i-spawn).
35
- See [PR #617](https://github.com/ruby-git/ruby-git/pull/617) for more details
36
- on the motivation for this implementation.
56
+ ## Install
37
57
 
38
- The tentative plan is to release `2.0.0` near the end of March 2024 depending on
39
- the feedback received during the pre-release period.
58
+ Install the gem and add to the application's Gemfile by executing:
40
59
 
41
- The `master` branch will be used for `2.x` development. If needed, fixes for `1.x`
42
- version will be done on the `v1` branch.
60
+ ```shell
61
+ bundle add git
62
+ ```
43
63
 
44
- ## Homepage
64
+ to install version 1.x:
45
65
 
46
- The project source code is at:
66
+ ```shell
67
+ bundle add git --version "~> 1.19"
68
+ ```
47
69
 
48
- http://github.com/ruby-git/ruby-git
70
+ If bundler is not being used to manage dependencies, install the gem by executing:
49
71
 
50
- ## Documentation
72
+ ```shell
73
+ gem install git
74
+ ```
51
75
 
52
- Detailed documentation can be found at:
76
+ to install version 1.x:
53
77
 
54
- https://rubydoc.info/gems/git/Git.html
78
+ ```shell
79
+ gem install git --version "~> 1.19"
80
+ ```
55
81
 
56
- Get started by obtaining a repository object by:
82
+ ## Quick Start
57
83
 
58
- * opening an existing working copy with [Git.open](https://rubydoc.info/gems/git/Git#open-class_method)
59
- * initializing a new repository with [Git.init](https://rubydoc.info/gems/git/Git#init-class_method)
60
- * cloning a repository with [Git.clone](https://rubydoc.info/gems/git/Git#clone-class_method)
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`.
61
87
 
62
- Methods that can be called on a repository object are documented in [Git::Base](https://rubydoc.info/gems/git/Git/Base)
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`.
63
92
 
64
- ## Install
93
+ Clone, read status, and log:
65
94
 
66
- You can install Ruby/Git like this:
95
+ ```ruby
96
+ require 'git'
67
97
 
68
- ```
69
- sudo gem install git
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 }
70
101
  ```
71
102
 
72
- ## Code Status
103
+ Open an existing repo and commit:
73
104
 
105
+ ```ruby
106
+ require 'git'
74
107
 
75
- ## Major Objects
108
+ repo = Git.open('/path/to/repo')
109
+ repo.add(all: true)
110
+ repo.commit('chore: update files')
111
+ repo.push
112
+ ```
76
113
 
77
- **Git::Base** - The object returned from a `Git.open` or `Git.clone`. Most major actions are called from this object.
114
+ Initialize a new repo and make the first commit:
78
115
 
79
- **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.
116
+ ```ruby
117
+ require 'git'
80
118
 
81
- **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.
119
+ repo = Git.init('my_project')
120
+ repo.add(all: true)
121
+ repo.commit('initial commit')
122
+ ```
82
123
 
83
- **Git::Status** - returns from a `@git.status` command. It is an Enumerable that returns
84
- `Git:Status::StatusFile` objects for each object in git, which includes files in the working
85
- directory, in the index and in the repository. Similar to running 'git status' on the command line to determine untracked and changed files.
124
+ ## Examples
86
125
 
87
- **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.
126
+ Beyond the basics covered in Quick Start, these examples show the full range of
127
+ options and variations for each operation.
88
128
 
89
- **Git::Remote**- A reference to a remote repository that is tracked by this repository.
129
+ ### Configuration
90
130
 
91
- **Git::Log** - An Enumerable object that references all the `Git::Object::Commit` objects that encompass your log query, which can be constructed through methods on the `Git::Log object`,
92
- like:
131
+ Configure the `git` command line:
93
132
 
94
- `@git.log(20).object("some_file").since("2 weeks ago").between('v2.6', 'v2.7').each { |commit| [block] }`
133
+ ```ruby
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')
144
+ ```
95
145
 
96
- Pass the `--all` option to `git log` as follows:
146
+ Configure the git gem:
97
147
 
98
- `@git.log.all.each { |commit| [block] }`
148
+ ```ruby
149
+ Git.configure do |config|
150
+ config.binary_path = '/usr/local/bin/git'
151
+ config.git_ssh = 'ssh -i ~/.ssh/id_rsa'
152
+ end
99
153
 
100
- **Git::Worktrees** - Enumerable object that holds `Git::Worktree objects`.
154
+ # or
101
155
 
102
- ## Examples
156
+ Git.config.binary_path = '/usr/local/bin/git'
157
+ Git.config.git_ssh = 'ssh -i ~/.ssh/id_rsa'
158
+ ```
103
159
 
104
- Here are a bunch of examples of how to use the Ruby/Git package.
160
+ **How SSH configuration is determined:**
105
161
 
106
- Ruby < 1.9 will require rubygems to be loaded.
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).
107
168
 
108
- ```ruby
109
- require 'rubygems'
110
- ```
169
+ You can also specify a custom SSH script on a per-repository basis:
111
170
 
112
- Require the 'git' gem.
113
171
  ```ruby
114
- require 'git'
115
- ```
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')
116
174
 
117
- Git env config
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')
118
178
 
119
- ```ruby
120
- Git.configure do |config|
121
- # If you want to use a custom git binary
122
- config.binary_path = '/git/bin/path'
123
-
124
- # If you need to use a custom SSH script
125
- config.git_ssh = '/path/to/ssh/script'
126
- end
179
+ # Or when initializing
180
+ git = Git.init('new-repo', git_ssh: 'ssh -i /path/to/private_key')
127
181
  ```
128
182
 
129
- _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.
130
185
 
131
- Here are the operations that need read permission only.
186
+ ### Read Operations
187
+
188
+ Here are the operations that need read permission only:
132
189
 
133
190
  ```ruby
134
- g = Git.open(working_dir, :log => Logger.new(STDOUT))
191
+ repo = Git.open(working_dir, :log => Logger.new(STDOUT))
192
+
193
+ repo.index
194
+ repo.index.readable?
195
+ repo.index.writable?
196
+ repo.repo
197
+ repo.dir
135
198
 
136
- g.index
137
- g.index.readable?
138
- g.index.writable?
139
- g.repo
140
- g.dir
199
+ # ls-tree with recursion into subtrees (list files)
200
+ repo.ls_tree("HEAD", recursive: true)
141
201
 
142
- g.log # returns a Git::Log object, which is an Enumerator of Git::Commit objects
143
- g.log(200)
144
- g.log.since('2 weeks ago')
145
- g.log.between('v2.5', 'v2.6')
146
- g.log.each {|l| puts l.sha }
147
- g.gblob('v2.5:Makefile').log.since('2 weeks ago')
202
+ # log - returns a Git::Log object, which is an Enumerator of Git::Commit objects
203
+ # default configuration returns a max of 30 commits
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')
148
211
 
149
- g.object('HEAD^').to_s # git show / git rev-parse
150
- g.object('HEAD^').contents
151
- g.object('v2.5:Makefile').size
152
- 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
153
216
 
154
- g.gtree(treeish)
155
- g.gblob(treeish)
156
- g.gcommit(treeish)
217
+ repo.gtree(treeish)
218
+ repo.gblob(treeish)
219
+ repo.gcommit(treeish)
157
220
 
158
221
 
159
- commit = g.gcommit('1cc8667014381')
222
+ commit = repo.gcommit('1cc8667014381')
160
223
 
161
224
  commit.gtree
162
225
  commit.parent.sha
@@ -168,59 +231,74 @@ commit.committer.name
168
231
  commit.date.strftime("%m-%d-%y")
169
232
  commit.message
170
233
 
171
- tree = g.gtree("HEAD^{tree}")
234
+ tree = repo.gtree("HEAD^{tree}")
172
235
 
173
236
  tree.blobs
174
237
  tree.subtrees
175
238
  tree.children # blobs and subtrees
176
239
 
177
- g.revparse('v2.5:Makefile')
178
-
179
- g.branches # returns Git::Branch objects
180
- g.branches.local
181
- g.current_branch
182
- g.branches.remote
183
- g.branches[:master].gcommit
184
- g.branches['origin/master'].gcommit
185
-
186
- g.grep('hello') # implies HEAD
187
- g.blob('v2.5:Makefile').grep('hello')
188
- g.tag('v2.5').grep('hello', 'docs/')
189
- g.describe()
190
- g.describe('0djf2aa')
191
- g.describe('HEAD', {:all => true, :tags => true})
192
-
193
- g.diff(commit1, commit2).size
194
- g.diff(commit1, commit2).stats
195
- g.diff(commit1, commit2).name_status
196
- g.gtree('v2.5').diff('v2.6').insertions
197
- g.diff('gitsearch1', 'v2.5').path('lib/')
198
- g.diff('gitsearch1', @git.gtree('v2.5'))
199
- g.diff('gitsearch1', 'v2.5').path('docs/').patch
200
- g.gtree('v2.5').diff('v2.6').patch
201
-
202
- 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|
203
267
  puts file_diff.path
204
268
  puts file_diff.patch
205
269
  puts file_diff.blob(:src).contents
206
270
  end
207
271
 
208
- g.worktrees # returns Git::Worktree objects
209
- g.worktrees.count
210
- g.worktrees.each do |worktree|
272
+ repo.worktrees # returns Git::Worktree objects
273
+ repo.worktrees.count
274
+ repo.worktrees.each do |worktree|
211
275
  worktree.dir
212
276
  worktree.gcommit
213
277
  worktree.to_s
214
278
  end
215
279
 
216
- g.config('user.name') # returns 'Scott Chacon'
217
- 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
218
296
 
219
297
  # Configuration can be set when cloning using the :config option.
220
298
  # This option can be an single configuration String or an Array
221
299
  # if multiple config items need to be set.
222
300
  #
223
- g = Git.clone(
301
+ repo = Git.clone(
224
302
  git_uri, destination_path,
225
303
  :config => [
226
304
  'core.sshCommand=ssh -i /home/user/.ssh/id_rsa',
@@ -228,200 +306,451 @@ g = Git.clone(
228
306
  ]
229
307
  )
230
308
 
231
- g.tags # returns array of Git::Tag objects
309
+ repo.tags # returns array of Git::Tag objects
232
310
 
233
- g.show()
234
- g.show('HEAD')
235
- g.show('v2.8', 'README.md')
311
+ repo.show()
312
+ repo.show('HEAD')
313
+ repo.show('v2.8', 'README.md')
236
314
 
237
315
  Git.ls_remote('https://github.com/ruby-git/ruby-git.git') # returns a hash containing the available references of the repo.
238
316
  Git.ls_remote('/path/to/local/repo')
239
317
  Git.ls_remote() # same as Git.ls_remote('.')
240
318
 
241
- Git.default_branch('https://github.com/ruby-git/ruby-git') #=> 'master'
319
+ Git.default_branch('https://github.com/ruby-git/ruby-git') #=> 'main'
242
320
  ```
243
321
 
322
+ ### Write Operations
323
+
244
324
  And here are the operations that will need to write to your git repository.
245
325
 
246
326
  ```ruby
247
- g = Git.init
248
- Git.init('project')
249
- Git.init('/home/schacon/proj',
250
- { :repository => '/opt/git/proj.git',
251
- :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
+ )
252
333
 
253
334
  # Clone from a git url
254
335
  git_url = 'https://github.com/ruby-git/ruby-git.git'
255
- # Clone into the ruby-git directory
256
- g = Git.clone(git_url)
336
+ repo = Git.clone(git_url)
257
337
 
258
338
  # Clone into /tmp/clone/ruby-git-clean
259
339
  name = 'ruby-git-clean'
260
340
  path = '/tmp/clone'
261
- g = Git.clone(git_url, name, :path => path)
262
- g.dir #=> /tmp/clone/ruby-git-clean
341
+ repo = Git.clone(git_url, name, :path => path)
342
+ repo.dir #=> /tmp/clone/ruby-git-clean
263
343
 
264
- g.config('user.name', 'Scott Chacon')
265
- g.config('user.email', 'email@email.com')
344
+ repo.config('user.name', 'Scott Chacon')
345
+ repo.config('user.email', 'email@email.com')
266
346
 
267
347
  # Clone can take a filter to tell the serve to send a partial clone
268
- 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)
269
352
 
270
353
  # Clone can take an optional logger
271
- logger = Logger.new
272
- g = Git.clone(git_url, NAME, :log => logger)
354
+ logger = Logger.new(STDOUT)
355
+ repo = Git.clone(git_url, 'my-repo', :log => logger)
273
356
 
274
- g.add # git add -- "."
275
- g.add(:all=>true) # git add --all -- "."
276
- g.add('file_path') # git add -- "file_path"
277
- 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"
278
361
 
279
- g.remove() # git rm -f -- "."
280
- g.remove('file.txt') # git rm -f -- "file.txt"
281
- g.remove(['file.txt', 'file2.txt']) # git rm -f -- "file.txt" "file2.txt"
282
- g.remove('file.txt', :recursive => true) # git rm -f -r -- "file.txt"
283
- 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"
284
367
 
285
- g.commit('message')
286
- g.commit_all('message')
368
+ repo.commit('message')
369
+ repo.commit_all('message')
287
370
 
288
371
  # Sign a commit using the gpg key configured in the user.signingkey config setting
289
- g.config('user.signingkey', '0A46826A')
290
- g.commit('message', gpg_sign: true)
372
+ repo.config('user.signingkey', '0A46826A')
373
+ repo.commit('message', gpg_sign: true)
291
374
 
292
375
  # Sign a commit using a specified gpg key
293
376
  key_id = '0A46826A'
294
- g.commit('message', gpg_sign: key_id)
377
+ repo.commit('message', gpg_sign: key_id)
295
378
 
296
379
  # Skip signing a commit (overriding any global gpgsign setting)
297
- g.commit('message', no_gpg_sign: true)
380
+ repo.commit('message', no_gpg_sign: true)
298
381
 
299
- g = Git.clone(repo, 'myrepo')
300
- g.chdir do
301
- new_file('test-file', 'blahblahblah')
302
- g.status.changed.each do |file|
303
- puts file.blob(:index).contents
304
- 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
305
388
  end
306
389
 
307
- g.reset # defaults to HEAD
308
- g.reset_hard(Git::Commit)
390
+ repo.reset # defaults to HEAD
391
+ repo.reset_hard(Git::Commit)
309
392
 
310
- g.branch('new_branch') # creates new or fetches existing
311
- g.branch('new_branch').checkout
312
- g.branch('new_branch').delete
313
- g.branch('existing_branch').checkout
314
- g.branch('master').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')
315
398
 
316
399
  # delete remote branch
317
- g.push('origin', 'remote_branch_name', force: true, delete: true)
400
+ repo.push('origin', 'remote_branch_name', force: true, delete: true)
318
401
 
319
- g.checkout('new_branch')
320
- g.checkout('new_branch', new_branch: true, start_point: 'master')
321
- 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'))
322
405
 
323
- g.branch(name).merge(branch2)
324
- g.branch(branch2).merge # merges HEAD with branch2
406
+ repo.branch(name).merge(branch2)
407
+ repo.branch(branch2).merge # merges HEAD with branch2
325
408
 
326
- g.branch(name).in_branch(message) { # add files } # auto-commits
327
- g.merge('new_branch')
328
- g.merge('new_branch', 'merge commit message', no_ff: true)
329
- g.merge('origin/remote_branch')
330
- g.merge(g.branch('master'))
331
- 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])
332
415
 
333
- g.merge_base('branch1', 'branch2')
416
+ repo.merge_base('branch1', 'branch2')
334
417
 
335
- r = g.add_remote(name, uri) # Git::Remote
336
- 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
337
420
 
338
- g.remotes # array of Git::Remotes
339
- g.remote(name).fetch
340
- g.remote(name).remove
341
- g.remote(name).merge
342
- 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)
343
426
 
344
- g.fetch
345
- g.fetch(g.remotes.first)
346
- g.fetch('origin', {:ref => 'some/ref/head'} )
347
- g.fetch(all: true, force: true, depth: 2)
348
- 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
349
429
 
350
- g.pull
351
- 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})
352
435
 
353
- g.add_tag('tag_name') # returns Git::Tag
354
- g.add_tag('tag_name', 'object_reference')
355
- g.add_tag('tag_name', 'object_reference', {:options => 'here'})
356
- g.add_tag('tag_name', {:options => 'here'})
436
+ repo.pull
437
+ repo.pull(Git::Repo, Git::Branch) # fetch and a merge
357
438
 
358
- Options:
359
- :a | :annotate
360
- :d
361
- :f
362
- :m | :message
363
- :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'})
364
443
 
365
- g.delete_tag('tag_name')
444
+ repo.delete_tag('tag_name')
366
445
 
367
- g.repack
446
+ repo.repack
368
447
 
369
- g.push
370
- g.push(g.remote('name'))
448
+ repo.push
449
+ repo.push(repo.remote('name'))
371
450
 
372
451
  # delete remote branch
373
- g.push('origin', 'remote_branch_name', force: true, delete: true)
452
+ repo.push('origin', 'remote_branch_name', force: true, delete: true)
374
453
 
375
454
  # push all branches to remote at one time
376
- g.push('origin', all: true)
455
+ repo.push('origin', all: true)
377
456
 
378
- g.worktree('/tmp/new_worktree').add
379
- g.worktree('/tmp/new_worktree', 'branch1').add
380
- g.worktree('/tmp/new_worktree').remove
381
- 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
382
461
  ```
383
462
 
463
+ ### Index and Tree Operations
464
+
384
465
  Some examples of more low-level index and tree operations
385
466
 
386
467
  ```ruby
387
- g.with_temp_index do
468
+ repo.with_temp_index do
388
469
 
389
- g.read_tree(tree3) # calls self.index.read_tree
390
- g.read_tree(tree1, :prefix => 'hi/')
470
+ repo.read_tree(tree3) # calls self.index.read_tree
471
+ repo.read_tree(tree1, :prefix => 'hi/')
391
472
 
392
- c = g.commit_tree('message')
473
+ c = repo.commit_tree('message')
393
474
  # or #
394
- t = g.write_tree
395
- 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])
396
477
 
397
- g.branch('branch_name').update_ref(c)
398
- g.update_ref(branch, c)
478
+ repo.branch('branch_name').update_ref(c)
479
+ repo.update_ref(branch, c)
399
480
 
400
- g.with_temp_working do # new blank working directory
401
- g.checkout
402
- g.checkout(another_index)
403
- 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
404
485
  end
405
486
  end
406
487
 
407
- g.set_index('/path/to/index')
488
+ repo.set_index('/path/to/index')
408
489
 
409
-
410
- g.with_index(path) do
490
+ repo.with_index(path) do
411
491
  # calls set_index, then switches back after
412
492
  end
413
493
 
414
- g.with_working(dir) do
494
+ repo.with_working(dir) do
415
495
  # calls set_working, then switches back after
416
496
  end
417
497
 
418
- g.with_temp_working(dir) do
419
- g.checkout_index(:prefix => dir, :path_limiter => path)
498
+ repo.with_temp_working(dir) do
499
+ repo.checkout_index(:prefix => dir, :path_limiter => path)
420
500
  # do file work
421
- g.commit # commits to index
501
+ repo.commit # commits to index
502
+ end
503
+ ```
504
+
505
+ ## Errors Raised By This Gem
506
+
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.
509
+
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.
512
+
513
+ ```ruby
514
+ begin
515
+ # some git operation
516
+ rescue Git::Error => e
517
+ puts "An error occurred: #{e.message}"
422
518
  end
423
519
  ```
424
520
 
425
- ## License
521
+ See [`Git::Error`](https://rubydoc.info/gems/git/Git/Error) for more information.
522
+
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
+ Or by setting this environment variable before loading the gem:
593
+
594
+ ```sh
595
+ GIT_DEPRECATION_BEHAVIOR=silence
596
+ ```
597
+
598
+ Accepted environment variable values are the behavior names supported by your
599
+ installed ActiveSupport version.
600
+
601
+ If `GIT_DEPRECATION_BEHAVIOR` is set to an unsupported value, loading the gem
602
+ raises `ArgumentError` with the accepted behavior names.
603
+
604
+ See [the Active Support Deprecation
605
+ documentation](https://api.rubyonrails.org/classes/ActiveSupport/Deprecation.html)
606
+ for more details.
607
+
608
+ If deprecation warnings are silenced, you should reenable them before upgrading the
609
+ git gem to the next major version. This will make it easier to identify changes
610
+ needed for the upgrade.
611
+
612
+ ## Project Policies
613
+
614
+ These documents set expectations for behavior, contribution workflows, AI-assisted
615
+ changes, decision making, maintainer roles, and licensing. Please review them before
616
+ opening issues or pull requests.
617
+
618
+ | Document | Description |
619
+ | -------- | ----------- |
620
+ | [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. |
621
+ | [CONTRIBUTING](CONTRIBUTING.md) | How to report issues, submit PRs with Conventional Commits, meet coding/testing standards, and follow the Code of Conduct. |
622
+ | [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. |
623
+ | [Ruby version support policy](#ruby-version-support-policy) | Supported Ruby runtimes and platforms; bump decisions and CI coverage expectations. |
624
+ | [Git version support policy](#git-version-support-policy) | Minimum supported git version and how version bumps are communicated and enforced. |
625
+ | [GOVERNANCE](GOVERNANCE.md) | Principles-first governance defining maintainer/project lead roles, least-privilege access, consensus/majority decisions, and nomination/emeritus steps. |
626
+ | [MAINTAINERS](MAINTAINERS.md) | Lists active maintainers (Project Lead noted) and emeritus alumni with links; see governance for role scope. |
627
+ | [LICENSE](LICENSE) | MIT License terms for using, modifying, and redistributing this project. |
628
+
629
+ ### Ruby Version Support Policy
630
+
631
+ This gem is expected to function correctly on:
632
+
633
+ - All [non-EOL versions](https://www.ruby-lang.org/en/downloads/branches/) of the MRI
634
+ Ruby on Mac, Linux, and Windows
635
+ - The latest version of JRuby 9.4+ on Linux
636
+ - The latest version of TruffleRuby 24+ on Linux
637
+
638
+ It is this project's intent to support the latest version of JRuby on Windows once
639
+ the [process_executer](https://github.com/main-branch/process_executer) gem properly
640
+ supports subprocess status reporting on JRuby for Windows (see
641
+ [main-branch/process_executer#156](https://github.com/main-branch/process_executer/issues/156)).
642
+
643
+ ### Git Version Support Policy
644
+
645
+ This gem requires git version 2.28.0 or greater as specified in the gemspec. This
646
+ requirement reflects:
647
+
648
+ - The minimum git version necessary to support all features provided by this gem
649
+ - A reasonable balance between supporting older systems and leveraging modern git
650
+ capabilities
651
+ - The practical limitations of testing across multiple git versions in CI
652
+
653
+ Git 2.28.0 was released on July 27, 2020. While this gem may work with earlier
654
+ versions of git, compatibility with versions prior to 2.28.0 is not tested or
655
+ guaranteed. Users on older git versions should upgrade to at least 2.28.0.
656
+
657
+ The supported git version may be increased in future major or minor releases of this
658
+ gem as new git features are adopted or as maintaining backward compatibility becomes
659
+ impractical. Such changes will be clearly documented in the CHANGELOG and release
660
+ notes.
661
+
662
+ ## 📢 Project Announcements 📢
663
+
664
+ ### 2026-01-07: AI Policy Introduced
665
+
666
+ We have adopted a formal [AI Policy](AI_POLICY.md) to clarify expectations for
667
+ AI-assisted contributions. Please review it before opening a PR to ensure your
668
+ changes are fully understood, meet our quality bar, and respect licensing
669
+ requirements.
670
+
671
+ We chose a principles-based policy to respect contributors’ time and expertise. It’s
672
+ quick to read, easy to remember, and avoids unnecessary policy overhead while still
673
+ setting clear expectations.
674
+
675
+ ### 2025-07-09: Architectural Redesign
676
+
677
+ The git gem is undergoing a significant architectural redesign for the upcoming
678
+ v5.0.0 release. The current architecture has several design challenges that make it
679
+ difficult to maintain and evolve. This redesign aims to address these issues by
680
+ introducing a clearer, more robust, and more testable structure.
681
+
682
+ We have prepared detailed documents outlining the analysis of the current
683
+ architecture and the proposed changes. We encourage our community and contributors to
684
+ review them:
685
+
686
+ 1. [Analysis of the Current Architecture](redesign/1_architecture_existing.md): A
687
+ breakdown of the existing design and its challenges.
688
+ 2. [The Proposed Redesign](redesign/2_architecture_redesign.md): An overview of the
689
+ new three-layered architecture.
690
+ 3. [Implementation Plan](redesign/3_architecture_implementation.md): The step-by-step
691
+ plan for implementing the redesign.
692
+
693
+ Your feedback is welcome! Please feel free to open an issue to discuss the proposed
694
+ changes.
695
+
696
+ > **DON'T PANIC!**
697
+ >
698
+ > While this is a major internal refactoring, our goal is to keep the primary public
699
+ API on the main repository object as stable as possible. Most users who rely on
700
+ documented methods like `g.commit`, `g.add`, and `g.status` should find the
701
+ transition to v5.0.0 straightforward.
702
+ >
703
+ > The breaking changes will primarily affect users who have been relying on the
704
+ internal g.lib accessor, which will be removed as part of this cleanup. For more
705
+ details, please see the "Impact on Users" section in [the redesign
706
+ document](redesign/2_architecture_redesign.md).
707
+
708
+ ### 2025-07-07: We Now Use RuboCop
709
+
710
+ To improve code consistency and maintainability, the `ruby-git` project has now
711
+ adopted [RuboCop](https://rubocop.org/) as our static code analyzer and formatter.
712
+
713
+ This integration is a key part of our ongoing commitment to making `ruby-git` a
714
+ high-quality, stable, and easy-to-contribute-to project. All new contributions will
715
+ be expected to adhere to the style guidelines enforced by our RuboCop configuration.
716
+
717
+ RuboCop can be run from the project's Rakefile:
718
+
719
+ ```shell
720
+ rake rubocop
721
+ ```
722
+
723
+ RuboCop is also run as part of the default rake task (by running `rake`) that is run
724
+ in our Continuous Integration workflow.
725
+
726
+ Going forward, any PRs that have any Robocop offenses will not be merged. In certain
727
+ rare cases, it might be acceptable to disable a RuboCop check for the most limited
728
+ scope possible.
729
+
730
+ If you have a problem fixing a RuboCop offense, don't be afraid to ask a
731
+ contributor.
732
+
733
+ ### 2025-06-06: Default Branch Rename
734
+
735
+ On June 6th, 2025, the default branch was renamed from 'master' to 'main'.
736
+
737
+ Instructions for renaming your local or forked branch to match can be found in the
738
+ gist [Default Branch Name
739
+ Change](https://gist.github.com/jcouball/580a10e395f7fdfaaa4297bbe816cc7d).
740
+
741
+ ### 2025-05-15: We've Switched to Conventional Commits
742
+
743
+ To enhance our development workflow, enable automated changelog generation, and pave
744
+ the way for Continuous Delivery, the `ruby-git` project has adopted the [Conventional
745
+ Commits standard](https://www.conventionalcommits.org/en/v1.0.0/) for all commit
746
+ messages.
747
+
748
+ Going forward, all commits to this repository **MUST** adhere to the Conventional
749
+ Commits standard. Commits not adhering to this standard will cause the CI build to
750
+ fail. PRs will not be merged if they include non-conventional commits.
751
+
752
+ A git pre-commit hook may be installed to validate your conventional commit messages
753
+ before pushing them to GitHub by running `bin/setup` in the project root.
426
754
 
427
- licensed under MIT License Copyright (c) 2008 Scott Chacon. See LICENSE for further details.
755
+ Read more about this change in the [Commit Message Guidelines section of
756
+ CONTRIBUTING.md](CONTRIBUTING.md#commit-message-guidelines)