Add "git-clone-pack" program to help with "git clone"
authorLinus Torvalds <torvalds@g5.osdl.org>
Tue, 5 Jul 2005 22:45:37 +0000 (15:45 -0700)
committerLinus Torvalds <torvalds@g5.osdl.org>
Tue, 5 Jul 2005 22:45:37 +0000 (15:45 -0700)
Makefile
clone-pack.c [new file with mode: 0644]

index 233d24a..877d10b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,7 @@ PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
        git-get-tar-commit-id git-apply git-stripspace \
        git-diff-stages git-rev-parse git-patch-id git-pack-objects \
        git-unpack-objects git-verify-pack git-receive-pack git-send-pack \
-       git-prune-packed git-fetch-pack git-upload-pack
+       git-prune-packed git-fetch-pack git-upload-pack git-clone-pack
 
 all: $(PROG)
 
diff --git a/clone-pack.c b/clone-pack.c
new file mode 100644 (file)
index 0000000..0337cec
--- /dev/null
@@ -0,0 +1,208 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char clone_pack_usage[] = "git-clone-pack [host:]directory [heads]*";
+static const char *exec = "git-upload-pack";
+
+struct ref {
+       struct ref *next;
+       unsigned char sha1[20];
+       char name[0];
+};
+
+struct ref *get_remote_refs(int fd, int nr_match, char **match)
+{
+       struct ref *ref_list = NULL, **next_ref = &ref_list;
+
+       for (;;) {
+               static char line[1000];
+               unsigned char sha1[20];
+               struct ref *ref;
+               char *refname;
+               int len;
+
+               len = packet_read_line(fd, line, sizeof(line));
+               if (!len)
+                       break;
+               if (line[len-1] == '\n')
+                       line[--len] = 0;
+               if (len < 42 || get_sha1_hex(line, sha1))
+                       die("git-fetch-pack: protocol error - expected ref descriptor, got '%s¤'", line);
+               refname = line+41;
+               len = len-40;
+               if (nr_match && !path_match(refname, nr_match, match))
+                       continue;
+               ref = xmalloc(sizeof(struct ref) + len);
+               ref->next = NULL;
+               memcpy(ref->sha1, sha1, 20);
+               memcpy(ref->name, refname, len);
+               *next_ref = ref;
+               next_ref = &ref->next;
+       }
+       return ref_list;
+}
+
+static void clone_handshake(int fd[2], struct ref *ref)
+{
+       unsigned char sha1[20];
+
+       while (ref) {
+               packet_write(fd[1], "want %s\n", sha1_to_hex(ref->sha1));
+               ref = ref->next;
+       }
+       packet_flush(fd[1]);
+
+       /* We don't have nuttin' */
+       packet_write(fd[1], "done\n");
+       if (get_ack(fd[0], sha1))
+               error("Huh! git-clone-pack got positive ack for %s", sha1_to_hex(sha1));
+}
+
+static int is_master(struct ref *ref)
+{
+       return !strcmp(ref->name, "refs/heads/master");
+}
+
+static void write_one_ref(struct ref *ref)
+{
+       char *path = git_path(ref->name);
+       int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       char *hex;
+
+       if (fd < 0)
+               die("unable to create ref %s", ref->name);
+       hex = sha1_to_hex(ref->sha1);
+       hex[40] = '\n';
+       if (write(fd, hex, 41) != 41)
+               die("unable to write ref %s", ref->name);
+       close(fd);
+}
+
+static void write_refs(struct ref *ref)
+{
+       struct ref *head = NULL, *head_ptr, *master_ref;
+       char *head_path;
+
+       if (!strcmp(ref->name, "HEAD")) {
+               head = ref;
+               ref = ref->next;
+       }
+       head_ptr = NULL;
+       master_ref = NULL;
+       while (ref) {
+               if (is_master(ref))
+                       master_ref = ref;
+               if (head && !memcmp(ref->sha1, head->sha1, 20)) {
+                       if (!head_ptr || ref == master_ref)
+                               head_ptr = ref;
+               }
+               write_one_ref(ref);
+               ref = ref->next;
+       }
+       if (!head)
+               return;
+
+       head_path = git_path("HEAD");
+       if (!head_ptr) {
+               /*
+                * If we had a master ref, and it wasn't HEAD, we need to undo the
+                * symlink, and write a standalone HEAD. Give a warning, because that's
+                * really really wrong.
+                */
+               if (master_ref) {
+                       error("HEAD doesn't point to any refs! Making standalone HEAD");
+                       unlink(head_path);
+               }
+               write_one_ref(head);
+               return;
+       }
+
+       /* We reset to the master branch if it's available */
+       if (master_ref)
+               return;
+
+       /*
+        * Uhhuh. Other end didn't have master. We start HEAD off with
+        * the first branch with the same value.
+        */
+       unlink(head_path);
+       if (symlink(head_ptr->name, head_path) < 0)
+               die("unable to link HEAD to %s", head_ptr->name);
+}
+
+static int clone_pack(int fd[2], int nr_match, char **match)
+{
+       struct ref *refs;
+       int status;
+       pid_t pid;
+
+       refs = get_remote_refs(fd[0], nr_match, match);
+       if (!refs) {
+               packet_flush(fd[1]);
+               die("no matching remote head");
+       }
+       clone_handshake(fd, refs);
+       pid = fork();
+       if (pid < 0)
+               die("git-clone-pack: unable to fork off git-unpack-objects");
+       if (!pid) {
+               close(fd[1]);
+               dup2(fd[0], 0);
+               close(fd[0]);
+               execlp("git-unpack-objects", "git-unpack-objects", NULL);
+               die("git-unpack-objects exec failed");
+       }
+       close(fd[0]);
+       close(fd[1]);
+       while (waitpid(pid, &status, 0) < 0) {
+               if (errno != EINTR)
+                       die("waiting for git-unpack-objects: %s", strerror(errno));
+       }
+       if (WIFEXITED(status)) {
+               int code = WEXITSTATUS(status);
+               if (code)
+                       die("git-unpack-objects died with error code %d", code);
+               write_refs(refs);
+               return 0;
+       }
+       if (WIFSIGNALED(status)) {
+               int sig = WTERMSIG(status);
+               die("git-unpack-objects died of signal %d", sig);
+       }
+       die("Sherlock Holmes! git-unpack-objects died of unnatural causes %d!", status);
+}
+
+int main(int argc, char **argv)
+{
+       int i, ret, nr_heads;
+       char *dest = NULL, **heads;
+       int fd[2];
+       pid_t pid;
+
+       nr_heads = 0;
+       heads = NULL;
+       for (i = 1; i < argc; i++) {
+               char *arg = argv[i];
+
+               if (*arg == '-') {
+                       /* Arguments go here */
+                       usage(clone_pack_usage);
+               }
+               dest = arg;
+               heads = argv + i + 1;
+               nr_heads = argc - i - 1;
+               break;
+       }
+       if (!dest)
+               usage(clone_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = clone_pack(fd, nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}