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.
- checksums.yaml +4 -4
- data/.commitlintrc.yml +38 -0
- data/.github/copilot-instructions.md +2733 -0
- data/.github/pull_request_template.md +17 -0
- data/.github/workflows/continuous_integration.yml +92 -21
- data/.github/workflows/enforce_conventional_commits.yml +29 -0
- data/.github/workflows/experimental_continuous_integration.yml +59 -0
- data/.github/workflows/release.yml +53 -0
- data/.gitignore +5 -0
- data/.husky/commit-msg +1 -0
- data/.release-please-manifest.json +3 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +12 -0
- data/.yardopts +4 -1
- data/AI_POLICY.md +24 -0
- data/CHANGELOG.md +501 -0
- data/CODE_OF_CONDUCT.md +25 -0
- data/CONTRIBUTING.md +323 -102
- data/GOVERNANCE.md +106 -0
- data/LICENSE +1 -1
- data/MAINTAINERS.md +17 -4
- data/README.md +575 -246
- data/Rakefile +13 -55
- data/git.gemspec +36 -30
- data/lib/git/args_builder.rb +111 -0
- data/lib/git/author.rb +9 -7
- data/lib/git/base.rb +602 -173
- data/lib/git/branch.rb +318 -38
- data/lib/git/branches.rb +21 -24
- data/lib/git/command_line.rb +330 -0
- data/lib/git/command_line_result.rb +9 -3
- data/lib/git/config.rb +10 -6
- data/lib/git/diff.rb +149 -81
- data/lib/git/diff_path_status.rb +46 -0
- data/lib/git/diff_stats.rb +59 -0
- data/lib/git/errors.rb +212 -0
- data/lib/git/escaped_path.rb +2 -2
- data/lib/git/fsck_object.rb +48 -0
- data/lib/git/fsck_result.rb +121 -0
- data/lib/git/index.rb +2 -1
- data/lib/git/lib.rb +1648 -643
- data/lib/git/log.rb +143 -106
- data/lib/git/object.rb +151 -125
- data/lib/git/path.rb +23 -16
- data/lib/git/remote.rb +5 -4
- data/lib/git/repository.rb +2 -2
- data/lib/git/stash.rb +11 -12
- data/lib/git/stashes.rb +16 -15
- data/lib/git/status.rb +104 -143
- data/lib/git/url.rb +3 -3
- data/lib/git/version.rb +3 -1
- data/lib/git/working_directory.rb +2 -0
- data/lib/git/worktree.rb +6 -5
- data/lib/git/worktrees.rb +6 -6
- data/lib/git.rb +131 -28
- data/package.json +10 -0
- data/redesign/1_architecture_existing.md +66 -0
- data/redesign/2_architecture_redesign.md +130 -0
- data/redesign/3_architecture_implementation.md +138 -0
- data/redesign/index.md +34 -0
- data/release-please-config.json +36 -0
- data/tasks/gem_tasks.rake +10 -0
- data/tasks/rubocop.rake +12 -0
- data/tasks/test.rake +13 -0
- data/tasks/test_gem.rake +12 -0
- data/tasks/yard.rake +23 -0
- metadata +114 -37
- data/.github/stale.yml +0 -25
- data/Dockerfile.changelog-rs +0 -12
- data/PULL_REQUEST_TEMPLATE.md +0 -9
- data/RELEASING.md +0 -70
- data/lib/git/base/factory.rb +0 -99
- data/lib/git/failed_error.rb +0 -53
- data/lib/git/git_execute_error.rb +0 -7
- data/lib/git/signaled_error.rb +0 -50
- /data/{ISSUE_TEMPLATE.md → .github/issue_template.md} +0 -0
data/lib/git/base.rb
CHANGED
|
@@ -1,53 +1,76 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'logger'
|
|
3
|
-
require 'open3'
|
|
4
4
|
|
|
5
5
|
module Git
|
|
6
|
-
#
|
|
6
|
+
# The main public interface for interacting with Git commands
|
|
7
7
|
#
|
|
8
8
|
# Instead of creating a Git::Base directly, obtain a Git::Base instance by
|
|
9
9
|
# calling one of the follow {Git} class methods: {Git.open}, {Git.init},
|
|
10
10
|
# {Git.clone}, or {Git.bare}.
|
|
11
11
|
#
|
|
12
|
+
# @api public
|
|
13
|
+
#
|
|
12
14
|
class Base
|
|
13
|
-
include Git::Base::Factory
|
|
14
|
-
|
|
15
15
|
# (see Git.bare)
|
|
16
16
|
def self.bare(git_dir, options = {})
|
|
17
17
|
normalize_paths(options, default_repository: git_dir, bare: true)
|
|
18
|
-
|
|
18
|
+
new(options)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# (see Git.clone)
|
|
22
22
|
def self.clone(repository_url, directory, options = {})
|
|
23
|
-
|
|
23
|
+
lib_options = {}
|
|
24
|
+
lib_options[:git_ssh] = options[:git_ssh] if options.key?(:git_ssh)
|
|
25
|
+
new_options = LibImpl.new(lib_options, options[:log]).clone(repository_url, directory, options)
|
|
24
26
|
normalize_paths(new_options, bare: options[:bare] || options[:mirror])
|
|
25
27
|
new(new_options)
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
# (see Git.default_branch)
|
|
29
31
|
def self.repository_default_branch(repository, options = {})
|
|
30
|
-
|
|
32
|
+
LibImpl.new(nil, options[:log]).repository_default_branch(repository)
|
|
31
33
|
end
|
|
32
34
|
|
|
33
35
|
# Returns (and initialize if needed) a Git::Config instance
|
|
34
36
|
#
|
|
35
37
|
# @return [Git::Config] the current config instance.
|
|
36
38
|
def self.config
|
|
37
|
-
|
|
39
|
+
@config ||= Config.new
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.binary_version(binary_path)
|
|
43
|
+
parse_version_string(execute_git_version(binary_path))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private_class_method def self.execute_git_version(binary_path)
|
|
47
|
+
bootstrap_command_line(binary_path).run('version', merge: true).stdout
|
|
48
|
+
rescue Git::CommandLineError => e
|
|
49
|
+
raise "Failed to get git version: #{e.result.status}\n#{e.result.stdout}"
|
|
50
|
+
rescue Errno::ENOENT, ProcessExecuter::SpawnError
|
|
51
|
+
raise "Failed to get git version: #{binary_path} not found"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private_class_method def self.parse_version_string(raw_string)
|
|
55
|
+
version_match = raw_string.match(/\d+(\.\d+)+/)
|
|
56
|
+
return [0, 0, 0] unless version_match
|
|
57
|
+
|
|
58
|
+
version_parts = version_match[0].split('.').map(&:to_i)
|
|
59
|
+
version_parts.fill(0, version_parts.length...3)
|
|
38
60
|
end
|
|
39
61
|
|
|
40
62
|
# (see Git.init)
|
|
41
63
|
def self.init(directory = '.', options = {})
|
|
42
|
-
normalize_paths(options, default_working_directory: directory, default_repository: directory,
|
|
64
|
+
normalize_paths(options, default_working_directory: directory, default_repository: directory,
|
|
65
|
+
bare: options[:bare])
|
|
43
66
|
|
|
44
67
|
init_options = {
|
|
45
|
-
:
|
|
46
|
-
:
|
|
68
|
+
bare: options[:bare],
|
|
69
|
+
initial_branch: options[:initial_branch]
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
directory = options[:bare] ? options[:repository] : options[:working_directory]
|
|
50
|
-
FileUtils.mkdir_p(directory)
|
|
73
|
+
FileUtils.mkdir_p(directory)
|
|
51
74
|
|
|
52
75
|
# TODO: this dance seems awkward: this creates a Git::Lib so we can call
|
|
53
76
|
# init so we can create a new Git::Base which in turn (ultimately)
|
|
@@ -59,21 +82,53 @@ module Git
|
|
|
59
82
|
# repository you have a Git::Base instance for. This would not
|
|
60
83
|
# change the existing interface (other than adding to it).
|
|
61
84
|
#
|
|
62
|
-
|
|
85
|
+
LibImpl.new(options).init(init_options)
|
|
63
86
|
|
|
64
|
-
|
|
87
|
+
new(options)
|
|
65
88
|
end
|
|
66
89
|
|
|
67
90
|
def self.root_of_worktree(working_dir)
|
|
68
|
-
|
|
69
|
-
|
|
91
|
+
raise ArgumentError, "'#{working_dir}' does not exist" unless Dir.exist?(working_dir)
|
|
92
|
+
|
|
93
|
+
execute_rev_parse_toplevel(working_dir)
|
|
94
|
+
end
|
|
70
95
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
96
|
+
private_class_method def self.execute_rev_parse_toplevel(working_dir)
|
|
97
|
+
bootstrap_command_line(Git::Base.config.binary_path).run(
|
|
98
|
+
'rev-parse', '--show-toplevel', chdir: File.expand_path(working_dir), merge: true, chomp: true
|
|
99
|
+
).stdout
|
|
100
|
+
rescue Git::CommandLineError
|
|
101
|
+
raise ArgumentError, "'#{working_dir}' is not in a git working tree"
|
|
102
|
+
rescue Errno::ENOENT, ProcessExecuter::SpawnError
|
|
103
|
+
raise ArgumentError, 'Failed to find the root of the worktree: git binary not found'
|
|
104
|
+
end
|
|
74
105
|
|
|
75
|
-
|
|
76
|
-
|
|
106
|
+
# A command line for the git commands that run before a repository is known
|
|
107
|
+
#
|
|
108
|
+
# `Open3` is deliberately not used here. Windows has no fork, so
|
|
109
|
+
# `Process.spawn` implements a redirect of the child's stdin by redirecting
|
|
110
|
+
# the *parent's* stdin and restoring it afterward, which replaces the
|
|
111
|
+
# process's stdin handle. That leaves a console REPL such as irb or pry
|
|
112
|
+
# unable to read input for the rest of the session. `Git::CommandLine`
|
|
113
|
+
# redirects only stdout and stderr, so stdin is never disturbed.
|
|
114
|
+
#
|
|
115
|
+
# @see https://github.com/ruby-git/ruby-git/issues/840 issue 840
|
|
116
|
+
#
|
|
117
|
+
# @param binary_path [String] the path to the git binary to run
|
|
118
|
+
#
|
|
119
|
+
# @return [Git::CommandLine] a command line that runs `binary_path` with
|
|
120
|
+
# `-c core.quotePath=true -c color.ui=false`
|
|
121
|
+
#
|
|
122
|
+
# Those are the options the `Open3` calls this replaced passed, kept as they were
|
|
123
|
+
# so these two commands behave exactly as before. Neither `git version` nor
|
|
124
|
+
# `git rev-parse --show-toplevel` emits colored output, so the remaining `color.*`
|
|
125
|
+
# settings in {Git::Lib}'s `STATIC_GLOBAL_OPTS` are not needed here.
|
|
126
|
+
#
|
|
127
|
+
# @api private
|
|
128
|
+
#
|
|
129
|
+
private_class_method def self.bootstrap_command_line(binary_path)
|
|
130
|
+
Git::CommandLine.new({}, binary_path, ['-c', 'core.quotePath=true', '-c', 'color.ui=false'],
|
|
131
|
+
Logger.new(nil))
|
|
77
132
|
end
|
|
78
133
|
|
|
79
134
|
# (see Git.open)
|
|
@@ -84,7 +139,7 @@ module Git
|
|
|
84
139
|
|
|
85
140
|
normalize_paths(options, default_working_directory: working_dir)
|
|
86
141
|
|
|
87
|
-
|
|
142
|
+
new(options)
|
|
88
143
|
end
|
|
89
144
|
|
|
90
145
|
# Create an object that executes Git commands in the context of a working
|
|
@@ -106,20 +161,54 @@ module Git
|
|
|
106
161
|
# commands are logged at the `:info` level. Additional logging is done
|
|
107
162
|
# at the `:debug` level.
|
|
108
163
|
#
|
|
164
|
+
# @option options [String, nil] :git_ssh Path to a custom SSH executable or script.
|
|
165
|
+
# Controls how SSH is configured for this {Git::Base} instance:
|
|
166
|
+
# - If this option is not provided, the global Git::Base.config.git_ssh setting is used.
|
|
167
|
+
# - If this option is explicitly set to nil, SSH is disabled for this instance.
|
|
168
|
+
# - If this option is a non-empty String, that value is used as the SSH command for
|
|
169
|
+
# this instance, overriding the global Git::Base.config.git_ssh setting.
|
|
170
|
+
#
|
|
109
171
|
# @return [Git::Base] an object that can execute git commands in the context
|
|
110
172
|
# of the opened working copy or bare repository
|
|
111
173
|
#
|
|
112
174
|
def initialize(options = {})
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
@logger.info("Starting Git")
|
|
175
|
+
options = default_paths(options)
|
|
176
|
+
setup_logger(options[:log])
|
|
177
|
+
@git_ssh = options.key?(:git_ssh) ? options[:git_ssh] : :use_global_config
|
|
178
|
+
initialize_components(options)
|
|
179
|
+
end
|
|
119
180
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
181
|
+
# Update the index from the current worktree to prepare the for the next commit
|
|
182
|
+
#
|
|
183
|
+
# @example
|
|
184
|
+
# lib.add('path/to/file')
|
|
185
|
+
# lib.add(['path/to/file1','path/to/file2'])
|
|
186
|
+
# lib.add(all: true)
|
|
187
|
+
#
|
|
188
|
+
# @param [String, Array<String>] paths a file or files to be added to the repository (relative to the worktree root)
|
|
189
|
+
# @param [Hash] options
|
|
190
|
+
#
|
|
191
|
+
# @option options [Boolean] :all Add, modify, and remove index entries to match the worktree
|
|
192
|
+
# @option options [Boolean] :force Allow adding otherwise ignored files
|
|
193
|
+
#
|
|
194
|
+
def add(paths = '.', **options)
|
|
195
|
+
lib.add(paths, options)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# adds a new remote to this repository
|
|
199
|
+
# url can be a git url or a Git::Base object if it's a local reference
|
|
200
|
+
#
|
|
201
|
+
# @git.add_remote('scotts_git', 'git://repo.or.cz/rubygit.git')
|
|
202
|
+
# @git.fetch('scotts_git')
|
|
203
|
+
# @git.merge('scotts_git/master')
|
|
204
|
+
#
|
|
205
|
+
# Options:
|
|
206
|
+
# :fetch => true
|
|
207
|
+
# :track => <branch_name>
|
|
208
|
+
def add_remote(name, url, opts = {})
|
|
209
|
+
url = url.repo.to_s if url.is_a?(Git::Base)
|
|
210
|
+
lib.remote_add(name, url, opts)
|
|
211
|
+
Git::Remote.new(self, name)
|
|
123
212
|
end
|
|
124
213
|
|
|
125
214
|
# changes current working directory for a block
|
|
@@ -132,16 +221,16 @@ module Git
|
|
|
132
221
|
# @git.commit('message')
|
|
133
222
|
# end
|
|
134
223
|
def chdir # :yields: the Git::Path
|
|
135
|
-
Dir.chdir(dir.
|
|
136
|
-
yield dir.
|
|
224
|
+
Dir.chdir(dir.to_s) do
|
|
225
|
+
yield dir.to_s
|
|
137
226
|
end
|
|
138
227
|
end
|
|
139
228
|
|
|
140
|
-
#g.config('user.name', 'Scott Chacon') # sets value
|
|
141
|
-
#g.config('user.email', 'email@email.com') # sets value
|
|
142
|
-
#g.config('user.email', 'email@email.com', file: 'path/to/custom/config) # sets value in file
|
|
143
|
-
#g.config('user.name') # returns 'Scott Chacon'
|
|
144
|
-
#g.config # returns whole config hash
|
|
229
|
+
# g.config('user.name', 'Scott Chacon') # sets value
|
|
230
|
+
# g.config('user.email', 'email@email.com') # sets value
|
|
231
|
+
# g.config('user.email', 'email@email.com', file: 'path/to/custom/config) # sets value in file
|
|
232
|
+
# g.config('user.name') # returns 'Scott Chacon'
|
|
233
|
+
# g.config # returns whole config hash
|
|
145
234
|
def config(name = nil, value = nil, options = {})
|
|
146
235
|
if name && value
|
|
147
236
|
# set value
|
|
@@ -163,9 +252,7 @@ module Git
|
|
|
163
252
|
end
|
|
164
253
|
|
|
165
254
|
# returns reference to the git index file
|
|
166
|
-
|
|
167
|
-
@index
|
|
168
|
-
end
|
|
255
|
+
attr_reader :index
|
|
169
256
|
|
|
170
257
|
# returns reference to the git repository directory
|
|
171
258
|
# @git.dir.path
|
|
@@ -175,50 +262,107 @@ module Git
|
|
|
175
262
|
|
|
176
263
|
# returns the repository size in bytes
|
|
177
264
|
def repo_size
|
|
178
|
-
Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
end.reduce(:+)
|
|
265
|
+
all_files = Dir.glob(File.join(repo.path, '**', '*'), File::FNM_DOTMATCH)
|
|
266
|
+
|
|
267
|
+
all_files.reject { |file| file.include?('..') }
|
|
268
|
+
.map { |file| File.expand_path(file) }
|
|
269
|
+
.uniq
|
|
270
|
+
.sum { |file| File.stat(file).size.to_i }
|
|
185
271
|
end
|
|
186
272
|
|
|
187
|
-
def set_index(index_file, check =
|
|
273
|
+
def set_index(index_file, check = nil, must_exist: nil)
|
|
274
|
+
unless check.nil?
|
|
275
|
+
Git::Deprecation.warn(
|
|
276
|
+
'The "check" argument is deprecated and will be removed in a future version. ' \
|
|
277
|
+
'Use "must_exist:" instead.'
|
|
278
|
+
)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# default is true
|
|
282
|
+
must_exist = must_exist.nil? && check.nil? ? true : must_exist | check
|
|
283
|
+
|
|
188
284
|
@lib = nil
|
|
189
|
-
@index = Git::Index.new(index_file.to_s,
|
|
285
|
+
@index = Git::Index.new(index_file.to_s, must_exist:)
|
|
190
286
|
end
|
|
191
287
|
|
|
192
|
-
def set_working(work_dir, check =
|
|
288
|
+
def set_working(work_dir, check = nil, must_exist: nil)
|
|
289
|
+
unless check.nil?
|
|
290
|
+
Git::Deprecation.warn(
|
|
291
|
+
'The "check" argument is deprecated and will be removed in a future version. ' \
|
|
292
|
+
'Use "must_exist:" instead.'
|
|
293
|
+
)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# default is true
|
|
297
|
+
must_exist = must_exist.nil? && check.nil? ? true : must_exist | check
|
|
298
|
+
|
|
193
299
|
@lib = nil
|
|
194
|
-
@working_directory = Git::WorkingDirectory.new(work_dir.to_s,
|
|
300
|
+
@working_directory = Git::WorkingDirectory.new(work_dir.to_s, must_exist:)
|
|
195
301
|
end
|
|
196
302
|
|
|
197
303
|
# returns +true+ if the branch exists locally
|
|
198
|
-
def
|
|
199
|
-
branch_names =
|
|
304
|
+
def local_branch?(branch)
|
|
305
|
+
branch_names = branches.local.map(&:name)
|
|
200
306
|
branch_names.include?(branch)
|
|
201
307
|
end
|
|
202
308
|
|
|
309
|
+
# @deprecated Use {#local_branch?} instead
|
|
310
|
+
def is_local_branch?(branch) # rubocop:disable Naming/PredicatePrefix
|
|
311
|
+
Git::Deprecation.warn(
|
|
312
|
+
'Git::Base#is_local_branch? is deprecated and will be removed in a future version. ' \
|
|
313
|
+
'Use Git::Base#local_branch? instead.'
|
|
314
|
+
)
|
|
315
|
+
local_branch?(branch)
|
|
316
|
+
end
|
|
317
|
+
|
|
203
318
|
# returns +true+ if the branch exists remotely
|
|
204
|
-
def
|
|
205
|
-
branch_names =
|
|
319
|
+
def remote_branch?(branch)
|
|
320
|
+
branch_names = branches.remote.map(&:name)
|
|
206
321
|
branch_names.include?(branch)
|
|
207
322
|
end
|
|
208
323
|
|
|
324
|
+
# @deprecated Use {#remote_branch?} instead
|
|
325
|
+
def is_remote_branch?(branch) # rubocop:disable Naming/PredicatePrefix
|
|
326
|
+
Git::Deprecation.warn(
|
|
327
|
+
'Git::Base#is_remote_branch? is deprecated and will be removed in a future version. ' \
|
|
328
|
+
'Use Git::Base#remote_branch? instead.'
|
|
329
|
+
)
|
|
330
|
+
remote_branch?(branch)
|
|
331
|
+
end
|
|
332
|
+
|
|
209
333
|
# returns +true+ if the branch exists
|
|
210
|
-
def
|
|
211
|
-
branch_names =
|
|
334
|
+
def branch?(branch)
|
|
335
|
+
branch_names = branches.map(&:name)
|
|
212
336
|
branch_names.include?(branch)
|
|
213
337
|
end
|
|
214
338
|
|
|
339
|
+
# @deprecated Use {#branch?} instead
|
|
340
|
+
def is_branch?(branch) # rubocop:disable Naming/PredicatePrefix
|
|
341
|
+
Git::Deprecation.warn(
|
|
342
|
+
'Git::Base#is_branch? is deprecated and will be removed in a future version. ' \
|
|
343
|
+
'Use Git::Base#branch? instead.'
|
|
344
|
+
)
|
|
345
|
+
branch?(branch)
|
|
346
|
+
end
|
|
347
|
+
|
|
215
348
|
# this is a convenience method for accessing the class that wraps all the
|
|
216
349
|
# actual 'git' forked system calls. At some point I hope to replace the Git::Lib
|
|
217
350
|
# class with one that uses native methods or libgit C bindings
|
|
218
351
|
def lib
|
|
219
|
-
@lib ||=
|
|
352
|
+
@lib ||= LibImpl.new(self, @logger)
|
|
220
353
|
end
|
|
221
354
|
|
|
355
|
+
# Returns the per-instance git_ssh configuration value.
|
|
356
|
+
#
|
|
357
|
+
# This may be:
|
|
358
|
+
# * a [String] path when an explicit git_ssh command has been configured
|
|
359
|
+
# * the Symbol `:use_global_config` when this instance is using the global config
|
|
360
|
+
# * `nil` when SSH has been explicitly disabled for this instance
|
|
361
|
+
#
|
|
362
|
+
# @return [String, Symbol, nil] the git_ssh configuration value for this instance
|
|
363
|
+
# @api private
|
|
364
|
+
attr_reader :git_ssh
|
|
365
|
+
|
|
222
366
|
# Run a grep for 'string' on the HEAD of the git repository
|
|
223
367
|
#
|
|
224
368
|
# @example Limit grep's scope by calling grep() from a specific object:
|
|
@@ -233,7 +377,8 @@ module Git
|
|
|
233
377
|
# end
|
|
234
378
|
#
|
|
235
379
|
# @param string [String] the string to search for
|
|
236
|
-
# @param path_limiter [String, Array] a path or array
|
|
380
|
+
# @param path_limiter [String, Pathname, Array<String, Pathname>] a path or array
|
|
381
|
+
# of paths to limit the search to or nil for no limit
|
|
237
382
|
# @param opts [Hash] options to pass to the underlying `git grep` command
|
|
238
383
|
#
|
|
239
384
|
# @option opts [Boolean] :ignore_case (false) ignore case when matching
|
|
@@ -251,48 +396,32 @@ module Git
|
|
|
251
396
|
# ```
|
|
252
397
|
#
|
|
253
398
|
def grep(string, path_limiter = nil, opts = {})
|
|
254
|
-
|
|
399
|
+
object('HEAD').grep(string, path_limiter, opts)
|
|
255
400
|
end
|
|
256
401
|
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
# @example
|
|
260
|
-
# git.add
|
|
261
|
-
# git.add('path/to/file')
|
|
262
|
-
# git.add(['path/to/file1','path/to/file2'])
|
|
263
|
-
# git.add(:all => true)
|
|
264
|
-
#
|
|
265
|
-
# options:
|
|
266
|
-
# :all => true
|
|
402
|
+
# List the files in the worktree that are ignored by git
|
|
403
|
+
# @return [Array<String>] the list of ignored files relative to teh root of the worktree
|
|
267
404
|
#
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# @option options [boolean] :all
|
|
271
|
-
# Update the index not only where the working tree has a file matching
|
|
272
|
-
# <pathspec> but also where the index already has an entry.
|
|
273
|
-
# See [the --all option to git-add](https://git-scm.com/docs/git-add#Documentation/git-add.txt--A)
|
|
274
|
-
# for more details.
|
|
275
|
-
#
|
|
276
|
-
def add(paths = '.', **options)
|
|
277
|
-
self.lib.add(paths, options)
|
|
405
|
+
def ignored_files
|
|
406
|
+
lib.ignored_files
|
|
278
407
|
end
|
|
279
408
|
|
|
280
409
|
# removes file(s) from the git repository
|
|
281
410
|
def rm(path = '.', opts = {})
|
|
282
|
-
|
|
411
|
+
lib.rm(path, opts)
|
|
283
412
|
end
|
|
284
413
|
|
|
285
414
|
alias remove rm
|
|
286
415
|
|
|
287
416
|
# resets the working directory to the provided commitish
|
|
288
417
|
def reset(commitish = nil, opts = {})
|
|
289
|
-
|
|
418
|
+
lib.reset(commitish, opts)
|
|
290
419
|
end
|
|
291
420
|
|
|
292
421
|
# resets the working directory to the commitish with '--hard'
|
|
293
422
|
def reset_hard(commitish = nil, opts = {})
|
|
294
|
-
opts = {:
|
|
295
|
-
|
|
423
|
+
opts = { hard: true }.merge(opts)
|
|
424
|
+
lib.reset(commitish, opts)
|
|
296
425
|
end
|
|
297
426
|
|
|
298
427
|
# cleans the working directory
|
|
@@ -303,7 +432,7 @@ module Git
|
|
|
303
432
|
# :ff
|
|
304
433
|
#
|
|
305
434
|
def clean(opts = {})
|
|
306
|
-
|
|
435
|
+
lib.clean(opts)
|
|
307
436
|
end
|
|
308
437
|
|
|
309
438
|
# returns the most recent tag that is reachable from a commit
|
|
@@ -321,8 +450,8 @@ module Git
|
|
|
321
450
|
# :always
|
|
322
451
|
# :match
|
|
323
452
|
#
|
|
324
|
-
def describe(committish=nil, opts={})
|
|
325
|
-
|
|
453
|
+
def describe(committish = nil, opts = {})
|
|
454
|
+
lib.describe(committish, opts)
|
|
326
455
|
end
|
|
327
456
|
|
|
328
457
|
# reverts the working directory to the provided commitish.
|
|
@@ -332,7 +461,7 @@ module Git
|
|
|
332
461
|
# :no_edit
|
|
333
462
|
#
|
|
334
463
|
def revert(commitish = nil, opts = {})
|
|
335
|
-
|
|
464
|
+
lib.revert(commitish, opts)
|
|
336
465
|
end
|
|
337
466
|
|
|
338
467
|
# commits all pending changes in the index file to the git repository
|
|
@@ -344,25 +473,25 @@ module Git
|
|
|
344
473
|
# :author
|
|
345
474
|
#
|
|
346
475
|
def commit(message, opts = {})
|
|
347
|
-
|
|
476
|
+
lib.commit(message, opts)
|
|
348
477
|
end
|
|
349
478
|
|
|
350
479
|
# commits all pending changes in the index file to the git repository,
|
|
351
480
|
# but automatically adds all modified files without having to explicitly
|
|
352
481
|
# calling @git.add() on them.
|
|
353
482
|
def commit_all(message, opts = {})
|
|
354
|
-
opts = {:
|
|
355
|
-
|
|
483
|
+
opts = { add_all: true }.merge(opts)
|
|
484
|
+
lib.commit(message, opts)
|
|
356
485
|
end
|
|
357
486
|
|
|
358
487
|
# checks out a branch as the new git working directory
|
|
359
|
-
def checkout(
|
|
360
|
-
|
|
488
|
+
def checkout(*, **)
|
|
489
|
+
lib.checkout(*, **)
|
|
361
490
|
end
|
|
362
491
|
|
|
363
492
|
# checks out an old version of a file
|
|
364
493
|
def checkout_file(version, file)
|
|
365
|
-
|
|
494
|
+
lib.checkout_file(version, file)
|
|
366
495
|
end
|
|
367
496
|
|
|
368
497
|
# fetches changes from a remote branch - this does not modify the working directory,
|
|
@@ -372,7 +501,7 @@ module Git
|
|
|
372
501
|
opts = remote
|
|
373
502
|
remote = nil
|
|
374
503
|
end
|
|
375
|
-
|
|
504
|
+
lib.fetch(remote, opts)
|
|
376
505
|
end
|
|
377
506
|
|
|
378
507
|
# Push changes to a remote repository
|
|
@@ -393,77 +522,111 @@ module Git
|
|
|
393
522
|
# @raise [Git::FailedError] if the push fails
|
|
394
523
|
# @raise [ArgumentError] if a branch is given without a remote
|
|
395
524
|
#
|
|
396
|
-
def push(
|
|
397
|
-
|
|
525
|
+
def push(*, **)
|
|
526
|
+
lib.push(*, **)
|
|
398
527
|
end
|
|
399
528
|
|
|
400
529
|
# merges one or more branches into the current working branch
|
|
401
530
|
#
|
|
402
531
|
# you can specify more than one branch to merge by passing an array of branches
|
|
403
532
|
def merge(branch, message = 'merge', opts = {})
|
|
404
|
-
|
|
533
|
+
lib.merge(branch, message, opts)
|
|
405
534
|
end
|
|
406
535
|
|
|
407
536
|
# iterates over the files which are unmerged
|
|
408
|
-
def each_conflict(&
|
|
409
|
-
|
|
537
|
+
def each_conflict(&) # :yields: file, your_version, their_version
|
|
538
|
+
lib.conflicts(&)
|
|
410
539
|
end
|
|
411
540
|
|
|
412
|
-
#
|
|
541
|
+
# Pulls the given branch from the given remote into the current branch
|
|
542
|
+
#
|
|
543
|
+
# @param remote [String] the remote repository to pull from
|
|
544
|
+
# @param branch [String] the branch to pull from
|
|
545
|
+
# @param opts [Hash] options to pass to the pull command
|
|
546
|
+
#
|
|
547
|
+
# @option opts [Boolean] :allow_unrelated_histories (false) Merges histories of
|
|
548
|
+
# two projects that started their lives independently
|
|
549
|
+
# @example pulls from origin/master
|
|
550
|
+
# @git.pull
|
|
551
|
+
# @example pulls from upstream/master
|
|
552
|
+
# @git.pull('upstream')
|
|
553
|
+
# @example pulls from upstream/develop
|
|
554
|
+
# @git.pull('upstream', 'develop')
|
|
413
555
|
#
|
|
414
|
-
#
|
|
415
|
-
# @git.pull('upstream') # pulls from upstream/master
|
|
416
|
-
# @git.pull('upstream', 'develope') # pulls from upstream/develop
|
|
556
|
+
# @return [Void]
|
|
417
557
|
#
|
|
418
|
-
|
|
419
|
-
|
|
558
|
+
# @raise [Git::FailedError] if the pull fails
|
|
559
|
+
# @raise [ArgumentError] if a branch is given without a remote
|
|
560
|
+
def pull(remote = nil, branch = nil, opts = {})
|
|
561
|
+
lib.pull(remote, branch, opts)
|
|
420
562
|
end
|
|
421
563
|
|
|
422
564
|
# returns an array of Git:Remote objects
|
|
423
565
|
def remotes
|
|
424
|
-
|
|
566
|
+
lib.remotes.map { |r| Git::Remote.new(self, r) }
|
|
425
567
|
end
|
|
426
568
|
|
|
427
|
-
#
|
|
569
|
+
# sets the url for a remote
|
|
428
570
|
# url can be a git url or a Git::Base object if it's a local reference
|
|
429
571
|
#
|
|
430
|
-
# @git.
|
|
431
|
-
# @git.fetch('scotts_git')
|
|
432
|
-
# @git.merge('scotts_git/master')
|
|
572
|
+
# @git.set_remote_url('scotts_git', 'git://repo.or.cz/rubygit.git')
|
|
433
573
|
#
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
def add_remote(name, url, opts = {})
|
|
438
|
-
url = url.repo.path if url.is_a?(Git::Base)
|
|
439
|
-
self.lib.remote_add(name, url, opts)
|
|
574
|
+
def set_remote_url(name, url)
|
|
575
|
+
url = url.repo.to_s if url.is_a?(Git::Base)
|
|
576
|
+
lib.remote_set_url(name, url)
|
|
440
577
|
Git::Remote.new(self, name)
|
|
441
578
|
end
|
|
442
579
|
|
|
443
|
-
#
|
|
444
|
-
# url can be a git url or a Git::Base object if it's a local reference
|
|
580
|
+
# Configures which branches are fetched for a remote
|
|
445
581
|
#
|
|
446
|
-
#
|
|
582
|
+
# Uses `git remote set-branches` to set or append fetch refspecs. When the `add:`
|
|
583
|
+
# option is not given, the `--add` option is not passed to the git command
|
|
447
584
|
#
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
585
|
+
# @example Replace fetched branches with a single glob pattern
|
|
586
|
+
# git = Git.open('/path/to/repo')
|
|
587
|
+
# # Only fetch branches matching "feature/*" from origin
|
|
588
|
+
# git.remote_set_branches('origin', 'feature/*')
|
|
589
|
+
#
|
|
590
|
+
# @example Append a glob pattern to existing fetched branches
|
|
591
|
+
# git = Git.open('/path/to/repo')
|
|
592
|
+
# # Keep existing fetch refspecs and add all release branches
|
|
593
|
+
# git.remote_set_branches('origin', 'release/*', add: true)
|
|
594
|
+
#
|
|
595
|
+
# @example Configure multiple explicit branches
|
|
596
|
+
# git = Git.open('/path/to/repo')
|
|
597
|
+
# git.remote_set_branches('origin', 'main', 'development', 'hotfix')
|
|
598
|
+
#
|
|
599
|
+
# @param name [String] the remote name (for example, "origin")
|
|
600
|
+
# @param branches [Array<String>] branch names or globs (for example, '*')
|
|
601
|
+
# @param add [Boolean] when true, append to existing refspecs instead of replacing them
|
|
602
|
+
#
|
|
603
|
+
# @return [nil]
|
|
604
|
+
#
|
|
605
|
+
# @raise [ArgumentError] if no branches are provided @raise [Git::FailedError] if
|
|
606
|
+
# the underlying git command fails
|
|
607
|
+
#
|
|
608
|
+
def remote_set_branches(name, *branches, add: false)
|
|
609
|
+
branch_list = branches.flatten
|
|
610
|
+
raise ArgumentError, 'branches are required' if branch_list.empty?
|
|
611
|
+
|
|
612
|
+
lib.remote_set_branches(name, branch_list, add: add)
|
|
613
|
+
|
|
614
|
+
nil
|
|
452
615
|
end
|
|
453
616
|
|
|
454
617
|
# removes a remote from this repository
|
|
455
618
|
#
|
|
456
619
|
# @git.remove_remote('scott_git')
|
|
457
620
|
def remove_remote(name)
|
|
458
|
-
|
|
621
|
+
lib.remote_remove(name)
|
|
459
622
|
end
|
|
460
623
|
|
|
461
624
|
# returns an array of all Git::Tag objects for this repository
|
|
462
625
|
def tags
|
|
463
|
-
|
|
626
|
+
lib.tags.map { |r| tag(r) }
|
|
464
627
|
end
|
|
465
628
|
|
|
466
|
-
#
|
|
629
|
+
# Create a new git tag
|
|
467
630
|
#
|
|
468
631
|
# @example
|
|
469
632
|
# repo.add_tag('tag_name', object_reference)
|
|
@@ -482,37 +645,86 @@ module Git
|
|
|
482
645
|
# @option options [boolean] :s Make a GPG-signed tag.
|
|
483
646
|
#
|
|
484
647
|
def add_tag(name, *options)
|
|
485
|
-
|
|
486
|
-
|
|
648
|
+
lib.tag(name, *options)
|
|
649
|
+
tag(name)
|
|
487
650
|
end
|
|
488
651
|
|
|
489
652
|
# deletes a tag
|
|
490
653
|
def delete_tag(name)
|
|
491
|
-
|
|
654
|
+
lib.tag(name, { d: true })
|
|
492
655
|
end
|
|
493
656
|
|
|
494
657
|
# creates an archive file of the given tree-ish
|
|
495
658
|
def archive(treeish, file = nil, opts = {})
|
|
496
|
-
|
|
659
|
+
object(treeish).archive(file, opts)
|
|
497
660
|
end
|
|
498
661
|
|
|
499
662
|
# repacks the repository
|
|
500
663
|
def repack
|
|
501
|
-
|
|
664
|
+
lib.repack
|
|
502
665
|
end
|
|
503
666
|
|
|
504
667
|
def gc
|
|
505
|
-
|
|
668
|
+
lib.gc
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# Verifies the connectivity and validity of objects in the database
|
|
672
|
+
#
|
|
673
|
+
# Runs `git fsck` to check repository integrity and identify dangling,
|
|
674
|
+
# missing, or unreachable objects.
|
|
675
|
+
#
|
|
676
|
+
# @overload fsck(objects = [], options = {})
|
|
677
|
+
# @param objects [Array<String>] specific objects to treat as heads for unreachability trace.
|
|
678
|
+
# If no objects are given, git fsck defaults to using the index file, all SHA-1
|
|
679
|
+
# references in the refs namespace, and all reflogs.
|
|
680
|
+
# @param [Hash] options options to pass to the underlying `git fsck` command
|
|
681
|
+
#
|
|
682
|
+
# @option options [Boolean] :unreachable print unreachable objects
|
|
683
|
+
# @option options [Boolean] :strict enable strict checking
|
|
684
|
+
# @option options [Boolean] :connectivity_only check only connectivity (faster)
|
|
685
|
+
# @option options [Boolean] :root report root nodes
|
|
686
|
+
# @option options [Boolean] :tags report tags
|
|
687
|
+
# @option options [Boolean] :cache consider objects in the index
|
|
688
|
+
# @option options [Boolean] :no_reflogs do not consider reflogs
|
|
689
|
+
# @option options [Boolean] :lost_found write dangling objects to .git/lost-found
|
|
690
|
+
# (note: this modifies the repository by creating files)
|
|
691
|
+
# @option options [Boolean, nil] :dangling print dangling objects (true/false/nil for default)
|
|
692
|
+
# @option options [Boolean, nil] :full check objects in alternate pools (true/false/nil for default)
|
|
693
|
+
# @option options [Boolean, nil] :name_objects name objects by refs (true/false/nil for default)
|
|
694
|
+
# @option options [Boolean, nil] :references check refs database consistency (true/false/nil for default)
|
|
695
|
+
#
|
|
696
|
+
# @return [Git::FsckResult] categorized objects flagged by fsck
|
|
697
|
+
#
|
|
698
|
+
# @example Check repository integrity
|
|
699
|
+
# result = git.fsck
|
|
700
|
+
# result.dangling.each { |obj| puts "#{obj.type}: #{obj.sha}" }
|
|
701
|
+
#
|
|
702
|
+
# @example Check with strict mode and suppress dangling output
|
|
703
|
+
# result = git.fsck(strict: true, dangling: false)
|
|
704
|
+
#
|
|
705
|
+
# @example Check if repository has any issues
|
|
706
|
+
# result = git.fsck
|
|
707
|
+
# puts "Repository is clean" if result.empty?
|
|
708
|
+
#
|
|
709
|
+
# @example List root commits
|
|
710
|
+
# result = git.fsck(root: true)
|
|
711
|
+
# result.root.each { |obj| puts obj.sha }
|
|
712
|
+
#
|
|
713
|
+
# @example Check specific objects
|
|
714
|
+
# result = git.fsck('abc1234', 'def5678')
|
|
715
|
+
#
|
|
716
|
+
def fsck(*objects, **opts) # rubocop:disable Style/ArgumentsForwarding
|
|
717
|
+
lib.fsck(*objects, **opts) # rubocop:disable Style/ArgumentsForwarding
|
|
506
718
|
end
|
|
507
719
|
|
|
508
720
|
def apply(file)
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
721
|
+
return unless File.exist?(file)
|
|
722
|
+
|
|
723
|
+
lib.apply(file)
|
|
512
724
|
end
|
|
513
725
|
|
|
514
726
|
def apply_mail(file)
|
|
515
|
-
|
|
727
|
+
lib.apply_mail(file) if File.exist?(file)
|
|
516
728
|
end
|
|
517
729
|
|
|
518
730
|
# Shows objects
|
|
@@ -520,8 +732,8 @@ module Git
|
|
|
520
732
|
# @param [String|NilClass] objectish the target object reference (nil == HEAD)
|
|
521
733
|
# @param [String|NilClass] path the path of the file to be shown
|
|
522
734
|
# @return [String] the object information
|
|
523
|
-
def show(objectish=nil, path=nil)
|
|
524
|
-
|
|
735
|
+
def show(objectish = nil, path = nil)
|
|
736
|
+
lib.show(objectish, path)
|
|
525
737
|
end
|
|
526
738
|
|
|
527
739
|
## LOWER LEVEL INDEX OPERATIONS ##
|
|
@@ -534,11 +746,11 @@ module Git
|
|
|
534
746
|
return_value
|
|
535
747
|
end
|
|
536
748
|
|
|
537
|
-
def with_temp_index
|
|
749
|
+
def with_temp_index(&)
|
|
538
750
|
# Workaround for JRUBY, since they handle the TempFile path different.
|
|
539
751
|
# MUST be improved to be safer and OS independent.
|
|
540
752
|
if RUBY_PLATFORM == 'java'
|
|
541
|
-
temp_path = "/tmp/temp-index-#{(0...15).map{ ('a'..'z').to_a[rand(26)] }.join}"
|
|
753
|
+
temp_path = "/tmp/temp-index-#{(0...15).map { ('a'..'z').to_a[rand(26)] }.join}"
|
|
542
754
|
else
|
|
543
755
|
tempfile = Tempfile.new('temp-index')
|
|
544
756
|
temp_path = tempfile.path
|
|
@@ -546,19 +758,19 @@ module Git
|
|
|
546
758
|
tempfile.unlink
|
|
547
759
|
end
|
|
548
760
|
|
|
549
|
-
with_index(temp_path, &
|
|
761
|
+
with_index(temp_path, &)
|
|
550
762
|
end
|
|
551
763
|
|
|
552
764
|
def checkout_index(opts = {})
|
|
553
|
-
|
|
765
|
+
lib.checkout_index(opts)
|
|
554
766
|
end
|
|
555
767
|
|
|
556
768
|
def read_tree(treeish, opts = {})
|
|
557
|
-
|
|
769
|
+
lib.read_tree(treeish, opts)
|
|
558
770
|
end
|
|
559
771
|
|
|
560
772
|
def write_tree
|
|
561
|
-
|
|
773
|
+
lib.write_tree
|
|
562
774
|
end
|
|
563
775
|
|
|
564
776
|
def write_and_commit_tree(opts = {})
|
|
@@ -570,9 +782,8 @@ module Git
|
|
|
570
782
|
branch(branch).update_ref(commit)
|
|
571
783
|
end
|
|
572
784
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
self.lib.ls_files(location)
|
|
785
|
+
def ls_files(location = nil)
|
|
786
|
+
lib.ls_files(location)
|
|
576
787
|
end
|
|
577
788
|
|
|
578
789
|
def with_working(work_dir) # :yields: the Git::WorkingDirectory
|
|
@@ -586,41 +797,219 @@ module Git
|
|
|
586
797
|
return_value
|
|
587
798
|
end
|
|
588
799
|
|
|
589
|
-
def with_temp_working
|
|
590
|
-
tempfile = Tempfile.new(
|
|
800
|
+
def with_temp_working(&)
|
|
801
|
+
tempfile = Tempfile.new('temp-workdir')
|
|
591
802
|
temp_dir = tempfile.path
|
|
592
803
|
tempfile.close
|
|
593
804
|
tempfile.unlink
|
|
594
|
-
Dir.mkdir(temp_dir,
|
|
595
|
-
with_working(temp_dir, &
|
|
805
|
+
Dir.mkdir(temp_dir, 0o700)
|
|
806
|
+
with_working(temp_dir, &)
|
|
596
807
|
end
|
|
597
808
|
|
|
598
809
|
# runs git rev-parse to convert the objectish to a full sha
|
|
599
810
|
#
|
|
600
811
|
# @example
|
|
601
|
-
# git.
|
|
602
|
-
# git.
|
|
603
|
-
# git.
|
|
812
|
+
# git.rev_parse("HEAD^^")
|
|
813
|
+
# git.rev_parse('v2.4^{tree}')
|
|
814
|
+
# git.rev_parse('v2.4:/doc/index.html')
|
|
604
815
|
#
|
|
605
|
-
def
|
|
606
|
-
|
|
816
|
+
def rev_parse(objectish)
|
|
817
|
+
lib.rev_parse(objectish)
|
|
607
818
|
end
|
|
608
819
|
|
|
609
|
-
|
|
610
|
-
|
|
820
|
+
# For backwards compatibility
|
|
821
|
+
alias revparse rev_parse
|
|
822
|
+
|
|
823
|
+
def ls_tree(objectish, opts = {})
|
|
824
|
+
lib.ls_tree(objectish, opts)
|
|
611
825
|
end
|
|
612
826
|
|
|
827
|
+
# Returns the contents of a git object
|
|
828
|
+
#
|
|
829
|
+
# Uses `git cat-file -p` to pretty-print the contents of the given object.
|
|
830
|
+
#
|
|
831
|
+
# @param objectish [String] a SHA, branch name, tag, or other revision reference
|
|
832
|
+
# to the git object
|
|
833
|
+
#
|
|
834
|
+
# @return [String] the contents of the object
|
|
835
|
+
#
|
|
836
|
+
# @see https://git-scm.com/docs/git-cat-file git-cat-file
|
|
837
|
+
#
|
|
613
838
|
def cat_file(objectish)
|
|
614
|
-
|
|
839
|
+
lib.cat_file_contents(objectish)
|
|
615
840
|
end
|
|
616
841
|
|
|
617
|
-
#
|
|
842
|
+
# The name of the branch HEAD refers to or 'HEAD' if detached
|
|
843
|
+
#
|
|
844
|
+
# Returns one of the following:
|
|
845
|
+
# * The branch name that HEAD refers to (even if it is an unborn branch)
|
|
846
|
+
# * 'HEAD' if in a detached HEAD state
|
|
847
|
+
#
|
|
848
|
+
# @return [String] the name of the branch HEAD refers to or 'HEAD' if detached
|
|
849
|
+
#
|
|
618
850
|
def current_branch
|
|
619
|
-
|
|
851
|
+
lib.branch_current
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
# @return [Git::Branch] an object for branch_name
|
|
855
|
+
def branch(branch_name = current_branch)
|
|
856
|
+
Git::Branch.new(self, branch_name)
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
# @return [Git::Branches] a collection of all the branches in the repository.
|
|
860
|
+
# Each branch is represented as a {Git::Branch}.
|
|
861
|
+
def branches
|
|
862
|
+
Git::Branches.new(self)
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
# returns a Git::Worktree object for dir, commitish
|
|
866
|
+
def worktree(dir, commitish = nil)
|
|
867
|
+
Git::Worktree.new(self, dir, commitish)
|
|
868
|
+
end
|
|
869
|
+
|
|
870
|
+
# returns a Git::worktrees object of all the Git::Worktrees
|
|
871
|
+
# objects for this repo
|
|
872
|
+
def worktrees
|
|
873
|
+
Git::Worktrees.new(self)
|
|
874
|
+
end
|
|
875
|
+
|
|
876
|
+
# @return [Git::Object::Commit] a commit object
|
|
877
|
+
def commit_tree(tree = nil, opts = {})
|
|
878
|
+
Git::Object::Commit.new(self, lib.commit_tree(tree, opts))
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# @return [Git::Diff] a Git::Diff object
|
|
882
|
+
def diff(objectish = 'HEAD', obj2 = nil)
|
|
883
|
+
Git::Diff.new(self, objectish, obj2)
|
|
884
|
+
end
|
|
885
|
+
|
|
886
|
+
# @return [Git::Object] a Git object
|
|
887
|
+
def gblob(objectish)
|
|
888
|
+
Git::Object.new(self, objectish, 'blob')
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
# @return [Git::Object] a Git object
|
|
892
|
+
def gcommit(objectish)
|
|
893
|
+
Git::Object.new(self, objectish, 'commit')
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
# @return [Git::Object] a Git object
|
|
897
|
+
def gtree(objectish)
|
|
898
|
+
Git::Object.new(self, objectish, 'tree')
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
# @return [Git::Log] a log with the specified number of commits
|
|
902
|
+
def log(count = 30)
|
|
903
|
+
Git::Log.new(self, count)
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
# returns a Git::Object of the appropriate type
|
|
907
|
+
# you can also call @git.gtree('tree'), but that's
|
|
908
|
+
# just for readability. If you call @git.gtree('HEAD') it will
|
|
909
|
+
# still return a Git::Object::Commit object.
|
|
910
|
+
#
|
|
911
|
+
# object calls a method that will run a rev-parse
|
|
912
|
+
# on the objectish and determine the type of the object and return
|
|
913
|
+
# an appropriate object for that type
|
|
914
|
+
#
|
|
915
|
+
# @return [Git::Object] an instance of the appropriate type of Git::Object
|
|
916
|
+
def object(objectish)
|
|
917
|
+
Git::Object.new(self, objectish)
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
# @return [Git::Remote] a remote of the specified name
|
|
921
|
+
def remote(remote_name = 'origin')
|
|
922
|
+
Git::Remote.new(self, remote_name)
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
# @return [Git::Status] a status object
|
|
926
|
+
def status
|
|
927
|
+
Git::Status.new(self)
|
|
928
|
+
end
|
|
929
|
+
|
|
930
|
+
# @return [Git::Object::Tag] a tag object
|
|
931
|
+
def tag(tag_name)
|
|
932
|
+
Git::Object::Tag.new(self, tag_name)
|
|
933
|
+
end
|
|
934
|
+
|
|
935
|
+
# Find as good common ancestors as possible for a merge
|
|
936
|
+
# example: g.merge_base('master', 'some_branch', 'some_sha', octopus: true)
|
|
937
|
+
#
|
|
938
|
+
# @return [Array<Git::Object::Commit>] a collection of common ancestors
|
|
939
|
+
def merge_base(*)
|
|
940
|
+
shas = lib.merge_base(*)
|
|
941
|
+
shas.map { |sha| gcommit(sha) }
|
|
620
942
|
end
|
|
621
943
|
|
|
944
|
+
# Returns a Git::Diff::Stats object for accessing diff statistics.
|
|
945
|
+
#
|
|
946
|
+
# @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
|
|
947
|
+
# @param obj2 [String, nil] The second commit or object to compare.
|
|
948
|
+
# @param opts [Hash] Options to filter the diff.
|
|
949
|
+
# @option opts [String, Pathname, Array<String, Pathname>] :path_limiter Limit stats to specified path(s).
|
|
950
|
+
# @return [Git::DiffStats]
|
|
951
|
+
def diff_stats(objectish = 'HEAD', obj2 = nil, opts = {})
|
|
952
|
+
Git::DiffStats.new(self, objectish, obj2, opts[:path_limiter])
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
# Returns a Git::Diff::PathStatus object for accessing the name-status report.
|
|
956
|
+
#
|
|
957
|
+
# @param objectish [String] The first commit or object to compare. Defaults to 'HEAD'.
|
|
958
|
+
# @param obj2 [String, nil] The second commit or object to compare.
|
|
959
|
+
# @param opts [Hash] Options to filter the diff.
|
|
960
|
+
# @option opts [String, Pathname, Array<String, Pathname>] :path_limiter Limit status to specified path(s).
|
|
961
|
+
# @option opts [String, Pathname, Array<String, Pathname>] :path (deprecated) Legacy alias for :path_limiter.
|
|
962
|
+
# @return [Git::DiffPathStatus]
|
|
963
|
+
def diff_path_status(objectish = 'HEAD', obj2 = nil, opts = {})
|
|
964
|
+
path_limiter = if opts.key?(:path_limiter)
|
|
965
|
+
opts[:path_limiter]
|
|
966
|
+
elsif opts.key?(:path)
|
|
967
|
+
Git::Deprecation.warn(
|
|
968
|
+
'Git::Base#diff_path_status :path option is deprecated. Use :path_limiter instead.'
|
|
969
|
+
)
|
|
970
|
+
opts[:path]
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
Git::DiffPathStatus.new(self, objectish, obj2, path_limiter)
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
# Provided for backwards compatibility
|
|
977
|
+
alias diff_name_status diff_path_status
|
|
978
|
+
|
|
622
979
|
private
|
|
623
980
|
|
|
981
|
+
# Sets default paths in the options hash for direct `Git::Base.new` calls
|
|
982
|
+
#
|
|
983
|
+
# Factory methods like `Git.open` pre-populate these options by calling
|
|
984
|
+
# `normalize_paths`, making this a fallback. It avoids mutating the
|
|
985
|
+
# original options hash by returning a new one.
|
|
986
|
+
#
|
|
987
|
+
# @param options [Hash] the original options hash
|
|
988
|
+
# @return [Hash] a new options hash with defaults applied
|
|
989
|
+
def default_paths(options)
|
|
990
|
+
return options unless (working_dir = options[:working_directory])
|
|
991
|
+
|
|
992
|
+
options.dup.tap do |opts|
|
|
993
|
+
opts[:repository] ||= File.join(working_dir, '.git')
|
|
994
|
+
opts[:index] ||= File.join(opts[:repository], 'index')
|
|
995
|
+
end
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
# Initializes the logger from the provided options
|
|
999
|
+
# @param log_option [Logger, nil] The logger instance from options.
|
|
1000
|
+
def setup_logger(log_option)
|
|
1001
|
+
@logger = log_option || Logger.new(nil)
|
|
1002
|
+
@logger.info('Starting Git')
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
# Initializes the core git objects based on the provided options
|
|
1006
|
+
# @param options [Hash] The processed options hash.
|
|
1007
|
+
def initialize_components(options)
|
|
1008
|
+
@working_directory = Git::WorkingDirectory.new(options[:working_directory]) if options[:working_directory]
|
|
1009
|
+
@repository = Git::Repository.new(options[:repository]) if options[:repository]
|
|
1010
|
+
@index = Git::Index.new(options[:index], must_exist: false) if options[:index]
|
|
1011
|
+
end
|
|
1012
|
+
|
|
624
1013
|
# Normalize options before they are sent to Git::Base.new
|
|
625
1014
|
#
|
|
626
1015
|
# Updates the options parameter by setting appropriate values for the following keys:
|
|
@@ -684,18 +1073,58 @@ module Git
|
|
|
684
1073
|
# 2. the working directory if NOT working with a bare repository
|
|
685
1074
|
#
|
|
686
1075
|
private_class_method def self.normalize_repository(options, default:, bare: false)
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
File.expand_path(options[:repository] || '.git', options[:working_directory])
|
|
692
|
-
end
|
|
1076
|
+
initial_path = initial_repository_path(options, default: default, bare: bare)
|
|
1077
|
+
final_path = resolve_gitdir_if_present(initial_path, options[:working_directory])
|
|
1078
|
+
options[:repository] = final_path
|
|
1079
|
+
end
|
|
693
1080
|
|
|
694
|
-
|
|
695
|
-
|
|
1081
|
+
# Determines the initial, potential path to the repository directory
|
|
1082
|
+
#
|
|
1083
|
+
# This path is considered 'initial' because it is not guaranteed to be the
|
|
1084
|
+
# final repository location. For features like submodules or worktrees,
|
|
1085
|
+
# this path may point to a text file containing a `gitdir:` pointer to the
|
|
1086
|
+
# actual repository directory elsewhere. This initial path must be
|
|
1087
|
+
# subsequently resolved.
|
|
1088
|
+
#
|
|
1089
|
+
# @api private
|
|
1090
|
+
#
|
|
1091
|
+
# @param options [Hash] The options hash, checked for `[:repository]`.
|
|
1092
|
+
#
|
|
1093
|
+
# @param default [String] A fallback path if `options[:repository]` is not set.
|
|
1094
|
+
#
|
|
1095
|
+
# @param bare [Boolean] Whether the repository is bare, which changes path resolution.
|
|
1096
|
+
#
|
|
1097
|
+
# @return [String] The initial, absolute path to the `.git` directory or file.
|
|
1098
|
+
#
|
|
1099
|
+
private_class_method def self.initial_repository_path(options, default:, bare:)
|
|
1100
|
+
if bare
|
|
1101
|
+
File.expand_path(options[:repository] || default || Dir.pwd)
|
|
1102
|
+
else
|
|
1103
|
+
File.expand_path(options[:repository] || '.git', options[:working_directory])
|
|
696
1104
|
end
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
# Resolves the path to the actual repository if it's a `gitdir:` pointer file.
|
|
1108
|
+
#
|
|
1109
|
+
# If `path` points to a file (common in submodules and worktrees), this
|
|
1110
|
+
# method reads the `gitdir:` path from it and returns the real repository
|
|
1111
|
+
# path. Otherwise, it returns the original path.
|
|
1112
|
+
#
|
|
1113
|
+
# @api private
|
|
1114
|
+
#
|
|
1115
|
+
# @param path [String] The initial path to the repository, which may be a pointer file.
|
|
1116
|
+
#
|
|
1117
|
+
# @param working_dir [String] The working directory, used as a base to resolve the path.
|
|
1118
|
+
#
|
|
1119
|
+
# @return [String] The final, resolved absolute path to the repository directory.
|
|
1120
|
+
#
|
|
1121
|
+
private_class_method def self.resolve_gitdir_if_present(path, working_dir)
|
|
1122
|
+
return path unless File.file?(path)
|
|
697
1123
|
|
|
698
|
-
|
|
1124
|
+
# The file contains `gitdir: <path>`, so we read the file,
|
|
1125
|
+
# extract the path part, and expand it.
|
|
1126
|
+
gitdir_pointer = File.read(path).sub(/\Agitdir: /, '').strip
|
|
1127
|
+
File.expand_path(gitdir_pointer, working_dir)
|
|
699
1128
|
end
|
|
700
1129
|
|
|
701
1130
|
# Normalize options[:index]
|