Merge branch 'jk/pack-bitmap'
authorJunio C Hamano <gitster@pobox.com>
Tue, 8 Apr 2014 19:00:32 +0000 (12:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 8 Apr 2014 19:00:33 +0000 (12:00 -0700)
* jk/pack-bitmap:
pack-objects: do not reuse packfiles without --delta-base-offset
add `ignore_missing_links` mode to revwalk

1  2 
builtin/pack-objects.c
list-objects.c
revision.c
revision.h
diff --combined builtin/pack-objects.c
index c1f43108710d950d44b783ce838d65e776c17a4d,c7590cb65acd96b6af208388193972cd1d24f7ee..de36c60ca11d248013c2de42d196f1aff8a2937b
@@@ -708,7 -708,7 +708,7 @@@ static struct object_entry **compute_wr
  static off_t write_reused_pack(struct sha1file *f)
  {
        unsigned char buffer[8192];
 -      off_t to_write;
 +      off_t to_write, total;
        int fd;
  
        if (!is_pack_valid(reuse_packfile))
        if (reuse_packfile_offset < 0)
                reuse_packfile_offset = reuse_packfile->pack_size - 20;
  
 -      to_write = reuse_packfile_offset - sizeof(struct pack_header);
 +      total = to_write = reuse_packfile_offset - sizeof(struct pack_header);
  
        while (to_write) {
                int read_pack = xread(fd, buffer, sizeof(buffer));
  
                sha1write(f, buffer, read_pack);
                to_write -= read_pack;
 +
 +              /*
 +               * We don't know the actual number of objects written,
 +               * only how many bytes written, how many bytes total, and
 +               * how many objects total. So we can fake it by pretending all
 +               * objects we are writing are the same size. This gives us a
 +               * smooth progress meter, and at the end it matches the true
 +               * answer.
 +               */
 +              written = reuse_packfile_objects *
 +                              (((double)(total - to_write)) / total);
 +              display_progress(progress_state, written);
        }
  
        close(fd);
 -      written += reuse_packfile_objects;
 +      written = reuse_packfile_objects;
 +      display_progress(progress_state, written);
        return reuse_packfile_offset - sizeof(struct pack_header);
  }
  
@@@ -768,7 -755,7 +768,7 @@@ static void write_pack_file(void
        struct object_entry **write_order;
  
        if (progress > pack_to_stdout)
 -              progress_state = start_progress("Writing objects", nr_result);
 +              progress_state = start_progress(_("Writing objects"), nr_result);
        written_list = xmalloc(to_pack.nr_objects * sizeof(*written_list));
        write_order = compute_write_order();
  
                        f = create_tmp_packfile(&pack_tmp_name);
  
                offset = write_pack_header(f, nr_remaining);
 -              if (!offset)
 -                      die_errno("unable to write pack header");
  
                if (reuse_packfile) {
                        off_t packfile_size;
  
                if (!pack_to_stdout) {
                        struct stat st;
 -                      char tmpname[PATH_MAX];
 +                      struct strbuf tmpname = STRBUF_INIT;
  
                        /*
                         * Packs are runtime accessed in their mtime
                                utb.modtime = --last_mtime;
                                if (utime(pack_tmp_name, &utb) < 0)
                                        warning("failed utime() on %s: %s",
 -                                              tmpname, strerror(errno));
 +                                              pack_tmp_name, strerror(errno));
                        }
  
 -                      /* Enough space for "-<sha-1>.pack"? */
 -                      if (sizeof(tmpname) <= strlen(base_name) + 50)
 -                              die("pack base name '%s' too long", base_name);
 -                      snprintf(tmpname, sizeof(tmpname), "%s-", base_name);
 +                      strbuf_addf(&tmpname, "%s-", base_name);
  
                        if (write_bitmap_index) {
                                bitmap_writer_set_checksum(sha1);
                                bitmap_writer_build_type_index(written_list, nr_written);
                        }
  
 -                      finish_tmp_packfile(tmpname, pack_tmp_name,
 +                      finish_tmp_packfile(&tmpname, pack_tmp_name,
                                            written_list, nr_written,
                                            &pack_idx_opts, sha1);
  
                        if (write_bitmap_index) {
 -                              char *end_of_name_prefix = strrchr(tmpname, 0);
 -                              sprintf(end_of_name_prefix, "%s.bitmap", sha1_to_hex(sha1));
 +                              strbuf_addf(&tmpname, "%s.bitmap", sha1_to_hex(sha1));
  
                                stop_progress(&progress_state);
  
                                bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1);
                                bitmap_writer_build(&to_pack);
                                bitmap_writer_finish(written_list, nr_written,
 -                                                   tmpname, write_bitmap_options);
 +                                                   tmpname.buf, write_bitmap_options);
                                write_bitmap_index = 0;
                        }
  
 +                      strbuf_release(&tmpname);
                        free(pack_tmp_name);
                        puts(sha1_to_hex(sha1));
                }
@@@ -1035,7 -1027,7 +1035,7 @@@ static int add_object_entry(const unsig
                            exclude, name && no_try_delta(name),
                            index_pos, found_pack, found_offset);
  
 -      display_progress(progress_state, to_pack.nr_objects);
 +      display_progress(progress_state, nr_result);
        return 1;
  }
  
@@@ -1051,7 -1043,7 +1051,7 @@@ static int add_object_entry_from_bitmap
  
        create_object_entry(sha1, type, name_hash, 0, 0, index_pos, pack, offset);
  
 -      display_progress(progress_state, to_pack.nr_objects);
 +      display_progress(progress_state, nr_result);
        return 1;
  }
  
