/* * Copyright (c) 2006 Franck Bui-Huu * Copyright (c) 2006 Rene Scharfe */ #include "cache.h" #include "builtin.h" #include "archive.h" #include "commit.h" #include "tree-walk.h" #include "exec_cmd.h" #include "pkt-line.h" #include "sideband.h" #include "attr.h" static const char archive_usage[] = \ "git-archive --format= [--prefix=/] [--verbose] [] [path...]"; static struct archiver_desc { const char *name; write_archive_fn_t write_archive; parse_extra_args_fn_t parse_extra; } archivers[] = { { "tar", write_tar_archive, NULL }, { "zip", write_zip_archive, parse_extra_zip_args }, }; static int run_remote_archiver(const char *remote, int argc, const char **argv) { char *url, buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; struct child_process *conn; const char *exec = "git-upload-archive"; int exec_at = 0; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!prefixcmp(arg, "--exec=")) { if (exec_at) die("multiple --exec specified"); exec = arg + 7; exec_at = i; break; } } url = xstrdup(remote); conn = git_connect(fd, url, exec, 0); for (i = 1; i < argc; i++) { if (i == exec_at) continue; packet_write(fd[1], "argument %s\n", argv[i]); } packet_flush(fd[1]); len = packet_read_line(fd[0], buf, sizeof(buf)); if (!len) die("git-archive: expected ACK/NAK, got EOF"); if (buf[len-1] == '\n') buf[--len] = 0; if (strcmp(buf, "ACK")) { if (len > 5 && !prefixcmp(buf, "NACK ")) die("git-archive: NACK %s", buf + 5); die("git-archive: protocol error"); } len = packet_read_line(fd[0], buf, sizeof(buf)); if (len) die("git-archive: expected a flush"); /* Now, start reading from fd[0] and spit it out to stdout */ rv = recv_sideband("archive", fd[0], 1, 2); close(fd[0]); close(fd[1]); rv |= finish_connect(conn); return !!rv; } static void format_subst(const struct commit *commit, const char *src, size_t len, struct strbuf *buf) { char *to_free = NULL; struct strbuf fmt; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); strbuf_init(&fmt, 0); for (;;) { const char *b, *c; b = memmem(src, len, "$Format:", 8); if (!b || src + len < b + 9) break; c = memchr(b + 8, '$', len - 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); len -= c + 1 - src; src = c + 1; } strbuf_add(buf, src, len); strbuf_release(&fmt); free(to_free); } static int convert_to_archive(const char *path, const void *src, size_t len, struct strbuf *buf, const struct commit *commit) { static struct git_attr *attr_export_subst; struct git_attr_check check[1]; if (!commit) return 0; if (!attr_export_subst) attr_export_subst = git_attr("export-subst", 12); check[0].attr = attr_export_subst; if (git_checkattr(path, ARRAY_SIZE(check), check)) return 0; if (!ATTR_TRUE(check[0].value)) return 0; format_subst(commit, src, len, buf); return 1; } void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *sizep, const struct commit *commit) { void *buffer; buffer = read_sha1_file(sha1, type, sizep); if (buffer && S_ISREG(mode)) { struct strbuf buf; size_t size = 0; strbuf_init(&buf, 0); strbuf_attach(&buf, buffer, *sizep, *sizep + 1); convert_to_working_tree(path, buf.buf, buf.len, &buf); convert_to_archive(path, buf.buf, buf.len, &buf, commit); buffer = strbuf_detach(&buf, &size); *sizep = size; } return buffer; } static int init_archiver(const char *name, struct archiver *ar) { int rv = -1, i; for (i = 0; i < ARRAY_SIZE(archivers); i++) { if (!strcmp(name, archivers[i].name)) { memset(ar, 0, sizeof(*ar)); ar->name = archivers[i].name; ar->write_archive = archivers[i].write_archive; ar->parse_extra = archivers[i].parse_extra; rv = 0; break; } } return rv; } void parse_pathspec_arg(const char **pathspec, struct archiver_args *ar_args) { ar_args->pathspec = get_pathspec(ar_args->base, pathspec); } void parse_treeish_arg(const char **argv, struct archiver_args *ar_args, const char *prefix) { const char *name = argv[0]; const unsigned char *commit_sha1; time_t archive_time; struct tree *tree; const struct commit *commit; unsigned char sha1[20]; if (get_sha1(name, sha1)) die("Not a valid object name"); commit = lookup_commit_reference_gently(sha1, 1); if (commit) { commit_sha1 = commit->object.sha1; archive_time = commit->date; } else { commit_sha1 = NULL; archive_time = time(NULL); } tree = parse_tree_indirect(sha1); if (tree == NULL) die("not a tree object"); if (prefix) { unsigned char tree_sha1[20]; unsigned int mode; int err; err = get_tree_entry(tree->object.sha1, prefix, tree_sha1, &mode); if (err || !S_ISDIR(mode)) die("current working directory is untracked"); tree = parse_tree_indirect(tree_sha1); } ar_args->tree = tree; ar_args->commit_sha1 = commit_sha1; ar_args->commit = commit; ar_args->time = archive_time; } int parse_archive_args(int argc, const char **argv, struct archiver *ar) { const char *extra_argv[MAX_EXTRA_ARGS]; int extra_argc = 0; const char *format = "tar"; const char *base = ""; int verbose = 0; int i; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--list") || !strcmp(arg, "-l")) { for (i = 0; i < ARRAY_SIZE(archivers); i++) printf("%s\n", archivers[i].name); exit(0); } if (!strcmp(arg, "--verbose") || !strcmp(arg, "-v")) { verbose = 1; continue; } if (!prefixcmp(arg, "--format=")) { format = arg + 9; continue; } if (!prefixcmp(arg, "--prefix=")) { base = arg + 9; continue; } if (!strcmp(arg, "--")) { i++; break; } if (arg[0] == '-') { if (extra_argc > MAX_EXTRA_ARGS - 1) die("Too many extra options"); extra_argv[extra_argc++] = arg; continue; } break; } /* We need at least one parameter -- tree-ish */ if (argc - 1 < i) usage(archive_usage); if (init_archiver(format, ar) < 0) die("Unknown archive format '%s'", format); if (extra_argc) { if (!ar->parse_extra) die("'%s' format does not handle %s", ar->name, extra_argv[0]); ar->args.extra = ar->parse_extra(extra_argc, extra_argv); } ar->args.verbose = verbose; ar->args.base = base; return i; } static const char *extract_remote_arg(int *ac, const char **av) { int ix, iy, cnt = *ac; int no_more_options = 0; const char *remote = NULL; for (ix = iy = 1; ix < cnt; ix++) { const char *arg = av[ix]; if (!strcmp(arg, "--")) no_more_options = 1; if (!no_more_options) { if (!prefixcmp(arg, "--remote=")) { if (remote) die("Multiple --remote specified"); remote = arg + 9; continue; } if (arg[0] != '-') no_more_options = 1; } if (ix != iy) av[iy] = arg; iy++; } if (remote) { av[--cnt] = NULL; *ac = cnt; } return remote; } int cmd_archive(int argc, const char **argv, const char *prefix) { struct archiver ar; int tree_idx; const char *remote = NULL; remote = extract_remote_arg(&argc, argv); if (remote) return run_remote_archiver(remote, argc, argv); setvbuf(stderr, NULL, _IOLBF, BUFSIZ); memset(&ar, 0, sizeof(ar)); tree_idx = parse_archive_args(argc, argv, &ar); if (prefix == NULL) prefix = setup_git_directory(); argv += tree_idx; parse_treeish_arg(argv, &ar.args, prefix); parse_pathspec_arg(argv + 1, &ar.args); return ar.write_archive(&ar.args); }