git 5.1.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.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -0
  3. data/CONTRIBUTING.md +202 -169
  4. data/LICENSE +1 -1
  5. data/README.md +192 -151
  6. data/UPGRADING.md +287 -1
  7. data/git.gemspec +35 -3
  8. data/lib/git/author.rb +11 -0
  9. data/lib/git/author_info.rb +66 -0
  10. data/lib/git/branch.rb +210 -15
  11. data/lib/git/branch_info.rb +1 -1
  12. data/lib/git/branches.rb +35 -7
  13. data/lib/git/command_line/base.rb +1 -2
  14. data/lib/git/commands/base.rb +1 -1
  15. data/lib/git/commands/cat_file/raw.rb +62 -7
  16. data/lib/git/object.rb +13 -7
  17. data/lib/git/parsers/stash.rb +50 -17
  18. data/lib/git/parsers/tag.rb +54 -8
  19. data/lib/git/remote.rb +37 -7
  20. data/lib/git/remote_info.rb +67 -10
  21. data/lib/git/repository/branching.rb +190 -6
  22. data/lib/git/repository/merging.rb +96 -2
  23. data/lib/git/repository/remote_operations.rb +57 -0
  24. data/lib/git/repository/shared_private.rb +67 -0
  25. data/lib/git/stash_info.rb +32 -34
  26. data/lib/git/tag_info.rb +21 -29
  27. data/lib/git/version.rb +1 -1
  28. data/lib/git.rb +1 -0
  29. metadata +6 -94
  30. data/.claude/commands/address-copilot-reviews.md +0 -14
  31. data/.claude/settings.json +0 -16
  32. data/.claude/skills +0 -1
  33. data/.commitlintrc.yml +0 -38
  34. data/.dockerignore +0 -27
  35. data/.github/copilot-instructions.md +0 -30
  36. data/.github/hooks/bin-setup-on-worktree.json +0 -11
  37. data/.github/hooks/run-bin-setup-once.sh +0 -20
  38. data/.github/issue_template.md +0 -15
  39. data/.github/prompts/iteratively-address-copilot-reviews.prompt.md +0 -188
  40. data/.github/pull_request_template.md +0 -21
  41. data/.github/skills/breaking-change-analysis/SKILL.md +0 -99
  42. data/.github/skills/ci-cd-troubleshooting/SKILL.md +0 -264
  43. data/.github/skills/command-implementation/REFERENCE.md +0 -994
  44. data/.github/skills/command-implementation/SKILL.md +0 -230
  45. data/.github/skills/command-test-conventions/SKILL.md +0 -664
  46. data/.github/skills/command-yard-documentation/SKILL.md +0 -434
  47. data/.github/skills/dependency-management/SKILL.md +0 -72
  48. data/.github/skills/development-workflow/SKILL.md +0 -512
  49. data/.github/skills/facade-implementation/REFERENCE.md +0 -837
  50. data/.github/skills/facade-implementation/SKILL.md +0 -269
  51. data/.github/skills/facade-test-conventions/SKILL.md +0 -391
  52. data/.github/skills/facade-yard-documentation/SKILL.md +0 -435
  53. data/.github/skills/make-skill-template/SKILL.md +0 -226
  54. data/.github/skills/pr-readiness-review/SKILL.md +0 -205
  55. data/.github/skills/project-context/SKILL.md +0 -306
  56. data/.github/skills/pull-request-review/SKILL.md +0 -168
  57. data/.github/skills/rebase/SKILL.md +0 -148
  58. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +0 -131
  59. data/.github/skills/release-management/SKILL.md +0 -125
  60. data/.github/skills/resolve-feedback/SKILL.md +0 -288
  61. data/.github/skills/review-arguments-dsl/CHECKLIST.md +0 -788
  62. data/.github/skills/review-arguments-dsl/SKILL.md +0 -214
  63. data/.github/skills/review-cross-command-consistency/SKILL.md +0 -139
  64. data/.github/skills/reviewing-skills/SKILL.md +0 -214
  65. data/.github/skills/rspec-unit-testing-standards/SKILL.md +0 -685
  66. data/.github/skills/tdd-refactor-step/SKILL.md +0 -236
  67. data/.github/skills/test-debugging/SKILL.md +0 -161
  68. data/.github/skills/yard-documentation/SKILL.md +0 -981
  69. data/.github/skills/yard-documentation/element-rules.md +0 -162
  70. data/.github/skills-deprecated/README.md +0 -21
  71. data/.github/skills-deprecated/extract-command-from-lib/SKILL.md +0 -487
  72. data/.github/skills-deprecated/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md +0 -22
  73. data/.github/skills-deprecated/extract-facade-from-base-lib/SKILL.md +0 -600
  74. data/.github/skills-deprecated/review-backward-compatibility/SKILL.md +0 -275
  75. data/.github/workflows/continuous_integration.yml +0 -358
  76. data/.github/workflows/enforce_conventional_commits.yml +0 -35
  77. data/.github/workflows/experimental_continuous_integration.yml +0 -59
  78. data/.github/workflows/release.yml +0 -52
  79. data/.github/workflows/warm_bundler_caches.yml +0 -82
  80. data/.gitignore +0 -30
  81. data/.husky/commit-msg +0 -1
  82. data/.husky/pre-commit +0 -13
  83. data/.release-please-config.json +0 -36
  84. data/.release-please-manifest.json +0 -3
  85. data/.rspec +0 -2
  86. data/.rubocop.yml +0 -44
  87. data/.rubocop_todo.yml +0 -30
  88. data/.yard-lint.yml +0 -75
  89. data/CLAUDE.md +0 -11
  90. data/Gemfile +0 -22
  91. data/Rakefile +0 -41
  92. data/docker/test/Dockerfile +0 -32
  93. data/docker/test/docker-compose.yml +0 -0
  94. data/package.json +0 -10
  95. data/redesign/1_architecture_existing.md +0 -102
  96. data/redesign/2_architecture_redesign.md +0 -449
  97. data/redesign/3_architecture_implementation.md +0 -1623
  98. data/redesign/Phase 4 - Step A.md +0 -366
  99. data/redesign/Phase 4 - Step B.md +0 -921
  100. data/redesign/Phase 4 - Step C.md +0 -833
  101. data/redesign/beta_release.md +0 -107
  102. data/redesign/branch_parse_refactor_plan.md +0 -163
  103. data/redesign/c1a-public-api-scope.tsv +0 -256
  104. data/redesign/c1c2_audit.md +0 -566
  105. data/redesign/c1c2_bucket6_lib_orphans.md +0 -626
  106. data/redesign/config_design.rb +0 -501
  107. data/redesign/index.md +0 -34
  108. data/redesign/info_object_migration_plan.md +0 -126
  109. data/redesign/integration_test_analysis.md +0 -521
  110. data/redesign/phase-4-step-b-test-audit.tsv +0 -485
  111. data/redesign/remote_refactor_plan.md +0 -164
  112. data/redesign/reverse_dependencies.sql +0 -44
  113. data/tasks/gem_tasks.rake +0 -14
  114. data/tasks/npm_tasks.rake +0 -7
  115. data/tasks/rspec.rake +0 -111
  116. data/tasks/rubocop.rake +0 -5
  117. data/tasks/test_gem.rake +0 -12
  118. data/tasks/yard.rake +0 -57
