git 5.6.0 → 6.0.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 +132 -16
- data/CONTRIBUTING.md +44 -31
- data/README.md +135 -67
- data/UPGRADING.md +915 -268
- data/git.gemspec +6 -8
- data/lib/git/author_info.rb +1 -2
- data/lib/git/commands/base.rb +22 -2
- data/lib/git/commands/cat_file/raw.rb +0 -68
- data/lib/git/commands/maintenance/register.rb +0 -4
- data/lib/git/commands/maintenance/run.rb +0 -4
- data/lib/git/commands/maintenance/start.rb +0 -4
- data/lib/git/commands/maintenance/stop.rb +0 -4
- data/lib/git/commands/maintenance/unregister.rb +0 -4
- data/lib/git/commands/maintenance.rb +0 -2
- data/lib/git/commands/show_ref/exclude_existing.rb +1 -1
- data/lib/git/commands/show_ref/exists.rb +0 -5
- data/lib/git/commands/show_ref/list.rb +1 -1
- data/lib/git/commands/show_ref/verify.rb +1 -1
- data/lib/git/commands/show_ref.rb +1 -1
- data/lib/git/commands/worktree/repair.rb +0 -5
- data/lib/git/diff_path_status.rb +2 -2
- data/lib/git/encoding_utils.rb +25 -0
- data/lib/git/errors.rb +23 -5
- data/lib/git/factories.rb +361 -125
- data/lib/git/log.rb +3 -75
- data/lib/git/object.rb +4 -185
- data/lib/git/path_resolver.rb +104 -36
- data/lib/git/repository/branching.rb +21 -189
- data/lib/git/repository/committing.rb +0 -25
- data/lib/git/repository/context_helpers.rb +249 -155
- data/lib/git/repository/diffing.rb +2 -44
- data/lib/git/repository/merging.rb +32 -54
- data/lib/git/repository/object_operations.rb +121 -338
- data/lib/git/repository/remote_operations.rb +7 -219
- data/lib/git/repository/staging.rb +6 -184
- data/lib/git/repository/stashing.rb +126 -130
- data/lib/git/repository/status_operations.rb +1 -54
- data/lib/git/repository/worktree_operations.rb +31 -118
- data/lib/git/repository.rb +20 -299
- data/lib/git/system_call_guard.rb +95 -0
- data/lib/git/version.rb +1 -1
- data/lib/git.rb +47 -364
- metadata +25 -19
- data/lib/git/author.rb +0 -46
- data/lib/git/branch.rb +0 -693
- data/lib/git/branches.rb +0 -194
- data/lib/git/remote.rb +0 -191
- data/lib/git/stash.rb +0 -124
- data/lib/git/stashes.rb +0 -198
- data/lib/git/status.rb +0 -384
- data/lib/git/worktree.rb +0 -180
- data/lib/git/worktrees.rb +0 -152
|
@@ -17,6 +17,7 @@ require 'git/parsers/grep'
|
|
|
17
17
|
require 'git/parsers/ls_tree'
|
|
18
18
|
require 'git/parsers/tag'
|
|
19
19
|
require 'git/repository/shared_private'
|
|
20
|
+
require 'git/system_call_guard'
|
|
20
21
|
require 'git/escaped_path'
|
|
21
22
|
require 'tempfile'
|
|
22
23
|
require 'zlib'
|
|
@@ -72,35 +73,34 @@ module Git
|
|
|
72
73
|
#
|
|
73
74
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
74
75
|
#
|
|
76
|
+
# @raise [Git::Error] if a block is given and the object content cannot be
|
|
77
|
+
# written to a temporary file
|
|
78
|
+
#
|
|
75
79
|
# @see https://git-scm.com/docs/git-cat-file git-cat-file documentation
|
|
76
80
|
#
|
|
77
|
-
def cat_file_contents(object)
|
|
81
|
+
def cat_file_contents(object, &block)
|
|
78
82
|
raise ArgumentError, "Invalid object: '#{object}'" if object&.start_with?('-')
|
|
79
83
|
|
|
80
84
|
return Git::Commands::CatFile::Raw.new(@execution_context).call(object, p: true).stdout unless block_given?
|
|
81
85
|
|
|
82
86
|
# Stream git output directly to a tempfile to avoid buffering large
|
|
83
87
|
# object content in memory when a block is given.
|
|
84
|
-
|
|
85
|
-
file
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
Git::SystemCallGuard.call('Failed to write the object content to a temporary file') do |guard|
|
|
89
|
+
Tempfile.create do |file|
|
|
90
|
+
file.binmode
|
|
91
|
+
Git::Commands::CatFile::Raw.new(@execution_context).call(object, p: true, out: file)
|
|
92
|
+
file.rewind
|
|
93
|
+
guard.unguarded { block.call(file) }
|
|
94
|
+
end
|
|
89
95
|
end
|
|
90
96
|
end
|
|
91
97
|
|
|
92
|
-
# Alias for {#cat_file_contents};
|
|
98
|
+
# Alias for {#cat_file_contents}; kept as a legacy name and not scheduled
|
|
99
|
+
# for removal
|
|
93
100
|
#
|
|
94
101
|
# @see #cat_file_contents
|
|
95
102
|
alias cat_file cat_file_contents
|
|
96
103
|
|
|
97
|
-
# Alias for {#cat_file_contents}
|
|
98
|
-
#
|
|
99
|
-
# @deprecated Use {#cat_file_contents} instead
|
|
100
|
-
#
|
|
101
|
-
# @see #cat_file_contents
|
|
102
|
-
alias object_contents cat_file_contents
|
|
103
|
-
|
|
104
104
|
# Returns the size of a git object in bytes
|
|
105
105
|
#
|
|
106
106
|
# @example Get the size of a commit object
|
|
@@ -125,13 +125,6 @@ module Git
|
|
|
125
125
|
Git::Commands::CatFile::Raw.new(@execution_context).call(object, s: true).stdout.chomp.to_i
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
# Alias for {#cat_file_size}
|
|
129
|
-
#
|
|
130
|
-
# @deprecated Use {#cat_file_size} instead
|
|
131
|
-
#
|
|
132
|
-
# @see #cat_file_size
|
|
133
|
-
alias object_size cat_file_size
|
|
134
|
-
|
|
135
128
|
# Returns the type of a git object
|
|
136
129
|
#
|
|
137
130
|
# @example Get the type of a commit reference
|
|
@@ -157,13 +150,6 @@ module Git
|
|
|
157
150
|
Git::Commands::CatFile::Raw.new(@execution_context).call(object, t: true).stdout.chomp
|
|
158
151
|
end
|
|
159
152
|
|
|
160
|
-
# Alias for {#cat_file_type}
|
|
161
|
-
#
|
|
162
|
-
# @deprecated Use {#cat_file_type} instead
|
|
163
|
-
#
|
|
164
|
-
# @see #cat_file_type
|
|
165
|
-
alias object_type cat_file_type
|
|
166
|
-
|
|
167
153
|
# Returns parsed commit data for the given git object
|
|
168
154
|
#
|
|
169
155
|
# @example Get commit data for HEAD
|
|
@@ -200,13 +186,6 @@ module Git
|
|
|
200
186
|
Git::Parsers::CatFile.parse_commit(result.stdout.split("\n"), object)
|
|
201
187
|
end
|
|
202
188
|
|
|
203
|
-
# Alias for {#cat_file_commit}
|
|
204
|
-
#
|
|
205
|
-
# @deprecated Use {#cat_file_commit} instead
|
|
206
|
-
#
|
|
207
|
-
# @see #cat_file_commit
|
|
208
|
-
alias commit_data cat_file_commit
|
|
209
|
-
|
|
210
189
|
# Returns parsed tag data for the given annotated tag object
|
|
211
190
|
#
|
|
212
191
|
# Does not work with lightweight tags. To list all annotated tags in a
|
|
@@ -256,13 +235,6 @@ module Git
|
|
|
256
235
|
Git::Parsers::CatFile.parse_tag(tdata, object)
|
|
257
236
|
end
|
|
258
237
|
|
|
259
|
-
# Alias for {#cat_file_tag}
|
|
260
|
-
#
|
|
261
|
-
# @deprecated Use {#cat_file_tag} instead
|
|
262
|
-
#
|
|
263
|
-
# @see #cat_file_tag
|
|
264
|
-
alias tag_data cat_file_tag
|
|
265
|
-
|
|
266
238
|
# Resolve a revision specifier to its full object ID
|
|
267
239
|
#
|
|
268
240
|
# Passes the given revision specifier to `git rev-parse` and returns the
|
|
@@ -292,12 +264,14 @@ module Git
|
|
|
292
264
|
Git::Commands::RevParse.new(@execution_context).call(objectish, '--', revs_only: true).stdout
|
|
293
265
|
end
|
|
294
266
|
|
|
295
|
-
alias revparse rev_parse
|
|
296
|
-
|
|
297
267
|
# Returns the SHA of a named tag
|
|
298
268
|
#
|
|
299
269
|
# Returns an empty string when the tag does not exist.
|
|
300
270
|
#
|
|
271
|
+
# Reads the loose ref file under `refs/tags` directly when it exists and
|
|
272
|
+
# falls back to `git show-ref` otherwise, including when the file cannot
|
|
273
|
+
# be read.
|
|
274
|
+
#
|
|
301
275
|
# @example Get the SHA of an existing tag
|
|
302
276
|
# repo.tag_sha('v1.0')
|
|
303
277
|
# #=> "abc1234567890abcdef1234567890abcdef123456"
|
|
@@ -315,9 +289,7 @@ module Git
|
|
|
315
289
|
def tag_sha(tag_name)
|
|
316
290
|
tags_dir = File.expand_path(File.join(@execution_context.git_dir, 'refs', 'tags'))
|
|
317
291
|
head = File.expand_path(File.join(tags_dir, tag_name))
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
Private.show_ref_tag_sha(@execution_context, tag_name)
|
|
292
|
+
Private.loose_tag_sha(tags_dir, head) || Private.show_ref_tag_sha(@execution_context, tag_name)
|
|
321
293
|
end
|
|
322
294
|
|
|
323
295
|
# Returns all recursive entries for a given tree object
|
|
@@ -395,13 +367,6 @@ module Git
|
|
|
395
367
|
Git::Commands::NameRev.new(@execution_context).call(commit_ish).stdout.split[1]
|
|
396
368
|
end
|
|
397
369
|
|
|
398
|
-
# Alias for {#name_rev}
|
|
399
|
-
#
|
|
400
|
-
# @deprecated Use {#name_rev} instead
|
|
401
|
-
#
|
|
402
|
-
# @see #name_rev
|
|
403
|
-
alias namerev name_rev
|
|
404
|
-
|
|
405
370
|
# Option keys accepted by {#ls_tree}
|
|
406
371
|
LS_TREE_ALLOWED_OPTS = %i[recursive path].freeze
|
|
407
372
|
private_constant :LS_TREE_ALLOWED_OPTS
|
|
@@ -621,20 +586,21 @@ module Git
|
|
|
621
586
|
#
|
|
622
587
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
623
588
|
#
|
|
589
|
+
# @raise [Git::Error] if the archive file cannot be written or gzip
|
|
590
|
+
# compression fails
|
|
591
|
+
#
|
|
592
|
+
# @note A failure leaves nothing behind: the staging and temporary files
|
|
593
|
+
# are removed and an existing `file` is not replaced.
|
|
594
|
+
#
|
|
624
595
|
# @see https://git-scm.com/docs/git-archive git-archive documentation
|
|
625
596
|
#
|
|
626
597
|
def archive(treeish, file = nil, opts = {})
|
|
627
598
|
SharedPrivate.assert_valid_opts!(ARCHIVE_ALLOWED_OPTS, **opts)
|
|
628
599
|
raise ArgumentError, "#{file.inspect} is a directory" if file && File.directory?(file)
|
|
629
600
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
Private.atomic_replace(tmp, file)
|
|
634
|
-
file
|
|
635
|
-
rescue StandardError
|
|
636
|
-
FileUtils.rm_f(tmp) if tmp
|
|
637
|
-
raise
|
|
601
|
+
Git::SystemCallGuard.call('Failed to write the archive') do
|
|
602
|
+
Private.write_archive(@execution_context, treeish, file, opts)
|
|
603
|
+
end
|
|
638
604
|
end
|
|
639
605
|
|
|
640
606
|
# Returns a blob object for the given object reference
|
|
@@ -701,50 +667,6 @@ module Git
|
|
|
701
667
|
Git::Object.new(self, objectish, 'tree')
|
|
702
668
|
end
|
|
703
669
|
|
|
704
|
-
# Returns a tag object for the given tag name
|
|
705
|
-
#
|
|
706
|
-
# Returns a {Git::Object::Tag} for `tag_name`. The returned object is
|
|
707
|
-
# either an annotated or a lightweight tag depending on the underlying
|
|
708
|
-
# ref type.
|
|
709
|
-
#
|
|
710
|
-
# @example Get a tag object
|
|
711
|
-
# repo.tag('v1.0')
|
|
712
|
-
# #=> #<Git::Object::Tag name="v1.0" ...>
|
|
713
|
-
#
|
|
714
|
-
# @param tag_name [String] the name of the tag
|
|
715
|
-
#
|
|
716
|
-
# @return [Git::Object::Tag] the tag object
|
|
717
|
-
#
|
|
718
|
-
# @raise [Git::UnexpectedResultError] if `tag_name` does not name an
|
|
719
|
-
# existing tag
|
|
720
|
-
#
|
|
721
|
-
# @raise [Git::FailedError] if the underlying `git show-ref` invocation
|
|
722
|
-
# exits with an unexpected status (i.e., outside the allowed 0..1 range)
|
|
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
|
-
#
|
|
740
|
-
def tag(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) }
|
|
746
|
-
end
|
|
747
|
-
|
|
748
670
|
# Returns the appropriate git object for the given object reference
|
|
749
671
|
#
|
|
750
672
|
# Runs `git cat-file -t` to determine the object type, then constructs
|
|
@@ -814,46 +736,7 @@ module Git
|
|
|
814
736
|
Git::Parsers::Tag.parse_list(result.stdout)
|
|
815
737
|
end
|
|
816
738
|
|
|
817
|
-
#
|
|
818
|
-
#
|
|
819
|
-
# Runs `git tag --list` with a machine-readable format, parses the output,
|
|
820
|
-
# and returns a {Git::Object::Tag} for each tag name.
|
|
821
|
-
#
|
|
822
|
-
# @example List the names of all tags
|
|
823
|
-
# repo.tags.map(&:name) #=> ["v1.0.0", "v2.0.0"]
|
|
824
|
-
#
|
|
825
|
-
# @example No tags exist
|
|
826
|
-
# repo.tags #=> []
|
|
827
|
-
#
|
|
828
|
-
# @return [Array<Git::Object::Tag>] one tag object per tag in the
|
|
829
|
-
# repository; empty when there are none
|
|
830
|
-
#
|
|
831
|
-
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
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
|
-
#
|
|
848
|
-
def tags
|
|
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) } }
|
|
854
|
-
end
|
|
855
|
-
|
|
856
|
-
# Option keys accepted by {#tag_create} and {#tag_add}
|
|
739
|
+
# Option keys accepted by {#tag_create}
|
|
857
740
|
TAG_CREATE_ALLOWED_OPTS = %i[
|
|
858
741
|
annotate a sign s no_sign local_user u force f message m file F
|
|
859
742
|
edit e no_edit trailer cleanup create_reflog
|
|
@@ -948,158 +831,28 @@ module Git
|
|
|
948
831
|
# @return [Git::TagInfo] the newly created tag
|
|
949
832
|
#
|
|
950
833
|
# @raise [ArgumentError] if unsupported options are provided, including the
|
|
951
|
-
# `:d` and `:delete` keys
|
|
952
|
-
# delete a tag
|
|
834
|
+
# `:d` and `:delete` keys; use {#tag_delete} to delete a tag
|
|
953
835
|
#
|
|
954
836
|
# @raise [ArgumentError] if an annotated or signed tag is requested without
|
|
955
837
|
# a message
|
|
956
838
|
#
|
|
957
839
|
# @raise [ArgumentError] if more than one positional argument follows the
|
|
958
|
-
# name (before any options hash)
|
|
959
|
-
# arguments and tagged the first
|
|
840
|
+
# name (before any options hash)
|
|
960
841
|
#
|
|
961
842
|
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
962
843
|
#
|
|
844
|
+
# @raise [Git::UnexpectedResultError] if the tag listing cannot be parsed
|
|
845
|
+
# or does not contain the new tag (for example, it was deleted by another
|
|
846
|
+
# process before the lookup)
|
|
847
|
+
#
|
|
963
848
|
# @see https://git-scm.com/docs/git-tag git-tag
|
|
964
849
|
#
|
|
965
850
|
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
|
-
#
|
|
990
|
-
# @return [Git::Object::Tag] the newly created tag
|
|
991
|
-
#
|
|
992
|
-
# @overload tag_add(name, target, options = {})
|
|
993
|
-
#
|
|
994
|
-
# @example Create a lightweight tag on a specific commit
|
|
995
|
-
# repo.tag_add('v1.0.0', 'abc123')
|
|
996
|
-
#
|
|
997
|
-
# @example Create an annotated tag on a specific commit
|
|
998
|
-
# repo.tag_add('v1.0.0', 'abc123', annotate: true, message: 'Release 1.0.0')
|
|
999
|
-
#
|
|
1000
|
-
# @param name [String] the name of the tag to create
|
|
1001
|
-
#
|
|
1002
|
-
# @param target [String] the object to tag (commit SHA, branch name, etc.)
|
|
1003
|
-
#
|
|
1004
|
-
# @param options [Hash] options for creating the tag (same keys as
|
|
1005
|
-
# {#tag_create})
|
|
1006
|
-
#
|
|
1007
|
-
# @return [Git::Object::Tag] the newly created tag
|
|
1008
|
-
#
|
|
1009
|
-
# @overload tag_add(name, delete_options)
|
|
1010
|
-
#
|
|
1011
|
-
# @deprecated Use {#tag_delete} instead.
|
|
1012
|
-
#
|
|
1013
|
-
# @example Delete a tag (deprecated)
|
|
1014
|
-
# repo.tag_add('v1.0.0', d: true)
|
|
1015
|
-
#
|
|
1016
|
-
# @param name [String] the name of the tag to delete
|
|
1017
|
-
#
|
|
1018
|
-
# @param delete_options [Hash{Symbol => Boolean}] deletion options;
|
|
1019
|
-
# only `:d` or `:delete` (set to `true`) is accepted — no other keys
|
|
1020
|
-
# and no `target` argument may be combined with this form
|
|
1021
|
-
#
|
|
1022
|
-
# @return [String] git's stdout from the delete
|
|
1023
|
-
#
|
|
1024
|
-
# @raise [ArgumentError] if a target is also provided
|
|
1025
|
-
#
|
|
1026
|
-
# @raise [ArgumentError] if options other than `:d`/`:delete` are also
|
|
1027
|
-
# provided
|
|
1028
|
-
#
|
|
1029
|
-
# @raise [ArgumentError] if unsupported options are provided
|
|
1030
|
-
#
|
|
1031
|
-
# @raise [ArgumentError] if an annotated or signed tag is requested without
|
|
1032
|
-
# a message
|
|
1033
|
-
#
|
|
1034
|
-
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
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
|
-
#
|
|
1048
|
-
def tag_add(name, *args)
|
|
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
851
|
target, options = Private.tag_target_and_options(args)
|
|
1054
|
-
|
|
1055
|
-
return Private.tag_add_delete_deprecated(self, name, target, options) if options[:d] || options[:delete]
|
|
1056
|
-
|
|
1057
|
-
options = options.except(:d, :delete)
|
|
1058
852
|
SharedPrivate.assert_valid_opts!(TAG_CREATE_ALLOWED_OPTS, **options)
|
|
1059
853
|
Private.create_tag(@execution_context, name, target, options)
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
# @overload add_tag(name, options = {})
|
|
1064
|
-
#
|
|
1065
|
-
# @param name [String] the name of the tag to create
|
|
1066
|
-
#
|
|
1067
|
-
# @param options [Hash] options for creating the tag
|
|
1068
|
-
#
|
|
1069
|
-
# @return [Git::Object::Tag] the newly created tag
|
|
1070
|
-
#
|
|
1071
|
-
# @overload add_tag(name, target, options = {})
|
|
1072
|
-
#
|
|
1073
|
-
# @param name [String] the name of the tag to create
|
|
1074
|
-
#
|
|
1075
|
-
# @param target [String] the object to tag (commit SHA, branch name, etc.)
|
|
1076
|
-
#
|
|
1077
|
-
# @param options [Hash] options for creating the tag
|
|
1078
|
-
#
|
|
1079
|
-
# @return [Git::Object::Tag] the newly created tag
|
|
1080
|
-
#
|
|
1081
|
-
# @raise [ArgumentError] if unsupported options are provided
|
|
1082
|
-
#
|
|
1083
|
-
# @raise [ArgumentError] if an annotated or signed tag is requested without
|
|
1084
|
-
# a message
|
|
1085
|
-
#
|
|
1086
|
-
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
1087
|
-
#
|
|
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.
|
|
1096
|
-
#
|
|
1097
|
-
def add_tag(name, *)
|
|
1098
|
-
Git::Deprecation.warn(
|
|
1099
|
-
'Git::Repository#add_tag is deprecated and will be removed in v6.0.0. ' \
|
|
1100
|
-
'Use Git::Repository#tag_add instead.'
|
|
1101
|
-
)
|
|
1102
|
-
tag_add(name, *)
|
|
854
|
+
tag_list(name).first ||
|
|
855
|
+
raise(Git::UnexpectedResultError, "tag was created but not found in the tag list: #{name}")
|
|
1103
856
|
end
|
|
1104
857
|
|
|
1105
858
|
# Delete a tag
|
|
@@ -1120,54 +873,34 @@ module Git
|
|
|
1120
873
|
result.stdout
|
|
1121
874
|
end
|
|
1122
875
|
|
|
1123
|
-
# @param name [String] the name of the tag to delete
|
|
1124
|
-
#
|
|
1125
|
-
# @return [String] git's stdout from the delete
|
|
1126
|
-
#
|
|
1127
|
-
# @raise [Git::FailedError] if git exits with a non-zero exit status
|
|
1128
|
-
#
|
|
1129
|
-
# @deprecated Use {#tag_delete} instead
|
|
1130
|
-
#
|
|
1131
|
-
def delete_tag(name)
|
|
1132
|
-
Git::Deprecation.warn(
|
|
1133
|
-
'Git::Repository#delete_tag is deprecated and will be removed in v6.0.0. ' \
|
|
1134
|
-
'Use Git::Repository#tag_delete instead.'
|
|
1135
|
-
)
|
|
1136
|
-
tag_delete(name)
|
|
1137
|
-
end
|
|
1138
|
-
|
|
1139
876
|
# Private helpers
|
|
1140
877
|
#
|
|
1141
878
|
# @api private
|
|
1142
879
|
#
|
|
1143
|
-
module Private
|
|
880
|
+
module Private # rubocop:disable Metrics/ModuleLength
|
|
1144
881
|
module_function
|
|
1145
882
|
|
|
1146
|
-
# Splits the variadic `*args` of {ObjectOperations#tag_create}
|
|
1147
|
-
#
|
|
883
|
+
# Splits the variadic `*args` of {ObjectOperations#tag_create} into the
|
|
884
|
+
# target and the options hash
|
|
1148
885
|
#
|
|
1149
|
-
#
|
|
886
|
+
# `tag_create` accepts `(name, opts = {})` and `(name, target, opts = {})`,
|
|
1150
887
|
# so a trailing `Hash` is the options and anything before it is the
|
|
1151
888
|
# target.
|
|
1152
889
|
#
|
|
1153
890
|
# @param args [Array] the arguments after the tag name
|
|
1154
891
|
#
|
|
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
892
|
# @return [Array((String, nil), Hash)] the two-element tuple
|
|
1160
893
|
# `[target, options]`; `target` is `nil` when only options were given
|
|
1161
894
|
#
|
|
1162
|
-
# @raise [ArgumentError] if
|
|
1163
|
-
#
|
|
895
|
+
# @raise [ArgumentError] if more than one positional argument precedes
|
|
896
|
+
# the options hash
|
|
1164
897
|
#
|
|
1165
898
|
# @api private
|
|
1166
899
|
#
|
|
1167
|
-
def tag_target_and_options(args
|
|
900
|
+
def tag_target_and_options(args)
|
|
1168
901
|
args = args.dup
|
|
1169
902
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
1170
|
-
if
|
|
903
|
+
if args.size > 1
|
|
1171
904
|
raise ArgumentError,
|
|
1172
905
|
"Expected at most one target before the options, got #{args.size}: #{args.inspect}"
|
|
1173
906
|
end
|
|
@@ -1258,39 +991,25 @@ module Git
|
|
|
1258
991
|
raise ArgumentError, 'Cannot create an annotated or signed tag without a message.'
|
|
1259
992
|
end
|
|
1260
993
|
|
|
1261
|
-
#
|
|
1262
|
-
#
|
|
1263
|
-
# Issues a deprecation warning and delegates to tag_delete. Raises
|
|
1264
|
-
# ArgumentError if a target or incompatible options are also supplied.
|
|
1265
|
-
#
|
|
1266
|
-
# @param facade [ObjectOperations] the calling facade instance
|
|
994
|
+
# Read the SHA from a loose tag ref file without forking git
|
|
1267
995
|
#
|
|
1268
|
-
#
|
|
996
|
+
# Returns `nil` when the ref is not a loose file under `tags_dir` or when
|
|
997
|
+
# the file cannot be read, so the caller falls back to `git show-ref`.
|
|
1269
998
|
#
|
|
1270
|
-
# @param
|
|
999
|
+
# @param tags_dir [String] absolute path of the `refs/tags` directory
|
|
1271
1000
|
#
|
|
1272
|
-
# @param
|
|
1001
|
+
# @param head [String] absolute path of the candidate loose ref file
|
|
1273
1002
|
#
|
|
1274
|
-
# @
|
|
1275
|
-
# `tag_add` form
|
|
1276
|
-
#
|
|
1277
|
-
# @option opts [Boolean] :delete (true) alias for `:d`
|
|
1278
|
-
#
|
|
1279
|
-
# @return [String] stdout from tag_delete
|
|
1003
|
+
# @return [String, nil] the SHA in the file, or `nil` to fall back to git
|
|
1280
1004
|
#
|
|
1281
1005
|
# @api private
|
|
1282
1006
|
#
|
|
1283
|
-
def
|
|
1284
|
-
|
|
1285
|
-
'Passing :d or :delete to tag_add is deprecated and will be removed in v6.0.0. ' \
|
|
1286
|
-
'Use tag_delete instead.'
|
|
1287
|
-
)
|
|
1288
|
-
raise ArgumentError, 'Cannot pass a target when using the :d/:delete option.' if target
|
|
1289
|
-
|
|
1290
|
-
extra = opts.keys - %i[d delete]
|
|
1291
|
-
raise ArgumentError, "Cannot combine :d/:delete with other options: #{extra.join(', ')}" unless extra.empty?
|
|
1007
|
+
def loose_tag_sha(tags_dir, head)
|
|
1008
|
+
return nil unless head.start_with?("#{tags_dir}#{File::SEPARATOR}") && File.file?(head)
|
|
1292
1009
|
|
|
1293
|
-
|
|
1010
|
+
File.read(head).chomp
|
|
1011
|
+
rescue SystemCallError
|
|
1012
|
+
nil
|
|
1294
1013
|
end
|
|
1295
1014
|
|
|
1296
1015
|
# Returns the direct SHA for a tag reference
|
|
@@ -1351,6 +1070,45 @@ module Git
|
|
|
1351
1070
|
File.dirname(File.expand_path(file))
|
|
1352
1071
|
end
|
|
1353
1072
|
|
|
1073
|
+
# Write a git archive, staging it in a temporary file first
|
|
1074
|
+
#
|
|
1075
|
+
# @param execution_context [Git::ExecutionContext] the execution context
|
|
1076
|
+
#
|
|
1077
|
+
# @param treeish [String] tree-ish to archive
|
|
1078
|
+
#
|
|
1079
|
+
# @param file [String, nil] destination path, or `nil` to keep the
|
|
1080
|
+
# temporary file
|
|
1081
|
+
#
|
|
1082
|
+
# @param opts [Hash] caller-supplied options (read-only)
|
|
1083
|
+
#
|
|
1084
|
+
# @option opts [String] :format ('zip') archive format (`'tar'`, `'zip'`,
|
|
1085
|
+
# or `'tgz'`)
|
|
1086
|
+
#
|
|
1087
|
+
# @option opts [Boolean, nil] :add_gzip (nil) apply gzip post-processing
|
|
1088
|
+
# to the generated archive
|
|
1089
|
+
#
|
|
1090
|
+
# @option opts [String] :prefix (nil) prefix for entries in the archive
|
|
1091
|
+
#
|
|
1092
|
+
# @option opts [String] :path (nil) path within `treeish` to archive
|
|
1093
|
+
#
|
|
1094
|
+
# @option opts [String] :remote (nil) remote repository from which to
|
|
1095
|
+
# retrieve the archive
|
|
1096
|
+
#
|
|
1097
|
+
# @return [String] path to the written archive file
|
|
1098
|
+
#
|
|
1099
|
+
# @api private
|
|
1100
|
+
#
|
|
1101
|
+
def write_archive(execution_context, treeish, file, opts)
|
|
1102
|
+
tmp = write_archive_tmp(execution_context, treeish, opts, dest_dir: staging_dir_for(file))
|
|
1103
|
+
return tmp unless file
|
|
1104
|
+
|
|
1105
|
+
atomic_replace(tmp, file)
|
|
1106
|
+
file
|
|
1107
|
+
rescue StandardError
|
|
1108
|
+
FileUtils.rm_f(tmp) if tmp
|
|
1109
|
+
raise
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1354
1112
|
# Write a git archive to a fresh temporary file and return its path
|
|
1355
1113
|
#
|
|
1356
1114
|
# Always writes to a new temporary file so that on error the caller's
|
|
@@ -1533,17 +1291,42 @@ module Git
|
|
|
1533
1291
|
#
|
|
1534
1292
|
# @return [void]
|
|
1535
1293
|
#
|
|
1294
|
+
# @raise [Git::Error] if zlib fails during compression, with the
|
|
1295
|
+
# `Zlib::Error` as `cause`
|
|
1296
|
+
#
|
|
1536
1297
|
# @api private
|
|
1537
1298
|
#
|
|
1538
1299
|
def apply_gzip(file)
|
|
1539
1300
|
gz_tmp = Tempfile.create('archive_gz', File.dirname(file)).tap(&:close).path
|
|
1540
|
-
|
|
1301
|
+
gzip_file(file, gz_tmp)
|
|
1541
1302
|
FileUtils.rm_f(file)
|
|
1542
1303
|
File.rename(gz_tmp, file)
|
|
1543
1304
|
rescue StandardError
|
|
1544
1305
|
FileUtils.rm_f(gz_tmp) if gz_tmp
|
|
1545
1306
|
raise
|
|
1546
1307
|
end
|
|
1308
|
+
|
|
1309
|
+
# Stream `src` through a {Zlib::GzipWriter} into `dest`
|
|
1310
|
+
#
|
|
1311
|
+
# A `Zlib::Error` is not a `SystemCallError`, so {Git::SystemCallGuard}
|
|
1312
|
+
# does not convert it; this method does, per ADR-0008.
|
|
1313
|
+
#
|
|
1314
|
+
# @param src [String] path to the file to compress
|
|
1315
|
+
#
|
|
1316
|
+
# @param dest [String] path to write the gzip stream to
|
|
1317
|
+
#
|
|
1318
|
+
# @return [void]
|
|
1319
|
+
#
|
|
1320
|
+
# @raise [Git::Error] if zlib fails during compression, with the
|
|
1321
|
+
# `Zlib::Error` as `cause`
|
|
1322
|
+
#
|
|
1323
|
+
# @api private
|
|
1324
|
+
#
|
|
1325
|
+
def gzip_file(src, dest)
|
|
1326
|
+
Zlib::GzipWriter.open(dest) { |gz| File.open(src, 'rb') { |f| IO.copy_stream(f, gz) } }
|
|
1327
|
+
rescue Zlib::Error => e
|
|
1328
|
+
raise Git::Error, "Failed to gzip the archive: #{e.message}"
|
|
1329
|
+
end
|
|
1547
1330
|
end
|
|
1548
1331
|
private_constant :Private
|
|
1549
1332
|
end
|