Merge with Linus' current tree
authorMatthias Urlichs <smurf@smurf.noris.de>
Tue, 5 Jul 2005 13:32:29 +0000 (15:32 +0200)
committerMatthias Urlichs <smurf@smurf.noris.de>
Tue, 5 Jul 2005 13:32:29 +0000 (15:32 +0200)
44 files changed:
Documentation/git-cat-file.txt
Documentation/git-fsck-cache.txt
Documentation/git-verify-pack.txt [new file with mode: 0644]
Documentation/git.txt
Makefile
apply.c
cache.h
cat-file.c
connect.c [new file with mode: 0644]
count-delta.c
csum-file.c
csum-file.h
delta.h
diff-delta.c
diffcore-break.c
diffcore-rename.c
epoch.c
fetch-pack.c [new file with mode: 0644]
fsck-cache.c
git-pull-script
git-repack-script [new file with mode: 0644]
ls-tree.c
pack-check.c [new file with mode: 0644]
pack-objects.c
pack.h [new file with mode: 0644]
patch-delta.c
pkt-line.c [new file with mode: 0644]
pkt-line.h [new file with mode: 0644]
prune-packed.c [new file with mode: 0644]
receive-pack.c [new file with mode: 0644]
refs.c
refs.h
rev-list.c
rev-parse.c
send-pack.c [new file with mode: 0644]
sha1_file.c
ssh-pull.c
ssh-push.c
t/t5300-pack-object.sh
t/t6001-rev-list-merge-order.sh
test-delta.c
unpack-objects.c
upload-pack.c [new file with mode: 0644]
verify-pack.c [new file with mode: 0644]

index 48fb377..2131a29 100644 (file)
@@ -9,12 +9,13 @@ git-cat-file - Provide content or type information for repository objects
 
 SYNOPSIS
 --------
-'git-cat-file' (-t | <type>) <object>
+'git-cat-file' (-t | -s | <type>) <object>
 
 DESCRIPTION
 -----------
 Provides content or type of objects in the repository. The type
-is required if '-t' is not being used to find the object type.
+is required unless '-t' is used to find the object type,
+or '-s' is used to find the object size.
 
 OPTIONS
 -------
@@ -25,6 +26,10 @@ OPTIONS
        Instead of the content, show the object type identified by
        <object>.
 
+-s::
+       Instead of the content, show the object size identified by
+       <object>.
+
 <type>::
        Typically this matches the real type of <object> but asking
        for a type that can trivially dereferenced from the given
@@ -35,7 +40,8 @@ OPTIONS
 
 OUTPUT
 ------
-If '-t' is specified, one of the <type>.
+If '-t' is specified, one of the <type>.  If '-s' is specified,
+the size of the <object> in bytes.
 
 Otherwise the raw (though uncompressed) contents of the <object> will
 be returned.
index 0ef01c3..f1c18c1 100644 (file)
@@ -9,7 +9,7 @@ git-fsck-cache - Verifies the connectivity and validity of the objects in the da
 
 SYNOPSIS
 --------
-'git-fsck-cache' [--tags] [--root] [--unreachable] [--cache] [<object>*]
+'git-fsck-cache' [--tags] [--root] [--unreachable] [--cache] [--standalone | --full] [<object>*]
 
 DESCRIPTION
 -----------
@@ -37,6 +37,22 @@ OPTIONS
        Consider any object recorded in the cache also as a head node for
        an unreachability trace.
 
+--standalone::
+       Limit checks to the contents of GIT_OBJECT_DIRECTORY
+       (.git/objects), making sure that it is consistent and
+       complete without referring to objects found in alternate
+       object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
+       nor packed GIT archives found in .git/objects/pack;
+       cannot be used with --full.
+
+--full::
+       Check not just objects in GIT_OBJECT_DIRECTORY
+       (.git/objects), but also the ones found in alternate
+       object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES,
+       and in packed GIT archives found in .git/objects/pack
+       and corresponding pack subdirectories in alternate
+       object pools; cannot be used with --standalone.
+
 It tests SHA1 and general object sanity, and it does full tracking of
 the resulting reachability and everything else. It prints out any
 corruption it finds (missing or bad objects), and if you use the
diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt
new file mode 100644 (file)
index 0000000..2df77de
--- /dev/null
@@ -0,0 +1,38 @@
+git-verify-pack(1)
+==================
+v0.1, June 2005
+
+NAME
+----
+git-verify-pack - Validate packed GIT archive files.
+
+
+SYNOPSIS
+--------
+'git-verify-pack' <pack>.idx ...
+
+
+DESCRIPTION
+-----------
+Reads given idx file for packed GIT archive created with
+git-pack-objects command and verifies idx file and the
+corresponding pack file.
+
+OPTIONS
+-------
+<pack>.idx ...::
+       The idx files to verify.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano
+
+GIT
+---
+Part of the link:git.html[git] suite
+
index 5252ee8..797eb33 100644 (file)
@@ -110,6 +110,9 @@ link:git-tar-tree.html[git-tar-tree]::
 link:git-unpack-file.html[git-unpack-file]::
        Creates a temporary file with a blob's contents
 
+link:git-verify-pack.html[git-verify-pack]::
+       Validates packed GIT archive files
+
 The interrogate commands may create files - and you can force them to
 touch the working file set - but in general they don't
 
index 9379e16..233d24a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -20,12 +20,18 @@ CC=gcc
 AR=ar
 INSTALL=install
 
+#
+# sparse is architecture-neutral, which means that we need to tell it
+# explicitly what architecture to check for. Fix this up for yours..
+#
+SPARSE_FLAGS=-D__BIG_ENDIAN__ -D__powerpc__
+
 SCRIPTS=git git-apply-patch-script git-merge-one-file-script git-prune-script \
        git-pull-script git-tag-script git-resolve-script git-whatchanged \
        git-fetch-script git-status-script git-commit-script \
        git-log-script git-shortlog git-cvsimport-script git-diff-script \
        git-reset-script git-add-script git-checkout-script git-clone-script \
-       gitk git-cherry git-rebase-script git-relink-script
+       gitk git-cherry git-rebase-script git-relink-script git-repack-script
 
 PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
        git-read-tree git-commit-tree git-cat-file git-fsck-cache \
@@ -35,8 +41,9 @@ PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
        git-http-pull git-ssh-push git-ssh-pull git-rev-list git-mktag \
        git-diff-helper git-tar-tree git-local-pull git-write-blob \
        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-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
 
 all: $(PROG)
 
@@ -45,9 +52,10 @@ install: $(PROG) $(SCRIPTS)
 
 LIB_OBJS=read-cache.o sha1_file.o usage.o object.o commit.o tree.o blob.o \
         tag.o date.o index.o diff-delta.o patch-delta.o entry.o \
-        epoch.o refs.o csum-file.o
+        epoch.o refs.o csum-file.o pack-check.o pkt-line.o connect.o
 LIB_FILE=libgit.a
-LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h csum-file.h
+LIB_H=cache.h object.h blob.h tree.h commit.h tag.h delta.h epoch.h csum-file.h \
+       pack.h pkt-line.h refs.h
 
 LIB_H += strbuf.h
 LIB_OBJS += strbuf.o
@@ -79,6 +87,9 @@ CFLAGS += '-DSHA1_HEADER=$(SHA1_HEADER)'
 $(LIB_FILE): $(LIB_OBJS)
        $(AR) rcs $@ $(LIB_OBJS)
 
+check:
+       for i in *.c; do sparse $(CFLAGS) $(SPARSE_FLAGS) $$i; done
+
 test-date: test-date.c date.o
        $(CC) $(CFLAGS) -o $@ test-date.c date.o
 
@@ -123,6 +134,11 @@ git-rev-parse: rev-parse.c
 git-patch-id: patch-id.c
 git-pack-objects: pack-objects.c
 git-unpack-objects: unpack-objects.c
+git-verify-pack: verify-pack.c
+git-receive-pack: receive-pack.c
+git-send-pack: send-pack.c
+git-prune-packed: prune-packed.c
+git-fetch-pack: fetch-pack.c
 
 git-http-pull: LIBS += -lcurl
 git-rev-list: LIBS += -lssl
diff --git a/apply.c b/apply.c
index c81b0a2..701c01a 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -724,8 +724,8 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
        return offset + hdrsize + patchsize;
 }
 
