[PATCH] ssh-push.c: Fix handling of ssh://host/path URLs
[git.git] / sha1_file.c
index ece9dff..e648c06 100644 (file)
@@ -6,9 +6,8 @@
  * This handles basic git sha1 object files - packing, unpacking,
  * creation etc.
  */
-#include <stdarg.h>
-#include <limits.h>
 #include "cache.h"
+#include "delta.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -59,7 +58,7 @@ static int get_sha1_file(const char *path, unsigned char *result)
        return get_sha1_hex(buffer, result);
 }
 
-static char *git_dir, *git_object_dir, *git_index_file;
+static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir;
 static void setup_git_env(void)
 {
        git_dir = gitenv(GIT_DIR_ENVIRONMENT);
@@ -70,6 +69,8 @@ static void setup_git_env(void)
                git_object_dir = xmalloc(strlen(git_dir) + 9);
                sprintf(git_object_dir, "%s/objects", git_dir);
        }
+       git_refs_dir = xmalloc(strlen(git_dir) + 6);
+       sprintf(git_refs_dir, "%s/refs", git_dir);
        git_index_file = gitenv(INDEX_ENVIRONMENT);
        if (!git_index_file) {
                git_index_file = xmalloc(strlen(git_dir) + 7);
@@ -84,6 +85,13 @@ char *get_object_directory(void)
        return git_object_dir;
 }
 
+char *get_refs_directory(void)
+{
+       if (!git_refs_dir)
+               setup_git_env();
+       return git_refs_dir;
+}
+
 char *get_index_file(void)
 {
        if (!git_index_file)
@@ -258,7 +266,7 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
        return NULL;
 }
 
-int check_sha1_signature(unsigned char *sha1, void *map, unsigned long size, const char *type)
+int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
 {
        char header[100];
        unsigned char real_sha1[20];
@@ -308,40 +316,189 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
        return map;
 }
 
+int unpack_sha1_header(z_stream *stream, void *map, unsigned long mapsize, void *buffer, unsigned long size)
+{
+       /* Get the data stream */
+       memset(stream, 0, sizeof(*stream));
+       stream->next_in = map;
+       stream->avail_in = mapsize;
+       stream->next_out = buffer;
+       stream->avail_out = size;
+
+       inflateInit(stream);
+       return inflate(stream, 0);
+}
+
+void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
+{
+       int bytes = strlen(buffer) + 1;
+       char *buf = xmalloc(1+size);
+
+       memcpy(buf, buffer + bytes, stream->total_out - bytes);
+       bytes = stream->total_out - bytes;
+       if (bytes < size) {
+               stream->next_out = buf + bytes;
+               stream->avail_out = size - bytes;
+               while (inflate(stream, Z_FINISH) == Z_OK)
+                       /* nothing */;
+       }
+       buf[size] = 0;
+       inflateEnd(stream);
+       return buf;
+}
+
+/*
+ * We used to just use "sscanf()", but that's actually way
+ * too permissive for what we want to check. So do an anal
+ * object header parse by hand.
+ */
+int parse_sha1_header(char *hdr, char *type, unsigned long *sizep)
+{
+       int i;
+       unsigned long size;
+
+       /*
+        * The type can be at most ten bytes (including the 
+        * terminating '\0' that we add), and is followed by
+        * a space. 
+        */
+       i = 10;
+       for (;;) {
+               char c = *hdr++;
+               if (c == ' ')
+                       break;
+               if (!--i)
+                       return -1;
+               *type++ = c;
+       }
+       *type = 0;
+
+       /*
+        * The length must follow immediately, and be in canonical
+        * decimal format (ie "010" is not valid).
+        */
+       size = *hdr++ - '0';
+       if (size > 9)
+               return -1;
+       if (size) {
+               for (;;) {
+                       unsigned long c = *hdr - '0';
+                       if (c > 9)
+                               break;
+                       hdr++;
+                       size = size * 10 + c;
+               }
+       }
+       *sizep = size;
+
+       /*
+        * The length must be followed by a zero byte
+        */
+       return *hdr ? -1 : 0;
+}
+
 void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size)
 {
-       int ret, bytes;
+       int ret;
        z_stream stream;
-       char buffer[8192];
-       unsigned char *buf;
-
-       /* Get the data stream */
-       memset(&stream, 0, sizeof(stream));
-       stream.next_in = map;
-       stream.avail_in = mapsize;
-       stream.next_out = (unsigned char *)buffer;
-       stream.avail_out = sizeof(buffer);
+       char hdr[8192];
 
-       inflateInit(&stream);
-       ret = inflate(&stream, 0);
-       if (ret < Z_OK)
-               return NULL;
-       if (sscanf(buffer, "%10s %lu", type, size) != 2)
+       ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+       if (ret < Z_OK || parse_sha1_header(hdr, type, size) < 0)
                return NULL;
 
-       bytes = strlen(buffer) + 1;
-       buf = xmalloc(*size);
+       return unpack_sha1_rest(&stream, hdr, *size);
+}
 