@@ -10,8 +10,8 @@ require 'git/repository/shared_private'
10
10
 
11
11
  module Git
12
12
  class Repository
13
- # Facade methods for merge operations: merging branches into the current branch,
14
- # and finding common ancestors between commits
13
+ # Facade methods for merge operations: merging branches into the current branch
14
+ # or into another branch, and finding common ancestors between commits
15
15
  #
16
16
  # Included by {Git::Repository}.
17
17
  #
@@ -97,6 +97,100 @@ module Git
97
97
  Git::Commands::Merge::Start.new(@execution_context).call(*branches, no_edit: true, **opts).stdout
98
98
  end
99
99
 
100
+ # Option keys accepted by {#merge_into}
101
+ #
102
+ # The keys accepted by {#merge} except `:no_commit`. A merge stopped before
103
+ # its commit leaves `target_branch` unchanged, and the restore checkout would
104
+ # carry the staged merge result onto the original branch instead.
105
+ MERGE_INTO_ALLOWED_OPTS = %i[no_ff m message].freeze
106
+ private_constant :MERGE_INTO_ALLOWED_OPTS
107
+
108
+ # Merge one or more branches into another branch without leaving the current branch
109
+ #
110
+ # Records the current branch (or the current commit when HEAD is detached),
111
+ # checks out `target_branch`, merges `branch` into it with {#merge}, then
112
+ # checks out the original branch or commit again. Use {#merge} directly when
113
+ # the target is the currently checked-out branch.
114
+ #
115
+ # `target_branch` must be an existing local branch. Unlike {#checkout}, a
116
+ # commit SHA, tag, or remote-tracking branch is rejected before any checkout
117
+ # happens: those detach HEAD, and the merge commit made there would be left
118
+ # dangling once the original branch is restored while the named ref stayed
119
+ # unchanged.
120
+ #
121
+ # HEAD must be on a branch with at least one commit, or detached: an unborn
122
+ # branch (no commits yet) cannot be checked out again by name, so it is
123
+ # rejected before any checkout happens.
124
+ #
125
+ # Option keys, the source list, and `target_branch` are checked before any
126
+ # branch is checked out, so those failures never leave the repository on
127
+ # `target_branch`. Anything rejected later, such as an option value that is
128
+ # not accepted or a source ref that does not exist, surfaces inside {#merge}
129
+ # after the checkout and follows the Note below.
130
+ #
131
+ # The `:no_commit` option is not accepted: a merge stopped before its commit
132
+ # would leave `target_branch` unchanged and the restore checkout would carry
133
+ # the staged result onto the original branch. To merge without committing,
134
+ # call {#checkout} and {#merge} directly.
135
+ #
136
+ # **Note:** the restore checkout is not wrapped in `ensure`. If the merge
137
+ # fails (for example, on a conflict), the repository is left checked out on
138
+ # `target_branch` with the merge in progress rather than restored to the
139
+ # original branch.
140
+ #
141
+ # @example Merge a feature branch into main while staying on the current branch
142
+ # repo.merge_into('main', 'feature')
143
+ #
144
+ # @example Merge with a no-fast-forward commit message
145
+ # repo.merge_into('main', 'feature', 'Merge feature into main', no_ff: true)
146
+ #
147
+ # @example Octopus merge of multiple branches into main
148
+ # repo.merge_into('main', %w[feature-a feature-b])
149
+ #
150
+ # @param target_branch [String] the name of an existing local branch to
151
+ # merge into
152
+ #
153
+ # @param branch [#to_s, Array<#to_s>] the branch or branches to merge into
154
+ # `target_branch`; accepts the same forms as {#merge}, but must name at
155
+ # least one branch
156
+ #
157
+ # @param message [String, nil] optional commit message for the merge commit;
158
+ # see {#merge} for how it interacts with the `:message` and `:m` options
159
+ #
160
+ # @param opts [Hash] additional options forwarded to {#merge}
161
+ #
162
+ # @option opts [Boolean, nil] :no_ff (nil) create a merge commit even when
163
+ # fast-forward is possible (`--no-ff`)
164
+ #
165
+ # @option opts [String] :message (nil) commit message; prefer the `:m` option
166
+ #
167
+ # @option opts [String] :m (nil) commit message (`-m` flag)
168
+ #
169
+ # @return [String] git's stdout from the merge command
170
+ #
171
+ # @raise [ArgumentError] when unsupported options (including `:no_commit`)
172
+ # are provided
173
+ #
174
+ # @raise [ArgumentError] when `branch` is `nil` or an empty Array
175
+ #
176
+ # @raise [ArgumentError] when `target_branch` is not an existing local branch
177
+ #
178
+ # @raise [Git::Error] when HEAD is on an unborn branch
179
+ #
180
+ # @raise [Git::FailedError] when git exits with a non-zero exit status
181
+ #
182
+ def merge_into(target_branch, branch, message = nil, opts = {})
183
+ SharedPrivate.assert_valid_opts!(MERGE_INTO_ALLOWED_OPTS, **opts)
184
+ raise ArgumentError, 'at least one branch to merge is required' if Array(branch).empty?
185
+
186
+ SharedPrivate.assert_local_branch!(self, target_branch)
187
+ restore_point = SharedPrivate.head_restore_point(self)
188
+ checkout(target_branch)
189
+ output = merge(branch, message, opts)
190
+ checkout(restore_point)
191
+ output
192
+ end
193
+
100
194
  # Find common ancestor commit(s) for use in a merge