-const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-const char minuses[]= "----------------------------------------------------------------------";
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
 
 static void show_stats(struct patch *patch)
 {
diff --git a/cache.h b/cache.h
index 383fc86..c79c70f 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -14,6 +14,8 @@
 #include <sys/mman.h>
 #include <sys/param.h>
 #include <netinet/in.h>
+#include <sys/types.h>
+#include <dirent.h>
 
 #include SHA1_HEADER
 #include <zlib.h>
@@ -174,6 +176,7 @@ extern int read_tree(void *buffer, unsigned long size, int stage);
 
 extern int write_sha1_from_fd(const unsigned char *sha1, int fd);
 
+extern int has_sha1_pack(const unsigned char *sha1);
 extern int has_sha1_file(const unsigned char *sha1);
 
 /* Convert to/from hex/sha1 representation */
@@ -233,4 +236,41 @@ struct checkout {
 
 extern int checkout_entry(struct cache_entry *ce, struct checkout *state);
 
+extern struct alternate_object_database {
+       char *base;
+       char *name;
+} *alt_odb;
+extern void prepare_alt_odb(void);
+
+extern struct packed_git {
+       struct packed_git *next;
+       unsigned long index_size;
+       unsigned long pack_size;
+       unsigned int *index_base;
+       void *pack_base;
+       unsigned int pack_last_used;
+       unsigned int pack_use_cnt;
+       char pack_name[0]; /* something like ".git/objects/pack/xxxxx.pack" */
+} *packed_git;
+
+struct pack_entry {
+       unsigned int offset;
+       unsigned char sha1[20];
+       struct packed_git *p;
+};
+
+extern int git_connect(int fd[2], char *url, const char *prog);
+extern int finish_connect(pid_t pid);
+extern int path_match(const char *path, int nr, char **match);
+
+extern void prepare_packed_git(void);
+extern int use_packed_git(struct packed_git *);
+extern void unuse_packed_git(struct packed_git *);
+extern struct packed_git *add_packed_git(char *, int);
+extern int num_packed_objects(const struct packed_git *p);
+extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
+extern int find_pack_entry_one(const unsigned char *, struct pack_entry *, struct packed_git *);
+extern void *unpack_entry_gently(struct pack_entry *, char *, unsigned long *);
+extern void packed_object_info_detail(struct pack_entry *, char *, unsigned long *, unsigned long *, int *, unsigned char *);
+
 #endif /* CACHE_H */
index be41f51..fa0bb72 100644 (file)
@@ -13,16 +13,22 @@ int main(int argc, char **argv)
        unsigned long size;
 
        if (argc != 3 || get_sha1(argv[2], sha1))
-               usage("git-cat-file [-t | tagname] <sha1>");
+               usage("git-cat-file [-t | -s | tagname] <sha1>");
 
-       if (!strcmp("-t", argv[1])) {
-               buf = read_sha1_file(sha1, type, &size);
-               if (buf) {
-                       buf = type;
-                       size = strlen(type);
-                       type[size] = '\n';
-                       size++;
+       if (!strcmp("-t", argv[1]) || !strcmp("-s", argv[1])) {
+               if (!sha1_object_info(sha1, type,
+                                     argv[1][1] == 's' ? &size : NULL)) {
+                       switch (argv[1][1]) {
+                       case 't':
+                               printf("%s\n", type);
+                               break;
+                       case 's':
+                               printf("%lu\n", size);
+                               break;
+                       }
+                       return 0;
                }
+               buf = NULL;
        } else {
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);
        }
diff --git a/connect.c b/connect.c
new file mode 100644 (file)
index 0000000..941bf29
--- /dev/null
+++ b/connect.c
@@ -0,0 +1,107 @@
+#include "cache.h"
+#include <sys/wait.h>
+
+int path_match(const char *path, int nr, char **match)
+{
+       int i;
+       int pathlen = strlen(path);
+
+       for (i = 0; i < nr; i++) {
+               char *s = match[i];
+               int len = strlen(s);
+
+               if (!len || len > pathlen)
+                       continue;
+               if (memcmp(path + pathlen - len, s, len))
+                       continue;
+               if (pathlen > len && path[pathlen - len - 1] != '/')
+                       continue;
+               *s = 0;
+               return 1;
+       }
+       return 0;
+}
+
+/*
+ * First, make it shell-safe.  We do this by just disallowing any
+ * special characters. Somebody who cares can do escaping and let
+ * through the rest. But since we're doing to feed this to ssh as
+ * a command line, we're going to be pretty damn anal for now.
+ */
+static char *shell_safe(char *url)
+{
+       char *n = url;
+       unsigned char c;
+       static const char flags[256] = {
+               ['0'...'9'] = 1,
+               ['a'...'z'] = 1,
+               ['A'...'Z'] = 1,
+               ['.'] = 1, ['/'] = 1,
+               ['-'] = 1, ['+'] = 1,
+               [':'] = 1
+       };
+
+       while ((c = *n++) != 0) {
+               if (flags[c] != 1)
+                       die("I don't like '%c'. Sue me.", c);
+       }
+       return url;
+}
+
+/*
+ * Yeah, yeah, fixme. Need to pass in the heads etc.
+ */
+int git_connect(int fd[2], char *url, const char *prog)
+{
+       char command[1024];
+       const char *host, *path;
+       char *colon;
+       int pipefd[2][2];
+       pid_t pid;
+
+       url = shell_safe(url);
+       host = NULL;
+       path = url;
+       colon = strchr(url, ':');
+       if (colon) {
+               *colon = 0;
+               host = url;
+               path = colon+1;
+       }
+       snprintf(command, sizeof(command), "%s %s", prog, path);
+       if (pipe(pipefd[0]) < 0 || pipe(pipefd[1]) < 0)
+               die("unable to create pipe pair for communication");
+       pid = fork();
+       if (!pid) {
+               dup2(pipefd[1][0], 0);
+               dup2(pipefd[0][1], 1);
+               close(pipefd[0][0]);
+               close(pipefd[0][1]);
+               close(pipefd[1][0]);
+               close(pipefd[1][1]);
+               if (host)
+                       execlp("ssh", "ssh", host, command, NULL);
+               else
+                       execlp("sh", "sh", "-c", command, NULL);
+               die("exec failed");
+       }               
+       fd[0] = pipefd[0][0];
+       fd[1] = pipefd[1][1];
+       close(pipefd[0][1]);
+       close(pipefd[1][0]);
+       return pid;
+}
+
+int finish_connect(pid_t pid)
+{
+       int ret;
+
+       for (;;) {
+               ret = waitpid(pid, NULL, 0);
+               if (!ret)
+                       break;
+               if (errno != EINTR)
+                       break;
+       }
+       return ret;
+}
index c7f3767..7559ff6 100644 (file)
@@ -6,26 +6,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
+#include "delta.h"
 #include "count-delta.h"
 
-static unsigned long get_hdr_size(const unsigned char **datap)
-{
-       const unsigned char *data = *datap;
-       unsigned long size;
-       unsigned char cmd;
-       int i;
-       size = i = 0;
-       cmd = *data++;
-       while (cmd) {
-               if (cmd & 1)
-                       size |= *data++ << i;
-               i += 8;
-               cmd >>= 1;
-       }
-       *datap = data;
-       return size;
-}
-
 /*
  * NOTE.  We do not _interpret_ delta fully.  As an approximation, we
  * just count the number of bytes that are copied from the source, and
@@ -47,15 +30,14 @@ int count_delta(void *delta_buf, unsigned long delta_size,
        unsigned char cmd;
        unsigned long src_size, dst_size, out;
 
-       /* the smallest delta size possible is 6 bytes */
-       if (delta_size < 6)
+       if (delta_size < DELTA_SIZE_MIN)
                return -1;
 
        data = delta_buf;
        top = delta_buf + delta_size;
 
-       src_size = get_hdr_size(&data);
-       dst_size = get_hdr_size(&data);
+       src_size = get_delta_hdr_size(&data);
+       dst_size = get_delta_hdr_size(&data);
 
        added_literal = copied_from_source = out = 0;
        while (data < top) {
index 3ab65b7..c8c7369 100644 (file)
@@ -96,6 +96,26 @@ struct sha1file *sha1create(const char *fmt, ...)
        return f;
 }
 
+struct sha1file *sha1fd(int fd, const char *name)
+{
+       struct sha1file *f;
+       unsigned len;
+
+       f = xmalloc(sizeof(*f));
+
+       len = strlen(name);
+       if (len >= PATH_MAX)
+               die("you wascally wabbit, you");
+       f->namelen = len;
+       memcpy(f->name, name, len+1);
+
+       f->fd = fd;
+       f->error = 0;
+       f->offset = 0;
+       SHA1_Init(&f->ctx);
+       return f;
+}
+
 int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
 {
        z_stream stream;
index 2b6c8dc..776cfb1 100644 (file)
@@ -10,6 +10,7 @@ struct sha1file {
        unsigned char buffer[8192];
 };
 
+extern struct sha1file *sha1fd(int fd, const char *name);
 extern struct sha1file *sha1create(const char *fmt, ...);
 extern int sha1close(struct sha1file *, unsigned char *, int);
 extern int sha1write(struct sha1file *, void *, unsigned int);
diff --git a/delta.h b/delta.h
index bc3a220..31d1820 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -9,4 +9,26 @@ extern void *patch_delta(void *src_buf, unsigned long src_size,
                         void *delta_buf, unsigned long delta_size,
                         unsigned long *dst_size);
 
+/* the smallest possible delta size is 4 bytes */
+#define DELTA_SIZE_MIN 4
+
+/*
+ * This must be called twice on the delta data buffer, first to get the
+ * expected reference buffer size, and again to get the result buffer size.
+ */
+static inline unsigned long get_delta_hdr_size(const unsigned char **datap)
+{
+       const unsigned char *data = *datap;
+       unsigned char cmd = *data++;
+       unsigned long size = cmd & ~0x80;
+       int i = 7;
+       while (cmd & 0x80) {
+               cmd = *data++;
+               size |= (cmd & ~0x80) << i;
+               i += 7;
+       }
+       *datap = data;
+       return size;
+}
+
 #endif
index fd9b37f..b2ae7b5 100644 (file)
@@ -228,28 +228,22 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        top = to_buf + to_size;
 
        /* store reference buffer size */
-       orig = out + outpos++;
-       *orig = i = 0;
-       do {
-               if (from_size & 0xff) {
-                       *orig |= (1 << i);
-                       out[outpos++] = from_size;
-               }
-               i++;
-               from_size >>= 8;
-       } while (from_size);
+       out[outpos++] = from_size;
+       from_size >>= 7;
+       while (from_size) {
+               out[outpos - 1] |= 0x80;
+               out[outpos++] = from_size;
+               from_size >>= 7;
+       }
 
        /* store target buffer size */
-       orig = out + outpos++;
-       *orig = i = 0;
-       do {
-               if (to_size & 0xff) {
-                       *orig |= (1 << i);
-                       out[outpos++] = to_size;
-               }
-               i++;
-               to_size >>= 8;
-       } while (to_size);
+       out[outpos++] = to_size;
+       to_size >>= 7;
+       while (to_size) {
+               out[outpos - 1] |= 0x80;
+               out[outpos++] = to_size;
+               to_size >>= 7;
+       }
 
        inscnt = 0;
        moff = 0;
@@ -312,12 +306,13 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                        *orig = i;
                }
 
-               /* next time around the largest possible output is 1 + 4 + 3 */
                if (max_size && outpos > max_size) {
                        free(out);
                        delta_cleanup(&bdf);
                        return NULL;
                }
+
+               /* next time around the largest possible output is 1 + 4 + 3 */
                if (outpos > outsize - 8) {
                        void *tmp = out;
                        outsize = outsize * 3 / 2;
index 9852f97..06f9a7f 100644 (file)
@@ -65,7 +65,7 @@ static int should_break(struct diff_filespec *src,
 
        delta = diff_delta(src->data, src->size,
                           dst->data, dst->size,
-                          &delta_size, ~0UL);
+                          &delta_size, 0);
 
        /* Estimate the edit size by interpreting delta. */
        if (count_delta(delta, delta_size,
index 29609c7..6a52699 100644 (file)
@@ -136,6 +136,7 @@ static int estimate_similarity(struct diff_filespec *src,
         */
        void *delta;
        unsigned long delta_size, base_size, src_copied, literal_added;
+       unsigned long delta_limit;
        int score;
 
        /* We deal only with regular files.  Symlink renames are handled
@@ -163,9 +164,13 @@ static int estimate_similarity(struct diff_filespec *src,
        if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
                return 0; /* error but caught downstream */
 
+       delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
        delta = diff_delta(src->data, src->size,
                           dst->data, dst->size,
-                          &delta_size, ~0UL);
+                          &delta_size, delta_limit);
+       if (!delta)
+               /* If delta_limit is exceeded, we have too much differences */
+               return 0;
 
        /* A delta that has a lot of literal additions would have
         * big delta_size no matter what else it does.
diff --git a/epoch.c b/epoch.c
index cbbc418..6dbcfb3 100644 (file)
--- a/epoch.c
+++ b/epoch.c
@@ -28,7 +28,7 @@ static BN_CTX *context = NULL;
 static struct fraction *one = NULL;
 static struct fraction *zero = NULL;
 
-static BN_CTX *get_BN_CTX()
+static BN_CTX *get_BN_CTX(void)
 {
        if (!context) {
                context = BN_CTX_new();
@@ -36,7 +36,7 @@ static BN_CTX *get_BN_CTX()
        return context;
 }
 
-static struct fraction *new_zero()
+static struct fraction *new_zero(void)
 {
        struct fraction *result = xmalloc(sizeof(*result));
        BN_init(&result->numerator);
@@ -75,7 +75,7 @@ static struct fraction *init_fraction(struct fraction *fraction)
        return fraction;
 }
 
-static struct fraction *get_one()
+static struct fraction *get_one(void)
 {
        if (!one) {
                one = new_zero();
@@ -84,7 +84,7 @@ static struct fraction *get_one()
        return one;
 }
 
-static struct fraction *get_zero()
+static struct fraction *get_zero(void)
 {
        if (!zero) {
                zero = new_zero();
@@ -585,14 +585,9 @@ int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
        for (; list; list = list->next) {
                struct commit *next = list->item;
 
-               if (!(next->object.flags & UNINTERESTING)) {
-                       if (next->object.flags & DUPCHECK) {
-                               fprintf(stderr, "%s: duplicate commit %s ignored\n",
-                                       __FUNCTION__, sha1_to_hex(next->object.sha1));
-                       } else {
-                               next->object.flags |= DUPCHECK;
-                               commit_list_insert(list->item, &reversed);
-                       }
+               if (!(next->object.flags & DUPCHECK)) {
+                       next->object.flags |= DUPCHECK;
+                       commit_list_insert(list->item, &reversed);
                }
        }
 
diff --git a/fetch-pack.c b/fetch-pack.c
new file mode 100644 (file)
index 0000000..b8367a4
--- /dev/null
@@ -0,0 +1,212 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char fetch_pack_usage[] = "git-fetch-pack [host:]directory [heads]* < mycommitlist";
+static const char *exec = "git-upload-pack";
+
+static int get_ack(int fd, unsigned char *result_sha1)
+{
+       static char line[1000];
+       int len = packet_read_line(fd, line, sizeof(line));
+
+       if (!len)
+               die("git-fetch-pack: expected ACK/NAK, got EOF");
+       if (line[len-1] == '\n')
+               line[--len] = 0;
+       if (!strcmp(line, "NAK"))
+               return 0;
+       if (!strncmp(line, "ACK ", 3)) {
+               if (!get_sha1_hex(line+4, result_sha1))
+                       return 1;
+       }
+       die("git-fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+static int find_common(int fd[2], unsigned char *result_sha1, unsigned char *remote)
+{
+       static char line[1000];
+       int count = 0, flushes = 0, retval;
+       FILE *revs;
+
+       revs = popen("git-rev-list $(git-rev-parse --all)", "r");
+       if (!revs)
+               die("unable to run 'git-rev-list'");
+       packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
+       packet_flush(fd[1]);
+       flushes = 1;
+       retval = -1;
+       while (fgets(line, sizeof(line), revs) != NULL) {
+               unsigned char sha1[20];
+               if (get_sha1_hex(line, sha1))
+                       die("git-fetch-pack: expected object name, got crud");
+               packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+               if (!(31 & ++count)) {
+                       packet_flush(fd[1]);
+                       flushes++;
+
+                       /*
+                        * We keep one window "ahead" of the other side, and
+                        * will wait for an ACK only on the next one
+                        */
+                       if (count == 32)
+                               continue;
+                       if (get_ack(fd[0], result_sha1)) {
+                               flushes = 0;
+                               retval = 0;
+                               break;
+                       }
+                       flushes--;
+               }
+       }
+       pclose(revs);
+       packet_write(fd[1], "done\n");
+       while (flushes) {
+               flushes--;
+               if (get_ack(fd[0], result_sha1))
+                       return 0;
+       }
+       return retval;
+}
+
+static int get_old_sha1(const char *refname, unsigned char *sha1)
+{
+       static char pathname[PATH_MAX];
+       const char *git_dir;
+       int fd, ret;
+
+       git_dir = gitenv(GIT_DIR_ENVIRONMENT) ? : DEFAULT_GIT_DIR_ENVIRONMENT;
+       snprintf(pathname, sizeof(pathname), "%s/%s", git_dir, refname);
+       fd = open(pathname, O_RDONLY);
+       ret = -1;
+       if (fd >= 0) {
+               char buffer[60];
+               if (read(fd, buffer, sizeof(buffer)) >= 40)
+                       ret = get_sha1_hex(buffer, sha1);
+               close(fd);
+       }
+       return ret;
+}
+
+static int check_ref(const char *refname, const unsigned char *sha1)
+{
+       unsigned char mysha1[20];
+       char oldhex[41];
+
+       if (get_old_sha1(refname, mysha1) < 0)
+               memset(mysha1, 0, 20);
+
+       if (!memcmp(sha1, mysha1, 20)) {
+               fprintf(stderr, "%s: unchanged\n", refname);
+               return 0;
+       }
+       
+       memcpy(oldhex, sha1_to_hex(mysha1), 41);
+       fprintf(stderr, "%s: %s (%s)\n", refname, sha1_to_hex(sha1), oldhex);
+       return 1;
+}
+
+static int get_remote_heads(int fd, int nr_match, char **match, unsigned char *result)
+{
+       int count = 0;
+
+       for (;;) {
+               static char line[1000];
+               unsigned char sha1[20];
+               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;
+               if (nr_match && !path_match(refname, nr_match, match))
+                       continue;
+               if (check_ref(refname, sha1)) {
+                       count++;
+                       memcpy(result, sha1, 20);
+               }
+       }
+       return count;
+}
+
+static int fetch_pack(int fd[2], int nr_match, char **match)
+{
+       unsigned char sha1[20], remote[20];
+       int heads, status;
+       pid_t pid;
+
+       heads = get_remote_heads(fd[0], nr_match, match, remote);
+       if (heads != 1) {
+               packet_flush(fd[1]);
+               die(heads ? "multiple remote heads" : "no matching remote head");
+       }
+       if (find_common(fd, sha1, remote) < 0)
+               die("git-fetch-pack: no common commits");
+       pid = fork();
+       if (pid < 0)
+               die("git-fetch-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);
+               puts(sha1_to_hex(remote));
+               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(fetch_pack_usage);
+               }
+               dest = arg;
+               heads = argv + i + 1;
+               nr_heads = argc - i - 1;
+               break;
+       }
+       if (!dest)
+               usage(fetch_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = fetch_pack(fd, nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
index 9167924..e42264e 100644 (file)
@@ -6,12 +6,16 @@
 #include "tree.h"
 #include "blob.h"
 #include "tag.h"
+#include "refs.h"
+#include "pack.h"
 
 #define REACHABLE 0x0001
 
 static int show_root = 0;
 static int show_tags = 0;
 static int show_unreachable = 0;
+static int standalone = 0;
+static int check_full = 0;
 static int keep_cache_objects = 0; 
 static unsigned char head_sha1[20];
 
@@ -25,13 +29,17 @@ static void check_connectivity(void)
                struct object_list *refs;
 
                if (!obj->parsed) {
-                       printf("missing %s %s\n",
-                              obj->type, sha1_to_hex(obj->sha1));
+                       if (!standalone && has_sha1_file(obj->sha1))
+                               ; /* it is in pack */
+                       else
+                               printf("missing %s %s\n",
+                                      obj->type, sha1_to_hex(obj->sha1));
                        continue;
                }
 
                for (refs = obj->refs; refs; refs = refs->next) {
-                       if (refs->item->parsed)
+                       if (refs->item->parsed ||
+                           (!standalone && has_sha1_file(refs->item->sha1)))
                                continue;
                        printf("broken link from %7s %s\n",
                               obj->type, sha1_to_hex(obj->sha1));
@@ -296,80 +304,72 @@ static int fsck_dir(int i, char *path)
        return 0;
 }
 
-static int read_sha1_reference(const char *path)
+static int default_refs = 0;
+
+static int fsck_handle_ref(const char *refname, const unsigned char *sha1)
 {
-       char hexname[60];
-       unsigned char sha1[20];
-       int fd = open(path, O_RDONLY), len;
        struct object *obj;
 
-       if (fd < 0)
-               return -1;
-
-       len = read(fd, hexname, sizeof(hexname));
-       close(fd);
-       if (len < 40)
-               return -1;
-
-       if (get_sha1_hex(hexname, sha1) < 0)
-               return -1;
-
        obj = lookup_object(sha1);
-       if (!obj)
-               return error("%s: invalid sha1 pointer %.40s", path, hexname);
-
+       if (!obj) {
+               if (!standalone && has_sha1_file(sha1))
+                       return 0; /* it is in a pack */
+               error("%s: invalid sha1 pointer %s", refname, sha1_to_hex(sha1));
+               /* We'll continue with the rest despite the error.. */
+               return 0;
+       }
+       default_refs++;
        obj->used = 1;
        mark_reachable(obj, REACHABLE);
        return 0;
 }
 
-static int find_file_objects(const char *base, const char *name)
+static void get_default_heads(void)
 {
-       int baselen = strlen(base);
-       int namelen = strlen(name);
-       char *path = xmalloc(baselen + namelen + 2);
-       struct stat st;
-
-       memcpy(path, base, baselen);
-       path[baselen] = '/';
-       memcpy(path + baselen + 1, name, namelen+1);
-       if (stat(path, &st) < 0)
-               return 0;
+       for_each_ref(fsck_handle_ref);
+       if (!default_refs)
+               die("No default references");
+}
 
-       /*
-        * Recurse into directories
-        */
-       if (S_ISDIR(st.st_mode)) {
-               int count = 0;
-               DIR *dir = opendir(path);
-               if (dir) {
-                       struct dirent *de;
-                       while ((de = readdir(dir)) != NULL) {
-                               if (de->d_name[0] == '.')
-                                       continue;
-                               count += find_file_objects(path, de->d_name);
-                       }
-                       closedir(dir);
-               }
-               return count;
+static void fsck_object_dir(const char *path)
+{
+       int i;
+       for (i = 0; i < 256; i++) {
+               static char dir[4096];
+               sprintf(dir, "%s/%02x", path, i);
+               fsck_dir(i, dir);
        }
-       if (S_ISREG(st.st_mode))
-               return read_sha1_reference(path) == 0;
-       return 0;
+       fsck_sha1_list();
 }
 
-static void get_default_heads(void)
+static int fsck_head_link(void)
 {
-       char *git_dir = gitenv(GIT_DIR_ENVIRONMENT) ? : DEFAULT_GIT_DIR_ENVIRONMENT;
-       int count = find_file_objects(git_dir, "refs");
-       if (!count)
-               die("No default references");
+       int fd, count;
+       char hex[40];
+       unsigned char sha1[20];
+       static char path[PATH_MAX], link[PATH_MAX];
+       const char *git_dir = gitenv(GIT_DIR_ENVIRONMENT) ? : DEFAULT_GIT_DIR_ENVIRONMENT;
+
+       snprintf(path, sizeof(path), "%s/HEAD", git_dir);
+       if (readlink(path, link, sizeof(link)) < 0)
+               return error("HEAD is not a symlink");
+       if (strncmp("refs/heads/", link, 11))
+               return error("HEAD points to something strange (%s)", link);
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return error("HEAD: %s", strerror(errno));
+       count = read(fd, hex, sizeof(hex));
+       close(fd);
+       if (count < 0)
+               return error("HEAD: %s", strerror(errno));
+       if (count < 40 || get_sha1_hex(hex, sha1))
+               return error("HEAD: not a valid git pointer");
+       return 0;
 }
 
 int main(int argc, char **argv)
 {
        int i, heads;
-       char *sha1_dir;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -390,17 +390,50 @@ int main(int argc, char **argv)
                        keep_cache_objects = 1;
                        continue;
                }
+               if (!strcmp(arg, "--standalone")) {
+                       standalone = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--full")) {
+                       check_full = 1;
+                       continue;
+               }
                if (*arg == '-')
-                       usage("git-fsck-cache [--tags] [[--unreachable] [--cache] <head-sha1>*]");
+                       usage("git-fsck-cache [--tags] [[--unreachable] [--cache] [--standalone | --full] <head-sha1>*]");
        }
 
-       sha1_dir = get_object_directory();
-       for (i = 0; i < 256; i++) {
-               static char dir[4096];
-               sprintf(dir, "%s/%02x", sha1_dir, i);
-               fsck_dir(i, dir);
+       if (standalone && check_full)
+               die("Only one of --standalone or --full can be used.");
+       if (standalone)
+               unsetenv("GIT_ALTERNATE_OBJECT_DIRECTORIES");
+
+       fsck_head_link();
+       fsck_object_dir(get_object_directory());
+       if (check_full) {
+               int j;
+               struct packed_git *p;
+               prepare_alt_odb();
+               for (j = 0; alt_odb[j].base; j++) {
+                       alt_odb[j].name[-1] = 0; /* was slash */
+                       fsck_object_dir(alt_odb[j].base);
+                       alt_odb[j].name[-1] = '/';
+               }
+               prepare_packed_git();
+               for (p = packed_git; p; p = p->next)
+                       /* verify gives error messages itself */
+                       verify_pack(p, 0);
+
+               for (p = packed_git; p; p = p->next) {
+                       int num = num_packed_objects(p);
+                       for (i = 0; i < num; i++) {
+                               unsigned char sha1[20];
+                               nth_packed_object_sha1(p, i, sha1);
+                               if (fsck_sha1(sha1) < 0)
+                                       fprintf(stderr, "bad sha1 entry '%s'\n", sha1_to_hex(sha1));
+
+                       }
+               }
        }
-       fsck_sha1_list();
 
        heads = 0;
        for (i = 1; i < argc; i++) {
index 4ec12bd..961edc4 100755 (executable)
@@ -12,7 +12,7 @@ fi
 if [ "$2" ]
 then
    merge_name="$type '$2' of $merge_name"
-   merge_head="refs/{$type}s/$2"
+   merge_head="refs/${type}s/$2"
 fi
 
 : ${GIT_DIR=.git}
diff --git a/git-repack-script b/git-repack-script
new file mode 100644 (file)
index 0000000..92281c2
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/sh
+: ${GIT_DIR=.git}
+: ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
+rm -f .tmp-pack-*
+packname=$(git-rev-list --unpacked --objects $(git-rev-parse --all) |
+       git-pack-objects --non-empty --incremental .tmp-pack) ||
+       exit 1
+if [ -z "$packname" ]; then
+       echo Nothing new to pack
+       exit 0
+fi
+mv .tmp-pack-$packname.pack "$GIT_OBJECT_DIRECTORY/pack/pack-$packname.pack"
+mv .tmp-pack-$packname.idx  "$GIT_OBJECT_DIRECTORY/pack/pack-$packname.idx"
index 450bff2..8d524b8 100644 (file)
--- a/ls-tree.c
+++ b/ls-tree.c
@@ -79,7 +79,7 @@ static struct tree_entry_list *find_entry(const char *path)
                slash = strchr(path, '/');
                if (!slash) {
                        len = strlen(path);
-                       next = 0;
+                       next = NULL;
                }
                else {
                        next = slash + 1;
diff --git a/pack-check.c b/pack-check.c
new file mode 100644 (file)
index 0000000..916257f
--- /dev/null
@@ -0,0 +1,143 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_packfile(struct packed_git *p)
+{
+       unsigned long index_size = p->index_size;
+       void *index_base = p->index_base;
+       SHA_CTX ctx;
+       unsigned char sha1[20];
+       unsigned long pack_size = p->pack_size;
+       void *pack_base;
+       struct pack_header *hdr;
+       int nr_objects, err, i;
+
+       /* Header consistency check */
+       hdr = p->pack_base;
+       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+               return error("Packfile signature mismatch", p->pack_name);
+       if (hdr->hdr_version != htonl(PACK_VERSION))
+               return error("Packfile version %d different from ours %d",
+                            ntohl(hdr->hdr_version), PACK_VERSION);
+       nr_objects = ntohl(hdr->hdr_entries);
+       if (num_packed_objects(p) != nr_objects)
+               return error("Packfile claims to have %d objects, "
+                            "while idx size expects %d", nr_objects,
+                            num_packed_objects(p));
+
+       SHA1_Init(&ctx);
+       pack_base = p->pack_base;
+       SHA1_Update(&ctx, pack_base, pack_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, index_base + index_size - 40, 20))
+               return error("Packfile %s SHA1 mismatch with idx",
+                            p->pack_name);
+       if (memcmp(sha1, pack_base + pack_size - 20, 20))
+               return error("Packfile %s SHA1 mismatch with itself",
+                            p->pack_name);
+
+       /* Make sure everything reachable from idx is valid.  Since we
+        * have verified that nr_objects matches between idx and pack,
+        * we do not do scan-streaming check on the pack file.
+        */
+       for (i = err = 0; i < nr_objects; i++) {
+               unsigned char sha1[20];
+               struct pack_entry e;
+               void *data;
+               char type[20];
+               unsigned long size;
+
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("internal error pack-check nth-packed-object");
+               if (!find_pack_entry_one(sha1, &e, p))
+                       die("internal error pack-check find-pack-entry-one");
+               data = unpack_entry_gently(&e, type, &size);
+               if (!data) {
+                       err = error("cannot unpack %s from %s",
+                                   sha1_to_hex(sha1), p->pack_name);
+                       continue;
+               }
+               if (check_sha1_signature(sha1, data, size, type)) {
+                       err = error("cannot packed %s from %s corrupt",
+                                   sha1_to_hex(sha1), p->pack_name);
+                       free(data);
+                       continue;
+               }
+               free(data);
+       }
+
+       return err;
+}
+
+
+static void show_pack_info(struct packed_git *p)
+{
+       struct pack_header *hdr;
+       int nr_objects, i;
+
+       hdr = p->pack_base;
+       nr_objects = ntohl(hdr->hdr_entries);
+
+       for (i = 0; i < nr_objects; i++) {
+               unsigned char sha1[20], base_sha1[20];
+               struct pack_entry e;
+               char type[20];
+               unsigned long size;
+               unsigned long store_size;
+               int delta_chain_length;
+
+               if (nth_packed_object_sha1(p, i, sha1))
+                       die("internal error pack-check nth-packed-object");
+               if (!find_pack_entry_one(sha1, &e, p))
+                       die("internal error pack-check find-pack-entry-one");
+
+               packed_object_info_detail(&e, type, &size, &store_size,
+                                         &delta_chain_length,
+                                         base_sha1);
+               printf("%s ", sha1_to_hex(sha1));
+               if (!delta_chain_length)
+                       printf("%-6s %lu %u\n", type, size, e.offset);
+               else
+                       printf("%-6s %lu %u %d %s\n", type, size, e.offset,
+                              delta_chain_length, sha1_to_hex(base_sha1));
+       }
+
+}
+
+int verify_pack(struct packed_git *p, int verbose)
+{
+       unsigned long index_size = p->index_size;
+       void *index_base = p->index_base;
+       SHA_CTX ctx;
+       unsigned char sha1[20];
+       int ret;
+
+       ret = 0;
+       /* Verify SHA1 sum of the index file */
+       SHA1_Init(&ctx);
+       SHA1_Update(&ctx, index_base, index_size - 20);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(sha1, index_base + index_size - 20, 20))
+               ret = error("Packfile index for %s SHA1 mismatch",
+                           p->pack_name);
+
+       if (!ret) {
+               /* Verify pack file */
+               use_packed_git(p);
+               ret = verify_packfile(p);
+               unuse_packed_git(p);
+       }
+
+       if (verbose) {
+               if (ret)
+                       printf("%s: bad\n", p->pack_name);
+               else {
+                       use_packed_git(p);
+                       show_pack_info(p);
+                       unuse_packed_git(p);
+                       printf("%s: ok\n", p->pack_name);
+               }
+       }
+
+       return ret;
+}
index 62ed265..ed24a33 100644 (file)
@@ -2,17 +2,10 @@
 #include "cache.h"
 #include "object.h"
 #include "delta.h"
+#include "pack.h"
 #include "csum-file.h"
 
-static const char pack_usage[] = "git-pack-objects [--window=N] [--depth=N] base-name < object-list";
-
-enum object_type {
-       OBJ_NONE,
-       OBJ_COMMIT,
-       OBJ_TREE,
-       OBJ_BLOB,
-       OBJ_DELTA
-};
+static const char pack_usage[] = "git-pack-objects [--incremental] [--window=N] [--depth=N] {--stdout | base-name} < object-list";
 
 struct object_entry {
        unsigned char sha1[20];
@@ -25,6 +18,9 @@ struct object_entry {
        struct object_entry *delta;
 };
 
+static unsigned char object_list_sha1[20];
+static int non_empty = 0;
+static int incremental = 0;
 static struct object_entry **sorted_by_sha, **sorted_by_type;
 static struct object_entry *objects = NULL;
 static int nr_objects = 0, nr_alloc = 0;
@@ -41,7 +37,7 @@ static void *delta_against(void *buf, unsigned long size, struct object_entry *e
        if (!otherbuf)
                die("unable to read %s", sha1_to_hex(entry->delta->sha1));
         delta_buf = diff_delta(otherbuf, othersize,
-                              buf, size, &delta_size, ~0UL);
+                              buf, size, &delta_size, 0);
         if (!delta_buf || delta_size != entry->delta_size)
                die("delta size changed");
         free(buf);
@@ -49,13 +45,41 @@ static void *delta_against(void *buf, unsigned long size, struct object_entry *e
        return delta_buf;
 }
 
+/*
+ * The per-object header is a pretty dense thing, which is
+ *  - first byte: low four bits are "size", then three bits of "type",
+ *    and the high bit is "size continues".
+ *  - each byte afterwards: low seven bits are size continuation,
+ *    with the high bit being "size continues"
+ */
+static int encode_header(enum object_type type, unsigned long size, unsigned char *hdr)
+{
+       int n = 1;
+       unsigned char c;
+
+       if (type < OBJ_COMMIT || type > OBJ_DELTA)
+               die("bad type %d", type);
+
+       c = (type << 4) | (size & 15);
+       size >>= 4;
+       while (size) {
+               *hdr++ = c | 0x80;
+               c = size & 0x7f;
+               size >>= 7;
+               n++;
+       }
+       *hdr = c;
+       return n;
+}
+
 static unsigned long write_object(struct sha1file *f, struct object_entry *entry)
 {
        unsigned long size;
        char type[10];
        void *buf = read_sha1_file(entry->sha1, type, &size);
-       char header[25];
+       unsigned char header[10];
        unsigned hdrlen, datalen;
+       enum object_type obj_type;
 
        if (!buf)
                die("unable to read %s", sha1_to_hex(entry->sha1));
@@ -63,39 +87,64 @@ static unsigned long write_object(struct sha1file *f, struct object_entry *entry
                die("object %s size inconsistency (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
 
        /*
-        * The object header is a byte of 'type' followed by four bytes of
-        * length, except for deltas that has the 20 bytes of delta sha
-        * instead.
+        * The object header is a byte of 'type' followed by zero or
+        * more bytes of length.  For deltas, the 20 bytes of delta sha1
+        * follows that.
         */
-       header[0] = ".CTB"[entry->type];
-       hdrlen = 5;
+       obj_type = entry->type;
        if (entry->delta) {
-               header[0] = 'D';
-               memcpy(header+5, entry->delta, 20);
                buf = delta_against(buf, size, entry);
                size = entry->delta_size;
-               hdrlen = 25;
+               obj_type = OBJ_DELTA;
        }
-       datalen = htonl(size);
-       memcpy(header+1, &datalen, 4);
+       hdrlen = encode_header(obj_type, size, header);
        sha1write(f, header, hdrlen);
+       if (entry->delta) {
+               sha1write(f, entry->delta, 20);
+               hdrlen += 20;
+       }
        datalen = sha1write_compressed(f, buf, size);
        free(buf);
        return hdrlen + datalen;
 }
 
+static unsigned long write_one(struct sha1file *f,
+                              struct object_entry *e,
+                              unsigned long offset)
+{
+       if (e->offset)
+               /* offset starts from header size and cannot be zero
+                * if it is written already.
+                */
+               return offset;
+       e->offset = offset;
+       offset += write_object(f, e);
+       /* if we are delitified, write out its base object. */
+       if (e->delta)
+               offset = write_one(f, e->delta, offset);
+       return offset;
+}
+
 static void write_pack_file(void)
 {
        int i;
-       struct sha1file *f = sha1create("%s.%s", base_name, "pack");
-       unsigned long offset = 0;
+       struct sha1file *f;
+       unsigned long offset;
        unsigned long mb;
+       struct pack_header hdr;
+
+       if (!base_name)
+               f = sha1fd(1, "<stdout>");
+       else
+               f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "pack");
+       hdr.hdr_signature = htonl(PACK_SIGNATURE);
+       hdr.hdr_version = htonl(PACK_VERSION);
+       hdr.hdr_entries = htonl(nr_objects);
+       sha1write(f, &hdr, sizeof(hdr));
+       offset = sizeof(hdr);
+       for (i = 0; i < nr_objects; i++)
+               offset = write_one(f, objects + i, offset);
 
-       for (i = 0; i < nr_objects; i++) {
-               struct object_entry *entry = objects + i;
-               entry->offset = offset;
-               offset += write_object(f, entry);
-       }
        sha1close(f, pack_file_sha1, 1);
        mb = offset >> 20;
        offset &= 0xfffff;
@@ -104,7 +153,7 @@ static void write_pack_file(void)
 static void write_index_file(void)
 {
        int i;
-       struct sha1file *f = sha1create("%s.%s", base_name, "idx");
+       struct sha1file *f = sha1create("%s-%s.%s", base_name, sha1_to_hex(object_list_sha1), "idx");
        struct object_entry **list = sorted_by_sha;
        struct object_entry **last = list + nr_objects;
        unsigned int array[256];
@@ -141,11 +190,14 @@ static void write_index_file(void)
        sha1close(f, NULL, 1);
 }
 
-static void add_object_entry(unsigned char *sha1, unsigned int hash)
+static int add_object_entry(unsigned char *sha1, unsigned int hash)
 {
        unsigned int idx = nr_objects;
        struct object_entry *entry;
 
+       if (incremental && has_sha1_pack(sha1))
+               return 0;
+
        if (idx >= nr_alloc) {
                unsigned int needed = (idx + 1024) * 3 / 2;
                objects = xrealloc(objects, needed * sizeof(*entry));
@@ -156,6 +208,7 @@ static void add_object_entry(unsigned char *sha1, unsigned int hash)
        memcpy(entry->sha1, sha1, 20);
        entry->hash = hash;
        nr_objects = idx+1;
+       return 1;
 }
 
 static void check_object(struct object_entry *entry)
@@ -169,6 +222,8 @@ static void check_object(struct object_entry *entry)
                        entry->type = OBJ_TREE;
                } else if (!strcmp(type, "blob")) {
                        entry->type = OBJ_BLOB;
+               } else if (!strcmp(type, "tag")) {
+                       entry->type = OBJ_TAG;
                } else
                        die("unable to pack object %s of type %s",
                            sha1_to_hex(entry->sha1), type);
@@ -331,14 +386,23 @@ static void find_deltas(struct object_entry **list, int window, int depth)
 
 int main(int argc, char **argv)
 {
+       SHA_CTX ctx;
        char line[PATH_MAX + 20];
-       int window = 10, depth = 10;
+       int window = 10, depth = 10, pack_to_stdout = 0;
        int i;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
                if (*arg == '-') {
+                       if (!strcmp("--non-empty", arg)) {
+                               non_empty = 1;
+                               continue;
+                       }
+                       if (!strcmp("--incremental", arg)) {
+                               incremental = 1;
+                               continue;
+                       }
                        if (!strncmp("--window=", arg, 9)) {
                                char *end;
                                window = strtoul(arg+9, &end, 0);
@@ -353,6 +417,10 @@ int main(int argc, char **argv)
                                        usage(pack_usage);
                                continue;
                        }
+                       if (!strcmp("--stdout", arg)) {
+                               pack_to_stdout = 1;
+                               continue;
+                       }
                        usage(pack_usage);
                }
                if (base_name)
@@ -360,9 +428,10 @@ int main(int argc, char **argv)
                base_name = arg;
        }
 
-       if (!base_name)
+       if (pack_to_stdout != !base_name)
                usage(pack_usage);
 
+       SHA1_Init(&ctx);
        while (fgets(line, sizeof(line), stdin) != NULL) {
                unsigned int hash;
                char *p;
@@ -378,11 +447,15 @@ int main(int argc, char **argv)
                                continue;
                        hash = hash * 11 + c;
                }
-               add_object_entry(sha1, hash);
+               if (add_object_entry(sha1, hash))
+                       SHA1_Update(&ctx, sha1, 20);
        }
+       SHA1_Final(object_list_sha1, &ctx);
+       if (non_empty && !nr_objects)
+               return 0;
        get_object_details();
 
-       printf("Packing %d objects\n", nr_objects);
+       fprintf(stderr, "Packing %d objects\n", nr_objects);
 
        sorted_by_sha = create_sorted_list(sha1_sort);
        sorted_by_type = create_sorted_list(type_size_sort);
@@ -390,6 +463,9 @@ int main(int argc, char **argv)
                find_deltas(sorted_by_type, window+1, depth);
 
        write_pack_file();
-       write_index_file();
+       if (!pack_to_stdout) {
+               write_index_file();
+               puts(sha1_to_hex(object_list_sha1));
+       }
        return 0;
 }
diff --git a/pack.h b/pack.h
new file mode 100644 (file)
index 0000000..657deaa
--- /dev/null
+++ b/pack.h
@@ -0,0 +1,32 @@
+#ifndef PACK_H
+#define PACK_H
+
+/*
+ * The packed object type is stored in 3 bits.
+ * The type value 0 is a reserved prefix if ever there is more than 7
+ * object types, or any future format extensions.
+ */
+enum object_type {
+       OBJ_EXT = 0,
+       OBJ_COMMIT = 1,
+       OBJ_TREE = 2,
+       OBJ_BLOB = 3,
+       OBJ_TAG = 4,
+       /* 5/6 for future expansion */
+       OBJ_DELTA = 7,
+};
+
+/*
+ * Packed object header
+ */
+#define PACK_SIGNATURE 0x5041434b      /* "PACK" */
+#define PACK_VERSION 2
+struct pack_header {
+       unsigned int hdr_signature;
+       unsigned int hdr_version;
+       unsigned int hdr_entries;
+};
+
+extern int verify_pack(struct packed_git *, int);
+
+#endif
index a8d75ee..26281ea 100644 (file)
@@ -20,36 +20,20 @@ void *patch_delta(void *src_buf, unsigned long src_size,
        const unsigned char *data, *top;
        unsigned char *dst_buf, *out, cmd;
        unsigned long size;
-       int i;
 
-       /* the smallest delta size possible is 6 bytes */
-       if (delta_size < 6)
+       if (delta_size < DELTA_SIZE_MIN)
                return NULL;
 
        data = delta_buf;
        top = delta_buf + delta_size;
 
        /* make sure the orig file size matches what we expect */
-       size = i = 0;
-       cmd = *data++;
-       while (cmd) {
-               if (cmd & 1)
-                       size |= *data++ << i;
-               i += 8;
-               cmd >>= 1;
-       }
+       size = get_delta_hdr_size(&data);
        if (size != src_size)
                return NULL;
 
        /* now the result size */
-       size = i = 0;
-       cmd = *data++;
-       while (cmd) {
-               if (cmd & 1)
-                       size |= *data++ << i;
-               i += 8;
-               cmd >>= 1;
-       }
+       size = get_delta_hdr_size(&data);
        dst_buf = malloc(size);
        if (!dst_buf)
                return NULL;
diff --git a/pkt-line.c b/pkt-line.c
new file mode 100644 (file)
index 0000000..6947304
--- /dev/null
@@ -0,0 +1,117 @@
+#include "cache.h"
+#include "pkt-line.h"
+
+/*
+ * Write a packetized stream, where each line is preceded by
+ * its length (including the header) as a 4-byte hex number.
+ * A length of 'zero' means end of stream (and a length of 1-3
+ * would be an error). 
+ *
+ * This is all pretty stupid, but we use this packetized line
+ * format to make a streaming format possible without ever
+ * over-running the read buffers. That way we'll never read
+ * into what might be the pack data (which should go to another
+ * process entirely).
+ *
+ * The writing side could use stdio, but since the reading
+ * side can't, we stay with pure read/write interfaces.
+ */
+static void safe_write(int fd, const void *buf, unsigned n)
+{
+       while (n) {
+               int ret = write(fd, buf, n);
+               if (ret > 0) {
+                       buf += ret;
+                       n -= ret;
+                       continue;
+               }
+               if (!ret)
+                       die("write error (disk full?)");
+               if (errno == EAGAIN || errno == EINTR)
+                       continue;
+               die("write error (%s)", strerror(errno));
+       }
+}
+
+/*
+ * If we buffered things up above (we don't, but we should),
+ * we'd flush it here
+ */
+void packet_flush(int fd)
+{
+       safe_write(fd, "0000", 4);
+}
+
+#define hex(a) (hexchar[(a) & 15])
+void packet_write(int fd, const char *fmt, ...)
+{
+       static char buffer[1000];
+       static char hexchar[] = "0123456789abcdef";
+       va_list args;
+       unsigned n;
+
+       va_start(args, fmt);
+       n = vsnprintf(buffer + 4, sizeof(buffer) - 4, fmt, args);
+       va_end(args);
+       if (n >= sizeof(buffer)-4)
+               die("protocol error: impossibly long line");
+       n += 4;
+       buffer[0] = hex(n >> 12);
+       buffer[1] = hex(n >> 8);
+       buffer[2] = hex(n >> 4);
+       buffer[3] = hex(n);
+       safe_write(fd, buffer, n);
+}
+
+static void safe_read(int fd, void *buffer, unsigned size)
+{
+       int n = 0;
+
+       while (n < size) {
+               int ret = read(fd, buffer + n, size - n);
+               if (ret < 0) {
+                       if (errno == EINTR || errno == EAGAIN)
+                               continue;
+                       die("read error (%s)", strerror(errno));
+               }
+               if (!ret)
+                       die("unexpected EOF");
+               n += ret;
+       }
+}
+
+int packet_read_line(int fd, char *buffer, unsigned size)
+{
+       int n;
+       unsigned len;
+       char linelen[4];
+
+       safe_read(fd, linelen, 4);
+
+       len = 0;
+       for (n = 0; n < 4; n++) {
+               unsigned char c = linelen[n];
+               len <<= 4;
+               if (c >= '0' && c <= '9') {
+                       len += c - '0';
+                       continue;
+               }
+               if (c >= 'a' && c <= 'f') {
+                       len += c - 'a' + 10;
+                       continue;
+               }
+               if (c >= 'A' && c <= 'F') {
+                       len += c - 'A' + 10;
+                       continue;
+               }
+               die("protocol error: bad line length character");
+       }
+       if (!len)
+               return 0;
+       len -= 4;
+       if (len >= size)
+               die("protocol error: bad line length %d", len);
+       safe_read(fd, buffer, len);
+       buffer[len] = 0;
+       return len;
+}
diff --git a/pkt-line.h b/pkt-line.h
new file mode 100644 (file)
index 0000000..b0b4f6d
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef PKTLINE_H
+#define PKTLINE_H
+
+/*
+ * Silly packetized line writing interface
+ */
+void packet_flush(int fd);
+void packet_write(int fd, const char *fmt, ...);
+
+int packet_read_line(int fd, char *buffer, unsigned size);
+
+#endif
diff --git a/prune-packed.c b/prune-packed.c
new file mode 100644 (file)
index 0000000..41ee2a7
--- /dev/null
@@ -0,0 +1,66 @@
+#include "cache.h"
+
+static const char prune_packed_usage[] = "git-prune-packed: usage: git-prune-packed";
+
+static void prune_dir(int i, DIR *dir, char *pathname, int len)
+{
+       struct dirent *de;
+       char hex[40];
+
+       sprintf(hex, "%02x", i);
+       while ((de = readdir(dir)) != NULL) {
+               unsigned char sha1[20];
+               if (strlen(de->d_name) != 38)
+                       continue;
+               memcpy(hex+2, de->d_name, 38);
+               if (get_sha1_hex(hex, sha1))
+                       continue;
+               if (!has_sha1_pack(sha1))
+                       continue;
+               memcpy(pathname + len, de->d_name, 38);
+               if (unlink(pathname) < 0)
+                       error("unable to unlink %s", pathname);
+       }
+}
+
+static void prune_packed_objects(void)
+{
+       int i;
+       static char pathname[PATH_MAX];
+       const char *dir = get_object_directory();
+       int len = strlen(dir);
+
+       if (len > PATH_MAX - 42)
+               die("impossible object directory");
+       memcpy(pathname, dir, len);
+       if (len && pathname[len-1] != '/')
+               pathname[len++] = '/';
+       for (i = 0; i < 256; i++) {
+               DIR *d;
+
+               sprintf(pathname + len, "%02x/", i);
+               d = opendir(pathname);
+               if (!d)
+                       die("unable to open %s", pathname);
+               prune_dir(i, d, pathname, len + 3);
+               closedir(d);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg == '-') {
+                       /* Handle flags here .. */
+                       usage(prune_packed_usage);
+               }
+               /* Handle arguments here .. */
+               usage(prune_packed_usage);
+       }
+       prune_packed_objects();
+       return 0;
+}
diff --git a/receive-pack.c b/receive-pack.c
new file mode 100644 (file)
index 0000000..dfa7cd1
--- /dev/null
@@ -0,0 +1,223 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+#include <sys/wait.h>
+
+static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+
+static const char *unpacker = "git-unpack-objects";
+
+static int show_ref(const char *path, const unsigned char *sha1)
+{
+       packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
+       return 0;
+}
+
+static void write_head_info(void)
+{
+       for_each_ref(show_ref);
+}
+
+struct command {
+       struct command *next;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       char ref_name[0];
+};
+
+static struct command *commands = NULL;
+
+static int is_all_zeroes(const char *hex)
+{
+       int i;
+       for (i = 0; i < 40; i++)
+               if (*hex++ != '0')
+                       return 0;
+       return 1;
+}
+
+static int verify_old_ref(const char *name, char *hex_contents)
+{
+       int fd, ret;
+       char buffer[60];
+
+       if (is_all_zeroes(hex_contents))
+               return 0;
+       fd = open(name, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       ret = read(fd, buffer, 40);
+       close(fd);
+       if (ret != 40)
+               return -1;
+       if (memcmp(buffer, hex_contents, 40))
+               return -1;
+       return 0;
+}
+
+static void update(const char *name, unsigned char *old_sha1, unsigned char *new_sha1)
+{
+       char new_hex[60], *old_hex, *lock_name;
+       int newfd, namelen, written;
+
+       namelen = strlen(name);
+       lock_name = xmalloc(namelen + 10);
+       memcpy(lock_name, name, namelen);
+       memcpy(lock_name + namelen, ".lock", 6);
+
+       strcpy(new_hex, sha1_to_hex(new_sha1));
+       old_hex = sha1_to_hex(old_sha1);
+       if (!has_sha1_file(new_sha1))
+               die("unpack should have generated %s, but I can't find it!", new_hex);
+
+       newfd = open(lock_name, O_CREAT | O_EXCL | O_WRONLY, 0644);
+       if (newfd < 0)
+               die("unable to create %s (%s)", lock_name, strerror(errno));
+
+       /* Write the ref with an ending '\n' */
+       new_hex[40] = '\n';
+       new_hex[41] = 0;
+       written = write(newfd, new_hex, 41);
+       /* Remove the '\n' again */
+       new_hex[40] = 0;
+
+       close(newfd);
+       if (written != 41) {
+               unlink(lock_name);
+               die("unable to write %s", lock_name);
+       }
+       if (verify_old_ref(name, old_hex) < 0) {
+               unlink(lock_name);
+               die("%s changed during push", name);
+       }
+       if (rename(lock_name, name) < 0) {
+               unlink(lock_name);
+               die("unable to replace %s", name);
+       }
+       fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
+}
+
+
+/*
+ * This gets called after(if) we've successfully
+ * unpacked the data payload.
+ */
+static void execute_commands(void)
+{
+       struct command *cmd = commands;
+
+       while (cmd) {
+               update(cmd->ref_name, cmd->old_sha1, cmd->new_sha1);
+               cmd = cmd->next;
+       }
+}
+
+static void read_head_info(void)
+{
+       struct command **p = &commands;
+       for (;;) {
+               static char line[1000];
+               unsigned char old_sha1[20], new_sha1[20];
+               struct command *cmd;
+               int len;
+
+               len = packet_read_line(0, line, sizeof(line));
+               if (!len)
+                       break;
+               if (line[len-1] == '\n')
+                       line[--len] = 0;
+               if (len < 83 ||
+                   line[40] != ' ' ||
+                   line[81] != ' ' ||
+                   get_sha1_hex(line, old_sha1) ||
+                   get_sha1_hex(line + 41, new_sha1))
+                       die("protocol error: expected old/new/ref, got '%s'", line);
+               cmd = xmalloc(sizeof(struct command) + len - 80);
+               memcpy(cmd->old_sha1, old_sha1, 20);
+               memcpy(cmd->new_sha1, new_sha1, 20);
+               memcpy(cmd->ref_name, line + 82, len - 81);
+               cmd->next = NULL;
+               *p = cmd;
+               p = &cmd->next;
+       }
+}
+
+static void unpack(void)
+{
+       pid_t pid = fork();
+
+       if (pid < 0)
+               die("unpack fork failed");
+       if (!pid) {
+               execlp(unpacker, unpacker, NULL);
+               die("unpack execute failed");
+       }
+
+       for (;;) {
+               int status, code;
+               int retval = waitpid(pid, &status, 0);
+
+               if (retval < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       die("waitpid failed (%s)", strerror(retval));
+               }
+               if (retval != pid)
+                       die("waitpid is confused");
+               if (WIFSIGNALED(status))
+                       die("%s died of signal %d", unpacker, WTERMSIG(status));
+               if (!WIFEXITED(status))
+                       die("%s died out of really strange complications", unpacker);
+               code = WEXITSTATUS(status);
+               if (code)
+                       die("%s exited with error code %d", unpacker, code);
+               return;
+       }
+}
+
+int main(int argc, char **argv)
+{
+       int i;
+       const char *dir = NULL;
+
+       argv++;
+       for (i = 1; i < argc; i++) {
+               const char *arg = *argv++;
+
+               if (*arg == '-') {
+                       /* Do flag handling here */
+                       usage(receive_pack_usage);
+               }
+               if (dir)
+                       usage(receive_pack_usage);
+               dir = arg;
+       }
+       if (!dir)
+               usage(receive_pack_usage);
+
+       /* chdir to the directory. If that fails, try appending ".git" */
+       if (chdir(dir) < 0) {
+               static char path[PATH_MAX];
+               snprintf(path, sizeof(path), "%s.git", dir);
+               if (chdir(path) < 0)
+                       die("unable to cd to %s", dir);
+       }
+
+       /* If we have a ".git" directory, chdir to it */
+       chdir(".git");
+       setenv("GIT_DIR", ".", 1);
+
+       if (access("objects", X_OK) < 0 || access("refs/heads", X_OK) < 0)
+               die("%s doesn't appear to be a git directory", dir);
+       write_head_info();
+
+       /* EOF */
+       packet_flush(1);
+
+       read_head_info();
+       if (commands) {
+               unpack();
+               execute_commands();
+       }
+       return 0;
+}
diff --git a/refs.c b/refs.c
index 9973d1f..7ccd721 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -3,6 +3,76 @@
 
 #include <errno.h>
 
+static int read_ref(const char *path, unsigned char *sha1)
+{
+       int ret = -1;
+       int fd = open(path, O_RDONLY);
+
+       if (fd >= 0) {
+               char buffer[60];
+               if (read(fd, buffer, sizeof(buffer)) >= 40)
+                       ret = get_sha1_hex(buffer, sha1);
+               close(fd);
+       }
+       return ret;
+}
+
+static int do_for_each_ref(const char *base, int (*fn)(const char *path, const unsigned char *sha1))
+{
+       int retval = 0;
+       DIR *dir = opendir(base);
+
+       if (dir) {
+               struct dirent *de;
+               int baselen = strlen(base);
+               char *path = xmalloc(baselen + 257);
+
+               if (!strncmp(base, "./", 2)) {
+                       base += 2;
+                       baselen -= 2;
+               }
+               memcpy(path, base, baselen);
+               if (baselen && base[baselen-1] != '/')
+                       path[baselen++] = '/';
+
+               while ((de = readdir(dir)) != NULL) {
+                       unsigned char sha1[20];
+                       struct stat st;
+                       int namelen;
+
+                       if (de->d_name[0] == '.')
+                               continue;
+                       namelen = strlen(de->d_name);
+                       if (namelen > 255)
+                               continue;
+                       memcpy(path + baselen, de->d_name, namelen+1);
+                       if (lstat(path, &st) < 0)
+                               continue;
+                       if (S_ISDIR(st.st_mode)) {
+                               retval = do_for_each_ref(path, fn);
+                               if (retval)
+                                       break;
+                               continue;
+                       }
+                       if (read_ref(path, sha1) < 0)
+                               continue;
+                       if (!has_sha1_file(sha1))
+                               continue;
+                       retval = fn(path, sha1);
+                       if (retval)
+                               break;
+               }
+               free(path);
+               closedir(dir);
+       }
+       return retval;
+}
+
+int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1))
+{
+       return do_for_each_ref(get_refs_directory(), fn);
+}
+
 static char *ref_file_name(const char *ref)
 {
        char *base = get_refs_directory();
diff --git a/refs.h b/refs.h
index 60cf480..a79cb13 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -1,6 +1,12 @@
 #ifndef REFS_H
 #define REFS_H
 
+/*
+ * Calls the specified function for each ref file until it returns nonzero,
+ * and returns the value
+ */
+extern int for_each_ref(int (*fn)(const char *path, const unsigned char *sha1));
+
 /** Reads the refs file specified into sha1 **/
 extern int get_ref_sha1(const char *ref, unsigned char *sha1);
 
index bf61b74..8250063 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "tag.h"
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
@@ -14,11 +15,16 @@ static const char rev_list_usage[] =
                      "  --max-count=nr\n"
                      "  --max-age=epoch\n"
                      "  --min-age=epoch\n"
+                     "  --bisect\n"
+                     "  --objects\n"
+                     "  --unpacked\n"
                      "  --header\n"
                      "  --pretty\n"
                      "  --merge-order [ --show-breaks ]";
 
+static int unpacked = 0;
 static int bisect_list = 0;
+static int tag_objects = 0;
 static int tree_objects = 0;
 static int blob_objects = 0;
 static int verbose_header = 0;
@@ -57,7 +63,8 @@ static void show_commit(struct commit *commit)
                static char pretty_header[16384];
                pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header));
                printf("%s%c", pretty_header, hdr_termination);
-       }       
+       }
+       fflush(stdout);
 }
 
 static int filter_commit(struct commit * commit)
@@ -102,7 +109,7 @@ static struct object_list **add_object(struct object *obj, struct object_list **
 {
        struct object_list *entry = xmalloc(sizeof(*entry));
        entry->item = obj;
-       entry->next = NULL;
+       entry->next = *p;
        entry->name = name;
        *p = entry;
        return &entry->next;
@@ -142,9 +149,11 @@ static struct object_list **process_tree(struct tree *tree, struct object_list *
        return p;
 }
 
+static struct object_list *pending_objects = NULL;
+
 static void show_commit_list(struct commit_list *list)
 {
-       struct object_list *objects = NULL, **p = &objects;
+       struct object_list *objects = NULL, **p = &objects, *pending;
        while (list) {
                struct commit *commit = pop_most_recent_commit(&list, SEEN);
 
@@ -152,6 +161,26 @@ static void show_commit_list(struct commit_list *list)
                if (process_commit(commit) == STOP)
                        break;
        }
+       for (pending = pending_objects; pending; pending = pending->next) {
+               struct object *obj = pending->item;
+               const char *name = pending->name;
+               if (obj->flags & (UNINTERESTING | SEEN))
+                       continue;
+               if (obj->type == tag_type) {
+                       obj->flags |= SEEN;
+                       p = add_object(obj, p, name);
+                       continue;
+               }
+               if (obj->type == tree_type) {
+                       p = process_tree((struct tree *)obj, p, name);
+                       continue;
+               }
+               if (obj->type == blob_type) {
+                       p = process_blob((struct blob *)obj, p, name);
+                       continue;
+               }
+               die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+       }
        while (objects) {
                printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
                objects = objects->next;
@@ -286,14 +315,16 @@ static struct commit_list *find_bisection(struct commit_list *list)
        return best;
 }
 
-struct commit_list *limit_list(struct commit_list *list)
+static struct commit_list *limit_list(struct commit_list *list)
 {
        struct commit_list *newlist = NULL;
        struct commit_list **p = &newlist;
-       do {
+       while (list) {
                struct commit *commit = pop_most_recent_commit(&list, SEEN);
                struct object *obj = &commit->object;
 
+               if (unpacked && has_sha1_pack(obj->sha1))
+                       obj->flags |= UNINTERESTING;
                if (obj->flags & UNINTERESTING) {
                        mark_parents_uninteresting(commit);
                        if (everybody_uninteresting(list))
@@ -301,12 +332,84 @@ struct commit_list *limit_list(struct commit_list *list)
                        continue;
                }
                p = &commit_list_insert(commit, p)->next;
-       } while (list);
+       }
        if (bisect_list)
                newlist = find_bisection(newlist);
        return newlist;
 }
 
+static void add_pending_object(struct object *obj, const char *name)
+{
+       add_object(obj, &pending_objects, name);
+}
+
+static struct commit *get_commit_reference(const char *name, unsigned int flags)
+{
+       unsigned char sha1[20];
+       struct object *object;
+
+       if (get_sha1(name, sha1))
+               usage(rev_list_usage);
+       object = parse_object(sha1);
+       if (!object)
+               die("bad object %s", name);
+
+       /*
+        * Tag object? Look what it points to..
+        */
+       if (object->type == tag_type) {
+               struct tag *tag = (struct tag *) object;
+               object->flags |= flags;
+               if (tag_objects && !(object->flags & UNINTERESTING))
+                       add_pending_object(object, tag->tag);
+               object = tag->tagged;
+       }
+
+       /*
+        * Commit object? Just return it, we'll do all the complex
+        * reachability crud.
+        */
+       if (object->type == commit_type) {
+               struct commit *commit = (struct commit *)object;
+               object->flags |= flags;
+               if (parse_commit(commit) < 0)
+                       die("unable to parse commit %s", name);
+               return commit;
+       }
+
+       /*
+        * Tree object? Either mark it uniniteresting, or add it
+        * to the list of objects to look at later..
+        */
+       if (object->type == tree_type) {
+               struct tree *tree = (struct tree *)object;
+               if (!tree_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_tree_uninteresting(tree);
+                       return NULL;
+               }
+               add_pending_object(object, "");
+               return NULL;
+       }
+
+       /*
+        * Blob object? You know the drill by now..
+        */
+       if (object->type == blob_type) {
+               struct blob *blob = (struct blob *)object;
+               if (!blob_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_blob_uninteresting(blob);
+                       return NULL;
+               }
+               add_pending_object(object, "");
+               return NULL;
+       }
+       die("%s is unknown object", name);
+}
+
 int main(int argc, char **argv)
 {
        struct commit_list *list = NULL;
@@ -315,7 +418,6 @@ int main(int argc, char **argv)
        for (i = 1 ; i < argc; i++) {
                int flags;
                char *arg = argv[i];
-               unsigned char sha1[20];
                struct commit *commit;
 
                if (!strncmp(arg, "--max-count=", 12)) {
@@ -350,10 +452,16 @@ int main(int argc, char **argv)
                        continue;
                }
                if (!strcmp(arg, "--objects")) {
+                       tag_objects = 1;
                        tree_objects = 1;
                        blob_objects = 1;
                        continue;
                }
+               if (!strcmp(arg, "--unpacked")) {
+                       unpacked = 1;
+                       limited = 1;
+                       continue;
+               }
                if (!strncmp(arg, "--merge-order", 13)) {
                        merge_order = 1;
                        continue;
@@ -369,18 +477,14 @@ int main(int argc, char **argv)
                        arg++;
                        limited = 1;
                }
-               if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
+               if (show_breaks && !merge_order)
                        usage(rev_list_usage);
-               commit = lookup_commit_reference(sha1);
-               if (!commit || parse_commit(commit) < 0)
-                       die("bad commit object %s", arg);
-               commit->object.flags |= flags;
-               commit_list_insert(commit, &list);
+               commit = get_commit_reference(arg, flags);
+               if (!commit)
+                       continue;
+               insert_by_date(&list, commit);
        }
 
-       if (!list)
-               usage(rev_list_usage);
-
        if (!merge_order) {             
                if (limited)
                        list = limit_list(list);
index c134ffd..d62efda 100644 (file)
@@ -5,6 +5,7 @@
  */
 #include "cache.h"
 #include "commit.h"
+#include "refs.h"
 
 static char *def = NULL;
 static int no_revs = 0;
@@ -46,7 +47,7 @@ static int is_rev_argument(const char *arg)
        }
 }
 
-static void show_rev(int type, unsigned char *sha1)
+static void show_rev(int type, const unsigned char *sha1)
 {
        if (no_revs)
                return;
@@ -101,13 +102,133 @@ static int get_parent(char *name, unsigned char *result, int idx)
        return -1;
 }
 
+static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
+{
+       static char dirname[PATH_MAX];
+       char hex[40];
+       DIR *dir;
+       int found;
+
+       snprintf(dirname, sizeof(dirname), "%s/%.2s", get_object_directory(), name);
+       dir = opendir(dirname);
+       sprintf(hex, "%.2s", name);
+       found = 0;
+       if (dir) {
+               struct dirent *de;
+               while ((de = readdir(dir)) != NULL) {
+                       if (strlen(de->d_name) != 38)
+                               continue;
+                       if (memcmp(de->d_name, name + 2, len-2))
+                               continue;
+                       memcpy(hex + 2, de->d_name, 38);
+                       if (++found > 1)
+                               break;
+               }
+               closedir(dir);
+       }
+       if (found == 1)
+               return get_sha1_hex(hex, sha1) == 0;
+       return 0;
+}
+
+static int match_sha(unsigned len, const unsigned char *a, const unsigned char *b)
+{
+       do {
+               if (*a != *b)
+                       return 0;
+               a++;
+               b++;
+               len -= 2;
+       } while (len > 1);
+       if (len)
+               if ((*a ^ *b) & 0xf0)
+                       return 0;
+       return 1;
+}
+
+static int find_short_packed_object(int len, const unsigned char *match, unsigned char *sha1)
+{
+       struct packed_git *p;
+
+       prepare_packed_git();
+       for (p = packed_git; p; p = p->next) {
+               unsigned num = num_packed_objects(p);
+               unsigned first = 0, last = num;
+               while (first < last) {
+                       unsigned mid = (first + last) / 2;
+                       unsigned char now[20];
+                       int cmp;
+
+                       nth_packed_object_sha1(p, mid, now);
+                       cmp = memcmp(match, now, 20);
+                       if (!cmp) {
+                               first = mid;
+                               break;
+                       }
+                       if (cmp > 0) {
+                               first = mid+1;
+                               continue;
+                       }
+                       last = mid;
+               }
+               if (first < num) {
+                       unsigned char now[20], next[20];
+                       nth_packed_object_sha1(p, first, now);
+                       if (match_sha(len, match, now)) {
+                               if (nth_packed_object_sha1(p, first+1, next) || !match_sha(len, match, next)) {
+                                       memcpy(sha1, now, 20);
+                                       return 1;
+                               }
+                       }
+               }       
+       }
+       return 0;
+}
+
+static int get_short_sha1(char *name, unsigned char *sha1)
+{
+       int i;
+       char canonical[40];
+       unsigned char res[20];
+
+       memset(res, 0, 20);
+       memset(canonical, 'x', 40);
+       for (i = 0;;i++) {
+               unsigned char c = name[i];
+               unsigned char val;
+               if (!c || i > 40)
+                       break;
+               if (c >= '0' && c <= '9')
+                       val = c - '0';
+               else if (c >= 'a' && c <= 'f')
+                       val = c - 'a' + 10;
+               else if (c >= 'A' && c <='F') {
+                       val = c - 'A' + 10;
+                       c -= 'A' - 'a';
+               }
+               else
+                       return -1;
+               canonical[i] = c;
+               if (!(i & 1))
+                       val <<= 4;
+               res[i >> 1] |= val;
+       }
+       if (i < 4)
+               return -1;
+       if (find_short_object_filename(i, canonical, sha1))
+               return 0;
+       if (find_short_packed_object(i, res, sha1))
+               return 0;
+       return -1;
+}
+
 /*
  * This is like "get_sha1()", except it allows "sha1 expressions",
  * notably "xyz^" for "parent of xyz"
  */
 static int get_extended_sha1(char *name, unsigned char *sha1)
 {
-       int parent;
+       int parent, ret;
        int len = strlen(name);
 
        parent = 1;
@@ -116,14 +237,16 @@ static int get_extended_sha1(char *name, unsigned char *sha1)
                len--;
        }
        if (len > 1 && name[len-1] == '^') {
-               int ret;
                name[len-1] = 0;
                ret = get_parent(name, sha1, parent);
                name[len-1] = '^';
                if (!ret)
                        return 0;
        }
-       return get_sha1(name, sha1);
+       ret = get_sha1(name, sha1);
+       if (!ret)
+               return 0;
+       return get_short_sha1(name, sha1);
 }
 
 static void show_default(void)
@@ -142,6 +265,12 @@ static void show_default(void)
        }
 }
 
+static int show_reference(const char *refname, const unsigned char *sha1)
+{
+       show_rev(NORMAL, sha1);
+       return 0;
+}
+
 int main(int argc, char **argv)
 {
        int i, as_is = 0;
@@ -185,6 +314,10 @@ int main(int argc, char **argv)
                                show_type ^= REVERSED;
                                continue;
                        }
+                       if (!strcmp(arg, "--all")) {
+                               for_each_ref(show_reference);
+                               continue;
+                       }
                        show_arg(arg);
                        continue;
                }
diff --git a/send-pack.c b/send-pack.c
new file mode 100644 (file)
index 0000000..f098acb
--- /dev/null
@@ -0,0 +1,199 @@
+#include "cache.h"
+#include "pkt-line.h"
+
+static const char send_pack_usage[] = "git-send-pack [--exec=other] destination [heads]*";
+static const char *exec = "git-receive-pack";
+
+struct ref {
+       struct ref *next;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       char name[0];
+};
+
+static void exec_pack_objects(void)
+{
+       static char *args[] = {
+               "git-pack-objects",
+               "--stdout",
+               NULL
+       };
+       execvp("git-pack-objects", args);
+       die("git-pack-objects exec failed (%s)", strerror(errno));
+}
+
+static void exec_rev_list(struct ref *refs)
+{
+       static char *args[1000];
+       int i = 0;
+
+       args[i++] = "git-rev-list";     /* 0 */
+       args[i++] = "--objects";        /* 1 */
+       while (refs) {
+               char *buf = malloc(100);
+               if (i > 900)
+                       die("git-rev-list environment overflow");
+               args[i++] = buf;
+               snprintf(buf, 50, "^%s", sha1_to_hex(refs->old_sha1));
+               buf += 50;
+               args[i++] = buf;
+               snprintf(buf, 50, "%s", sha1_to_hex(refs->new_sha1));
+               refs = refs->next;
+       }
+       args[i] = NULL;
+       execvp("git-rev-list", args);
+       die("git-rev-list exec failed (%s)", strerror(errno));
+}
+
+static void rev_list(int fd, struct ref *refs)
+{
+       int pipe_fd[2];
+       pid_t pack_objects_pid;
+
+       if (pipe(pipe_fd) < 0)
+               die("rev-list setup: pipe failed");
+       pack_objects_pid = fork();
+       if (!pack_objects_pid) {
+               dup2(pipe_fd[0], 0);
+               dup2(fd, 1);
+               close(pipe_fd[0]);
+               close(pipe_fd[1]);
+               close(fd);
+               exec_pack_objects();
+               die("pack-objects setup failed");
+       }
+       if (pack_objects_pid < 0)
+               die("pack-objects fork failed");
+       dup2(pipe_fd[1], 1);
+       close(pipe_fd[0]);
+       close(pipe_fd[1]);
+       close(fd);
+       exec_rev_list(refs);
+}
+
+static int pack_objects(int fd, struct ref *refs)
+{
+       pid_t rev_list_pid;
+
+       rev_list_pid = fork();
+       if (!rev_list_pid) {
+               rev_list(fd, refs);
+               die("rev-list setup failed");
+       }
+       if (rev_list_pid < 0)
+               die("rev-list fork failed");
+       /*
+        * We don't wait for the rev-list pipeline in the parent:
+        * we end up waiting for the other end instead
+        */
+       return 0;
+}
+
+static int read_ref(const char *ref, unsigned char *sha1)
+{
+       int fd, ret;
+       static char pathname[PATH_MAX];
+       char buffer[60];
+       const char *git_dir = gitenv(GIT_DIR_ENVIRONMENT) ? : DEFAULT_GIT_DIR_ENVIRONMENT;
+
+       snprintf(pathname, sizeof(pathname), "%s/%s", git_dir, ref);
+       fd = open(pathname, O_RDONLY);
+       if (fd < 0)
+               return -1;
+       ret = -1;
+       if (read(fd, buffer, sizeof(buffer)) >= 40)
+               ret = get_sha1_hex(buffer, sha1);
+       close(fd);
+       return ret;
+}
+
+static int send_pack(int in, int out, int nr_match, char **match)
+{
+       struct ref *ref_list = NULL, **last_ref = &ref_list;
+       struct ref *ref;
+
+       for (;;) {
+               unsigned char old_sha1[20];
+               unsigned char new_sha1[20];
+               static char buffer[1000];
+               char *name;
+               int len;
+
+               len = packet_read_line(in, buffer, sizeof(buffer));
+               if (!len)
+                       break;
+               if (buffer[len-1] == '\n')
+                       buffer[--len] = 0;
+
+               if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
+                       die("protocol error: expected sha/ref, got '%s'", buffer);
+               name = buffer + 41;
+               if (nr_match && !path_match(name, nr_match, match))
+                       continue;
+               if (read_ref(name, new_sha1) < 0)
+                       return error("no such local reference '%s'", name);
+               if (!has_sha1_file(old_sha1))
+                       return error("remote '%s' points to object I don't have", name);
+               if (!memcmp(old_sha1, new_sha1, 20)) {
+                       fprintf(stderr, "'%s' unchanged\n", name);
+                       continue;
+               }
+               ref = xmalloc(sizeof(*ref) + len - 40);
+               memcpy(ref->old_sha1, old_sha1, 20);
+               memcpy(ref->new_sha1, new_sha1, 20);
+               memcpy(ref->name, buffer + 41, len - 40);
+               ref->next = NULL;
+               *last_ref = ref;
+               last_ref = &ref->next;
+       }
+
+       for (ref = ref_list; ref; ref = ref->next) {
+               char old_hex[60], *new_hex;
+               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
+               new_hex = sha1_to_hex(ref->new_sha1);
+               packet_write(out, "%s %s %s", old_hex, new_hex, ref->name);
+               fprintf(stderr, "'%s': updating from %s to %s\n", ref->name, old_hex, new_hex);
+       }
+       
+       packet_flush(out);
+       if (ref_list)
+               pack_objects(out, ref_list);
+       close(out);
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       int i, nr_heads = 0;
+       char *dest = NULL;
+       char **heads = NULL;
+       int fd[2], ret;
+       pid_t pid;
+
+       argv++;
+       for (i = 1; i < argc; i++) {
+               char *arg = *argv++;
+
+               if (*arg == '-') {
+                       if (!strncmp(arg, "--exec=", 7)) {
+                               exec = arg + 7;
+                               continue;
+                       }
+                       usage(send_pack_usage);
+               }
+               dest = arg;
+               heads = argv;
+               nr_heads = argc - i -1;
+               break;
+       }
+       if (!dest)
+               usage(send_pack_usage);
+       pid = git_connect(fd, dest, exec);
+       if (pid < 0)
+               return 1;
+       ret = send_pack(fd[0], fd[1], nr_heads, heads);
+       close(fd[0]);
+       close(fd[1]);
+       finish_connect(pid);
+       return ret;
+}
index 698b43c..8f20e2f 100644 (file)
@@ -10,6 +10,7 @@
 #include <dirent.h>
 #include "cache.h"
 #include "delta.h"
+#include "pack.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -184,10 +185,7 @@ char *sha1_file_name(const unsigned char *sha1)
        return base;
 }
 
