#include "archive.h"
#include "parse-options.h"
#include "unpack-trees.h"
+ #include "dir.h"
static char const * const archive_usage[] = {
N_("git archive [options] <tree-ish> [<path>...]"),
static const struct archiver **archivers;
static int nr_archivers;
static int alloc_archivers;
+static int remote_allow_unreachable;
void register_archiver(struct archiver *ar)
{
check[1].attr = attr_export_subst;
}
+ struct directory {
+ struct directory *up;
+ unsigned char sha1[20];
+ int baselen, len;
+ unsigned mode;
+ int stage;
+ char path[FLEX_ARRAY];
+ };
+
struct archiver_context {
struct archiver_args *args;
write_archive_entry_fn_t write_entry;
+ struct directory *bottom;
};
static int write_archive_entry(const unsigned char *sha1, const char *base,
return write_entry(args, sha1, path.buf, path.len, mode);
}
+ static void queue_directory(const unsigned char *sha1,
+ const char *base, int baselen, const char *filename,
+ unsigned mode, int stage, struct archiver_context *c)
+ {
+ struct directory *d;
+ d = xmallocz(sizeof(*d) + baselen + 1 + strlen(filename));
+ d->up = c->bottom;
+ d->baselen = baselen;
+ d->mode = mode;
+ d->stage = stage;
+ c->bottom = d;
+ d->len = sprintf(d->path, "%.*s%s/", baselen, base, filename);
+ hashcpy(d->sha1, sha1);
+ }
+
+ static int write_directory(struct archiver_context *c)
+ {
+ struct directory *d = c->bottom;
+ int ret;
+
+ if (!d)
+ return 0;
+ c->bottom = d->up;
+ d->path[d->len - 1] = '\0'; /* no trailing slash */
+ ret =
+ write_directory(c) ||
+ write_archive_entry(d->sha1, d->path, d->baselen,
+ d->path + d->baselen, d->mode,
+ d->stage, c) != READ_TREE_RECURSIVE;
+ free(d);
+ return ret ? -1 : 0;
+ }
+
+ static int queue_or_write_archive_entry(const unsigned char *sha1,
+ const char *base, int baselen, const char *filename,
+ unsigned mode, int stage, void *context)
+ {
+ struct archiver_context *c = context;
+
+ while (c->bottom &&
+ !(baselen >= c->bottom->len &&
+ !strncmp(base, c->bottom->path, c->bottom->len))) {
+ struct directory *next = c->bottom->up;
+ free(c->bottom);
+ c->bottom = next;
+ }
+
+ if (S_ISDIR(mode)) {
+ queue_directory(sha1, base, baselen, filename,
+ mode, stage, c);
+ return READ_TREE_RECURSIVE;
+ }
+
+ if (write_directory(c))
+ return -1;
+ return write_archive_entry(sha1, base, baselen, filename, mode,
+ stage, context);
+ }
+
int write_archive_entries(struct archiver_args *args,
write_archive_entry_fn_t write_entry)
{
return err;
}
+ memset(&context, 0, sizeof(context));
context.args = args;
context.write_entry = write_entry;
}
err = read_tree_recursive(args->tree, "", 0, 0, &args->pathspec,
- write_archive_entry, &context);
+ args->pathspec.has_wildcard ?
+ queue_or_write_archive_entry :
+ write_archive_entry,
+ &context);
if (err == READ_TREE_RECURSIVE)
err = 0;
+ while (context.bottom) {
+ struct directory *next = context.bottom->up;
+ free(context.bottom);
+ context.bottom = next;
+ }
return err;
}
int baselen, const char *filename, unsigned mode,
int stage, void *context)
{
- return -1;
+ int ret = -1;
+ if (S_ISDIR(mode)) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_addstr(&sb, base);
+ strbuf_addstr(&sb, filename);
+ if (!match_pathspec(context, sb.buf, sb.len, 0, NULL, 1))
+ ret = READ_TREE_RECURSIVE;
+ strbuf_release(&sb);
+ }
+ return ret;
}
static int path_exists(struct tree *tree, const char *path)
int ret;
parse_pathspec(&pathspec, 0, 0, "", paths);
- ret = read_tree_recursive(tree, "", 0, 0, &pathspec, reject_entry, NULL);
+ pathspec.recursive = 1;
+ ret = read_tree_recursive(tree, "", 0, 0, &pathspec,
+ reject_entry, &pathspec);
free_pathspec(&pathspec);
return ret != 0;
}
parse_pathspec(&ar_args->pathspec, 0,
PATHSPEC_PREFER_FULL,
"", pathspec);
+ ar_args->pathspec.recursive = 1;
if (pathspec) {
while (*pathspec) {
if (**pathspec && !path_exists(ar_args->tree, *pathspec))
unsigned char sha1[20];
/* Remotes are only allowed to fetch actual refs */
- if (remote) {
+ if (remote && !remote_allow_unreachable) {
char *ref = NULL;
- const char *colon = strchr(name, ':');
- int refnamelen = colon ? colon - name : strlen(name);
+ const char *colon = strchrnul(name, ':');
+ int refnamelen = colon - name;
if (!dwim_ref(name, refnamelen, sha1, &ref))
die("no such ref: %.*s", refnamelen, name);
if (setup_prefix && prefix == NULL)
prefix = setup_git_directory_gently(&nongit);
+ git_config_get_bool("uploadarchive.allowunreachable", &remote_allow_unreachable);
git_config(git_default_config, NULL);
+
init_tar_archiver();
init_zip_archiver();
for header in *.paxheader
do
data=${header%.paxheader}.data &&
- if test -h $data -o -e $data
+ if test -h $data || test -e $data
then
path=$(get_pax_header $header path) &&
if test -n "$path"
'echo ignore me >a/ignored &&
echo ignored export-ignore >.git/info/attributes'
-test_expect_success \
- 'add files to repository' \
- 'find a -type f | xargs git update-index --add &&
- find a -type l | xargs git update-index --add &&
- treeid=`git write-tree` &&
- echo $treeid >treeid &&
- git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
- git commit-tree $treeid </dev/null)'
+test_expect_success 'add files to repository' '
+ git add a &&
+ GIT_COMMITTER_DATE="2005-05-27 22:00" git commit -m initial
+'
test_expect_success 'setup export-subst' '
echo "substfile?" export-subst >>.git/info/attributes &&
test_expect_success 'git archive on large files' '
test_config core.bigfilethreshold 1 &&
git archive HEAD >b3.tar &&
- test_cmp b.tar b3.tar
+ test_cmp_bin b.tar b3.tar
'
test_expect_success \
test_expect_success \
'git archive vs. the same in a bare repo' \
- 'test_cmp b.tar b3.tar'
+ 'test_cmp_bin b.tar b3.tar'
test_expect_success 'git archive with --output' \
'git archive --output=b4.tar HEAD &&
- test_cmp b.tar b4.tar'
+ test_cmp_bin b.tar b4.tar'
test_expect_success 'git archive --remote' \
'git archive --remote=. HEAD >b5.tar &&
- test_cmp b.tar b5.tar'
+ test_cmp_bin b.tar b5.tar'
test_expect_success \
'validate file modification time' \
test_expect_success 'git archive with --output, override inferred format' '
git archive --format=tar --output=d4.zip HEAD &&
- test_cmp b.tar d4.zip
+ test_cmp_bin b.tar d4.zip
'
test_expect_success \
test_expect_success 'clients cannot access unreachable commits' '
test_commit unreachable &&
- sha1=`git rev-parse HEAD` &&
+ sha1=$(git rev-parse HEAD) &&
git reset --hard HEAD^ &&
git archive $sha1 >remote.tar &&
test_must_fail git archive --remote=. $sha1 >remote.tar
'
+test_expect_success 'upload-archive can allow unreachable commits' '
+ test_commit unreachable1 &&
+ sha1=$(git rev-parse HEAD) &&
+ git reset --hard HEAD^ &&
+ git archive $sha1 >remote.tar &&
+ test_config uploadarchive.allowUnreachable true &&
+ git archive --remote=. $sha1 >remote.tar
+'
+
test_expect_success 'setup tar filters' '
git config tar.tar.foo.command "tr ab ba" &&
git config tar.bar.command "tr ab ba" &&
test_expect_success 'invoke tar filter by format' '
git archive --format=tar.foo HEAD >config.tar.foo &&
tr ab ba <config.tar.foo >config.tar &&
- test_cmp b.tar config.tar &&
+ test_cmp_bin b.tar config.tar &&
git archive --format=bar HEAD >config.bar &&
tr ab ba <config.bar >config.tar &&
- test_cmp b.tar config.tar
+ test_cmp_bin b.tar config.tar
'
test_expect_success 'invoke tar filter by extension' '
git archive -o config-implicit.tar.foo HEAD &&
- test_cmp config.tar.foo config-implicit.tar.foo &&
+ test_cmp_bin config.tar.foo config-implicit.tar.foo &&
git archive -o config-implicit.bar HEAD &&
- test_cmp config.tar.foo config-implicit.bar
+ test_cmp_bin config.tar.foo config-implicit.bar
'
test_expect_success 'default output format remains tar' '
git archive -o config-implicit.baz HEAD &&
- test_cmp b.tar config-implicit.baz
+ test_cmp_bin b.tar config-implicit.baz
'
test_expect_success 'extension matching requires dot' '
git archive -o config-implicittar.foo HEAD &&
- test_cmp b.tar config-implicittar.foo
+ test_cmp_bin b.tar config-implicittar.foo
'
test_expect_success 'only enabled filters are available remotely' '
test_must_fail git archive --remote=. --format=tar.foo HEAD \
>remote.tar.foo &&
git archive --remote=. --format=bar >remote.bar HEAD &&
- test_cmp remote.bar config.bar
+ test_cmp_bin remote.bar config.bar
'
test_expect_success GZIP 'git archive --format=tgz' '
test_expect_success GZIP 'git archive --format=tar.gz' '
git archive --format=tar.gz HEAD >j1.tar.gz &&
- test_cmp j.tgz j1.tar.gz
+ test_cmp_bin j.tgz j1.tar.gz
'
test_expect_success GZIP 'infer tgz from .tgz filename' '
git archive --output=j2.tgz HEAD &&
- test_cmp j.tgz j2.tgz
+ test_cmp_bin j.tgz j2.tgz
'
test_expect_success GZIP 'infer tgz from .tar.gz filename' '
git archive --output=j3.tar.gz HEAD &&
- test_cmp j.tgz j3.tar.gz
+ test_cmp_bin j.tgz j3.tar.gz
'
test_expect_success GZIP 'extract tgz file' '
gzip -d -c <j.tgz >j.tar &&
- test_cmp b.tar j.tar
+ test_cmp_bin b.tar j.tar
'
test_expect_success GZIP 'remote tar.gz is allowed by default' '
git archive --remote=. --format=tar.gz HEAD >remote.tar.gz &&
- test_cmp j.tgz remote.tar.gz
+ test_cmp_bin j.tgz remote.tar.gz
'
test_expect_success GZIP 'remote tar.gz can be disabled' '
>remote.tar.gz
'
+ test_expect_success 'archive and :(glob)' '
+ git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual &&
+ cat >expect <<EOF &&
+ a/
+ a/bin/
+ a/bin/sh
+ EOF
+ test_cmp expect actual
+ '
+
+ test_expect_success 'catch non-matching pathspec' '
+ test_must_fail git archive -v HEAD -- "*.abc" >/dev/null
+ '
+
test_done