Merge branch 'bs/maint-1.6.0-tree-walk-prefix'
[gitweb.git] / entry.c
diff --git a/entry.c b/entry.c
index aa2ee46a84033585d8e07a585610c5a697af82c2..5daacc2fe51eada819bedea05f958fbf87f5b889 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,42 +1,37 @@
 #include "cache.h"
 #include "blob.h"
+#include "dir.h"
 
-static void create_directories(const char *path, const struct checkout *state)
+static void create_directories(const char *path, int path_len,
+                              const struct checkout *state)
 {
-       int len = strlen(path);
-       char *buf = xmalloc(len + 1);
-       const char *slash = path;
-
-       while ((slash = strchr(slash+1, '/')) != NULL) {
-               struct stat st;
-               int stat_status;
-
-               len = slash - path;
-               memcpy(buf, path, len);
+       char *buf = xmalloc(path_len + 1);
+       int len = 0;
+
+       while (len < path_len) {
+               do {
+                       buf[len] = path[len];
+                       len++;
+               } while (len < path_len && path[len] != '/');
+               if (len >= path_len)
+                       break;
                buf[len] = 0;
 
-               if (len <= state->base_dir_len)
-                       /*
-                        * checkout-index --prefix=<dir>; <dir> is
-                        * allowed to be a symlink to an existing
-                        * directory.
-                        */
-                       stat_status = stat(buf, &st);
-               else
-                       /*
-                        * if there currently is a symlink, we would
-                        * want to replace it with a real directory.
-                        */
-                       stat_status = lstat(buf, &st);
-
-               if (!stat_status && S_ISDIR(st.st_mode))
+               /*
+                * For 'checkout-index --prefix=<dir>', <dir> is
+                * allowed to be a symlink to an existing directory,
+                * and we set 'state->base_dir_len' below, such that
+                * we test the path components of the prefix with the
+                * stat() function instead of the lstat() function.
+                */
+               if (has_dirs_only_path(buf, len, state->base_dir_len))
                        continue; /* ok, it is already a directory. */
 
                /*
-                * We know stat_status == 0 means something exists
-                * there and this mkdir would fail, but that is an
-                * error codepath; we do not care, as we unlink and
-                * mkdir again in such a case.
+                * If this mkdir() would fail, it could be that there
+                * is already a symlink or something else exists
+                * there, therefore we then try to unlink it and try
+                * one more time to create the directory.
                 */
                if (mkdir(buf, 0777)) {
                        if (errno == EEXIST && state->force &&
@@ -62,9 +57,7 @@ static void remove_subtree(const char *path)
        *name++ = '/';
        while ((de = readdir(dir)) != NULL) {
                struct stat st;
-               if ((de->d_name[0] == '.') &&
-                   ((de->d_name[1] == 0) ||
-                    ((de->d_name[1] == '.') && de->d_name[2] == 0)))
+               if (is_dot_or_dotdot(de->d_name))
                        continue;
                strcpy(name, de->d_name);
                if (lstat(pathbuf, &st))
@@ -85,7 +78,7 @@ static int create_file(const char *path, unsigned int mode)
        return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
 }
 
-static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned long *size)
+static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
 {
        enum object_type type;
        void *new = read_sha1_file(ce->sha1, &type, size);
@@ -100,36 +93,52 @@ static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned
 
 static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
 {
-       int fd;
-       long wrote;
-
-       switch (ce->ce_mode & S_IFMT) {
-               char *new;
-               struct strbuf buf;
-               unsigned long size;
+       unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
+       int fd, ret, fstat_done = 0;
+       char *new;
+       struct strbuf buf = STRBUF_INIT;
+       unsigned long size;
+       size_t wrote, newsize = 0;
+       struct stat st;
 
+       switch (ce_mode_s_ifmt) {
        case S_IFREG:
-               new = read_blob_entry(ce, path, &size);
+       case S_IFLNK:
+               new = read_blob_entry(ce, &size);
                if (!new)
                        return error("git checkout-index: unable to read sha1 file of %s (%s)",
                                path, sha1_to_hex(ce->sha1));
 
+               if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {
+                       ret = symlink(new, path);
+                       free(new);
+                       if (ret)
+                               return error("git checkout-index: unable to create symlink %s (%s)",
+                                            path, strerror(errno));
+                       break;
+               }
+
                /*
                 * Convert from git internal format to working tree format
                 */
-               strbuf_init(&buf, 0);
-               if (convert_to_working_tree(ce->name, new, size, &buf)) {
-                       size_t newsize = 0;
+               if (ce_mode_s_ifmt == S_IFREG &&
+                   convert_to_working_tree(ce->name, new, size, &buf)) {
                        free(new);
                        new = strbuf_detach(&buf, &newsize);
                        size = newsize;
                }
 
                if (to_tempfile) {
-                       strcpy(path, ".merge_file_XXXXXX");
+                       if (ce_mode_s_ifmt == S_IFREG)
+                               strcpy(path, ".merge_file_XXXXXX");
+                       else
+                               strcpy(path, ".merge_link_XXXXXX");
                        fd = mkstemp(path);
-               } else
+               } else if (ce_mode_s_ifmt == S_IFREG) {
                        fd = create_file(path, ce->ce_mode);
+               } else {
+                       fd = create_file(path, 0666);
+               }
                if (fd < 0) {
                        free(new);
                        return error("git checkout-index: unable to create file %s (%s)",
@@ -137,41 +146,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                }
 
                wrote = write_in_full(fd, new, size);
+               /* use fstat() only when path == ce->name */
+               if (state->refresh_cache && !to_tempfile && !state->base_dir_len) {
+                       fstat(fd, &st);
+                       fstat_done = 1;
+               }
                close(fd);
                free(new);
                if (wrote != size)
                        return error("git checkout-index: unable to write file %s", path);
                break;
-       case S_IFLNK:
-               new = read_blob_entry(ce, path, &size);
-               if (!new)
-                       return error("git checkout-index: unable to read sha1 file of %s (%s)",
-                               path, sha1_to_hex(ce->sha1));
-               if (to_tempfile || !has_symlinks) {
-                       if (to_tempfile) {
-                               strcpy(path, ".merge_link_XXXXXX");
-                               fd = mkstemp(path);
-                       } else
-                               fd = create_file(path, 0666);
-                       if (fd < 0) {
-                               free(new);
-                               return error("git checkout-index: unable to create "
-                                                "file %s (%s)", path, strerror(errno));
-                       }
-                       wrote = write_in_full(fd, new, size);
-                       close(fd);
-                       free(new);
-                       if (wrote != size)
-                               return error("git checkout-index: unable to write file %s",
-                                       path);
-               } else {
-                       wrote = symlink(new, path);
-                       free(new);
-                       if (wrote)
-                               return error("git checkout-index: unable to create "
-                                                "symlink %s (%s)", path, strerror(errno));
-               }
-               break;
        case S_IFGITLINK:
                if (to_tempfile)
                        return error("git checkout-index: cannot create temporary subproject %s", path);
@@ -183,8 +167,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        }
 
        if (state->refresh_cache) {
-               struct stat st;
-               lstat(ce->name, &st);
+               if (!fstat_done)
+                       lstat(ce->name, &st);
                fill_stat_cache_info(ce, &st);
        }
        return 0;
@@ -201,6 +185,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
 
        memcpy(path, state->base_dir, len);
        strcpy(path + len, ce->name);
+       len += ce_namelen(ce);
 
        if (!lstat(path, &st)) {
                unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
@@ -229,6 +214,6 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
                        return error("unable to unlink old '%s' (%s)", path, strerror(errno));
        } else if (state->not_new)
                return 0;
-       create_directories(path, state);
+       create_directories(path, len, state);
        return write_entry(ce, path, state, 0);
 }