Browse Source

archive: add --add-file

Allow users to append non-tracked files.  This simplifies the generation
of source packages with a few extra files, e.g. containing version
information.  They get the same access times and user information as
tracked files.

Signed-off-by: René Scharfe <l.s.r@web.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
maint
René Scharfe 4 years ago committed by Junio C Hamano
parent
commit
2947a7930d
  1. 6
      Documentation/git-archive.txt
  2. 86
      archive.c
  3. 2
      archive.h
  4. 29
      t/t5000-tar-tree.sh
  5. 28
      t/t5003-archive-zip.sh

6
Documentation/git-archive.txt

@ -55,6 +55,12 @@ OPTIONS @@ -55,6 +55,12 @@ OPTIONS
--output=<file>::
Write the archive to <file> instead of stdout.

--add-file=<file>::
Add a non-tracked file to the archive. Can be repeated to add
multiple files. The path of the file in the archive is built
by concatenating the value for `--prefix` (if any) and the
basename of <file>.

--worktree-attributes::
Look for attributes in .gitattributes files in the working tree
as well (see <<ATTRIBUTES>>).

86
archive.c

@ -266,6 +266,11 @@ static int queue_or_write_archive_entry(const struct object_id *oid, @@ -266,6 +266,11 @@ static int queue_or_write_archive_entry(const struct object_id *oid,
stage, context);
}

struct extra_file_info {
char *base;
struct stat stat;
};

