Merge branch 'jk/name-decoration-alloc'
authorJunio C Hamano <gitster@pobox.com>
Thu, 11 Sep 2014 17:33:36 +0000 (10:33 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 11 Sep 2014 17:33:36 +0000 (10:33 -0700)
The API to allocate the structure to keep track of commit
decoration was cumbersome to use, inviting lazy code to
overallocate memory.

* jk/name-decoration-alloc:
log-tree: use FLEX_ARRAY in name_decoration
log-tree: make name_decoration hash static
log-tree: make add_name_decoration a public function

1  2 
bisect.c
commit.h
log-tree.c
revision.c
diff --combined bisect.c
index d6e851d783c3541eb21edd47170ce7a32e082e61,59f1aeebe339df0ce5df8efadc5e4855f1108df8..df09cbc8cabe9dcb87a0023445babcf14c3d7691
+++ b/bisect.c
@@@ -21,7 -21,8 +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)
  
  /*
@@@ -215,11 -216,12 +215,12 @@@ static struct commit_list *best_bisecti
        }
        qsort(array, cnt, sizeof(*array), compare_commit_dist);
        for (p = list, i = 0; i < cnt; i++) {
-               struct name_decoration *r = xmalloc(sizeof(*r) + 100);
+               char buf[100]; /* enough for dist=%d */
                struct object *obj = &(array[i].commit->object);
  
-               sprintf(r->name, "dist=%d", array[i].distance);
-               r->next = add_decoration(&name_decoration, obj, r);
+               snprintf(buf, sizeof(buf), "dist=%d", array[i].distance);
+               add_name_decoration(DECORATION_NONE, buf, obj);
                p->item = array[i].commit;
                p = p->next;
        }
@@@ -684,6 -686,7 +685,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 commit.h
index aa8c3ca50af42375caf954e309d76731b4a9295e,99b9e78209d391b548f0e59239b63299bffaf300..a401ddfbc4e5c2e5e4a9d7aeedebc34e6103c9fa
+++ b/commit.h
@@@ -20,19 -20,32 +20,31 @@@ struct commit 
        unsigned long date;
        struct commit_list *parents;
        struct tree *tree;
 -      char *buffer;
  };
  
  extern int save_commit_buffer;
  extern const char *commit_type;
  
  /* While we can decorate any object with a name, it's only used for commits.. */
- extern struct decoration name_decoration;
  struct name_decoration {
        struct name_decoration *next;
        int type;
-       char name[1];
+       char name[FLEX_ARRAY];
  };
  
+ enum decoration_type {
+       DECORATION_NONE = 0,
+       DECORATION_REF_LOCAL,
+       DECORATION_REF_REMOTE,
+       DECORATION_REF_TAG,
+       DECORATION_REF_STASH,
+       DECORATION_REF_HEAD,
+       DECORATION_GRAFTED,
+ };
+ void add_name_decoration(enum decoration_type type, const char *name, struct object *obj);
+ const struct name_decoration *get_name_decoration(const struct object *obj);
  struct commit *lookup_commit(const unsigned char *sha1);
  struct commit *lookup_commit_reference(const unsigned char *sha1);
  struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
