Documentation: Allow custom diff tools to be specified in 'diff.tool'
[gitweb.git] / fast-import.c
index ff56ea28918dd706f57617632c82336654db3543..b3ba778924908946a0e20ebfdae9bc2058e8a55e 100644 (file)
@@ -132,14 +132,17 @@ Format of STDIN stream:
   ts    ::= # time since the epoch in seconds, ascii base10 notation;
   tz    ::= # GIT style timezone;
 
-     # note: comments may appear anywhere in the input, except
-     # within a data command.  Any form of the data command
-     # always escapes the related input from comment processing.
+     # note: comments and cat requests may appear anywhere
+     # in the input, except within a data command.  Any form
+     # of the data command always escapes the related input
+     # from comment processing.
      #
      # In case it is not clear, the '#' that starts the comment
      # must be the first character on that line (an lf
      # preceded it).
      #
+  cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
+
   comment ::= '#' not_lf* lf;
   not_lf  ::= # Any byte that is not ASCII newline (LF);
 */
@@ -156,14 +159,14 @@ Format of STDIN stream:
 #include "csum-file.h"
 #include "quote.h"
 #include "exec_cmd.h"
+#include "dir.h"
 
 #define PACK_ID_BITS 16
 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
 #define DEPTH_BITS 13
 #define MAX_DEPTH ((1<<DEPTH_BITS)-1)
 
