git 5.3.0 → 5.4.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.
@@ -721,8 +721,28 @@ module Git
721
721
  # @raise [Git::FailedError] if the underlying `git show-ref` invocation
722
722
  # exits with an unexpected status (i.e., outside the allowed 0..1 range)
723
723
  #
724
+ # @deprecated Use `tag_list(name).first` instead
725
+ #
726
+ # {#tag_list} returns immutable {Git::TagInfo} value objects rather
727
+ # than {Git::Object::Tag}. `tag_list(name).first` is `nil` when the tag
728
+ # does not exist, where this method raises
729
+ # {Git::UnexpectedResultError}. Call the corresponding
730
+ # {Git::Repository} method (e.g. {#archive}, {#log}, {#diff},
731
+ # {#cat_file_contents}) with `info.oid || info.target_oid` for
732
+ # operations on a tag; that is the object this method's return value
733
+ # pins at construction, so a later move of the tag does not redirect
734
+ # it, whereas the tag name would. The
735
+ # {Git::Object::Tag} constructor is deprecated too; this method
736
+ # silences it so one call emits one warning.
737
+ #
738
+ # @see #tag_list
739
+ #
724
740
  def tag(tag_name)
725
- Git::Object::Tag.new(self, tag_name)
741
+ Git::Deprecation.warn(
742
+ 'Git::Repository#tag is deprecated and will be removed in v6.0.0. ' \
743
+ 'Use Git::Repository#tag_list(name).first instead.'
744
+ )
745
+ Git::Deprecation.silence { Git::Object::Tag.new(self, tag_name) }
726
746
  end
727
747
 
728
748
  # Returns the appropriate git object for the given object reference
@@ -753,6 +773,47 @@ module Git
753
773
  Git::Object.new(self, objectish)
754
774
  end
755
775
 
776
+ # Returns the tags in the repository as structured objects
777
+ #
778
+ # @example List all tags
779
+ # repo.tag_list
780
+ # # => [#<data Git::TagInfo name="v1.0.0", oid=nil, target_oid="abc123...", ...>,
781
+ # # #<data Git::TagInfo name="v2.0.0", oid="def456...", target_oid="789abc...", ...>]
782
+ #
783
+ # @example Look up a single tag by name
784
+ # repo.tag_list('v1.0.0').first
785
+ # # => #<data Git::TagInfo name="v1.0.0", ...>
786
+ #
787
+ # @example Look up a tag that does not exist
788
+ # repo.tag_list('nonexistent').first #=> nil
789
+ #
790
+ # @example Filter using glob patterns
791
+ # repo.tag_list('v1.*', 'v2.*')
792
+ #
793
+ # @example List only annotated tags
794
+ # repo.tag_list.select(&:annotated?)
795
+ #
796
+ # @param patterns [Array<String>] optional shell wildcard patterns passed
797
+ # directly to `git tag --list`; when empty (the default) all tags are
798
+ # returned
799
+ #
800
+ # @return [Array<Git::TagInfo>] parsed tag information for every tag
801
+ # matching the patterns, in the order `git tag --list` reports them
802
+ #
803
+ # Returns an empty array when the repository has no tags or no tag
804
+ # matches the given patterns.
805
+ #
806
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
807
+ #
808
+ # @see https://git-scm.com/docs/git-tag git-tag
809
+ #
810
+ def tag_list(*patterns)
811
+ result = Git::Commands::Tag::List.new(@execution_context).call(
812
+ *patterns, format: Git::Parsers::Tag::FORMAT_STRING
813
+ )
814
+ Git::Parsers::Tag.parse_list(result.stdout)
815
+ end
816
+
756
817
  # Returns all tags in the repository as tag objects
757
818
  #
758
819
  # Runs `git tag --list` with a machine-readable format, parses the output,
@@ -769,30 +830,50 @@ module Git
769
830
  #
770
831
  # @raise [Git::FailedError] if git exits with a non-zero exit status
771
832
  #