-static struct alternate_object_database {
-       char *base;
-       char *name;
-} *alt_odb;
+struct alternate_object_database *alt_odb;
 
 /*
  * Prepare alternate object database registry.
@@ -205,13 +203,15 @@ static struct alternate_object_database {
  * pointed by base fields of the array elements with one xmalloc();
  * the string pool immediately follows the array.
  */
-static void prepare_alt_odb(void)
+void prepare_alt_odb(void)
 {
        int pass, totlen, i;
        const char *cp, *last;
        char *op = NULL;
        const char *alt = gitenv(ALTERNATE_DB_ENVIRONMENT) ? : "";
 
+       if (alt_odb)
+               return;
        /* The first pass counts how large an area to allocate to
         * hold the entire alt_odb structure, including array of
         * structs and path buffers for them.  The second pass fills
@@ -258,8 +258,7 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
 
        if (!stat(name, st))
                return name;
-       if (!alt_odb)
-               prepare_alt_odb();
+       prepare_alt_odb();
        for (i = 0; (name = alt_odb[i].name) != NULL; i++) {
                fill_sha1_path(name, sha1);
                if (!stat(alt_odb[i].base, st))
@@ -271,21 +270,7 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
 #define PACK_MAX_SZ (1<<26)
 static int pack_used_ctr;
 static unsigned long pack_mapped;
-static struct packed_git {
-       struct packed_git *next;
-       unsigned long index_size;
-       unsigned long pack_size;
-       unsigned int *index_base;
-       void *pack_base;
-       unsigned int pack_last_used;
-       char pack_name[0]; /* something like ".git/objects/pack/xxxxx.pack" */
-} *packed_git;
-
-struct pack_entry {
-       unsigned int offset;
-       unsigned char sha1[20];
-       struct packed_git *p;
-};
+struct packed_git *packed_git;
 
 static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
                                void **idx_map_)