@@@ -50,44 -63,6 +62,44 @@@ int parse_commit_buffer(struct commit *
  int parse_commit(struct commit *item);
  void parse_commit_or_die(struct commit *item);
  
 +/*
 + * Associate an object buffer with the commit. The ownership of the
 + * memory is handed over to the commit, and must be free()-able.
 + */
 +void set_commit_buffer(struct commit *, void *buffer, unsigned long size);
 +
 +/*
 + * Get any cached object buffer associated with the commit. Returns NULL
 + * if none. The resulting memory should not be freed.
 + */
 +const void *get_cached_commit_buffer(const struct commit *, unsigned long *size);
 +
 +/*
 + * Get the commit's object contents, either from cache or by reading the object
 + * from disk. The resulting memory should not be modified, and must be given
 + * to unuse_commit_buffer when the caller is done.
 + */
 +const void *get_commit_buffer(const struct commit *, unsigned long *size);
 +
 +/*
 + * Tell the commit subsytem that we are done with a particular commit buffer.
 + * The commit and buffer should be the input and return value, respectively,
 + * from an earlier call to get_commit_buffer.  The buffer may or may not be
 + * freed by this call; callers should not access the memory afterwards.
 + */
 +void unuse_commit_buffer(const struct commit *, const void *buffer);
 +
 +/*
 + * Free any cached object buffer associated with the commit.
 + */
 +void free_commit_buffer(struct commit *);
 +
 +/*
 + * Disassociate any cached object buffer from the commit, but do not free it.
 + * The buffer (or NULL, if none) is returned.
 + */
 +const void *detach_commit_buffer(struct commit *, unsigned long *sizep);
 +
  /* Find beginning and length of commit subject. */
  int find_commit_subject(const char *commit_buffer, const char **subject);
  
@@@ -152,14 -127,14 +164,14 @@@ struct userformat_want 
  
  extern int has_non_ascii(const char *text);
  struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
 -extern char *logmsg_reencode(const struct commit *commit,
 -                           char **commit_encoding,
 -                           const char *output_encoding);
 -extern void logmsg_free(char *msg, const struct commit *commit);
 +extern const char *logmsg_reencode(const struct commit *commit,
 +                                 char **commit_encoding,
 +                                 const char *output_encoding);
  extern void get_commit_format(const char *arg, struct rev_info *);
  extern const char *format_subject(struct strbuf *sb, const char *msg,
                                  const char *line_separator);
  extern void userformat_find_requirements(const char *fmt, struct userformat_want *w);
 +extern int commit_format_is_empty(enum cmit_fmt);
  extern void format_commit_message(const struct commit *commit,
                                  const char *format, struct strbuf *sb,
                                  const struct pretty_print_context *context);
@@@ -272,7 -247,6 +284,7 @@@ extern void assign_shallow_commits_to_r
                                           int *ref_status);
  extern int delayed_reachability_test(struct shallow_info *si, int c);
  extern void prune_shallow(int show_only);
 +extern struct trace_key trace_shallow;
  
  int is_descendant_of(struct commit *, struct commit_list *);
  int in_merge_bases(struct commit *, struct commit *);
