| /* |
| * 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=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [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); |
| } |