@@ -309,9 +294,11 @@ static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
                return -1;
 
        index = idx_map;
+       *idx_map_ = idx_map;
+       *idx_size_ = idx_size;
 
        /* check index map */
-       if (idx_size < 4*256 + 20)
+       if (idx_size < 4*256 + 20 + 20)
                return error("index file too small");
        nr = 0;
        for (i = 0; i < 256; i++) {
@@ -331,17 +318,32 @@ static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
        if (idx_size != 4*256 + nr * 24 + 20 + 20)
                return error("wrong index file size");
 
-       *idx_map_ = idx_map;
-       *idx_size_ = idx_size;
        return 0;
 }
 
-static void unuse_one_packed_git(void)
+static int unuse_one_packed_git(void)
+{
+       struct packed_git *p, *lru = NULL;
+
+       for (p = packed_git; p; p = p->next) {
+               if (p->pack_use_cnt || !p->pack_base)
+                       continue;
+               if (!lru || p->pack_last_used < lru->pack_last_used)
+                       lru = p;
+       }
+       if (!lru)
+               return 0;
+       munmap(lru->pack_base, lru->pack_size);
+       lru->pack_base = NULL;
+       return 1;
+}
+
+void unuse_packed_git(struct packed_git *p)
 {
-       /* NOTYET */
+       p->pack_use_cnt--;
 }
 
