Merge branch 'jk/maint-mv' into maint
[gitweb.git] / entry.c
diff --git a/entry.c b/entry.c
index 915514aa5c43d3fd39f077a79f63ac27b841d255..852fea13955475c1e2fda9cfc25a63a54a1f61c7 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "blob.h"
 #include "dir.h"
+#include "streaming.h"
 
 static void create_directories(const char *path, int path_len,
                               const struct checkout *state)
@@ -35,9 +36,9 @@ static void create_directories(const char *path, int path_len,
                 */
                if (mkdir(buf, 0777)) {
                        if (errno == EEXIST && state->force &&
-                           !unlink(buf) && !mkdir(buf, 0777))
+                           !unlink_or_warn(buf) && !mkdir(buf, 0777))
                                continue;
-                       die("cannot create directory at %s", buf);
+                       die_errno("cannot create directory at '%s'", buf);
                }
        }
        free(buf);
@@ -51,7 +52,7 @@ static void remove_subtree(const char *path)
        char *name;
 
        if (!dir)
-               die("cannot opendir %s (%s)", path, strerror(errno));
+               die_errno("cannot opendir '%s'", path);
        strcpy(pathbuf, path);
        name = pathbuf + strlen(path);
        *name++ = '/';
@@ -61,15 +62,15 @@ static void remove_subtree(const char *path)
                        continue;
                strcpy(name, de->d_name);
                if (lstat(pathbuf, &st))
-                       die("cannot lstat %s (%s)", pathbuf, strerror(errno));
+                       die_errno("cannot lstat '%s'", pathbuf);
                if (S_ISDIR(st.st_mode))
                        remove_subtree(pathbuf);
                else if (unlink(pathbuf))
-                       die("cannot unlink %s (%s)", pathbuf, strerror(errno));
+                       die_errno("cannot unlink '%s'", pathbuf);
        }
        closedir(dir);
        if (rmdir(path))
-               die("cannot rmdir %s (%s)", path, strerror(errno));
+               die_errno("cannot rmdir '%s'", path);
 }
 
 static int create_file(const char *path, unsigned int mode)
@@ -91,6 +92,91 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
        return NULL;
 }
 
