123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- #include "cache.h"
- #include "config.h"
- #include "refs.h"
- #include "object-store.h"
- #include "commit.h"
- #include "tree-walk.h"
- #include "attr.h"
- #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>...]"),
- N_("git archive --list"),
- N_("git archive --remote <repo> [--exec <cmd>] [<options>] <tree-ish> [<path>...]"),
- N_("git archive --remote <repo> [--exec <cmd>] --list"),
- NULL
- };
- static const struct archiver **archivers;
- static int nr_archivers;
- static int alloc_archivers;
- static int remote_allow_unreachable;
- void register_archiver(struct archiver *ar)
- {
- ALLOC_GROW(archivers, nr_archivers + 1, alloc_archivers);
- archivers[nr_archivers++] = ar;
- }
- void init_archivers(void)
- {
- init_tar_archiver();
- init_zip_archiver();
- }
- static void format_subst(const struct commit *commit,
- const char *src, size_t len,
- struct strbuf *buf)
- {
- char *to_free = NULL;
- struct strbuf fmt = STRBUF_INIT;
- struct pretty_print_context ctx = {0};
- ctx.date_mode.type = DATE_NORMAL;
- ctx.abbrev = DEFAULT_ABBREV;
- if (src == buf->buf)
- to_free = strbuf_detach(buf, NULL);
- for (;;) {
- const char *b, *c;
- b = memmem(src, len, "$Format:", 8);
- if (!b)
- break;
- c = memchr(b + 8, '$', (src + len) - b - 8);
- if (!c)
- break;
- strbuf_reset(&fmt);
- strbuf_add(&fmt, b + 8, c - b - 8);
- strbuf_add(buf, src, b - src);
- format_commit_message(commit, fmt.buf, buf, &ctx);
- len -= c + 1 - src;
- src = c + 1;
- }
- strbuf_add(buf, src, len);
- strbuf_release(&fmt);
- free(to_free);
- }
- static void *object_file_to_archive(const struct archiver_args *args,
- const char *path,
- const struct object_id *oid,
- unsigned int mode,
- enum object_type *type,
- unsigned long *sizep)
- {
- void *buffer;
- const struct commit *commit = args->convert ? args->commit : NULL;
- struct checkout_metadata meta;
- init_checkout_metadata(&meta, args->refname,
- args->commit_oid ? args->commit_oid :
- (args->tree ? &args->tree->object.oid : NULL), oid);
- path += args->baselen;
- buffer = read_object_file(oid, type, sizep);
- if (buffer && S_ISREG(mode)) {
- struct strbuf buf = STRBUF_INIT;
- size_t size = 0;
- strbuf_attach(&buf, buffer, *sizep, *sizep + 1);
- convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta);
- if (commit)
- format_subst(commit, buf.buf, buf.len, &buf);
- buffer = strbuf_detach(&buf, &size);
- *sizep = size;
- }
- return buffer;
- }
- struct directory {
- struct directory *up;
- struct object_id oid;
- 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 const struct attr_check *get_archive_attrs(struct index_state *istate,
- const char *path)
- {
- static struct attr_check *check;
- if (!check)
- check = attr_check_initl("export-ignore", "export-subst", NULL);
- git_check_attr(istate, path, check);
- return check;
- }
- static int check_attr_export_ignore(const struct attr_check *check)
- {
- return check && ATTR_TRUE(check->items[0].value);
- }
- static int check_attr_export_subst(const struct attr_check *check)
- {
- return check && ATTR_TRUE(check->items[1].value);
- }
- static int write_archive_entry(const struct object_id *oid, const char *base,
- int baselen, const char *filename, unsigned mode, int stage,
- void *context)
- {
- static struct strbuf path = STRBUF_INIT;
- struct archiver_context *c = context;
- struct archiver_args *args = c->args;
- write_archive_entry_fn_t write_entry = c->write_entry;
- int err;
- const char *path_without_prefix;
- unsigned long size;
- void *buffer;
- enum object_type type;
- args->convert = 0;
- strbuf_reset(&path);
- strbuf_grow(&path, PATH_MAX);
- strbuf_add(&path, args->base, args->baselen);
- strbuf_add(&path, base, baselen);
- strbuf_addstr(&path, filename);
- if (S_ISDIR(mode) || S_ISGITLINK(mode))
- strbuf_addch(&path, '/');
- path_without_prefix = path.buf + args->baselen;
- if (!S_ISDIR(mode)) {
- const struct attr_check *check;
- check = get_archive_attrs(args->repo->index, path_without_prefix);
- if (check_attr_export_ignore(check))
- return 0;
- args->convert = check_attr_export_subst(check);
- }
- if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
- if (args->verbose)
- fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
- err = write_entry(args, oid, path.buf, path.len, mode, NULL, 0);
- if (err)
- return err;
- return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
- }
- if (args->verbose)
- fprintf(stderr, "%.*s\n", (int)path.len, path.buf);
- /* Stream it? */
- if (S_ISREG(mode) && !args->convert &&
- oid_object_info(args->repo, oid, &size) == OBJ_BLOB &&
- size > big_file_threshold)
- return write_entry(args, oid, path.buf, path.len, mode, NULL, size);
- buffer = object_file_to_archive(args, path.buf, oid, mode, &type, &size);
- if (!buffer)
- return error(_("cannot read %s"), oid_to_hex(oid));
- err = write_entry(args, oid, path.buf, path.len, mode, buffer, size);
- free(buffer);
- return err;
- }
- static void queue_directory(const unsigned char *sha1,
- struct strbuf *base, const char *filename,
- unsigned mode, int stage, struct archiver_context *c)
- {
- struct directory *d;
- size_t len = st_add4(base->len, 1, strlen(filename), 1);
- d = xmalloc(st_add(sizeof(*d), len));
- d->up = c->bottom;
- d->baselen = base->len;
- d->mode = mode;
- d->stage = stage;
- c->bottom = d;
- d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename);
- hashcpy(d->oid.hash, 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->oid, 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 struct object_id *oid,
- struct strbuf *base, const char *filename,
- unsigned mode, int stage, void *context)
- {
- struct archiver_context *c = context;
- while (c->bottom &&
- !(base->len >= c->bottom->len &&
- !strncmp(base->buf, c->bottom->path, c->bottom->len))) {
- struct directory *next = c->bottom->up;
- free(c->bottom);
- c->bottom = next;
- }
- if (S_ISDIR(mode)) {
- size_t baselen = base->len;
- const struct attr_check *check;
- /* Borrow base, but restore its original value when done. */
- strbuf_addstr(base, filename);
- strbuf_addch(base, '/');
- check = get_archive_attrs(c->args->repo->index, base->buf);
- strbuf_setlen(base, baselen);
- if (check_attr_export_ignore(check))
- return 0;
- queue_directory(oid->hash, base, filename,
- mode, stage, c);
- return READ_TREE_RECURSIVE;
- }
- if (write_directory(c))
- return -1;
- return write_archive_entry(oid, base->buf, base->len, filename, mode,
- 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)
- {
- struct archiver_context context;
- 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;
- while (len > 1 && args->base[len - 2] == '/')
- len--;
- if (args->verbose)
- fprintf(stderr, "%.*s\n", (int)len, args->base);
- err = write_entry(args, &args->tree->object.oid, args->base,
- len, 040777, NULL, 0);
- if (err)
- return err;
- }
- memset(&context, 0, sizeof(context));
- context.args = args;
- context.write_entry = write_entry;
- /*
- * Setup index and instruct attr to read index only
- */
- if (!args->worktree_attributes) {
- memset(&opts, 0, sizeof(opts));
- opts.index_only = 1;
- opts.head_idx = -1;
- opts.src_index = args->repo->index;
- opts.dst_index = args->repo->index;
- opts.fn = oneway_merge;
- init_tree_desc(&t, args->tree->buffer, args->tree->size);
- if (unpack_trees(1, &t, &opts))
- return -1;
- git_attr_set_direction(GIT_ATTR_INDEX);
- }
- err = read_tree_recursive(args->repo, args->tree, "",
- 0, 0, &args->pathspec,
- queue_or_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;
- }
- 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;
- }
- static const struct archiver *lookup_archiver(const char *name)
- {
- int i;
- if (!name)
- return NULL;
- for (i = 0; i < nr_archivers; i++) {
- if (!strcmp(name, archivers[i]->name))
- return archivers[i];
- }
- return NULL;
- }
- struct path_exists_context {
- struct pathspec pathspec;
- struct archiver_args *args;
- };
- static int reject_entry(const struct object_id *oid, struct strbuf *base,
- const char *filename, unsigned mode,
- int stage, void *context)
- {
- int ret = -1;
- struct path_exists_context *ctx = context;
- if (S_ISDIR(mode)) {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addbuf(&sb, base);
- strbuf_addstr(&sb, filename);
- if (!match_pathspec(ctx->args->repo->index,
- &ctx->pathspec,
- sb.buf, sb.len, 0, NULL, 1))
- ret = READ_TREE_RECURSIVE;
- strbuf_release(&sb);
- }
- return ret;
- }
- static int path_exists(struct archiver_args *args, const char *path)
- {
- const char *paths[] = { path, NULL };
- struct path_exists_context ctx;
- int ret;
- ctx.args = args;
- parse_pathspec(&ctx.pathspec, 0, 0, "", paths);
- ctx.pathspec.recursive = 1;
- ret = read_tree_recursive(args->repo, args->tree, "",
- 0, 0, &ctx.pathspec,
- reject_entry, &ctx);
- clear_pathspec(&ctx.pathspec);
- return ret != 0;
- }
- static void parse_pathspec_arg(const char **pathspec,
- struct archiver_args *ar_args)
- {
- /*
- * must be consistent with parse_pathspec in path_exists()
- * Also if pathspec patterns are dependent, we're in big
- * trouble as we test each one separately
- */
- 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, *pathspec))
- die(_("pathspec '%s' did not match any files"), *pathspec);
- pathspec++;
- }
- }
- }
- static void parse_treeish_arg(const char **argv,
- struct archiver_args *ar_args, const char *prefix,
- int remote)
- {
- const char *name = argv[0];
- const struct object_id *commit_oid;
- time_t archive_time;
- struct tree *tree;
- const struct commit *commit;
- struct object_id oid;
- char *ref = NULL;
- /* Remotes are only allowed to fetch actual refs */
- if (remote && !remote_allow_unreachable) {
- const char *colon = strchrnul(name, ':');
- int refnamelen = colon - name;
- if (!dwim_ref(name, refnamelen, &oid, &ref, 0))
- die(_("no such ref: %.*s"), refnamelen, name);
- } else {
- dwim_ref(name, strlen(name), &oid, &ref, 0);
- }
- if (get_oid(name, &oid))
- die(_("not a valid object name: %s"), name);
- commit = lookup_commit_reference_gently(ar_args->repo, &oid, 1);
- if (commit) {
- commit_oid = &commit->object.oid;
- archive_time = commit->date;
- } else {
- commit_oid = NULL;
- archive_time = time(NULL);
- }
- tree = parse_tree_indirect(&oid);
- if (tree == NULL)
- die(_("not a tree object: %s"), oid_to_hex(&oid));
- if (prefix) {
- struct object_id tree_oid;
- unsigned short mode;
- int err;
- err = get_tree_entry(ar_args->repo,
- &tree->object.oid,
- prefix, &tree_oid,
- &mode);
- if (err || !S_ISDIR(mode))
- die(_("current working directory is untracked"));
- tree = parse_tree_indirect(&tree_oid);
- }
- ar_args->refname = ref;
- ar_args->tree = tree;
- ar_args->commit_oid = commit_oid;
- ar_args->commit = commit;
- 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) \
- OPT_SET_INT_F(s, NULL, v, "", p, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN)
- static int parse_archive_args(int argc, const char **argv,
- const struct archiver **ar, struct archiver_args *args,
- const char *name_hint, int is_remote)
- {
- const char *format = NULL;
- const char *base = NULL;
- const char *remote = NULL;
- const char *exec = NULL;
- const char *output = NULL;
- int compression_level = -1;
- int verbose = 0;
- int i;
- int list = 0;
- int worktree_attributes = 0;
- struct option opts[] = {
- OPT_GROUP(""),
- 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,
- N_("read .gitattributes in working directory")),
- OPT__VERBOSE(&verbose, N_("report archived files on stderr")),
- OPT__COMPR('0', &compression_level, N_("store only"), 0),
- OPT__COMPR('1', &compression_level, N_("compress faster"), 1),
- OPT__COMPR_HIDDEN('2', &compression_level, 2),
- OPT__COMPR_HIDDEN('3', &compression_level, 3),
- OPT__COMPR_HIDDEN('4', &compression_level, 4),
- OPT__COMPR_HIDDEN('5', &compression_level, 5),
- OPT__COMPR_HIDDEN('6', &compression_level, 6),
- OPT__COMPR_HIDDEN('7', &compression_level, 7),
- OPT__COMPR_HIDDEN('8', &compression_level, 8),
- OPT__COMPR('9', &compression_level, N_("compress better"), 9),
- OPT_GROUP(""),
- OPT_BOOL('l', "list", &list,
- N_("list supported archive formats")),
- OPT_GROUP(""),
- OPT_STRING(0, "remote", &remote, N_("repo"),
- N_("retrieve the archive from remote repository <repo>")),
- OPT_STRING(0, "exec", &exec, N_("command"),
- N_("path to the remote git-upload-archive command")),
- OPT_END()
- };
- argc = parse_options(argc, argv, NULL, opts, archive_usage, 0);
- if (remote)
- die(_("Unexpected option --remote"));
- if (exec)
- 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 = "";
- if (list) {
- for (i = 0; i < nr_archivers; i++)
- if (!is_remote || archivers[i]->flags & ARCHIVER_REMOTE)
- printf("%s\n", archivers[i]->name);
- exit(0);
- }
- if (!format && name_hint)
- format = archive_format_from_filename(name_hint);
- if (!format)
- format = "tar";
- /* We need at least one parameter -- tree-ish */
- if (argc < 1)
- usage_with_options(archive_usage, opts);
- *ar = lookup_archiver(format);
- if (!*ar || (is_remote && !((*ar)->flags & ARCHIVER_REMOTE)))
- die(_("Unknown archive format '%s'"), format);
- args->compression_level = Z_DEFAULT_COMPRESSION;
- if (compression_level != -1) {
- if ((*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS)
- args->compression_level = compression_level;
- else {
- die(_("Argument not supported for format '%s': -%d"),
- format, compression_level);
- }
- }
- args->verbose = verbose;
- args->base = base;
- args->baselen = strlen(base);
- args->worktree_attributes = worktree_attributes;
- return argc;
- }
- int write_archive(int argc, const char **argv, const char *prefix,
- struct repository *repo,
- const char *name_hint, int remote)
- {
- 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) {
- /*
- * We know this will die() with an error, so we could just
- * die ourselves; but its error message will be more specific
- * than what we could write here.
- */
- setup_git_directory();
- }
- parse_treeish_arg(argv, &args, prefix, remote);
- parse_pathspec_arg(argv + 1, &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)
- {
- int prefixlen = strlen(filename) - strlen(ext);
- /*
- * We need 1 character for the '.', and 1 character to ensure that the
- * prefix is non-empty (k.e., we don't match .tar.gz with no actual
- * filename).
- */
- if (prefixlen < 2 || filename[prefixlen - 1] != '.')
- return 0;
- return !strcmp(filename + prefixlen, ext);
- }
- const char *archive_format_from_filename(const char *filename)
- {
- int i;
- for (i = 0; i < nr_archivers; i++)
- if (match_extension(filename, archivers[i]->name))
- return archivers[i]->name;
- return NULL;
- }
|