ewah: support platforms that require aligned reads
[gitweb.git] / fast-import.c
index f9347f55bac6b2d9a775c165013aa7107790e5f4..f4d9969e5c81f9c1415f0908f090bf62b156e179 100644 (file)
@@ -22,8 +22,8 @@ Format of STDIN stream:
     ('author' (sp name)? sp '<' email '>' sp when lf)?
     'committer' (sp name)? sp '<' email '>' sp when lf
     commit_msg
-    ('from' sp committish lf)?
-    ('merge' sp committish lf)*
+    ('from' sp commit-ish lf)?
+    ('merge' sp commit-ish lf)*
     (file_change | ls)*
     lf?;
   commit_msg ::= data;
@@ -43,18 +43,18 @@ Format of STDIN stream:
   file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
   file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
     data;
-  note_obm ::= 'N' sp (hexsha1 | idnum) sp committish lf;
-  note_inm ::= 'N' sp 'inline' sp committish lf
+  note_obm ::= 'N' sp (hexsha1 | idnum) sp commit-ish lf;
+  note_inm ::= 'N' sp 'inline' sp commit-ish lf
     data;
 
   new_tag ::= 'tag' sp tag_str lf
-    'from' sp committish lf
+    'from' sp commit-ish lf
     ('tagger' (sp name)? sp '<' email '>' sp when lf)?
     tag_msg;
   tag_msg ::= data;
 
   reset_branch ::= 'reset' sp ref_str lf
-    ('from' sp committish lf)?
+    ('from' sp commit-ish lf)?
     lf?;
 
   checkpoint ::= 'checkpoint' lf
@@ -93,7 +93,7 @@ Format of STDIN stream:
      # stream formatting is: \, " and LF.  Otherwise these values
      # are UTF8.
      #
-  committish  ::= (ref_str | hexsha1 | sha1exp_str | idnum);
+  commit-ish  ::= (ref_str | hexsha1 | sha1exp_str | idnum);
   ref_str     ::= ref;
   sha1exp_str ::= sha1exp;
   tag_str     ::= tag;
@@ -297,7 +297,7 @@ static int failure;
 static FILE *pack_edges;
 static unsigned int show_stats = 1;
 static int global_argc;
-static const char **global_argv;
+static char **global_argv;
 
 /* Memory pools */
 static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
@@ -855,15 +855,15 @@ static struct tree_content *dup_tree_content(struct tree_content *s)
 
 static void start_packfile(void)
 {
-       static char tmpfile[PATH_MAX];
+       static char tmp_file[PATH_MAX];
        struct packed_git *p;
        struct pack_header hdr;
        int pack_fd;
 
-       pack_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+       pack_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                              "pack/tmp_pack_XXXXXX");
-       p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
-       strcpy(p->pack_name, tmpfile);
+       p = xcalloc(1, sizeof(*p) + strlen(tmp_file) + 2);
+       strcpy(p->pack_name, tmp_file);
        p->pack_fd = pack_fd;
        p->do_not_close = 1;
        pack_file = sha1fd(pack_fd, p->pack_name);
@@ -1143,17 +1143,11 @@ static int store_object(
        return 0;
 }
 
-static void truncate_pack(off_t to, git_SHA_CTX *ctx)
+static void truncate_pack(struct sha1file_checkpoint *checkpoint)
 {
-       if (ftruncate(pack_data->pack_fd, to)
-        || lseek(pack_data->pack_fd, to, SEEK_SET) != to)
+       if (sha1file_truncate(pack_file, checkpoint))
                die_errno("cannot truncate pack to skip duplicate");
-       pack_size = to;
-
-       /* yes this is a layering violation */
-       pack_file->total = to;
-       pack_file->offset = 0;
-       pack_file->ctx = *ctx;
+       pack_size = checkpoint->offset;
 }
 
 static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
@@ -1166,8 +1160,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
        unsigned long hdrlen;
        off_t offset;
        git_SHA_CTX c;
-       git_SHA_CTX pack_file_ctx;
        git_zstream s;
+       struct sha1file_checkpoint checkpoint;
        int status = Z_OK;
 
        /* Determine if we should auto-checkpoint. */