101
195
  #
102
196
  # @example Find the common ancestor of two branches
@@ -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.1.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.1.0
4
+ version: 5.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon and others
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '4.0'
46
+ version: '4.1'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '4.0'
53
+ version: '4.1'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: rchardet
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -298,83 +298,20 @@ executables: []
298
298
  extensions: []
299
299
  extra_rdoc_files: []
300
300
  files:
301
- - ".claude/commands/address-copilot-reviews.md"
302
- - ".claude/settings.json"
303
- - ".claude/skills"
304
- - ".commitlintrc.yml"
305
- - ".dockerignore"
306
- - ".github/copilot-instructions.md"
307
- - ".github/hooks/bin-setup-on-worktree.json"
308
- - ".github/hooks/run-bin-setup-once.sh"
309
- - ".github/issue_template.md"
310
- - ".github/prompts/iteratively-address-copilot-reviews.prompt.md"
311
- - ".github/pull_request_template.md"
312
- - ".github/skills-deprecated/README.md"
313
- - ".github/skills-deprecated/extract-command-from-lib/SKILL.md"
314
- - ".github/skills-deprecated/extract-facade-from-base-lib/KEYWORD_ARG_REMEDIATION.md"
315
- - ".github/skills-deprecated/extract-facade-from-base-lib/SKILL.md"
316
- - ".github/skills-deprecated/review-backward-compatibility/SKILL.md"
317
- - ".github/skills/breaking-change-analysis/SKILL.md"
318
- - ".github/skills/ci-cd-troubleshooting/SKILL.md"
319
- - ".github/skills/command-implementation/REFERENCE.md"
320
- - ".github/skills/command-implementation/SKILL.md"
321
- - ".github/skills/command-test-conventions/SKILL.md"
322
- - ".github/skills/command-yard-documentation/SKILL.md"
323
- - ".github/skills/dependency-management/SKILL.md"
324
- - ".github/skills/development-workflow/SKILL.md"
325
- - ".github/skills/facade-implementation/REFERENCE.md"
326
- - ".github/skills/facade-implementation/SKILL.md"
327
- - ".github/skills/facade-test-conventions/SKILL.md"
328
- - ".github/skills/facade-yard-documentation/SKILL.md"
329
- - ".github/skills/make-skill-template/SKILL.md"
330
- - ".github/skills/pr-readiness-review/SKILL.md"
331
- - ".github/skills/project-context/SKILL.md"
332
- - ".github/skills/pull-request-review/SKILL.md"
333
- - ".github/skills/rebase/SKILL.md"
334
- - ".github/skills/refactor-command-to-commandlineresult/SKILL.md"
335
- - ".github/skills/release-management/SKILL.md"
336
- - ".github/skills/resolve-feedback/SKILL.md"
337
- - ".github/skills/review-arguments-dsl/CHECKLIST.md"
338
- - ".github/skills/review-arguments-dsl/SKILL.md"
339
- - ".github/skills/review-cross-command-consistency/SKILL.md"
340
- - ".github/skills/reviewing-skills/SKILL.md"
341
- - ".github/skills/rspec-unit-testing-standards/SKILL.md"
342
- - ".github/skills/tdd-refactor-step/SKILL.md"
343
- - ".github/skills/test-debugging/SKILL.md"
344
- - ".github/skills/yard-documentation/SKILL.md"
345
- - ".github/skills/yard-documentation/element-rules.md"
346
- - ".github/workflows/continuous_integration.yml"
347
- - ".github/workflows/enforce_conventional_commits.yml"
348
- - ".github/workflows/experimental_continuous_integration.yml"
349
- - ".github/workflows/release.yml"
350
- - ".github/workflows/warm_bundler_caches.yml"
351
- - ".gitignore"
352
- - ".husky/commit-msg"
353
- - ".husky/pre-commit"
354
- - ".release-please-config.json"
355
- - ".release-please-manifest.json"
356
- - ".rspec"
357
- - ".rubocop.yml"
358
- - ".rubocop_todo.yml"
359
- - ".yard-lint.yml"
360
301
  - ".yardopts"
