Use core.filemode.
[gitweb.git] / sha1_file.c
index 64eac592bbf191b2c97ccf8739fdb0d4712d7788..6e3ea232eecc0f190925568ecb86fb24c93e6c94 100644 (file)
@@ -20,6 +20,8 @@
 #endif
 #endif
 
+const unsigned char null_sha1[20] = { 0, };
+
 static unsigned int sha1_file_open_flag = O_NOATIME;
 
 static unsigned hexval(char c)
@@ -50,26 +52,33 @@ static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
        *git_graft_file;
 static void setup_git_env(void)
 {
-       git_dir = gitenv(GIT_DIR_ENVIRONMENT);
+       git_dir = getenv(GIT_DIR_ENVIRONMENT);
        if (!git_dir)
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-       git_object_dir = gitenv(DB_ENVIRONMENT);
+       git_object_dir = getenv(DB_ENVIRONMENT);
        if (!git_object_dir) {
                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);
+       git_index_file = getenv(INDEX_ENVIRONMENT);
        if (!git_index_file) {
                git_index_file = xmalloc(strlen(git_dir) + 7);
                sprintf(git_index_file, "%s/index", git_dir);
        }
-       git_graft_file = gitenv(GRAFT_ENVIRONMENT);
+       git_graft_file = getenv(GRAFT_ENVIRONMENT);
        if (!git_graft_file)
                git_graft_file = strdup(git_path("info/grafts"));
 }
 
+char *get_git_dir(void)
+{
+       if (!git_dir)
+               setup_git_env();
+       return git_dir;
+}
+
 char *get_object_directory(void)
 {
        if (!git_object_dir)
@@ -240,10 +249,12 @@ static struct alternate_object_database **alt_odb_tail;
  * SHA1, an extra slash for the first level indirection, and the
  * terminating NUL.
  */
-static void link_alt_odb_entries(const char *alt, const char *ep, int sep)
+static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
+                                const char *relative_base)
 {
        const char *cp, *last;
        struct alternate_object_database *ent;
+       int base_len = -1;
 
        last = alt;
        while (last < ep) {
@@ -261,12 +272,25 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep)
                        int pfxlen = cp - last;
                        int entlen = pfxlen + 43;
 
+                       if (*last != '/' && relative_base) {
+                               /* Relative alt-odb */
+                               if (base_len < 0)
+                                       base_len = strlen(relative_base) + 1;
+                               entlen += base_len;
+                               pfxlen += base_len;
+                       }
                        ent = xmalloc(sizeof(*ent) + entlen);
                        *alt_odb_tail = ent;
                        alt_odb_tail = &(ent->next);
                        ent->next = NULL;
-
-                       memcpy(ent->base, last, pfxlen);
+                       if (*last != '/' && relative_base) {
+                               memcpy(ent->base, relative_base, base_len - 1);
+                               ent->base[base_len - 1] = '/';
+                               memcpy(ent->base + base_len,
+                                      last, cp - last);
+                       }
+                       else
+                               memcpy(ent->base, last, pfxlen);
                        ent->name = ent->base + pfxlen + 1;
                        ent->base[pfxlen] = ent->base[pfxlen + 3] = '/';
                        ent->base[entlen-1] = 0;
@@ -285,15 +309,15 @@ void prepare_alt_odb(void)
        struct stat st;
        char *alt;
 
-       alt = gitenv(ALTERNATE_DB_ENVIRONMENT);
+       alt = getenv(ALTERNATE_DB_ENVIRONMENT);
        if (!alt) alt = "";
 
-       sprintf(path, "%s/info/alternates", get_object_directory());
        if (alt_odb_tail)
                return;
        alt_odb_tail = &alt_odb_list;
-       link_alt_odb_entries(alt, alt + strlen(alt), ':');
+       link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL);
 
+       sprintf(path, "%s/info/alternates", get_object_directory());
        fd = open(path, O_RDONLY);
        if (fd < 0)
                return;
@@ -306,7 +330,8 @@ void prepare_alt_odb(void)
        if (map == MAP_FAILED)
                return;
 
-       link_alt_odb_entries(map, map + st.st_size, '\n');
+       link_alt_odb_entries(map, map + st.st_size, '\n',
+                            get_object_directory());
        munmap(map, st.st_size);
 }
 
@@ -848,7 +873,8 @@ void packed_object_info_detail(struct pack_entry *e,
                strcpy(type, "tag");
                break;
        default:
-               die("corrupted pack file");
+               die("corrupted pack file %s containing object of kind %d",
+                   p->pack_name, kind);
        }
        *store_size = 0; /* notyet */
 }
@@ -887,7 +913,8 @@ static int packed_object_info(struct pack_entry *entry,
                strcpy(type, "tag");
                break;
        default:
-               die("corrupted pack file");
+               die("corrupted pack file %s containing object of kind %d",
+                   p->pack_name, kind);
        }
        if (sizep)
                *sizep = size;
@@ -987,7 +1014,7 @@ static void *unpack_entry(struct pack_entry *entry,
        retval = unpack_entry_gently(entry, type, sizep);
        unuse_packed_git(p);
        if (!retval)
-               die("corrupted pack file");
+               die("corrupted pack file %s", p->pack_name);
        return retval;
 }
 
@@ -1221,6 +1248,73 @@ char *write_sha1_file_prepare(void *buf,
        return sha1_file_name(sha1);
 }
 