833
+ # @deprecated Use {#tag_list} instead
834
+ #
835
+ # {#tag_list} returns `Array<Git::TagInfo>` (immutable value objects)
836
+ # rather than `Array<Git::Object::Tag>`. Look a tag up by name with
837
+ # `tag_list(name).first`, and call the corresponding {Git::Repository}
838
+ # method (e.g. {#archive}, {#log}, {#diff}, {#cat_file_contents}) with
839
+ # `info.oid || info.target_oid` for operations on a tag; that is the
840
+ # object each returned {Git::Object::Tag} pins at construction, so a
841
+ # later move of the tag does not redirect it, whereas the tag name
842
+ # would. The {Git::Object::Tag}
843
+ # constructor is deprecated too; this method silences it so one call
844
+ # emits one warning.
845
+ #
846
+ # @see #tag_list
847
+ #
772
848
  def tags
773
- result = Git::Commands::Tag::List.new(@execution_context).call(format: Git::Parsers::Tag::FORMAT_STRING)
774
- Git::Parsers::Tag.parse_list(result.stdout).map { |info| tag(info.name) }
849
+ Git::Deprecation.warn(
850
+ 'Git::Repository#tags is deprecated and will be removed in v6.0.0. ' \
851
+ 'Use Git::Repository#tag_list instead.'
852
+ )
853
+ Git::Deprecation.silence { tag_list.map { |info| Git::Object::Tag.new(self, info.name) } }
775
854
  end
776
855
 