@@@ -1076,7 -1068,7 +1076,7 @@@ static int pbase_tree_cache_ix_incr(in
  static struct pbase_tree {
        struct pbase_tree *next;
        /* This is a phony "cache" entry; we are not
 -       * going to evict it nor find it through _get()
 +       * going to evict it or find it through _get()
         * mechanism -- this is for the toplevel node that
         * would almost always change with any commit.
         */
@@@ -1233,9 -1225,12 +1233,9 @@@ static int check_pbase_path(unsigned ha
        if (0 <= pos)
                return 1;
        pos = -pos - 1;
 -      if (done_pbase_paths_alloc <= done_pbase_paths_num) {
 -              done_pbase_paths_alloc = alloc_nr(done_pbase_paths_alloc);
 -              done_pbase_paths = xrealloc(done_pbase_paths,
 -                                          done_pbase_paths_alloc *
 -                                          sizeof(unsigned));
 -      }
 +      ALLOC_GROW(done_pbase_paths,
 +                 done_pbase_paths_num + 1,
 +                 done_pbase_paths_alloc);
        done_pbase_paths_num++;
        if (pos < done_pbase_paths_num)
                memmove(done_pbase_paths + pos + 1,
@@@ -2104,7 -2099,7 +2104,7 @@@ static int add_ref_tag(const char *path
  {
        unsigned char peeled[20];
  
 -      if (!prefixcmp(path, "refs/tags/") && /* is a tag? */
 +      if (starts_with(path, "refs/tags/") && /* is a tag? */
            !peel_ref(path, peeled)        && /* peelable? */
            packlist_find(&to_pack, peeled, NULL))      /* object packed? */
                add_object_entry(sha1, OBJ_TAG, NULL, 0);
@@@ -2171,7 -2166,7 +2171,7 @@@ static void prepare_pack(int window, in
        if (nr_deltas && n > 1) {
                unsigned nr_done = 0;
                if (progress)
 -                      progress_state = start_progress("Compressing objects",
 +                      progress_state = start_progress(_("Compressing objects"),
                                                        nr_deltas);
                qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
                ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
@@@ -2439,18 -2434,34 +2439,29 @@@ static void loosen_unused_packed_object
        }
  }
  
+ /*
+  * This tracks any options which a reader of the pack might
+  * not understand, and which would therefore prevent blind reuse
+  * of what we have on disk.
+  */
+ static int pack_options_allow_reuse(void)
+ {
+       return allow_ofs_delta;
+ }
  static int get_object_list_from_bitmap(struct rev_info *revs)
  {
        if (prepare_bitmap_walk(revs) < 0)
                return -1;
  
-       if (!reuse_partial_packfile_from_bitmap(
+       if (pack_options_allow_reuse() &&
+           !reuse_partial_packfile_from_bitmap(
                        &reuse_packfile,
                        &reuse_packfile_objects,
                        &reuse_packfile_offset)) {
                assert(reuse_packfile_objects);
                nr_result += reuse_packfile_objects;
 -
 -              if (progress) {
 -                      fprintf(stderr, "Reusing existing pack: %d, done.\n",
 -                              reuse_packfile_objects);
 -                      fflush(stderr);
 -              }
 +              display_progress(progress_state, nr_result);
        }
  
        traverse_bitmap_commit_list(&add_object_entry_from_bitmap);
@@@ -2467,9 -2478,6 +2478,9 @@@ static void get_object_list(int ac, con
        save_commit_buffer = 0;
        setup_revisions(ac, av, &revs, NULL);
  
 +      /* make sure shallows are read */
 +      is_repository_shallow();
 +
        while (fgets(line, sizeof(line), stdin) != NULL) {
                int len = strlen(line);
                if (len && line[len - 1] == '\n')
                                write_bitmap_index = 0;
                                continue;
                        }
 +                      if (starts_with(line, "--shallow ")) {
 +                              unsigned char sha1[20];
 +                              if (get_sha1_hex(line + 10, sha1))
 +                                      die("not an SHA-1 '%s'", line + 10);
 +                              register_shallow(sha1);
 +                              continue;
 +                      }
                        die("not a rev '%s'", line);
                }
                if (handle_revision_arg(line, &revs, flags, REVARG_CANNOT_BE_FILENAME))
@@@ -2634,7 -2635,7 +2645,7 @@@ int cmd_pack_objects(int argc, const ch
                OPT_END(),
        };
  
 -      read_replace_refs = 0;
 +      check_replace_refs = 0;
  
        reset_pack_idx_option(&pack_idx_opts);
        git_config(git_pack_config, NULL);
        prepare_packed_git();
  
        if (progress)
 -              progress_state = start_progress("Counting objects", 0);
 +              progress_state = start_progress(_("Counting objects"), 0);
        if (!use_internal_rev_list)
                read_object_list_from_stdin();
        else {
diff --combined list-objects.c
index 206816fa9c47eb419775bcf2005f9cd5144bc297,b2db6ba0d3dcae4af3e738600de3275d4c03a079..3595ee7a22c4d6099ea9b995ffe1c61c60bb77cd
@@@ -81,8 -81,11 +81,11 @@@ static void process_tree(struct rev_inf
                die("bad tree object");
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
-       if (parse_tree(tree) < 0)
+       if (parse_tree(tree) < 0) {
+               if (revs->ignore_missing_links)
+                       return;
                die("bad tree object %s", sha1_to_hex(obj->sha1));
+       }
        obj->flags |= SEEN;
        show(obj, path, name, cb_data);
        me.up = path;
@@@ -162,17 -165,15 +165,17 @@@ void mark_edges_uninteresting(struct re
                }
                mark_edge_parents_uninteresting(commit, revs, show_edge);
        }
 -      for (i = 0; i < revs->cmdline.nr; i++) {
 -              struct object *obj = revs->cmdline.rev[i].item;
 -              struct commit *commit = (struct commit *)obj;
 -              if (obj->type != OBJ_COMMIT || !(obj->flags & UNINTERESTING))
 -                      continue;
 -              mark_tree_uninteresting(commit->tree);
 -              if (revs->edge_hint && !(obj->flags & SHOWN)) {
 -                      obj->flags |= SHOWN;
 -                      show_edge(commit);
 +      if (revs->edge_hint) {
 +              for (i = 0; i < revs->cmdline.nr; i++) {
 +                      struct object *obj = revs->cmdline.rev[i].item;
 +                      struct commit *commit = (struct commit *)obj;
 +                      if (obj->type != OBJ_COMMIT || !(obj->flags & UNINTERESTING))
 +                              continue;
 +                      mark_tree_uninteresting(commit->tree);
 +                      if (!(obj->flags & SHOWN)) {
 +                              obj->flags |= SHOWN;
 +                              show_edge(commit);
 +                      }
                }
        }
  }
diff --combined revision.c
index 794a8835c09142a170df40adf48d56500daae403,520b3e7df6cc195e369059b3ad134780580c3e60..71e233742331a435edeadacc7c6a52226bb18b7a
@@@ -16,7 -16,6 +16,7 @@@
  #include "line-log.h"
  #include "mailmap.h"
  #include "commit-slab.h"
 +#include "dir.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -105,12 -104,17 +105,12 @@@ static void mark_blob_uninteresting(str
        blob->object.flags |= UNINTERESTING;
  }
  
 -void mark_tree_uninteresting(struct tree *tree)
 +static void mark_tree_contents_uninteresting(struct tree *tree)
  {
        struct tree_desc desc;
        struct name_entry entry;
        struct object *obj = &tree->object;
  
 -      if (!tree)
 -              return;
 -      if (obj->flags & UNINTERESTING)
 -              return;
 -      obj->flags |= UNINTERESTING;
        if (!has_sha1_file(obj->sha1))
                return;
        if (parse_tree(tree) < 0)
        free_tree_buffer(tree);
  }
  
 +void mark_tree_uninteresting(struct tree *tree)
 +{
 +      struct object *obj = &tree->object;
 +
 +      if (!tree)
 +              return;
 +      if (obj->flags & UNINTERESTING)
 +              return;
 +      obj->flags |= UNINTERESTING;
 +      mark_tree_contents_uninteresting(tree);
 +}
 +
  void mark_parents_uninteresting(struct commit *commit)
  {
        struct commit_list *parents = NULL, *l;
@@@ -284,7 -276,6 +284,7 @@@ static struct commit *handle_commit(str
                                return NULL;
                        die("bad object %s", sha1_to_hex(tag->tagged->sha1));
                }
 +              object->flags |= flags;
        }
  
        /*
                if (parse_commit(commit) < 0)
                        die("unable to parse commit %s", name);
                if (flags & UNINTERESTING) {
 -                      commit->object.flags |= UNINTERESTING;
                        mark_parents_uninteresting(commit);
                        revs->limited = 1;
                }
                if (!revs->tree_objects)
                        return NULL;
                if (flags & UNINTERESTING) {
 -                      mark_tree_uninteresting(tree);
 +                      mark_tree_contents_uninteresting(tree);
                        return NULL;
                }
                add_pending_object(revs, object, "");
         * Blob object? You know the drill by now..
         */
        if (object->type == OBJ_BLOB) {
 -              struct blob *blob = (struct blob *)object;
                if (!revs->blob_objects)
                        return NULL;
 -              if (flags & UNINTERESTING) {
 -                      mark_blob_uninteresting(blob);
 +              if (flags & UNINTERESTING)
                        return NULL;
 -              }
                add_pending_object(revs, object, "");
                return NULL;
        }
@@@ -497,14 -492,24 +497,14 @@@ static int rev_compare_tree(struct rev_
  static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit)
  {
        int retval;
 -      void *tree;
 -      unsigned long size;
 -      struct tree_desc empty, real;
        struct tree *t1 = commit->tree;
  
        if (!t1)
                return 0;
  
 -      tree = read_object_with_reference(t1->object.sha1, tree_type, &size, NULL);
 -      if (!tree)
 -              return 0;
 -      init_tree_desc(&real, tree, size);
 -      init_tree_desc(&empty, "", 0);
 -
        tree_difference = REV_TREE_SAME;
        DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES);
 -      retval = diff_tree(&empty, &real, "", &revs->pruning);
 -      free(tree);
 +      retval = diff_tree_sha1(NULL, t1->object.sha1, "", &revs->pruning);
  
        return retval >= 0 && (tree_difference == REV_TREE_SAME);
  }
@@@ -1179,28 -1184,11 +1179,28 @@@ struct all_refs_cb 
        const char *name_for_errormsg;
  };
  
 +int ref_excluded(struct string_list *ref_excludes, const char *path)
 +{
 +      struct string_list_item *item;
 +
 +      if (!ref_excludes)
 +              return 0;
 +      for_each_string_list_item(item, ref_excludes) {
 +              if (!wildmatch(item->string, path, 0, NULL))
 +                      return 1;
 +      }
 +      return 0;
 +}
 +
  static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
  {
        struct all_refs_cb *cb = cb_data;
 -      struct object *object = get_reference(cb->all_revs, path, sha1,
 -                                            cb->all_flags);
 +      struct object *object;
 +
 +      if (ref_excluded(cb->all_revs->ref_excludes, path))
 +          return 0;
 +
 +      object = get_reference(cb->all_revs, path, sha1, cb->all_flags);
        add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
        add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
        return 0;
@@@ -1213,24 -1201,6 +1213,24 @@@ static void init_all_refs_cb(struct all
        cb->all_flags = flags;
  }
  
 +void clear_ref_exclusion(struct string_list **ref_excludes_p)
 +{
 +      if (*ref_excludes_p) {
 +              string_list_clear(*ref_excludes_p, 0);
 +              free(*ref_excludes_p);
 +      }
 +      *ref_excludes_p = NULL;
 +}
 +
 +void add_ref_exclusion(struct string_list **ref_excludes_p, const char *exclude)
 +{
 +      if (!*ref_excludes_p) {
 +              *ref_excludes_p = xcalloc(1, sizeof(**ref_excludes_p));
 +              (*ref_excludes_p)->strdup_strings = 1;
 +      }
 +      string_list_append(*ref_excludes_p, exclude);
 +}
 +
  static void handle_refs(const char *submodule, struct rev_info *revs, unsigned flags,
                int (*for_each)(const char *, each_ref_fn, void *))
  {
@@@ -1395,7 -1365,7 +1395,7 @@@ static void prepare_show_merge(struct r
                const struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
 -              if (ce_path_match(ce, &revs->prune_data)) {
 +              if (ce_path_match(ce, &revs->prune_data, NULL)) {
                        prune_num++;
                        prune = xrealloc(prune, sizeof(*prune) * prune_num);
                        prune[prune_num-2] = ce->name;
                        i++;
        }
        free_pathspec(&revs->prune_data);
 -      parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC, 0, "", prune);
 +      parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL,
 +                     PATHSPEC_PREFER_FULL | PATHSPEC_LITERAL_PATH, "", prune);
        revs->limited = 1;
  }
  
@@@ -1454,40 -1423,26 +1454,40 @@@ int handle_revision_arg(const char *arg
                }
                if (!get_sha1_committish(this, from_sha1) &&
                    !get_sha1_committish(next, sha1)) {
 -                      struct commit *a, *b;
 -                      struct commit_list *exclude;
 -
 -                      a = lookup_commit_reference(from_sha1);
 -                      b = lookup_commit_reference(sha1);
 -                      if (!a || !b) {
 -                              if (revs->ignore_missing)
 -                                      return 0;
 -                              die(symmetric ?
 -                                  "Invalid symmetric difference expression %s...%s" :
 -                                  "Invalid revision range %s..%s",
 -                                  arg, next);
 -                      }
 +                      struct object *a_obj, *b_obj;
  
                        if (!cant_be_filename) {
                                *dotdot = '.';
                                verify_non_filename(revs->prefix, arg);
                        }
  
 -                      if (symmetric) {
 +                      a_obj = parse_object(from_sha1);
 +                      b_obj = parse_object(sha1);
 +                      if (!a_obj || !b_obj) {
 +                      missing:
 +                              if (revs->ignore_missing)
 +                                      return 0;
 +                              die(symmetric
 +                                  ? "Invalid symmetric difference expression %s"
 +                                  : "Invalid revision range %s", arg);
 +                      }
 +
 +                      if (!symmetric) {
 +                              /* just A..B */
 +                              a_flags = flags_exclude;
 +                      } else {
 +                              /* A...B -- find merge bases between the two */
 +                              struct commit *a, *b;
 +                              struct commit_list *exclude;
 +
 +                              a = (a_obj->type == OBJ_COMMIT
 +                                   ? (struct commit *)a_obj
 +                                   : lookup_commit_reference(a_obj->sha1));
 +                              b = (b_obj->type == OBJ_COMMIT
 +                                   ? (struct commit *)b_obj
 +                                   : lookup_commit_reference(b_obj->sha1));
 +                              if (!a || !b)
 +                                      goto missing;
                                exclude = get_merge_bases(a, b, 1);
                                add_rev_cmdline_list(revs, exclude,
                                                     REV_CMD_MERGE_BASE,
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
 +
                                a_flags = flags | SYMMETRIC_LEFT;
 -                      } else
 -                              a_flags = flags_exclude;
 -                      a->object.flags |= a_flags;
 -                      b->object.flags |= flags;
 -                      add_rev_cmdline(revs, &a->object, this,
 +                      }
 +
 +                      a_obj->flags |= a_flags;
 +                      b_obj->flags |= flags;
 +                      add_rev_cmdline(revs, a_obj, this,
                                        REV_CMD_LEFT, a_flags);
 -                      add_rev_cmdline(revs, &b->object, next,
 +                      add_rev_cmdline(revs, b_obj, next,
                                        REV_CMD_RIGHT, flags);
 -                      add_pending_object(revs, &a->object, this);
 -                      add_pending_object(revs, &b->object, next);
 +                      add_pending_object(revs, a_obj, this);
 +                      add_pending_object(revs, b_obj, next);
                        return 0;
                }
                *dotdot = '.';
@@@ -1553,7 -1507,7 +1553,7 @@@ struct cmdline_pathspec 
  static void append_prune_data(struct cmdline_pathspec *prune, const char **av)
  {
        while (*av) {
 -              ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
 +              ALLOC_GROW(prune->path, prune->nr + 1, prune->alloc);
                prune->path[prune->nr++] = *(av++);
        }
  }
@@@ -1565,7 -1519,7 +1565,7 @@@ static void read_pathspec_from_stdin(st
                int len = sb->len;
                if (len && sb->buf[len - 1] == '\n')
                        sb->buf[--len] = '\0';
 -              ALLOC_GROW(prune->path, prune->nr+1, prune->alloc);
 +              ALLOC_GROW(prune->path, prune->nr + 1, prune->alloc);
                prune->path[prune->nr++] = xstrdup(sb->buf);
        }
  }
@@@ -1575,10 -1529,6 +1575,10 @@@ static void read_revisions_from_stdin(s
  {
        struct strbuf sb;
        int seen_dashdash = 0;
 +      int save_warning;
 +
 +      save_warning = warn_on_object_refname_ambiguity;
 +      warn_on_object_refname_ambiguity = 0;
  
        strbuf_init(&sb, 1000);
        while (strbuf_getwholeline(&sb, stdin, '\n') != EOF) {
        }
        if (seen_dashdash)
                read_pathspec_from_stdin(revs, &sb, prune);
 +
        strbuf_release(&sb);
 +      warn_on_object_refname_ambiguity = save_warning;
  }
  
  static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
@@@ -1632,9 -1580,9 +1632,9 @@@ static int handle_revision_opt(struct r
            !strcmp(arg, "--tags") || !strcmp(arg, "--remotes") ||
            !strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
            !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
 -          !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") ||
 -          !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") ||
 -          !prefixcmp(arg, "--remotes=") || !prefixcmp(arg, "--no-walk="))
 +          !strcmp(arg, "--bisect") || starts_with(arg, "--glob=") ||
 +          starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
 +          starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
        {
                unkv[(*unkc)++] = arg;
                return 1;
                revs->max_count = atoi(argv[1]);
                revs->no_walk = 0;
                return 2;
 -      } else if (!prefixcmp(arg, "-n")) {
 +      } else if (starts_with(arg, "-n")) {
                revs->max_count = atoi(arg + 2);
                revs->no_walk = 0;
        } else if ((argcount = parse_long_opt("max-age", argv, &optarg))) {
        } else if (!strcmp(arg, "--author-date-order")) {
                revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
                revs->topo_order = 1;
 -      } else if (!prefixcmp(arg, "--early-output")) {
 +      } else if (starts_with(arg, "--early-output")) {
                int count = 100;
                switch (arg[14]) {
                case '=':
                revs->min_parents = 2;
        } else if (!strcmp(arg, "--no-merges")) {
                revs->max_parents = 1;
 -      } else if (!prefixcmp(arg, "--min-parents=")) {
 +      } else if (starts_with(arg, "--min-parents=")) {
                revs->min_parents = atoi(arg+14);
 -      } else if (!prefixcmp(arg, "--no-min-parents")) {
 +      } else if (starts_with(arg, "--no-min-parents")) {
                revs->min_parents = 0;
 -      } else if (!prefixcmp(arg, "--max-parents=")) {
 +      } else if (starts_with(arg, "--max-parents=")) {
                revs->max_parents = atoi(arg+14);
 -      } else if (!prefixcmp(arg, "--no-max-parents")) {
 +      } else if (starts_with(arg, "--no-max-parents")) {
                revs->max_parents = -1;
        } else if (!strcmp(arg, "--boundary")) {
                revs->boundary = 1;
                revs->verify_objects = 1;
        } else if (!strcmp(arg, "--unpacked")) {
                revs->unpacked = 1;
 -      } else if (!prefixcmp(arg, "--unpacked=")) {
 +      } else if (starts_with(arg, "--unpacked=")) {
                die("--unpacked=<packfile> no longer supported.");
        } else if (!strcmp(arg, "-r")) {
                revs->diff = 1;
                revs->verbose_header = 1;
                revs->pretty_given = 1;
                get_commit_format(arg+8, revs);
 -      } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) {
 +      } else if (starts_with(arg, "--pretty=") || starts_with(arg, "--format=")) {
                /*
                 * Detached form ("--pretty X" as opposed to "--pretty=X")
                 * not allowed, since the argument is optional.
                revs->notes_opt.use_default_notes = 1;
        } else if (!strcmp(arg, "--show-signature")) {
                revs->show_signature = 1;
 -      } else if (!prefixcmp(arg, "--show-notes=") ||
 -                 !prefixcmp(arg, "--notes=")) {
 +      } else if (!strcmp(arg, "--show-linear-break") ||
 +                 starts_with(arg, "--show-linear-break=")) {
 +              if (starts_with(arg, "--show-linear-break="))
 +                      revs->break_bar = xstrdup(arg + 20);
 +              else
 +                      revs->break_bar = "                    ..........";
 +              revs->track_linear = 1;
 +              revs->track_first_time = 1;
 +      } else if (starts_with(arg, "--show-notes=") ||
 +                 starts_with(arg, "--notes=")) {
                struct strbuf buf = STRBUF_INIT;
                revs->show_notes = 1;
                revs->show_notes_given = 1;
 -              if (!prefixcmp(arg, "--show-notes")) {
 +              if (starts_with(arg, "--show-notes")) {
                        if (revs->notes_opt.use_default_notes < 0)
                                revs->notes_opt.use_default_notes = 1;
                        strbuf_addstr(&buf, arg+13);
                revs->abbrev = 0;
        } else if (!strcmp(arg, "--abbrev")) {
                revs->abbrev = DEFAULT_ABBREV;
 -      } else if (!prefixcmp(arg, "--abbrev=")) {
 +      } else if (starts_with(arg, "--abbrev=")) {
                revs->abbrev = strtoul(arg + 9, NULL, 10);
                if (revs->abbrev < MINIMUM_ABBREV)
                        revs->abbrev = MINIMUM_ABBREV;
                        unkv[(*unkc)++] = arg;
                return opts;
        }
 +      if (revs->graph && revs->track_linear)
 +              die("--show-linear-break and --graph are incompatible");
  
        return 1;
  }
@@@ -2019,51 -1957,40 +2019,51 @@@ static int handle_revision_pseudo_opt(c
        if (!strcmp(arg, "--all")) {
                handle_refs(submodule, revs, *flags, for_each_ref_submodule);
                handle_refs(submodule, revs, *flags, head_ref_submodule);
 +              clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--branches")) {
                handle_refs(submodule, revs, *flags, for_each_branch_ref_submodule);
 +              clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--bisect")) {
                handle_refs(submodule, revs, *flags, for_each_bad_bisect_ref);
                handle_refs(submodule, revs, *flags ^ (UNINTERESTING | BOTTOM), for_each_good_bisect_ref);
                revs->bisect = 1;
        } else if (!strcmp(arg, "--tags")) {
                handle_refs(submodule, revs, *flags, for_each_tag_ref_submodule);
 +              clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--remotes")) {
                handle_refs(submodule, revs, *flags, for_each_remote_ref_submodule);
 +              clear_ref_exclusion(&revs->ref_excludes);
        } else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref(handle_one_ref, optarg, &cb);
 +              clear_ref_exclusion(&revs->ref_excludes);
 +              return argcount;
 +      } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
 +              add_ref_exclusion(&revs->ref_excludes, optarg);
                return argcount;
 -      } else if (!prefixcmp(arg, "--branches=")) {
 +      } else if (starts_with(arg, "--branches=")) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, arg + 11, "refs/heads/", &cb);
 -      } else if (!prefixcmp(arg, "--tags=")) {
 +              clear_ref_exclusion(&revs->ref_excludes);
 +      } else if (starts_with(arg, "--tags=")) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, arg + 7, "refs/tags/", &cb);
 -      } else if (!prefixcmp(arg, "--remotes=")) {
 +              clear_ref_exclusion(&revs->ref_excludes);
 +      } else if (starts_with(arg, "--remotes=")) {
                struct all_refs_cb cb;
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, arg + 10, "refs/remotes/", &cb);
 +              clear_ref_exclusion(&revs->ref_excludes);
        } else if (!strcmp(arg, "--reflog")) {
                handle_reflog(revs, *flags);
        } else if (!strcmp(arg, "--not")) {
                *flags ^= UNINTERESTING | BOTTOM;
        } else if (!strcmp(arg, "--no-walk")) {
                revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
 -      } else if (!prefixcmp(arg, "--no-walk=")) {
 +      } else if (starts_with(arg, "--no-walk=")) {
                /*
                 * Detached form ("--no-walk X" as opposed to "--no-walk=X")
                 * not allowed, since the argument is optional.
@@@ -2195,7 -2122,7 +2195,7 @@@ int setup_revisions(int argc, const cha
                 *      call init_pathspec() to set revs->prune_data here.
                 * }
                 */
 -              ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
 +              ALLOC_GROW(prune_data.path, prune_data.nr + 1, prune_data.alloc);
                prune_data.path[prune_data.nr++] = NULL;
                parse_pathspec(&revs->prune_data, 0, 0,
                               revs->prefix, prune_data.path);
@@@ -2912,27 -2839,6 +2912,27 @@@ enum commit_action simplify_commit(stru
        return action;
  }
  
 +static void track_linear(struct rev_info *revs, struct commit *commit)
 +{
 +      if (revs->track_first_time) {
 +              revs->linear = 1;
 +              revs->track_first_time = 0;
 +      } else {
 +              struct commit_list *p;
 +              for (p = revs->previous_parents; p; p = p->next)
 +                      if (p->item == NULL || /* first commit */
 +                          !hashcmp(p->item->object.sha1, commit->object.sha1))
 +                              break;
 +              revs->linear = p != NULL;
 +      }
 +      if (revs->reverse) {
 +              if (revs->linear)
 +                      commit->object.flags |= TRACK_LINEAR;
 +      }
 +      free_commit_list(revs->previous_parents);
 +      revs->previous_parents = copy_commit_list(commit->parents);
 +}
 +
  static struct commit *get_revision_1(struct rev_info *revs)
  {
        if (!revs->commits)
                        if (revs->max_age != -1 &&
                            (commit->date < revs->max_age))
                                continue;
-                       if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0)
-                               die("Failed to traverse parents of commit %s",
-                                   sha1_to_hex(commit->object.sha1));
+                       if (add_parents_to_list(revs, commit, &revs->commits, NULL) < 0) {
+                               if (!revs->ignore_missing_links)
+                                       die("Failed to traverse parents of commit %s",
+                                               sha1_to_hex(commit->object.sha1));
+                       }
                }
  
                switch (simplify_commit(revs, commit)) {
                        die("Failed to simplify parents of commit %s",
                            sha1_to_hex(commit->object.sha1));
                default:
 +                      if (revs->track_linear)
 +                              track_linear(revs, commit);
                        return commit;
                }
        } while (revs->commits);
@@@ -3071,7 -2977,7 +3073,7 @@@ static struct commit *get_revision_inte
        if (revs->max_count) {
                c = get_revision_1(revs);
                if (c) {
 -                      while (0 < revs->skip_count) {
 +                      while (revs->skip_count > 0) {
                                revs->skip_count--;
                                c = get_revision_1(revs);
                                if (!c)
        if (c)
                c->object.flags |= SHOWN;
  
 -      if (!revs->boundary) {
 +      if (!revs->boundary)
                return c;
 -      }
  
        if (!c) {
                /*
@@@ -3133,30 -3040,22 +3135,30 @@@ struct commit *get_revision(struct rev_
  
        if (revs->reverse) {
                reversed = NULL;
 -              while ((c = get_revision_internal(revs))) {
 +              while ((c = get_revision_internal(revs)))
                        commit_list_insert(c, &reversed);
 -              }
                revs->commits = reversed;
                revs->reverse = 0;
                revs->reverse_output_stage = 1;
        }
  
 -      if (revs->reverse_output_stage)
 -              return pop_commit(&revs->commits);
 +      if (revs->reverse_output_stage) {
 +              c = pop_commit(&revs->commits);
 +              if (revs->track_linear)
 +                      revs->linear = !!(c && c->object.flags & TRACK_LINEAR);
 +              return c;
 +      }
  
        c = get_revision_internal(revs);
        if (c && revs->graph)
                graph_update(revs->graph, c);
 -      if (!c)
 +      if (!c) {
                free_saved_parents(revs);
 +              if (revs->previous_parents) {
 +                      free_commit_list(revs->previous_parents);
 +                      revs->previous_parents = NULL;
 +              }
 +      }
        return c;
  }
  
diff --combined revision.h
index d9907dd4606f0b5e7b022b7a54d70ac3ad85358b,4f051d2ea2568509065e3080d23f8932c8516b21..a6205307cf3bb2364b8642b0a4857c5b0ae9b5a9
@@@ -5,9 -5,7 +5,9 @@@
  #include "grep.h"
  #include "notes.h"
  #include "commit.h"
 +#include "diff.h"
  
 +/* Remember to update object flag allocation in object.h */
  #define SEEN          (1u<<0)
  #define UNINTERESTING   (1u<<1)
  #define TREESAME      (1u<<2)
@@@ -19,8 -17,7 +19,8 @@@
  #define SYMMETRIC_LEFT        (1u<<8)
  #define PATCHSAME     (1u<<9)
  #define BOTTOM                (1u<<10)
 -#define ALL_REV_FLAGS ((1u<<11)-1)
 +#define TRACK_LINEAR  (1u<<26)
 +#define ALL_REV_FLAGS (((1u<<11)-1) | TRACK_LINEAR)
  
  #define DECORATE_SHORT_REFS   1
  #define DECORATE_FULL_REFS    2
@@@ -63,9 -60,6 +63,9 @@@ struct rev_info 
        /* The end-points specified by the end user */
        struct rev_cmdline_info cmdline;
  
 +      /* excluding from --branches, --refs, etc. expansion */
 +      struct string_list *ref_excludes;
 +
        /* Basic information */
        const char *prefix;
        const char *def;
@@@ -75,7 -69,8 +75,8 @@@
        enum rev_sort_order sort_order;
  
        unsigned int    early_output:1,
-                       ignore_missing:1;
+                       ignore_missing:1,
+                       ignore_missing_links:1;
  
        /* Traversal flags */
        unsigned int    dense:1,
                        preserve_subject:1;
        unsigned int    disable_stdin:1;
        unsigned int    leak_pending:1;
 +      /* --show-linear-break */
 +      unsigned int    track_linear:1,
 +                      track_first_time:1,
 +                      linear:1;
  
        enum date_mode date_mode;
  
  
        /* copies of the parent lists, for --full-diff display */
        struct saved_parents *saved_parents_slab;
 +
 +      struct commit_list *previous_parents;
 +      const char *break_bar;
  };
  
 +extern int ref_excluded(struct string_list *, const char *path);
 +void clear_ref_exclusion(struct string_list **);
 +void add_ref_exclusion(struct string_list **, const char *exclude);
 +
 +
  #define REV_TREE_SAME         0
  #define REV_TREE_NEW          1       /* Only new files */
  #define REV_TREE_OLD          2       /* Only files removed */