Merge branch 'nd/log-show-linear-break'
authorJunio C Hamano <gitster@pobox.com>
Thu, 3 Apr 2014 19:38:11 +0000 (12:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 3 Apr 2014 19:38:11 +0000 (12:38 -0700)
Attempts to show where a single-strand-of-pearls break in "git log"
output.

* nd/log-show-linear-break:
log: add --show-linear-break to help see non-linear history
object.h: centralize object flag allocation

12 files changed:
1  2 
Documentation/rev-list-options.txt
bisect.c
builtin/blame.c
bundle.c
commit.c
fetch-pack.c
log-tree.c
object.h
revision.c
revision.h
sha1_name.c
upload-pack.c
index 9a3da3646e58f6155a6ff3397eddfd239703447a,1261ac0865a845191cf7d7493a077b3c4998a41f..b8139610b9b22b49fa9427c57154408451b2d333
@@@ -257,14 -257,6 +257,14 @@@ See also linkgit:git-reflog[1]
        Output excluded boundary commits. Boundary commits are
        prefixed with `-`.
  
 +ifdef::git-rev-list[]
 +--use-bitmap-index::
 +
 +      Try to speed up the traversal using the pack bitmap index (if
 +      one is available). Note that when traversing with `--objects`,
 +      trees and blobs will not have their associated path printed.
 +endif::git-rev-list[]
 +
  --
  
  History Simplification
@@@ -758,6 -750,13 +758,13 @@@ This enables parent rewriting, see 'His
  This implies the `--topo-order` option by default, but the
  `--date-order` option may also be specified.
  
+ --show-linear-break[=<barrier>]::
+       When --graph is not used, all history branches are flattened
+       which can make it hard to see that the two consecutive commits
+       do not belong to a linear branch. This option puts a barrier
+       in between them in that case. If `<barrier>` is specified, it
+       is the string that will be shown instead of the default one.
  ifdef::git-rev-list[]
  --count::
        Print a number stating how many commits would have been
diff --combined bisect.c
index 8448d27877e3d77fcae01b1354d192121e050528,48ccbf118f3bc2e2df837f8579717546a617064e..d6e851d783c3541eb21edd47170ce7a32e082e61
+++ b/bisect.c
@@@ -21,8 -21,7 +21,7 @@@ static const char *argv_checkout[] = {"
  static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
  static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL};
  
- /* bits #0-15 in revision.h */
+ /* Remember to update object flag allocation in object.h */
  #define COUNTED               (1u<<16)
  
  /*
@@@ -685,6 -684,7 +684,6 @@@ static void mark_expected_rev(char *bis
  
  static int bisect_checkout(char *bisect_rev_hex, int no_checkout)
  {
 -      int res;
  
        mark_expected_rev(bisect_rev_hex);
  
                        die("update-ref --no-deref HEAD failed on %s",
                            bisect_rev_hex);
        } else {
 +              int res;
                res = run_command_v_opt(argv_checkout, RUN_GIT_CMD);
                if (res)
                        exit(res);
diff --combined builtin/blame.c
index e5b5d71bad8653f4b9d19044c3c36dc2ab8b42f1,599fb580652039e7bc9cdedf193d58bfb4b3baaf..88cb7997274de6f9ab6f8a5944748334ce605f60
@@@ -74,7 -74,7 +74,7 @@@ static unsigned blame_copy_score
  #define BLAME_DEFAULT_MOVE_SCORE      20
  #define BLAME_DEFAULT_COPY_SCORE      40
  
- /* bits #0..7 in revision.h, #8..11 used for merge_bases() in commit.c */
+ /* Remember to update object flag allocation in object.h */
  #define METAINFO_SHOWN                (1u<<12)
  #define MORE_THAN_ONE_PATH    (1u<<13)
  
@@@ -197,6 -197,7 +197,6 @@@ static void drop_origin_blob(struct ori
   * scoreboard structure, sorted by the target line number.
   */
  struct blame_entry {
 -      struct blame_entry *prev;
        struct blame_entry *next;
  
        /* the first line of this group in the final image;
@@@ -255,6 -256,15 +255,6 @@@ struct scoreboard 
        int *lineno;
  };
  
 -static inline int same_suspect(struct origin *a, struct origin *b)
 -{
 -      if (a == b)
 -              return 1;
 -      if (a->commit != b->commit)
 -              return 0;
 -      return !strcmp(a->path, b->path);
 -}
 -
  static void sanity_check_refcnt(struct scoreboard *);
  
  /*
@@@ -267,11 -277,13 +267,11 @@@ static void coalesce(struct scoreboard 
        struct blame_entry *ent, *next;
  
        for (ent = sb->ent; ent && (next = ent->next); ent = next) {
 -              if (same_suspect(ent->suspect, next->suspect) &&
 +              if (ent->suspect == next->suspect &&
                    ent->guilty == next->guilty &&
                    ent->s_lno + ent->num_lines == next->s_lno) {
                        ent->num_lines += next->num_lines;
                        ent->next = next->next;
 -                      if (ent->next)
 -                              ent->next->prev = ent;
                        origin_decref(next->suspect);
                        free(next);
                        ent->score = 0;
@@@ -522,7 -534,7 +522,7 @@@ static void add_blame_entry(struct scor
                prev = ent;
  
        /* prev, if not NULL, is the last one that is below e */
 -      e->prev = prev;
 +
        if (prev) {
                e->next = prev->next;
                prev->next = e;
                e->next = sb->ent;
                sb->ent = e;
        }
 -      if (e->next)
 -              e->next->prev = e;
  }
  
  /*
   */
  static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
  {
 -      struct blame_entry *p, *n;
 +      struct blame_entry *n;
  
 -      p = dst->prev;
        n = dst->next;
        origin_incref(src->suspect);
        origin_decref(dst->suspect);
        memcpy(dst, src, sizeof(*src));
 -      dst->prev = p;
        dst->next = n;
        dst->score = 0;
  }
@@@ -726,7 -742,7 +726,7 @@@ static int find_last_in_target(struct s
        int last_in_target = -1;
  
        for (e = sb->ent; e; e = e->next) {
 -              if (e->guilty || !same_suspect(e->suspect, target))
 +              if (e->guilty || e->suspect != target)
                        continue;
                if (last_in_target < e->s_lno + e->num_lines)
                        last_in_target = e->s_lno + e->num_lines;
@@@ -746,7 -762,7 +746,7 @@@ static void blame_chunk(struct scoreboa
        struct blame_entry *e;
  
        for (e = sb->ent; e; e = e->next) {
 -              if (e->guilty || !same_suspect(e->suspect, target))
 +              if (e->guilty || e->suspect != target)
                        continue;
                if (same <= e->s_lno)
                        continue;
@@@ -923,6 -939,7 +923,6 @@@ static void find_copy_in_blob(struct sc
                              mmfile_t *file_p)
  {
        const char *cp;
 -      int cnt;
        mmfile_t file_o;
        struct handle_split_cb_data d;
  
         */
        cp = nth_line(sb, ent->lno);
        file_o.ptr = (char *) cp;
 -      cnt = ent->num_lines;
 -
 -      while (cnt && cp < sb->final_buf + sb->final_buf_size) {
 -              if (*cp++ == '\n')
 -                      cnt--;
 -      }
 -      file_o.size = cp - file_o.ptr;
 +      file_o.size = nth_line(sb, ent->lno + ent->num_lines) - cp;
  
        /*
         * file_o is a part of final image we are annotating.
@@@ -969,7 -992,7 +969,7 @@@ static int find_move_in_parent(struct s
        while (made_progress) {
                made_progress = 0;
                for (e = sb->ent; e; e = e->next) {
 -                      if (e->guilty || !same_suspect(e->suspect, target) ||
 +                      if (e->guilty || e->suspect != target ||
                            ent_score(sb, e) < blame_move_score)
                                continue;
                        find_copy_in_blob(sb, e, parent, split, &file_p);
@@@ -1004,14 -1027,14 +1004,14 @@@ static struct blame_list *setup_blame_l
  
        for (e = sb->ent, num_ents = 0; e; e = e->next)
                if (!e->scanned && !e->guilty &&
 -                  same_suspect(e->suspect, target) &&
 +                  e->suspect == target &&
                    min_score < ent_score(sb, e))
                        num_ents++;
        if (num_ents) {
                blame_list = xcalloc(num_ents, sizeof(struct blame_list));
                for (e = sb->ent, i = 0; e; e = e->next)
                        if (!e->scanned && !e->guilty &&
 -                          same_suspect(e->suspect, target) &&
 +                          e->suspect == target &&
                            min_score < ent_score(sb, e))
                                blame_list[i++].ent = e;
        }
@@@ -1155,7 -1178,7 +1155,7 @@@ static void pass_whole_blame(struct sco
                origin->file.ptr = NULL;
        }
        for (e = sb->ent; e; e = e->next) {
 -              if (!same_suspect(e->suspect, origin))
 +              if (e->suspect != origin)
                        continue;
                origin_incref(porigin);
                origin_decref(e->suspect);
@@@ -1544,7 -1567,7 +1544,7 @@@ static void assign_blame(struct scorebo
  
                /* Take responsibility for the remaining entries */
                for (ent = sb->ent; ent; ent = ent->next)
 -                      if (same_suspect(ent->suspect, suspect))
 +                      if (ent->suspect == suspect)
                                found_guilty_entry(ent);
                origin_decref(suspect);
  
@@@ -1557,14 -1580,14 +1557,14 @@@ static const char *format_time(unsigne
                               int show_raw_time)
  {
        static char time_buf[128];
 -      const char *time_str;
 -      int time_len;
 -      int tz;
  
        if (show_raw_time) {
                snprintf(time_buf, sizeof(time_buf), "%lu %s", time, tz_str);
        }
        else {
 +              const char *time_str;
 +              int time_len;
 +              int tz;
                tz = atoi(tz_str);
                time_str = show_date(time, tz, blame_date_mode);
                time_len = strlen(time_str);
@@@ -1749,41 -1772,25 +1749,41 @@@ static int prepare_lines(struct scorebo
  {
        const char *buf = sb->final_buf;
        unsigned long len = sb->final_buf_size;
 -      int num = 0, incomplete = 0, bol = 1;
 +      const char *end = buf + len;
 +      const char *p;
 +      int *lineno;
 +      int num = 0, incomplete = 0;
  
 -      if (len && buf[len-1] != '\n')
 -              incomplete++; /* incomplete line at the end */
 -      while (len--) {
 -              if (bol) {
 -                      sb->lineno = xrealloc(sb->lineno,
 -                                            sizeof(int *) * (num + 1));
 -                      sb->lineno[num] = buf - sb->final_buf;
 -                      bol = 0;
 -              }
 -              if (*buf++ == '\n') {
 +      for (p = buf;;) {
 +              p = memchr(p, '\n', end - p);
 +              if (p) {
 +                      p++;
                        num++;
 -                      bol = 1;
 +                      continue;
                }
 +              break;
        }
 -      sb->lineno = xrealloc(sb->lineno,
 -                            sizeof(int *) * (num + incomplete + 1));
 -      sb->lineno[num + incomplete] = buf - sb->final_buf;
 +
 +      if (len && end[-1] != '\n')
 +              incomplete++; /* incomplete line at the end */
 +
 +      sb->lineno = xmalloc(sizeof(*sb->lineno) * (num + incomplete + 1));
 +      lineno = sb->lineno;
 +
 +      *lineno++ = 0;
 +      for (p = buf;;) {
 +              p = memchr(p, '\n', end - p);
 +              if (p) {
 +                      p++;
 +                      *lineno++ = p - buf;
 +                      continue;
 +              }
 +              break;
 +      }
 +
 +      if (incomplete)
 +              *lineno++ = len;
 +
        sb->num_lines = num + incomplete;
        return sb->num_lines;
  }
@@@ -2495,6 -2502,8 +2495,6 @@@ parse_done
                ent->suspect = o;
                ent->s_lno = bottom;
                ent->next = next;
 -              if (next)
 -                      next->prev = ent;
                origin_incref(o);
        }
        origin_decref(o);
diff --combined bundle.c
index a85e0e4532ad10934b3d9c510db9e26b338f14f1,85d4a6abe156621007bed50e894442fdfdf0ee19..1222952075fe4c9f3c25373123d8793b7c1692ef
+++ b/bundle.c
@@@ -14,8 -14,12 +14,8 @@@ static const char bundle_signature[] = 
  static void add_to_ref_list(const unsigned char *sha1, const char *name,
                struct ref_list *list)
  {
 -      if (list->nr + 1 >= list->alloc) {
 -              list->alloc = alloc_nr(list->nr + 1);
 -              list->list = xrealloc(list->list,
 -                              list->alloc * sizeof(list->list[0]));
 -      }
 -      memcpy(list->list[list->nr].sha1, sha1, 20);
 +      ALLOC_GROW(list->list, list->nr + 1, list->alloc);
 +      hashcpy(list->list[list->nr].sha1, sha1);
        list->list[list->nr].name = xstrdup(name);
        list->nr++;
  }
@@@ -120,6 -124,7 +120,7 @@@ static int list_refs(struct ref_list *r
        return 0;
  }
  
+ /* Remember to update object flag allocation in object.h */
  #define PREREQ_MARK (1u<<16)
  
  int verify_bundle(struct bundle_header *header, int verbose)
diff --combined commit.c
index 0f28902bc351fef9d621cfe290f887921ec779c4,96278c2fa509a154ec1216750809261af3690296..f4793316a21fe5f3ee1932f7e24cf4ab0920b04c
+++ b/commit.c
@@@ -10,7 -10,6 +10,7 @@@
  #include "mergesort.h"
  #include "commit-slab.h"
  #include "prio-queue.h"
 +#include "sha1-lookup.h"
  
  static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
  
@@@ -115,16 -114,23 +115,16 @@@ static unsigned long parse_commit_date(
  static struct commit_graft **commit_graft;
  static int commit_graft_alloc, commit_graft_nr;
  
 +static const unsigned char *commit_graft_sha1_access(size_t index, void *table)
 +{
 +      struct commit_graft **commit_graft_table = table;
 +      return commit_graft_table[index]->sha1;
 +}
 +
  static int commit_graft_pos(const unsigned char *sha1)
  {
 -      int lo, hi;
 -      lo = 0;
 -      hi = commit_graft_nr;
 -      while (lo < hi) {
 -              int mi = (lo + hi) / 2;
 -              struct commit_graft *graft = commit_graft[mi];
 -              int cmp = hashcmp(sha1, graft->sha1);
 -              if (!cmp)
 -                      return mi;
 -              if (cmp < 0)
 -                      hi = mi;
 -              else
 -                      lo = mi + 1;
 -      }
 -      return -lo - 1;
 +      return sha1_pos(sha1, commit_graft, commit_graft_nr,
 +                      commit_graft_sha1_access);
  }
  
  int register_commit_graft(struct commit_graft *graft, int ignore_dups)
                return 1;
        }
        pos = -pos - 1;
 -      if (commit_graft_alloc <= ++commit_graft_nr) {
 -              commit_graft_alloc = alloc_nr(commit_graft_alloc);
 -              commit_graft = xrealloc(commit_graft,
 -                                      sizeof(*commit_graft) *
 -                                      commit_graft_alloc);
 -      }
 +      ALLOC_GROW(commit_graft, commit_graft_nr + 1, commit_graft_alloc);
 +      commit_graft_nr++;
        if (pos < commit_graft_nr)
                memmove(commit_graft + pos + 1,
                        commit_graft + pos,
@@@ -538,7 -548,7 +538,7 @@@ define_commit_slab(author_date_slab, un
  static void record_author_date(struct author_date_slab *author_date,
                               struct commit *commit)
  {
 -      const char *buf, *line_end;
 +      const char *buf, *line_end, *ident_line;
        char *buffer = NULL;
        struct ident_split ident;
        char *date_end;
             buf;
             buf = line_end + 1) {
                line_end = strchrnul(buf, '\n');
 -              if (!starts_with(buf, "author ")) {
 +              ident_line = skip_prefix(buf, "author ");
 +              if (!ident_line) {
                        if (!line_end[0] || line_end[1] == '\n')
                                return; /* end of header */
                        continue;
                }
                if (split_ident_line(&ident,
 -                                   buf + strlen("author "),
 -                                   line_end - (buf + strlen("author "))) ||
 +                                   ident_line, line_end - ident_line) ||
                    !ident.date_begin || !ident.date_end)
                        goto fail_exit; /* malformed "author" line */
                break;
@@@ -721,7 -731,7 +721,7 @@@ void sort_in_topological_order(struct c
  
  /* merge-base stuff */
  
- /* bits #0..15 in revision.h */
+ /* Remember to update object flag allocation in object.h */
  #define PARENT1               (1u<<16)
  #define PARENT2               (1u<<17)
  #define STALE         (1u<<18)
@@@ -1183,8 -1193,10 +1183,8 @@@ static void parse_gpg_output(struct sig
        for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
                const char *found, *next;
  
 -              if (starts_with(buf, sigcheck_gpg_status[i].check + 1)) {
 -                      /* At the very beginning of the buffer */
 -                      found = buf + strlen(sigcheck_gpg_status[i].check + 1);
 -              } else {
 +              found = skip_prefix(buf, sigcheck_gpg_status[i].check + 1);
 +              if (!found) {
                        found = strstr(buf, sigcheck_gpg_status[i].check);
                        if (!found)
                                continue;
diff --combined fetch-pack.c
index 90d47da8a902f24bb6a7ae63654a104626751b1e,a7388bee9ff06db90a73ea601dfefcd13dc58020..eeee2bb7e08a21dfe091a30c8378da6d488228f5
@@@ -26,6 -26,7 +26,7 @@@ static int agent_supported
  static struct lock_file shallow_lock;
  static const char *alternate_shallow_file;
  
+ /* Remember to update object flag allocation in object.h */
  #define COMPLETE      (1U << 0)
  #define COMMON                (1U << 1)
  #define COMMON_REF    (1U << 2)
@@@ -439,8 -440,7 +440,8 @@@ done
        }
        strbuf_release(&req_buf);
  
 -      consume_shallow_list(args, fd[0]);
 +      if (!got_ready || !no_done)
 +              consume_shallow_list(args, fd[0]);
        while (flushes || multi_ack) {
                int ack = get_ack(fd[0], result_sha1);
                if (ack) {
@@@ -507,7 -507,7 +508,7 @@@ static void filter_refs(struct fetch_pa
                next = ref->next;
  
                if (!memcmp(ref->name, "refs/", 5) &&
 -                  check_refname_format(ref->name + 5, 0))
 +                  check_refname_format(ref->name, 0))
                        ; /* trash */
                else {
                        while (i < nr_sought) {
@@@ -948,6 -948,17 +949,6 @@@ static void update_shallow(struct fetch
        if (!si->shallow || !si->shallow->nr)
                return;
  
 -      if (alternate_shallow_file) {
 -              /*
 -               * The temporary shallow file is only useful for
 -               * index-pack and unpack-objects because it may
 -               * contain more roots than we want. Delete it.
 -               */
 -              if (*alternate_shallow_file)
 -                      unlink(alternate_shallow_file);
 -              free((char *)alternate_shallow_file);
 -      }
 -
        if (args->cloning) {
                /*
                 * remote is shallow, but this is a clone, there are
diff --combined log-tree.c
index 5ce217d5eba33a54419ac8b29223cc16aa1f905e,17862f6cfbd5c1f5faca4c11110351432f30b2d6..cf2f86c86632c6df8862fc065299e6cc35aa4829
@@@ -100,7 -100,7 +100,7 @@@ static int add_ref_decoration(const cha
  
        if (starts_with(refname, "refs/replace/")) {
                unsigned char original_sha1[20];
 -              if (!read_replace_refs)
 +              if (!check_replace_refs)
                        return 0;
                if (get_sha1_hex(refname + 13, original_sha1)) {
                        warning("invalid replace ref %s", refname);
@@@ -805,12 -805,16 +805,16 @@@ int log_tree_commit(struct rev_info *op
        if (opt->line_level_traverse)
                return line_log_print(opt, commit);
  
+       if (opt->track_linear && !opt->linear && !opt->reverse_output_stage)
+               printf("\n%s\n", opt->break_bar);
        shown = log_tree_diff(opt, commit, &log);
        if (!shown && opt->loginfo && opt->always_show_header) {
                log.parent = NULL;
                show_log(opt);
                shown = 1;
        }
+       if (opt->track_linear && !opt->linear && opt->reverse_output_stage)
+               printf("\n%s\n", opt->break_bar);
        opt->loginfo = NULL;
        maybe_flush_or_die(stdout, "stdout");
        return shown;
diff --combined object.h
index 732bf4d7e7012ce56045ec52404ac92b077305a6,ce011428ffd842efd5b4e6da45c45774c4d4779b..6e12f2c7f4582b5121a4be18be5fedaecd30f38f
+++ b/object.h
@@@ -26,6 -26,19 +26,19 @@@ struct object_array 
  #define OBJECT_ARRAY_INIT { 0, 0, NULL }
  
  #define TYPE_BITS   3
+ /*
+  * object flag allocation:
+  * revision.h:      0---------10                                26
+  * fetch-pack.c:    0---4
+  * walker.c:        0-2
+  * upload-pack.c:               11----------------19
+  * builtin/blame.c:               12-13
+  * bisect.c:                               16
+  * bundle.c:                               16
+  * http-push.c:                            16-----19
+  * commit.c:                               16-----19
+  * sha1_name.c:                                     20
+  */
  #define FLAG_BITS  27
  
  /*
@@@ -42,14 -55,7 +55,14 @@@ struct object 
  extern const char *typename(unsigned int type);
  extern int type_from_string(const char *str);
  
 +/*
 + * Return the current number of buckets in the object hashmap.
 + */
  extern unsigned int get_max_object_index(void);
 +
 +/*
 + * Return the object from the specified bucket in the object hashmap.
 + */
  extern struct object *get_indexed_object(unsigned int);
  
  /*
diff --combined revision.c
index 85085501f66b02a9cff9b404f34c0925270cea7f,05b76c796600aaad092fbaa0ac8c036a5a5b537c..794a8835c09142a170df40adf48d56500daae403
@@@ -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);
  }
@@@ -774,10 -779,6 +774,10 @@@ static int add_parents_to_list(struct r
                return 0;
        commit->object.flags |= ADDED;
  
 +      if (revs->include_check &&
 +          !revs->include_check(commit, revs->include_check_data))
 +              return 0;
 +
        /*
         * If the commit is uninteresting, don't try to
         * prune parents - we want the maximal uninteresting
@@@ -1186,7 -1187,7 +1186,7 @@@ int ref_excluded(struct string_list *re
        if (!ref_excludes)
                return 0;
        for_each_string_list_item(item, ref_excludes) {
 -              if (!fnmatch(item->string, path, 0))
 +              if (!wildmatch(item->string, path, 0, NULL))
                        return 1;
        }
        return 0;
@@@ -1395,7 -1396,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;
@@@ -1575,10 -1576,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)
@@@ -1837,6 -1832,14 +1837,14 @@@ static int handle_revision_opt(struct r
                revs->notes_opt.use_default_notes = 1;
        } else if (!strcmp(arg, "--show-signature")) {
                revs->show_signature = 1;
+       } 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;
                        unkv[(*unkc)++] = arg;
                return opts;
        }
+       if (revs->graph && revs->track_linear)
+               die("--show-linear-break and --graph are incompatible");
  
        return 1;
  }
@@@ -2902,6 -2907,27 +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)
                        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);
@@@ -3107,14 -3135,23 +3140,23 @@@ struct commit *get_revision(struct rev_
                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 1eb94c1548c019a4d41cb42af805918afbc237bf,b8f4bc9a763e6ae7b1d8884897157e4b0bf8bd28..d9907dd4606f0b5e7b022b7a54d70ac3ad85358b
@@@ -7,6 -7,7 +7,7 @@@
  #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)
@@@ -18,7 -19,8 +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
@@@ -137,6 -139,10 +139,10 @@@ struct rev_info 
                        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;
  
        unsigned long min_age;
        int min_parents;
        int max_parents;
 +      int (*include_check)(struct commit *, void *);
 +      void *include_check_data;
  
        /* diff info for patches and for paths limiting */
        struct diff_options diffopt;
  
        /* 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);
diff --combined sha1_name.c
index 6fca8692d2dd8875281fcb8379cc93805e647cea,23bb821d25fb8055ea9b7687305a790339908171..2b6322fad064845f4c782286fe9764f6b958f30d
@@@ -430,7 -430,7 +430,7 @@@ static inline int upstream_mark(const c
  }
  
  static int get_sha1_1(const char *name, int len, unsigned char *sha1, unsigned lookup_flags);
 -static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf);
 +static int interpret_nth_prior_checkout(const char *name, int namelen, struct strbuf *buf);
  
  static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
  {
                struct strbuf buf = STRBUF_INIT;
                int detached;
  
 -              if (interpret_nth_prior_checkout(str, &buf) > 0) {
 +              if (interpret_nth_prior_checkout(str, len, &buf) > 0) {
                        detached = (buf.len == 40 && !get_sha1_hex(buf.buf, sha1));
                        strbuf_release(&buf);
                        if (detached)
@@@ -819,6 -819,8 +819,8 @@@ static int get_sha1_1(const char *name
   * For future extension, ':/!' is reserved. If you want to match a message
   * beginning with a '!', you have to repeat the exclamation mark.
   */
+ /* Remember to update object flag allocation in object.h */
  #define ONELINE_SEEN (1u<<20)
  
  static int handle_one_ref(const char *path,
@@@ -929,8 -931,7 +931,8 @@@ static int grab_nth_branch_switch(unsig
   * Parse @{-N} syntax, return the number of characters parsed
   * if successful; otherwise signal an error with negative value.
   */
 -static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
 +static int interpret_nth_prior_checkout(const char *name, int namelen,
 +                                      struct strbuf *buf)
  {
        long nth;
        int retval;
        const char *brace;
        char *num_end;
  
 +      if (namelen < 4)
 +              return -1;
        if (name[0] != '@' || name[1] != '{' || name[2] != '-')
                return -1;
 -      brace = strchr(name, '}');
 +      brace = memchr(name, '}', namelen);
        if (!brace)
                return -1;
        nth = strtol(name + 3, &num_end, 10);
@@@ -1015,7 -1014,7 +1017,7 @@@ static int interpret_empty_at(const cha
                return -1;
  
        /* make sure it's a single @, or @@{.*}, not @foo */
 -      next = strchr(name + len + 1, '@');
 +      next = memchr(name + len + 1, '@', namelen - len - 1);
        if (next && next[1] != '{')
                return -1;
        if (!next)
@@@ -1049,57 -1048,6 +1051,57 @@@ static int reinterpret(const char *name
        return ret - used + len;
  }
  
 +static void set_shortened_ref(struct strbuf *buf, const char *ref)
 +{
 +      char *s = shorten_unambiguous_ref(ref, 0);
 +      strbuf_reset(buf);
 +      strbuf_addstr(buf, s);
 +      free(s);
 +}
 +
 +static const char *get_upstream_branch(const char *branch_buf, int len)
 +{
 +      char *branch = xstrndup(branch_buf, len);
 +      struct branch *upstream = branch_get(*branch ? branch : NULL);
 +
 +      /*
 +       * Upstream can be NULL only if branch refers to HEAD and HEAD
 +       * points to something different than a branch.
 +       */
 +      if (!upstream)
 +              die(_("HEAD does not point to a branch"));
 +      if (!upstream->merge || !upstream->merge[0]->dst) {
 +              if (!ref_exists(upstream->refname))
 +                      die(_("No such branch: '%s'"), branch);
 +              if (!upstream->merge) {
 +                      die(_("No upstream configured for branch '%s'"),
 +                              upstream->name);
 +              }
 +              die(
 +                      _("Upstream branch '%s' not stored as a remote-tracking branch"),
 +                      upstream->merge[0]->src);
 +      }
 +      free(branch);
 +
 +      return upstream->merge[0]->dst;
 +}
 +
 +static int interpret_upstream_mark(const char *name, int namelen,
 +                                 int at, struct strbuf *buf)
 +{
 +      int len;
 +
 +      len = upstream_mark(name + at, namelen - at);
 +      if (!len)
 +              return -1;
 +
 +      if (memchr(name, ':', at))
 +              return -1;
 +
 +      set_shortened_ref(buf, get_upstream_branch(name, at));
 +      return len + at;
 +}
 +
  /*
   * This reads short-hand syntax that not only evaluates to a commit
   * object name, but also can act as if the end user spelled the name
   */
  int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
  {
 -      char *cp;
 -      struct branch *upstream;
 -      int len = interpret_nth_prior_checkout(name, buf);
 -      int tmp_len;
 +      char *at;
 +      const char *start;
 +      int len = interpret_nth_prior_checkout(name, namelen, buf);
  
        if (!namelen)
                namelen = strlen(name);
                        return reinterpret(name, namelen, len, buf);
        }
  
 -      cp = strchr(name, '@');
 -      if (!cp)
 -              return -1;
 -
 -      len = interpret_empty_at(name, namelen, cp - name, buf);
 -      if (len > 0)
 -              return reinterpret(name, namelen, len, buf);
 +      for (start = name;
 +           (at = memchr(start, '@', namelen - (start - name)));
 +           start = at + 1) {
  
 -      tmp_len = upstream_mark(cp, namelen - (cp - name));
 -      if (!tmp_len)
 -              return -1;
 +              len = interpret_empty_at(name, namelen, at - name, buf);
 +              if (len > 0)
 +                      return reinterpret(name, namelen, len, buf);
  
 -      len = cp + tmp_len - name;
 -      cp = xstrndup(name, cp - name);
 -      upstream = branch_get(*cp ? cp : NULL);
 -      /*
 -       * Upstream can be NULL only if cp refers to HEAD and HEAD
 -       * points to something different than a branch.
 -       */
 -      if (!upstream)
 -              die(_("HEAD does not point to a branch"));
 -      if (!upstream->merge || !upstream->merge[0]->dst) {
 -              if (!ref_exists(upstream->refname))
 -                      die(_("No such branch: '%s'"), cp);
 -              if (!upstream->merge) {
 -                      die(_("No upstream configured for branch '%s'"),
 -                              upstream->name);
 -              }
 -              die(
 -                      _("Upstream branch '%s' not stored as a remote-tracking branch"),
 -                      upstream->merge[0]->src);
 +              len = interpret_upstream_mark(name, namelen, at - name, buf);
 +              if (len > 0)
 +                      return len;
        }
 -      free(cp);
 -      cp = shorten_unambiguous_ref(upstream->merge[0]->dst, 0);
 -      strbuf_reset(buf);
 -      strbuf_addstr(buf, cp);
 -      free(cp);
 -      return len;
 +
 +      return -1;
  }
  
  int strbuf_branchname(struct strbuf *sb, const char *name)
diff --combined upload-pack.c
index 286a9ed3ea960d07c267f5e2992e63589361f5e9,96013b35eca329ab134dc869ecf3824eea9e76e0..01de944a0a23f752364de51d7f5a5be6480575e8
@@@ -17,7 -17,7 +17,7 @@@
  
  static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>";
  
- /* bits #0..7 in revision.h, #8..10 in commit.c */
+ /* Remember to update object flag allocation in object.h */
  #define THEY_HAVE     (1u << 11)
  #define OUR_REF               (1u << 12)
  #define WANTED                (1u << 13)
@@@ -70,14 -70,6 +70,14 @@@ static ssize_t send_client_data(int fd
        return sz;
  }
  
 +static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
 +{
 +      FILE *fp = cb_data;
 +      if (graft->nr_parent == -1)
 +              fprintf(fp, "--shallow %s\n", sha1_to_hex(graft->sha1));
 +      return 0;
 +}
 +
  static void create_pack_file(void)
  {
        struct child_process pack_objects;
        const char *argv[12];
        int i, arg = 0;
        FILE *pipe_fd;
 -      char *shallow_file = NULL;
  
        if (shallow_nr) {
 -              shallow_file = setup_temporary_shallow(NULL);
                argv[arg++] = "--shallow-file";
 -              argv[arg++] = shallow_file;
 +              argv[arg++] = "";
        }
        argv[arg++] = "pack-objects";
        argv[arg++] = "--revs";
  
        pipe_fd = xfdopen(pack_objects.in, "w");
  
 +      if (shallow_nr)
 +              for_each_commit_graft(write_one_shallow, pipe_fd);
 +
        for (i = 0; i < want_obj.nr; i++)
                fprintf(pipe_fd, "%s\n",
                        sha1_to_hex(want_obj.objects[i].item->sha1));
                error("git upload-pack: git-pack-objects died with error.");
                goto fail;
        }
 -      if (shallow_file) {
 -              if (*shallow_file)
 -                      unlink(shallow_file);
 -              free(shallow_file);
 -      }
  
        /* flush the data */
        if (0 <= buffered) {
@@@ -800,7 -796,7 +800,7 @@@ int main(int argc, char **argv
  
        packet_trace_identity("upload-pack");
        git_extract_argv0_path(argv[0]);
 -      read_replace_refs = 0;
 +      check_replace_refs = 0;
  
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];