-struct object_entry
-{
+struct object_entry {
        struct pack_idx_entry idx;
        struct object_entry *next;
        uint32_t type : TYPE_BITS,
@@ -171,16 +174,14 @@ struct object_entry
                depth : DEPTH_BITS;
 };
 
-struct object_entry_pool
-{
+struct object_entry_pool {
        struct object_entry_pool *next_pool;
        struct object_entry *next_free;
        struct object_entry *end;
        struct object_entry entries[FLEX_ARRAY]; /* more */
 };
 
-struct mark_set
-{
+struct mark_set {
        union {
                struct object_entry *marked[1024];
                struct mark_set *sets[1024];
@@ -188,57 +189,49 @@ struct mark_set
        unsigned int shift;
 };
 
-struct last_object
-{
+struct last_object {
        struct strbuf data;
        off_t offset;
        unsigned int depth;
        unsigned no_swap : 1;
 };
 
-struct mem_pool
-{
+struct mem_pool {
        struct mem_pool *next_pool;
        char *next_free;
        char *end;
        uintmax_t space[FLEX_ARRAY]; /* more */
 };
 
-struct atom_str
-{
+struct atom_str {
        struct atom_str *next_atom;
        unsigned short str_len;
        char str_dat[FLEX_ARRAY]; /* more */
 };
 
 struct tree_content;
-struct tree_entry
-{
+struct tree_entry {
        struct tree_content *tree;
        struct atom_str *name;
-       struct tree_entry_ms
-       {
+       struct tree_entry_ms {
                uint16_t mode;
                unsigned char sha1[20];
        } versions[2];
 };
 
-struct tree_content
-{
+struct tree_content {
        unsigned int entry_capacity; /* must match avail_tree_content */
        unsigned int entry_count;
        unsigned int delta_depth;
        struct tree_entry *entries[FLEX_ARRAY]; /* more */
 };
 
-struct avail_tree_content
-{
+struct avail_tree_content {
        unsigned int entry_capacity; /* must match tree_content */
        struct avail_tree_content *next_avail;
 };
 
-struct branch
-{
+struct branch {
        struct branch *table_next_branch;
        struct branch *active_next_branch;
        const char *name;
@@ -250,16 +243,14 @@ struct branch
        unsigned char sha1[20];
 };
 
-struct tag
-{
+struct tag {
        struct tag *next_tag;
        const char *name;
        unsigned int pack_id;
        unsigned char sha1[20];
 };
 
-struct hash_list
-{
+struct hash_list {
        struct hash_list *next;
        unsigned char sha1[20];
 };
@@ -267,11 +258,10 @@ struct hash_list
 typedef enum {
        WHENSPEC_RAW = 1,
        WHENSPEC_RFC2822,
-       WHENSPEC_NOW,
+       WHENSPEC_NOW
 } whenspec_type;
 
-struct recent_command
-{
+struct recent_command {
        struct recent_command *prev;
        struct recent_command *next;
        char *buf;
@@ -361,7 +351,14 @@ static uintmax_t next_mark;
 static struct strbuf new_data = STRBUF_INIT;
 static int seen_data_command;
 
+/* Signal handling */
+static volatile sig_atomic_t checkpoint_requested;
+
+/* Where to write output of cat-blob commands */
+static int cat_blob_fd = STDOUT_FILENO;
+
 static void parse_argv(void);
+static void parse_cat_blob(void);
 
 static void write_branch_report(FILE *rpt, struct branch *b)
 {
@@ -500,6 +497,32 @@ static NORETURN void die_nicely(const char *err, va_list params)
        exit(128);
 }
 
+#ifndef SIGUSR1        /* Windows, for example */
+
+static void set_checkpoint_signal(void)
+{
+}
+
+#else
+
+static void checkpoint_signal(int signo)
+{
+       checkpoint_requested = 1;
+}
+
+static void set_checkpoint_signal(void)
+{
+       struct sigaction sa;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = checkpoint_signal;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGUSR1, &sa, NULL);
+}
+
+#endif
+
 static void alloc_objects(unsigned int cnt)
 {
        struct object_entry_pool *b;
@@ -539,22 +562,17 @@ static struct object_entry *insert_object(unsigned char *sha1)
 {
        unsigned int h = sha1[0] << 8 | sha1[1];
        struct object_entry *e = object_table[h];
-       struct object_entry *p = NULL;
 
        while (e) {
                if (!hashcmp(sha1, e->idx.sha1))
                        return e;
-               p = e;
                e = e->next;
        }
 
        e = new_object(sha1);
-       e->next = NULL;
+       e->next = object_table[h];
        e->idx.offset = 0;
-       if (p)
-               p->next = e;
-       else
-               object_table[h] = e;
+       object_table[h] = e;
        return e;
 }
 
@@ -839,6 +857,7 @@ static void start_packfile(void)
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
        p->pack_fd = pack_fd;
+       p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
 
        hdr.hdr_signature = htonl(PACK_SIGNATURE);
@@ -980,29 +999,6 @@ static void cycle_packfile(void)
        start_packfile();
 }
 
-static size_t encode_header(
-       enum object_type type,
-       uintmax_t size,
-       unsigned char *hdr)
-{
-       int n = 1;
-       unsigned char c;
-
-       if (type < OBJ_COMMIT || type > OBJ_REF_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 int store_object(
        enum object_type type,
        struct strbuf *dat,
@@ -1103,7 +1099,7 @@ static int store_object(
                delta_count_by_type[type]++;
                e->depth = last->depth + 1;
 
-               hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr);
+               hdrlen = encode_in_pack_object_header(OBJ_OFS_DELTA, deltalen, hdr);
                sha1write(pack_file, hdr, hdrlen);
                pack_size += hdrlen;
 
@@ -1114,7 +1110,7 @@ static int store_object(
                pack_size += sizeof(hdr) - pos;
        } else {
                e->depth = 0;
-               hdrlen = encode_header(type, dat->len, hdr);
+               hdrlen = encode_in_pack_object_header(type, dat->len, hdr);
                sha1write(pack_file, hdr, hdrlen);
                pack_size += hdrlen;
        }
@@ -1188,7 +1184,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
        memset(&s, 0, sizeof(s));
        deflateInit(&s, pack_compression_level);
 
-       hdrlen = encode_header(OBJ_BLOB, len, out_buf);
+       hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf);
        if (out_sz <= hdrlen)
                die("impossibly large object header");
 
@@ -1460,6 +1456,20 @@ static void store_tree(struct tree_entry *root)
        t->entry_count -= del;
 }
 
+static void tree_content_replace(
+       struct tree_entry *root,
+       const unsigned char *sha1,
+       const uint16_t mode,
+       struct tree_content *newtree)
+{
+       if (!S_ISDIR(mode))
+               die("Root cannot be a non-directory");
+       hashcpy(root->versions[1].sha1, sha1);
+       if (root->tree)
+               release_tree_content_recursive(root->tree);
+       root->tree = newtree;
+}
+
 static int tree_content_set(
        struct tree_entry *root,
        const char *p,
@@ -1467,7 +1477,7 @@ static int tree_content_set(
        const uint16_t mode,
        struct tree_content *subtree)
 {
-       struct tree_content *t = root->tree;
+       struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
@@ -1482,9 +1492,12 @@ static int tree_content_set(
        if (!slash1 && !S_ISDIR(mode) && subtree)
                die("Non-directories cannot have subtrees");
 
+       if (!root->tree)
+               load_tree(root);
+       t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
-               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+               if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (!slash1) {
                                if (!S_ISDIR(mode)
                                                && e->versions[1].mode == mode
@@ -1537,7 +1550,7 @@ static int tree_content_remove(
        const char *p,
        struct tree_entry *backup_leaf)
 {
-       struct tree_content *t = root->tree;
+       struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
@@ -1548,9 +1561,20 @@ static int tree_content_remove(
        else
                n = strlen(p);
 
+       if (!root->tree)
+               load_tree(root);
+       t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
-               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+               if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
+                       if (slash1 && !S_ISDIR(e->versions[1].mode))
+                               /*
+                                * If p names a file in some subdirectory, and a
+                                * file or symlink matching the name of the
+                                * parent directory of p exists, then p cannot
+                                * exist and need not be deleted.
+                                */
+                               return 1;
                        if (!slash1 || !S_ISDIR(e->versions[1].mode))
                                goto del_entry;
                        if (!e->tree)
@@ -1587,7 +1611,7 @@ static int tree_content_get(
        const char *p,
        struct tree_entry *leaf)
 {
-       struct tree_content *t = root->tree;
+       struct tree_content *t;
        const char *slash1;
        unsigned int i, n;
        struct tree_entry *e;
@@ -1598,9 +1622,12 @@ static int tree_content_get(
        else
                n = strlen(p);
 
+       if (!root->tree)
+               load_tree(root);
+       t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
-               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+               if (e->name->str_len == n && !strncmp_icase(p, e->name->str_dat, n)) {
                        if (!slash1) {
                                memcpy(leaf, e, sizeof(*leaf));
                                if (e->tree && is_null_sha1(e->versions[1].sha1))
@@ -1689,7 +1716,7 @@ static void dump_marks_helper(FILE *f,
        if (m->shift) {
                for (k = 0; k < 1024; k++) {
                        if (m->data.sets[k])
-                               dump_marks_helper(f, (base + k) << m->shift,
+                               dump_marks_helper(f, base + (k << m->shift),
                                        m->data.sets[k]);
                }
        } else {
@@ -1796,7 +1823,7 @@ static int read_next_command(void)
                return EOF;
        }
 
-       do {
+       for (;;) {
                if (unread_command_buf) {
                        unread_command_buf = 0;
                } else {
@@ -1829,9 +1856,14 @@ static int read_next_command(void)
                        rc->prev->next = rc;
                        cmd_tail = rc;
                }
-       } while (command_buf.buf[0] == '#');
-
-       return 0;
+               if (!prefixcmp(command_buf.buf, "cat-blob ")) {
+                       parse_cat_blob();
+                       continue;
+               }
+               if (command_buf.buf[0] == '#')
+                       continue;
+               return 0;
+       }
 }
 
 static void skip_optional_lf(void)
@@ -2154,6 +2186,7 @@ static void file_change_m(struct branch *b)
        case S_IFREG | 0644:
        case S_IFREG | 0755:
        case S_IFLNK:
+       case S_IFDIR:
        case S_IFGITLINK:
                /* ok */
                break;
@@ -2185,6 +2218,12 @@ static void file_change_m(struct branch *b)
                p = uq.buf;
        }
 
+       /* Git does not track empty, non-toplevel directories. */
+       if (S_ISDIR(mode) && !memcmp(sha1, EMPTY_TREE_SHA1_BIN, 20) && *p) {
+               tree_content_remove(&b->branch_tree, p, NULL);
+               return;
+       }
+
        if (S_ISGITLINK(mode)) {
                if (inline_data)
                        die("Git links cannot be specified 'inline': %s",
@@ -2199,25 +2238,34 @@ static void file_change_m(struct branch *b)
                 * another repository.
                 */
        } else if (inline_data) {
+               if (S_ISDIR(mode))
+                       die("Directories cannot be specified 'inline': %s",
+                               command_buf.buf);
                if (p != uq.buf) {
                        strbuf_addstr(&uq, p);
                        p = uq.buf;
                }
                read_next_command();
                parse_and_store_blob(&last_blob, sha1, 0);
-       } else if (oe) {
-               if (oe->type != OBJ_BLOB)
-                       die("Not a blob (actually a %s): %s",
-                               typename(oe->type), command_buf.buf);
        } else {
-               enum object_type type = sha1_object_info(sha1, NULL);
+               enum object_type expected = S_ISDIR(mode) ?
+                                               OBJ_TREE: OBJ_BLOB;
+               enum object_type type = oe ? oe->type :
+                                       sha1_object_info(sha1, NULL);
                if (type < 0)
-                       die("Blob not found: %s", command_buf.buf);
-               if (type != OBJ_BLOB)
-                       die("Not a blob (actually a %s): %s",
-                           typename(type), command_buf.buf);
+                       die("%s not found: %s",
+                                       S_ISDIR(mode) ?  "Tree" : "Blob",
+                                       command_buf.buf);
+               if (type != expected)
+                       die("Not a %s (actually a %s): %s",
+                               typename(expected), typename(type),
+                               command_buf.buf);
        }
 
+       if (!*p) {
+               tree_content_replace(&b->branch_tree, sha1, mode, NULL);
+               return;
+       }
        tree_content_set(&b->branch_tree, p, sha1, mode, NULL);
 }
 
@@ -2276,6 +2324,13 @@ static void file_change_cr(struct branch *b, int rename)
                tree_content_get(&b->branch_tree, s, &leaf);
        if (!leaf.versions[1].mode)
                die("Path %s not in branch", s);
+       if (!*d) {      /* C "path/to/subdir" "" */
+               tree_content_replace(&b->branch_tree,
+                       leaf.versions[1].sha1,
+                       leaf.versions[1].mode,
+                       leaf.tree);
+               return;
+       }
        tree_content_set(&b->branch_tree, d,
                leaf.versions[1].sha1,
                leaf.versions[1].mode,
@@ -2689,14 +2744,95 @@ static void parse_reset_branch(void)
                unread_command_buf = 1;
 }
 
-static void parse_checkpoint(void)
+static void cat_blob_write(const char *buf, unsigned long size)
+{
+       if (write_in_full(cat_blob_fd, buf, size) != size)
+               die_errno("Write to frontend failed");
+}
+
+static void cat_blob(struct object_entry *oe, unsigned char sha1[20])
 {
+       struct strbuf line = STRBUF_INIT;
+       unsigned long size;
+       enum object_type type = 0;
+       char *buf;
+
+       if (!oe || oe->pack_id == MAX_PACK_ID) {
+               buf = read_sha1_file(sha1, &type, &size);
+       } else {
+               type = oe->type;
+               buf = gfi_unpack_entry(oe, &size);
+       }
+
+       /*
+        * Output based on batch_one_object() from cat-file.c.
+        */
+       if (type <= 0) {
+               strbuf_reset(&line);
+               strbuf_addf(&line, "%s missing\n", sha1_to_hex(sha1));
+               cat_blob_write(line.buf, line.len);
+               strbuf_release(&line);
+               free(buf);
+               return;
+       }
+       if (!buf)
+               die("Can't read object %s", sha1_to_hex(sha1));
+       if (type != OBJ_BLOB)
+               die("Object %s is a %s but a blob was expected.",
+                   sha1_to_hex(sha1), typename(type));
+       strbuf_reset(&line);
+       strbuf_addf(&line, "%s %s %lu\n", sha1_to_hex(sha1),
+                                               typename(type), size);
+       cat_blob_write(line.buf, line.len);
+       strbuf_release(&line);
+       cat_blob_write(buf, size);
+       cat_blob_write("\n", 1);
+       free(buf);
+}
+
+static void parse_cat_blob(void)
+{
+       const char *p;
+       struct object_entry *oe = oe;
+       unsigned char sha1[20];
+
+       /* cat-blob SP <object> LF */
+       p = command_buf.buf + strlen("cat-blob ");
+       if (*p == ':') {
+               char *x;
+               oe = find_mark(strtoumax(p + 1, &x, 10));
+               if (x == p + 1)
+                       die("Invalid mark: %s", command_buf.buf);
+               if (!oe)
+                       die("Unknown mark: %s", command_buf.buf);
+               if (*x)
+                       die("Garbage after mark: %s", command_buf.buf);
+               hashcpy(sha1, oe->idx.sha1);
+       } else {
+               if (get_sha1_hex(p, sha1))
+                       die("Invalid SHA1: %s", command_buf.buf);
+               if (p[40])
+                       die("Garbage after SHA1: %s", command_buf.buf);
+               oe = find_object(sha1);
+       }
+
+       cat_blob(oe, sha1);
+}
+
+static void checkpoint(void)
+{
+       checkpoint_requested = 0;
        if (object_count) {
                cycle_packfile();
                dump_branches();
                dump_tags();
                dump_marks();
        }
+}
+
+static void parse_checkpoint(void)
+{
+       checkpoint_requested = 1;
        skip_optional_lf();
 }
 
@@ -2730,6 +2866,7 @@ static void option_import_marks(const char *marks, int from_stream)
        }
 
        import_marks_file = make_fast_import_path(marks);
+       safe_create_leading_directories_const(import_marks_file);
        import_marks_file_from_stream = from_stream;
 }
 
@@ -2745,21 +2882,39 @@ static void option_date_format(const char *fmt)
                die("unknown --date-format argument %s", fmt);
 }
 
+static unsigned long ulong_arg(const char *option, const char *arg)
+{
+       char *endptr;
+       unsigned long rv = strtoul(arg, &endptr, 0);
+       if (strchr(arg, '-') || endptr == arg || *endptr)
+               die("%s: argument must be a non-negative integer", option);
+       return rv;
+}
+
 static void option_depth(const char *depth)
 {
-       max_depth = strtoul(depth, NULL, 0);
+       max_depth = ulong_arg("--depth", depth);
        if (max_depth > MAX_DEPTH)
                die("--depth cannot exceed %u", MAX_DEPTH);
 }
 
 static void option_active_branches(const char *branches)
 {
-       max_active_branches = strtoul(branches, NULL, 0);
+       max_active_branches = ulong_arg("--active-branches", branches);
 }
 
 static void option_export_marks(const char *marks)
 {
        export_marks_file = make_fast_import_path(marks);
+       safe_create_leading_directories_const(export_marks_file);
+}
+
+static void option_cat_blob_fd(const char *fd)
+{
+       unsigned long n = ulong_arg("--cat-blob-fd", fd);
+       if (n > (unsigned long) INT_MAX)
+               die("--cat-blob-fd cannot exceed %d", INT_MAX);
+       cat_blob_fd = (int) n;
 }
 
 static void option_export_pack_edges(const char *edges)
@@ -2815,6 +2970,8 @@ static int parse_one_feature(const char *feature, int from_stream)
                option_import_marks(feature + 13, from_stream);
        } else if (!prefixcmp(feature, "export-marks=")) {
                option_export_marks(feature + 13);
+       } else if (!strcmp(feature, "cat-blob")) {
+               ; /* Don't die - this feature is supported */
        } else if (!prefixcmp(feature, "relative-marks")) {
                relative_marks_paths = 1;
        } else if (!prefixcmp(feature, "no-relative-marks")) {
@@ -2893,7 +3050,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)
 }
 
 static const char fast_import_usage[] =
-"git fast-import [--date-format=f] [--max-pack-size=n] [--big-file-threshold=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+"git fast-import [--date-format=<f>] [--max-pack-size=<n>] [--big-file-threshold=<n>] [--depth=<n>] [--active-branches=<n>] [--export-marks=<marks.file>]";
 
 static void parse_argv(void)
 {
@@ -2911,6 +3068,11 @@ static void parse_argv(void)
                if (parse_one_feature(a + 2, 0))
                        continue;
 
+               if (!prefixcmp(a + 2, "cat-blob-fd=")) {
+                       option_cat_blob_fd(a + 2 + strlen("cat-blob-fd="));
+                       continue;
+               }
+
                die("unknown option %s", a);
        }
        if (i != global_argc)
@@ -2953,6 +3115,7 @@ int main(int argc, const char **argv)
        prepare_packed_git();
        start_packfile();
        set_die_routine(die_nicely);
+       set_checkpoint_signal();
        while (read_next_command() != EOF) {
                if (!strcmp("blob", command_buf.buf))
                        parse_new_blob();
@@ -2974,6 +3137,9 @@ int main(int argc, const char **argv)
                        /* ignore non-git options*/;
                else
                        die("Unsupported command: %s", command_buf.buf);
+
+               if (checkpoint_requested)
+                       checkpoint();
        }
 
        /* argv hasn't been parsed yet, do so */