-       memcpy(buf, buffer + bytes, stream.total_out - bytes);
-       bytes = stream.total_out - bytes;
-       if (bytes < *size && ret == Z_OK) {
-               stream.next_out = buf + bytes;
-               stream.avail_out = *size - bytes;
-               while (inflate(&stream, Z_FINISH) == Z_OK)
-                       /* nothing */;
+int sha1_delta_base(const unsigned char *sha1, unsigned char *base_sha1)
+{
+       int ret;
+       unsigned long mapsize, size;
+       void *map;
+       z_stream stream;
+       char hdr[64], type[20];
+       void *delta_data_head;
+
+       map = map_sha1_file(sha1, &mapsize);
+       if (!map)
+               return -1;
+       ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+       if (ret < Z_OK || parse_sha1_header(hdr, type, &size) < 0) {
+               ret = -1;
+               goto out;
+       }
+       if (strcmp(type, "delta")) {
+               ret = 0;
+               goto out;
        }
+
+       delta_data_head = hdr + strlen(hdr) + 1;
+       ret = 1;
+       memcpy(base_sha1, delta_data_head, 20);
+ out:
        inflateEnd(&stream);
-       return buf;
+       munmap(map, mapsize);
+       return ret;
+}
+
+int sha1_file_size(const unsigned char *sha1, unsigned long *sizep)
+{
+       int ret, status;
+       unsigned long mapsize, size;
+       void *map;
+       z_stream stream;
+       char hdr[64], type[20];
+       const unsigned char *data;
+       unsigned char cmd;
+       int i;
+
+       map = map_sha1_file(sha1, &mapsize);
+       if (!map)
+               return -1;
+       ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr));
+       status = -1;
+       if (ret < Z_OK || parse_sha1_header(hdr, type, &size) < 0)
+               goto out;
+       if (strcmp(type, "delta")) {
+               *sizep = size;
+               status = 0;
+               goto out;
+       }
+
+       /* We are dealing with a delta object.  Inflated, the first
+        * 20 bytes hold the base object SHA1, and delta data follows
+        * immediately after it.
+        *
+        * The initial part of the delta starts at delta_data_head +
+        * 20.  Borrow code from patch-delta to read the result size.
+        */
+       data = hdr + strlen(hdr) + 1 + 20;
+
+       /* Skip over the source size; we are not interested in
+        * it and we cannot verify it because we do not want
+        * to read the base object.
+        */
+       cmd = *data++;
+       while (cmd) {
+               if (cmd & 1)
+                       data++;
+               cmd >>= 1;
+       }
+       /* Read the result size */
+       size = i = 0;
+       cmd = *data++;
+       while (cmd) {
+               if (cmd & 1)
+                       size |= *data++ << i;
+               i += 8;
+               cmd >>= 1;
+       }
+       *sizep = size;
+       status = 0;
+ out:
+       inflateEnd(&stream);
+       munmap(map, mapsize);
+       return status;
 }
 
 void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size)
@@ -353,6 +510,19 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size
        if (map) {
                buf = unpack_sha1_file(map, mapsize, type, size);
                munmap(map, mapsize);
+               if (buf && !strcmp(type, "delta")) {
+                       void *ref = NULL, *delta = buf;
+                       unsigned long ref_size, delta_size = *size;
+                       buf = NULL;
+                       if (delta_size > 20)
+                               ref = read_sha1_file(delta, type, &ref_size);
+                       if (ref)
+                               buf = patch_delta(ref, ref_size,
+                                                 delta+20, delta_size-20, 
+                                                 size);
+                       free(delta);
+                       free(ref);
+               }
                return buf;
        }
        return NULL;