-static int use_packed_git(struct packed_git *p)
+int use_packed_git(struct packed_git *p)
 {
        if (!p->pack_base) {
                int fd;
@@ -349,28 +351,36 @@ static int use_packed_git(struct packed_git *p)
                void *map;
 
                pack_mapped += p->pack_size;
-               while (PACK_MAX_SZ < pack_mapped)
-                       unuse_one_packed_git();
+               while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git())
+                       ; /* nothing */
                fd = open(p->pack_name, O_RDONLY);
                if (fd < 0)
-                       return -1;
+                       die("packfile %s cannot be opened", p->pack_name);
                if (fstat(fd, &st)) {
                        close(fd);
-                       return -1;
+                       die("packfile %s cannot be opened", p->pack_name);
                }
                if (st.st_size != p->pack_size)
-                       return -1;
+                       die("packfile %s size mismatch.", p->pack_name);
                map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
                close(fd);
                if (map == MAP_FAILED)
-                       return -1;
+                       die("packfile %s cannot be mapped.", p->pack_name);
                p->pack_base = map;
+
+               /* Check if the pack file matches with the index file.
+                * this is cheap.
+                */
+               if (memcmp((char*)(p->index_base) + p->index_size - 40,
+                          p->pack_base + p->pack_size - 20, 20))
+                       die("packfile %s does not match index.", p->pack_name);
        }
        p->pack_last_used = pack_used_ctr++;