int write_archive_entries(struct archiver_args *args,
write_archive_entry_fn_t write_entry)
{
@ -273,6 +278,10 @@ int write_archive_entries(struct archiver_args *args, @@ -273,6 +278,10 @@ int write_archive_entries(struct archiver_args *args,
struct unpack_trees_options opts;
struct tree_desc t;
int err;
struct strbuf path_in_archive = STRBUF_INIT;
struct strbuf content = STRBUF_INIT;
struct object_id fake_oid = null_oid;
int i;

if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
size_t len = args->baselen;
@ -318,6 +327,33 @@ int write_archive_entries(struct archiver_args *args, @@ -318,6 +327,33 @@ int write_archive_entries(struct archiver_args *args,
free(context.bottom);
context.bottom = next;
}

for (i = 0; i < args->extra_files.nr; i++) {
struct string_list_item *item = args->extra_files.items + i;
char *path = item->string;
struct extra_file_info *info = item->util;

put_be64(fake_oid.hash, i + 1);

strbuf_reset(&path_in_archive);
if (info->base)
strbuf_addstr(&path_in_archive, info->base);
strbuf_addstr(&path_in_archive, basename(path));

strbuf_reset(&content);
if (strbuf_read_file(&content, path, info->stat.st_size) < 0)
err = error_errno(_("could not read '%s'"), path);
else
err = write_entry(args, &fake_oid, path_in_archive.buf,
path_in_archive.len,
info->stat.st_mode,
content.buf, content.len);
if (err)
break;
}
strbuf_release(&path_in_archive);
strbuf_release(&content);

return err;
}

@ -457,6 +493,42 @@ static void parse_treeish_arg(const char **argv, @@ -457,6 +493,42 @@ static void parse_treeish_arg(const char **argv,
ar_args->time = archive_time;
}

static void extra_file_info_clear(void *util, const char *str)
{
struct extra_file_info *info = util;
free(info->base);
free(info);
}

static int add_file_cb(const struct option *opt, const char *arg, int unset)
{
struct archiver_args *args = opt->value;
const char **basep = (const char **)opt->defval;
const char *base = *basep;
char *path;
struct string_list_item *item;
struct extra_file_info *info;

if (unset) {
string_list_clear_func(&args->extra_files,
extra_file_info_clear);
return 0;
}

if (!arg)
return -1;

path = prefix_filename(args->prefix, arg);
item = string_list_append_nodup(&args->extra_files, path);
item->util = info = xmalloc(sizeof(*info));
info->base = xstrdup_or_null(base);
if (stat(path, &info->stat))
die(_("File not found: %s"), path);
if (!S_ISREG(info->stat.st_mode))
die(_("Not a regular file: %s"), path);
return 0;
}

#define OPT__COMPR(s, v, h, p) \
OPT_SET_INT_F(s, NULL, v, h, p, PARSE_OPT_NONEG)
#define OPT__COMPR_HIDDEN(s, v, p) \
@ -481,6 +553,9 @@ static int parse_archive_args(int argc, const char **argv, @@ -481,6 +553,9 @@ static int parse_archive_args(int argc, const char **argv,
OPT_STRING(0, "format", &format, N_("fmt"), N_("archive format")),
OPT_STRING(0, "prefix", &base, N_("prefix"),
N_("prepend prefix to each pathname in the archive")),
{ OPTION_CALLBACK, 0, "add-file", args, N_("file"),
N_("add untracked file to archive"), 0, add_file_cb,
(intptr_t)&base },
OPT_STRING('o', "output", &output, N_("file"),
N_("write the archive to this file")),
OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
@ -515,6 +590,8 @@ static int parse_archive_args(int argc, const char **argv, @@ -515,6 +590,8 @@ static int parse_archive_args(int argc, const char **argv,
die(_("Option --exec can only be used together with --remote"));
if (output)
die(_("Unexpected option --output"));
if (is_remote && args->extra_files.nr)
die(_("Options --add-file and --remote cannot be used together"));

if (!base)
base = "";
@ -561,11 +638,14 @@ int write_archive(int argc, const char **argv, const char *prefix, @@ -561,11 +638,14 @@ int write_archive(int argc, const char **argv, const char *prefix,
{
const struct archiver *ar = NULL;
struct archiver_args args;
int rc;

git_config_get_bool("uploadarchive.allowunreachable", &remote_allow_unreachable);
git_config(git_default_config, NULL);

args.repo = repo;
args.prefix = prefix;
string_list_init(&args.extra_files, 1);
argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote);
if (!startup_info->have_repository) {
/*
@ -579,7 +659,11 @@ int write_archive(int argc, const char **argv, const char *prefix, @@ -579,7 +659,11 @@ int write_archive(int argc, const char **argv, const char *prefix,
parse_treeish_arg(argv, &args, prefix, remote);
parse_pathspec_arg(argv + 1, &args);

return ar->write_archive(ar, &args);
rc = ar->write_archive(ar, &args);

string_list_clear_func(&args.extra_files, extra_file_info_clear);

return rc;
}

static int match_extension(const char *filename, const char *ext)

2
archive.h

@ -9,6 +9,7 @@ struct repository; @@ -9,6 +9,7 @@ struct repository;
struct archiver_args {
struct repository *repo;
const char *refname;
const char *prefix;
const char *base;
size_t baselen;
struct tree *tree;
@ -20,6 +21,7 @@ struct archiver_args { @@ -20,6 +21,7 @@ struct archiver_args {
unsigned int worktree_attributes : 1;
unsigned int convert : 1;
int compression_level;
struct string_list extra_files;
};

/* main api */

29
t/t5000-tar-tree.sh

@ -94,6 +94,16 @@ check_tar() { @@ -94,6 +94,16 @@ check_tar() {
'
}

check_added() {
dir=$1
path_in_fs=$2
path_in_archive=$3

test_expect_success " validate extra file $path_in_archive" '
diff -r $path_in_fs $dir/$path_in_archive
'
}

test_expect_success 'setup' '
test_oid_cache <<-EOF
obj sha1:19f9c8273ec45a8938e6999cb59b3ff66739902a
@ -164,6 +174,25 @@ test_expect_success 'git-archive --prefix=olde-' ' @@ -164,6 +174,25 @@ test_expect_success 'git-archive --prefix=olde-' '

check_tar with_olde-prefix olde-

test_expect_success 'git archive --add-file' '
echo untracked >untracked &&
git archive --add-file=untracked HEAD >with_untracked.tar
'

check_tar with_untracked
check_added with_untracked untracked untracked

test_expect_success 'git archive --add-file twice' '
echo untracked >untracked &&
git archive --prefix=one/ --add-file=untracked \
--prefix=two/ --add-file=untracked \
--prefix= HEAD >with_untracked2.tar
'

check_tar with_untracked2
check_added with_untracked2 untracked one/untracked
check_added with_untracked2 untracked two/untracked

test_expect_success 'git archive on large files' '
test_config core.bigfilethreshold 1 &&
git archive HEAD >b3.tar &&

28
t/t5003-archive-zip.sh

@ -72,6 +72,16 @@ check_zip() { @@ -72,6 +72,16 @@ check_zip() {
"
}

check_added() {
dir=$1
path_in_fs=$2
path_in_archive=$3

test_expect_success UNZIP " validate extra file $path_in_archive" '
diff -r $path_in_fs $dir/$path_in_archive
'
}

test_expect_success \
'populate workdir' \
'mkdir a &&
@ -188,4 +198,22 @@ test_expect_success 'git archive --format=zip on large files' ' @@ -188,4 +198,22 @@ test_expect_success 'git archive --format=zip on large files' '

check_zip large-compressed

test_expect_success 'git archive --format=zip --add-file' '
echo untracked >untracked &&
git archive --format=zip --add-file=untracked HEAD >with_untracked.zip
'

check_zip with_untracked
check_added with_untracked untracked untracked

test_expect_success 'git archive --format=zip --add-file twice' '
echo untracked >untracked &&
git archive --format=zip --prefix=one/ --add-file=untracked \
--prefix=two/ --add-file=untracked \
--prefix= HEAD >with_untracked2.zip
'
check_zip with_untracked2
check_added with_untracked2 untracked one/untracked
check_added with_untracked2 untracked two/untracked

test_done

Loading…
Cancel
Save