aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Documentation/git-archive.txt100
-rw-r--r--Documentation/git-upload-archive.txt37
-rw-r--r--Makefile8
-rw-r--r--archive.h47
-rw-r--r--builtin-archive.c263
-rw-r--r--builtin-tar-tree.c155
-rw-r--r--builtin-upload-archive.c174
-rw-r--r--builtin-zip-tree.c45
-rw-r--r--builtin.h2
-rw-r--r--daemon.c7
-rw-r--r--fetch-clone.c32
-rw-r--r--fetch-pack.c12
-rwxr-xr-xgenerate-cmdlist.sh1
-rw-r--r--git.c2
-rw-r--r--sideband.c74
-rw-r--r--sideband.h13
-rw-r--r--upload-pack.c60
18 files changed, 892 insertions, 142 deletions
diff --git a/.gitignore b/.gitignore
index 0d608fe12..90d6d7c66 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ git-apply
git-applymbox
git-applypatch
git-archimport
+git-archive
git-bisect
git-branch
git-cat-file
@@ -118,6 +119,7 @@ git-unpack-objects
git-update-index
git-update-ref
git-update-server-info
+git-upload-archive
git-upload-pack
git-upload-tar
git-var
diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt
new file mode 100644
index 000000000..913528d37
--- /dev/null
+++ b/Documentation/git-archive.txt
@@ -0,0 +1,100 @@
+git-archive(1)
+==============
+
+NAME
+----
+git-archive - Creates a archive of the files in the named tree
+
+
+SYNOPSIS
+--------
+'git-archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
+ [--remote=<repo>] <tree-ish> [path...]
+
+DESCRIPTION
+-----------
+Creates an archive of the specified format containing the tree
+structure for the named tree. If <prefix> is specified it is
+prepended to the filenames in the archive.
+
+'git-archive' behaves differently when given a tree ID versus when
+given a commit ID or tag ID. In the first case the current time is
+used as modification time of each file in the archive. In the latter
+case the commit time as recorded in the referenced commit object is
+used instead. Additionally the commit ID is stored in a global
+extended pax header if the tar format is used; it can be extracted
+using 'git-get-tar-commit-id'. In ZIP files it is stored as a file
+comment.
+
+OPTIONS
+-------
+
+--format=<fmt>::
+ Format of the resulting archive: 'tar', 'zip'...
+
+--list::
+ Show all available formats.
+
+--prefix=<prefix>/::
+ Prepend <prefix>/ to each filename in the archive.
+
+<extra>::
+ This can be any options that the archiver backend understand.
+
+--remote=<repo>::
+ Instead of making a tar archive from local repository,
+ retrieve a tar archive from a remote repository.
+
+<tree-ish>::
+ The tree or commit to produce an archive for.
+
+path::
+ If one or more paths are specified, include only these in the
+ archive, otherwise include all files and subdirectories.
+
+CONFIGURATION
+-------------
+By default, file and directories modes are set to 0666 or 0777 in tar
+archives. It is possible to change this by setting the "umask" variable
+in the repository configuration as follows :
+
+[tar]
+ umask = 002 ;# group friendly
+
+The special umask value "user" indicates that the user's current umask
+will be used instead. The default value remains 0, which means world
+readable/writable files and directories.
+
+EXAMPLES
+--------
+git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)::
+
+ Create a tar archive that contains the contents of the
+ latest commit on the current branch, and extracts it in
+ `/var/tmp/junk` directory.
+
+git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
+
+ Create a compressed tarball for v1.4.0 release.
+
+git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz::
+
+ Create a compressed tarball for v1.4.0 release, but without a
+ global extended pax header.
+
+git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip::
+
+ Put everything in the current head's Documentation/ directory
+ into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
+
+Author
+------
+Written by Franck Bui-Huu and Rene Scharfe.
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt
new file mode 100644
index 000000000..388bb53d2
--- /dev/null
+++ b/Documentation/git-upload-archive.txt
@@ -0,0 +1,37 @@
+git-upload-archive(1)
+====================
+
+NAME
+----
+git-upload-archive - Send archive
+
+
+SYNOPSIS
+--------
+'git-upload-archive' <directory>
+
+DESCRIPTION
+-----------
+Invoked by 'git-archive --remote' and sends a generated archive to the
+other end over the git protocol.
+
+This command is usually not invoked directly by the end user. The UI
+for the protocol is on the 'git-archive' side, and the program pair
+is meant to be used to get an archive from a remote repository.
+
+OPTIONS
+-------
+<directory>::
+ The repository to get a tar archive from.
+
+Author
+------
+Written by Franck Bui-Huu.
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/Makefile b/Makefile
index b9938acd4..b98745045 100644
--- a/Makefile
+++ b/Makefile
@@ -234,8 +234,8 @@ LIB_FILE=libgit.a
XDIFF_LIB=xdiff/lib.a
LIB_H = \
- blob.h cache.h commit.h csum-file.h delta.h \
- diff.h object.h pack.h pkt-line.h quote.h refs.h \
+ archive.h blob.h cache.h commit.h csum-file.h delta.h \
+ diff.h object.h pack.h pkt-line.h quote.h refs.h sideband.h \
run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h
@@ -247,7 +247,7 @@ DIFF_OBJS = \
LIB_OBJS = \
blob.o commit.o connect.o csum-file.o cache-tree.o base85.o \
date.o diff-delta.o entry.o exec_cmd.o ident.o lockfile.o \
- object.o pack-check.o patch-delta.o path.o pkt-line.o \
+ object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \
quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
@@ -258,6 +258,7 @@ LIB_OBJS = \
BUILTIN_OBJS = \
builtin-add.o \
builtin-apply.o \
+ builtin-archive.o \
builtin-cat-file.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
@@ -294,6 +295,7 @@ BUILTIN_OBJS = \
builtin-unpack-objects.o \
builtin-update-index.o \
builtin-update-ref.o \
+ builtin-upload-archive.o \
builtin-upload-tar.o \
builtin-verify-pack.o \
builtin-write-tree.o \
diff --git a/archive.h b/archive.h
new file mode 100644
index 000000000..16dcdb875
--- /dev/null
+++ b/archive.h
@@ -0,0 +1,47 @@
+#ifndef ARCHIVE_H
+#define ARCHIVE_H
+
+#define MAX_EXTRA_ARGS 32
+#define MAX_ARGS (MAX_EXTRA_ARGS + 32)
+
+struct archiver_args {
+ const char *base;
+ struct tree *tree;
+ const unsigned char *commit_sha1;
+ time_t time;
+ const char **pathspec;
+ unsigned int verbose : 1;
+ void *extra;
+};
+
+typedef int (*write_archive_fn_t)(struct archiver_args *);
+
+typedef void *(*parse_extra_args_fn_t)(int argc, const char **argv);
+
+struct archiver {
+ const char *name;
+ struct archiver_args args;
+ write_archive_fn_t write_archive;
+ parse_extra_args_fn_t parse_extra;
+};
+
+extern struct archiver archivers[];
+
+extern int parse_archive_args(int argc,
+ const char **argv,
+ struct archiver *ar);
+
+extern void parse_treeish_arg(const char **treeish,
+ struct archiver_args *ar_args,
+ const char *prefix);
+
+extern void parse_pathspec_arg(const char **pathspec,
+ struct archiver_args *args);
+/*
+ * Archive-format specific backends.
+ */
+extern int write_tar_archive(struct archiver_args *);
+extern int write_zip_archive(struct archiver_args *);
+extern void *parse_extra_zip_args(int argc, const char **argv);
+
+#endif /* ARCHIVE_H */
diff --git a/builtin-archive.c b/builtin-archive.c
new file mode 100644
index 000000000..6dabdee20
--- /dev/null
+++ b/builtin-archive.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ * Copyright (c) 2006 Rene Scharfe
+ */
+#include <time.h>
+#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"
+
+static const char archive_usage[] = \
+"git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
+
+struct archiver archivers[] = {
+ {
+ .name = "tar",
+ .write_archive = write_tar_archive,
+ },
+ {
+ .name = "zip",
+ .write_archive = write_zip_archive,
+ .parse_extra = 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;
+ pid_t pid;
+ const char *exec = "git-upload-archive";
+ int exec_at = 0;
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (!strncmp("--exec=", arg, 7)) {
+ if (exec_at)
+ die("multiple --exec specified");
+ exec = arg + 7;
+ exec_at = i;
+ break;
+ }
+ }
+
+ url = xstrdup(remote);
+ pid = git_connect(fd, url, exec);
+ if (pid < 0)
+ return pid;
+
+ 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 && !strncmp(buf, "NACK ", 5))
+ 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, buf, sizeof(buf));
+ close(fd[0]);
+ rv |= finish_connect(pid);
+
+ return !!rv;
+}
+
+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)) {
+ memcpy(ar, &archivers[i], sizeof(struct archiver));
+ 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;
+ 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");
+
+ free(tree);
+ tree = parse_tree_indirect(tree_sha1);
+ }
+ ar_args->tree = tree;
+ ar_args->commit_sha1 = commit_sha1;
+ 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 = NULL; /* might want to default to "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 (!strncmp(arg, "--format=", 9)) {
+ format = arg + 9;
+ continue;
+ }
+ if (!strncmp(arg, "--prefix=", 9)) {
+ 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 (!format)
+ die("You must specify an archive format");
+ 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 (!strncmp(arg, "--remote=", 9)) {
+ 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);
+
+ setlinebuf(stderr);
+
+ 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);
+}
diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c
index fa666f78c..437eb726a 100644
--- a/builtin-tar-tree.c
+++ b/builtin-tar-tree.c
@@ -3,12 +3,12 @@
*/
#include <time.h>
#include "cache.h"
-#include "tree-walk.h"
#include "commit.h"
#include "strbuf.h"
#include "tar.h"
#include "builtin.h"
#include "pkt-line.h"
+#include "archive.h"
#define RECORDSIZE (512)
#define BLOCKSIZE (RECORDSIZE * 20)
@@ -21,6 +21,7 @@ static unsigned long offset;
static time_t archive_time;
static int tar_umask;
+static int verbose;
/* writes out the whole block, but only if it is full */
static void write_if_needed(void)
@@ -168,6 +169,8 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path,
mode = 0100666;
sprintf(header.name, "%s.paxheader", sha1_to_hex(sha1));
} else {
+ if (verbose)
+ fprintf(stderr, "%.*s\n", path->len, path->buf);
if (S_ISDIR(mode)) {
*header.typeflag = TYPEFLAG_DIR;
mode = (mode | 0777) & ~tar_umask;
@@ -244,37 +247,6 @@ static void write_global_extended_header(const unsigned char *sha1)
free(ext_header.buf);
}
-static void traverse_tree(struct tree_desc *tree, struct strbuf *path)
-{
- int pathlen = path->len;
- struct name_entry entry;
-
- while (tree_entry(tree, &entry)) {
- void *eltbuf;
- char elttype[20];
- unsigned long eltsize;
-
- eltbuf = read_sha1_file(entry.sha1, elttype, &eltsize);
- if (!eltbuf)
- die("cannot read %s", sha1_to_hex(entry.sha1));
-
- path->len = pathlen;
- strbuf_append_string(path, entry.path);
- if (S_ISDIR(entry.mode))
- strbuf_append_string(path, "/");
-
- write_entry(entry.sha1, path, entry.mode, eltbuf, eltsize);
-
- if (S_ISDIR(entry.mode)) {
- struct tree_desc subtree;
- subtree.buf = eltbuf;
- subtree.size = eltsize;
- traverse_tree(&subtree, path);
- }
- free(eltbuf);
- }
-}
-
static int git_tar_config(const char *var, const char *value)
{
if (!strcmp(var, "tar.umask")) {
@@ -291,50 +263,95 @@ static int git_tar_config(const char *var, const char *value)
static int generate_tar(int argc, const char **argv, const char *prefix)
{
- unsigned char sha1[20], tree_sha1[20];
- struct commit *commit;
- struct tree_desc tree;
- struct strbuf current_path;
- void *buffer;
-
- current_path.buf = xmalloc(PATH_MAX);
- current_path.alloc = PATH_MAX;
- current_path.len = current_path.eof = 0;
+ struct archiver_args args;
+ int result;
+ char *base = NULL;
git_config(git_tar_config);
- switch (argc) {
- case 3:
- strbuf_append_string(&current_path, argv[2]);
- strbuf_append_string(&current_path, "/");
- /* FALLTHROUGH */
- case 2:
- if (get_sha1(argv[1], sha1))
- die("Not a valid object name %s", argv[1]);
- break;
- default:
+ memset(&args, 0, sizeof(args));
+ if (argc != 2 && argc != 3)
usage(tar_tree_usage);
+ if (argc == 3) {
+ int baselen = strlen(argv[2]);
+ base = xmalloc(baselen + 2);
+ memcpy(base, argv[2], baselen);
+ base[baselen] = '/';
+ base[baselen + 1] = '\0';
}
+ args.base = base;
+ parse_treeish_arg(argv + 1, &args, NULL);
- commit = lookup_commit_reference_gently(sha1, 1);
- if (commit) {
- write_global_extended_header(commit->object.sha1);
- archive_time = commit->date;
- } else
- archive_time = time(NULL);
-
- tree.buf = buffer = read_object_with_reference(sha1, tree_type,
- &tree.size, tree_sha1);
- if (!tree.buf)
- die("not a reference to a tag, commit or tree object: %s",
- sha1_to_hex(sha1));
-
- if (current_path.len > 0)
- write_entry(tree_sha1, &current_path, 040777, NULL, 0);
- traverse_tree(&tree, &current_path);
- write_trailer();
+ result = write_tar_archive(&args);
+ free(base);
+
+ return result;
+}
+
+static int write_tar_entry(const unsigned char *sha1,
+ const char *base, int baselen,
+ const char *filename, unsigned mode, int stage)
+{
+ static struct strbuf path;
+ int filenamelen = strlen(filename);
+ void *buffer;
+ char type[20];
+ unsigned long size;
+
+ if (!path.alloc) {
+ path.buf = xmalloc(PATH_MAX);
+ path.alloc = PATH_MAX;
+ path.len = path.eof = 0;
+ }
+ if (path.alloc < baselen + filenamelen) {
+ free(path.buf);
+ path.buf = xmalloc(baselen + filenamelen);
+ path.alloc = baselen + filenamelen;
+ }
+ memcpy(path.buf, base, baselen);
+ memcpy(path.buf + baselen, filename, filenamelen);
+ path.len = baselen + filenamelen;
+ if (S_ISDIR(mode)) {
+ strbuf_append_string(&path, "/");
+ buffer = NULL;
+ size = 0;
+ } else {
+ buffer = read_sha1_file(sha1, type, &size);
+ if (!buffer)
+ die("cannot read %s", sha1_to_hex(sha1));
+ }
+
+ write_entry(sha1, &path, mode, buffer, size);
free(buffer);
- free(current_path.buf);
+
+ return READ_TREE_RECURSIVE;
+}
+
+int write_tar_archive(struct archiver_args *args)
+{
+ int plen = args->base ? strlen(args->base) : 0;
+
+ git_config(git_tar_config);
+
+ archive_time = args->time;
+ verbose = args->verbose;
+
+ if (args->commit_sha1)
+ write_global_extended_header(args->commit_sha1);
+
+ if (args->base && plen > 0 && args->base[plen - 1] == '/') {
+ char *base = xstrdup(args->base);
+ int baselen = strlen(base);
+
+ while (baselen > 0 && base[baselen - 1] == '/')
+ base[--baselen] = '\0';
+ write_tar_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
+ free(base);
+ }
+ read_tree_recursive(args->tree, args->base, plen, 0,
+ args->pathspec, write_tar_entry);
+ write_trailer();
+
return 0;
}
diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c
new file mode 100644
index 000000000..115a12dc1
--- /dev/null
+++ b/builtin-upload-archive.c
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2006 Franck Bui-Huu
+ */
+#include <time.h>
+#include "cache.h"
+#include "builtin.h"
+#include "archive.h"
+#include "pkt-line.h"
+#include "sideband.h"
+#include <sys/wait.h>
+#include <sys/poll.h>
+
+static const char upload_archive_usage[] =
+ "git-upload-archive <repo>";
+
+static const char deadchild[] =
+"git-upload-archive: archiver died with error";
+
+static const char lostchild[] =
+"git-upload-archive: archiver process was lost";
+
+
+static int run_upload_archive(int argc, const char **argv, const char *prefix)
+{
+ struct archiver ar;
+ const char *sent_argv[MAX_ARGS];
+ const char *arg_cmd = "argument ";
+ char *p, buf[4096];
+ int treeish_idx;
+ int sent_argc;
+ int len;
+
+ if (argc != 2)
+ usage(upload_archive_usage);
+
+ if (strlen(argv[1]) > sizeof(buf))
+ die("insanely long repository name");
+
+ strcpy(buf, argv[1]); /* enter-repo smudges its argument */
+
+ if (!enter_repo(buf, 0))
+ die("not a git archive");
+
+ /* put received options in sent_argv[] */
+ sent_argc = 1;
+ sent_argv[0] = "git-upload-archive";
+ for (p = buf;;) {
+ /* This will die if not enough free space in buf */
+ len = packet_read_line(0, p, (buf + sizeof buf) - p);
+ if (len == 0)
+ break; /* got a flush */
+ if (sent_argc > MAX_ARGS - 2)
+ die("Too many options (>29)");
+
+ if (p[len-1] == '\n') {
+ p[--len] = 0;
+ }
+ if (len < strlen(arg_cmd) ||
+ strncmp(arg_cmd, p, strlen(arg_cmd)))
+ die("'argument' token or flush expected");
+
+ len -= strlen(arg_cmd);
+ memmove(p, p + strlen(arg_cmd), len);
+ sent_argv[sent_argc++] = p;
+ p += len;
+ *p++ = 0;
+ }
+ sent_argv[sent_argc] = NULL;
+
+ /* parse all options sent by the client */
+ treeish_idx = parse_archive_args(sent_argc, sent_argv, &ar);
+
+ parse_treeish_arg(sent_argv + treeish_idx, &ar.args, prefix);
+ parse_pathspec_arg(sent_argv + treeish_idx + 1, &ar.args);
+
+ return ar.write_archive(&ar.args);
+}
+
+static void error_clnt(const char *fmt, ...)
+{
+ char buf[1024];
+ va_list params;
+ int len;
+
+ va_start(params, fmt);
+ len = vsprintf(buf, fmt, params);
+ va_end(params);
+ send_sideband(1, 3, buf, len, LARGE_PACKET_MAX);
+ die("sent error to the client: %s", buf);
+}
+
+static void process_input(int child_fd, int band)
+{
+ char buf[16384];
+ ssize_t sz = read(child_fd, buf, sizeof(buf));
+ if (sz < 0) {
+ if (errno != EINTR)
+ error_clnt("read error: %s\n", strerror(errno));
+ return;
+ }
+ send_sideband(1, band, buf, sz, LARGE_PACKET_MAX);
+}
+
+int cmd_upload_archive(int argc, const char **argv, const char *prefix)
+{
+ pid_t writer;
+ int fd1[2], fd2[2];
+ /*
+ * Set up sideband subprocess.
+ *
+ * We (parent) monitor and read from child, sending its fd#1 and fd#2
+ * multiplexed out to our fd#1. If the child dies, we tell the other
+ * end over channel #3.
+ */
+ if (pipe(fd1) < 0 || pipe(fd2) < 0) {
+ int err = errno;
+ packet_write(1, "NACK pipe failed on the remote side\n");
+ die("upload-archive: %s", strerror(err));
+ }
+ writer = fork();
+ if (writer < 0) {
+ int err = errno;
+ packet_write(1, "NACK fork failed on the remote side\n");
+ die("upload-archive: %s", strerror(err));
+ }
+ if (!writer) {
+ /* child - connect fd#1 and fd#2 to the pipe */
+ dup2(fd1[1], 1);
+ dup2(fd2[1], 2);
+ close(fd1[1]); close(fd2[1]);
+ close(fd1[0]); close(fd2[0]); /* we do not read from pipe */
+
+ exit(run_upload_archive(argc, argv, prefix));
+ }
+
+ /* parent - read from child, multiplex and send out to fd#1 */
+ close(fd1[1]); close(fd2[1]); /* we do not write to pipe */
+ packet_write(1, "ACK\n");
+ packet_flush(1);
+
+ while (1) {
+ struct pollfd pfd[2];
+ int status;
+
+ pfd[0].fd = fd1[0];
+ pfd[0].events = POLLIN;
+ pfd[1].fd = fd2[0];
+ pfd[1].events = POLLIN;
+ if (poll(pfd, 2, -1) < 0) {
+ if (errno != EINTR) {
+ error("poll failed resuming: %s",
+ strerror(errno));
+ sleep(1);
+ }
+ continue;
+ }
+ if (pfd[0].revents & POLLIN)
+ /* Data stream ready */
+ process_input(pfd[0].fd, 1);
+ if (pfd[1].revents & POLLIN)
+ /* Status stream ready */
+ process_input(pfd[1].fd, 2);
+ if ((pfd[0].revents | pfd[1].revents) == POLLIN)
+ continue;
+
+ if (waitpid(writer, &status, 0) < 0)
+ error_clnt("%s", lostchild);
+ else if (!WIFEXITED(status) || WEXITSTATUS(status) > 0)
+ error_clnt("%s", deadchild);
+ packet_flush(1);
+ break;
+ }
+ return 0;
+}
diff --git a/builtin-zip-tree.c b/builtin-zip-tree.c
index 1c1f6830c..52d4b7a17 100644
--- a/builtin-zip-tree.c
+++ b/builtin-zip-tree.c
@@ -8,10 +8,12 @@
#include "tree.h"
#include "quote.h"
#include "builtin.h"
+#include "archive.h"
static const char zip_tree_usage[] =
"git-zip-tree [-0|...|-9] <tree-ish> [ <base> ]";
+static int verbose;
static int zip_date;
static int zip_time;
@@ -163,6 +165,8 @@ static int write_zip_entry(const unsigned char *sha1,
crc = crc32(0, Z_NULL, 0);
path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen);
+ if (verbose)
+ fprintf(stderr, "%s\n", path);
if (pathlen > 0xffff) {
error("path too long (%d chars, SHA1: %s): %s", pathlen,
sha1_to_hex(sha1), path);
@@ -351,3 +355,44 @@ int cmd_zip_tree(int argc, const char **argv, const char *prefix)
return 0;
}
+
+int write_zip_archive(struct archiver_args *args)
+{
+ int plen = strlen(args->base);
+
+ dos_time(&args->time, &zip_date, &zip_time);
+
+ zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
+ zip_dir_size = ZIP_DIRECTORY_MIN_SIZE;
+ verbose = args->verbose;
+
+ if (args->base && plen > 0 && args->base[plen - 1] == '/') {
+ char *base = xstrdup(args->base);
+ int baselen = strlen(base);
+
+ while (baselen > 0 && base[baselen - 1] == '/')
+ base[--baselen] = '\0';
+ write_zip_entry(args->tree->object.sha1, "", 0, base, 040777, 0);
+ free(base);
+ }
+ read_tree_recursive(args->tree, args->base, plen, 0,
+ args->pathspec, write_zip_entry);
+ write_zip_trailer(args->commit_sha1);
+
+ free(zip_dir);
+
+ return 0;
+}
+
+void *parse_extra_zip_args(int argc, const char **argv)
+{
+ for (; argc > 0; argc--, argv++) {
+ const char *arg = argv[0];
+
+ if (arg[0] == '-' && isdigit(arg[1]) && arg[2] == '\0')
+ zlib_compression_level = arg[1] - '0';
+ else
+ die("Unknown argument for zip format: %s", arg);
+ }
+ return NULL;
+}
diff --git a/builtin.h b/builtin.h
index 398eafbf9..37a8c875a 100644
--- a/builtin.h
+++ b/builtin.h
@@ -14,6 +14,7 @@ extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
+extern int cmd_archive(int argc, const char **argv, const char *prefix);
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
@@ -55,6 +56,7 @@ extern int cmd_zip_tree(int argc, const char **argv, const char *prefix);
extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
extern int cmd_update_index(int argc, const char **argv, const char *prefix);
extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
extern int cmd_version(int argc, const char **argv, const char *prefix);
extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
diff --git a/daemon.c b/daemon.c
index b14d8083b..a2954a045 100644
--- a/daemon.c
+++ b/daemon.c
@@ -325,7 +325,14 @@ static int upload_pack(void)
return -1;
}
+static int upload_archive(void)
+{
+ execl_git_cmd("upload-archive", ".", NULL);
+ return -1;
+}
+
static struct daemon_service daemon_service[] = {
+ { "upload-archive", "uploadarch", upload_archive, 0, 1 },
{ "upload-pack", "uploadpack", upload_pack, 1, 1 },
};
diff --git a/fetch-clone.c b/fetch-clone.c
index c5cf4776f..b632ca043 100644
--- a/fetch-clone.c
+++ b/fetch-clone.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "exec_cmd.h"
#include "pkt-line.h"
+#include "sideband.h"
#include <sys/wait.h>
#include <sys/time.h>
@@ -114,36 +115,13 @@ static pid_t setup_sideband(int sideband, const char *me, int fd[2], int xd[2])
die("%s: unable to fork off sideband demultiplexer", me);
if (!side_pid) {
/* subprocess */
+ char buf[LARGE_PACKET_MAX];
+
close(fd[0]);
if (xd[0] != xd[1])
close(xd[1]);
- while (1) {
- char buf[1024];
- int len = packet_read_line(xd[0], buf, sizeof(buf));
- if (len == 0)
- break;
- if (len < 1)
- die("%s: protocol error: no band designator",
- me);
- len--;
- switch (buf[0] & 0xFF) {
- case 3:
- safe_write(2, "remote: ", 8);
- safe_write(2, buf+1, len);
- safe_write(2, "\n", 1);
- exit(1);
- case 2:
- safe_write(2, "remote: ", 8);
- safe_write(2, buf+1, len);
- continue;
- case 1:
- safe_write(fd[1], buf+1, len);
- continue;
- default:
- die("%s: protocol error: bad band #%d",
- me, (buf[0] & 0xFF));
- }
- }
+ if (recv_sideband(me, xd[0], fd[1], 2, buf, sizeof(buf)))
+ exit(1);
exit(0);
}
close(xd[0]);
diff --git a/fetch-pack.c b/fetch-pack.c
index 1b4d8272d..e8708aa80 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -166,10 +166,11 @@ static int find_common(int fd[2], unsigned char *result_sha1,
}
if (!fetching)
- packet_write(fd[1], "want %s%s%s%s\n",
+ packet_write(fd[1], "want %s%s%s%s%s\n",
sha1_to_hex(remote),
(multi_ack ? " multi_ack" : ""),
- (use_sideband ? " side-band" : ""),
+ (use_sideband == 2 ? " side-band-64k" : ""),
+ (use_sideband == 1 ? " side-band" : ""),
(use_thin_pack ? " thin-pack" : ""));
else
packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
@@ -426,7 +427,12 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
fprintf(stderr, "Server supports multi_ack\n");
multi_ack = 1;
}
- if (server_supports("side-band")) {
+ if (server_supports("side-band-64k")) {
+ if (verbose)
+ fprintf(stderr, "Server supports side-band-64k\n");
+ use_sideband = 2;
+ }
+ else if (server_supports("side-band")) {
if (verbose)
fprintf(stderr, "Server supports side-band\n");
use_sideband = 1;
diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh
index ec1eda20d..5450918be 100755
--- a/generate-cmdlist.sh
+++ b/generate-cmdlist.sh
@@ -12,6 +12,7 @@ struct cmdname_help common_cmds[] = {"
sort <<\EOF |
add
apply
+archive
bisect
branch
checkout
diff --git a/git.c b/git.c
index 8c182a5f5..70cafb026 100644
--- a/git.c
+++ b/git.c
@@ -220,6 +220,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
} commands[] = {
{ "add", cmd_add, RUN_SETUP },
{ "apply", cmd_apply },
+ { "archive", cmd_archive },
{ "cat-file", cmd_cat_file, RUN_SETUP },
{ "checkout-index", cmd_checkout_index, RUN_SETUP },
{ "check-ref-format", cmd_check_ref_format },
@@ -261,6 +262,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
{ "unpack-objects", cmd_unpack_objects, RUN_SETUP },
{ "update-index", cmd_update_index, RUN_SETUP },
{ "update-ref", cmd_update_ref, RUN_SETUP },
+ { "upload-archive", cmd_upload_archive },
{ "upload-tar", cmd_upload_tar },
{ "version", cmd_version },
{ "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
diff --git a/sideband.c b/sideband.c
new file mode 100644
index 000000000..1b14ff889
--- /dev/null
+++ b/sideband.c
@@ -0,0 +1,74 @@
+#include "pkt-line.h"
+#include "sideband.h"
+
+/*
+ * Receive multiplexed output stream over git native protocol.
+ * in_stream is the input stream from the remote, which carries data
+ * in pkt_line format with band designator. Demultiplex it into out
+ * and err and return error appropriately. Band #1 carries the
+ * primary payload. Things coming over band #2 is not necessarily
+ * error; they are usually informative message on the standard error
+ * stream, aka "verbose"). A message over band #3 is a signal that
+ * the remote died unexpectedly. A flush() concludes the stream.
+ */
+int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, int bufsz)
+{
+ while (1) {
+ int len = packet_read_line(in_stream, buf, bufsz);
+ if (len == 0)
+ break;
+ if (len < 1) {
+ len = sprintf(buf, "%s: protocol error: no band designator\n", me);
+ safe_write(err, buf, len);
+ return SIDEBAND_PROTOCOL_ERROR;
+ }
+ len--;
+ switch (buf[0] & 0xFF) {
+ case 3:
+ safe_write(err, "remote: ", 8);
+ safe_write(err, buf+1, len);
+ safe_write(err, "\n", 1);
+ return SIDEBAND_REMOTE_ERROR;
+ case 2:
+ safe_write(err, "remote: ", 8);
+ safe_write(err, buf+1, len);
+ continue;
+ case 1:
+ safe_write(out, buf+1, len);
+ continue;
+ default:
+ len = sprintf(buf + 1,
+ "%s: protocol error: bad band #%d\n",
+ me, buf[0] & 0xFF);
+ safe_write(err, buf+1, len);
+ return SIDEBAND_PROTOCOL_ERROR;
+ }
+ }
+ return 0;
+}
+
+/*
+ * fd is connected to the remote side; send the sideband data
+ * over multiplexed packet stream.
+ */
+ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max)
+{
+ ssize_t ssz = sz;
+ const char *p = data;
+
+ while (sz) {
+ unsigned n;
+ char hdr[5];
+
+ n = sz;
+ if (packet_max - 5 < n)
+ n = packet_max - 5;
+ sprintf(hdr, "%04x", n + 5);
+ hdr[4] = band;
+ safe_write(fd, hdr, 5);
+ safe_write(fd, p, n);
+ p += n;
+ sz -= n;
+ }
+ return ssz;
+}
diff --git a/sideband.h b/sideband.h
new file mode 100644
index 000000000..4872106fa
--- /dev/null
+++ b/sideband.h
@@ -0,0 +1,13 @@
+#ifndef SIDEBAND_H
+#define SIDEBAND_H
+
+#define SIDEBAND_PROTOCOL_ERROR -2
+#define SIDEBAND_REMOTE_ERROR -1
+
+#define DEFAULT_PACKET_MAX 1000
+#define LARGE_PACKET_MAX 65520
+
+int recv_sideband(const char *me, int in_stream, int out, int err, char *, int);
+ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
+
+#endif
diff --git a/upload-pack.c b/upload-pack.c
index 51ce936b0..b673d8cb9 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -4,6 +4,7 @@
#include "cache.h"
#include "refs.h"
#include "pkt-line.h"
+#include "sideband.h"
#include "tag.h"
#include "object.h"
#include "commit.h"
@@ -19,6 +20,9 @@ static int use_thin_pack;
static struct object_array have_obj;
static struct object_array want_obj;
static unsigned int timeout;
+/* 0 for no sideband,
+ * otherwise maximum packet size (up to 65520 bytes).
+ */
static int use_sideband;
static void reset_timeout(void)
@@ -33,45 +37,18 @@ static int strip(char *line, int len)
return len;
}
-#define PACKET_MAX 1000
static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
{
- ssize_t ssz;
- const char *p;
-
- if (!data) {
- if (!use_sideband)
- return 0;
- packet_flush(1);
- }
-
- if (!use_sideband) {
- if (fd == 3)
- /* emergency quit */
- fd = 2;
- if (fd == 2) {
- xwrite(fd, data, sz);
- return sz;
- }
- return safe_write(fd, data, sz);
- }
- p = data;
- ssz = sz;
- while (sz) {
- unsigned n;
- char hdr[5];
-
- n = sz;
- if (PACKET_MAX - 5 < n)
- n = PACKET_MAX - 5;
- sprintf(hdr, "%04x", n + 5);
- hdr[4] = fd;
- safe_write(1, hdr, 5);
- safe_write(1, p, n);
- p += n;
- sz -= n;
+ if (use_sideband)
+ return send_sideband(1, fd, data, sz, use_sideband);
+ if (fd == 3)
+ /* emergency quit */
+ fd = 2;
+ if (fd == 2) {
+ xwrite(fd, data, sz);
+ return sz;
}
- return ssz;
+ return safe_write(fd, data, sz);
}
static void create_pack_file(void)
@@ -308,7 +285,8 @@ static void create_pack_file(void)
goto fail;
fprintf(stderr, "flushed.\n");
}
- send_client_data(1, NULL, 0);
+ if (use_sideband)
+ packet_flush(1);
return;
}
fail:
@@ -413,8 +391,10 @@ static void receive_needs(void)
multi_ack = 1;
if (strstr(line+45, "thin-pack"))
use_thin_pack = 1;
- if (strstr(line+45, "side-band"))
- use_sideband = 1;
+ if (strstr(line+45, "side-band-64k"))
+ use_sideband = LARGE_PACKET_MAX;
+ else if (strstr(line+45, "side-band"))
+ use_sideband = DEFAULT_PACKET_MAX;
/* We have sent all our refs already, and the other end
* should have chosen out of them; otherwise they are
@@ -436,7 +416,7 @@ static void receive_needs(void)
static int send_ref(const char *refname, const unsigned char *sha1)
{
- static const char *capabilities = "multi_ack thin-pack side-band";
+ static const char *capabilities = "multi_ack thin-pack side-band side-band-64k";
struct object *o = parse_object(sha1);
if (!o)