+       p->pack_use_cnt++;
        return 0;
 }
 
-static struct packed_git *add_packed_git(char *path, int path_len)
+struct packed_git *add_packed_git(char *path, int path_len)
 {
        struct stat st;
        struct packed_git *p;
@@ -395,7 +405,9 @@ static struct packed_git *add_packed_git(char *path, int path_len)
        p->pack_size = st.st_size;
        p->index_base = idx_map;
        p->next = NULL;
+       p->pack_base = NULL;
        p->pack_last_used = 0;
+       p->pack_use_cnt = 0;
        return p;
 }
 
@@ -429,7 +441,7 @@ static void prepare_packed_git_one(char *objdir)
        }
 }
 
-static void prepare_packed_git(void)
+void prepare_packed_git(void)
 {
        int i;
        static int run_once = 0;
@@ -438,8 +450,7 @@ static void prepare_packed_git(void)
                return;
 
        prepare_packed_git_one(get_object_directory());
-       if (!alt_odb)
-               prepare_alt_odb();
+       prepare_alt_odb();
        for (i = 0; alt_odb[i].base != NULL; i++) {
                alt_odb[i].name[0] = 0;
                prepare_packed_git_one(alt_odb[i].base);
@@ -520,7 +531,7 @@ int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void
        return inflate(stream, 0);
 }
 
-void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
+static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
 {
        int bytes = strlen(buffer) + 1;
        unsigned char *buf = xmalloc(1+size);
@@ -601,39 +612,188 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l
        return unpack_sha1_rest(&stream, hdr, *size);
 }
 
-/* Returns 0 on fast-path success, returns 1 on deltified
- * and need to unpack to see info.
- */
+/* forward declaration for a mutually recursive function */
+static int packed_object_info(struct pack_entry *entry,
+                             char *type, unsigned long *sizep);
+
+static int packed_delta_info(unsigned char *base_sha1,
+                            unsigned long delta_size,
+                            unsigned long left,
+                            char *type,
+                            unsigned long *sizep,
+                            struct packed_git *p)
+{
+       struct pack_entry base_ent;
+
+       if (left < 20)
+               die("truncated pack file");
+
+       /* The base entry _must_ be in the same pack */
+       if (!find_pack_entry_one(base_sha1, &base_ent, p))
+               die("failed to find delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+
+       /* We choose to only get the type of the base object and
+        * ignore potentially corrupt pack file that expects the delta
+        * based on a base with a wrong size.  This saves tons of
+        * inflate() calls.
+        */
+
+       if (packed_object_info(&base_ent, type, NULL))
+               die("cannot get info for delta-pack base");
+
+       if (sizep) {
+               const unsigned char *data;
+               unsigned char delta_head[64];
+               unsigned long result_size;
+               z_stream stream;
+               int st;
+
+               memset(&stream, 0, sizeof(stream));
+
+               data = stream.next_in = base_sha1 + 20;
+               stream.avail_in = left - 20;
+               stream.next_out = delta_head;
+               stream.avail_out = sizeof(delta_head);
+
+               inflateInit(&stream);
+               st = inflate(&stream, Z_FINISH);
+               inflateEnd(&stream);
+               if ((st != Z_STREAM_END) &&
+                   stream.total_out != sizeof(delta_head))
+                       die("delta data unpack-initial failed");
+
+               /* Examine the initial part of the delta to figure out
+                * the result size.
+                */
+               data = delta_head;
+               get_delta_hdr_size(&data); /* ignore base size */
+
+               /* Read the result size */
+               result_size = get_delta_hdr_size(&data);
+               *sizep = result_size;
+       }
+       return 0;
+}
+
+static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset,
+       enum object_type *type, unsigned long *sizep)
+{
+       unsigned shift;
+       unsigned char *pack, c;
+       unsigned long size;
+
+       if (offset >= p->pack_size)
+               die("object offset outside of pack file");
+
+       pack =  p->pack_base + offset;
+       c = *pack++;
+       offset++;
+       *type = (c >> 4) & 7;
+       size = c & 15;
+       shift = 4;
+       while (c & 0x80) {
+               if (offset >= p->pack_size)
+                       die("object offset outside of pack file");
+               c = *pack++;
+               offset++;
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       *sizep = size;
+       return offset;
+}
+
+void packed_object_info_detail(struct pack_entry *e,
+                              char *type,
+                              unsigned long *size,
+                              unsigned long *store_size,
+                              int *delta_chain_length,
+                              unsigned char *base_sha1)
+{
+       struct packed_git *p = e->p;
+       unsigned long offset, left;
+       unsigned char *pack;
+       enum object_type kind;
+
+       offset = unpack_object_header(p, e->offset, &kind, size);
+       pack = p->pack_base + offset;
+       left = p->pack_size - offset;
+       if (kind != OBJ_DELTA)
+               *delta_chain_length = 0;
+       else {
+               int chain_length = 0;
+               memcpy(base_sha1, pack, 20);
+               do {
+                       struct pack_entry base_ent;
+                       unsigned long junk;
+
+                       find_pack_entry_one(pack, &base_ent, p);
+                       offset = unpack_object_header(p, base_ent.offset,
+                                                     &kind, &junk);
+                       pack = p->pack_base + offset;
+                       chain_length++;
+               } while (kind == OBJ_DELTA);
+               *delta_chain_length = chain_length;
+       }
+       switch (kind) {
+       case OBJ_COMMIT:
+               strcpy(type, "commit");
+               break;
+       case OBJ_TREE:
+               strcpy(type, "tree");
+               break;
+       case OBJ_BLOB:
+               strcpy(type, "blob");
+               break;
+       case OBJ_TAG:
+               strcpy(type, "tag");
+               break;
+       default:
+               die("corrupted pack file");
+       }
+       *store_size = 0; /* notyet */
+}
+
 static int packed_object_info(struct pack_entry *entry,
                              char *type, unsigned long *sizep)
 {
        struct packed_git *p = entry->p;
        unsigned long offset, size, left;
        unsigned char *pack;
+       enum object_type kind;
+       int retval;
 
-       offset = entry->offset;
-       if (p->pack_size - 5 < offset)
-               die("object offset outside of pack file");
+       if (use_packed_git(p))
+               die("cannot map packed file");
+
+       offset = unpack_object_header(p, entry->offset, &kind, &size);
        pack = p->pack_base + offset;
-       size = (pack[1] << 24) + (pack[2] << 16) + (pack[3] << 8) + pack[4];
-       left = p->pack_size - offset - 5;
-       switch (*pack) {
-       case 'D':
-               return 1;
-               break;
-       case 'C':
+       left = p->pack_size - offset;
+
+       switch (kind) {
+       case OBJ_DELTA:
+               retval = packed_delta_info(pack, size, left, type, sizep, p);
+               unuse_packed_git(p);
+               return retval;
+       case OBJ_COMMIT:
                strcpy(type, "commit");
                break;
-       case 'T':
+       case OBJ_TREE:
                strcpy(type, "tree");
                break;
-       case 'B':
+       case OBJ_BLOB:
                strcpy(type, "blob");
                break;
+       case OBJ_TAG:
+               strcpy(type, "tag");
+               break;
        default:
                die("corrupted pack file");
        }
-       *sizep = size;
+       if (sizep)
+               *sizep = size;
+       unuse_packed_git(p);
        return 0;
 }
 
@@ -644,8 +804,10 @@ static void *unpack_delta_entry(unsigned char *base_sha1,
                                unsigned long delta_size,
                                unsigned long left,
                                char *type,
-                               unsigned long *sizep)
+                               unsigned long *sizep,
+                               struct packed_git *p)
 {
+       struct pack_entry base_ent;
        void *data, *delta_data, *result, *base;
        unsigned long data_size, result_size, base_size;
        z_stream stream;
@@ -670,8 +832,11 @@ static void *unpack_delta_entry(unsigned char *base_sha1,
        if ((st != Z_STREAM_END) || stream.total_out != delta_size)
                die("delta data unpack failed");
 
-       /* This may recursively unpack the base, which is what we want */
-       base = read_sha1_file(base_sha1, type, &base_size);
+       /* The base entry _must_ be in the same pack */
+       if (!find_pack_entry_one(base_sha1, &base_ent, p))
+               die("failed to find delta-pack base object %s",
+                   sha1_to_hex(base_sha1));
+       base = unpack_entry_gently(&base_ent, type, &base_size);
        if (!base)
                die("failed to read delta-pack base object %s",
                    sha1_to_hex(base_sha1));
@@ -692,7 +857,7 @@ static void *unpack_non_delta_entry(unsigned char *data,
 {
        int st;
        z_stream stream;
-       char *buffer;
+       unsigned char *buffer;
 
        buffer = xmalloc(size + 1);
        buffer[size] = 0;
@@ -717,42 +882,74 @@ static void *unpack_entry(struct pack_entry *entry,
                          char *type, unsigned long *sizep)
 {
        struct packed_git *p = entry->p;
-       unsigned long offset, size, left;
-       unsigned char *pack;
-
-       offset = entry->offset;
-       if (p->pack_size - 5 < offset)
-               die("object offset outside of pack file");
+       void *retval;
 
        if (use_packed_git(p))
                die("cannot map packed file");
+       retval = unpack_entry_gently(entry, type, sizep);
+       unuse_packed_git(p);
+       if (!retval)
+               die("corrupted pack file");
+       return retval;
+}
+
+/* The caller is responsible for use_packed_git()/unuse_packed_git() pair */
+void *unpack_entry_gently(struct pack_entry *entry,
+                         char *type, unsigned long *sizep)
+{
+       struct packed_git *p = entry->p;
+       unsigned long offset, size, left;
+       unsigned char *pack;
+       enum object_type kind;
+       void *retval;
 
+       offset = unpack_object_header(p, entry->offset, &kind, &size);
        pack = p->pack_base + offset;
-       size = (pack[1] << 24) + (pack[2] << 16) + (pack[3] << 8) + pack[4];
-       left = p->pack_size - offset - 5;
-       switch (*pack) {
-       case 'D':
-               return unpack_delta_entry(pack+5, size, left, type, sizep);
-       case 'C':
+       left = p->pack_size - offset;
+       switch (kind) {
+       case OBJ_DELTA:
+               retval = unpack_delta_entry(pack, size, left, type, sizep, p);
+               return retval;
+       case OBJ_COMMIT:
                strcpy(type, "commit");
                break;
-       case 'T':
+       case OBJ_TREE:
                strcpy(type, "tree");
                break;
-       case 'B':
+       case OBJ_BLOB:
                strcpy(type, "blob");
                break;
+       case OBJ_TAG:
+               strcpy(type, "tag");
+               break;
        default:
-               die("corrupted pack file");
+               return NULL;
        }
        *sizep = size;
-       return unpack_non_delta_entry(pack+5, size, left);
+       retval = unpack_non_delta_entry(pack, size, left);
+       return retval;
+}
+
+int num_packed_objects(const struct packed_git *p)
+{
+       /* See check_packed_git_idx() */
+       return (p->index_size - 20 - 20 - 4*256) / 24;
+}
+
+int nth_packed_object_sha1(const struct packed_git *p, int n,
+                          unsigned char* sha1)
+{
+       void *index = p->index_base + 256;
+       if (n < 0 || num_packed_objects(p) <= n)
+               return -1;
+       memcpy(sha1, (index + 24 * n + 4), 20);
+       return 0;
 }
 
-static int find_pack_entry_1(const unsigned char *sha1,
-                            struct pack_entry *e, struct packed_git *p)
+int find_pack_entry_one(const unsigned char *sha1,
+                       struct pack_entry *e, struct packed_git *p)
 {
-       int *level1_ofs = p->index_base;
+       unsigned int *level1_ofs = p->index_base;
        int hi = ntohl(level1_ofs[*sha1]);
        int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
        void *index = p->index_base + 256;
@@ -780,7 +977,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
        prepare_packed_git();
 
        for (p = packed_git; p; p = p->next) {
-               if (find_pack_entry_1(sha1, e, p))
+               if (find_pack_entry_one(sha1, e, p))
                        return 1;
        }
        return 0;
@@ -800,12 +997,7 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
 
                if (!find_pack_entry(sha1, &e))
                        return error("unable to find %s", sha1_to_hex(sha1));
-               if (!packed_object_info(&e, type, sizep))
-                       return 0;
-               /* sheesh */
-               map = unpack_entry(&e, type, sizep);
-               free(map);
-               return (map == NULL) ? 0 : -1;
+               return packed_object_info(&e, type, sizep);
        }
        if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
                status = error("unable to unpack %s header",
@@ -814,7 +1006,8 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
                status = error("unable to parse %s header", sha1_to_hex(sha1));
        else {
                status = 0;
-               *sizep = size;
+               if (sizep)
+                       *sizep = size;
        }
        inflateEnd(&stream);
        munmap(map, mapsize);
@@ -1074,6 +1267,12 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd)
        return 0;
 }
 
+int has_sha1_pack(const unsigned char *sha1)
+{
+       struct pack_entry e;
+       return find_pack_entry(sha1, &e);
+}
+
 int has_sha1_file(const unsigned char *sha1)
 {
        struct stat st;
index 87d5238..26356dd 100644 (file)
@@ -29,7 +29,7 @@ int fetch(unsigned char *sha1)
        return ret;
 }
 
-int get_version(void)
+static int get_version(void)
 {
        char type = 'v';
        write(fd_out, &type, 1);
index db69c88..090d6f9 100644 (file)
@@ -4,10 +4,10 @@
 
 #include <string.h>
 
-unsigned char local_version = 1;
-unsigned char remote_version = 0;
+static unsigned char local_version = 1;
+static unsigned char remote_version = 0;
 
-int serve_object(int fd_in, int fd_out) {
+static int serve_object(int fd_in, int fd_out) {
        ssize_t size;
        int posn = 0;
        unsigned char sha1[20];
@@ -57,7 +57,7 @@ int serve_object(int fd_in, int fd_out) {
        return 0;
 }
 
-int serve_version(int fd_in, int fd_out)
+static int serve_version(int fd_in, int fd_out)
 {
        if (read(fd_in, &remote_version, 1) < 1)
                return -1;
@@ -65,7 +65,7 @@ int serve_version(int fd_in, int fd_out)
        return 0;
 }
 
-int serve_ref(int fd_in, int fd_out)
+static int serve_ref(int fd_in, int fd_out)
 {
        char ref[PATH_MAX];
        unsigned char sha1[20];
@@ -86,7 +86,7 @@ int serve_ref(int fd_in, int fd_out)
 }
 
 
-void service(int fd_in, int fd_out) {
+static void service(int fd_in, int fd_out) {
        char type;
        int retval;
        do {
index 7a39f7e..b0b9329 100755 (executable)
@@ -35,17 +35,18 @@ test_expect_success \
 
 test_expect_success \
     'pack without delta' \
-    'git-pack-objects --window=0 test-1 <obj-list'
+    'packname_1=$(git-pack-objects --window=0 test-1 <obj-list)'
 
 rm -fr .git2
 mkdir .git2
 
 test_expect_success \
     'unpack without delta' \
-    'GIT_OBJECT_DIRECTORY=.git2/objects &&
+    "GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
      git-init-db &&
-     git-unpack-objects test-1'
+     git-unpack-objects -n <test-1-${packname_1}.pack &&
+     git-unpack-objects <test-1-${packname_1}.pack"
 
 unset GIT_OBJECT_DIRECTORY
 cd $TRASH/.git2
@@ -65,7 +66,7 @@ cd $TRASH
 test_expect_success \
     'pack with delta' \
     'pwd &&
-     git-pack-objects test-2 <obj-list'
+     packname_2=$(git-pack-objects test-2 <obj-list)'
 
 rm -fr .git2
 mkdir .git2
@@ -75,7 +76,8 @@ test_expect_success \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
      git-init-db &&
-     git-unpack-objects test-2'
+     git-unpack-objects -n <test-2-${packname_2}.pack &&
+     git-unpack-objects <test-2-${packname_2}.pack'
 
 unset GIT_OBJECT_DIRECTORY
 cd $TRASH/.git2
@@ -99,7 +101,7 @@ test_expect_success \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
      git-init-db &&
-     cp test-1.pack test-1.idx .git2/objects/pack && {
+     cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
         git-diff-tree --root -p $commit &&
         while read object
         do
@@ -115,7 +117,7 @@ test_expect_success \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
      rm -f .git2/objects/pack/test-?.idx &&
-     cp test-2.pack test-2.idx .git2/objects/pack && {
+     cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
         git-diff-tree --root -p $commit &&
         while read object
         do
@@ -125,4 +127,42 @@ test_expect_success \
     } >current &&
     diff expect current'
 
+unset GIT_OBJECT_DIRECTORY
+
+test_expect_success \
+    'verify pack' \
+    'git-verify-pack test-1-${packname_1}.idx test-2-${packname_2}.idx'
+
+test_expect_success \
+    'corrupt a pack and see if verify catches' \
+    'cp test-1-${packname_1}.idx test-3.idx &&
+     cp test-2-${packname_2}.pack test-3.pack &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     cp test-1-${packname_1}.pack test-3.pack &&
+     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
+     if git-verify-pack test-3.idx
+     then false
+     else :;
+     fi &&
+
+     :'
+
 test_done
index 4263db5..04dede0 100755 (executable)
@@ -85,13 +85,12 @@ check_output()
 {
        _name=$1
        shift 1
-       if "$@" | entag > $_name.actual
+       if eval "$*" | entag > $_name.actual
        then
                diff $_name.expected $_name.actual
        else
                return 1;
        fi
-       
 }
 
 # Turn a reasonable test description into a reasonable test name.
@@ -114,7 +113,7 @@ test_output_expect_success()
         [ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF"
         _name=$(echo $_description | name_from_description)
        cat > $_name.expected
-       test_expect_success "$_description" "check_output $_name $_test
+       test_expect_success "$_description" "check_output $_name \"$_test\"
 }
 
 # --- end of stuff to move ---
@@ -367,33 +366,33 @@ test_output_expect_success "three nodes one head, one internal, one base" 'git-r
 EOF
 
 test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF
-= l2
+^ l2
 | l1
 | l0
 EOF
 
 test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF
-= l2
+^ l2
 | l1
 EOF
 
 test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF
-= l2
+^ l2
 EOF
 
 test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF
-= l5
+^ l5
 | l4
 | l3
 EOF
 
 test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF
-= l5
+^ l5
 | l4
 EOF
 
 test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF
-= l5
+^ l5
 EOF
 
 test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF
@@ -409,17 +408,17 @@ test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-or
 | b2
 EOF
 
-test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5 | sort' <<EOF
+test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF
+l5
+l4
+l3
 a4
-b2
-b3
 b4
-c1
-c2
+a3
+a2
 c3
-l3
-l4
-l5
+c2
+b3
 EOF
 
 test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
@@ -543,6 +542,11 @@ test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-ord
 = alt_root
 EOF
 
+test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF
+a3
+a2
+a1
+EOF
 
 #
 #
index da51efc..37ef86b 100644 (file)
@@ -61,7 +61,7 @@ int main(int argc, char *argv[])
        if (argv[1][1] == 'd')
                out_buf = diff_delta(from_buf, from_size,
                                     data_buf, data_size,
-                                    &out_size, ~0UL);
+                                    &out_size, 0);
        else
                out_buf = patch_delta(from_buf, from_size,
                                      data_buf, data_size,
index a792b92..97d2681 100644 (file)
 #include "cache.h"
 #include "object.h"
 #include "delta.h"
+#include "pack.h"
 
 static int dry_run;
-static int nr_entries;
-static const char *base_name;
-static const char unpack_usage[] = "git-unpack-objects basename";
+static const char unpack_usage[] = "git-unpack-objects < pack-file";
 
-struct pack_entry {
-       unsigned int offset; /* network byte order */
-       unsigned char sha1[20];
-};
-
-static void *pack_base;
-static unsigned long pack_size;
-static void *index_base;
-static unsigned long index_size;
+/* We always read in 4kB chunks. */
+static unsigned char buffer[4096];
+static unsigned long offset, len, eof;
+static SHA_CTX ctx;
 
-static struct pack_entry **pack_list;
+/*
+ * Make sure at least "min" bytes are available in the buffer, and
+ * return the pointer to the buffer.
+ */
+static void * fill(int min)
+{
+       if (min <= len)
+               return buffer + offset;
+       if (eof)
+               die("unable to fill input");
+       if (min > sizeof(buffer))
+               die("cannot fill %d bytes", min);
+       if (offset) {
+               SHA1_Update(&ctx, buffer, offset);
+               memcpy(buffer, buffer + offset, len);
+               offset = 0;
+       }
+       do {
+               int ret = read(0, buffer + len, sizeof(buffer) - len);
+               if (ret <= 0) {
+                       if (!ret)
+                               die("early EOF");
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       die("read error on input: %s", strerror(errno));
+               }
+               len += ret;
+       } while (len < min);
+       return buffer;
+}
 
-static void *map_file(const char *suffix, unsigned long *sizep)
+static void use(int bytes)
 {
-       static char pathname[PATH_MAX];
-       unsigned long len;
-       int fd;
-       struct stat st;
-       void *map;
-
-       len = snprintf(pathname, PATH_MAX, "%s.%s", base_name, suffix);
-       if (len >= PATH_MAX)
-               die("bad pack base-name");
-       fd = open(pathname, O_RDONLY);
-       if (fd < 0 || fstat(fd, &st))
-               die("unable to open '%s'", pathname);
-       len = st.st_size;
-       if (!len)
-               die("bad pack file '%s'", pathname);
-       map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
-       if (-1 == (int)(long)map)
-               die("unable to mmap '%s'", pathname);
-       close(fd);
-       *sizep = len;
-       return map;
+       if (bytes > len)
+               die("used more bytes than were available");
+       len -= bytes;
+       offset += bytes;
 }
 
-static int sort_by_offset(const void *_a, const void *_b)
+static void *get_data(unsigned long size)
 {
-       struct pack_entry *a = *(struct pack_entry **)_a;
-       struct pack_entry *b = *(struct pack_entry **)_b;
-       unsigned int o1, o2;
+       z_stream stream;
+       void *buf = xmalloc(size);
+
+       if (!size)
+               return buf;
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_out = buf;
+       stream.avail_out = size;
+       stream.next_in = fill(1);
+       stream.avail_in = len;
+       inflateInit(&stream);
 
-       o1 = ntohl(a->offset);
-       o2 = ntohl(b->offset);
-       return o1 < o2 ? -1 : 1;
+       for (;;) {
+               int ret = inflate(&stream, 0);
+               use(len - stream.avail_in);
+               if (stream.total_out == size && ret == Z_STREAM_END)
+                       break;
+               if (ret != Z_OK)
+                       die("inflate returned %d\n", ret);
+               stream.next_in = fill(1);
+               stream.avail_in = len;
+       }
+       return buf;
 }
 
-static int check_index(void)
+struct delta_info {
+       unsigned char base_sha1[20];
+       unsigned long size;
+       void *delta;
+       struct delta_info *next;
+};
+
+static struct delta_info *delta_list;
+
+static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
 {
-       unsigned int *array = index_base;
-       unsigned int nr;
-       int i;
+       struct delta_info *info = xmalloc(sizeof(*info));
 
-       if (index_size < 4*256 + 20)
-               return error("index file too small");
-       nr = 0;
-       for (i = 0; i < 256; i++) {
-               unsigned int n = ntohl(array[i]);
-               if (n < nr)
-                       return error("non-monotonic index");
-               nr = n;
-       }
-       /*
-        * Total size:
-        *  - 256 index entries 4 bytes each
-        *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
-        *  - 20-byte SHA1 of the packfile
-        *  - 20-byte SHA1 file checksum
-        */
-       if (index_size != 4*256 + nr * 24 + 20 + 20)
-               return error("wrong index file size");
-
-       nr_entries = nr;
-       pack_list = xmalloc(nr * sizeof(struct pack_entry *));
-       for (i = 0; i < nr; i++)
-               pack_list[i] = index_base + 4*256 + i*24;
-
-       qsort(pack_list, nr, sizeof(*pack_list), sort_by_offset);
-
-       printf("%d entries\n", nr);
-       return 0;
+       memcpy(info->base_sha1, base_sha1, 20);
+       info->size = size;
+       info->delta = delta;
+       info->next = delta_list;
+       delta_list = info;
 }
 
-static int unpack_non_delta_entry(struct pack_entry *entry,
-                                 int kind,
-                                 unsigned char *data,
-                                 unsigned long size,
-                                 unsigned long left)
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+
+static void write_object(void *buf, unsigned long size, const char *type)
 {
-       int st;
-       z_stream stream;
-       char *buffer;
        unsigned char sha1[20];
-       char *type_s;
+       if (write_sha1_file(buf, size, type, sha1) < 0)
+               die("failed to write object");
+       added_object(sha1, type, buf, size);
+}
 
-       printf("%s %c %lu\n", sha1_to_hex(entry->sha1), kind, size);
-       if (dry_run)
-               return 0;
+static int resolve_delta(const char *type,
+       void *base, unsigned long base_size, 
+       void *delta, unsigned long delta_size)
+{
+       void *result;
+       unsigned long result_size;
 
-       buffer = xmalloc(size + 1);
-       buffer[size] = 0;
-       memset(&stream, 0, sizeof(stream));
-       stream.next_in = data;
-       stream.avail_in = left;
-       stream.next_out = buffer;
-       stream.avail_out = size;
+       result = patch_delta(base, base_size,
+                            delta, delta_size,
+                            &result_size);
+       if (!result)
+               die("failed to apply delta");
+       free(delta);
+       write_object(result, result_size, type);
+       free(result);
+       return 0;
+}
 
-       inflateInit(&stream);
-       st = inflate(&stream, Z_FINISH);
-       inflateEnd(&stream);
-       if ((st != Z_STREAM_END) || stream.total_out != size)
-               goto err_finish;
-       switch (kind) {
-       case 'C': type_s = "commit"; break;
-       case 'T': type_s = "tree"; break;
-       case 'B': type_s = "blob"; break;
-       default: goto err_finish;
+static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+{
+       struct delta_info **p = &delta_list;
+       struct delta_info *info;
+
+       while ((info = *p) != NULL) {
+               if (!memcmp(info->base_sha1, sha1, 20)) {
+                       *p = info->next;
+                       p = &delta_list;
+                       resolve_delta(type, data, size, info->delta, info->size);
+                       free(info);
+                       continue;
+               }
+               p = &info->next;
        }
-       if (write_sha1_file(buffer, size, type_s, sha1) < 0)
-               die("failed to write %s (%s)",
-                   sha1_to_hex(entry->sha1), type_s);
-       printf("%s %s\n", sha1_to_hex(sha1), type_s);
-       if (memcmp(sha1, entry->sha1, 20))
-               die("resulting %s have wrong SHA1", type_s);
-
- finish:
-       st = 0;
-       free(buffer);
-       return st;
- err_finish:
-       st = -1;
-       goto finish;
 }
 
-static int find_pack_entry(unsigned char *sha1, struct pack_entry **ent)
+static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
 {
-       int *level1_ofs = index_base;
-       int hi = ntohl(level1_ofs[*sha1]);
-       int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
-       void *index = index_base + 4*256;
+       void *buf = get_data(size);
+       const char *type;
 
-       do {
-               int mi = (lo + hi) / 2;
-               int cmp = memcmp(index + 24 * mi + 4, sha1, 20);
-               if (!cmp) {
-                       *ent = index + 24 * mi;
-                       return 1;
-               }
-               if (cmp > 0)
-                       hi = mi;
-               else
-                       lo = mi+1;
-       } while (lo < hi);
+       switch (kind) {
+       case OBJ_COMMIT: type = "commit"; break;
+       case OBJ_TREE:   type = "tree"; break;
+       case OBJ_BLOB:   type = "blob"; break;
+       case OBJ_TAG:    type = "tag"; break;
+       default: die("bad type %d", kind);
+       }
+       if (!dry_run)
+               write_object(buf, size, type);
+       free(buf);
        return 0;
 }
 
-/* forward declaration for a mutually recursive function */
-static void unpack_entry(struct pack_entry *);
-
-static int unpack_delta_entry(struct pack_entry *entry,
-                             unsigned char *base_sha1,
-                             unsigned long delta_size,
-                             unsigned long left)
+static int unpack_delta_entry(unsigned long delta_size)
 {
-       void *data, *delta_data, *result, *base;
-       unsigned long data_size, result_size, base_size;
-       z_stream stream;
-       int st;
+       void *delta_data, *base;
+       unsigned long base_size;
        char type[20];
-       unsigned char sha1[20];
+       unsigned char base_sha1[20];
 
-       if (left < 20)
-               die("truncated pack file");
-       data = base_sha1 + 20;
-       data_size = left - 20;
-       printf("%s D %lu", sha1_to_hex(entry->sha1), delta_size);
-       printf(" %s\n", sha1_to_hex(base_sha1));
+       memcpy(base_sha1, fill(20), 20);
+       use(20);
 
-       if (dry_run)
+       delta_data = get_data(delta_size);
+       if (dry_run) {
+               free(delta_data);
                return 0;
+       }
 
-       /* pack+5 is the base sha1, unless we have it, we need to
-        * unpack it first.
-        */
        if (!has_sha1_file(base_sha1)) {
-               struct pack_entry *base;
-               if (!find_pack_entry(base_sha1, &base))
-                       die("cannot find delta-pack base object");
-               unpack_entry(base);
+               add_delta_to_list(base_sha1, delta_data, delta_size);
+               return 0;
        }
-       delta_data = xmalloc(delta_size);
-
-       memset(&stream, 0, sizeof(stream));
-
-       stream.next_in = data;
-       stream.avail_in = data_size;
-       stream.next_out = delta_data;
-       stream.avail_out = delta_size;
-
-       inflateInit(&stream);
-       st = inflate(&stream, Z_FINISH);
-       inflateEnd(&stream);
-       if ((st != Z_STREAM_END) || stream.total_out != delta_size)
-               die("delta data unpack failed");
-
        base = read_sha1_file(base_sha1, type, &base_size);
        if (!base)
                die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1));
-       result = patch_delta(base, base_size,
-                            delta_data, delta_size,
-                            &result_size);
-       if (!result)
-               die("failed to apply delta");
-       free(delta_data);
-
-       if (write_sha1_file(result, result_size, type, sha1) < 0)
-               die("failed to write %s (%s)",
-                   sha1_to_hex(entry->sha1), type);
-       free(result);
-       printf("%s %s\n", sha1_to_hex(sha1), type);
-       if (memcmp(sha1, entry->sha1, 20))
-               die("resulting %s have wrong SHA1", type);
-       return 0;
+       return resolve_delta(type, base, base_size, delta_data, delta_size);
 }
 
-static void unpack_entry(struct pack_entry *entry)
+static void unpack_one(void)
 {
-       unsigned long offset, size, left;
-       unsigned char *pack;
-
-       /* Have we done this one already due to deltas based on it? */
-       if (lookup_object(entry->sha1))
+       unsigned shift;
+       unsigned char *pack, c;
+       unsigned long size;
+       enum object_type type;
+
+       pack = fill(1);
+       c = *pack;
+       use(1);
+       type = (c >> 4) & 7;
+       size = (c & 15);
+       shift = 4;
+       while (c & 0x80) {
+               pack = fill(1);
+               c = *pack++;
+               use(1);
+               size += (c & 0x7f) << shift;
+               shift += 7;
+       }
+       switch (type) {
+       case OBJ_COMMIT:
+       case OBJ_TREE:
+       case OBJ_BLOB:
+       case OBJ_TAG:
+               unpack_non_delta_entry(type, size);
+               return;
+       case OBJ_DELTA:
+               unpack_delta_entry(size);
                return;
-
-       offset = ntohl(entry->offset);
-       if (offset > pack_size - 5)
-               die("object offset outside of pack file");
-       pack = pack_base + offset;
-       size = (pack[1] << 24) + (pack[2] << 16) + (pack[3] << 8) + pack[4];
-       left = pack_size - offset - 5;
-       switch (*pack) {
-       case 'C': case 'T': case 'B':
-               unpack_non_delta_entry(entry, *pack, pack+5, size, left);
-               break;
-       case 'D':
-               unpack_delta_entry(entry, pack+5, size, left);
-               break;
        default:
-               die("corrupted pack file");
+               die("bad object type %d", type);
        }
 }
 
@@ -267,17 +229,28 @@ static void unpack_entry(struct pack_entry *entry)
  */
 static void unpack_all(void)
 {
-       int i = nr_entries;
-
-       while (--i >= 0) {
-               struct pack_entry *entry = pack_list[i];
-               unpack_entry(entry);
-       }
+       int i;
+       struct pack_header *hdr = fill(sizeof(struct pack_header));
+       unsigned version = ntohl(hdr->hdr_version);
+       unsigned nr_objects = ntohl(hdr->hdr_entries);
+
+       if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE)
+               die("bad pack file");
+       if (version != PACK_VERSION)
+               die("unable to handle pack file version %d", version);
+       fprintf(stderr, "Unpacking %d objects\n", nr_objects);
+
+       use(sizeof(struct pack_header));
+       for (i = 0; i < nr_objects; i++)
+               unpack_one();
+       if (delta_list)
+               die("unresolved deltas left after unpacking");
 }
 
 int main(int argc, char **argv)
 {
        int i;
+       unsigned char sha1[20];
 
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
@@ -289,16 +262,32 @@ int main(int argc, char **argv)
                        }
                        usage(unpack_usage);
                }
-               if (base_name)
-                       usage(unpack_usage);
-               base_name = arg;
-       }
-       if (!base_name)
+
+               /* We don't take any non-flag arguments now.. Maybe some day */
                usage(unpack_usage);
-       index_base = map_file("idx", &index_size);
-       pack_base = map_file("pack", &pack_size);
-       if (check_index() < 0)
-               die("bad index file");
+       }
+       SHA1_Init(&ctx);
        unpack_all();
+       SHA1_Update(&ctx, buffer, offset);
+       SHA1_Final(sha1, &ctx);
+       if (memcmp(fill(20), sha1, 20))
+               die("final sha1 did not match");
+       use(20);
+
+       /* Write the last part of the buffer to stdout */
+       while (len) {
+               int ret = write(1, buffer + offset, len);
+               if (!ret)
+                       break;
+               if (ret < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       break;
+               }
+               len -= ret;
+               offset += ret;
+       }
+
+       /* All done */
        return 0;
 }
diff --git a/upload-pack.c b/upload-pack.c
new file mode 100644 (file)
index 0000000..d35c068
--- /dev/null
@@ -0,0 +1,180 @@
+#include "cache.h"
+#include "refs.h"
+#include "pkt-line.h"
+
+static const char upload_pack_usage[] = "git-upload-pack <dir>";
+
+#define MAX_HAS (16)
+#define MAX_NEEDS (16)
+static int nr_has = 0, nr_needs = 0;
+static unsigned char has_sha1[MAX_HAS][20];
+static unsigned char needs_sha1[MAX_NEEDS][20];
+
+static int strip(char *line, int len)
+{
+       if (len && line[len-1] == '\n')
+               line[--len] = 0;
+       return len;
+}
+
+static void create_pack_file(void)
+{
+       int fd[2];
+       pid_t pid;
+
+       if (pipe(fd) < 0)
+               die("git-upload-pack: unable to create pipe");
+       pid = fork();
+       if (pid < 0)
+               die("git-upload-pack: unable to fork git-rev-list");
+
+       if (!pid) {
+               int i;
+               int args = nr_has + nr_needs + 5;
+               char **argv = xmalloc(args * sizeof(char *));
+               char *buf = xmalloc(args * 45);
+               char **p = argv;
+
+               dup2(fd[1], 1);
+               close(0);
+               close(fd[0]);
+               close(fd[1]);
+               *p++ = "git-rev-list";
+               *p++ = "--objects";
+               for (i = 0; i < nr_needs; i++) {
+                       *p++ = buf;
+                       memcpy(buf, sha1_to_hex(needs_sha1[i]), 41);
+                       buf += 41;
+               }
+               for (i = 0; i < nr_has; i++) {
+                       *p++ = buf;
+                       *buf++ = '^';
+                       memcpy(buf, sha1_to_hex(has_sha1[i]), 41);
+                       buf += 41;
+               }
+               *p++ = NULL;
+               execvp("git-rev-list", 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");
+}
+
+static int got_sha1(char *hex, unsigned char *sha1)
+{
+       int nr;
+       if (get_sha1_hex(hex, sha1))
+               die("git-upload-pack: expected SHA1 object, got '%s'", hex);
+       if (!has_sha1_file(sha1))
+               return 0;
+       nr = nr_has;
+       if (nr < MAX_HAS) {
+               memcpy(has_sha1[nr], sha1, 20);
+               nr_has = nr+1;
+       }
+       return 1;
+}
+
+static int get_common_commits(void)
+{
+       static char line[1000];
+       unsigned char sha1[20];
+       int len;
+
+       for(;;) {
+               len = packet_read_line(0, line, sizeof(line));
+
+               if (!len) {
+                       packet_write(1, "NAK\n");
+                       continue;
+               }
+               len = strip(line, len);
+               if (!strncmp(line, "have ", 5)) {
+                       if (got_sha1(line+5, sha1)) {
+                               packet_write(1, "ACK %s\n", sha1_to_hex(sha1));
+                               break;
+                       }
+                       continue;
+               }
+               if (!strcmp(line, "done")) {
+                       packet_write(1, "NAK\n");
+                       return -1;
+               }
+               die("git-upload-pack: expected SHA1 list, got '%s'", line);
+       }
+
+       for (;;) {
+               len = packet_read_line(0, line, sizeof(line));
+               if (!len)
+                       continue;
+               len = strip(line, len);
+               if (!strncmp(line, "have ", 5)) {
+                       got_sha1(line+5, sha1);
+                       continue;
+               }
+               if (!strcmp(line, "done"))
+                       break;
+               die("git-upload-pack: expected SHA1 list, got '%s'", line);
+       }
+       return 0;
+}
+
+static int receive_needs(void)
+{
+       static char line[1000];
+       int len, needs;
+
+       needs = 0;
+       for (;;) {
+               len = packet_read_line(0, line, sizeof(line));
+               if (!len)
+                       return needs;
+
+               /*
+                * This is purely theoretical right now: git-fetch-pack only
+                * ever asks for a single HEAD
+                */
+               if (needs >= MAX_NEEDS)
+                       die("I'm only doing a max of %d requests", MAX_NEEDS);
+               if (strncmp("want ", line, 5) || get_sha1_hex(line+5, needs_sha1[needs]))
+                       die("git-upload-pack: protocol error, expected to get sha, not '%s'", line);
+               needs++;
+       }
+}
+
+static int send_ref(const char *refname, const unsigned char *sha1)
+{
+       packet_write(1, "%s %s\n", sha1_to_hex(sha1), refname);
+       return 0;
+}
+
+static int upload_pack(void)
+{
+       for_each_ref(send_ref);
+       packet_flush(1);
+       nr_needs = receive_needs();
+       if (!nr_needs)
+               return 0;
+       get_common_commits();
+       create_pack_file();
+       return 0;
+}
+
+int main(int argc, char **argv)
+{
+       const char *dir;
+       if (argc != 2)
+               usage(upload_pack_usage);
+       dir = argv[1];
+       if (chdir(dir))
+               die("git-upload-pack unable to chdir to %s", dir);
+       chdir(".git");
+       if (access("objects", X_OK) || access("refs", X_OK))
+               die("git-upload-pack: %s doesn't seem to be a git archive", dir);
+       setenv("GIT_DIR", ".", 1);
+       upload_pack();
+       return 0;
+}
diff --git a/verify-pack.c b/verify-pack.c
new file mode 100644 (file)
index 0000000..30c40fe
--- /dev/null
@@ -0,0 +1,57 @@
+#include "cache.h"
+#include "pack.h"
+
+static int verify_one_pack(char *arg, int verbose)
+{
+       int len = strlen(arg);
+       struct packed_git *g;
+       
+       while (1) {
+               /* Should name foo.idx, but foo.pack may be named;
+                * convert it to foo.idx
+                */
+               if (!strcmp(arg + len - 5, ".pack")) {
+                       strcpy(arg + len - 5, ".idx");
+                       len--;
+               }
+               /* Should name foo.idx now */
+               if ((g = add_packed_git(arg, len)))
+                       break;
+               /* No?  did you name just foo? */
+               strcpy(arg + len, ".idx");
+               len += 4;
+               if ((g = add_packed_git(arg, len)))
+                       break;
+               return error("packfile %s not found.", arg);
+       }
+       return verify_pack(g, verbose);
+}
+
+static const char *verify_pack_usage = "git-verify-pack [-v] <pack>...";
+
+int main(int ac, char **av)
+{
+       int errs = 0;
+       int verbose = 0;
+       int no_more_options = 0;
+
+       while (1 < ac) {
+               char path[PATH_MAX];
+
+               if (!no_more_options && av[1][0] == '-') {
+                       if (!strcmp("-v", av[1]))
+                               verbose = 1;
+                       else if (!strcmp("--", av[1]))
+                               no_more_options = 1;
+                       else
+                               usage(verify_pack_usage);
+               }
+               else {
+                       strcpy(path, av[1]);
+                       if (verify_one_pack(path, verbose))
+                               errs++;
+               }
+               ac--; av++;
+       }
+       return !!errs;
+}