#include "tag.h"
#include "object.h"
#include "commit.h"
+#include "exec_cmd.h"
+#include <signal.h>
+#include <sys/poll.h>
+#include <sys/wait.h>
static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
#define MAX_HAS 256
#define MAX_NEEDS 256
static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0;
+static int use_thin_pack = 0;
static unsigned char has_sha1[MAX_HAS][20];
static unsigned char needs_sha1[MAX_NEEDS][20];
static unsigned int timeout = 0;
static void create_pack_file(void)
{
- int fd[2];
- pid_t pid;
+ /* Pipes between rev-list to pack-objects, pack-objects to us
+ * and pack-objects error stream for progress bar.
+ */
+ int lp_pipe[2], pu_pipe[2], pe_pipe[2];
+ pid_t pid_rev_list, pid_pack_objects;
int create_full_pack = (nr_our_refs == nr_needs && !nr_has);
+ char data[8193], progress[128];
+ int buffered = -1;
- if (pipe(fd) < 0)
+ if (pipe(lp_pipe) < 0)
die("git-upload-pack: unable to create pipe");
- pid = fork();
- if (pid < 0)
+ pid_rev_list = fork();
+ if (pid_rev_list < 0)
die("git-upload-pack: unable to fork git-rev-list");
- if (!pid) {
+ if (!pid_rev_list) {
int i;
int args;
- char **argv;
+ const char **argv;
char *buf;
char **p;
- if (create_full_pack)
+ if (create_full_pack) {
args = 10;
+ use_thin_pack = 0; /* no point doing it */
+ }
else
args = nr_has + nr_needs + 5;
- argv = xmalloc(args * sizeof(char *));
+ p = xmalloc(args * sizeof(char *));
+ argv = (const char **) p;
buf = xmalloc(args * 45);
- p = argv;
- dup2(fd[1], 1);
+ dup2(lp_pipe[1], 1);
close(0);
- close(fd[0]);
- close(fd[1]);
- *p++ = "git-rev-list";
- *p++ = "--objects";
+ close(lp_pipe[0]);
+ close(lp_pipe[1]);
+ *p++ = "rev-list";
+ *p++ = use_thin_pack ? "--objects-edge" : "--objects";
if (create_full_pack || MAX_NEEDS <= nr_needs)
*p++ = "--all";
else {
buf += 41;
}
*p++ = NULL;
- execvp("git-rev-list", argv);
+ execv_git_cmd(argv);
die("git-upload-pack: unable to exec git-rev-list");
}
- dup2(fd[0], 0);
- close(fd[0]);
- close(fd[1]);
- execlp("git-pack-objects", "git-pack-objects", "--stdout", NULL);
- die("git-upload-pack: unable to exec git-pack-objects");
+
+ if (pipe(pu_pipe) < 0)
+ die("git-upload-pack: unable to create pipe");
+ if (pipe(pe_pipe) < 0)
+ die("git-upload-pack: unable to create pipe");
+ pid_pack_objects = fork();
+ if (pid_pack_objects < 0) {
+ /* daemon sets things up to ignore TERM */
+ kill(pid_rev_list, SIGKILL);
+ die("git-upload-pack: unable to fork git-pack-objects");
+ }
+ if (!pid_pack_objects) {
+ dup2(lp_pipe[0], 0);
+ dup2(pu_pipe[1], 1);
+ dup2(pe_pipe[1], 2);
+
+ close(lp_pipe[0]);
+ close(lp_pipe[1]);
+ close(pu_pipe[0]);
+ close(pu_pipe[1]);
+ close(pe_pipe[0]);
+ close(pe_pipe[1]);
+ execl_git_cmd("pack-objects", "--stdout", "--progress", NULL);
+ kill(pid_rev_list, SIGKILL);
+ die("git-upload-pack: unable to exec git-pack-objects");
+ }
+
+ close(lp_pipe[0]);
+ close(lp_pipe[1]);
+
+ /* We read from pe_pipe[0] to capture stderr output for
+ * progress bar, and pu_pipe[0] to capture the pack data.
+ */
+ close(pe_pipe[1]);
+ close(pu_pipe[1]);
+
+ while (1) {
+ const char *who;
+ char *cp;
+ struct pollfd pfd[2];
+ pid_t pid;
+ int status;
+ ssize_t sz;
+ int pe, pu, pollsize;
+
+ pollsize = 0;
+ pe = pu = -1;
+
+ if (0 <= pu_pipe[0]) {
+ pfd[pollsize].fd = pu_pipe[0];
+ pfd[pollsize].events = POLLIN;
+ pu = pollsize;
+ pollsize++;
+ }
+ if (0 <= pe_pipe[0]) {
+ pfd[pollsize].fd = pe_pipe[0];
+ pfd[pollsize].events = POLLIN;
+ pe = pollsize;
+ pollsize++;
+ }
+
+ if (pollsize) {
+ if (poll(pfd, pollsize, -1) < 0) {
+ if (errno != EINTR) {
+ error("poll failed, resuming: %s",
+ strerror(errno));
+ sleep(1);
+ }
+ continue;
+ }
+ if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) {
+ /* Data ready; we keep the last byte
+ * to ourselves in case we detect
+ * broken rev-list, so that we can
+ * leave the stream corrupted. This
+ * is unfortunate -- unpack-objects
+ * would happily accept a valid pack
+ * data with trailing garbage, so
+ * appending garbage after we pass all
+ * the pack data is not good enough to
+ * signal breakage to downstream.
+ */
+ char *cp = data;
+ ssize_t outsz = 0;
+ if (0 <= buffered) {
+ *cp++ = buffered;
+ outsz++;
+ }
+ sz = read(pu_pipe[0], cp,
+ sizeof(data) - outsz);
+ if (0 < sz)
+ ;
+ else if (sz == 0) {
+ close(pu_pipe[0]);
+ pu_pipe[0] = -1;
+ }
+ else
+ goto fail;
+ sz += outsz;
+ if (1 < sz) {
+ buffered = data[sz-1] & 0xFF;
+ sz--;
+ }
+ else
+ buffered = -1;
+ sz = xwrite(1, data, sz);
+ if (sz < 0)
+ goto fail;
+ }
+ if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) {
+ /* Status ready; we do not use it for now,
+ * but later we will add side-band to send it
+ * to the other side.
+ */
+ sz = read(pe_pipe[0], progress,
+ sizeof(progress));
+ if (0 < sz)
+ write(2, progress, sz);
+ else if (sz == 0) {
+ close(pe_pipe[0]);
+ pe_pipe[0] = -1;
+ }
+ else
+ goto fail;
+ }
+ }
+
+ /* See if the children are still there */
+ if (pid_rev_list || pid_pack_objects) {
+ pid = waitpid(-1, &status, WNOHANG);
+ if (!pid)
+ continue;
+ who = ((pid == pid_rev_list) ? "git-rev-list" :
+ (pid == pid_pack_objects) ? "git-pack-objects" :
+ NULL);
+ if (!who) {
+ if (pid < 0) {
+ error("git-upload-pack: %s",
+ strerror(errno));
+ goto fail;
+ }
+ error("git-upload-pack: we weren't "
+ "waiting for %d", pid);
+ continue;
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) > 0) {
+ error("git-upload-pack: %s died with error.",
+ who);
+ goto fail;
+ }
+ if (pid == pid_rev_list)
+ pid_rev_list = 0;
+ if (pid == pid_pack_objects)
+ pid_pack_objects = 0;
+ if (pid_rev_list || pid_pack_objects)
+ continue;
+ }
+
+ /* both died happily */
+ if (pollsize)
+ continue;
+
+ /* flush the data */
+ if (0 <= buffered) {
+ data[0] = buffered;
+ sz = xwrite(1, data, 1);
+ if (sz < 0)
+ goto fail;
+ fprintf(stderr, "flushed.\n");
+ }
+ return;
+ }
+ fail:
+ if (pid_pack_objects)
+ kill(pid_pack_objects, SIGKILL);
+ if (pid_rev_list)
+ kill(pid_rev_list, SIGKILL);
+ die("git-upload-pack: aborting due to possible repository corruption on the remote side.");
}
static int got_sha1(char *hex, unsigned char *sha1)
o = parse_object(sha1);
if (!o)
die("oops (%s)", sha1_to_hex(sha1));
- if (o->type == commit_type) {
+ if (o->type == TYPE_COMMIT) {
struct commit_list *parents;
if (o->flags & THEY_HAVE)
return 0;
"expected to get sha, not '%s'", line);
if (strstr(line+45, "multi_ack"))
multi_ack = 1;
+ if (strstr(line+45, "thin-pack"))
+ use_thin_pack = 1;
/* We have sent all our refs already, and the other end
* should have chosen out of them; otherwise they are
static int send_ref(const char *refname, const unsigned char *sha1)
{
- static char *capabilities = "multi_ack";
+ static char *capabilities = "multi_ack thin-pack";
struct object *o = parse_object(sha1);
+ if (!o)
+ die("git-upload-pack: cannot find object %s:", sha1_to_hex(sha1));
+
if (capabilities)
packet_write(1, "%s %s%c%s\n", sha1_to_hex(sha1), refname,
0, capabilities);
o->flags |= OUR_REF;
nr_our_refs++;
}
- if (o->type == tag_type) {
+ if (o->type == TYPE_TAG) {
o = deref_tag(o, refname, 0);
packet_write(1, "%s %s^{}\n", sha1_to_hex(o->sha1), refname);
}