777
- # Option keys accepted by {#tag_add}
778
- TAG_ADD_ALLOWED_OPTS = %i[
856
+ # Option keys accepted by {#tag_create} and {#tag_add}
857
+ TAG_CREATE_ALLOWED_OPTS = %i[
779
858
  annotate a sign s no_sign local_user u force f message m file F
780
859
  edit e no_edit trailer cleanup create_reflog
781
860
  ].freeze
782
- private_constant :TAG_ADD_ALLOWED_OPTS
861
+ private_constant :TAG_CREATE_ALLOWED_OPTS
783
862
 
784
- # Create a new tag
863
+ # Create a new tag and return its metadata
785
864
  #
786
- # @overload tag_add(name, options = {})
865
+ # @overload tag_create(name, options = {})
787
866
  #
788
867
  # @example Create a lightweight tag on HEAD
789
- # repo.tag_add('v1.0.0')
868
+ # repo.tag_create('v1.0.0')
869
+ # # => #<data Git::TagInfo name="v1.0.0", oid=nil, target_oid="abc123...", ...>
790
870
  #
791
871
  # @example Create an annotated tag on HEAD
792
- # repo.tag_add('v1.0.0', annotate: true, message: 'Release 1.0.0')
872
+ # repo.tag_create('v1.0.0', annotate: true, message: 'Release 1.0.0')
873
+ # # => #<data Git::TagInfo name="v1.0.0", oid="def456...", message="Release 1.0.0", ...>
793
874
  #
794
875
  # @example Replace an existing tag on HEAD
795
- # repo.tag_add('v1.0.0', force: true)
876
+ # repo.tag_create('v1.0.0', force: true)
796
877
  #
797
878
  # @param name [String] the name of the tag to create
798
879
  #
@@ -847,6 +928,65 @@ module Git
847
928
  # @option options [Boolean, nil] :create_reflog (nil) create a reflog for
848
929
  # the tag
849
930
  #
931
+ # @return [Git::TagInfo] the newly created tag
932
+ #
933
+ # @overload tag_create(name, target, options = {})
934
+ #
935
+ # @example Create a lightweight tag on a specific commit
936
+ # repo.tag_create('v1.0.0', 'abc123')
937
+ #
938
+ # @example Create an annotated tag on a specific commit
939
+ # repo.tag_create('v1.0.0', 'abc123', annotate: true, message: 'Release 1.0.0')
940
+ #
941
+ # @param name [String] the name of the tag to create
942
+ #
943
+ # @param target [String] the object to tag (commit SHA, branch name, etc.)
944
+ #
945
+ # @param options [Hash] options for creating the tag (same keys as the
946
+ # first overload)
947
+ #
948
+ # @return [Git::TagInfo] the newly created tag
949
+ #
950
+ # @raise [ArgumentError] if unsupported options are provided, including the
951
+ # `:d` and `:delete` keys that {#tag_add} accepts; use {#tag_delete} to
952
+ # delete a tag
953
+ #
954
+ # @raise [ArgumentError] if an annotated or signed tag is requested without
955
+ # a message
956
+ #
957
+ # @raise [ArgumentError] if more than one positional argument follows the
958
+ # name (before any options hash); {#tag_add} silently ignored the extra
959
+ # arguments and tagged the first
960
+ #
961
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
962
+ #
963
+ # @see https://git-scm.com/docs/git-tag git-tag
964
+ #
965
+ def tag_create(name, *args)
966
+ target, options = Private.tag_target_and_options(args, strict: true)
967
+ SharedPrivate.assert_valid_opts!(TAG_CREATE_ALLOWED_OPTS, **options)
968
+ Private.create_tag(@execution_context, name, target, options)
969
+ tag_list(name).first
970
+ end
971
+
972
+ # Create a new tag
973
+ #
974
+ # @overload tag_add(name, options = {})
975
+ #
976
+ # @example Create a lightweight tag on HEAD
977
+ # repo.tag_add('v1.0.0')
978
+ #
979
+ # @example Create an annotated tag on HEAD
980
+ # repo.tag_add('v1.0.0', annotate: true, message: 'Release 1.0.0')
981
+ #
982
+ # @example Replace an existing tag on HEAD
983
+ # repo.tag_add('v1.0.0', force: true)
984
+ #
985
+ # @param name [String] the name of the tag to create
986
+ #
987
+ # @param options [Hash] options for creating the tag (same keys as
988
+ # {#tag_create})
989
+ #
850
990
  # @return [Git::Object::Tag] the newly created tag
851
991
  #
852
992
  # @overload tag_add(name, target, options = {})
@@ -861,8 +1001,8 @@ module Git
861
1001
  #
862
1002
  # @param target [String] the object to tag (commit SHA, branch name, etc.)
863
1003
  #
864
- # @param options [Hash] options for creating the tag (same keys as the
865
- # first overload)
1004
+ # @param options [Hash] options for creating the tag (same keys as
1005
+ # {#tag_create})
866
1006
  #
867
1007
  # @return [Git::Object::Tag] the newly created tag
868
1008
  #
@@ -893,17 +1033,31 @@ module Git
893
1033
  #
894
1034
  # @raise [Git::FailedError] if git exits with a non-zero exit status
895
1035
  #
1036
+ # @deprecated Use {#tag_create} instead
1037
+ #
1038
+ # {#tag_create} accepts the same `name`, `target`, and options and
1039
+ # returns a {Git::TagInfo} (an immutable value object) rather than a
1040
+ # {Git::Object::Tag}. It does not accept the `:d`/`:delete` form; use
1041
+ # {#tag_delete} for that. The {Git::Object::Tag} constructor is
1042
+ # deprecated too; this method silences it so one call emits one
1043
+ # warning, except that the `:d`/`:delete` form emits a second warning
1044
+ # of its own.
1045
+ #
1046
+ # @see #tag_create
1047
+ #
896
1048
  def tag_add(name, *args)
897
- options = args.last.is_a?(Hash) ? args.pop : {}
898
- target = args.first
1049
+ Git::Deprecation.warn(
1050
+ 'Git::Repository#tag_add is deprecated and will be removed in v6.0.0. ' \
1051
+ 'Use Git::Repository#tag_create instead.'
1052
+ )
1053
+ target, options = Private.tag_target_and_options(args)
899
1054
 
900
1055
  return Private.tag_add_delete_deprecated(self, name, target, options) if options[:d] || options[:delete]
901
1056
 
902
1057
  options = options.except(:d, :delete)
903
- SharedPrivate.assert_valid_opts!(TAG_ADD_ALLOWED_OPTS, **options)
904
- Private.validate_tag_options!(options)
905
- Git::Commands::Tag::Create.new(@execution_context).call(name, target, **options)
906
- tag(name)
1058
+ SharedPrivate.assert_valid_opts!(TAG_CREATE_ALLOWED_OPTS, **options)
1059
+ Private.create_tag(@execution_context, name, target, options)
1060
+ Git::Deprecation.silence { Git::Object::Tag.new(self, name) }
907
1061
  end
908
1062
 
909
1063
  # @overload add_tag(name, options = {})
@@ -931,7 +1085,14 @@ module Git
931
1085
  #
932
1086
  # @raise [Git::FailedError] if git exits with a non-zero exit status
933
1087
  #
934
- # @deprecated Use {#tag_add} instead
1088
+ # @deprecated Use {#tag_create} instead
1089
+ #
1090
+ # The warning names {#tag_add}, the replacement this method shipped
1091
+ # with, and {#tag_add} is deprecated as well, so a creation call emits
1092
+ # two warnings: one for this method and one for {#tag_add}. The delete
1093
+ # form `add_tag(name, d: true)` emits a third, for the deprecated `:d`
1094
+ # and `:delete` options on {#tag_add}; use {#tag_delete} for that. Go
1095
+ # straight to {#tag_create} for creation.
935
1096
  #
936
1097
  def add_tag(name, *)
937
1098
  Git::Deprecation.warn(
@@ -982,6 +1143,82 @@ module Git
982
1143
  module Private
983
1144
  module_function
984
1145
 
1146
+ # Splits the variadic `*args` of {ObjectOperations#tag_create} and
1147
+ # {ObjectOperations#tag_add} into the target and the options hash
1148
+ #
1149
+ # Both methods accept `(name, opts = {})` and `(name, target, opts = {})`,
1150
+ # so a trailing `Hash` is the options and anything before it is the
1151
+ # target.
1152
+ #
1153
+ # @param args [Array] the arguments after the tag name
1154
+ #
1155
+ # @param strict [Boolean] when `true`, raise instead of silently ignoring
1156
+ # a second positional argument before the options; `tag_create` is
1157
+ # strict, while the deprecated `tag_add` keeps its lenient behavior
1158
+ #
1159
+ # @return [Array((String, nil), Hash)] the two-element tuple
1160
+ # `[target, options]`; `target` is `nil` when only options were given
1161
+ #
1162
+ # @raise [ArgumentError] if `strict` is `true` and more than one
1163
+ # positional argument precedes the options hash
1164
+ #
1165
+ # @api private
1166
+ #
1167
+ def tag_target_and_options(args, strict: false)
1168
+ args = args.dup
1169
+ options = args.last.is_a?(Hash) ? args.pop : {}
1170
+ if strict && args.size > 1
1171
+ raise ArgumentError,
1172
+ "Expected at most one target before the options, got #{args.size}: #{args.inspect}"
1173
+ end
1174
+
1175
+ [args.first, options]
1176
+ end
1177
+
1178
+ # Validates the tag-creation options and runs `git tag`
1179
+ #
1180
+ # @param execution_context [Git::ExecutionContext::Repository] the
1181
+ # execution context for git commands
1182
+ #
1183
+ # @param name [String] the name of the tag to create
1184
+ #
1185
+ # @param target [String, nil] the object to tag, or `nil` for HEAD
1186
+ #
1187
+ # @param options [Hash] the tag-creation options, already checked
1188
+ # against the allowed keys (see {ObjectOperations#tag_create} for the
1189
+ # full list)
1190
+ #
1191
+ # @option options [Boolean, nil] :annotate (nil) make an annotated tag;
1192
+ # requires a message (alias: `:a`)
1193
+ #
1194
+ # @option options [Boolean, nil] :sign (nil) make a signed tag; requires
1195
+ # a message (alias: `:s`)
1196
+ #
1197
+ # @option options [String] :local_user (nil) sign with the given key;
1198
+ # requires a message (alias: `:u`)
1199
+ #
1200
+ # @option options [String] :message (nil) the tag message (alias: `:m`)
1201
+ #
1202
+ # @option options [String] :file (nil) a file to read the tag message
1203
+ # from (alias: `:F`)
1204
+ #
1205
+ # @option options [Boolean, nil] :force (nil) replace an existing tag
1206
+ # (alias: `:f`)
1207
+ #
1208
+ # @return [void]
1209
+ #
1210
+ # @raise [ArgumentError] when an annotated or signed tag is requested
1211
+ # without a message
1212
+ #
1213
+ # @raise [Git::FailedError] if git exits with a non-zero exit status
1214
+ #
1215
+ # @api private
1216
+ #
1217
+ def create_tag(execution_context, name, target, options)
1218
+ validate_tag_options!(options)
1219
+ Git::Commands::Tag::Create.new(execution_context).call(name, target, **options)
1220
+ end
1221
+
985
1222
  # Validate that a message is present when an annotated or signed tag is
986
1223
  # requested
987
1224
  #