+/*
+ * Link the tempfile to the final place, possibly creating the
+ * last directory level as you do so.
+ *
+ * Returns the errno on failure, 0 on success.
+ */
+static int link_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret;
+
+       if (!link(tmpfile, filename))
+               return 0;
+
+       /*
+        * Try to mkdir the last path component if that failed
+        * with an ENOENT.
+        *
+        * Re-try the "link()" regardless of whether the mkdir
+        * succeeds, since a race might mean that somebody
+        * else succeeded.
+        */
+       ret = errno;
+       if (ret == ENOENT) {
+               char *dir = strrchr(filename, '/');
+               if (dir) {
+                       *dir = 0;
+                       mkdir(filename, 0777);
+                       *dir = '/';
+                       if (!link(tmpfile, filename))
+                               return 0;
+                       ret = errno;
+               }
+       }
+       return ret;
+}
+
+/*
+ * Move the just written object into its final resting place
+ */
+int move_temp_to_file(const char *tmpfile, char *filename)
+{
+       int ret = link_temp_to_file(tmpfile, filename);
+       if (ret) {
+               /*
+                * Coda hack - coda doesn't like cross-directory links,
+                * so we fall back to a rename, which will mean that it
+                * won't be able to check collisions, but that's not a
+                * big deal.
+                *
+                * When this succeeds, we just return 0. We have nothing
+                * left to unlink.
+                */
+               if (ret == EXDEV && !rename(tmpfile, filename))
+                       return 0;
+       }
+       unlink(tmpfile);
+       if (ret) {
+               if (ret != EEXIST) {
+                       fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret));
+                       return -1;
+               }
+               /* FIXME!!! Collision check here ? */
+       }
+
+       return 0;
+}
+
 int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
        int size;
@@ -1230,7 +1324,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        char *filename;
        static char tmpfile[PATH_MAX];
        unsigned char hdr[50];
-       int fd, hdrlen, ret;
+       int fd, hdrlen;
 
        /* Normally if we have it in the pack then we do not bother writing
         * it out into .git/objects/??/?{38} file.
@@ -1293,32 +1387,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        close(fd);
        free(compressed);
 
-       ret = link(tmpfile, filename);
-       if (ret < 0) {
-               ret = errno;
-
-               /*
-                * Coda hack - coda doesn't like cross-directory links,
-                * so we fall back to a rename, which will mean that it
-                * won't be able to check collisions, but that's not a
-                * big deal.
-                *
-                * When this succeeds, we just return 0. We have nothing
-                * left to unlink.
-                */
-               if (ret == EXDEV && !rename(tmpfile, filename))
-                       return 0;
-       }
-       unlink(tmpfile);
-       if (ret) {
-               if (ret != EEXIST) {
-                       fprintf(stderr, "unable to write sha1 filename %s: %s", filename, strerror(ret));
-                       return -1;
-               }
-               /* FIXME!!! Collision check here ? */
-       }
-
-       return 0;
+       return move_temp_to_file(tmpfile, filename);
 }
 
 int write_sha1_to_fd(int fd, const unsigned char *sha1)
@@ -1393,8 +1462,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1)
 int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                       size_t bufsize, size_t *bufposn)
 {
-       char *filename = sha1_file_name(sha1);
-
+       char tmpfile[PATH_MAX];
        int local;
        z_stream stream;
        unsigned char real_sha1[20];
@@ -1402,10 +1470,11 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        int ret;
        SHA_CTX c;
 
-       local = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
+       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
 
+       local = mkstemp(tmpfile);
        if (local < 0)
-               return error("Couldn't open %s\n", filename);
+               return error("Couldn't open %s for %s\n", tmpfile, sha1_to_hex(sha1));
 
        memset(&stream, 0, sizeof(stream));
 
@@ -1435,7 +1504,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                size = read(fd, buffer + *bufposn, bufsize - *bufposn);
                if (size <= 0) {
                        close(local);
-                       unlink(filename);
+                       unlink(tmpfile);
                        if (!size)
                                return error("Connection closed?");
                        perror("Reading from connection");
@@ -1448,15 +1517,15 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        close(local);
        SHA1_Final(real_sha1, &c);
        if (ret != Z_STREAM_END) {
-               unlink(filename);
+               unlink(tmpfile);
                return error("File %s corrupted", sha1_to_hex(sha1));
        }
        if (memcmp(sha1, real_sha1, 20)) {
-               unlink(filename);
+               unlink(tmpfile);
                return error("File %s has bad hash\n", sha1_to_hex(sha1));
        }
-       
-       return 0;
+
+       return move_temp_to_file(tmpfile, sha1_file_name(sha1));
 }
 
 int has_pack_index(const unsigned char *sha1)
@@ -1518,3 +1587,42 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
                munmap(buf, size);
        return ret;
 }
+
+int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object)
+{
+       int fd;
+       char *target;
+
+       switch (st->st_mode & S_IFMT) {
+       case S_IFREG:
+               fd = open(path, O_RDONLY);
+               if (fd < 0)
+                       return error("open(\"%s\"): %s", path,
+                                    strerror(errno));
+               if (index_fd(sha1, fd, st, write_object, NULL) < 0)
+                       return error("%s: failed to insert into database",
+                                    path);
+               break;
+       case S_IFLNK:
+               target = xmalloc(st->st_size+1);
+               if (readlink(path, target, st->st_size+1) != st->st_size) {
+                       char *errstr = strerror(errno);
+                       free(target);
+                       return error("readlink(\"%s\"): %s", path,
+                                    errstr);
+               }
+               if (!write_object) {
+                       unsigned char hdr[50];
+                       int hdrlen;
+                       write_sha1_file_prepare(target, st->st_size, "blob",
+                                               sha1, hdr, &hdrlen);
+               } else if (write_sha1_file(target, st->st_size, "blob", sha1))
+                       return error("%s: failed to insert into database",
+                                    path);
+               free(target);
+               break;
+       default:
+               return error("%s: unsupported file type", path);
+       }
+       return 0;
+}