git 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +17 -4
- data/README.md +48 -7
- data/UPGRADING.md +287 -1
- data/lib/git/author.rb +11 -0
- data/lib/git/author_info.rb +66 -0
- data/lib/git/branch.rb +210 -15
- data/lib/git/branch_info.rb +1 -1
- data/lib/git/branches.rb +35 -7
- data/lib/git/commands/cat_file/raw.rb +60 -6
- data/lib/git/object.rb +13 -7
- data/lib/git/parsers/stash.rb +50 -17
- data/lib/git/parsers/tag.rb +54 -8
- data/lib/git/remote.rb +37 -7
- data/lib/git/remote_info.rb +67 -10
- data/lib/git/repository/branching.rb +111 -1
- data/lib/git/repository/merging.rb +96 -2
- data/lib/git/repository/remote_operations.rb +57 -0
- data/lib/git/repository/shared_private.rb +67 -0
- data/lib/git/stash_info.rb +32 -34
- data/lib/git/tag_info.rb +21 -29
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +1 -0
- metadata +4 -3
|
@@ -525,7 +525,37 @@ module Git
|
|
|
525
525
|
#
|
|
526
526
|
# @raise [Git::FailedError] when git exits with a non-zero status
|
|
527
527
|
#
|
|
528
|
+
# @deprecated Use `remote_list.find { |r| r.name == name }` for the fields
|
|
529
|
+
# {Git::RemoteInfo} models, or filter {Git::Configuring#config_list} on
|
|
530
|
+
# the `remote.<name>.` key prefix to keep every entry
|
|
531
|
+
#
|
|
532
|
+
# {#remote_list} returns a {Git::RemoteInfo} per remote. Its `url` and
|
|
533
|
+
# `fetch` members hold every configured value as `Array<String>`,
|
|
534
|
+
# whereas this method returns a flat hash in which a repeated `url` or
|
|
535
|
+
# `fetch` key overwrites the earlier value.
|
|
536
|
+
#
|
|
537
|
+
# {Git::RemoteInfo} models only the remote variables git defines and
|
|
538
|
+
# drops any other `remote.<name>.*` entry, whereas this method returns
|
|
539
|
+
# every entry. Callers that read custom keys should filter
|
|
540
|
+
# {Git::Configuring#config_list} instead, which returns the same hash
|
|
541
|
+
# (shown here for the `origin` remote):
|
|
542
|
+
#
|
|
543
|
+
# prefix = 'remote.origin.'
|
|
544
|
+
# repo.config_list
|
|
545
|
+
# .select { |entry| entry.key.start_with?(prefix) }
|
|
546
|
+
# .to_h { |entry| [entry.key.delete_prefix(prefix), entry.value] }
|
|
547
|
+
#
|
|
548
|
+
# @see #remote_list
|
|
549
|
+
#
|
|
550
|
+
# @see Git::Configuring#config_list
|
|
551
|
+
#
|
|
528
552
|
def config_remote(name)
|
|
553
|
+
Git::Deprecation.warn(
|
|
554
|
+
'Git::Repository#config_remote is deprecated and will be removed in v6.0.0. ' \
|
|
555
|
+
'Use Git::Repository#remote_list.find { |r| r.name == name } for the fields ' \
|
|
556
|
+
'Git::RemoteInfo models, or filter Git::Repository#config_list on the ' \
|
|
557
|
+
'"remote.<name>." key prefix to keep every entry.'
|
|
558
|
+
)
|
|
529
559
|
prefix = "remote.#{name}."
|
|
530
560
|
Private.config_list(@execution_context).each_with_object({}) do |(key, value), hsh|
|
|
531
561
|
hsh[key.delete_prefix(prefix)] = value if key.start_with?(prefix)
|
|
@@ -574,7 +604,19 @@ module Git
|
|
|
574
604
|
#
|
|
575
605
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
576
606
|
#
|
|
607
|
+
# @deprecated Use `remote_list.find { |r| r.name == name }` instead
|
|
608
|
+
#
|
|
609
|
+
# {#remote_list} returns immutable {Git::RemoteInfo} value objects
|
|
610
|
+
# rather than {Git::Remote}. Call the corresponding {Git::Repository}
|
|
611
|
+
# method (e.g. {#fetch}, {#remote_remove}) for operations on a remote.
|
|
612
|
+
#
|
|
613
|
+
# @see #remote_list
|
|
614
|
+
#
|
|
577
615
|
def remote(name = 'origin')
|
|
616
|
+
Git::Deprecation.warn(
|
|
617
|
+
'Git::Repository#remote is deprecated and will be removed in v6.0.0. ' \
|
|
618
|
+
'Use Git::Repository#remote_list.find { |r| r.name == name } instead.'
|
|
619
|
+
)
|
|
578
620
|
Git::Remote.new(self, name)
|
|
579
621
|
end
|
|
580
622
|
|
|
@@ -589,7 +631,22 @@ module Git
|
|
|
589
631
|
#
|
|
590
632
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
591
633
|
#
|
|
634
|
+
# @deprecated Use {#remote_list} instead
|
|
635
|
+
#
|
|
636
|
+
# {#remote_list} returns `Array<Git::RemoteInfo>` (immutable value
|
|
637
|
+
# objects) rather than `Array<Git::Remote>`. Call the corresponding
|
|
638
|
+
# {Git::Repository} method (e.g. {#fetch}, {#remote_remove}) for
|
|
639
|
+
# operations on a remote. Each {Git::Remote} this method constructs
|
|
640
|
+
# emits its own deprecation warning, so a call produces one warning
|
|
641
|
+
# for this method plus one per remote returned.
|
|
642
|
+
#
|
|
643
|
+
# @see #remote_list
|
|
644
|
+
#
|
|
592
645
|
def remotes
|
|
646
|
+
Git::Deprecation.warn(
|
|
647
|
+
'Git::Repository#remotes is deprecated and will be removed in v6.0.0. ' \
|
|
648
|
+
'Use Git::Repository#remote_list instead.'
|
|
649
|
+
)
|
|
593
650
|
result = Git::Commands::Remote::List.new(@execution_context).call
|
|
594
651
|
result.stdout.split("\n").map { |name| Git::Remote.new(self, name) }
|
|
595
652
|
end
|
|
@@ -46,6 +46,73 @@ module Git
|
|
|
46
46
|
|
|
47
47
|
raise ArgumentError, "Unknown options: #{unknown.join(', ')}"
|
|
48
48
|
end
|
|
49
|
+
|
|
50
|
+
# Raise unless `branch` names an existing local branch
|
|
51
|
+
#
|
|
52
|
+
# Used by facade methods that check out a branch, do work on it, and switch
|
|
53
|
+
# back. {Git::Repository#checkout} also accepts commit SHAs, tags, and
|
|
54
|
+
# remote-tracking branches, all of which detach HEAD; work committed there
|
|
55
|
+
# would be left dangling once the original branch is restored, and git has
|
|
56
|
+
# no way to report that.
|
|
57
|
+
#
|
|
58
|
+
# @example With an existing local branch
|
|
59
|
+
# SharedPrivate.assert_local_branch!(repo, 'feature') #=> nil
|
|
60
|
+
#
|
|
61
|
+
# @example With a tag
|
|
62
|
+
# SharedPrivate.assert_local_branch!(repo, 'v1.0.0')
|
|
63
|
+
# #=> raises ArgumentError: 'v1.0.0' is not an existing local branch
|
|
64
|
+
#
|
|
65
|
+
# @param repository [Git::Repository] the repository to check
|
|
66
|
+
#
|
|
67
|
+
# @param branch [String] the branch name to verify
|
|
68
|
+
#
|
|
69
|
+
# @return [void]
|
|
70
|
+
#
|
|
71
|
+
# @raise [ArgumentError] when `branch` is not an existing local branch
|
|
72
|
+
#
|
|
73
|
+
# @raise [Git::FailedError] when git exits with a non-zero exit status
|
|
74
|
+
#
|
|
75
|
+
def assert_local_branch!(repository, branch)
|
|
76
|
+
return if repository.local_branch?(branch)
|
|
77
|
+
|
|
78
|
+
raise ArgumentError, "'#{branch}' is not an existing local branch"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns a revision that restores the current HEAD after switching branches
|
|
82
|
+
#
|
|
83
|
+
# Used by facade methods that temporarily check out another branch and
|
|
84
|
+
# then switch back. On a branch, the branch name is enough. When HEAD is
|
|
85
|
+
# detached, {Git::Repository#current_branch} reports `'HEAD'`, which after
|
|
86
|
+
# a checkout resolves to the new branch rather than the original commit,
|
|
87
|
+
# so the commit SHA is captured instead. An unborn branch (one with no
|
|
88
|
+
# commits yet) has no ref to check out by name, so it is rejected here,
|
|
89
|
+
# before the caller switches away from it.
|
|
90
|
+
#
|
|
91
|
+
# @example On a branch
|
|
92
|
+
# SharedPrivate.head_restore_point(repo) #=> "main"
|
|
93
|
+
#
|
|
94
|
+
# @example With a detached HEAD
|
|
95
|
+
# SharedPrivate.head_restore_point(repo) #=> "9b9b31e704c0b85ffdd8d2af2ded85170a5af87d"
|
|
96
|
+
#
|
|
97
|
+
# @param repository [Git::Repository] the repository whose HEAD to record
|
|
98
|
+
#
|
|
99
|
+
# @return [String] the current branch name, or the full HEAD commit SHA
|
|
100
|
+
# when HEAD is detached
|
|
101
|
+
#
|
|
102
|
+
# @raise [Git::Error] when HEAD is on an unborn branch
|
|
103
|
+
#
|
|
104
|
+
# @raise [Git::FailedError] when git exits with a non-zero exit status
|
|
105
|
+
#
|
|
106
|
+
def head_restore_point(repository)
|
|
107
|
+
head = repository.current_branch_state
|
|
108
|
+
case head.state
|
|
109
|
+
when :detached then repository.rev_parse('HEAD').strip
|
|
110
|
+
when :unborn
|
|
111
|
+
raise Git::Error, "HEAD is on the unborn branch '#{head.name}', which cannot be restored " \
|
|
112
|
+
'after switching branches; make a commit on it first'
|
|
113
|
+
else head.name
|
|
114
|
+
end
|
|
115
|
+
end
|
|
49
116
|
end
|
|
50
117
|
|
|
51
118
|
private_constant :SharedPrivate
|
data/lib/git/stash_info.rb
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
require 'git/author_info'
|
|
6
|
+
|
|
3
7
|
module Git
|
|
4
8
|
# Immutable value object representing stash entry information
|
|
5
9
|
#
|
|
6
10
|
# StashInfo encapsulates the parsed data from `git stash list` output.
|
|
7
11
|
# Each entry contains comprehensive information about the stash including
|
|
8
|
-
# its index, reference name, commit SHA, branch, message, author
|
|
9
|
-
#
|
|
12
|
+
# its index, reference name, commit SHA, branch, message, and the author and
|
|
13
|
+
# committer identities.
|
|
10
14
|
#
|
|
15
|
+
# The author and committer are nested {Git::AuthorInfo} values. Their `date`
|
|
16
|
+
# is a `Time` parsed from git's ISO 8601 output, not an ISO 8601 string.
|
|
11
17
|
#
|
|
12
18
|
# @example Create a StashInfo from parsed stash list output
|
|
13
19
|
# info = Git::StashInfo.new(
|
|
@@ -17,12 +23,16 @@ module Git
|
|
|
17
23
|
# short_oid: 'abc123d',
|
|
18
24
|
# branch: 'main',
|
|
19
25
|
# message: 'WIP on main: abc123 Initial commit',
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
+
# author: Git::AuthorInfo.new(
|
|
27
|
+
# name: 'Jane Doe',
|
|
28
|
+
# email: 'jane@example.com',
|
|
29
|
+
# date: Time.iso8601('2026-01-24T10:30:00-08:00')
|
|
30
|
+
# ),
|
|
31
|
+
# committer: Git::AuthorInfo.new(
|
|
32
|
+
# name: 'Jane Doe',
|
|
33
|
+
# email: 'jane@example.com',
|
|
34
|
+
# date: Time.iso8601('2026-01-24T10:30:00-08:00')
|
|
35
|
+
# )
|
|
26
36
|
# )
|
|
27
37
|
#
|
|
28
38
|
# info.index # => 0
|
|
@@ -31,12 +41,12 @@ module Git
|
|
|
31
41
|
# info.short_oid # => 'abc123d'
|
|
32
42
|
# info.branch # => 'main'
|
|
33
43
|
# info.message # => 'WIP on main: abc123 Initial commit'
|
|
34
|
-
# info.
|
|
35
|
-
# info.
|
|
36
|
-
# info.
|
|
37
|
-
# info.
|
|
38
|
-
#
|
|
39
|
-
#
|
|
44
|
+
# info.author.name # => 'Jane Doe'
|
|
45
|
+
# info.author.email # => 'jane@example.com'
|
|
46
|
+
# info.author.date # => 2026-01-24 10:30:00 -0800
|
|
47
|
+
# info.committer.name # => 'Jane Doe'
|
|
48
|
+
#
|
|
49
|
+
# @see Git::AuthorInfo for the nested author and committer identities
|
|
40
50
|
#
|
|
41
51
|
# @api public
|
|
42
52
|
#
|
|
@@ -59,23 +69,15 @@ module Git
|
|
|
59
69
|
# @!attribute [r] message
|
|
60
70
|
# @return [String] the stash message (e.g., 'WIP on main: abc123 commit msg')
|
|
61
71
|
#
|
|
62
|
-
# @!attribute [r]
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# @!attribute [r] author_email
|
|
66
|
-
# @return [String] the email of the stash author
|
|
67
|
-
#
|
|
68
|
-
# @!attribute [r] author_date
|
|
69
|
-
# @return [String] the author date in ISO 8601 format
|
|
72
|
+
# @!attribute [r] author
|
|
73
|
+
# The identity of the stash author; the nested `date` is a `Time`.
|
|
70
74
|
#
|
|
71
|
-
#
|
|
72
|
-
# @return [String] the name of the stash committer
|
|
75
|
+
# @return [Git::AuthorInfo] the author of the stash commit
|
|
73
76
|
#
|
|
74
|
-
# @!attribute [r]
|
|
75
|
-
#
|
|
77
|
+
# @!attribute [r] committer
|
|
78
|
+
# The identity of the stash committer; the nested `date` is a `Time`.
|
|
76
79
|
#
|
|
77
|
-
#
|
|
78
|
-
# @return [String] the committer date in ISO 8601 format
|
|
80
|
+
# @return [Git::AuthorInfo] the committer of the stash commit
|
|
79
81
|
#
|
|
80
82
|
StashInfo = Data.define(
|
|
81
83
|
:index,
|
|
@@ -84,12 +86,8 @@ module Git
|
|
|
84
86
|
:short_oid,
|
|
85
87
|
:branch,
|
|
86
88
|
:message,
|
|
87
|
-
:
|
|
88
|
-
:
|
|
89
|
-
:author_date,
|
|
90
|
-
:committer_name,
|
|
91
|
-
:committer_email,
|
|
92
|
-
:committer_date
|
|
89
|
+
:author,
|
|
90
|
+
:committer
|
|
93
91
|
) do
|
|
94
92
|
# Returns the stash reference name
|
|
95
93
|
#
|
data/lib/git/tag_info.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
require 'git/author_info'
|
|
4
6
|
|
|
5
7
|
module Git
|
|
6
8
|
# Value object representing tag metadata from git tag output
|
|
@@ -9,19 +11,25 @@ module Git
|
|
|
9
11
|
# commands. It contains only the data parsed from git output without any
|
|
10
12
|
# repository context or operations.
|
|
11
13
|
#
|
|
14
|
+
# The tagger identity is a nested {Git::AuthorInfo}. Its `date` is a `Time`
|
|
15
|
+
# parsed from git's strict ISO 8601 output, not an ISO 8601 string.
|
|
16
|
+
#
|
|
12
17
|
# @example Annotated tag
|
|
13
18
|
# info = Git::TagInfo.new(
|
|
14
19
|
# name: 'v1.0.0',
|
|
15
20
|
# oid: 'abc123def456', # tag object's ID
|
|
16
21
|
# target_oid: 'def456abc789', # commit it points to
|
|
17
22
|
# objecttype: 'tag',
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
23
|
+
# tagger: Git::AuthorInfo.new(
|
|
24
|
+
# name: 'John Doe',
|
|
25
|
+
# email: 'john@example.com',
|
|
26
|
+
# date: Time.iso8601('2024-01-15T10:30:00-08:00')
|
|
27
|
+
# ),
|
|
21
28
|
# message: 'Release version 1.0.0'
|
|
22
29
|
# )
|
|
23
30
|
# info.annotated? #=> true
|
|
24
31
|
# info.tagger.name #=> 'John Doe'
|
|
32
|
+
# info.tagger.date #=> 2024-01-15 10:30:00 -0800
|
|
25
33
|
#
|
|
26
34
|
# @example Lightweight tag
|
|
27
35
|
# info = Git::TagInfo.new(
|
|
@@ -29,9 +37,7 @@ module Git
|
|
|
29
37
|
# oid: nil, # no tag object exists
|
|
30
38
|
# target_oid: 'def456abc789', # commit ID
|
|
31
39
|
# objecttype: 'commit',
|
|
32
|
-
#
|
|
33
|
-
# tagger_email: nil,
|
|
34
|
-
# tagger_date: nil,
|
|
40
|
+
# tagger: nil,
|
|
35
41
|
# message: nil
|
|
36
42
|
# )
|
|
37
43
|
# info.lightweight? #=> true
|
|
@@ -41,6 +47,8 @@ module Git
|
|
|
41
47
|
#
|
|
42
48
|
# @see Git::Commands::Tag::List for the command that produces these
|
|
43
49
|
#
|
|
50
|
+
# @see Git::AuthorInfo for the nested tagger identity
|
|
51
|
+
#
|
|
44
52
|
# @api public
|
|
45
53
|
#
|
|
46
54
|
# @!attribute [r] name
|
|
@@ -65,19 +73,18 @@ module Git
|
|
|
65
73
|
# @!attribute [r] objecttype
|
|
66
74
|
# @return [String] 'tag' for annotated tags, 'commit' for lightweight tags
|
|
67
75
|
#
|
|
68
|
-
# @!attribute [r]
|
|
69
|
-
#
|
|
76
|
+
# @!attribute [r] tagger
|
|
77
|
+
# The identity of the person who created the tag object.
|
|
70
78
|
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
79
|
+
# Lightweight tags have no tag object and therefore no tagger. The nested
|
|
80
|
+
# `email` has no angle brackets and the nested `date` is a `Time`.
|
|
73
81
|
#
|
|
74
|
-
#
|
|
75
|
-
# @return [String, nil] the tag date in ISO 8601 format, or nil for lightweight tags
|
|
82
|
+
# @return [Git::AuthorInfo, nil] the tagger, or nil for lightweight tags
|
|
76
83
|
#
|
|
77
84
|
# @!attribute [r] message
|
|
78
85
|
# @return [String, nil] the tag message, or nil for lightweight tags
|
|
79
86
|
#
|
|
80
|
-
TagInfo = Data.define(:name, :oid, :target_oid, :objecttype, :
|
|
87
|
+
TagInfo = Data.define(:name, :oid, :target_oid, :objecttype, :tagger, :message) do
|
|
81
88
|
# @return [Boolean] true if this is an annotated tag (oid is present)
|
|
82
89
|
def annotated?
|
|
83
90
|
!oid.nil?
|
|
@@ -87,20 +94,5 @@ module Git
|
|
|
87
94
|
def lightweight?
|
|
88
95
|
oid.nil?
|
|
89
96
|
end
|
|
90
|
-
|
|
91
|
-
# Return the tagger as an Author object
|
|
92
|
-
#
|
|
93
|
-
# @return [Git::Author, nil] the tagger as an Author object, or nil for lightweight tags
|
|
94
|
-
def tagger
|
|
95
|
-
return nil unless annotated? && tagger_name && tagger_email
|
|
96
|
-
|
|
97
|
-
# Git::Author expects format "Name <email> timestamp timezone"
|
|
98
|
-
# We construct a minimal format that will parse correctly
|
|
99
|
-
author = Git::Author.new('')
|
|
100
|
-
author.name = tagger_name
|
|
101
|
-
# Remove angle brackets if present
|
|
102
|
-
author.email = tagger_email.gsub(/\A<|>\z/, '')
|
|
103
|
-
author
|
|
104
|
-
end
|
|
105
97
|
end
|
|
106
98
|
end
|
data/lib/git/version.rb
CHANGED
data/lib/git.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: git
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott Chacon and others
|
|
@@ -311,6 +311,7 @@ files:
|
|
|
311
311
|
- git.gemspec
|
|
312
312
|
- lib/git.rb
|
|
313
313
|
- lib/git/author.rb
|
|
314
|
+
- lib/git/author_info.rb
|
|
314
315
|
- lib/git/branch.rb
|
|
315
316
|
- lib/git/branch_delete_failure.rb
|
|
316
317
|
- lib/git/branch_delete_result.rb
|
|
@@ -542,8 +543,8 @@ licenses:
|
|
|
542
543
|
metadata:
|
|
543
544
|
homepage_uri: http://github.com/ruby-git/ruby-git
|
|
544
545
|
source_code_uri: http://github.com/ruby-git/ruby-git
|
|
545
|
-
changelog_uri: https://rubydoc.info/gems/git/5.
|
|
546
|
-
documentation_uri: https://rubydoc.info/gems/git/5.
|
|
546
|
+
changelog_uri: https://rubydoc.info/gems/git/5.3.0/file/CHANGELOG.md
|
|
547
|
+
documentation_uri: https://rubydoc.info/gems/git/5.3.0
|
|
547
548
|
rubygems_mfa_required: 'true'
|
|
548
549
|
rdoc_options: []
|
|
549
550
|
require_paths:
|