-struct labels_entry {
- struct hashmap_entry entry;
- char label[FLEX_ARRAY];
-};
-
-static int labels_cmp(const void *fndata, const struct labels_entry *a,
- const struct labels_entry *b, const void *key)
-{
- return key ? strcmp(a->label, key) : strcmp(a->label, b->label);
-}
-
-struct string_entry {
- struct oidmap_entry entry;
- char string[FLEX_ARRAY];
-};
-
-struct label_state {
- struct oidmap commit2label;
- struct hashmap labels;
- struct strbuf buf;
-};
-
-static const char *label_oid(struct object_id *oid, const char *label,
- struct label_state *state)
-{
- struct labels_entry *labels_entry;
- struct string_entry *string_entry;
- struct object_id dummy;
- size_t len;
- int i;
-
- string_entry = oidmap_get(&state->commit2label, oid);
- if (string_entry)
- return string_entry->string;
-
- /*
- * For "uninteresting" commits, i.e. commits that are not to be
- * rebased, and which can therefore not be labeled, we use a unique
- * abbreviation of the commit name. This is slightly more complicated
- * than calling find_unique_abbrev() because we also need to make
- * sure that the abbreviation does not conflict with any other
- * label.
- *
- * We disallow "interesting" commits to be labeled by a string that
- * is a valid full-length hash, to ensure that we always can find an
- * abbreviation for any uninteresting commit's names that does not
- * clash with any other label.
- */
- if (!label) {
- char *p;
-
- strbuf_reset(&state->buf);
- strbuf_grow(&state->buf, GIT_SHA1_HEXSZ);
- label = p = state->buf.buf;
-
- find_unique_abbrev_r(p, oid->hash, default_abbrev);
-
- /*
- * We may need to extend the abbreviated hash so that there is
- * no conflicting label.
- */
- if (hashmap_get_from_hash(&state->labels, strihash(p), p)) {
- size_t i = strlen(p) + 1;
-
- oid_to_hex_r(p, oid);
- for (; i < GIT_SHA1_HEXSZ; i++) {
- char save = p[i];
- p[i] = '\0';
- if (!hashmap_get_from_hash(&state->labels,
- strihash(p), p))
- break;
- p[i] = save;
- }
- }
- } else if (((len = strlen(label)) == GIT_SHA1_RAWSZ &&
- !get_oid_hex(label, &dummy)) ||
- (len == 1 && *label == '#') ||
- hashmap_get_from_hash(&state->labels,
- strihash(label), label)) {
- /*
- * If the label already exists, or if the label is a valid full
- * OID, or the label is a '#' (which we use as a separator
- * between merge heads and oneline), we append a dash and a
- * number to make it unique.
- */
- struct strbuf *buf = &state->buf;
-
- strbuf_reset(buf);
- strbuf_add(buf, label, len);
-
- for (i = 2; ; i++) {
- strbuf_setlen(buf, len);
- strbuf_addf(buf, "-%d", i);
- if (!hashmap_get_from_hash(&state->labels,
- strihash(buf->buf),
- buf->buf))
- break;
- }
-
- label = buf->buf;
- }
-
- FLEX_ALLOC_STR(labels_entry, label, label);
- hashmap_entry_init(labels_entry, strihash(label));
- hashmap_add(&state->labels, labels_entry);
-
- FLEX_ALLOC_STR(string_entry, string, label);
- oidcpy(&string_entry->entry.oid, oid);
- oidmap_put(&state->commit2label, string_entry);
-
- return string_entry->string;
-}
-
-static int make_script_with_merges(struct pretty_print_context *pp,
- struct rev_info *revs, FILE *out,
- unsigned flags)
-{
- int keep_empty = flags & TODO_LIST_KEEP_EMPTY;
- int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;
- struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT;
- struct strbuf label = STRBUF_INIT;
- struct commit_list *commits = NULL, **tail = &commits, *iter;
- struct commit_list *tips = NULL, **tips_tail = &tips;
- struct commit *commit;
- struct oidmap commit2todo = OIDMAP_INIT;
- struct string_entry *entry;
- struct oidset interesting = OIDSET_INIT, child_seen = OIDSET_INIT,
- shown = OIDSET_INIT;
- struct label_state state = { OIDMAP_INIT, { NULL }, STRBUF_INIT };
-
- int abbr = flags & TODO_LIST_ABBREVIATE_CMDS;
- const char *cmd_pick = abbr ? "p" : "pick",
- *cmd_label = abbr ? "l" : "label",
- *cmd_reset = abbr ? "t" : "reset",
- *cmd_merge = abbr ? "m" : "merge";
-
- oidmap_init(&commit2todo, 0);
- oidmap_init(&state.commit2label, 0);
- hashmap_init(&state.labels, (hashmap_cmp_fn) labels_cmp, NULL, 0);
- strbuf_init(&state.buf, 32);
-
- if (revs->cmdline.nr && (revs->cmdline.rev[0].flags & BOTTOM)) {
- struct object_id *oid = &revs->cmdline.rev[0].item->oid;
- FLEX_ALLOC_STR(entry, string, "onto");
- oidcpy(&entry->entry.oid, oid);
- oidmap_put(&state.commit2label, entry);
- }
-
- /*
- * First phase:
- * - get onelines for all commits
- * - gather all branch tips (i.e. 2nd or later parents of merges)
- * - label all branch tips
- */
- while ((commit = get_revision(revs))) {
- struct commit_list *to_merge;
- int is_octopus;
- const char *p1, *p2;
- struct object_id *oid;
-
- tail = &commit_list_insert(commit, tail)->next;
- oidset_insert(&interesting, &commit->object.oid);
-
- if ((commit->object.flags & PATCHSAME))
- continue;
-
- strbuf_reset(&oneline);
- pretty_print_commit(pp, commit, &oneline);
-
- to_merge = commit->parents ? commit->parents->next : NULL;
- if (!to_merge) {
- /* non-merge commit: easy case */
- strbuf_reset(&buf);
- if (!keep_empty && is_original_commit_empty(commit))
- strbuf_addf(&buf, "%c ", comment_line_char);
- strbuf_addf(&buf, "%s %s %s", cmd_pick,
- oid_to_hex(&commit->object.oid),
- oneline.buf);
-
- FLEX_ALLOC_STR(entry, string, buf.buf);
- oidcpy(&entry->entry.oid, &commit->object.oid);
- oidmap_put(&commit2todo, entry);
-
- continue;
- }
-
- is_octopus = to_merge && to_merge->next;
-
- if (is_octopus)
- BUG("Octopus merges not yet supported");
-
- /* Create a label */
- strbuf_reset(&label);
- if (skip_prefix(oneline.buf, "Merge ", &p1) &&
- (p1 = strchr(p1, '\'')) &&
- (p2 = strchr(++p1, '\'')))
- strbuf_add(&label, p1, p2 - p1);
- else if (skip_prefix(oneline.buf, "Merge pull request ",
- &p1) &&
- (p1 = strstr(p1, " from ")))
- strbuf_addstr(&label, p1 + strlen(" from "));
- else
- strbuf_addbuf(&label, &oneline);
-
- for (p1 = label.buf; *p1; p1++)
- if (isspace(*p1))
- *(char *)p1 = '-';
-
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s -C %s",
- cmd_merge, oid_to_hex(&commit->object.oid));
-
- /* label the tip of merged branch */
- oid = &to_merge->item->object.oid;
- strbuf_addch(&buf, ' ');
-
- if (!oidset_contains(&interesting, oid))
- strbuf_addstr(&buf, label_oid(oid, NULL, &state));
- else {
- tips_tail = &commit_list_insert(to_merge->item,
- tips_tail)->next;
-
- strbuf_addstr(&buf, label_oid(oid, label.buf, &state));
- }
- strbuf_addf(&buf, " # %s", oneline.buf);
-
- FLEX_ALLOC_STR(entry, string, buf.buf);
- oidcpy(&entry->entry.oid, &commit->object.oid);
- oidmap_put(&commit2todo, entry);
- }
-
- /*
- * Second phase:
- * - label branch points
- * - add HEAD to the branch tips
- */
- for (iter = commits; iter; iter = iter->next) {
- struct commit_list *parent = iter->item->parents;
- for (; parent; parent = parent->next) {
- struct object_id *oid = &parent->item->object.oid;
- if (!oidset_contains(&interesting, oid))
- continue;
- if (!oidset_contains(&child_seen, oid))
- oidset_insert(&child_seen, oid);
- else
- label_oid(oid, "branch-point", &state);
- }
-
- /* Add HEAD as implict "tip of branch" */
- if (!iter->next)
- tips_tail = &commit_list_insert(iter->item,
- tips_tail)->next;
- }
-
- /*
- * Third phase: output the todo list. This is a bit tricky, as we
- * want to avoid jumping back and forth between revisions. To
- * accomplish that goal, we walk backwards from the branch tips,
- * gathering commits not yet shown, reversing the list on the fly,
- * then outputting that list (labeling revisions as needed).
- */
- fprintf(out, "%s onto\n", cmd_label);
- for (iter = tips; iter; iter = iter->next) {
- struct commit_list *list = NULL, *iter2;
-
- commit = iter->item;
- if (oidset_contains(&shown, &commit->object.oid))
- continue;
- entry = oidmap_get(&state.commit2label, &commit->object.oid);
-
- if (entry)
- fprintf(out, "\n# Branch %s\n", entry->string);
- else
- fprintf(out, "\n");
-
- while (oidset_contains(&interesting, &commit->object.oid) &&
- !oidset_contains(&shown, &commit->object.oid)) {
- commit_list_insert(commit, &list);
- if (!commit->parents) {
- commit = NULL;
- break;
- }
- commit = commit->parents->item;
- }
-
- if (!commit)
- fprintf(out, "%s onto\n", cmd_reset);
- else {
- const char *to = NULL;
-
- entry = oidmap_get(&state.commit2label,
- &commit->object.oid);
- if (entry)
- to = entry->string;
- else if (!rebase_cousins)
- to = label_oid(&commit->object.oid, NULL,
- &state);
-
- if (!to || !strcmp(to, "onto"))
- fprintf(out, "%s onto\n", cmd_reset);
- else {
- strbuf_reset(&oneline);
- pretty_print_commit(pp, commit, &oneline);
- fprintf(out, "%s %s # %s\n",
- cmd_reset, to, oneline.buf);
- }
- }
-
- for (iter2 = list; iter2; iter2 = iter2->next) {
- struct object_id *oid = &iter2->item->object.oid;
- entry = oidmap_get(&commit2todo, oid);
- /* only show if not already upstream */
- if (entry)
- fprintf(out, "%s\n", entry->string);
- entry = oidmap_get(&state.commit2label, oid);
- if (entry)
- fprintf(out, "%s %s\n",
- cmd_label, entry->string);
- oidset_insert(&shown, oid);
- }
-
- free_commit_list(list);
- }
-
- free_commit_list(commits);
- free_commit_list(tips);
-
- strbuf_release(&label);
- strbuf_release(&oneline);
- strbuf_release(&buf);
-
- oidmap_free(&commit2todo, 1);
- oidmap_free(&state.commit2label, 1);
- hashmap_free(&state.labels, 1);
- strbuf_release(&state.buf);
-
- return 0;
-}
-