@@@ -299,13 -273,11 +311,13 @@@ struct commit_extra_header 
  extern void append_merge_tag_headers(struct commit_list *parents,
                                     struct commit_extra_header ***tail);
  
 -extern int commit_tree(const struct strbuf *msg, const unsigned char *tree,
 +extern int commit_tree(const char *msg, size_t msg_len,
 +                     const unsigned char *tree,
                       struct commit_list *parents, unsigned char *ret,
                       const char *author, const char *sign_commit);
  
 -extern int commit_tree_extended(const struct strbuf *msg, const unsigned char *tree,
 +extern int commit_tree_extended(const char *msg, size_t msg_len,
 +                              const unsigned char *tree,
                                struct commit_list *parents, unsigned char *ret,
                                const char *author, const char *sign_commit,
                                struct commit_extra_header *);
@@@ -314,11 -286,6 +326,11 @@@ extern struct commit_extra_header *read
  
  extern void free_commit_extra_headers(struct commit_extra_header *extra);
  
 +typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra,
 +                               void *cb_data);
 +
 +extern void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data);
 +
  struct merge_remote_desc {
        struct object *obj; /* the named object, could be a tag */
        const char *name;
   */
  struct commit *get_merge_parent(const char *name);
  
 -extern int parse_signed_commit(const unsigned char *sha1,
 +extern int parse_signed_commit(const struct commit *commit,
                               struct strbuf *message, struct strbuf *signature);
 +extern int remove_signature(struct strbuf *buf);
 +
  extern void print_commit_list(struct commit_list *list,
                              const char *format_cur,
                              const char *format_last);
diff --combined log-tree.c
index 95e9b1da259ef33a1c5bc7f7d07e853ebc5dbcec,de760db1a630463980c2d52e8ee39322d9bd901f..bcee7c596696eb1da109b3ff375e09453045a60f
  #include "sequencer.h"
  #include "line-log.h"
  
- struct decoration name_decoration = { "object names" };
- enum decoration_type {
-       DECORATION_NONE = 0,
-       DECORATION_REF_LOCAL,
-       DECORATION_REF_REMOTE,
-       DECORATION_REF_TAG,
-       DECORATION_REF_STASH,
-       DECORATION_REF_HEAD,
-       DECORATION_GRAFTED,
- };
+ static struct decoration name_decoration = { "object names" };
  
  static char decoration_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
@@@ -84,15 -74,20 +74,20 @@@ int parse_decorate_color_config(const c
  #define decorate_get_color_opt(o, ix) \
        decorate_get_color((o)->use_color, ix)
  
static void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
+ void add_name_decoration(enum decoration_type type, const char *name, struct object *obj)
  {
        int nlen = strlen(name);
-       struct name_decoration *res = xmalloc(sizeof(struct name_decoration) + nlen);
+       struct name_decoration *res = xmalloc(sizeof(*res) + nlen + 1);
        memcpy(res->name, name, nlen + 1);
        res->type = type;
        res->next = add_decoration(&name_decoration, obj, res);
  }
  
+ const struct name_decoration *get_name_decoration(const struct object *obj)
+ {
+       return lookup_decoration(&name_decoration, obj);
+ }
  static int add_ref_decoration(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
  {
        struct object *obj;
  
        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);
@@@ -187,13 -182,13 +182,13 @@@ void format_decorations(struct strbuf *
                        int use_color)
  {
        const char *prefix;
-       struct name_decoration *decoration;
+       const struct name_decoration *decoration;
        const char *color_commit =
                diff_get_color(use_color, DIFF_COMMIT);
        const char *color_reset =
                decorate_get_color(use_color, DECORATION_NONE);
  
-       decoration = lookup_decoration(&name_decoration, &commit->object);
+       decoration = get_name_decoration(&commit->object);
        if (!decoration)
                return;
        prefix = " (";
@@@ -365,7 -360,6 +360,7 @@@ static void show_sig_lines(struct rev_i
                eol = strchrnul(bol, '\n');
                printf("%s%.*s%s%s", color, (int)(eol - bol), bol, reset,
                       *eol ? "\n" : "");
 +              graph_show_oneline(opt->graph);
                bol = (*eol) ? (eol + 1) : eol;
        }
  }
@@@ -377,7 -371,7 +372,7 @@@ static void show_signature(struct rev_i
        struct strbuf gpg_output = STRBUF_INIT;
        int status;
  
 -      if (parse_signed_commit(commit->object.sha1, &payload, &signature) <= 0)
 +      if (parse_signed_commit(commit, &payload, &signature) <= 0)
                goto out;
  
        status = verify_signed_buffer(payload.buf, payload.len,
@@@ -414,11 -408,10 +409,11 @@@ static int is_common_merge(const struc
                && !commit->parents->next->next);
  }
  
 -static void show_one_mergetag(struct rev_info *opt,
 +static void show_one_mergetag(struct commit *commit,
                              struct commit_extra_header *extra,
 -                            struct commit *commit)
 +                            void *data)
  {
 +      struct rev_info *opt = (struct rev_info *)data;
        unsigned char sha1[20];
        struct tag *tag;
        struct strbuf verify_message;
  
        payload_size = parse_signature(extra->value, extra->len);
        status = -1;
 -      if (extra->len > payload_size)
 -              if (verify_signed_buffer(extra->value, payload_size,
 -                                       extra->value + payload_size,
 -                                       extra->len - payload_size,
 -                                       &verify_message, NULL)) {
 -                      if (verify_message.len <= gpg_message_offset)
 -                              strbuf_addstr(&verify_message, "No signature\n");
 -                      else
 -                              status = 0;
 -              }
 +      if (extra->len > payload_size) {
 +              /* could have a good signature */
 +              if (!verify_signed_buffer(extra->value, payload_size,
 +                                        extra->value + payload_size,
 +                                        extra->len - payload_size,
 +                                        &verify_message, NULL))
 +                      status = 0; /* good */
 +              else if (verify_message.len <= gpg_message_offset)
 +                      strbuf_addstr(&verify_message, "No signature\n");
 +              /* otherwise we couldn't verify, which is shown as bad */
 +      }
  
        show_sig_lines(opt, status, verify_message.buf);
        strbuf_release(&verify_message);
  
  static void show_mergetag(struct rev_info *opt, struct commit *commit)
  {
 -      struct commit_extra_header *extra, *to_free;
 -
 -      to_free = read_commit_extra_headers(commit, NULL);
 -      for (extra = to_free; extra; extra = extra->next) {
 -              if (strcmp(extra->key, "mergetag"))
 -                      continue; /* not a merge tag */
 -              show_one_mergetag(opt, extra, commit);
 -      }
 -      free_commit_extra_headers(to_free);
 +      for_each_mergetag(show_one_mergetag, commit, opt);
  }
  
  void show_log(struct rev_info *opt)
                show_mergetag(opt, commit);
        }
  
 -      if (!commit->buffer)
 +      if (!get_cached_commit_buffer(commit, NULL))
                return;
  
        if (opt->show_notes) {
                graph_show_commit_msg(opt->graph, &msgbuf);
        else
                fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
 -      if (opt->use_terminator) {
 +      if (opt->use_terminator && !commit_format_is_empty(opt->commit_format)) {
                if (!opt->missing_newline)
                        graph_show_padding(opt->graph);
                putchar(opt->diffopt.line_termination);
@@@ -676,8 -676,7 +671,8 @@@ int log_tree_diff_flush(struct rev_inf
                show_log(opt);
                if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
                    opt->verbose_header &&
 -                  opt->commit_format != CMIT_FMT_ONELINE) {
 +                  opt->commit_format != CMIT_FMT_ONELINE &&
 +                  !commit_format_is_empty(opt->commit_format)) {
                        /*
                         * When showing a verbose header (i.e. log message),
                         * and not in --pretty=oneline format, we would want
@@@ -801,16 -800,12 +796,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 revision.c
index 615535c98453336323a437530132f27824b51ac2,46b9c349e8ca2b879cfd9a7c76b2205673200251..0d3e4171ef73f0cfb58a4f396e17717f3b0bb5a3
@@@ -473,7 -473,7 +473,7 @@@ static int rev_compare_tree(struct rev_
                 * If we are simplifying by decoration, then the commit
                 * is worth showing if it has a tag pointing at it.
                 */
-               if (lookup_decoration(&name_decoration, &commit->object))
+               if (get_name_decoration(&commit->object))
                        return REV_TREE_DIFFERENT;
                /*
                 * A commit that is not pointed by a tag is uninteresting
  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 -784,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 -1192,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;
@@@ -1575,10 -1581,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)
@@@ -1633,7 -1633,6 +1633,7 @@@ static int handle_revision_opt(struct r
            !strcmp(arg, "--reflog") || !strcmp(arg, "--not") ||
            !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
            !strcmp(arg, "--bisect") || starts_with(arg, "--glob=") ||
 +          starts_with(arg, "--exclude=") ||
            starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
            starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
        {
                revs->skip_count = atoi(optarg);
                return argcount;
        } else if ((*arg == '-') && isdigit(arg[1])) {
 -      /* accept -<digit>, like traditional "head" */
 -              revs->max_count = atoi(arg + 1);
 +              /* accept -<digit>, like traditional "head" */
 +              if (strtol_i(arg + 1, 10, &revs->max_count) < 0 ||
 +                  revs->max_count < 0)
 +                      die("'%s': not a non-negative integer", arg + 1);
                revs->no_walk = 0;
        } else if (!strcmp(arg, "-n")) {
                if (argc <= 1)
        } else if (!strcmp(arg, "--pretty")) {
                revs->verbose_header = 1;
                revs->pretty_given = 1;
 -              get_commit_format(arg+8, revs);
 +              get_commit_format(NULL, revs);
        } else if (starts_with(arg, "--pretty=") || starts_with(arg, "--format=")) {
                /*
                 * Detached form ("--pretty X" as opposed to "--pretty=X")
                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;
  }
@@@ -2791,7 -2778,7 +2791,7 @@@ static int commit_match(struct commit *
  {
        int retval;
        const char *encoding;
 -      char *message;
 +      const char *message;
        struct strbuf buf = STRBUF_INIT;
  
        if (!opt->grep_filter.pattern_list && !opt->grep_filter.header_list)
                format_display_notes(commit->object.sha1, &buf, encoding, 1);
        }
  
 -      /* Find either in the original commit message, or in the temporary */
 +      /*
 +       * Find either in the original commit message, or in the temporary.
 +       * Note that we cast away the constness of "message" here. It is
 +       * const because it may come from the cached commit buffer. That's OK,
 +       * because we know that it is modifiable heap memory, and that while
 +       * grep_buffer may modify it for speed, it will restore any
 +       * changes before returning.
 +       */
        if (buf.len)
                retval = grep_buffer(&opt->grep_filter, buf.buf, buf.len);
        else
                retval = grep_buffer(&opt->grep_filter,
 -                                   message, strlen(message));
 +                                   (char *)message, strlen(message));
        strbuf_release(&buf);
 -      logmsg_free(message, commit);
 +      unuse_commit_buffer(commit, message);
        return retval;
  }
  
@@@ -2922,27 -2902,6 +2922,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);
@@@ -3152,23 -3107,14 +3152,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;
  }