361
302
  - AI_POLICY.md
362
303
  - CHANGELOG.md
363
- - CLAUDE.md
364
304
  - CODE_OF_CONDUCT.md
365
305
  - CONTRIBUTING.md
366
306
  - GOVERNANCE.md
367
- - Gemfile
368
307
  - LICENSE
369
308
  - MAINTAINERS.md
370
309
  - README.md
371
- - Rakefile
372
310
  - UPGRADING.md
373
- - docker/test/Dockerfile
374
- - docker/test/docker-compose.yml
375
311
  - git.gemspec
376
312
  - lib/git.rb
377
313
  - lib/git/author.rb
314
+ - lib/git/author_info.rb
378
315
  - lib/git/branch.rb
379
316
  - lib/git/branch_delete_failure.rb
380
317
  - lib/git/branch_delete_result.rb
@@ -600,39 +537,14 @@ files:
600
537
  - lib/git/version_constraint.rb
601
538
  - lib/git/worktree.rb
602
539
  - lib/git/worktrees.rb
603
- - package.json
604
- - redesign/1_architecture_existing.md
605
- - redesign/2_architecture_redesign.md
606
- - redesign/3_architecture_implementation.md
607
- - redesign/Phase 4 - Step A.md
608
- - redesign/Phase 4 - Step B.md
609
- - redesign/Phase 4 - Step C.md
610
- - redesign/beta_release.md
611
- - redesign/branch_parse_refactor_plan.md
612
- - redesign/c1a-public-api-scope.tsv
613
- - redesign/c1c2_audit.md
614
- - redesign/c1c2_bucket6_lib_orphans.md
615
- - redesign/config_design.rb
616
- - redesign/index.md
617
- - redesign/info_object_migration_plan.md
618
- - redesign/integration_test_analysis.md
619
- - redesign/phase-4-step-b-test-audit.tsv
620
- - redesign/remote_refactor_plan.md
621
- - redesign/reverse_dependencies.sql
622
- - tasks/gem_tasks.rake
623
- - tasks/npm_tasks.rake
624
- - tasks/rspec.rake
625
- - tasks/rubocop.rake
626
- - tasks/test_gem.rake
627
- - tasks/yard.rake
628
540
  homepage: http://github.com/ruby-git/ruby-git
629
541
  licenses:
630
542
  - MIT
631
543
  metadata:
632
544
  homepage_uri: http://github.com/ruby-git/ruby-git
633
545
  source_code_uri: http://github.com/ruby-git/ruby-git
634
- changelog_uri: https://rubydoc.info/gems/git/5.1.0/file/CHANGELOG.md
635
- documentation_uri: https://rubydoc.info/gems/git/5.1.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
636
548
  rubygems_mfa_required: 'true'
637
549
  rdoc_options: []
638
550
  require_paths:
@@ -1,14 +0,0 @@
1
- ---
2
- description: Address all unresolved Copilot review threads on the active pull request
3
- argument-hint: "[PR number — defaults to the PR for the current branch]"
4
- ---
5
-
6
- Follow the shared agent prompt in
7
- `.github/prompts/iteratively-address-copilot-reviews.prompt.md` exactly. That file
8
- is the canonical instructions for this task and is shared with other AI agents; read
9
- it before doing anything else.
10
-
11
- @.github/prompts/iteratively-address-copilot-reviews.prompt.md
12
-
13
- If `$ARGUMENTS` is non-empty, treat it as the pull request number to operate on
14
- instead of detecting the PR from the current branch.