+static int open_output_fd(char *path, struct cache_entry *ce, int to_tempfile)
+{
+       int symlink = (ce->ce_mode & S_IFMT) != S_IFREG;
+       if (to_tempfile) {
+               strcpy(path, symlink
+                      ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX");
+               return mkstemp(path);
+       } else {
+               return create_file(path, !symlink ? ce->ce_mode : 0666);
+       }
+}
+
+static int fstat_output(int fd, const struct checkout *state, struct stat *st)
+{
+       /* use fstat() only when path == ce->name */
+       if (fstat_is_reliable() &&
+           state->refresh_cache && !state->base_dir_len) {
+               fstat(fd, st);
+               return 1;
+       }
+       return 0;
+}
+
+static int streaming_write_entry(struct cache_entry *ce, char *path,
+                                struct stream_filter *filter,
+                                const struct checkout *state, int to_tempfile,
+                                int *fstat_done, struct stat *statbuf)
+{
+       struct git_istream *st;
+       enum object_type type;
+       unsigned long sz;
+       int result = -1;
+       ssize_t kept = 0;
+       int fd = -1;
+
+       st = open_istream(ce->sha1, &type, &sz, filter);
+       if (!st)
+               return -1;
+       if (type != OBJ_BLOB)
+               goto close_and_exit;
+
+       fd = open_output_fd(path, ce, to_tempfile);
+       if (fd < 0)
+               goto close_and_exit;
+
+       for (;;) {
+               char buf[1024 * 16];
+               ssize_t wrote, holeto;
+               ssize_t readlen = read_istream(st, buf, sizeof(buf));
+
+               if (!readlen)
+                       break;
+               if (sizeof(buf) == readlen) {
+                       for (holeto = 0; holeto < readlen; holeto++)
+                               if (buf[holeto])
+                                       break;
+                       if (readlen == holeto) {
+                               kept += holeto;
+                               continue;
+                       }
+               }
+
+               if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1)
+                       goto close_and_exit;
+               else
+                       kept = 0;
+               wrote = write_in_full(fd, buf, readlen);
+
+               if (wrote != readlen)
+                       goto close_and_exit;
+       }
+       if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 ||
+                    write(fd, "", 1) != 1))
+               goto close_and_exit;
+       *fstat_done = fstat_output(fd, state, statbuf);
+
+close_and_exit:
+       close_istream(st);
+       if (0 <= fd)
+               result = close(fd);
+       if (result && 0 <= fd)
+               unlink(path);
+       return result;
+}
+
 static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
 {
        unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
@@ -101,19 +187,28 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        size_t wrote, newsize = 0;
        struct stat st;
 
+       if (ce_mode_s_ifmt == S_IFREG) {
+               struct stream_filter *filter = get_stream_filter(path, ce->sha1);
+               if (filter &&
+                   !streaming_write_entry(ce, path, filter,
+                                          state, to_tempfile,
+                                          &fstat_done, &st))
+                       goto finish;
+       }
+
        switch (ce_mode_s_ifmt) {
        case S_IFREG:
        case S_IFLNK:
                new = read_blob_entry(ce, &size);
                if (!new)
-                       return error("git checkout-index: unable to read sha1 file of %s (%s)",
+                       return error("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)",
+                               return error("unable to create symlink %s (%s)",
                                             path, strerror(errno));
                        break;
                }
@@ -128,45 +223,32 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                        size = newsize;
                }
 
-               if (to_tempfile) {
-                       if (ce_mode_s_ifmt == S_IFREG)
-                               strcpy(path, ".merge_file_XXXXXX");
-                       else
-                               strcpy(path, ".merge_link_XXXXXX");
-                       fd = mkstemp(path);
-               } else if (ce_mode_s_ifmt == S_IFREG) {
-                       fd = create_file(path, ce->ce_mode);
-               } else {
-                       fd = create_file(path, 0666);
-               }
+               fd = open_output_fd(path, ce, to_tempfile);
                if (fd < 0) {
                        free(new);
-                       return error("git checkout-index: unable to create file %s (%s)",
+                       return error("unable to create file %s (%s)",
                                path, strerror(errno));
                }
 
                wrote = write_in_full(fd, new, size);
-               /* use fstat() only when path == ce->name */
-               if (fstat_is_reliable() &&
-                   state->refresh_cache && !to_tempfile && !state->base_dir_len) {
-                       fstat(fd, &st);
-                       fstat_done = 1;
-               }
+               if (!to_tempfile)
+                       fstat_done = fstat_output(fd, state, &st);
                close(fd);
                free(new);
                if (wrote != size)
-                       return error("git checkout-index: unable to write file %s", path);
+                       return error("unable to write file %s", path);
                break;
        case S_IFGITLINK:
                if (to_tempfile)
-                       return error("git checkout-index: cannot create temporary subproject %s", path);
+                       return error("cannot create temporary subproject %s", path);
                if (mkdir(path, 0777) < 0)
-                       return error("git checkout-index: cannot create subproject directory %s", path);
+                       return error("cannot create subproject directory %s", path);
                break;
        default:
-               return error("git checkout-index: unknown file mode for %s", path);
+               return error("unknown file mode for %s in index", path);
        }
 
+finish:
        if (state->refresh_cache) {
                if (!fstat_done)
                        lstat(ce->name, &st);
@@ -175,6 +257,23 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        return 0;
 }
 
+/*
+ * This is like 'lstat()', except it refuses to follow symlinks
+ * in the path, after skipping "skiplen".
+ */
+static int check_path(const char *path, int len, struct stat *st, int skiplen)
+{
+       const char *slash = path + len;
+
+       while (path < slash && *slash != '/')
+               slash--;
+       if (!has_dirs_only_path(path, slash - path, skiplen)) {
+               errno = ENOENT;
+               return -1;
+       }
+       return lstat(path, st);
+}
+
 int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath)
 {
        static char path[PATH_MAX + 1];
@@ -188,13 +287,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
        strcpy(path + len, ce->name);
        len += ce_namelen(ce);
 
-       if (!lstat(path, &st)) {
-               unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
+       if (!check_path(path, len, &st, state->base_dir_len)) {
+               unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
                if (!changed)
                        return 0;
                if (!state->force) {
                        if (!state->quiet)
-                               fprintf(stderr, "git-checkout-index: %s already exists\n", path);
+                               fprintf(stderr, "%s already exists, no checkout\n", path);
                        return -1;
                }