archive: add --mtime

Allow users to specify the modification time of archive entries. The new option --mtime uses approxidate() to parse a time specification and overrides the default of using the current time for trees and the commit time for tags and commits. It can be used to create a reproducible archive for a tree, or to use a specific mtime without creating a commit with GIT_COMMITTER_DATE set. This implementation doesn't support the negated form of the new option, i.e. --no-mtime is not accepted. It is not possible to have no mtime at all. We could use the Unix epoch or revert to the default behavior, but since negation is not necessary for the intended use it's left undecided for now. Requested-by: Raul E Rangel <rrangel@chromium.org> Suggested-by: demerphq <demerphq@gmail.com> Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Feb 18, 2023 at 09:36 UTC fd2da4b1ea871df317322f33e82cb3aa1f95ee10
4 files changed +32
Documentation/git-archive.txt
+5
@@ -86,6 +86,11 @@ cases, write an untracked file and use `--add-file` instead.
86 Look for attributes in .gitattributes files in the working tree
87 as well (see <<ATTRIBUTES>>).
88
89 +--mtime=<time>::
90 + Set modification time of archive entries. Without this option
91 + the committer time is used if `<tree-ish>` is a commit or tag,
92 + and the current time if it is a tree.
93 +
94 <extra>::
95 This can be any options that the archiver backend understands.
96 See next section.
archive.c
+7
@@ -472,6 +472,8 @@ static void parse_treeish_arg(const char **argv,
472 commit_oid = NULL;
473 archive_time = time(NULL);
474 }
475 + if (ar_args->mtime_option)
476 + archive_time = approxidate(ar_args->mtime_option);
477
478 tree = parse_tree_indirect(&oid);
479 if (!tree)
@@ -586,6 +588,7 @@ static int parse_archive_args(int argc, const char **argv,
588 const char *remote = NULL;
589 const char *exec = NULL;
590 const char *output = NULL;
591 + const char *mtime_option = NULL;
592 int compression_level = -1;
593 int verbose = 0;
594 int i;
@@ -607,6 +610,9 @@ static int parse_archive_args(int argc, const char **argv,
610 OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
611 N_("read .gitattributes in working directory")),
612 OPT__VERBOSE(&verbose, N_("report archived files on stderr")),
613 + { OPTION_STRING, 0, "mtime", &mtime_option, N_("time"),
614 + N_("set modification time of archive entries"),
615 + PARSE_OPT_NONEG },
616 OPT_NUMBER_CALLBACK(&compression_level,
617 N_("set compression level"), number_callback),
618 OPT_GROUP(""),
@@ -668,6 +674,7 @@ static int parse_archive_args(int argc, const char **argv,
674 args->base = base;
675 args->baselen = strlen(base);
676 args->worktree_attributes = worktree_attributes;
677 + args->mtime_option = mtime_option;
678
679 return argc;
680 }
archive.h
+1
@@ -16,6 +16,7 @@ struct archiver_args {
16 struct tree *tree;
17 const struct object_id *commit_oid;
18 const struct commit *commit;
19 + const char *mtime_option;
20 timestamp_t time;
21 struct pathspec pathspec;
22 unsigned int verbose : 1;
t/t5000-tar-tree.sh
+19
@@ -105,6 +105,18 @@ check_added() {
105 '
106 }
107
108 +check_mtime() {
109 + dir=$1
110 + path_in_archive=$2
111 + mtime=$3
112 +
113 + test_expect_success " validate mtime of $path_in_archive" '
114 + test-tool chmtime --get $dir/$path_in_archive >actual.mtime &&
115 + echo $mtime >expect.mtime &&
116 + test_cmp expect.mtime actual.mtime
117 + '
118 +}
119 +
120 test_expect_success 'setup' '
121 test_oid_cache <<-EOF
122 obj sha1:19f9c8273ec45a8938e6999cb59b3ff66739902a
@@ -174,6 +186,13 @@ test_expect_success 'git archive' '
186
187 check_tar b
188
189 +test_expect_success 'git archive --mtime' '
190 + git archive --mtime=2002-02-02T02:02:02-0200 HEAD >with_mtime.tar
191 +'
192 +
193 +check_tar with_mtime
194 +check_mtime with_mtime a/a 1012622522
195 +
196 test_expect_success 'git archive --prefix=prefix/' '
197 git archive --prefix=prefix/ HEAD >with_prefix.tar
198 '