@@ -1175,11 +1169,8 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
                || (pack_size + 60 + len) < pack_size)
                cycle_packfile();
 
-       offset = pack_size;
-
-       /* preserve the pack_file SHA1 ctx in case we have to truncate later */
-       sha1flush(pack_file);
-       pack_file_ctx = pack_file->ctx;
+       sha1file_checkpoint(pack_file, &checkpoint);
+       offset = checkpoint.offset;
 
        hdrlen = snprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1;
        if (out_sz <= hdrlen)
@@ -1245,14 +1236,14 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark)
 
        if (e->idx.offset) {
                duplicate_count_by_type[OBJ_BLOB]++;
-               truncate_pack(offset, &pack_file_ctx);
+               truncate_pack(&checkpoint);
 
        } else if (find_sha1_pack(sha1, packed_git)) {
                e->type = OBJ_BLOB;
                e->pack_id = MAX_PACK_ID;
                e->idx.offset = 1; /* just not zero! */
                duplicate_count_by_type[OBJ_BLOB]++;
-               truncate_pack(offset, &pack_file_ctx);
+               truncate_pack(&checkpoint);
 
        } else {
                e->depth = 0;
@@ -1577,7 +1568,8 @@ static int tree_content_set(
 static int tree_content_remove(
        struct tree_entry *root,
        const char *p,
-       struct tree_entry *backup_leaf)
+       struct tree_entry *backup_leaf,
+       int allow_root)
 {
        struct tree_content *t;
        const char *slash1;
@@ -1592,6 +1584,12 @@ static int tree_content_remove(
 
        if (!root->tree)
                load_tree(root);
+
+       if (!*p && allow_root) {
+               e = root;
+               goto del_entry;
+       }
+
        t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
@@ -1608,7 +1606,7 @@ static int tree_content_remove(
                                goto del_entry;
                        if (!e->tree)
                                load_tree(e);
-                       if (tree_content_remove(e, slash1 + 1, backup_leaf)) {
+                       if (tree_content_remove(e, slash1 + 1, backup_leaf, 0)) {
                                for (n = 0; n < e->tree->entry_count; n++) {
                                        if (e->tree->entries[n]->versions[1].mode) {
                                                hashclr(root->versions[1].sha1);
@@ -1638,7 +1636,8 @@ static int tree_content_remove(
 static int tree_content_get(
        struct tree_entry *root,
        const char *p,
-       struct tree_entry *leaf)
+       struct tree_entry *leaf,
+       int allow_root)
 {
        struct tree_content *t;
        const char *slash1;
@@ -1650,29 +1649,39 @@ static int tree_content_get(
                n = slash1 - p;
        else
                n = strlen(p);
+       if (!n && !allow_root)
+               die("Empty path component found in input");
 
        if (!root->tree)
                load_tree(root);
+
+       if (!n) {
+               e = root;
+               goto found_entry;
+       }
+
        t = root->tree;
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
                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))
-                                       leaf->tree = dup_tree_content(e->tree);
-                               else
-                                       leaf->tree = NULL;
-                               return 1;
-                       }
+                       if (!slash1)
+                               goto found_entry;
                        if (!S_ISDIR(e->versions[1].mode))
                                return 0;
                        if (!e->tree)
                                load_tree(e);
-                       return tree_content_get(e, slash1 + 1, leaf);
+                       return tree_content_get(e, slash1 + 1, leaf, 0);
                }
        }
        return 0;
+
+found_entry:
+       memcpy(leaf, e, sizeof(*leaf));
+       if (e->tree && is_null_sha1(e->versions[1].sha1))
+               leaf->tree = dup_tree_content(e->tree);
+       else
+               leaf->tree = NULL;
+       return 1;
 }
 
 static int update_branch(struct branch *b)
@@ -1685,7 +1694,7 @@ static int update_branch(struct branch *b)
                return 0;
        if (read_ref(b->name, old_sha1))
                hashclr(old_sha1);
-       lock = lock_any_ref_for_update(b->name, old_sha1, 0);
+       lock = lock_any_ref_for_update(b->name, old_sha1, 0, NULL);
        if (!lock)
                return error("Unable to lock %s", b->name);
        if (!force_update && !is_null_sha1(old_sha1)) {
@@ -1698,7 +1707,7 @@ static int update_branch(struct branch *b)
                        return error("Branch %s is missing commits.", b->name);
                }
 
-               if (!in_merge_bases(old_cmit, &new_cmit, 1)) {
+               if (!in_merge_bases(old_cmit, new_cmit)) {
                        unlock_ref(lock);
                        warning("Not updating %s"
                                " (new tip %s does not contain %s)",
@@ -1829,7 +1838,7 @@ static void read_marks(void)
                *end = 0;
                mark = strtoumax(line + 1, &end, 10);
                if (!mark || end == line + 1
-                       || *end != ' ' || get_sha1(end + 1, sha1))
+                       || *end != ' ' || get_sha1_hex(end + 1, sha1))
                        die("corrupt mark line: %s", line);
                e = find_object(sha1);
                if (!e) {
@@ -2173,6 +2182,11 @@ static uintmax_t do_change_note_fanout(
 
                if (tmp_hex_sha1_len == 40 && !get_sha1_hex(hex_sha1, sha1)) {
                        /* This is a note entry */
+                       if (fanout == 0xff) {
+                               /* Counting mode, no rename */
+                               num_notes++;
+                               continue;
+                       }
                        construct_path_with_fanout(hex_sha1, fanout, realpath);
                        if (!strcmp(fullpath, realpath)) {
                                /* Note entry is in correct location */
@@ -2181,7 +2195,7 @@ static uintmax_t do_change_note_fanout(
                        }
 
                        /* Rename fullpath to realpath */
-                       if (!tree_content_remove(orig_root, fullpath, &leaf))
+                       if (!tree_content_remove(orig_root, fullpath, &leaf, 0))
                                die("Failed to remove path %s", fullpath);
                        tree_content_set(orig_root, realpath,
                                leaf.versions[1].sha1,
@@ -2209,12 +2223,65 @@ static uintmax_t change_note_fanout(struct tree_entry *root,
        return do_change_note_fanout(root, root, hex_sha1, 0, path, 0, fanout);
 }
 
+/*
+ * Given a pointer into a string, parse a mark reference:
+ *
+ *   idnum ::= ':' bigint;
+ *
+ * Return the first character after the value in *endptr.
+ *
+ * Complain if the following character is not what is expected,
+ * either a space or end of the string.
+ */
+static uintmax_t parse_mark_ref(const char *p, char **endptr)
+{
+       uintmax_t mark;
+
+       assert(*p == ':');
+       p++;
+       mark = strtoumax(p, endptr, 10);
+       if (*endptr == p)
+               die("No value after ':' in mark: %s", command_buf.buf);
+       return mark;
+}
+
+/*
+ * Parse the mark reference, and complain if this is not the end of
+ * the string.
+ */
+static uintmax_t parse_mark_ref_eol(const char *p)
+{
+       char *end;
+       uintmax_t mark;
+
+       mark = parse_mark_ref(p, &end);
+       if (*end != '\0')
+               die("Garbage after mark: %s", command_buf.buf);
+       return mark;
+}
+
+/*
+ * Parse the mark reference, demanding a trailing space.  Return a
+ * pointer to the space.
+ */
+static uintmax_t parse_mark_ref_space(const char **p)
+{
+       uintmax_t mark;
+       char *end;
+
+       mark = parse_mark_ref(*p, &end);
+       if (*end != ' ')
+               die("Missing space after mark: %s", command_buf.buf);
+       *p = end;
+       return mark;
+}
+
 static void file_change_m(struct branch *b)
 {
        const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
        const char *endp;
-       struct object_entry *oe = oe;
+       struct object_entry *oe;
        unsigned char sha1[20];
        uint16_t mode, inline_data = 0;
 
@@ -2237,21 +2304,22 @@ static void file_change_m(struct branch *b)
        }
 
        if (*p == ':') {
-               char *x;
-               oe = find_mark(strtoumax(p + 1, &x, 10));
+               oe = find_mark(parse_mark_ref_space(&p));
                hashcpy(sha1, oe->idx.sha1);
-               p = x;
-       } else if (!prefixcmp(p, "inline")) {
+       } else if (!prefixcmp(p, "inline ")) {
                inline_data = 1;
-               p += 6;
+               oe = NULL; /* not used with inline_data, but makes gcc happy */
+               p += strlen("inline");  /* advance to space */
        } else {
                if (get_sha1_hex(p, sha1))
-                       die("Invalid SHA1: %s", command_buf.buf);
+                       die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(sha1);
                p += 40;
+               if (*p != ' ')
+                       die("Missing space after SHA1: %s", command_buf.buf);
        }
-       if (*p++ != ' ')
-               die("Missing space after SHA1: %s", command_buf.buf);
+       assert(*p == ' ');
+       p++;  /* skip space */
 
        strbuf_reset(&uq);
        if (!unquote_c_style(&uq, p, &endp)) {
@@ -2262,7 +2330,7 @@ static void file_change_m(struct branch *b)
 
        /* 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);
+               tree_content_remove(&b->branch_tree, p, NULL, 0);
                return;
        }
 
@@ -2323,7 +2391,7 @@ static void file_change_d(struct branch *b)
                        die("Garbage after path in: %s", command_buf.buf);
                p = uq.buf;
        }
-       tree_content_remove(&b->branch_tree, p, NULL);
+       tree_content_remove(&b->branch_tree, p, NULL, 1);
 }
 
 static void file_change_cr(struct branch *b, int rename)
@@ -2361,9 +2429,9 @@ static void file_change_cr(struct branch *b, int rename)
 
        memset(&leaf, 0, sizeof(leaf));
        if (rename)
-               tree_content_remove(&b->branch_tree, s, &leaf);
+               tree_content_remove(&b->branch_tree, s, &leaf, 1);
        else
-               tree_content_get(&b->branch_tree, s, &leaf);
+               tree_content_get(&b->branch_tree, s, &leaf, 1);
        if (!leaf.versions[1].mode)
                die("Path %s not in branch", s);
        if (!*d) {      /* C "path/to/subdir" "" */
@@ -2379,41 +2447,61 @@ static void file_change_cr(struct branch *b, int rename)
                leaf.tree);
 }
 
-static void note_change_n(struct branch *b, unsigned char old_fanout)
+static void note_change_n(struct branch *b, unsigned char *old_fanout)
 {
        const char *p = command_buf.buf + 2;
        static struct strbuf uq = STRBUF_INIT;
-       struct object_entry *oe = oe;
+       struct object_entry *oe;
        struct branch *s;
        unsigned char sha1[20], commit_sha1[20];
        char path[60];
        uint16_t inline_data = 0;
        unsigned char new_fanout;
 
+       /*
+        * When loading a branch, we don't traverse its tree to count the real
+        * number of notes (too expensive to do this for all non-note refs).
+        * This means that recently loaded notes refs might incorrectly have
+        * b->num_notes == 0, and consequently, old_fanout might be wrong.
+        *
+        * Fix this by traversing the tree and counting the number of notes
+        * when b->num_notes == 0. If the notes tree is truly empty, the
+        * calculation should not take long.
+        */
+       if (b->num_notes == 0 && *old_fanout == 0) {
+               /* Invoke change_note_fanout() in "counting mode". */
+               b->num_notes = change_note_fanout(&b->branch_tree, 0xff);
+               *old_fanout = convert_num_notes_to_fanout(b->num_notes);
+       }
+
+       /* Now parse the notemodify command. */
        /* <dataref> or 'inline' */
        if (*p == ':') {
-               char *x;
-               oe = find_mark(strtoumax(p + 1, &x, 10));
+               oe = find_mark(parse_mark_ref_space(&p));
                hashcpy(sha1, oe->idx.sha1);
-               p = x;
-       } else if (!prefixcmp(p, "inline")) {
+       } else if (!prefixcmp(p, "inline ")) {
                inline_data = 1;
-               p += 6;
+               oe = NULL; /* not used with inline_data, but makes gcc happy */
+               p += strlen("inline");  /* advance to space */
        } else {
                if (get_sha1_hex(p, sha1))
-                       die("Invalid SHA1: %s", command_buf.buf);
+                       die("Invalid dataref: %s", command_buf.buf);
                oe = find_object(sha1);
                p += 40;
+               if (*p != ' ')
+                       die("Missing space after SHA1: %s", command_buf.buf);
        }
-       if (*p++ != ' ')
-               die("Missing space after SHA1: %s", command_buf.buf);
+       assert(*p == ' ');
+       p++;  /* skip space */
 
-       /* <committish> */
+       /* <commit-ish> */
        s = lookup_branch(p);
        if (s) {
+               if (is_null_sha1(s->sha1))
+                       die("Can't add a note on empty branch.");
                hashcpy(commit_sha1, s->sha1);
        } else if (*p == ':') {
-               uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
+               uintmax_t commit_mark = parse_mark_ref_eol(p);
                struct object_entry *commit_oe = find_mark(commit_mark);
                if (commit_oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", commit_mark);
@@ -2448,8 +2536,8 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
                            typename(type), command_buf.buf);
        }
 
-       construct_path_with_fanout(sha1_to_hex(commit_sha1), old_fanout, path);
-       if (tree_content_remove(&b->branch_tree, path, NULL))
+       construct_path_with_fanout(sha1_to_hex(commit_sha1), *old_fanout, path);
+       if (tree_content_remove(&b->branch_tree, path, NULL, 0))
                b->num_notes--;
 
        if (is_null_sha1(sha1))
@@ -2520,7 +2608,7 @@ static int parse_from(struct branch *b)
                hashcpy(b->branch_tree.versions[0].sha1, t);
                hashcpy(b->branch_tree.versions[1].sha1, t);
        } else if (*from == ':') {
-               uintmax_t idnum = strtoumax(from + 1, NULL, 10);
+               uintmax_t idnum = parse_mark_ref_eol(from);
                struct object_entry *oe = find_mark(idnum);
                if (oe->type != OBJ_COMMIT)
                        die("Mark :%" PRIuMAX " not a commit", idnum);
@@ -2543,7 +2631,7 @@ static int parse_from(struct branch *b)
 
 static struct hash_list *parse_merge(unsigned int *count)
 {
-       struct hash_list *list = NULL, *n, *e = e;
+       struct hash_list *list = NULL, **tail = &list, *n;
        const char *from;
        struct branch *s;
 
@@ -2555,7 +2643,7 @@ static struct hash_list *parse_merge(unsigned int *count)
                if (s)
                        hashcpy(n->sha1, s->sha1);
                else if (*from == ':') {
-                       uintmax_t idnum = strtoumax(from + 1, NULL, 10);
+                       uintmax_t idnum = parse_mark_ref_eol(from);
                        struct object_entry *oe = find_mark(idnum);
                        if (oe->type != OBJ_COMMIT)
                                die("Mark :%" PRIuMAX " not a commit", idnum);
@@ -2571,11 +2659,9 @@ static struct hash_list *parse_merge(unsigned int *count)
                        die("Invalid ref name or SHA1 expression: %s", from);
 
                n->next = NULL;
-               if (list)
-                       e->next = n;
-               else
-                       list = n;
-               e = n;
+               *tail = n;
+               tail = &n->next;
+
                (*count)++;
                read_next_command();
        }
@@ -2635,7 +2721,7 @@ static void parse_new_commit(void)
                else if (!prefixcmp(command_buf.buf, "C "))
                        file_change_cr(b, 0);
                else if (!prefixcmp(command_buf.buf, "N "))
-                       note_change_n(b, prev_fanout);
+                       note_change_n(b, &prev_fanout);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else if (!prefixcmp(command_buf.buf, "ls "))
@@ -2697,7 +2783,7 @@ static void parse_new_tag(void)
        /* Obtain the new tag name from the rest of our command */
        sp = strchr(command_buf.buf, ' ') + 1;
        t = pool_alloc(sizeof(struct tag));
-       t->next_tag = NULL;
+       memset(t, 0, sizeof(struct tag));
        t->name = pool_strdup(sp);
        if (last_tag)
                last_tag->next_tag = t;
@@ -2712,11 +2798,13 @@ static void parse_new_tag(void)
        from = strchr(command_buf.buf, ' ') + 1;
        s = lookup_branch(from);
        if (s) {
+               if (is_null_sha1(s->sha1))
+                       die("Can't tag an empty branch.");
                hashcpy(sha1, s->sha1);
                type = OBJ_COMMIT;
        } else if (*from == ':') {
                struct object_entry *oe;
-               from_mark = strtoumax(from + 1, NULL, 10);
+               from_mark = parse_mark_ref_eol(from);
                oe = find_mark(from_mark);
                type = oe->type;
                hashcpy(sha1, oe->idx.sha1);
@@ -2848,18 +2936,13 @@ static void parse_cat_blob(void)
        /* 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);
+               oe = find_mark(parse_mark_ref_eol(p));
                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);
+                       die("Invalid dataref: %s", command_buf.buf);
                if (p[40])
                        die("Garbage after SHA1: %s", command_buf.buf);
                oe = find_object(sha1);
@@ -2890,7 +2973,7 @@ static struct object_entry *dereference(struct object_entry *oe,
        case OBJ_TAG:
                break;
        default:
-               die("Not a treeish: %s", command_buf.buf);
+               die("Not a tree-ish: %s", command_buf.buf);
        }
 
        if (oe->pack_id != MAX_PACK_ID) {       /* in a pack being written */
@@ -2925,17 +3008,13 @@ static struct object_entry *parse_treeish_dataref(const char **p)
        struct object_entry *e;
 
        if (**p == ':') {       /* <mark> */
-               char *endptr;
-               e = find_mark(strtoumax(*p + 1, &endptr, 10));
-               if (endptr == *p + 1)
-                       die("Invalid mark: %s", command_buf.buf);
+               e = find_mark(parse_mark_ref_space(p));
                if (!e)
                        die("Unknown mark: %s", command_buf.buf);
-               *p = endptr;
                hashcpy(sha1, e->idx.sha1);
        } else {        /* <sha1> */
                if (get_sha1_hex(*p, sha1))
-                       die("Invalid SHA1: %s", command_buf.buf);
+                       die("Invalid dataref: %s", command_buf.buf);
                e = find_object(sha1);
                *p += 40;
        }
@@ -2978,7 +3057,7 @@ static void parse_ls(struct branch *b)
        struct tree_entry *root = NULL;
        struct tree_entry leaf = {NULL};
 
-       /* ls SP (<treeish> SP)? <path> */
+       /* ls SP (<tree-ish> SP)? <path> */
        p = command_buf.buf + strlen("ls ");
        if (*p == '"') {
                if (!b)
@@ -2988,6 +3067,8 @@ static void parse_ls(struct branch *b)
                struct object_entry *e = parse_treeish_dataref(&p);
                root = new_tree_entry();
                hashcpy(root->versions[1].sha1, e->idx.sha1);
+               if (!is_null_sha1(root->versions[1].sha1))
+                       root->versions[1].mode = S_IFDIR;
                load_tree(root);
                if (*p++ != ' ')
                        die("Missing space after tree-ish: %s", command_buf.buf);
@@ -3002,7 +3083,7 @@ static void parse_ls(struct branch *b)
                        die("Garbage after path in: %s", command_buf.buf);
                p = uq.buf;
        }
-       tree_content_get(root, p, &leaf);
+       tree_content_get(root, p, &leaf, 1);
        /*
         * A directory in preparation would have a sha1 of zero
         * until it is saved.  Save, for simplicity.
@@ -3011,6 +3092,8 @@ static void parse_ls(struct branch *b)
                store_tree(&leaf);
 
        print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p);
+       if (leaf.tree)
+               release_tree_content_recursive(leaf.tree);
        if (!b || root != &b->branch_tree)
                release_tree_entry(root);
 }
@@ -3282,12 +3365,14 @@ static void parse_argv(void)
                read_marks();
 }
 
-int main(int argc, const char **argv)
+int main(int argc, char **argv)
 {
        unsigned int i;
 
        git_extract_argv0_path(argv[0]);
 
+       git_setup_gettext();
+
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(fast_import_usage);