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.
@@ -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
@@ -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/committer
9
- # details, and timestamps.
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
- # author_name: 'Jane Doe',
21
- # author_email: 'jane@example.com',
22
- # author_date: '2026-01-24T10:30:00-08:00',
23
- # committer_name: 'Jane Doe',
24
- # committer_email: 'jane@example.com',
25
- # committer_date: '2026-01-24T10:30:00-08:00'
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.author_name # => 'Jane Doe'
35
- # info.author_email # => 'jane@example.com'
36
- # info.author_date # => '2026-01-24T10:30:00-08:00'
37
- # info.committer_name # => 'Jane Doe'
38
- # info.committer_email # => 'jane@example.com'
39
- # info.committer_date # => '2026-01-24T10:30:00-08:00'
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] author_name
63
- # @return [String] the name of the stash author
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
- # @!attribute [r] committer_name
72
- # @return [String] the name of the stash committer
75
+ # @return [Git::AuthorInfo] the author of the stash commit
73
76
  #
74
- # @!attribute [r] committer_email
75
- # @return [String] the email of the stash committer
77
+ # @!attribute [r] committer
78
+ # The identity of the stash committer; the nested `date` is a `Time`.
76
79
  #
77
- # @!attribute [r] committer_date
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
- :author_name,
88
- :author_email,
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 'git/author'
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
- # tagger_name: 'John Doe',
19
- # tagger_email: '<john@example.com>',
20
- # tagger_date: '2024-01-15T10:30:00-08:00',
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
- # tagger_name: nil,
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] tagger_name
69
- # @return [String, nil] the tagger's name, or nil for lightweight tags
76
+ # @!attribute [r] tagger
77
+ # The identity of the person who created the tag object.
70
78
  #
71
- # @!attribute [r] tagger_email
72
- # @return [String, nil] the tagger's email, or nil for lightweight tags
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
- # @!attribute [r] tagger_date
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, :tagger_name, :tagger_email, :tagger_date, :message) do
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
@@ -4,7 +4,7 @@ module Git
4
4
  # The current gem version
5
5
  #
6
6
  # @return [String] the current gem version
7
- VERSION = '5.2.0'
7
+ VERSION = '5.3.0'
8
8
 
9
9
  # Represents a git version with major, minor, and patch components
10
10
  #
data/lib/git.rb CHANGED
@@ -46,6 +46,7 @@ module Git
46
46
  end
47
47
 
48
48
  require 'git/author'
49
+ require 'git/author_info'
49
50
  require 'git/branch'
50
51
  require 'git/branch_info'
51
52
  require 'git/branches'
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.2.0
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.2.0/file/CHANGELOG.md
546
- documentation_uri: https://rubydoc.info/gems/git/5.2.0
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: