From: Junio C Hamano Date: Mon, 25 Jun 2018 20:22:35 +0000 (-0700) Subject: Merge branch 'nd/commit-util-to-slab' X-Git-Tag: v2.19.0-rc0~193 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/b3b2aaf0fd6c65f83d5f636bb177d96c88079b13?hp=-c Merge branch 'nd/commit-util-to-slab' The in-core "commit" object had an all-purpose "void *util" field, which was tricky to use especially in library-ish part of the code. All of the existing uses of the field has been migrated to a more dedicated "commit-slab" mechanism and the field is eliminated. * nd/commit-util-to-slab: commit.h: delete 'util' field in struct commit merge: use commit-slab in merge remote desc instead of commit->util log: use commit-slab in prepare_bases() instead of commit->util show-branch: note about its object flags usage show-branch: use commit-slab for commit-name instead of commit->util name-rev: use commit-slab for rev-name instead of commit->util bisect.c: use commit-slab for commit weight instead of commit->util revision.c: use commit-slab for show_source sequencer.c: use commit-slab to associate todo items to commits sequencer.c: use commit-slab to mark seen commits shallow.c: use commit-slab for commit depth instead of commit->util describe: use commit-slab for commit names instead of commit->util blame: use commit-slab for blame suspects instead of commit->util commit-slab: support shared commit-slab commit-slab.h: code split --- b3b2aaf0fd6c65f83d5f636bb177d96c88079b13 diff --combined blame.c index 14d0e0b575,18e8bd996a..ba1b33c7f4 --- a/blame.c +++ b/blame.c @@@ -6,6 -6,24 +6,24 @@@ #include "diffcore.h" #include "tag.h" #include "blame.h" + #include "commit-slab.h" + + define_commit_slab(blame_suspects, struct blame_origin *); + static struct blame_suspects blame_suspects; + + struct blame_origin *get_blame_suspects(struct commit *commit) + { + struct blame_origin **result; + + result = blame_suspects_peek(&blame_suspects, commit); + + return result ? *result : NULL; + } + + static void set_blame_suspects(struct commit *commit, struct blame_origin *origin) + { + *blame_suspects_at(&blame_suspects, commit) = origin; + } void blame_origin_decref(struct blame_origin *o) { @@@ -15,12 -33,12 +33,12 @@@ blame_origin_decref(o->previous); free(o->file.ptr); /* Should be present exactly once in commit chain */ - for (p = o->commit->util; p; l = p, p = p->next) { + for (p = get_blame_suspects(o->commit); p; l = p, p = p->next) { if (p == o) { if (l) l->next = p->next; else - o->commit->util = p->next; + set_blame_suspects(o->commit, p->next); free(o); return; } @@@ -41,8 -59,8 +59,8 @@@ static struct blame_origin *make_origin FLEX_ALLOC_STR(o, path, path); o->commit = commit; o->refcnt = 1; - o->next = commit->util; - commit->util = o; + o->next = get_blame_suspects(commit); + set_blame_suspects(commit, o); return o; } @@@ -54,13 -72,13 +72,13 @@@ static struct blame_origin *get_origin( { struct blame_origin *o, *l; - for (o = commit->util, l = NULL; o; l = o, o = o->next) { + for (o = get_blame_suspects(commit), l = NULL; o; l = o, o = o->next) { if (!strcmp(o->path, path)) { /* bump to front */ if (l) { l->next = o->next; - o->next = commit->util; - commit->util = o; + o->next = get_blame_suspects(commit); + set_blame_suspects(commit, o); } return blame_origin_incref(o); } @@@ -81,7 -99,7 +99,7 @@@ static void verify_working_tree_path(st unsigned mode; if (!get_tree_entry(commit_oid, path, &blob_oid, &mode) && - oid_object_info(&blob_oid, NULL) == OBJ_BLOB) + oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) return; } @@@ -478,7 -496,7 +496,7 @@@ static void queue_blames(struct blame_s porigin->suspects = blame_merge(porigin->suspects, sorted); else { struct blame_origin *o; - for (o = porigin->commit->util; o; o = o->next) { + for (o = get_blame_suspects(porigin->commit); o; o = o->next) { if (o->suspects) { porigin->suspects = sorted; return; @@@ -504,7 -522,7 +522,7 @@@ static int fill_blob_sha1_and_mode(stru return 0; if (get_tree_entry(&origin->commit->object.oid, origin->path, &origin->blob_oid, &origin->mode)) goto error_out; - if (oid_object_info(&origin->blob_oid, NULL) != OBJ_BLOB) + if (oid_object_info(the_repository, &origin->blob_oid, NULL) != OBJ_BLOB) goto error_out; return 0; error_out: @@@ -525,7 -543,7 +543,7 @@@ static struct blame_origin *find_origin const char *paths[2]; /* First check any existing origins */ - for (porigin = parent->util; porigin; porigin = porigin->next) + for (porigin = get_blame_suspects(parent); porigin; porigin = porigin->next) if (!strcmp(porigin->path, origin->path)) { /* * The same path between origin and its parent @@@ -551,10 -569,10 +569,10 @@@ diff_setup_done(&diff_opts); if (is_null_oid(&origin->commit->object.oid)) - do_diff_cache(&parent->tree->object.oid, &diff_opts); + do_diff_cache(get_commit_tree_oid(parent), &diff_opts); else - diff_tree_oid(&parent->tree->object.oid, - &origin->commit->tree->object.oid, + diff_tree_oid(get_commit_tree_oid(parent), + get_commit_tree_oid(origin->commit), "", &diff_opts); diffcore_std(&diff_opts); @@@ -620,10 -638,10 +638,10 @@@ static struct blame_origin *find_rename diff_setup_done(&diff_opts); if (is_null_oid(&origin->commit->object.oid)) - do_diff_cache(&parent->tree->object.oid, &diff_opts); + do_diff_cache(get_commit_tree_oid(parent), &diff_opts); else - diff_tree_oid(&parent->tree->object.oid, - &origin->commit->tree->object.oid, + diff_tree_oid(get_commit_tree_oid(parent), + get_commit_tree_oid(origin->commit), "", &diff_opts); diffcore_std(&diff_opts); @@@ -1255,10 -1273,10 +1273,10 @@@ static void find_copy_in_parent(struct diff_opts.flags.find_copies_harder = 1; if (is_null_oid(&target->commit->object.oid)) - do_diff_cache(&parent->tree->object.oid, &diff_opts); + do_diff_cache(get_commit_tree_oid(parent), &diff_opts); else - diff_tree_oid(&parent->tree->object.oid, - &target->commit->tree->object.oid, + diff_tree_oid(get_commit_tree_oid(parent), + get_commit_tree_oid(target->commit), "", &diff_opts); if (!diff_opts.flags.find_copies_harder) @@@ -1550,7 -1568,7 +1568,7 @@@ void assign_blame(struct blame_scoreboa while (commit) { struct blame_entry *ent; - struct blame_origin *suspect = commit->util; + struct blame_origin *suspect = get_blame_suspects(commit); /* find one suspect to break down */ while (suspect && !suspect->suspects) @@@ -1752,6 -1770,8 +1770,8 @@@ void setup_scoreboard(struct blame_scor struct commit *final_commit = NULL; enum object_type type; + init_blame_suspects(&blame_suspects); + if (sb->reverse && sb->contents_from) die(_("--contents and --reverse do not blend well.")); @@@ -1806,7 -1826,7 +1826,7 @@@ l->item = c; if (add_decoration(&sb->revs->children, &c->parents->item->object, l)) - die("BUG: not unique item in first-parent chain"); + BUG("not unique item in first-parent chain"); c = c->parents->item; } @@@ -1815,7 -1835,7 +1835,7 @@@ } if (is_null_oid(&sb->final->object.oid)) { - o = sb->final->util; + o = get_blame_suspects(sb->final); sb->final_buf = xmemdupz(o->file.ptr, o->file.size); sb->final_buf_size = o->file.size; } diff --combined builtin/blame.c index 3295718841,969572810d..5a0388aaef --- a/builtin/blame.c +++ b/builtin/blame.c @@@ -7,7 -7,6 +7,7 @@@ #include "cache.h" #include "config.h" +#include "color.h" #include "builtin.h" #include "commit.h" #include "diff.h" @@@ -24,7 -23,6 +24,7 @@@ #include "dir.h" #include "progress.h" #include "blame.h" +#include "string-list.h" static char blame_usage[] = N_("git blame [] [] [] [--] "); @@@ -48,8 -46,6 +48,8 @@@ static int xdl_opts static int abbrev = -1; static int no_whole_file_rename; static int show_progress; +static char repeated_meta_color[COLOR_MAXLEN]; +static int coloring_mode; static struct date_mode blame_date_mode = { DATE_ISO8601 }; static size_t blame_date_width; @@@ -320,12 -316,10 +320,12 @@@ static const char *format_time(timestam #define OUTPUT_PORCELAIN 010 #define OUTPUT_SHOW_NAME 020 #define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 -#define OUTPUT_NO_AUTHOR 0200 +#define OUTPUT_SHOW_SCORE 0100 +#define OUTPUT_NO_AUTHOR 0200 #define OUTPUT_SHOW_EMAIL 0400 -#define OUTPUT_LINE_PORCELAIN 01000 +#define OUTPUT_LINE_PORCELAIN 01000 +#define OUTPUT_COLOR_LINE 02000 +#define OUTPUT_SHOW_AGE_WITH_COLOR 04000 static void emit_porcelain_details(struct blame_origin *suspect, int repeat) { @@@ -373,64 -367,6 +373,64 @@@ static void emit_porcelain(struct blame putchar('\n'); } +static struct color_field { + timestamp_t hop; + char col[COLOR_MAXLEN]; +} *colorfield; +static int colorfield_nr, colorfield_alloc; + +static void parse_color_fields(const char *s) +{ + struct string_list l = STRING_LIST_INIT_DUP; + struct string_list_item *item; + enum { EXPECT_DATE, EXPECT_COLOR } next = EXPECT_COLOR; + + colorfield_nr = 0; + + /* Ideally this would be stripped and split at the same time? */ + string_list_split(&l, s, ',', -1); + ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc); + + for_each_string_list_item(item, &l) { + switch (next) { + case EXPECT_DATE: + colorfield[colorfield_nr].hop = approxidate(item->string); + next = EXPECT_COLOR; + colorfield_nr++; + ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc); + break; + case EXPECT_COLOR: + if (color_parse(item->string, colorfield[colorfield_nr].col)) + die(_("expecting a color: %s"), item->string); + next = EXPECT_DATE; + break; + } + } + + if (next == EXPECT_COLOR) + die (_("must end with a color")); + + colorfield[colorfield_nr].hop = TIME_MAX; + string_list_clear(&l, 0); +} + +static void setup_default_color_by_age(void) +{ + parse_color_fields("blue,12 month ago,white,1 month ago,red"); +} + +static void determine_line_heat(struct blame_entry *ent, const char **dest_color) +{ + int i = 0; + struct commit_info ci; + get_commit_info(ent->suspect->commit, &ci, 1); + + while (i < colorfield_nr && ci.author_time > colorfield[i].hop) + i++; + + *dest_color = colorfield[i].col; +} + static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) { int cnt; @@@ -439,35 -375,15 +439,35 @@@ struct commit_info ci; char hex[GIT_MAX_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); + const char *default_color = NULL, *color = NULL, *reset = NULL; get_commit_info(suspect->commit, &ci, 1); oid_to_hex_r(hex, &suspect->commit->object.oid); cp = blame_nth_line(sb, ent->lno); + + if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) { + determine_line_heat(ent, &default_color); + color = default_color; + reset = GIT_COLOR_RESET; + } + for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; + if (opt & OUTPUT_COLOR_LINE) { + if (cnt > 0) { + color = repeated_meta_color; + reset = GIT_COLOR_RESET; + } else { + color = default_color ? default_color : NULL; + reset = default_color ? GIT_COLOR_RESET : NULL; + } + } + if (color) + fputs(color, stdout); + if (suspect->commit->object.flags & UNINTERESTING) { if (blank_boundary) memset(hex, ' ', length); @@@ -517,8 -433,6 +517,8 @@@ printf(" %*d) ", max_digits, ent->lno + 1 + cnt); } + if (reset) + fputs(reset, stdout); do { ch = *cp++; putchar(ch); @@@ -543,7 -457,7 +543,7 @@@ static void output(struct blame_scorebo struct commit *commit = ent->suspect->commit; if (commit->object.flags & MORE_THAN_ONE_PATH) continue; - for (suspect = commit->util; suspect; suspect = suspect->next) { + for (suspect = get_blame_suspects(commit); suspect; suspect = suspect->next) { if (suspect->guilty && count++) { commit->object.flags |= MORE_THAN_ONE_PATH; break; @@@ -693,30 -607,6 +693,30 @@@ static int git_blame_config(const char parse_date_format(value, &blame_date_mode); return 0; } + if (!strcmp(var, "color.blame.repeatedlines")) { + if (color_parse_mem(value, strlen(value), repeated_meta_color)) + warning(_("invalid color '%s' in color.blame.repeatedLines"), + value); + return 0; + } + if (!strcmp(var, "color.blame.highlightrecent")) { + parse_color_fields(value); + return 0; + } + + if (!strcmp(var, "blame.coloring")) { + if (!strcmp(value, "repeatedLines")) { + coloring_mode |= OUTPUT_COLOR_LINE; + } else if (!strcmp(value, "highlightRecent")) { + coloring_mode |= OUTPUT_SHOW_AGE_WITH_COLOR; + } else if (!strcmp(value, "none")) { + coloring_mode &= ~(OUTPUT_COLOR_LINE | + OUTPUT_SHOW_AGE_WITH_COLOR); + } else { + warning(_("invalid value for blame.coloring")); + return 0; + } + } if (git_diff_heuristic_config(var, value, cb) < 0) return -1; @@@ -765,7 -655,7 +765,7 @@@ static int is_a_rev(const char *name if (get_oid(name, &oid)) return 0; - return OBJ_NONE < oid_object_info(&oid, NULL); + return OBJ_NONE < oid_object_info(the_repository, &oid, NULL); } int cmd_blame(int argc, const char **argv, const char *prefix) @@@ -800,8 -690,6 +800,8 @@@ OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE), + OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR), /* * The following two options are parsed by parse_revision_opt() @@@ -826,7 -714,6 +826,7 @@@ unsigned int range_i; long anchor; + setup_default_color_by_age(); git_config(git_blame_config, &output_option); init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; @@@ -1062,17 -949,8 +1062,17 @@@ parse_done blame_coalesce(&sb); - if (!(output_option & OUTPUT_PORCELAIN)) + if (!(output_option & (OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR))) + output_option |= coloring_mode; + + if (!(output_option & OUTPUT_PORCELAIN)) { find_alignment(&sb, &output_option); + if (!*repeated_meta_color && + (output_option & OUTPUT_COLOR_LINE)) + strcpy(repeated_meta_color, GIT_COLOR_CYAN); + } + if (output_option & OUTPUT_ANNOTATE_COMPAT) + output_option &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR); output(&sb, output_option); free((void *)sb.final_buf); diff --combined builtin/describe.c index cf1ae77d7c,1b6ca42553..bec2513b66 --- a/builtin/describe.c +++ b/builtin/describe.c @@@ -15,9 -15,12 +15,12 @@@ #include "run-command.h" #include "revision.h" #include "list-objects.h" + #include "commit-slab.h" #define MAX_TAGS (FLAG_BITS - 1) + define_commit_slab(commit_names, struct commit_name *); + static const char * const describe_usage[] = { N_("git describe [] [...]"), N_("git describe [] --dirty"), @@@ -37,6 -40,7 +40,7 @@@ static struct string_list patterns = ST static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP; static int always; static const char *suffix, *dirty, *broken; + static struct commit_names commit_names; /* diff-index command arguments to check if working tree is dirty. */ static const char *diff_index_args[] = { @@@ -321,11 -325,14 +325,14 @@@ static void describe_commit(struct obje if (!have_util) { struct hashmap_iter iter; struct commit *c; - struct commit_name *n = hashmap_iter_first(&names, &iter); + struct commit_name *n; + + init_commit_names(&commit_names); + n = hashmap_iter_first(&names, &iter); for (; n; n = hashmap_iter_next(&iter)) { c = lookup_commit_reference_gently(&n->peeled, 1); if (c) - c->util = n; + *commit_names_at(&commit_names, c) = n; } have_util = 1; } @@@ -336,8 -343,11 +343,11 @@@ while (list) { struct commit *c = pop_commit(&list); struct commit_list *parents = c->parents; + struct commit_name **slot; + seen_commits++; - n = c->util; + slot = commit_names_peek(&commit_names, c); + n = slot ? *slot : NULL; if (n) { if (!tags && !all && n->prio < 2) { unannotated_cnt++; @@@ -502,7 -512,7 +512,7 @@@ static void describe(const char *arg, i if (cmit) describe_commit(&oid, &sb); - else if (oid_object_info(&oid, NULL) == OBJ_BLOB) + else if (oid_object_info(the_repository, &oid, NULL) == OBJ_BLOB) describe_blob(oid, &sb); else die(_("%s is neither a commit nor blob"), arg); @@@ -612,7 -622,7 +622,7 @@@ int cmd_describe(int argc, const char * suffix = broken; } } else if (dirty) { - static struct lock_file index_lock; + struct lock_file index_lock = LOCK_INIT; struct rev_info revs; struct argv_array args = ARGV_ARRAY_INIT; int fd, result; diff --combined builtin/fast-export.c index 6c9768742f,5aaf5c8e59..73d12c1020 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@@ -7,7 -7,6 +7,7 @@@ #include "cache.h" #include "config.h" #include "refs.h" +#include "refspec.h" #include "commit.h" #include "object.h" #include "tag.h" @@@ -22,6 -21,7 +22,7 @@@ #include "quote.h" #include "remote.h" #include "blob.h" + #include "commit-slab.h" static const char *fast_export_usage[] = { N_("git fast-export [rev-list-opts]"), @@@ -36,8 -36,10 +37,9 @@@ static int use_done_feature static int no_data; static int full_tree; static struct string_list extra_refs = STRING_LIST_INIT_NODUP; -static struct refspec *refspecs; -static int refspecs_nr; +static struct refspec refspecs = REFSPEC_INIT_FETCH; static int anonymize; + static struct revision_sources revision_sources; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@@ -156,14 -158,15 +158,14 @@@ static void anonymize_path(struct strbu } } -/* Since intptr_t is C99, we do not use it here */ -static inline uint32_t *mark_to_ptr(uint32_t mark) +static inline void *mark_to_ptr(uint32_t mark) { - return ((uint32_t *)NULL) + mark; + return (void *)(uintptr_t)mark; } static inline uint32_t ptr_to_mark(void * mark) { - return (uint32_t *)mark - (uint32_t *)NULL; + return (uint32_t)(uintptr_t)mark; } static inline void mark_object(struct object *object, uint32_t mark) @@@ -516,7 -519,7 +518,7 @@@ static void anonymize_ident_line(const /* skip "committer", "author", "tagger", etc */ end_of_header = strchr(*beg, ' '); if (!end_of_header) - die("BUG: malformed line fed to anonymize_ident_line: %.*s", + BUG("malformed line fed to anonymize_ident_line: %.*s", (int)(*end - *beg), *beg); end_of_header++; strbuf_add(out, *beg, end_of_header - *beg); @@@ -577,11 -580,11 +579,11 @@@ static void handle_commit(struct commi get_object_mark(&commit->parents->item->object) != 0 && !full_tree) { parse_commit_or_die(commit->parents->item); - diff_tree_oid(&commit->parents->item->tree->object.oid, - &commit->tree->object.oid, "", &rev->diffopt); + diff_tree_oid(get_commit_tree_oid(commit->parents->item), + get_commit_tree_oid(commit), "", &rev->diffopt); } else - diff_root_tree_oid(&commit->tree->object.oid, + diff_root_tree_oid(get_commit_tree_oid(commit), "", &rev->diffopt); /* Export the referenced blobs, and remember the marks. */ @@@ -589,7 -592,7 +591,7 @@@ if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) export_blob(&diff_queued_diff.queue[i]->two->oid); - refname = commit->util; + refname = *revision_sources_at(&revision_sources, commit); if (anonymize) { refname = anonymize_refname(refname); anonymize_ident_line(&committer, &committer_end); @@@ -828,9 -831,9 +830,9 @@@ static void get_tags_and_duplicates(str if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1) continue; - if (refspecs) { + if (refspecs.nr) { char *private; - private = apply_refspecs(refspecs, refspecs_nr, full_name); + private = apply_refspecs(&refspecs, full_name); if (private) { free(full_name); full_name = private; @@@ -861,10 -864,11 +863,11 @@@ * This ref will not be updated through a commit, lets make * sure it gets properly updated eventually. */ - if (commit->util || commit->object.flags & SHOWN) + if (*revision_sources_at(&revision_sources, commit) || + commit->object.flags & SHOWN) string_list_append(&extra_refs, full_name)->util = commit; - if (!commit->util) - commit->util = full_name; + if (!*revision_sources_at(&revision_sources, commit)) + *revision_sources_at(&revision_sources, commit) = full_name; } } @@@ -949,7 -953,7 +952,7 @@@ static void import_marks(char *input_fi if (last_idnum < mark) last_idnum = mark; - type = oid_object_info(&oid, NULL); + type = oid_object_info(the_repository, &oid, NULL); if (type < 0) die("object not found: %s", oid_to_hex(&oid)); @@@ -976,8 -980,8 +979,8 @@@ static void handle_deletes(void) { int i; - for (i = 0; i < refspecs_nr; i++) { - struct refspec *refspec = &refspecs[i]; + for (i = 0; i < refspecs.nr; i++) { + struct refspec_item *refspec = &refspecs.items[i]; if (*refspec->src) continue; @@@ -1028,8 -1032,9 +1031,9 @@@ int cmd_fast_export(int argc, const cha git_config(git_default_config, NULL); init_revisions(&revs, prefix); + init_revision_sources(&revision_sources); revs.topo_order = 1; - revs.show_source = 1; + revs.sources = &revision_sources; revs.rewrite_parents = 1; argc = parse_options(argc, argv, prefix, options, fast_export_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); @@@ -1038,12 -1043,18 +1042,12 @@@ usage_with_options (fast_export_usage, options); if (refspecs_list.nr) { - const char **refspecs_str; int i; - ALLOC_ARRAY(refspecs_str, refspecs_list.nr); for (i = 0; i < refspecs_list.nr; i++) - refspecs_str[i] = refspecs_list.items[i].string; - - refspecs_nr = refspecs_list.nr; - refspecs = parse_fetch_refspec(refspecs_nr, refspecs_str); + refspec_append(&refspecs, refspecs_list.items[i].string); string_list_clear(&refspecs_list, 1); - free(refspecs_str); } if (use_done_feature) @@@ -1082,7 -1093,7 +1086,7 @@@ if (use_done_feature) printf("done\n"); - free_refspec(refspecs_nr, refspecs); + refspec_clear(&refspecs); return 0; } diff --combined builtin/log.c index 4686f68594,967fbc5caa..0583f7f383 --- a/builtin/log.c +++ b/builtin/log.c @@@ -28,6 -28,7 +28,7 @@@ #include "mailmap.h" #include "gpg-interface.h" #include "progress.h" + #include "commit-slab.h" #define MAIL_DEFAULT_WRAP 72 @@@ -148,6 -149,7 +149,7 @@@ static void cmd_log_init_finish(int arg static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; struct decoration_filter decoration_filter = {&decorate_refs_include, &decorate_refs_exclude}; + static struct revision_sources revision_sources; const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), @@@ -194,8 -196,10 +196,10 @@@ rev->diffopt.filter || rev->diffopt.flags.follow_renames) rev->always_show_header = 0; - if (source) - rev->show_source = 1; + if (source) { + init_revision_sources(&revision_sources); + rev->sources = &revision_sources; + } if (mailmap) { rev->mailmap = xcalloc(1, sizeof(struct string_list)); @@@ -1019,7 -1023,7 +1023,7 @@@ static void make_cover_letter(struct re open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; - log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte); + log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte, 0); for (i = 0; !need_8bit_cte && i < nr; i++) { const char *buf = get_commit_buffer(list[i], NULL); @@@ -1067,8 -1071,8 +1071,8 @@@ diff_setup_done(&opts); - diff_tree_oid(&origin->tree->object.oid, - &head->tree->object.oid, + diff_tree_oid(get_commit_tree_oid(origin), + get_commit_tree_oid(head), "", &opts); diffcore_std(&opts); diff_flush(&opts); @@@ -1337,6 -1341,8 +1341,8 @@@ static struct commit *get_base_commit(c return base; } + define_commit_slab(commit_base, int); + static void prepare_bases(struct base_tree_info *bases, struct commit *base, struct commit **list, @@@ -1345,11 -1351,13 +1351,13 @@@ struct commit *commit; struct rev_info revs; struct diff_options diffopt; + struct commit_base commit_base; int i; if (!base) return; + init_commit_base(&commit_base); diff_setup(&diffopt); diffopt.flags.recursive = 1; diff_setup_done(&diffopt); @@@ -1362,7 -1370,7 +1370,7 @@@ for (i = 0; i < total; i++) { list[i]->object.flags &= ~UNINTERESTING; add_pending_object(&revs, &list[i]->object, "rev_list"); - list[i]->util = (void *)1; + *commit_base_at(&commit_base, list[i]) = 1; } base->object.flags |= UNINTERESTING; add_pending_object(&revs, &base->object, "base"); @@@ -1376,7 -1384,7 +1384,7 @@@ while ((commit = get_revision(&revs)) != NULL) { struct object_id oid; struct object_id *patch_id; - if (commit->util) + if (*commit_base_at(&commit_base, commit)) continue; if (commit_patch_id(commit, &diffopt, &oid, 0)) die(_("cannot get patch id")); @@@ -1385,6 -1393,7 +1393,7 @@@ oidcpy(patch_id, &oid); bases->nr_patch_id++; } + clear_commit_base(&commit_base); } static void print_bases(struct base_tree_info *bases, FILE *file) @@@ -1474,9 -1483,9 +1483,9 @@@ int cmd_format_patch(int argc, const ch N_("output all-zero hash in From header")), OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream, N_("don't include a patch matching a commit upstream")), - { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL, - N_("show patch format instead of default (patch + stat)"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1}, + OPT_SET_INT_F('p', "no-stat", &use_patch_format, + N_("show patch format instead of default (patch + stat)"), + 1, PARSE_OPT_NONEG), OPT_GROUP(N_("Messaging")), { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"), N_("add email header"), 0, header_callback }, diff --combined builtin/merge.c index b00d6f4821,fc55bc264b..fb4faca250 --- a/builtin/merge.c +++ b/builtin/merge.c @@@ -14,7 -14,6 +14,7 @@@ #include "run-command.h" #include "diff.h" #include "refs.h" +#include "refspec.h" #include "commit.h" #include "diffcore.h" #include "revision.h" @@@ -35,7 -34,6 +35,7 @@@ #include "string-list.h" #include "packfile.h" #include "tag.h" +#include "alias.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@@ -215,9 -213,9 +215,9 @@@ static struct option builtin_merge_opti OPT_BOOL('e', "edit", &option_edit, N_("edit message before committing")), OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW), - { OPTION_SET_INT, 0, "ff-only", &fast_forward, NULL, - N_("abort if fast-forward is not possible"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY }, + OPT_SET_INT_F(0, "ff-only", &fast_forward, + N_("abort if fast-forward is not possible"), + FF_ONLY, PARSE_OPT_NONEG), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), OPT_BOOL(0, "verify-signatures", &verify_signatures, N_("verify that the named commit has a valid GPG signature")), @@@ -282,7 -280,7 +282,7 @@@ out return rc; } -static void read_empty(unsigned const char *sha1, int verbose) +static void read_empty(const struct object_id *oid, int verbose) { int i = 0; const char *args[7]; @@@ -292,15 -290,15 +292,15 @@@ args[i++] = "-v"; args[i++] = "-m"; args[i++] = "-u"; - args[i++] = EMPTY_TREE_SHA1_HEX; - args[i++] = sha1_to_hex(sha1); + args[i++] = empty_tree_oid_hex(); + args[i++] = oid_to_hex(oid); args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) die(_("read-tree failed")); } -static void reset_hard(unsigned const char *sha1, int verbose) +static void reset_hard(const struct object_id *oid, int verbose) { int i = 0; const char *args[6]; @@@ -310,7 -308,7 +310,7 @@@ args[i++] = "-v"; args[i++] = "--reset"; args[i++] = "-u"; - args[i++] = sha1_to_hex(sha1); + args[i++] = oid_to_hex(oid); args[i] = NULL; if (run_command_v_opt(args, RUN_GIT_CMD)) @@@ -326,7 -324,7 +326,7 @@@ static void restore_state(const struct if (is_null_oid(stash)) return; - reset_hard(head->hash, 1); + reset_hard(head, 1); args[2] = oid_to_hex(stash); @@@ -445,6 -443,7 +445,7 @@@ static void merge_name(const char *remo struct object_id branch_head; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; + struct merge_remote_desc *desc; const char *ptr; char *found_ref; int len, early; @@@ -517,16 -516,13 +518,13 @@@ strbuf_release(&truname); } - if (remote_head->util) { - struct merge_remote_desc *desc; - desc = merge_remote_util(remote_head); - if (desc && desc->obj && desc->obj->type == OBJ_TAG) { - strbuf_addf(msg, "%s\t\t%s '%s'\n", - oid_to_hex(&desc->obj->oid), - type_name(desc->obj->type), - remote); - goto cleanup; - } + desc = merge_remote_util(remote_head); + if (desc && desc->obj && desc->obj->type == OBJ_TAG) { + strbuf_addf(msg, "%s\t\t%s '%s'\n", + oid_to_hex(&desc->obj->oid), + type_name(desc->obj->type), + remote); + goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", @@@ -649,7 -645,7 +647,7 @@@ static int try_merge_strategy(const cha struct commit_list *remoteheads, struct commit *head) { - static struct lock_file lock; + struct lock_file lock = LOCK_INIT; const char *head_arg = "HEAD"; hold_locked_index(&lock, LOCK_DIE_ON_ERROR); @@@ -807,7 -803,7 +805,7 @@@ static int merge_trivial(struct commit { struct object_id result_tree, result_commit; struct commit_list *parents, **pptr = &parents; - static struct lock_file lock; + struct lock_file lock = LOCK_INIT; hold_locked_index(&lock, LOCK_DIE_ON_ERROR); refresh_cache(REFRESH_QUIET); @@@ -934,8 -930,11 +932,11 @@@ static void write_merge_heads(struct co for (j = remoteheads; j; j = j->next) { struct object_id *oid; struct commit *c = j->item; - if (c->util && merge_remote_util(c)->obj) { - oid = &merge_remote_util(c)->obj->oid; + struct merge_remote_desc *desc; + + desc = merge_remote_util(c); + if (desc && desc->obj) { + oid = &desc->obj->oid; } else { oid = &c->object.oid; } @@@ -1299,7 -1298,7 +1300,7 @@@ int cmd_merge(int argc, const char **ar if (remoteheads->next) die(_("Can merge only exactly one commit into empty head")); remote_head_oid = &remoteheads->item->object.oid; - read_empty(remote_head_oid->hash, 0); + read_empty(remote_head_oid, 0); update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); goto done; diff --combined commit.c index 0030e79940,e63a8dfeaa..2d4431a21f --- a/commit.c +++ b/commit.c @@@ -13,7 -13,6 +13,7 @@@ #include "prio-queue.h" #include "sha1-lookup.h" #include "wt-status.h" +#include "advice.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@@ -178,15 -177,6 +178,15 @@@ static int read_graft_file(const char * struct strbuf buf = STRBUF_INIT; if (!fp) return -1; + if (advice_graft_file_deprecated) + advise(_("Support for /info/grafts is deprecated\n" + "and will be removed in a future Git version.\n" + "\n" + "Please use \"git replace --convert-graft-file\"\n" + "to convert the grafts into replace refs.\n" + "\n" + "Turn this message off by running\n" + "\"git config advice.graftFileDeprecated false\"")); while (!strbuf_getwholeline(&buf, fp, '\n')) { /* The format is just "Commit Parent1 Parent2 ...\n" */ struct commit_graft *graft = read_graft_line(&buf); @@@ -207,9 -197,6 +207,9 @@@ static void prepare_commit_graft(void if (commit_graft_prepared) return; + if (!startup_info->have_repository) + return; + graft_file = get_graft_file(); read_graft_file(graft_file); /* make sure shallows are read */ @@@ -309,22 -296,6 +309,22 @@@ void free_commit_buffer(struct commit * } } +struct tree *get_commit_tree(const struct commit *commit) +{ + if (commit->maybe_tree || !commit->object.parsed) + return commit->maybe_tree; + + if (commit->graph_pos == COMMIT_NOT_FROM_GRAPH) + BUG("commit has NULL tree, but was not loaded from commit-graph"); + + return get_commit_tree_in_graph(commit); +} + +struct object_id *get_commit_tree_oid(const struct commit *commit) +{ + return &get_commit_tree(commit)->object.oid; +} + const void *detach_commit_buffer(struct commit *commit, unsigned long *sizep) { struct commit_buffer *v = buffer_slab_peek(&buffer_slab, commit); @@@ -361,10 -332,10 +361,10 @@@ int parse_commit_buffer(struct commit * if (tail <= bufptr + tree_entry_len + 1 || memcmp(bufptr, "tree ", 5) || bufptr[tree_entry_len] != '\n') return error("bogus commit object %s", oid_to_hex(&item->object.oid)); - if (get_sha1_hex(bufptr + 5, parent.hash) < 0) + if (get_oid_hex(bufptr + 5, &parent) < 0) return error("bad tree pointer in commit %s", oid_to_hex(&item->object.oid)); - item->tree = lookup_tree(&parent); + item->maybe_tree = lookup_tree(&parent); bufptr += tree_entry_len + 1; /* "tree " + "hex sha1" + "\n" */ pptr = &item->parents; @@@ -373,7 -344,7 +373,7 @@@ struct commit *new_parent; if (tail <= bufptr + parent_entry_len + 1 || - get_sha1_hex(bufptr + 7, parent.hash) || + get_oid_hex(bufptr + 7, &parent) || bufptr[parent_entry_len] != '\n') return error("bad parents in commit %s", oid_to_hex(&item->object.oid)); bufptr += parent_entry_len + 1; @@@ -1320,19 -1291,17 +1320,19 @@@ struct commit_extra_header *read_commit return extra; } -void for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data) +int for_each_mergetag(each_mergetag_fn fn, struct commit *commit, void *data) { struct commit_extra_header *extra, *to_free; + int res = 0; to_free = read_commit_extra_headers(commit, NULL); - for (extra = to_free; extra; extra = extra->next) { + for (extra = to_free; !res && extra; extra = extra->next) { if (strcmp(extra->key, "mergetag")) continue; /* not a merge tag */ - fn(commit, extra, data); + res = fn(commit, extra, data); } free_commit_extra_headers(to_free); + return res; } static inline int standard_header_field(const char *field, size_t len) @@@ -1605,13 -1574,21 +1605,21 @@@ out return result; } + define_commit_slab(merge_desc_slab, struct merge_remote_desc *); + static struct merge_desc_slab merge_desc_slab = COMMIT_SLAB_INIT(1, merge_desc_slab); + + struct merge_remote_desc *merge_remote_util(struct commit *commit) + { + return *merge_desc_slab_at(&merge_desc_slab, commit); + } + void set_merge_remote_desc(struct commit *commit, const char *name, struct object *obj) { struct merge_remote_desc *desc; FLEX_ALLOC_STR(desc, name, name); desc->obj = obj; - commit->util = desc; + *merge_desc_slab_at(&merge_desc_slab, commit) = desc; } struct commit *get_merge_parent(const char *name) @@@ -1623,7 -1600,7 +1631,7 @@@ return NULL; obj = parse_object(&oid); commit = (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); - if (commit && !commit->util) + if (commit && !merge_remote_util(commit)) set_merge_remote_desc(commit, name, obj); return commit; } diff --combined commit.h index c3af512f8b,4432458367..e48f05dffc --- a/commit.h +++ b/commit.h @@@ -16,20 -16,18 +16,24 @@@ struct commit_list struct commit_list *next; }; + /* + * The size of this struct matters in full repo walk operations like + * 'git clone' or 'git gc'. Consider using commit-slab to attach data + * to a commit instead of adding new fields here. + */ struct commit { struct object object; - void *util; - unsigned int index; timestamp_t date; struct commit_list *parents; - struct tree *tree; + + /* + * If the commit is loaded from the commit-graph file, then this + * member may be NULL. Only access it through get_commit_tree() + * or get_commit_tree_oid(). + */ + struct tree *maybe_tree; uint32_t graph_pos; + unsigned int index; }; extern int save_commit_buffer; @@@ -108,9 -106,6 +112,9 @@@ void unuse_commit_buffer(const struct c */ void free_commit_buffer(struct commit *); +struct tree *get_commit_tree(const struct commit *); +struct object_id *get_commit_tree_oid(const struct commit *); + /* * Disassociate any cached object buffer from the commit, but do not free it. * The buffer (or NULL, if none) is returned. @@@ -303,16 -298,16 +307,16 @@@ extern const char *find_commit_header(c /* Find the end of the log message, the right place for a new trailer. */ extern int ignore_non_trailer(const char *buf, size_t len); -typedef void (*each_mergetag_fn)(struct commit *commit, struct commit_extra_header *extra, +typedef int (*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); +extern int 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 */ char name[FLEX_ARRAY]; }; - #define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util)) + extern struct merge_remote_desc *merge_remote_util(struct commit *); extern void set_merge_remote_desc(struct commit *commit, const char *name, struct object *obj); diff --combined log-tree.c index 4aef85331e,0b41ee3235..0b97de5e87 --- a/log-tree.c +++ b/log-tree.c @@@ -295,8 -295,12 +295,12 @@@ void show_decorations(struct rev_info * { struct strbuf sb = STRBUF_INIT; - if (opt->show_source && commit->util) - fprintf(opt->diffopt.file, "\t%s", (char *) commit->util); + if (opt->sources) { + char **slot = revision_sources_peek(opt->sources, commit); + + if (slot && *slot) + fprintf(opt->diffopt.file, "\t%s", *slot); + } if (!opt->show_decorations) return; format_decorations(&sb, commit, opt->diffopt.use_color); @@@ -362,8 -366,7 +366,8 @@@ void fmt_output_email_subject(struct st void log_write_email_headers(struct rev_info *opt, struct commit *commit, const char **extra_headers_p, - int *need_8bit_cte_p) + int *need_8bit_cte_p, + int maybe_multipart) { const char *extra_headers = opt->extra_headers; const char *name = oid_to_hex(opt->zero_commit ? @@@ -386,16 -389,12 +390,16 @@@ opt->ref_message_ids->items[i].string); graph_show_oneline(opt->graph); } - if (opt->mime_boundary) { - static char subject_buffer[1024]; - static char buffer[1024]; + if (opt->mime_boundary && maybe_multipart) { + static struct strbuf subject_buffer = STRBUF_INIT; + static struct strbuf buffer = STRBUF_INIT; struct strbuf filename = STRBUF_INIT; *need_8bit_cte_p = -1; /* NEVER */ - snprintf(subject_buffer, sizeof(subject_buffer) - 1, + + strbuf_reset(&subject_buffer); + strbuf_reset(&buffer); + + strbuf_addf(&subject_buffer, "%s" "MIME-Version: 1.0\n" "Content-Type: multipart/mixed;" @@@ -410,13 -409,13 +414,13 @@@ extra_headers ? extra_headers : "", mime_boundary_leader, opt->mime_boundary, mime_boundary_leader, opt->mime_boundary); - extra_headers = subject_buffer; + extra_headers = subject_buffer.buf; if (opt->numbered_files) strbuf_addf(&filename, "%d", opt->nr); else fmt_output_commit(&filename, commit, opt); - snprintf(buffer, sizeof(buffer) - 1, + strbuf_addf(&buffer, "\n--%s%s\n" "Content-Type: text/x-patch;" " name=\"%s\"\n" @@@ -427,7 -426,7 +431,7 @@@ filename.buf, opt->no_inline ? "attachment" : "inline", filename.buf); - opt->diffopt.stat_sep = buffer; + opt->diffopt.stat_sep = buffer.buf; strbuf_release(&filename); } *extra_headers_p = extra_headers; @@@ -493,9 -492,9 +497,9 @@@ static int is_common_merge(const struc && !commit->parents->next->next); } -static void show_one_mergetag(struct commit *commit, - struct commit_extra_header *extra, - void *data) +static int show_one_mergetag(struct commit *commit, + struct commit_extra_header *extra, + void *data) { struct rev_info *opt = (struct rev_info *)data; struct object_id oid; @@@ -507,7 -506,7 +511,7 @@@ hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &oid); tag = lookup_tag(&oid); if (!tag) - return; /* error message already given */ + return -1; /* error message already given */ strbuf_init(&verify_message, 256); if (parse_tag_buffer(tag, extra->value, extra->len)) @@@ -541,12 -540,11 +545,12 @@@ show_sig_lines(opt, status, verify_message.buf); strbuf_release(&verify_message); + return 0; } -static void show_mergetag(struct rev_info *opt, struct commit *commit) +static int show_mergetag(struct rev_info *opt, struct commit *commit) { - for_each_mergetag(show_one_mergetag, commit, opt); + return for_each_mergetag(show_one_mergetag, commit, opt); } void show_log(struct rev_info *opt) @@@ -616,7 -614,7 +620,7 @@@ if (cmit_fmt_is_mail(opt->commit_format)) { log_write_email_headers(opt, commit, &extra_headers, - &ctx.need_8bit_cte); + &ctx.need_8bit_cte, 1); ctx.rev = opt; ctx.print_email_subject = 1; } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { @@@ -812,7 -810,7 +816,7 @@@ static int log_tree_diff(struct rev_inf return 0; parse_commit_or_die(commit); - oid = &commit->tree->object.oid; + oid = get_commit_tree_oid(commit); /* Root commit? */ parents = get_saved_parents(opt, commit); @@@ -837,7 -835,7 +841,7 @@@ * we merged _in_. */ parse_commit_or_die(parents->item); - diff_tree_oid(&parents->item->tree->object.oid, + diff_tree_oid(get_commit_tree_oid(parents->item), oid, "", &opt->diffopt); log_tree_diff_flush(opt); return !opt->loginfo; @@@ -852,7 -850,7 +856,7 @@@ struct commit *parent = parents->item; parse_commit_or_die(parent); - diff_tree_oid(&parent->tree->object.oid, + diff_tree_oid(get_commit_tree_oid(parent), oid, "", &opt->diffopt); log_tree_diff_flush(opt); diff --combined merge-recursive.c index f110e1c5ec,5537f01f8e..404f050caf --- a/merge-recursive.c +++ b/merge-recursive.c @@@ -23,7 -23,6 +23,7 @@@ #include "merge-recursive.h" #include "dir.h" #include "submodule.h" +#include "revision.h" struct path_hashmap_entry { struct hashmap_entry e; @@@ -50,67 -49,6 +50,67 @@@ static unsigned int path_hash(const cha return ignore_case ? strihash(path) : strhash(path); } +static struct dir_rename_entry *dir_rename_find_entry(struct hashmap *hashmap, + char *dir) +{ + struct dir_rename_entry key; + + if (dir == NULL) + return NULL; + hashmap_entry_init(&key, strhash(dir)); + key.dir = dir; + return hashmap_get(hashmap, &key, NULL); +} + +static int dir_rename_cmp(const void *unused_cmp_data, + const void *entry, + const void *entry_or_key, + const void *unused_keydata) +{ + const struct dir_rename_entry *e1 = entry; + const struct dir_rename_entry *e2 = entry_or_key; + + return strcmp(e1->dir, e2->dir); +} + +static void dir_rename_init(struct hashmap *map) +{ + hashmap_init(map, dir_rename_cmp, NULL, 0); +} + +static void dir_rename_entry_init(struct dir_rename_entry *entry, + char *directory) +{ + hashmap_entry_init(entry, strhash(directory)); + entry->dir = directory; + entry->non_unique_new_dir = 0; + strbuf_init(&entry->new_dir, 0); + string_list_init(&entry->possible_new_dirs, 0); +} + +static struct collision_entry *collision_find_entry(struct hashmap *hashmap, + char *target_file) +{ + struct collision_entry key; + + hashmap_entry_init(&key, strhash(target_file)); + key.target_file = target_file; + return hashmap_get(hashmap, &key, NULL); +} + +static int collision_cmp(void *unused_cmp_data, + const struct collision_entry *e1, + const struct collision_entry *e2, + const void *unused_keydata) +{ + return strcmp(e1->target_file, e2->target_file); +} + +static void collision_init(struct hashmap *map) +{ + hashmap_init(map, (hashmap_cmp_fn) collision_cmp, NULL, 0); +} + static void flush_output(struct merge_options *o) { if (o->buffer_output < 2 && o->obuf.len) { @@@ -163,7 -101,7 +163,7 @@@ static struct commit *make_virtual_comm struct commit *commit = alloc_commit_node(); set_merge_remote_desc(commit, comment, (struct object *)commit); - commit->tree = tree; + commit->maybe_tree = tree; commit->object.parsed = 1; return commit; } @@@ -181,7 -119,6 +181,7 @@@ static int oid_eq(const struct object_i enum rename_type { RENAME_NORMAL = 0, + RENAME_DIR, RENAME_DELETE, RENAME_ONE_FILE_TO_ONE, RENAME_ONE_FILE_TO_TWO, @@@ -286,10 -223,12 +286,12 @@@ static void output(struct merge_option static void output_commit_title(struct merge_options *o, struct commit *commit) { + struct merge_remote_desc *desc; + strbuf_addchars(&o->obuf, ' ', o->call_depth * 2); - if (commit->util) - strbuf_addf(&o->obuf, "virtual %s\n", - merge_remote_util(commit)->name); + desc = merge_remote_util(commit); + if (desc) + strbuf_addf(&o->obuf, "virtual %s\n", desc->name); else { strbuf_add_unique_abbrev(&o->obuf, &commit->object.oid, DEFAULT_ABBREV); @@@ -317,7 -256,7 +319,7 @@@ static int add_cacheinfo(struct merge_o ce = make_cache_entry(mode, oid ? oid->hash : null_sha1, path, stage, 0); if (!ce) - return err(o, _("addinfo_cache failed for path '%s'"), path); + return err(o, _("add_cacheinfo failed for path '%s'; merge aborting."), path); ret = add_cache_entry(ce, options); if (refresh) { @@@ -325,7 -264,7 +327,7 @@@ nce = refresh_cache_entry(ce, CE_MATCH_REFRESH | CE_MATCH_IGNORE_MISSING); if (!nce) - return err(o, _("addinfo_cache failed for path '%s'"), path); + return err(o, _("add_cacheinfo failed to refresh for path '%s'; merge aborting."), path); if (nce != ce) ret = add_cache_entry(nce, options); } @@@ -338,54 -277,36 +340,54 @@@ static void init_tree_desc_from_tree(st init_tree_desc(desc, tree->buffer, tree->size); } -static int git_merge_trees(int index_only, - struct tree *common, - struct tree *head, - struct tree *merge) +static int unpack_trees_start(struct merge_options *o, + struct tree *common, + struct tree *head, + struct tree *merge) { int rc; struct tree_desc t[3]; - struct unpack_trees_options opts; + struct index_state tmp_index = { NULL }; - memset(&opts, 0, sizeof(opts)); - if (index_only) - opts.index_only = 1; + memset(&o->unpack_opts, 0, sizeof(o->unpack_opts)); + if (o->call_depth) + o->unpack_opts.index_only = 1; else - opts.update = 1; - opts.merge = 1; - opts.head_idx = 2; - opts.fn = threeway_merge; - opts.src_index = &the_index; - opts.dst_index = &the_index; - setup_unpack_trees_porcelain(&opts, "merge"); + o->unpack_opts.update = 1; + o->unpack_opts.merge = 1; + o->unpack_opts.head_idx = 2; + o->unpack_opts.fn = threeway_merge; + o->unpack_opts.src_index = &the_index; + o->unpack_opts.dst_index = &tmp_index; + o->unpack_opts.aggressive = !merge_detect_rename(o); + setup_unpack_trees_porcelain(&o->unpack_opts, "merge"); init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); init_tree_desc_from_tree(t+2, merge); - rc = unpack_trees(3, t, &opts); + rc = unpack_trees(3, t, &o->unpack_opts); cache_tree_free(&active_cache_tree); + + /* + * Update the_index to match the new results, AFTER saving a copy + * in o->orig_index. Update src_index to point to the saved copy. + * (verify_uptodate() checks src_index, and the original index is + * the one that had the necessary modification timestamps.) + */ + o->orig_index = the_index; + the_index = tmp_index; + o->unpack_opts.src_index = &o->orig_index; + return rc; } +static void unpack_trees_finish(struct merge_options *o) +{ + discard_index(&o->orig_index); + clear_unpack_trees_porcelain(&o->unpack_opts); +} + struct tree *write_tree_from_memory(struct merge_options *o) { struct tree *result = NULL; @@@ -399,7 -320,7 +401,7 @@@ fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce), (int)ce_namelen(ce), ce->name); } - die("BUG: unmerged index entries in merge-recursive.c"); + BUG("unmerged index entries in merge-recursive.c"); } if (!active_cache_tree) @@@ -441,21 -362,6 +443,21 @@@ static void get_files_dirs(struct merge read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o); } +static int get_tree_entry_if_blob(const struct object_id *tree, + const char *path, + struct object_id *hashy, + unsigned int *mode_o) +{ + int ret; + + ret = get_tree_entry(tree, path, hashy, mode_o); + if (S_ISDIR(*mode_o)) { + oidcpy(hashy, &null_oid); + *mode_o = 0; + } + return ret; +} + /* * Returns an index_entry instance which doesn't have to correspond to * a real cache entry in Git's index. @@@ -466,12 -372,12 +468,12 @@@ static struct stage_data *insert_stage_ { struct string_list_item *item; struct stage_data *e = xcalloc(1, sizeof(struct stage_data)); - get_tree_entry(&o->object.oid, path, - &e->stages[1].oid, &e->stages[1].mode); - get_tree_entry(&a->object.oid, path, - &e->stages[2].oid, &e->stages[2].mode); - get_tree_entry(&b->object.oid, path, - &e->stages[3].oid, &e->stages[3].mode); + get_tree_entry_if_blob(&o->object.oid, path, + &e->stages[1].oid, &e->stages[1].mode); + get_tree_entry_if_blob(&a->object.oid, path, + &e->stages[2].oid, &e->stages[2].mode); + get_tree_entry_if_blob(&b->object.oid, path, + &e->stages[3].oid, &e->stages[3].mode); item = string_list_insert(entries, path); item->util = e; return e; @@@ -630,10 -536,78 +632,10 @@@ struct rename */ struct stage_data *src_entry; struct stage_data *dst_entry; + unsigned add_turned_into_rename:1; unsigned processed:1; }; -/* - * Get information of all renames which occurred between 'o_tree' and - * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and - * 'b_tree') to be able to associate the correct cache entries with - * the rename information. 'tree' is always equal to either a_tree or b_tree. - */ -static struct string_list *get_renames(struct merge_options *o, - struct tree *tree, - struct tree *o_tree, - struct tree *a_tree, - struct tree *b_tree, - struct string_list *entries) -{ - int i; - struct string_list *renames; - struct diff_options opts; - - renames = xcalloc(1, sizeof(struct string_list)); - if (!o->detect_rename) - return renames; - - diff_setup(&opts); - opts.flags.recursive = 1; - opts.flags.rename_empty = 0; - opts.detect_rename = DIFF_DETECT_RENAME; - opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit : - o->diff_rename_limit >= 0 ? o->diff_rename_limit : - 1000; - opts.rename_score = o->rename_score; - opts.show_rename_progress = o->show_rename_progress; - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_setup_done(&opts); - diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts); - diffcore_std(&opts); - if (opts.needed_rename_limit > o->needed_rename_limit) - o->needed_rename_limit = opts.needed_rename_limit; - for (i = 0; i < diff_queued_diff.nr; ++i) { - struct string_list_item *item; - struct rename *re; - struct diff_filepair *pair = diff_queued_diff.queue[i]; - if (pair->status != 'R') { - diff_free_filepair(pair); - continue; - } - re = xmalloc(sizeof(*re)); - re->processed = 0; - re->pair = pair; - item = string_list_lookup(entries, re->pair->one->path); - if (!item) - re->src_entry = insert_stage_data(re->pair->one->path, - o_tree, a_tree, b_tree, entries); - else - re->src_entry = item->util; - - item = string_list_lookup(entries, re->pair->two->path); - if (!item) - re->dst_entry = insert_stage_data(re->pair->two->path, - o_tree, a_tree, b_tree, entries); - else - re->dst_entry = item->util; - item = string_list_insert(renames, pair->one->path); - item->util = re; - } - opts.output_format = DIFF_FORMAT_NO_OUTPUT; - diff_queued_diff.nr = 0; - diff_flush(&opts); - return renames; -} - static int update_stages(struct merge_options *opt, const char *path, const struct diff_filespec *o, const struct diff_filespec *a, @@@ -665,27 -639,6 +667,27 @@@ return 0; } +static int update_stages_for_stage_data(struct merge_options *opt, + const char *path, + const struct stage_data *stage_data) +{ + struct diff_filespec o, a, b; + + o.mode = stage_data->stages[1].mode; + oidcpy(&o.oid, &stage_data->stages[1].oid); + + a.mode = stage_data->stages[2].mode; + oidcpy(&a.oid, &stage_data->stages[2].oid); + + b.mode = stage_data->stages[3].mode; + oidcpy(&b.oid, &stage_data->stages[3].oid); + + return update_stages(opt, path, + is_null_oid(&o.oid) ? NULL : &o, + is_null_oid(&a.oid) ? NULL : &a, + is_null_oid(&b.oid) ? NULL : &b); +} + static void update_entry(struct stage_data *entry, struct diff_filespec *o, struct diff_filespec *a, @@@ -787,92 -740,31 +789,92 @@@ static int dir_in_way(const char *path !(empty_ok && is_empty_dir(path)); } -static int was_tracked(const char *path) +/* + * Returns whether path was tracked in the index before the merge started, + * and its oid and mode match the specified values + */ +static int was_tracked_and_matches(struct merge_options *o, const char *path, + const struct object_id *oid, unsigned mode) { - int pos = cache_name_pos(path, strlen(path)); + int pos = index_name_pos(&o->orig_index, path, strlen(path)); + struct cache_entry *ce; + + if (0 > pos) + /* we were not tracking this path before the merge */ + return 0; + + /* See if the file we were tracking before matches */ + ce = o->orig_index.cache[pos]; + return (oid_eq(&ce->oid, oid) && ce->ce_mode == mode); +} + +/* + * Returns whether path was tracked in the index before the merge started + */ +static int was_tracked(struct merge_options *o, const char *path) +{ + int pos = index_name_pos(&o->orig_index, path, strlen(path)); if (0 <= pos) - /* we have been tracking this path */ + /* we were tracking this path before the merge */ return 1; - /* - * Look for an unmerged entry for the path, - * specifically stage #2, which would indicate - * that "our" side before the merge started - * had the path tracked (and resulted in a conflict). - */ - for (pos = -1 - pos; - pos < active_nr && !strcmp(path, active_cache[pos]->name); - pos++) - if (ce_stage(active_cache[pos]) == 2) - return 1; return 0; } static int would_lose_untracked(const char *path) { - return !was_tracked(path) && file_exists(path); + /* + * This may look like it can be simplified to: + * return !was_tracked(o, path) && file_exists(path) + * but it can't. This function needs to know whether path was in + * the working tree due to EITHER having been tracked in the index + * before the merge OR having been put into the working copy and + * index by unpack_trees(). Due to that either-or requirement, we + * check the current index instead of the original one. + * + * Note that we do not need to worry about merge-recursive itself + * updating the index after unpack_trees() and before calling this + * function, because we strictly require all code paths in + * merge-recursive to update the working tree first and the index + * second. Doing otherwise would break + * update_file()/would_lose_untracked(); see every comment in this + * file which mentions "update_stages". + */ + int pos = cache_name_pos(path, strlen(path)); + + if (pos < 0) + pos = -1 - pos; + while (pos < active_nr && + !strcmp(path, active_cache[pos]->name)) { + /* + * If stage #0, it is definitely tracked. + * If it has stage #2 then it was tracked + * before this merge started. All other + * cases the path was not tracked. + */ + switch (ce_stage(active_cache[pos])) { + case 0: + case 2: + return 0; + } + pos++; + } + return file_exists(path); +} + +static int was_dirty(struct merge_options *o, const char *path) +{ + struct cache_entry *ce; + int dirty = 1; + + if (o->call_depth || !was_tracked(o, path)) + return !dirty; + + ce = index_file_exists(o->unpack_opts.src_index, + path, strlen(path), ignore_case); + dirty = verify_uptodate(ce, &o->unpack_opts) != 0; + return dirty; } static int make_room_for_path(struct merge_options *o, const char *path) @@@ -1003,9 -895,7 +1005,9 @@@ static int update_file_flags(struct mer } update_index: if (!ret && update_cache) - add_cacheinfo(o, mode, oid, path, 0, update_wd, ADD_CACHE_OK_TO_ADD); + if (add_cacheinfo(o, mode, oid, path, 0, update_wd, + ADD_CACHE_OK_TO_ADD)) + return -1; return ret; } @@@ -1089,193 -979,13 +1091,193 @@@ static int merge_3way(struct merge_opti return merge_status; } +static int find_first_merges(struct object_array *result, const char *path, + struct commit *a, struct commit *b) +{ + int i, j; + struct object_array merges = OBJECT_ARRAY_INIT; + struct commit *commit; + int contains_another; + + char merged_revision[42]; + const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path", + "--all", merged_revision, NULL }; + struct rev_info revs; + struct setup_revision_opt rev_opts; + + memset(result, 0, sizeof(struct object_array)); + memset(&rev_opts, 0, sizeof(rev_opts)); + + /* get all revisions that merge commit a */ + xsnprintf(merged_revision, sizeof(merged_revision), "^%s", + oid_to_hex(&a->object.oid)); + init_revisions(&revs, NULL); + rev_opts.submodule = path; + /* FIXME: can't handle linked worktrees in submodules yet */ + revs.single_worktree = path != NULL; + setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts); + + /* save all revisions from the above list that contain b */ + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); + while ((commit = get_revision(&revs)) != NULL) { + struct object *o = &(commit->object); + if (in_merge_bases(b, commit)) + add_object_array(o, NULL, &merges); + } + reset_revision_walk(); + + /* Now we've got all merges that contain a and b. Prune all + * merges that contain another found merge and save them in + * result. + */ + for (i = 0; i < merges.nr; i++) { + struct commit *m1 = (struct commit *) merges.objects[i].item; + + contains_another = 0; + for (j = 0; j < merges.nr; j++) { + struct commit *m2 = (struct commit *) merges.objects[j].item; + if (i != j && in_merge_bases(m2, m1)) { + contains_another = 1; + break; + } + } + + if (!contains_another) + add_object_array(merges.objects[i].item, NULL, result); + } + + object_array_clear(&merges); + return result->nr; +} + +static void print_commit(struct commit *commit) +{ + struct strbuf sb = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + ctx.date_mode.type = DATE_NORMAL; + format_commit_message(commit, " %h: %m %s", &sb, &ctx); + fprintf(stderr, "%s\n", sb.buf); + strbuf_release(&sb); +} + +static int merge_submodule(struct merge_options *o, + struct object_id *result, const char *path, + const struct object_id *base, const struct object_id *a, + const struct object_id *b) +{ + struct commit *commit_base, *commit_a, *commit_b; + int parent_count; + struct object_array merges; + + int i; + int search = !o->call_depth; + + /* store a in result in case we fail */ + oidcpy(result, a); + + /* we can not handle deletion conflicts */ + if (is_null_oid(base)) + return 0; + if (is_null_oid(a)) + return 0; + if (is_null_oid(b)) + return 0; + + if (add_submodule_odb(path)) { + output(o, 1, _("Failed to merge submodule %s (not checked out)"), path); + return 0; + } + + if (!(commit_base = lookup_commit_reference(base)) || + !(commit_a = lookup_commit_reference(a)) || + !(commit_b = lookup_commit_reference(b))) { + output(o, 1, _("Failed to merge submodule %s (commits not present)"), path); + return 0; + } + + /* check whether both changes are forward */ + if (!in_merge_bases(commit_base, commit_a) || + !in_merge_bases(commit_base, commit_b)) { + output(o, 1, _("Failed to merge submodule %s (commits don't follow merge-base)"), path); + return 0; + } + + /* Case #1: a is contained in b or vice versa */ + if (in_merge_bases(commit_a, commit_b)) { + oidcpy(result, b); + if (show(o, 3)) { + output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path); + output_commit_title(o, commit_b); + } else if (show(o, 2)) + output(o, 2, _("Fast-forwarding submodule %s"), path); + else + ; /* no output */ + + return 1; + } + if (in_merge_bases(commit_b, commit_a)) { + oidcpy(result, a); + if (show(o, 3)) { + output(o, 3, _("Fast-forwarding submodule %s to the following commit:"), path); + output_commit_title(o, commit_a); + } else if (show(o, 2)) + output(o, 2, _("Fast-forwarding submodule %s"), path); + else + ; /* no output */ + + return 1; + } + + /* + * Case #2: There are one or more merges that contain a and b in + * the submodule. If there is only one, then present it as a + * suggestion to the user, but leave it marked unmerged so the + * user needs to confirm the resolution. + */ + + /* Skip the search if makes no sense to the calling context. */ + if (!search) + return 0; + + /* find commit which merges them */ + parent_count = find_first_merges(&merges, path, commit_a, commit_b); + switch (parent_count) { + case 0: + output(o, 1, _("Failed to merge submodule %s (merge following commits not found)"), path); + break; + + case 1: + output(o, 1, _("Failed to merge submodule %s (not fast-forward)"), path); + output(o, 2, _("Found a possible merge resolution for the submodule:\n")); + print_commit((struct commit *) merges.objects[0].item); + output(o, 2, _( + "If this is correct simply add it to the index " + "for example\n" + "by using:\n\n" + " git update-index --cacheinfo 160000 %s \"%s\"\n\n" + "which will accept this suggestion.\n"), + oid_to_hex(&merges.objects[0].item->oid), path); + break; + + default: + output(o, 1, _("Failed to merge submodule %s (multiple merges found)"), path); + for (i = 0; i < merges.nr; i++) + print_commit((struct commit *) merges.objects[i].item); + } + + object_array_clear(&merges); + return 0; +} + static int merge_file_1(struct merge_options *o, - const struct diff_filespec *one, - const struct diff_filespec *a, - const struct diff_filespec *b, - const char *branch1, - const char *branch2, - struct merge_file_info *result) + const struct diff_filespec *one, + const struct diff_filespec *a, + const struct diff_filespec *b, + const char *filename, + const char *branch1, + const char *branch2, + struct merge_file_info *result) { result->merge = 0; result->clean = 1; @@@ -1331,11 -1041,12 +1333,11 @@@ return ret; result->clean = (merge_status == 0); } else if (S_ISGITLINK(a->mode)) { - result->clean = merge_submodule(&result->oid, + result->clean = merge_submodule(o, &result->oid, one->path, &one->oid, &a->oid, - &b->oid, - !o->call_depth); + &b->oid); } else if (S_ISLNK(a->mode)) { switch (o->recursive_variant) { case MERGE_RECURSIVE_NORMAL: @@@ -1351,25 -1062,21 +1353,25 @@@ break; } } else - die("BUG: unsupported object type in the tree"); + BUG("unsupported object type in the tree"); } + if (result->merge) + output(o, 2, _("Auto-merging %s"), filename); + return 0; } static int merge_file_special_markers(struct merge_options *o, - const struct diff_filespec *one, - const struct diff_filespec *a, - const struct diff_filespec *b, - const char *branch1, - const char *filename1, - const char *branch2, - const char *filename2, - struct merge_file_info *mfi) + const struct diff_filespec *one, + const struct diff_filespec *a, + const struct diff_filespec *b, + const char *target_filename, + const char *branch1, + const char *filename1, + const char *branch2, + const char *filename2, + struct merge_file_info *mfi) { char *side1 = NULL; char *side2 = NULL; @@@ -1380,23 -1087,22 +1382,23 @@@ if (filename2) side2 = xstrfmt("%s:%s", branch2, filename2); - ret = merge_file_1(o, one, a, b, + ret = merge_file_1(o, one, a, b, target_filename, side1 ? side1 : branch1, side2 ? side2 : branch2, mfi); + free(side1); free(side2); return ret; } static int merge_file_one(struct merge_options *o, - const char *path, - const struct object_id *o_oid, int o_mode, - const struct object_id *a_oid, int a_mode, - const struct object_id *b_oid, int b_mode, - const char *branch1, - const char *branch2, - struct merge_file_info *mfi) + const char *path, + const struct object_id *o_oid, int o_mode, + const struct object_id *a_oid, int a_mode, + const struct object_id *b_oid, int b_mode, + const char *branch1, + const char *branch2, + struct merge_file_info *mfi) { struct diff_filespec one, a, b; @@@ -1407,39 -1113,7 +1409,39 @@@ a.mode = a_mode; oidcpy(&b.oid, b_oid); b.mode = b_mode; - return merge_file_1(o, &one, &a, &b, branch1, branch2, mfi); + return merge_file_1(o, &one, &a, &b, path, branch1, branch2, mfi); +} + +static int conflict_rename_dir(struct merge_options *o, + struct diff_filepair *pair, + const char *rename_branch, + const char *other_branch) +{ + const struct diff_filespec *dest = pair->two; + + if (!o->call_depth && would_lose_untracked(dest->path)) { + char *alt_path = unique_path(o, dest->path, rename_branch); + + output(o, 1, _("Error: Refusing to lose untracked file at %s; " + "writing to %s instead."), + dest->path, alt_path); + /* + * Write the file in worktree at alt_path, but not in the + * index. Instead, write to dest->path for the index but + * only at the higher appropriate stage. + */ + if (update_file(o, 0, &dest->oid, dest->mode, alt_path)) + return -1; + free(alt_path); + return update_stages(o, dest->path, NULL, + rename_branch == o->branch1 ? dest : NULL, + rename_branch == o->branch1 ? NULL : dest); + } + + /* Update dest->path both in index and in worktree */ + if (update_file(o, 1, &dest->oid, dest->mode, dest->path)) + return -1; + return 0; } static int handle_change_delete(struct merge_options *o, @@@ -1455,8 -1129,7 +1457,8 @@@ const char *update_path = path; int ret = 0; - if (dir_in_way(path, !o->call_depth, 0)) { + if (dir_in_way(path, !o->call_depth, 0) || + (!o->call_depth && would_lose_untracked(path))) { update_path = alt_path = unique_path(o, path, change_branch); } @@@ -1571,34 -1244,17 +1573,34 @@@ static int handle_file(struct merge_opt add = filespec_from_entry(&other, dst_entry, stage ^ 1); if (add) { + int ren_src_was_dirty = was_dirty(o, rename->path); char *add_name = unique_path(o, rename->path, other_branch); if (update_file(o, 0, &add->oid, add->mode, add_name)) return -1; - remove_file(o, 0, rename->path, 0); + if (ren_src_was_dirty) { + output(o, 1, _("Refusing to lose dirty file at %s"), + rename->path); + } + /* + * Because the double negatives somehow keep confusing me... + * 1) update_wd iff !ren_src_was_dirty. + * 2) no_wd iff !update_wd + * 3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty + */ + remove_file(o, 0, rename->path, ren_src_was_dirty); dst_name = unique_path(o, rename->path, cur_branch); } else { if (dir_in_way(rename->path, !o->call_depth, 0)) { dst_name = unique_path(o, rename->path, cur_branch); output(o, 1, _("%s is a directory in %s adding as %s instead"), rename->path, other_branch, dst_name); + } else if (!o->call_depth && + would_lose_untracked(rename->path)) { + dst_name = unique_path(o, rename->path, cur_branch); + output(o, 1, _("Refusing to lose untracked file at %s; " + "adding as %s instead"), + rename->path, dst_name); } } if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name))) @@@ -1685,8 -1341,6 +1687,8 @@@ static int conflict_rename_rename_2to1( struct diff_filespec *c1 = ci->pair1->two; struct diff_filespec *c2 = ci->pair2->two; char *path = c1->path; /* == c2->path */ + char *path_side_1_desc; + char *path_side_2_desc; struct merge_file_info mfi_c1; struct merge_file_info mfi_c2; int ret; @@@ -1700,19 -1354,13 +1702,19 @@@ remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path)); remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path)); + path_side_1_desc = xstrfmt("%s (was %s)", path, a->path); + path_side_2_desc = xstrfmt("%s (was %s)", path, b->path); if (merge_file_special_markers(o, a, c1, &ci->ren1_other, + path_side_1_desc, o->branch1, c1->path, o->branch2, ci->ren1_other.path, &mfi_c1) || merge_file_special_markers(o, b, &ci->ren2_other, c2, + path_side_2_desc, o->branch1, ci->ren2_other.path, o->branch2, c2->path, &mfi_c2)) return -1; + free(path_side_1_desc); + free(path_side_2_desc); if (o->call_depth) { /* @@@ -1732,43 -1380,11 +1734,43 @@@ char *new_path2 = unique_path(o, path, ci->branch2); output(o, 1, _("Renaming %s to %s and %s to %s instead"), a->path, new_path1, b->path, new_path2); - remove_file(o, 0, path, 0); + if (was_dirty(o, path)) + output(o, 1, _("Refusing to lose dirty file at %s"), + path); + else if (would_lose_untracked(path)) + /* + * Only way we get here is if both renames were from + * a directory rename AND user had an untracked file + * at the location where both files end up after the + * two directory renames. See testcase 10d of t6043. + */ + output(o, 1, _("Refusing to lose untracked file at " + "%s, even though it's in the way."), + path); + else + remove_file(o, 0, path, 0); ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1); if (!ret) ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode, new_path2); + /* + * unpack_trees() actually populates the index for us for + * "normal" rename/rename(2to1) situtations so that the + * correct entries are at the higher stages, which would + * make the call below to update_stages_for_stage_data + * unnecessary. However, if either of the renames came + * from a directory rename, then unpack_trees() will not + * have gotten the right data loaded into the index, so we + * need to do so now. (While it'd be tempting to move this + * call to update_stages_for_stage_data() to + * apply_directory_rename_modifications(), that would break + * our intermediate calls to would_lose_untracked() since + * those rely on the current in-memory index. See also the + * big "NOTE" in update_stages()). + */ + if (update_stages_for_stage_data(o, path, ci->dst_entry1)) + ret = -1; + free(new_path2); free(new_path1); } @@@ -1776,781 -1392,25 +1778,781 @@@ return ret; } -static int process_renames(struct merge_options *o, - struct string_list *a_renames, - struct string_list *b_renames) +/* + * Get the diff_filepairs changed between o_tree and tree. + */ +static struct diff_queue_struct *get_diffpairs(struct merge_options *o, + struct tree *o_tree, + struct tree *tree) { - int clean_merge = 1, i, j; - struct string_list a_by_dst = STRING_LIST_INIT_NODUP; - struct string_list b_by_dst = STRING_LIST_INIT_NODUP; - const struct rename *sre; + struct diff_queue_struct *ret; + struct diff_options opts; - for (i = 0; i < a_renames->nr; i++) { - sre = a_renames->items[i].util; - string_list_insert(&a_by_dst, sre->pair->two->path)->util - = (void *)sre; - } - for (i = 0; i < b_renames->nr; i++) { - sre = b_renames->items[i].util; - string_list_insert(&b_by_dst, sre->pair->two->path)->util - = (void *)sre; - } + diff_setup(&opts); + opts.flags.recursive = 1; + opts.flags.rename_empty = 0; + opts.detect_rename = merge_detect_rename(o); + /* + * We do not have logic to handle the detection of copies. In + * fact, it may not even make sense to add such logic: would we + * really want a change to a base file to be propagated through + * multiple other files by a merge? + */ + if (opts.detect_rename > DIFF_DETECT_RENAME) + opts.detect_rename = DIFF_DETECT_RENAME; + opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit : + o->diff_rename_limit >= 0 ? o->diff_rename_limit : + 1000; + opts.rename_score = o->rename_score; + opts.show_rename_progress = o->show_rename_progress; + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_setup_done(&opts); + diff_tree_oid(&o_tree->object.oid, &tree->object.oid, "", &opts); + diffcore_std(&opts); + if (opts.needed_rename_limit > o->needed_rename_limit) + o->needed_rename_limit = opts.needed_rename_limit; + + ret = xmalloc(sizeof(*ret)); + *ret = diff_queued_diff; + + opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_queued_diff.nr = 0; + diff_queued_diff.queue = NULL; + diff_flush(&opts); + return ret; +} + +static int tree_has_path(struct tree *tree, const char *path) +{ + struct object_id hashy; + unsigned int mode_o; + + return !get_tree_entry(&tree->object.oid, path, + &hashy, &mode_o); +} + +/* + * Return a new string that replaces the beginning portion (which matches + * entry->dir), with entry->new_dir. In perl-speak: + * new_path_name = (old_path =~ s/entry->dir/entry->new_dir/); + * NOTE: + * Caller must ensure that old_path starts with entry->dir + '/'. + */ +static char *apply_dir_rename(struct dir_rename_entry *entry, + const char *old_path) +{ + struct strbuf new_path = STRBUF_INIT; + int oldlen, newlen; + + if (entry->non_unique_new_dir) + return NULL; + + oldlen = strlen(entry->dir); + newlen = entry->new_dir.len + (strlen(old_path) - oldlen) + 1; + strbuf_grow(&new_path, newlen); + strbuf_addbuf(&new_path, &entry->new_dir); + strbuf_addstr(&new_path, &old_path[oldlen]); + + return strbuf_detach(&new_path, NULL); +} + +static void get_renamed_dir_portion(const char *old_path, const char *new_path, + char **old_dir, char **new_dir) +{ + char *end_of_old, *end_of_new; + int old_len, new_len; + + *old_dir = NULL; + *new_dir = NULL; + + /* + * For + * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c" + * the "e/foo.c" part is the same, we just want to know that + * "a/b/c/d" was renamed to "a/b/some/thing/else" + * so, for this example, this function returns "a/b/c/d" in + * *old_dir and "a/b/some/thing/else" in *new_dir. + * + * Also, if the basename of the file changed, we don't care. We + * want to know which portion of the directory, if any, changed. + */ + end_of_old = strrchr(old_path, '/'); + end_of_new = strrchr(new_path, '/'); + + if (end_of_old == NULL || end_of_new == NULL) + return; + while (*--end_of_new == *--end_of_old && + end_of_old != old_path && + end_of_new != new_path) + ; /* Do nothing; all in the while loop */ + /* + * We've found the first non-matching character in the directory + * paths. That means the current directory we were comparing + * represents the rename. Move end_of_old and end_of_new back + * to the full directory name. + */ + if (*end_of_old == '/') + end_of_old++; + if (*end_of_old != '/') + end_of_new++; + end_of_old = strchr(end_of_old, '/'); + end_of_new = strchr(end_of_new, '/'); + + /* + * It may have been the case that old_path and new_path were the same + * directory all along. Don't claim a rename if they're the same. + */ + old_len = end_of_old - old_path; + new_len = end_of_new - new_path; + + if (old_len != new_len || strncmp(old_path, new_path, old_len)) { + *old_dir = xstrndup(old_path, old_len); + *new_dir = xstrndup(new_path, new_len); + } +} + +static void remove_hashmap_entries(struct hashmap *dir_renames, + struct string_list *items_to_remove) +{ + int i; + struct dir_rename_entry *entry; + + for (i = 0; i < items_to_remove->nr; i++) { + entry = items_to_remove->items[i].util; + hashmap_remove(dir_renames, entry, NULL); + } + string_list_clear(items_to_remove, 0); +} + +/* + * See if there is a directory rename for path, and if there are any file + * level conflicts for the renamed location. If there is a rename and + * there are no conflicts, return the new name. Otherwise, return NULL. + */ +static char *handle_path_level_conflicts(struct merge_options *o, + const char *path, + struct dir_rename_entry *entry, + struct hashmap *collisions, + struct tree *tree) +{ + char *new_path = NULL; + struct collision_entry *collision_ent; + int clean = 1; + struct strbuf collision_paths = STRBUF_INIT; + + /* + * entry has the mapping of old directory name to new directory name + * that we want to apply to path. + */ + new_path = apply_dir_rename(entry, path); + + if (!new_path) { + /* This should only happen when entry->non_unique_new_dir set */ + if (!entry->non_unique_new_dir) + BUG("entry->non_unqiue_dir not set and !new_path"); + output(o, 1, _("CONFLICT (directory rename split): " + "Unclear where to place %s because directory " + "%s was renamed to multiple other directories, " + "with no destination getting a majority of the " + "files."), + path, entry->dir); + clean = 0; + return NULL; + } + + /* + * The caller needs to have ensured that it has pre-populated + * collisions with all paths that map to new_path. Do a quick check + * to ensure that's the case. + */ + collision_ent = collision_find_entry(collisions, new_path); + if (collision_ent == NULL) + BUG("collision_ent is NULL"); + + /* + * Check for one-sided add/add/.../add conflicts, i.e. + * where implicit renames from the other side doing + * directory rename(s) can affect this side of history + * to put multiple paths into the same location. Warn + * and bail on directory renames for such paths. + */ + if (collision_ent->reported_already) { + clean = 0; + } else if (tree_has_path(tree, new_path)) { + collision_ent->reported_already = 1; + strbuf_add_separated_string_list(&collision_paths, ", ", + &collision_ent->source_files); + output(o, 1, _("CONFLICT (implicit dir rename): Existing " + "file/dir at %s in the way of implicit " + "directory rename(s) putting the following " + "path(s) there: %s."), + new_path, collision_paths.buf); + clean = 0; + } else if (collision_ent->source_files.nr > 1) { + collision_ent->reported_already = 1; + strbuf_add_separated_string_list(&collision_paths, ", ", + &collision_ent->source_files); + output(o, 1, _("CONFLICT (implicit dir rename): Cannot map " + "more than one path to %s; implicit directory " + "renames tried to put these paths there: %s"), + new_path, collision_paths.buf); + clean = 0; + } + + /* Free memory we no longer need */ + strbuf_release(&collision_paths); + if (!clean && new_path) { + free(new_path); + return NULL; + } + + return new_path; +} + +/* + * There are a couple things we want to do at the directory level: + * 1. Check for both sides renaming to the same thing, in order to avoid + * implicit renaming of files that should be left in place. (See + * testcase 6b in t6043 for details.) + * 2. Prune directory renames if there are still files left in the + * the original directory. These represent a partial directory rename, + * i.e. a rename where only some of the files within the directory + * were renamed elsewhere. (Technically, this could be done earlier + * in get_directory_renames(), except that would prevent us from + * doing the previous check and thus failing testcase 6b.) + * 3. Check for rename/rename(1to2) conflicts (at the directory level). + * In the future, we could potentially record this info as well and + * omit reporting rename/rename(1to2) conflicts for each path within + * the affected directories, thus cleaning up the merge output. + * NOTE: We do NOT check for rename/rename(2to1) conflicts at the + * directory level, because merging directories is fine. If it + * causes conflicts for files within those merged directories, then + * that should be detected at the individual path level. + */ +static void handle_directory_level_conflicts(struct merge_options *o, + struct hashmap *dir_re_head, + struct tree *head, + struct hashmap *dir_re_merge, + struct tree *merge) +{ + struct hashmap_iter iter; + struct dir_rename_entry *head_ent; + struct dir_rename_entry *merge_ent; + + struct string_list remove_from_head = STRING_LIST_INIT_NODUP; + struct string_list remove_from_merge = STRING_LIST_INIT_NODUP; + + hashmap_iter_init(dir_re_head, &iter); + while ((head_ent = hashmap_iter_next(&iter))) { + merge_ent = dir_rename_find_entry(dir_re_merge, head_ent->dir); + if (merge_ent && + !head_ent->non_unique_new_dir && + !merge_ent->non_unique_new_dir && + !strbuf_cmp(&head_ent->new_dir, &merge_ent->new_dir)) { + /* 1. Renamed identically; remove it from both sides */ + string_list_append(&remove_from_head, + head_ent->dir)->util = head_ent; + strbuf_release(&head_ent->new_dir); + string_list_append(&remove_from_merge, + merge_ent->dir)->util = merge_ent; + strbuf_release(&merge_ent->new_dir); + } else if (tree_has_path(head, head_ent->dir)) { + /* 2. This wasn't a directory rename after all */ + string_list_append(&remove_from_head, + head_ent->dir)->util = head_ent; + strbuf_release(&head_ent->new_dir); + } + } + + remove_hashmap_entries(dir_re_head, &remove_from_head); + remove_hashmap_entries(dir_re_merge, &remove_from_merge); + + hashmap_iter_init(dir_re_merge, &iter); + while ((merge_ent = hashmap_iter_next(&iter))) { + head_ent = dir_rename_find_entry(dir_re_head, merge_ent->dir); + if (tree_has_path(merge, merge_ent->dir)) { + /* 2. This wasn't a directory rename after all */ + string_list_append(&remove_from_merge, + merge_ent->dir)->util = merge_ent; + } else if (head_ent && + !head_ent->non_unique_new_dir && + !merge_ent->non_unique_new_dir) { + /* 3. rename/rename(1to2) */ + /* + * We can assume it's not rename/rename(1to1) because + * that was case (1), already checked above. So we + * know that head_ent->new_dir and merge_ent->new_dir + * are different strings. + */ + output(o, 1, _("CONFLICT (rename/rename): " + "Rename directory %s->%s in %s. " + "Rename directory %s->%s in %s"), + head_ent->dir, head_ent->new_dir.buf, o->branch1, + head_ent->dir, merge_ent->new_dir.buf, o->branch2); + string_list_append(&remove_from_head, + head_ent->dir)->util = head_ent; + strbuf_release(&head_ent->new_dir); + string_list_append(&remove_from_merge, + merge_ent->dir)->util = merge_ent; + strbuf_release(&merge_ent->new_dir); + } + } + + remove_hashmap_entries(dir_re_head, &remove_from_head); + remove_hashmap_entries(dir_re_merge, &remove_from_merge); +} + +static struct hashmap *get_directory_renames(struct diff_queue_struct *pairs, + struct tree *tree) +{ + struct hashmap *dir_renames; + struct hashmap_iter iter; + struct dir_rename_entry *entry; + int i; + + /* + * Typically, we think of a directory rename as all files from a + * certain directory being moved to a target directory. However, + * what if someone first moved two files from the original + * directory in one commit, and then renamed the directory + * somewhere else in a later commit? At merge time, we just know + * that files from the original directory went to two different + * places, and that the bulk of them ended up in the same place. + * We want each directory rename to represent where the bulk of the + * files from that directory end up; this function exists to find + * where the bulk of the files went. + * + * The first loop below simply iterates through the list of file + * renames, finding out how often each directory rename pair + * possibility occurs. + */ + dir_renames = xmalloc(sizeof(*dir_renames)); + dir_rename_init(dir_renames); + for (i = 0; i < pairs->nr; ++i) { + struct string_list_item *item; + int *count; + struct diff_filepair *pair = pairs->queue[i]; + char *old_dir, *new_dir; + + /* File not part of directory rename if it wasn't renamed */ + if (pair->status != 'R') + continue; + + get_renamed_dir_portion(pair->one->path, pair->two->path, + &old_dir, &new_dir); + if (!old_dir) + /* Directory didn't change at all; ignore this one. */ + continue; + + entry = dir_rename_find_entry(dir_renames, old_dir); + if (!entry) { + entry = xmalloc(sizeof(*entry)); + dir_rename_entry_init(entry, old_dir); + hashmap_put(dir_renames, entry); + } else { + free(old_dir); + } + item = string_list_lookup(&entry->possible_new_dirs, new_dir); + if (!item) { + item = string_list_insert(&entry->possible_new_dirs, + new_dir); + item->util = xcalloc(1, sizeof(int)); + } else { + free(new_dir); + } + count = item->util; + *count += 1; + } + + /* + * For each directory with files moved out of it, we find out which + * target directory received the most files so we can declare it to + * be the "winning" target location for the directory rename. This + * winner gets recorded in new_dir. If there is no winner + * (multiple target directories received the same number of files), + * we set non_unique_new_dir. Once we've determined the winner (or + * that there is no winner), we no longer need possible_new_dirs. + */ + hashmap_iter_init(dir_renames, &iter); + while ((entry = hashmap_iter_next(&iter))) { + int max = 0; + int bad_max = 0; + char *best = NULL; + + for (i = 0; i < entry->possible_new_dirs.nr; i++) { + int *count = entry->possible_new_dirs.items[i].util; + + if (*count == max) + bad_max = max; + else if (*count > max) { + max = *count; + best = entry->possible_new_dirs.items[i].string; + } + } + if (bad_max == max) + entry->non_unique_new_dir = 1; + else { + assert(entry->new_dir.len == 0); + strbuf_addstr(&entry->new_dir, best); + } + /* + * The relevant directory sub-portion of the original full + * filepaths were xstrndup'ed before inserting into + * possible_new_dirs, and instead of manually iterating the + * list and free'ing each, just lie and tell + * possible_new_dirs that it did the strdup'ing so that it + * will free them for us. + */ + entry->possible_new_dirs.strdup_strings = 1; + string_list_clear(&entry->possible_new_dirs, 1); + } + + return dir_renames; +} + +static struct dir_rename_entry *check_dir_renamed(const char *path, + struct hashmap *dir_renames) +{ + char *temp = xstrdup(path); + char *end; + struct dir_rename_entry *entry = NULL;; + + while ((end = strrchr(temp, '/'))) { + *end = '\0'; + entry = dir_rename_find_entry(dir_renames, temp); + if (entry) + break; + } + free(temp); + return entry; +} + +static void compute_collisions(struct hashmap *collisions, + struct hashmap *dir_renames, + struct diff_queue_struct *pairs) +{ + int i; + + /* + * Multiple files can be mapped to the same path due to directory + * renames done by the other side of history. Since that other + * side of history could have merged multiple directories into one, + * if our side of history added the same file basename to each of + * those directories, then all N of them would get implicitly + * renamed by the directory rename detection into the same path, + * and we'd get an add/add/.../add conflict, and all those adds + * from *this* side of history. This is not representable in the + * index, and users aren't going to easily be able to make sense of + * it. So we need to provide a good warning about what's + * happening, and fall back to no-directory-rename detection + * behavior for those paths. + * + * See testcases 9e and all of section 5 from t6043 for examples. + */ + collision_init(collisions); + + for (i = 0; i < pairs->nr; ++i) { + struct dir_rename_entry *dir_rename_ent; + struct collision_entry *collision_ent; + char *new_path; + struct diff_filepair *pair = pairs->queue[i]; + + if (pair->status != 'A' && pair->status != 'R') + continue; + dir_rename_ent = check_dir_renamed(pair->two->path, + dir_renames); + if (!dir_rename_ent) + continue; + + new_path = apply_dir_rename(dir_rename_ent, pair->two->path); + if (!new_path) + /* + * dir_rename_ent->non_unique_new_path is true, which + * means there is no directory rename for us to use, + * which means it won't cause us any additional + * collisions. + */ + continue; + collision_ent = collision_find_entry(collisions, new_path); + if (!collision_ent) { + collision_ent = xcalloc(1, + sizeof(struct collision_entry)); + hashmap_entry_init(collision_ent, strhash(new_path)); + hashmap_put(collisions, collision_ent); + collision_ent->target_file = new_path; + } else { + free(new_path); + } + string_list_insert(&collision_ent->source_files, + pair->two->path); + } +} + +static char *check_for_directory_rename(struct merge_options *o, + const char *path, + struct tree *tree, + struct hashmap *dir_renames, + struct hashmap *dir_rename_exclusions, + struct hashmap *collisions, + int *clean_merge) +{ + char *new_path = NULL; + struct dir_rename_entry *entry = check_dir_renamed(path, dir_renames); + struct dir_rename_entry *oentry = NULL; + + if (!entry) + return new_path; + + /* + * This next part is a little weird. We do not want to do an + * implicit rename into a directory we renamed on our side, because + * that will result in a spurious rename/rename(1to2) conflict. An + * example: + * Base commit: dumbdir/afile, otherdir/bfile + * Side 1: smrtdir/afile, otherdir/bfile + * Side 2: dumbdir/afile, dumbdir/bfile + * Here, while working on Side 1, we could notice that otherdir was + * renamed/merged to dumbdir, and change the diff_filepair for + * otherdir/bfile into a rename into dumbdir/bfile. However, Side + * 2 will notice the rename from dumbdir to smrtdir, and do the + * transitive rename to move it from dumbdir/bfile to + * smrtdir/bfile. That gives us bfile in dumbdir vs being in + * smrtdir, a rename/rename(1to2) conflict. We really just want + * the file to end up in smrtdir. And the way to achieve that is + * to not let Side1 do the rename to dumbdir, since we know that is + * the source of one of our directory renames. + * + * That's why oentry and dir_rename_exclusions is here. + * + * As it turns out, this also prevents N-way transient rename + * confusion; See testcases 9c and 9d of t6043. + */ + oentry = dir_rename_find_entry(dir_rename_exclusions, entry->new_dir.buf); + if (oentry) { + output(o, 1, _("WARNING: Avoiding applying %s -> %s rename " + "to %s, because %s itself was renamed."), + entry->dir, entry->new_dir.buf, path, entry->new_dir.buf); + } else { + new_path = handle_path_level_conflicts(o, path, entry, + collisions, tree); + *clean_merge &= (new_path != NULL); + } + + return new_path; +} + +static void apply_directory_rename_modifications(struct merge_options *o, + struct diff_filepair *pair, + char *new_path, + struct rename *re, + struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct string_list *entries, + int *clean) +{ + struct string_list_item *item; + int stage = (tree == a_tree ? 2 : 3); + int update_wd; + + /* + * In all cases where we can do directory rename detection, + * unpack_trees() will have read pair->two->path into the + * index and the working copy. We need to remove it so that + * we can instead place it at new_path. It is guaranteed to + * not be untracked (unpack_trees() would have errored out + * saying the file would have been overwritten), but it might + * be dirty, though. + */ + update_wd = !was_dirty(o, pair->two->path); + if (!update_wd) + output(o, 1, _("Refusing to lose dirty file at %s"), + pair->two->path); + remove_file(o, 1, pair->two->path, !update_wd); + + /* Find or create a new re->dst_entry */ + item = string_list_lookup(entries, new_path); + if (item) { + /* + * Since we're renaming on this side of history, and it's + * due to a directory rename on the other side of history + * (which we only allow when the directory in question no + * longer exists on the other side of history), the + * original entry for re->dst_entry is no longer + * necessary... + */ + re->dst_entry->processed = 1; + + /* + * ...because we'll be using this new one. + */ + re->dst_entry = item->util; + } else { + /* + * re->dst_entry is for the before-dir-rename path, and we + * need it to hold information for the after-dir-rename + * path. Before creating a new entry, we need to mark the + * old one as unnecessary (...unless it is shared by + * src_entry, i.e. this didn't use to be a rename, in which + * case we can just allow the normal processing to happen + * for it). + */ + if (pair->status == 'R') + re->dst_entry->processed = 1; + + re->dst_entry = insert_stage_data(new_path, + o_tree, a_tree, b_tree, + entries); + item = string_list_insert(entries, new_path); + item->util = re->dst_entry; + } + + /* + * Update the stage_data with the information about the path we are + * moving into place. That slot will be empty and available for us + * to write to because of the collision checks in + * handle_path_level_conflicts(). In other words, + * re->dst_entry->stages[stage].oid will be the null_oid, so it's + * open for us to write to. + * + * It may be tempting to actually update the index at this point as + * well, using update_stages_for_stage_data(), but as per the big + * "NOTE" in update_stages(), doing so will modify the current + * in-memory index which will break calls to would_lose_untracked() + * that we need to make. Instead, we need to just make sure that + * the various conflict_rename_*() functions update the index + * explicitly rather than relying on unpack_trees() to have done it. + */ + get_tree_entry(&tree->object.oid, + pair->two->path, + &re->dst_entry->stages[stage].oid, + &re->dst_entry->stages[stage].mode); + + /* Update pair status */ + if (pair->status == 'A') { + /* + * Recording rename information for this add makes it look + * like a rename/delete conflict. Make sure we can + * correctly handle this as an add that was moved to a new + * directory instead of reporting a rename/delete conflict. + */ + re->add_turned_into_rename = 1; + } + /* + * We don't actually look at pair->status again, but it seems + * pedagogically correct to adjust it. + */ + pair->status = 'R'; + + /* + * Finally, record the new location. + */ + pair->two->path = new_path; +} + +/* + * Get information of all renames which occurred in 'pairs', making use of + * any implicit directory renames inferred from the other side of history. + * We need the three trees in the merge ('o_tree', 'a_tree' and 'b_tree') + * to be able to associate the correct cache entries with the rename + * information; tree is always equal to either a_tree or b_tree. + */ +static struct string_list *get_renames(struct merge_options *o, + struct diff_queue_struct *pairs, + struct hashmap *dir_renames, + struct hashmap *dir_rename_exclusions, + struct tree *tree, + struct tree *o_tree, + struct tree *a_tree, + struct tree *b_tree, + struct string_list *entries, + int *clean_merge) +{ + int i; + struct hashmap collisions; + struct hashmap_iter iter; + struct collision_entry *e; + struct string_list *renames; + + compute_collisions(&collisions, dir_renames, pairs); + renames = xcalloc(1, sizeof(struct string_list)); + + for (i = 0; i < pairs->nr; ++i) { + struct string_list_item *item; + struct rename *re; + struct diff_filepair *pair = pairs->queue[i]; + char *new_path; /* non-NULL only with directory renames */ + + if (pair->status != 'A' && pair->status != 'R') { + diff_free_filepair(pair); + continue; + } + new_path = check_for_directory_rename(o, pair->two->path, tree, + dir_renames, + dir_rename_exclusions, + &collisions, + clean_merge); + if (pair->status != 'R' && !new_path) { + diff_free_filepair(pair); + continue; + } + + re = xmalloc(sizeof(*re)); + re->processed = 0; + re->add_turned_into_rename = 0; + re->pair = pair; + item = string_list_lookup(entries, re->pair->one->path); + if (!item) + re->src_entry = insert_stage_data(re->pair->one->path, + o_tree, a_tree, b_tree, entries); + else + re->src_entry = item->util; + + item = string_list_lookup(entries, re->pair->two->path); + if (!item) + re->dst_entry = insert_stage_data(re->pair->two->path, + o_tree, a_tree, b_tree, entries); + else + re->dst_entry = item->util; + item = string_list_insert(renames, pair->one->path); + item->util = re; + if (new_path) + apply_directory_rename_modifications(o, pair, new_path, + re, tree, o_tree, + a_tree, b_tree, + entries, + clean_merge); + } + + hashmap_iter_init(&collisions, &iter); + while ((e = hashmap_iter_next(&iter))) { + free(e->target_file); + string_list_clear(&e->source_files, 0); + } + hashmap_free(&collisions, 1); + return renames; +} + +static int process_renames(struct merge_options *o, + struct string_list *a_renames, + struct string_list *b_renames) +{ + int clean_merge = 1, i, j; + struct string_list a_by_dst = STRING_LIST_INIT_NODUP; + struct string_list b_by_dst = STRING_LIST_INIT_NODUP; + const struct rename *sre; + + for (i = 0; i < a_renames->nr; i++) { + sre = a_renames->items[i].util; + string_list_insert(&a_by_dst, sre->pair->two->path)->util + = (void *)sre; + } + for (i = 0; i < b_renames->nr; i++) { + sre = b_renames->items[i].util; + string_list_insert(&b_by_dst, sre->pair->two->path)->util + = (void *)sre; + } for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { struct string_list *renames1, *renames2Dst; @@@ -2604,7 -1464,7 +2606,7 @@@ const char *ren2_dst = ren2->pair->two->path; enum rename_type rename_type; if (strcmp(ren1_src, ren2_src) != 0) - die("BUG: ren1_src != ren2_src"); + BUG("ren1_src != ren2_src"); ren2->dst_entry->processed = 1; ren2->processed = 1; if (strcmp(ren1_dst, ren2_dst) != 0) { @@@ -2638,7 -1498,7 +2640,7 @@@ ren2 = lookup->util; ren2_dst = ren2->pair->two->path; if (strcmp(ren1_dst, ren2_dst) != 0) - die("BUG: ren1_dst != ren2_dst"); + BUG("ren1_dst != ren2_dst"); clean_merge = 0; ren2->processed = 1; @@@ -2680,7 -1540,7 +2682,7 @@@ * add-source case). */ remove_file(o, 1, ren1_src, - renamed_stage == 2 || !was_tracked(ren1_src)); + renamed_stage == 2 || !was_tracked(o, ren1_src)); oidcpy(&src_other.oid, &ren1->src_entry->stages[other_stage].oid); @@@ -2690,19 -1550,7 +2692,19 @@@ dst_other.mode = ren1->dst_entry->stages[other_stage].mode; try_merge = 0; - if (oid_eq(&src_other.oid, &null_oid)) { + if (oid_eq(&src_other.oid, &null_oid) && + ren1->add_turned_into_rename) { + setup_rename_conflict_info(RENAME_DIR, + ren1->pair, + NULL, + branch1, + branch2, + ren1->dst_entry, + NULL, + o, + NULL, + NULL); + } else if (oid_eq(&src_other.oid, &null_oid)) { setup_rename_conflict_info(RENAME_DELETE, ren1->pair, NULL, @@@ -2799,105 -1647,6 +2801,105 @@@ cleanup_and_return return clean_merge; } +struct rename_info { + struct string_list *head_renames; + struct string_list *merge_renames; +}; + +static void initial_cleanup_rename(struct diff_queue_struct *pairs, + struct hashmap *dir_renames) +{ + struct hashmap_iter iter; + struct dir_rename_entry *e; + + hashmap_iter_init(dir_renames, &iter); + while ((e = hashmap_iter_next(&iter))) { + free(e->dir); + strbuf_release(&e->new_dir); + /* possible_new_dirs already cleared in get_directory_renames */ + } + hashmap_free(dir_renames, 1); + free(dir_renames); + + free(pairs->queue); + free(pairs); +} + +static int handle_renames(struct merge_options *o, + struct tree *common, + struct tree *head, + struct tree *merge, + struct string_list *entries, + struct rename_info *ri) +{ + struct diff_queue_struct *head_pairs, *merge_pairs; + struct hashmap *dir_re_head, *dir_re_merge; + int clean = 1; + + ri->head_renames = NULL; + ri->merge_renames = NULL; + + if (!merge_detect_rename(o)) + return 1; + + head_pairs = get_diffpairs(o, common, head); + merge_pairs = get_diffpairs(o, common, merge); + + dir_re_head = get_directory_renames(head_pairs, head); + dir_re_merge = get_directory_renames(merge_pairs, merge); + + handle_directory_level_conflicts(o, + dir_re_head, head, + dir_re_merge, merge); + + ri->head_renames = get_renames(o, head_pairs, + dir_re_merge, dir_re_head, head, + common, head, merge, entries, + &clean); + if (clean < 0) + goto cleanup; + ri->merge_renames = get_renames(o, merge_pairs, + dir_re_head, dir_re_merge, merge, + common, head, merge, entries, + &clean); + if (clean < 0) + goto cleanup; + clean &= process_renames(o, ri->head_renames, ri->merge_renames); + +cleanup: + /* + * Some cleanup is deferred until cleanup_renames() because the + * data structures are still needed and referenced in + * process_entry(). But there are a few things we can free now. + */ + initial_cleanup_rename(head_pairs, dir_re_head); + initial_cleanup_rename(merge_pairs, dir_re_merge); + + return clean; +} + +static void final_cleanup_rename(struct string_list *rename) +{ + const struct rename *re; + int i; + + if (rename == NULL) + return; + + for (i = 0; i < rename->nr; i++) { + re = rename->items[i].util; + diff_free_filepair(re->pair); + } + string_list_clear(rename, 1); + free(rename); +} + +static void final_cleanup_renames(struct rename_info *re_info) +{ + final_cleanup_rename(re_info->head_renames); + final_cleanup_rename(re_info->merge_renames); +} + static struct object_id *stage_oid(const struct object_id *oid, unsigned mode) { return (is_null_oid(oid) || mode == 0) ? NULL: (struct object_id *)oid; @@@ -2988,7 -1737,6 +2990,7 @@@ static int handle_modify_delete(struct static int merge_content(struct merge_options *o, const char *path, + int is_dirty, struct object_id *o_oid, int o_mode, struct object_id *a_oid, int a_mode, struct object_id *b_oid, int b_mode, @@@ -3029,26 -1777,29 +3031,26 @@@ S_ISGITLINK(pair1->two->mode))) df_conflict_remains = 1; } - if (merge_file_special_markers(o, &one, &a, &b, + if (merge_file_special_markers(o, &one, &a, &b, path, o->branch1, path1, o->branch2, path2, &mfi)) return -1; - if (mfi.clean && !df_conflict_remains && - oid_eq(&mfi.oid, a_oid) && mfi.mode == a_mode) { - int path_renamed_outside_HEAD; + /* + * We can skip updating the working tree file iff: + * a) The merge is clean + * b) The merge matches what was in HEAD (content, mode, pathname) + * c) The target path is usable (i.e. not involved in D/F conflict) + */ + if (mfi.clean && + was_tracked_and_matches(o, path, &mfi.oid, mfi.mode) && + !df_conflict_remains) { output(o, 3, _("Skipped %s (merged same as existing)"), path); - /* - * The content merge resulted in the same file contents we - * already had. We can return early if those file contents - * are recorded at the correct path (which may not be true - * if the merge involves a rename). - */ - path_renamed_outside_HEAD = !path2 || !strcmp(path, path2); - if (!path_renamed_outside_HEAD) { - add_cacheinfo(o, mfi.mode, &mfi.oid, path, - 0, (!o->call_depth), 0); - return mfi.clean; - } - } else - output(o, 2, _("Auto-merging %s"), path); + if (add_cacheinfo(o, mfi.mode, &mfi.oid, path, + 0, (!o->call_depth && !is_dirty), 0)) + return -1; + return mfi.clean; + } if (!mfi.clean) { if (S_ISGITLINK(mfi.mode)) @@@ -3060,7 -1811,7 +3062,7 @@@ return -1; } - if (df_conflict_remains) { + if (df_conflict_remains || is_dirty) { char *new_path; if (o->call_depth) { remove_file_from_cache(path); @@@ -3069,7 -1820,7 +3071,7 @@@ if (update_stages(o, path, &one, &a, &b)) return -1; } else { - int file_from_stage2 = was_tracked(path); + int file_from_stage2 = was_tracked(o, path); struct diff_filespec merged; oidcpy(&merged.oid, &mfi.oid); merged.mode = mfi.mode; @@@ -3082,10 -1833,6 +3084,10 @@@ } new_path = unique_path(o, path, rename_conflict_info->branch1); + if (is_dirty) { + output(o, 1, _("Refusing to lose dirty file at %s"), + path); + } output(o, 1, _("Adding as %s instead"), new_path); if (update_file(o, 0, &mfi.oid, mfi.mode, new_path)) { free(new_path); @@@ -3095,20 -1842,7 +3097,20 @@@ mfi.clean = 0; } else if (update_file(o, mfi.clean, &mfi.oid, mfi.mode, path)) return -1; - return mfi.clean; + return !is_dirty && mfi.clean; +} + +static int conflict_rename_normal(struct merge_options *o, + const char *path, + struct object_id *o_oid, unsigned int o_mode, + struct object_id *a_oid, unsigned int a_mode, + struct object_id *b_oid, unsigned int b_mode, + struct rename_conflict_info *ci) +{ + /* Merge the content and write it out */ + return merge_content(o, path, was_dirty(o, path), + o_oid, o_mode, a_oid, a_mode, b_oid, b_mode, + ci); } /* Per entry merge function */ @@@ -3130,20 -1864,9 +3132,20 @@@ static int process_entry(struct merge_o switch (conflict_info->rename_type) { case RENAME_NORMAL: case RENAME_ONE_FILE_TO_ONE: - clean_merge = merge_content(o, path, - o_oid, o_mode, a_oid, a_mode, b_oid, b_mode, - conflict_info); + clean_merge = conflict_rename_normal(o, + path, + o_oid, o_mode, + a_oid, a_mode, + b_oid, b_mode, + conflict_info); + break; + case RENAME_DIR: + clean_merge = 1; + if (conflict_rename_dir(o, + conflict_info->pair1, + conflict_info->branch1, + conflict_info->branch2)) + clean_merge = -1; break; case RENAME_DELETE: clean_merge = 0; @@@ -3231,8 -1954,7 +3233,8 @@@ } else if (a_oid && b_oid) { /* Case C: Added in both (check for same permissions) and */ /* case D: Modified in both, but differently. */ - clean_merge = merge_content(o, path, + int is_dirty = 0; /* unpack_trees would have bailed if dirty */ + clean_merge = merge_content(o, path, is_dirty, o_oid, o_mode, a_oid, a_mode, b_oid, b_mode, NULL); } else if (!o_oid && !a_oid && !b_oid) { @@@ -3242,7 -1964,7 +3244,7 @@@ */ remove_file(o, 1, path, !a_mode); } else - die("BUG: fatal merge failure, shouldn't happen."); + BUG("fatal merge failure, shouldn't happen."); return clean_merge; } @@@ -3273,20 -1995,18 +3275,20 @@@ int merge_trees(struct merge_options *o return 1; } - code = git_merge_trees(o->call_depth, common, head, merge); + code = unpack_trees_start(o, common, head, merge); if (code != 0) { if (show(o, 4) || o->call_depth) err(o, _("merging of trees %s and %s failed"), oid_to_hex(&head->object.oid), oid_to_hex(&merge->object.oid)); + unpack_trees_finish(o); return -1; } if (unmerged_cache()) { - struct string_list *entries, *re_head, *re_merge; + struct string_list *entries; + struct rename_info re_info; int i; /* * Only need the hashmap while processing entries, so @@@ -3300,8 -2020,9 +3302,8 @@@ get_files_dirs(o, merge); entries = get_unmerged(); - re_head = get_renames(o, head, common, head, merge, entries); - re_merge = get_renames(o, merge, common, head, merge, entries); - clean = process_renames(o, re_head, re_merge); + clean = handle_renames(o, common, head, merge, entries, + &re_info); record_df_conflict_files(o, entries); if (clean < 0) goto cleanup; @@@ -3321,28 -2042,27 +3323,28 @@@ for (i = 0; i < entries->nr; i++) { struct stage_data *e = entries->items[i].util; if (!e->processed) - die("BUG: unprocessed path??? %s", + BUG("unprocessed path??? %s", entries->items[i].string); } cleanup: - string_list_clear(re_merge, 0); - string_list_clear(re_head, 0); + final_cleanup_renames(&re_info); + string_list_clear(entries, 1); + free(entries); hashmap_free(&o->current_file_dir_set, 1); - free(re_merge); - free(re_head); - free(entries); - - if (clean < 0) + if (clean < 0) { + unpack_trees_finish(o); return clean; + } } else clean = 1; + unpack_trees_finish(o); + if (o->call_depth && !(*result = write_tree_from_memory(o))) return -1; @@@ -3436,8 -2156,7 +3438,8 @@@ int merge_recursive(struct merge_option read_cache(); o->ancestor = "merged common ancestors"; - clean = merge_trees(o, h1->tree, h2->tree, merged_common_ancestors->tree, + clean = merge_trees(o, get_commit_tree(h1), get_commit_tree(h2), + get_commit_tree(merged_common_ancestors), &mrtree); if (clean < 0) { flush_output(o); @@@ -3515,18 -2234,9 +3517,18 @@@ int merge_recursive_generic(struct merg static void merge_recursive_config(struct merge_options *o) { + char *value = NULL; git_config_get_int("merge.verbosity", &o->verbosity); git_config_get_int("diff.renamelimit", &o->diff_rename_limit); git_config_get_int("merge.renamelimit", &o->merge_rename_limit); + if (!git_config_get_string("diff.renames", &value)) { + o->diff_detect_rename = git_config_rename("diff.renames", value); + free(value); + } + if (!git_config_get_string("merge.renames", &value)) { + o->merge_detect_rename = git_config_rename("merge.renames", value); + free(value); + } git_config(git_xmerge_config, NULL); } @@@ -3539,8 -2249,7 +3541,8 @@@ void init_merge_options(struct merge_op o->diff_rename_limit = -1; o->merge_rename_limit = -1; o->renormalize = 0; - o->detect_rename = 1; + o->diff_detect_rename = -1; + o->merge_detect_rename = -1; merge_recursive_config(o); merge_verbosity = getenv("GIT_MERGE_VERBOSITY"); if (merge_verbosity) @@@ -3591,16 -2300,16 +3593,16 @@@ int parse_merge_opt(struct merge_option else if (!strcmp(s, "no-renormalize")) o->renormalize = 0; else if (!strcmp(s, "no-renames")) - o->detect_rename = 0; + o->merge_detect_rename = 0; else if (!strcmp(s, "find-renames")) { - o->detect_rename = 1; + o->merge_detect_rename = 1; o->rename_score = 0; } else if (skip_prefix(s, "find-renames=", &arg) || skip_prefix(s, "rename-threshold=", &arg)) { if ((o->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0) return -1; - o->detect_rename = 1; + o->merge_detect_rename = 1; } else return -1; diff --combined object.h index 5c13955000,caf36529f3..acc560b24e --- a/object.h +++ b/object.h @@@ -25,6 -25,7 +25,6 @@@ struct object_array #define OBJECT_ARRAY_INIT { 0, 0, NULL } -#define TYPE_BITS 3 /* * object flag allocation: * revision.h: 0---------10 26 @@@ -42,6 -43,7 +42,7 @@@ * builtin/index-pack.c: 2021 * builtin/pack-objects.c: 20 * builtin/reflog.c: 10--12 + * builtin/show-branch.c: 0-------------------------------------------26 * builtin/unpack-objects.c: 2021 */ #define FLAG_BITS 27 diff --combined revision.c index 40fd91ff2b,be8fe7d67b..0afae4744a --- a/revision.c +++ b/revision.c @@@ -29,6 -29,8 +29,8 @@@ volatile show_early_output_fn_t show_ea static const char *term_bad; static const char *term_good; + implement_shared_commit_slab(revision_sources, char *); + void show_object_with_name(FILE *out, struct object *obj, const char *name) { const char *p; @@@ -52,9 -54,12 +54,9 @@@ static void mark_tree_contents_unintere { struct tree_desc desc; struct name_entry entry; - struct object *obj = &tree->object; - if (!has_object_file(&obj->oid)) + if (parse_tree_gently(tree, 1) < 0) return; - if (parse_tree(tree) < 0) - die("bad tree %s", oid_to_hex(&obj->oid)); init_tree_desc(&desc, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { @@@ -92,63 -97,50 +94,63 @@@ void mark_tree_uninteresting(struct tre mark_tree_contents_uninteresting(tree); } -void mark_parents_uninteresting(struct commit *commit) +struct commit_stack { + struct commit **items; + size_t nr, alloc; +}; +#define COMMIT_STACK_INIT { NULL, 0, 0 } + +static void commit_stack_push(struct commit_stack *stack, struct commit *commit) { - struct commit_list *parents = NULL, *l; + ALLOC_GROW(stack->items, stack->nr + 1, stack->alloc); + stack->items[stack->nr++] = commit; +} - for (l = commit->parents; l; l = l->next) - commit_list_insert(l->item, &parents); +static struct commit *commit_stack_pop(struct commit_stack *stack) +{ + return stack->nr ? stack->items[--stack->nr] : NULL; +} - while (parents) { - struct commit *commit = pop_commit(&parents); +static void commit_stack_clear(struct commit_stack *stack) +{ + FREE_AND_NULL(stack->items); + stack->nr = stack->alloc = 0; +} - while (commit) { - /* - * A missing commit is ok iff its parent is marked - * uninteresting. - * - * We just mark such a thing parsed, so that when - * it is popped next time around, we won't be trying - * to parse it and get an error. - */ - if (!commit->object.parsed && - !has_object_file(&commit->object.oid)) - commit->object.parsed = 1; +static void mark_one_parent_uninteresting(struct commit *commit, + struct commit_stack *pending) +{ + struct commit_list *l; - if (commit->object.flags & UNINTERESTING) - break; + if (commit->object.flags & UNINTERESTING) + return; + commit->object.flags |= UNINTERESTING; + + /* + * Normally we haven't parsed the parent + * yet, so we won't have a parent of a parent + * here. However, it may turn out that we've + * reached this commit some other way (where it + * wasn't uninteresting), in which case we need + * to mark its parents recursively too.. + */ + for (l = commit->parents; l; l = l->next) + commit_stack_push(pending, l->item); +} - commit->object.flags |= UNINTERESTING; +void mark_parents_uninteresting(struct commit *commit) +{ + struct commit_stack pending = COMMIT_STACK_INIT; + struct commit_list *l; - /* - * Normally we haven't parsed the parent - * yet, so we won't have a parent of a parent - * here. However, it may turn out that we've - * reached this commit some other way (where it - * wasn't uninteresting), in which case we need - * to mark its parents recursively too.. - */ - if (!commit->parents) - break; + for (l = commit->parents; l; l = l->next) + mark_one_parent_uninteresting(l->item, &pending); - for (l = commit->parents->next; l; l = l->next) - commit_list_insert(l->item, &parents); - commit = commit->parents->item; - } - } + while (pending.nr > 0) + mark_one_parent_uninteresting(commit_stack_pop(&pending), + &pending); + + commit_stack_clear(&pending); } static void add_pending_object_with_path(struct rev_info *revs, @@@ -265,14 -257,19 +267,19 @@@ static struct commit *handle_commit(str */ if (object->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)object; + if (parse_commit(commit) < 0) die("unable to parse commit %s", name); if (flags & UNINTERESTING) { mark_parents_uninteresting(commit); revs->limited = 1; } - if (revs->show_source && !commit->util) - commit->util = xstrdup(name); + if (revs->sources) { + char **slot = revision_sources_at(revs->sources, commit); + + if (!*slot) + *slot = xstrdup(name); + } return commit; } @@@ -451,8 -448,8 +458,8 @@@ static void file_change(struct diff_opt static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct commit *commit) { - struct tree *t1 = parent->tree; - struct tree *t2 = commit->tree; + struct tree *t1 = get_commit_tree(parent); + struct tree *t2 = get_commit_tree(commit); if (!t1) return REV_TREE_NEW; @@@ -488,7 -485,7 +495,7 @@@ static int rev_same_tree_as_empty(struct rev_info *revs, struct commit *commit) { int retval; - struct tree *t1 = commit->tree; + struct tree *t1 = get_commit_tree(commit); if (!t1) return 0; @@@ -626,7 -623,7 +633,7 @@@ static void try_to_simplify_commit(stru if (!revs->prune) return; - if (!commit->tree) + if (!get_commit_tree(commit)) return; if (!commit->parents) { @@@ -824,8 -821,12 +831,12 @@@ static int add_parents_to_list(struct r } return -1; } - if (revs->show_source && !p->util) - p->util = commit->util; + if (revs->sources) { + char **slot = revision_sources_at(revs->sources, p); + + if (!*slot) + *slot = *revision_sources_at(revs->sources, commit); + } p->object.flags |= left_flag; if (!(p->object.flags & SEEN)) { p->object.flags |= SEEN; @@@ -1762,7 -1763,6 +1773,7 @@@ static int handle_revision_opt(struct r const char *arg = argv[0]; const char *optarg; int argcount; + const unsigned hexsz = the_hash_algo->hexsz; /* pseudo revision arguments */ if (!strcmp(arg, "--all") || !strcmp(arg, "--branches") || @@@ -2050,8 -2050,8 +2061,8 @@@ revs->abbrev = strtoul(optarg, NULL, 10); if (revs->abbrev < MINIMUM_ABBREV) revs->abbrev = MINIMUM_ABBREV; - else if (revs->abbrev > 40) - revs->abbrev = 40; + else if (revs->abbrev > hexsz) + revs->abbrev = hexsz; } else if (!strcmp(arg, "--abbrev-commit")) { revs->abbrev_commit = 1; revs->abbrev_commit_given = 1; @@@ -2119,7 -2119,7 +2130,7 @@@ revs->ignore_missing = 1; } else if (!strcmp(arg, "--exclude-promisor-objects")) { if (fetch_if_missing) - die("BUG: exclude_promisor_objects can only be used when fetch_if_missing is 0"); + BUG("exclude_promisor_objects can only be used when fetch_if_missing is 0"); revs->exclude_promisor_objects = 1; } else { int opts = diff_opt_parse(&revs->diffopt, argv, argc, revs->prefix); @@@ -2185,7 -2185,7 +2196,7 @@@ static int handle_revision_pseudo_opt(c * supported right now, so stick to single worktree. */ if (!revs->single_worktree) - die("BUG: --single-worktree cannot be used together with submodule"); + BUG("--single-worktree cannot be used together with submodule"); refs = get_submodule_ref_store(submodule); } else refs = get_main_ref_store(the_repository); @@@ -3098,7 -3098,7 +3109,7 @@@ enum commit_action get_commit_action(st { if (commit->object.flags & SHOWN) return commit_ignore; - if (revs->unpacked && has_sha1_pack(commit->object.oid.hash)) + if (revs->unpacked && has_object_pack(&commit->object.oid)) return commit_ignore; if (commit->object.flags & UNINTERESTING) return commit_ignore; diff --combined sequencer.c index 4034c0461b,3b6d56d085..ee1f7b2df7 --- a/sequencer.c +++ b/sequencer.c @@@ -23,11 -23,7 +23,12 @@@ #include "hashmap.h" #include "notes-utils.h" #include "sigchain.h" +#include "unpack-trees.h" +#include "worktree.h" +#include "oidmap.h" +#include "oidset.h" + #include "commit-slab.h" +#include "alias.h" #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" @@@ -79,6 -75,13 +80,6 @@@ static GIT_PATH_FUNC(rebase_path_messag * previous commit and from the first squash/fixup commit are written * to it. The commit message for each subsequent squash/fixup commit * is appended to the file as it is processed. - * - * The first line of the file is of the form - * # This is a combination of $count commits. - * where $count is the number of commits whose messages have been - * written to the file so far (including the initial "pick" commit). - * Each time that a commit message is processed, this line is read and - * updated. It is deleted just before the combined commit is made. */ static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash") /* @@@ -89,11 -92,6 +90,11 @@@ * commit without opening the editor.) */ static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup") +/* + * This file contains the list fixup/squash commands that have been + * accumulated into message-fixup or message-squash so far. + */ +static GIT_PATH_FUNC(rebase_path_current_fixups, "rebase-merge/current-fixups") /* * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and * GIT_AUTHOR_DATE that will be used for the commit that is currently @@@ -123,19 -121,6 +124,19 @@@ static GIT_PATH_FUNC(rebase_path_stoppe static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list") static GIT_PATH_FUNC(rebase_path_rewritten_pending, "rebase-merge/rewritten-pending") + +/* + * The path of the file containig the OID of the "squash onto" commit, i.e. + * the dummy commit used for `reset [new root]`. + */ +static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto") + +/* + * The path of the file listing refs that need to be deleted after the rebase + * finishes. This is used by the `label` command to record the need for cleanup. + */ +static GIT_PATH_FUNC(rebase_path_refs_to_delete, "rebase-merge/refs-to-delete") + /* * The following files are written by git-rebase just after parsing the * command-line (and are only consumed, not modified, by the sequencer). @@@ -261,35 -246,18 +262,35 @@@ static const char *gpg_sign_opt_quoted( int sequencer_remove_state(struct replay_opts *opts) { - struct strbuf dir = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; int i; + if (is_rebase_i(opts) && + strbuf_read_file(&buf, rebase_path_refs_to_delete(), 0) > 0) { + char *p = buf.buf; + while (*p) { + char *eol = strchr(p, '\n'); + if (eol) + *eol = '\0'; + if (delete_ref("(rebase -i) cleanup", p, NULL, 0) < 0) + warning(_("could not delete '%s'"), p); + if (!eol) + break; + p = eol + 1; + } + } + free(opts->gpg_sign); free(opts->strategy); for (i = 0; i < opts->xopts_nr; i++) free(opts->xopts[i]); free(opts->xopts); + strbuf_release(&opts->current_fixups); - strbuf_addstr(&dir, get_dir(opts)); - remove_dir_recursively(&dir, 0); - strbuf_release(&dir); + strbuf_reset(&buf); + strbuf_addstr(&buf, get_dir(opts)); + remove_dir_recursively(&buf, 0); + strbuf_release(&buf); return 0; } @@@ -379,14 -347,12 +380,14 @@@ static int write_message(const void *bu if (msg_fd < 0) return error_errno(_("could not lock '%s'"), filename); if (write_in_full(msg_fd, buf, len) < 0) { + error_errno(_("could not write to '%s'"), filename); rollback_lock_file(&msg_file); - return error_errno(_("could not write to '%s'"), filename); + return -1; } if (append_eol && write(msg_fd, "\n", 1) < 0) { + error_errno(_("could not write eol to '%s'"), filename); rollback_lock_file(&msg_file); - return error_errno(_("could not write eol to '%s'"), filename); + return -1; } if (commit_lock_file(&msg_file) < 0) return error(_("failed to finalize '%s'"), filename); @@@ -476,8 -442,7 +477,8 @@@ static int fast_forward_to(const struc transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, "HEAD", - to, unborn ? &null_oid : from, + to, unborn && !is_rebase_i(opts) ? + &null_oid : from, 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); @@@ -536,8 -501,8 +537,8 @@@ static int do_recursive_merge(struct co o.show_rename_progress = 1; head_tree = parse_tree_indirect(head); - next_tree = next ? next->tree : empty_tree(); - base_tree = base ? base->tree : empty_tree(); + next_tree = next ? get_commit_tree(next) : empty_tree(); + base_tree = base ? get_commit_tree(base) : empty_tree(); for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) parse_merge_opt(&o, *xopt); @@@ -569,23 -534,9 +570,23 @@@ return !clean; } +static struct object_id *get_cache_tree_oid(void) +{ + if (!active_cache_tree) + active_cache_tree = cache_tree(); + + if (!cache_tree_fully_valid(active_cache_tree)) + if (cache_tree_update(&the_index, 0)) { + error(_("unable to update cache tree")); + return NULL; + } + + return &active_cache_tree->oid; +} + static int is_index_unchanged(void) { - struct object_id head_oid; + struct object_id head_oid, *cache_tree_oid; struct commit *head_commit; if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) @@@ -604,10 -555,15 +605,10 @@@ if (parse_commit(head_commit)) return -1; - if (!active_cache_tree) - active_cache_tree = cache_tree(); - - if (!cache_tree_fully_valid(active_cache_tree)) - if (cache_tree_update(&the_index, 0)) - return error(_("unable to update cache tree")); + if (!(cache_tree_oid = get_cache_tree_oid())) + return -1; - return !oidcmp(&active_cache_tree->oid, - &head_commit->tree->object.oid); + return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit)); } static int write_author_script(const char *message) @@@ -699,52 -655,6 +700,52 @@@ static char *get_author(const char *mes return NULL; } +/* Read author-script and return an ident line (author timestamp) */ +static const char *read_author_ident(struct strbuf *buf) +{ + const char *keys[] = { + "GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE=" + }; + char *in, *out, *eol; + int i = 0, len; + + if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0) + return NULL; + + /* dequote values and construct ident line in-place */ + for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) { + if (!skip_prefix(in, keys[i], (const char **)&in)) { + warning("could not parse '%s' (looking for '%s'", + rebase_path_author_script(), keys[i]); + return NULL; + } + + eol = strchrnul(in, '\n'); + *eol = '\0'; + sq_dequote(in); + len = strlen(in); + + if (i > 0) /* separate values by spaces */ + *(out++) = ' '; + if (i == 1) /* email needs to be surrounded by <...> */ + *(out++) = '<'; + memmove(out, in, len); + out += len; + if (i == 1) /* email needs to be surrounded by <...> */ + *(out++) = '>'; + in = eol + 1; + } + + if (i < 3) { + warning("could not parse '%s' (looking for '%s')", + rebase_path_author_script(), keys[i]); + return NULL; + } + + buf->len = out - buf->buf; + return buf->buf; +} + static const char staged_changes_advice[] = N_("you have staged changes in your working tree\n" "If these changes are meant to be squashed into the previous commit, run:\n" @@@ -764,7 -674,6 +765,7 @@@ #define AMEND_MSG (1<<2) #define CLEANUP_MSG (1<<3) #define VERIFY_MSG (1<<4) +#define CREATE_ROOT_COMMIT (1<<5) /* * If we are cherry-pick, and if the merge did not result in @@@ -784,40 -693,6 +785,40 @@@ static int run_git_commit(const char *d struct child_process cmd = CHILD_PROCESS_INIT; const char *value; + if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) { + struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT; + const char *author = is_rebase_i(opts) ? + read_author_ident(&script) : NULL; + struct object_id root_commit, *cache_tree_oid; + int res = 0; + + if (!defmsg) + BUG("root commit without message"); + + if (!(cache_tree_oid = get_cache_tree_oid())) + res = -1; + + if (!res) + res = strbuf_read_file(&msg, defmsg, 0); + + if (res <= 0) + res = error_errno(_("could not read '%s'"), defmsg); + else + res = commit_tree(msg.buf, msg.len, cache_tree_oid, + NULL, &root_commit, author, + opts->gpg_sign); + + strbuf_release(&msg); + strbuf_release(&script); + if (!res) { + update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL, + REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR); + res = update_ref(NULL, "HEAD", &root_commit, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); + } + return res < 0 ? error(_("writing root commit")) : 0; + } + cmd.git_cmd = 1; if (is_rebase_i(opts)) { @@@ -844,8 -719,6 +845,8 @@@ argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign); if (defmsg) argv_array_pushl(&cmd.args, "-F", defmsg, NULL); + else if (!(flags & EDIT_MSG)) + argv_array_pushl(&cmd.args, "-C", "HEAD", NULL); if ((flags & CLEANUP_MSG)) argv_array_push(&cmd.args, "--cleanup=strip"); if ((flags & EDIT_MSG)) @@@ -1247,8 -1120,8 +1248,8 @@@ static int try_to_commit(struct strbuf } if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ? - ¤t_head->tree->object.oid : - &empty_tree_oid, &tree)) { + get_commit_tree_oid(current_head) : + the_hash_algo->empty_tree, &tree)) { res = 1; /* run 'git commit' to display error message */ goto out; } @@@ -1308,8 -1181,7 +1309,8 @@@ static int do_commit(const char *msg_fi { int res = 1; - if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) { + if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) && + !(flags & CREATE_ROOT_COMMIT)) { struct object_id oid; struct strbuf sb = STRBUF_INIT; @@@ -1348,12 -1220,12 +1349,12 @@@ static int is_original_commit_empty(str if (parse_commit(parent)) return error(_("could not parse parent commit %s"), oid_to_hex(&parent->object.oid)); - ptree_oid = &parent->tree->object.oid; + ptree_oid = get_commit_tree_oid(parent); } else { ptree_oid = the_hash_algo->empty_tree; /* commit is root */ } - return !oidcmp(ptree_oid, &commit->tree->object.oid); + return !oidcmp(ptree_oid, get_commit_tree_oid(commit)); } /* @@@ -1409,9 -1281,6 +1410,9 @@@ enum todo_command TODO_SQUASH, /* commands that do something else than handling a single commit */ TODO_EXEC, + TODO_LABEL, + TODO_RESET, + TODO_MERGE, /* commands that do nothing but are counted for reporting progress */ TODO_NOOP, TODO_DROP, @@@ -1430,9 -1299,6 +1431,9 @@@ static struct { 'f', "fixup" }, { 's', "squash" }, { 'x', "exec" }, + { 'l', "label" }, + { 't', "reset" }, + { 'm', "merge" }, { 0, "noop" }, { 'd', "drop" }, { 0, NULL } @@@ -1462,43 -1328,38 +1463,43 @@@ static int is_fixup(enum todo_command c return command == TODO_FIXUP || command == TODO_SQUASH; } +/* Does this command create a (non-merge) commit? */ +static int is_pick_or_similar(enum todo_command command) +{ + switch (command) { + case TODO_PICK: + case TODO_REVERT: + case TODO_EDIT: + case TODO_REWORD: + case TODO_FIXUP: + case TODO_SQUASH: + return 1; + default: + return 0; + } +} + static int update_squash_messages(enum todo_command command, struct commit *commit, struct replay_opts *opts) { struct strbuf buf = STRBUF_INIT; - int count, res; + int res; const char *message, *body; - if (file_exists(rebase_path_squash_msg())) { + if (opts->current_fixup_count > 0) { struct strbuf header = STRBUF_INIT; - char *eol, *p; + char *eol; - if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0) + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 9) <= 0) return error(_("could not read '%s'"), rebase_path_squash_msg()); - p = buf.buf + 1; - eol = strchrnul(buf.buf, '\n'); - if (buf.buf[0] != comment_line_char || - (p += strcspn(p, "0123456789\n")) == eol) - return error(_("unexpected 1st line of squash message:" - "\n\n\t%.*s"), - (int)(eol - buf.buf), buf.buf); - count = strtol(p, NULL, 10); - - if (count < 1) - return error(_("invalid 1st line of squash message:\n" - "\n\t%.*s"), - (int)(eol - buf.buf), buf.buf); + eol = buf.buf[0] != comment_line_char ? + buf.buf : strchrnul(buf.buf, '\n'); strbuf_addf(&header, "%c ", comment_line_char); - strbuf_addf(&header, - _("This is a combination of %d commits."), ++count); + strbuf_addf(&header, _("This is a combination of %d commits."), + opts->current_fixup_count + 2); strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); strbuf_release(&header); } else { @@@ -1521,8 -1382,10 +1522,8 @@@ rebase_path_fixup_msg()); } - count = 2; strbuf_addf(&buf, "%c ", comment_line_char); - strbuf_addf(&buf, _("This is a combination of %d commits."), - count); + strbuf_addf(&buf, _("This is a combination of %d commits."), 2); strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addstr(&buf, _("This is the 1st commit message:")); strbuf_addstr(&buf, "\n\n"); @@@ -1539,14 -1402,13 +1540,14 @@@ if (command == TODO_SQUASH) { unlink(rebase_path_fixup_msg()); strbuf_addf(&buf, "\n%c ", comment_line_char); - strbuf_addf(&buf, _("This is the commit message #%d:"), count); + strbuf_addf(&buf, _("This is the commit message #%d:"), + ++opts->current_fixup_count); strbuf_addstr(&buf, "\n\n"); strbuf_addstr(&buf, body); } else if (command == TODO_FIXUP) { strbuf_addf(&buf, "\n%c ", comment_line_char); strbuf_addf(&buf, _("The commit message #%d will be skipped:"), - count); + ++opts->current_fixup_count); strbuf_addstr(&buf, "\n\n"); strbuf_add_commented_lines(&buf, body, strlen(body)); } else @@@ -1555,17 -1417,6 +1556,17 @@@ res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0); strbuf_release(&buf); + + if (!res) { + strbuf_addf(&opts->current_fixups, "%s%s %s", + opts->current_fixups.len ? "\n" : "", + command_to_string(command), + oid_to_hex(&commit->object.oid)); + res = write_message(opts->current_fixups.buf, + opts->current_fixups.len, + rebase_path_current_fixups(), 0); + } + return res; } @@@ -1631,16 -1482,9 +1632,16 @@@ static int do_pick_commit(enum todo_com return error(_("your index file is unmerged.")); } else { unborn = get_oid("HEAD", &head); - if (unborn) + /* Do we want to generate a root commit? */ + if (is_pick_or_similar(command) && opts->have_squash_onto && + !oidcmp(&head, &opts->squash_onto)) { + if (is_fixup(command)) + return error(_("cannot fixup root commit")); + flags |= CREATE_ROOT_COMMIT; + unborn = 1; + } else if (unborn) oidcpy(&head, the_hash_algo->empty_tree); - if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", + if (index_differs_from(unborn ? empty_tree_oid_hex() : "HEAD", NULL, 0)) return error_dirty_index(opts); } @@@ -1835,9 -1679,6 +1836,9 @@@ fast_forward_edit if (!res && final_fixup) { unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); + unlink(rebase_path_current_fixups()); + strbuf_reset(&opts->current_fixups); + opts->current_fixup_count = 0; } leave: @@@ -1885,14 -1726,9 +1886,14 @@@ static int read_and_refresh_cache(struc return 0; } +enum todo_item_flags { + TODO_EDIT_MERGE_MSG = 1 +}; + struct todo_item { enum todo_command command; struct commit *commit; + unsigned int flags; const char *arg; int arg_len; size_t offset_in_buf; @@@ -1927,8 -1763,6 +1928,8 @@@ static int parse_insn_line(struct todo_ char *end_of_object_name; int i, saved, status, padding; + item->flags = 0; + /* left-trim */ bol += strspn(bol, " \t"); @@@ -1970,29 -1804,13 +1971,29 @@@ return error(_("missing arguments for %s"), command_to_string(item->command)); - if (item->command == TODO_EXEC) { + if (item->command == TODO_EXEC || item->command == TODO_LABEL || + item->command == TODO_RESET) { item->commit = NULL; item->arg = bol; item->arg_len = (int)(eol - bol); return 0; } + if (item->command == TODO_MERGE) { + if (skip_prefix(bol, "-C", &bol)) + bol += strspn(bol, " \t"); + else if (skip_prefix(bol, "-c", &bol)) { + bol += strspn(bol, " \t"); + item->flags |= TODO_EDIT_MERGE_MSG; + } else { + item->flags |= TODO_EDIT_MERGE_MSG; + item->commit = NULL; + item->arg = bol; + item->arg_len = (int)(eol - bol); + return 0; + } + } + end_of_object_name = (char *) bol + strcspn(bol, " \t\n"); saved = *end_of_object_name; *end_of_object_name = '\0'; @@@ -2054,23 -1872,6 +2055,23 @@@ static int count_commands(struct todo_l return count; } +static int get_item_line_offset(struct todo_list *todo_list, int index) +{ + return index < todo_list->nr ? + todo_list->items[index].offset_in_buf : todo_list->buf.len; +} + +static const char *get_item_line(struct todo_list *todo_list, int index) +{ + return todo_list->buf.buf + get_item_line_offset(todo_list, index); +} + +static int get_item_line_length(struct todo_list *todo_list, int index) +{ + return get_item_line_offset(todo_list, index + 1) + - get_item_line_offset(todo_list, index); +} + static ssize_t strbuf_read_file_or_whine(struct strbuf *sb, const char *path) { int fd; @@@ -2254,22 -2055,6 +2255,22 @@@ static int read_populate_opts(struct re read_strategy_opts(opts, &buf); strbuf_release(&buf); + if (read_oneliner(&opts->current_fixups, + rebase_path_current_fixups(), 1)) { + const char *p = opts->current_fixups.buf; + opts->current_fixup_count = 1; + while ((p = strchr(p, '\n'))) { + opts->current_fixup_count++; + p++; + } + } + + if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) { + if (get_oid_hex(buf.buf, &opts->squash_onto) < 0) + return error(_("unusable squash-onto")); + opts->have_squash_onto = 1; + } + return 0; } @@@ -2343,9 -2128,9 +2344,9 @@@ static int save_head(const char *head written = write_in_full(fd, buf.buf, buf.len); strbuf_release(&buf); if (written < 0) { + error_errno(_("could not write to '%s'"), git_path_head_file()); rollback_lock_file(&head_lock); - return error_errno(_("could not write to '%s'"), - git_path_head_file()); + return -1; } if (commit_lock_file(&head_lock) < 0) return error(_("failed to finalize '%s'"), git_path_head_file()); @@@ -2466,27 -2251,29 +2467,27 @@@ static int save_todo(struct todo_list * fd = hold_lock_file_for_update(&todo_lock, todo_path, 0); if (fd < 0) return error_errno(_("could not lock '%s'"), todo_path); - offset = next < todo_list->nr ? - todo_list->items[next].offset_in_buf : todo_list->buf.len; + offset = get_item_line_offset(todo_list, next); if (write_in_full(fd, todo_list->buf.buf + offset, todo_list->buf.len - offset) < 0) return error_errno(_("could not write to '%s'"), todo_path); if (commit_lock_file(&todo_lock) < 0) return error(_("failed to finalize '%s'"), todo_path); - if (is_rebase_i(opts)) { - const char *done_path = rebase_path_done(); - int fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); - int prev_offset = !next ? 0 : - todo_list->items[next - 1].offset_in_buf; + if (is_rebase_i(opts) && next > 0) { + const char *done = rebase_path_done(); + int fd = open(done, O_CREAT | O_WRONLY | O_APPEND, 0666); + int ret = 0; - if (fd >= 0 && offset > prev_offset && - write_in_full(fd, todo_list->buf.buf + prev_offset, - offset - prev_offset) < 0) { - close(fd); - return error_errno(_("could not write to '%s'"), - done_path); - } - if (fd >= 0) - close(fd); + if (fd < 0) + return 0; + if (write_in_full(fd, get_item_line(todo_list, next - 1), + get_item_line_length(todo_list, next - 1)) + < 0) + ret = error_errno(_("could not write to '%s'"), done); + if (close(fd) < 0) + ret = error_errno(_("failed to finalize '%s'"), done); + return ret; } return 0; } @@@ -2614,9 -2401,10 +2615,9 @@@ static int error_with_patch(struct comm static int error_failed_squash(struct commit *commit, struct replay_opts *opts, int subject_len, const char *subject) { - if (rename(rebase_path_squash_msg(), rebase_path_message())) - return error(_("could not rename '%s' to '%s'"), + if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666)) + return error(_("could not copy '%s' to '%s'"), rebase_path_squash_msg(), rebase_path_message()); - unlink(rebase_path_fixup_msg()); unlink(git_path_merge_msg()); if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666)) return error(_("could not copy '%s' to '%s'"), @@@ -2669,377 -2457,6 +2670,377 @@@ static int do_exec(const char *command_ return status; } +static int safe_append(const char *filename, const char *fmt, ...) +{ + va_list ap; + struct lock_file lock = LOCK_INIT; + int fd = hold_lock_file_for_update(&lock, filename, + LOCK_REPORT_ON_ERROR); + struct strbuf buf = STRBUF_INIT; + + if (fd < 0) + return -1; + + if (strbuf_read_file(&buf, filename, 0) < 0 && errno != ENOENT) { + error_errno(_("could not read '%s'"), filename); + rollback_lock_file(&lock); + return -1; + } + strbuf_complete(&buf, '\n'); + va_start(ap, fmt); + strbuf_vaddf(&buf, fmt, ap); + va_end(ap); + + if (write_in_full(fd, buf.buf, buf.len) < 0) { + error_errno(_("could not write to '%s'"), filename); + strbuf_release(&buf); + rollback_lock_file(&lock); + return -1; + } + if (commit_lock_file(&lock) < 0) { + strbuf_release(&buf); + rollback_lock_file(&lock); + return error(_("failed to finalize '%s'"), filename); + } + + strbuf_release(&buf); + return 0; +} + +static int do_label(const char *name, int len) +{ + struct ref_store *refs = get_main_ref_store(the_repository); + struct ref_transaction *transaction; + struct strbuf ref_name = STRBUF_INIT, err = STRBUF_INIT; + struct strbuf msg = STRBUF_INIT; + int ret = 0; + struct object_id head_oid; + + if (len == 1 && *name == '#') + return error("Illegal label name: '%.*s'", len, name); + + strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); + strbuf_addf(&msg, "rebase -i (label) '%.*s'", len, name); + + transaction = ref_store_transaction_begin(refs, &err); + if (!transaction) { + error("%s", err.buf); + ret = -1; + } else if (get_oid("HEAD", &head_oid)) { + error(_("could not read HEAD")); + ret = -1; + } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid, + NULL, 0, msg.buf, &err) < 0 || + ref_transaction_commit(transaction, &err)) { + error("%s", err.buf); + ret = -1; + } + ref_transaction_free(transaction); + strbuf_release(&err); + strbuf_release(&msg); + + if (!ret) + ret = safe_append(rebase_path_refs_to_delete(), + "%s\n", ref_name.buf); + strbuf_release(&ref_name); + + return ret; +} + +static const char *reflog_message(struct replay_opts *opts, + const char *sub_action, const char *fmt, ...); + +static int do_reset(const char *name, int len, struct replay_opts *opts) +{ + struct strbuf ref_name = STRBUF_INIT; + struct object_id oid; + struct lock_file lock = LOCK_INIT; + struct tree_desc desc; + struct tree *tree; + struct unpack_trees_options unpack_tree_opts; + int ret = 0, i; + + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) + return -1; + + if (len == 10 && !strncmp("[new root]", name, len)) { + if (!opts->have_squash_onto) { + const char *hex; + if (commit_tree("", 0, the_hash_algo->empty_tree, + NULL, &opts->squash_onto, + NULL, NULL)) + return error(_("writing fake root commit")); + opts->have_squash_onto = 1; + hex = oid_to_hex(&opts->squash_onto); + if (write_message(hex, strlen(hex), + rebase_path_squash_onto(), 0)) + return error(_("writing squash-onto")); + } + oidcpy(&oid, &opts->squash_onto); + } else { + /* Determine the length of the label */ + for (i = 0; i < len; i++) + if (isspace(name[i])) + len = i; + + strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); + if (get_oid(ref_name.buf, &oid) && + get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) { + error(_("could not read '%s'"), ref_name.buf); + rollback_lock_file(&lock); + strbuf_release(&ref_name); + return -1; + } + } + + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(&unpack_tree_opts, "reset"); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = &the_index; + unpack_tree_opts.dst_index = &the_index; + unpack_tree_opts.fn = oneway_merge; + unpack_tree_opts.merge = 1; + unpack_tree_opts.update = 1; + + if (read_cache_unmerged()) { + rollback_lock_file(&lock); + strbuf_release(&ref_name); + return error_resolve_conflict(_(action_name(opts))); + } + + if (!fill_tree_descriptor(&desc, &oid)) { + error(_("failed to find tree of %s"), oid_to_hex(&oid)); + rollback_lock_file(&lock); + free((void *)desc.buffer); + strbuf_release(&ref_name); + return -1; + } + + if (unpack_trees(1, &desc, &unpack_tree_opts)) { + rollback_lock_file(&lock); + free((void *)desc.buffer); + strbuf_release(&ref_name); + return -1; + } + + tree = parse_tree_indirect(&oid); + prime_cache_tree(&the_index, tree); + + if (write_locked_index(&the_index, &lock, COMMIT_LOCK) < 0) + ret = error(_("could not write index")); + free((void *)desc.buffer); + + if (!ret) + ret = update_ref(reflog_message(opts, "reset", "'%.*s'", + len, name), "HEAD", &oid, + NULL, 0, UPDATE_REFS_MSG_ON_ERR); + + strbuf_release(&ref_name); + return ret; +} + +static int do_merge(struct commit *commit, const char *arg, int arg_len, + int flags, struct replay_opts *opts) +{ + int run_commit_flags = (flags & TODO_EDIT_MERGE_MSG) ? + EDIT_MSG | VERIFY_MSG : 0; + struct strbuf ref_name = STRBUF_INIT; + struct commit *head_commit, *merge_commit, *i; + struct commit_list *bases, *j, *reversed = NULL; + struct merge_options o; + int merge_arg_len, oneline_offset, can_fast_forward, ret; + static struct lock_file lock; + const char *p; + + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { + ret = -1; + goto leave_merge; + } + + head_commit = lookup_commit_reference_by_name("HEAD"); + if (!head_commit) { + ret = error(_("cannot merge without a current revision")); + goto leave_merge; + } + + oneline_offset = arg_len; + merge_arg_len = strcspn(arg, " \t\n"); + p = arg + merge_arg_len; + p += strspn(p, " \t\n"); + if (*p == '#' && (!p[1] || isspace(p[1]))) { + p += 1 + strspn(p + 1, " \t\n"); + oneline_offset = p - arg; + } else if (p - arg < arg_len) + BUG("octopus merges are not supported yet: '%s'", p); + + strbuf_addf(&ref_name, "refs/rewritten/%.*s", merge_arg_len, arg); + merge_commit = lookup_commit_reference_by_name(ref_name.buf); + if (!merge_commit) { + /* fall back to non-rewritten ref or commit */ + strbuf_splice(&ref_name, 0, strlen("refs/rewritten/"), "", 0); + merge_commit = lookup_commit_reference_by_name(ref_name.buf); + } + + if (!merge_commit) { + ret = error(_("could not resolve '%s'"), ref_name.buf); + goto leave_merge; + } + + if (opts->have_squash_onto && + !oidcmp(&head_commit->object.oid, &opts->squash_onto)) { + /* + * When the user tells us to "merge" something into a + * "[new root]", let's simply fast-forward to the merge head. + */ + rollback_lock_file(&lock); + ret = fast_forward_to(&merge_commit->object.oid, + &head_commit->object.oid, 0, opts); + goto leave_merge; + } + + if (commit) { + const char *message = get_commit_buffer(commit, NULL); + const char *body; + int len; + + if (!message) { + ret = error(_("could not get commit message of '%s'"), + oid_to_hex(&commit->object.oid)); + goto leave_merge; + } + write_author_script(message); + find_commit_subject(message, &body); + len = strlen(body); + ret = write_message(body, len, git_path_merge_msg(), 0); + unuse_commit_buffer(commit, message); + if (ret) { + error_errno(_("could not write '%s'"), + git_path_merge_msg()); + goto leave_merge; + } + } else { + struct strbuf buf = STRBUF_INIT; + int len; + + strbuf_addf(&buf, "author %s", git_author_info(0)); + write_author_script(buf.buf); + strbuf_reset(&buf); + + if (oneline_offset < arg_len) { + p = arg + oneline_offset; + len = arg_len - oneline_offset; + } else { + strbuf_addf(&buf, "Merge branch '%.*s'", + merge_arg_len, arg); + p = buf.buf; + len = buf.len; + } + + ret = write_message(p, len, git_path_merge_msg(), 0); + strbuf_release(&buf); + if (ret) { + error_errno(_("could not write '%s'"), + git_path_merge_msg()); + goto leave_merge; + } + } + + /* + * If HEAD is not identical to the first parent of the original merge + * commit, we cannot fast-forward. + */ + can_fast_forward = opts->allow_ff && commit && commit->parents && + !oidcmp(&commit->parents->item->object.oid, + &head_commit->object.oid); + + /* + * If the merge head is different from the original one, we cannot + * fast-forward. + */ + if (can_fast_forward) { + struct commit_list *second_parent = commit->parents->next; + + if (second_parent && !second_parent->next && + oidcmp(&merge_commit->object.oid, + &second_parent->item->object.oid)) + can_fast_forward = 0; + } + + if (can_fast_forward && commit->parents->next && + !commit->parents->next->next && + !oidcmp(&commit->parents->next->item->object.oid, + &merge_commit->object.oid)) { + rollback_lock_file(&lock); + ret = fast_forward_to(&commit->object.oid, + &head_commit->object.oid, 0, opts); + goto leave_merge; + } + + write_message(oid_to_hex(&merge_commit->object.oid), GIT_SHA1_HEXSZ, + git_path_merge_head(), 0); + write_message("no-ff", 5, git_path_merge_mode(), 0); + + bases = get_merge_bases(head_commit, merge_commit); + if (bases && !oidcmp(&merge_commit->object.oid, + &bases->item->object.oid)) { + ret = 0; + /* skip merging an ancestor of HEAD */ + goto leave_merge; + } + + for (j = bases; j; j = j->next) + commit_list_insert(j->item, &reversed); + free_commit_list(bases); + + read_cache(); + init_merge_options(&o); + o.branch1 = "HEAD"; + o.branch2 = ref_name.buf; + o.buffer_output = 2; + + ret = merge_recursive(&o, head_commit, merge_commit, reversed, &i); + if (ret <= 0) + fputs(o.obuf.buf, stdout); + strbuf_release(&o.obuf); + if (ret < 0) { + error(_("could not even attempt to merge '%.*s'"), + merge_arg_len, arg); + goto leave_merge; + } + /* + * The return value of merge_recursive() is 1 on clean, and 0 on + * unclean merge. + * + * Let's reverse that, so that do_merge() returns 0 upon success and + * 1 upon failed merge (keeping the return value -1 for the cases where + * we will want to reschedule the `merge` command). + */ + ret = !ret; + + if (active_cache_changed && + write_locked_index(&the_index, &lock, COMMIT_LOCK)) { + ret = error(_("merge: Unable to write new index file")); + goto leave_merge; + } + + rollback_lock_file(&lock); + if (ret) + rerere(opts->allow_rerere_auto); + else + /* + * In case of problems, we now want to return a positive + * value (a negative one would indicate that the `merge` + * command needs to be rescheduled). + */ + ret = !!run_git_commit(git_path_merge_msg(), opts, + run_commit_flags); + +leave_merge: + strbuf_release(&ref_name); + rollback_lock_file(&lock); + return ret; +} + static int is_final_fixup(struct todo_list *todo_list) { int i = todo_list->current; @@@ -3130,20 -2547,9 +3131,20 @@@ static const char *reflog_message(struc return buf.buf; } +static const char rescheduled_advice[] = +N_("Could not execute the todo command\n" +"\n" +" %.*s" +"\n" +"It has been rescheduled; To edit the command before continuing, please\n" +"edit the todo list first:\n" +"\n" +" git rebase --edit-todo\n" +" git rebase --continue\n"); + static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) { - int res = 0; + int res = 0, reschedule = 0; setenv(GIT_REFLOG_ACTION, action_name(opts), 0); if (opts->allow_ff) @@@ -3186,11 -2592,6 +3187,11 @@@ opts, is_final_fixup(todo_list)); if (is_rebase_i(opts) && res < 0) { /* Reschedule */ + advise(_(rescheduled_advice), + get_item_line_length(todo_list, + todo_list->current), + get_item_line(todo_list, + todo_list->current)); todo_list->current--; if (save_todo(todo_list, opts)) return -1; @@@ -3214,7 -2615,7 +3215,7 @@@ intend_to_amend(); return error_failed_squash(item->commit, opts, item->arg_len, item->arg); - } else if (res && is_rebase_i(opts)) + } else if (res && is_rebase_i(opts) && item->commit) return res | error_with_patch(item->commit, item->arg, item->arg_len, opts, res, item->command == TODO_REWORD); @@@ -3240,44 -2641,9 +3241,44 @@@ /* `current` will be incremented below */ todo_list->current = -1; } + } else if (item->command == TODO_LABEL) { + if ((res = do_label(item->arg, item->arg_len))) + reschedule = 1; + } else if (item->command == TODO_RESET) { + if ((res = do_reset(item->arg, item->arg_len, opts))) + reschedule = 1; + } else if (item->command == TODO_MERGE) { + if ((res = do_merge(item->commit, + item->arg, item->arg_len, + item->flags, opts)) < 0) + reschedule = 1; + else if (item->commit) + record_in_rewritten(&item->commit->object.oid, + peek_command(todo_list, 1)); + if (res > 0) + /* failed with merge conflicts */ + return error_with_patch(item->commit, + item->arg, + item->arg_len, opts, + res, 0); } else if (!is_noop(item->command)) return error(_("unknown command %d"), item->command); + if (reschedule) { + advise(_(rescheduled_advice), + get_item_line_length(todo_list, + todo_list->current), + get_item_line(todo_list, todo_list->current)); + todo_list->current--; + if (save_todo(todo_list, opts)) + return -1; + if (item->commit) + return error_with_patch(item->commit, + item->arg, + item->arg_len, opts, + res, 0); + } + todo_list->current++; if (res) return res; @@@ -3404,16 -2770,19 +3405,16 @@@ static int continue_single_pick(void return run_command_v_opt(argv, RUN_GIT_CMD); } -static int commit_staged_changes(struct replay_opts *opts) +static int commit_staged_changes(struct replay_opts *opts, + struct todo_list *todo_list) { unsigned int flags = ALLOW_EMPTY | EDIT_MSG; + unsigned int final_fixup = 0, is_clean; if (has_unstaged_changes(1)) return error(_("cannot rebase: You have unstaged changes.")); - if (!has_uncommitted_changes(0)) { - const char *cherry_pick_head = git_path_cherry_pick_head(); - if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) - return error(_("could not remove CHERRY_PICK_HEAD")); - return 0; - } + is_clean = !has_uncommitted_changes(0); if (file_exists(rebase_path_amend())) { struct strbuf rev = STRBUF_INIT; @@@ -3426,107 -2795,19 +3427,107 @@@ if (get_oid_hex(rev.buf, &to_amend)) return error(_("invalid contents: '%s'"), rebase_path_amend()); - if (oidcmp(&head, &to_amend)) + if (!is_clean && oidcmp(&head, &to_amend)) return error(_("\nYou have uncommitted changes in your " "working tree. Please, commit them\n" "first and then run 'git rebase " "--continue' again.")); + /* + * When skipping a failed fixup/squash, we need to edit the + * commit message, the current fixup list and count, and if it + * was the last fixup/squash in the chain, we need to clean up + * the commit message and if there was a squash, let the user + * edit it. + */ + if (is_clean && !oidcmp(&head, &to_amend) && + opts->current_fixup_count > 0 && + file_exists(rebase_path_stopped_sha())) { + const char *p = opts->current_fixups.buf; + int len = opts->current_fixups.len; + + opts->current_fixup_count--; + if (!len) + BUG("Incorrect current_fixups:\n%s", p); + while (len && p[len - 1] != '\n') + len--; + strbuf_setlen(&opts->current_fixups, len); + if (write_message(p, len, rebase_path_current_fixups(), + 0) < 0) + return error(_("could not write file: '%s'"), + rebase_path_current_fixups()); + + /* + * If a fixup/squash in a fixup/squash chain failed, the + * commit message is already correct, no need to commit + * it again. + * + * Only if it is the final command in the fixup/squash + * chain, and only if the chain is longer than a single + * fixup/squash command (which was just skipped), do we + * actually need to re-commit with a cleaned up commit + * message. + */ + if (opts->current_fixup_count > 0 && + !is_fixup(peek_command(todo_list, 0))) { + final_fixup = 1; + /* + * If there was not a single "squash" in the + * chain, we only need to clean up the commit + * message, no need to bother the user with + * opening the commit message in the editor. + */ + if (!starts_with(p, "squash ") && + !strstr(p, "\nsquash ")) + flags = (flags & ~EDIT_MSG) | CLEANUP_MSG; + } else if (is_fixup(peek_command(todo_list, 0))) { + /* + * We need to update the squash message to skip + * the latest commit message. + */ + struct commit *commit; + const char *path = rebase_path_squash_msg(); + + if (parse_head(&commit) || + !(p = get_commit_buffer(commit, NULL)) || + write_message(p, strlen(p), path, 0)) { + unuse_commit_buffer(commit, p); + return error(_("could not write file: " + "'%s'"), path); + } + unuse_commit_buffer(commit, p); + } + } strbuf_release(&rev); flags |= AMEND_MSG; } - if (run_git_commit(rebase_path_message(), opts, flags)) + if (is_clean) { + const char *cherry_pick_head = git_path_cherry_pick_head(); + + if (file_exists(cherry_pick_head) && unlink(cherry_pick_head)) + return error(_("could not remove CHERRY_PICK_HEAD")); + if (!final_fixup) + return 0; + } + + if (run_git_commit(final_fixup ? NULL : rebase_path_message(), + opts, flags)) return error(_("could not commit staged changes.")); unlink(rebase_path_amend()); + if (final_fixup) { + unlink(rebase_path_fixup_msg()); + unlink(rebase_path_squash_msg()); + } + if (opts->current_fixup_count > 0) { + /* + * Whether final fixup or not, we just cleaned up the commit + * message... + */ + unlink(rebase_path_current_fixups()); + strbuf_reset(&opts->current_fixups); + opts->current_fixup_count = 0; + } return 0; } @@@ -3538,16 -2819,14 +3539,16 @@@ int sequencer_continue(struct replay_op if (read_and_refresh_cache(opts)) return -1; + if (read_populate_opts(opts)) + return -1; if (is_rebase_i(opts)) { - if (commit_staged_changes(opts)) + if ((res = read_populate_todo(&todo_list, opts))) + goto release_todo_list; + if (commit_staged_changes(opts, &todo_list)) return -1; } else if (!file_exists(get_todo_path(opts))) return continue_single_pick(); - if (read_populate_opts(opts)) - return -1; - if ((res = read_populate_todo(&todo_list, opts))) + else if ((res = read_populate_todo(&todo_list, opts))) goto release_todo_list; if (!is_rebase_i(opts)) { @@@ -3606,8 -2885,7 +3607,8 @@@ int sequencer_pick_revisions(struct rep if (!get_oid(name, &oid)) { if (!lookup_commit_reference_gently(&oid, 1)) { - enum object_type type = oid_object_info(&oid, + enum object_type type = oid_object_info(the_repository, + &oid, NULL); return error(_("%s: can't cherry-pick a %s"), name, type_name(type)); @@@ -3718,348 -2996,6 +3719,348 @@@ void append_signoff(struct strbuf *msgb strbuf_release(&sob); } +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, 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)) == the_hash_algo->hexsz && + !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; + int is_empty; + + tail = &commit_list_insert(commit, tail)->next; + oidset_insert(&interesting, &commit->object.oid); + + is_empty = is_original_commit_empty(commit); + if (!is_empty && (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_empty) + 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 %s\n", cmd_reset, + rebase_cousins ? "onto" : "[new root]"); + 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; +} + int sequencer_make_script(FILE *out, int argc, const char **argv, unsigned flags) { @@@ -4070,12 -3006,10 +4071,12 @@@ struct commit *commit; int keep_empty = flags & TODO_LIST_KEEP_EMPTY; const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; + int rebase_merges = flags & TODO_LIST_REBASE_MERGES; init_revisions(&revs, NULL); revs.verbose_header = 1; - revs.max_parents = 1; + if (!rebase_merges) + revs.max_parents = 1; revs.cherry_mark = 1; revs.limited = 1; revs.reverse = 1; @@@ -4100,9 -3034,6 +4101,9 @@@ if (prepare_revision_walk(&revs) < 0) return error(_("make_script: error preparing revisions")); + if (rebase_merges) + return make_script_with_merges(&pp, &revs, out, flags); + while ((commit = get_revision(&revs))) { int is_empty = is_original_commit_empty(commit); @@@ -4196,16 -3127,8 +4197,16 @@@ int transform_todos(unsigned flags short_commit_name(item->commit) : oid_to_hex(&item->commit->object.oid); + if (item->command == TODO_MERGE) { + if (item->flags & TODO_EDIT_MERGE_MSG) + strbuf_addstr(&buf, " -c"); + else + strbuf_addstr(&buf, " -C"); + } + strbuf_addf(&buf, " %s", oid); } + /* add all the rest */ if (!item->arg_len) strbuf_addch(&buf, '\n'); @@@ -4238,6 -3161,7 +4239,7 @@@ static enum check_level get_missing_com return CHECK_IGNORE; } + define_commit_slab(commit_seen, unsigned char); /* * Check if the user dropped some commits by mistake * Behaviour determined by rebase.missingCommitsCheck. @@@ -4251,6 -3175,9 +4253,9 @@@ int check_todo_list(void struct todo_list todo_list = TODO_LIST_INIT; struct strbuf missing = STRBUF_INIT; int advise_to_edit_todo = 0, res = 0, i; + struct commit_seen commit_seen; + + init_commit_seen(&commit_seen); strbuf_addstr(&todo_file, rebase_path_todo()); if (strbuf_read_file_or_whine(&todo_list.buf, todo_file.buf) < 0) { @@@ -4267,7 -3194,7 +4272,7 @@@ for (i = 0; i < todo_list.nr; i++) { struct commit *commit = todo_list.items[i].commit; if (commit) - commit->util = (void *)1; + *commit_seen_at(&commit_seen, commit) = 1; } todo_list_release(&todo_list); @@@ -4283,11 -3210,11 +4288,11 @@@ for (i = todo_list.nr - 1; i >= 0; i--) { struct todo_item *item = todo_list.items + i; struct commit *commit = item->commit; - if (commit && !commit->util) { + if (commit && !*commit_seen_at(&commit_seen, commit)) { strbuf_addf(&missing, " - %s %.*s\n", short_commit_name(commit), item->arg_len, item->arg); - commit->util = (void *)1; + *commit_seen_at(&commit_seen, commit) = 1; } } @@@ -4313,6 -3240,7 +4318,7 @@@ "The possible behaviours are: ignore, warn, error.\n\n")); leave_check: + clear_commit_seen(&commit_seen); strbuf_release(&todo_file); todo_list_release(&todo_list); @@@ -4385,7 -3313,8 +4391,7 @@@ int skip_unnecessary_picks(void oid = &item->commit->object.oid; } if (i > 0) { - int offset = i < todo_list.nr ? - todo_list.items[i].offset_in_buf : todo_list.buf.len; + int offset = get_item_line_offset(&todo_list, i); const char *done_path = rebase_path_done(); fd = open(done_path, O_CREAT | O_WRONLY | O_APPEND, 0666); @@@ -4433,6 -3362,8 +4439,8 @@@ static int subject2item_cmp(const void return key ? strcmp(a->subject, key) : strcmp(a->subject, b->subject); } + define_commit_slab(commit_todo_item, struct todo_item *); + /* * Rearrange the todo list that has both "pick commit-id msg" and "pick * commit-id fixup!/squash! msg" in it so that the latter is put immediately @@@ -4449,6 -3380,7 +4457,7 @@@ int rearrange_squash(void struct hashmap subject2item; int res = 0, rearranged = 0, *next, *tail, i; char **subjects; + struct commit_todo_item commit_todo; if (strbuf_read_file_or_whine(&todo_list.buf, todo_file) < 0) return -1; @@@ -4457,6 -3389,7 +4466,7 @@@ return -1; } + init_commit_todo_item(&commit_todo); /* * The hashmap maps onelines to the respective todo list index. * @@@ -4480,17 -3413,18 +4490,18 @@@ struct subject2item_entry *entry; next[i] = tail[i] = -1; - if (item->command >= TODO_EXEC) { + if (!item->commit || item->command == TODO_DROP) { subjects[i] = NULL; continue; } if (is_fixup(item->command)) { todo_list_release(&todo_list); + clear_commit_todo_item(&commit_todo); return error(_("the script was already rearranged.")); } - item->commit->util = item; + *commit_todo_item_at(&commit_todo, item->commit) = item; parse_commit(item->commit); commit_buffer = get_commit_buffer(item->commit, NULL); @@@ -4517,9 -3451,9 +4528,9 @@@ else if (!strchr(p, ' ') && (commit2 = lookup_commit_reference_by_name(p)) && - commit2->util) + *commit_todo_item_at(&commit_todo, commit2)) /* found by commit name */ - i2 = (struct todo_item *)commit2->util + i2 = *commit_todo_item_at(&commit_todo, commit2) - todo_list.items; else { /* copy can be a prefix of the commit subject */ @@@ -4565,10 -3499,12 +4576,10 @@@ continue; while (cur >= 0) { - int offset = todo_list.items[cur].offset_in_buf; - int end_offset = cur + 1 < todo_list.nr ? - todo_list.items[cur + 1].offset_in_buf : - todo_list.buf.len; - char *bol = todo_list.buf.buf + offset; - char *eol = todo_list.buf.buf + end_offset; + const char *bol = + get_item_line(&todo_list, cur); + const char *eol = + get_item_line(&todo_list, cur + 1); /* replace 'pick', by 'fixup' or 'squash' */ command = todo_list.items[cur].command; @@@ -4596,5 -3532,6 +4607,6 @@@ hashmap_free(&subject2item, 1); todo_list_release(&todo_list); + clear_commit_todo_item(&commit_todo); return res; } diff --combined shallow.c index 9ff83cabcd,0301049781..2abebeb8c8 --- a/shallow.c +++ b/shallow.c @@@ -12,6 -12,7 +12,7 @@@ #include "commit-slab.h" #include "revision.h" #include "list-objects.h" + #include "commit-slab.h" static int is_shallow = -1; static struct stat_validity shallow_stat; @@@ -20,7 -21,7 +21,7 @@@ static char *alternate_shallow_file void set_alternate_shallow_file(const char *path, int override) { if (is_shallow != -1) - die("BUG: is_repository_shallow must not be called before set_alternate_shallow_file"); + BUG("is_repository_shallow must not be called before set_alternate_shallow_file"); if (alternate_shallow_file && !override) return; free(alternate_shallow_file); @@@ -74,6 -75,11 +75,11 @@@ int is_repository_shallow(void return is_shallow; } + /* + * TODO: use "int" elemtype instead of "int *" when/if commit-slab + * supports a "valid" flag. + */ + define_commit_slab(commit_depth, int *); struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag) { @@@ -82,25 -88,29 +88,29 @@@ struct object_array stack = OBJECT_ARRAY_INIT; struct commit *commit = NULL; struct commit_graft *graft; + struct commit_depth depths; + init_commit_depth(&depths); while (commit || i < heads->nr || stack.nr) { struct commit_list *p; if (!commit) { if (i < heads->nr) { + int **depth_slot; commit = (struct commit *) deref_tag(heads->objects[i++].item, NULL, 0); if (!commit || commit->object.type != OBJ_COMMIT) { commit = NULL; continue; } - if (!commit->util) - commit->util = xmalloc(sizeof(int)); - *(int *)commit->util = 0; + depth_slot = commit_depth_at(&depths, commit); + if (!*depth_slot) + *depth_slot = xmalloc(sizeof(int)); + **depth_slot = 0; cur_depth = 0; } else { commit = (struct commit *) object_array_pop(&stack); - cur_depth = *(int *)commit->util; + cur_depth = **commit_depth_at(&depths, commit); } } parse_commit_or_die(commit); @@@ -116,25 -126,31 +126,31 @@@ } commit->object.flags |= not_shallow_flag; for (p = commit->parents, commit = NULL; p; p = p->next) { - if (!p->item->util) { - int *pointer = xmalloc(sizeof(int)); - p->item->util = pointer; - *pointer = cur_depth; + int **depth_slot = commit_depth_at(&depths, p->item); + if (!*depth_slot) { + *depth_slot = xmalloc(sizeof(int)); + **depth_slot = cur_depth; } else { - int *pointer = p->item->util; - if (cur_depth >= *pointer) + if (cur_depth >= **depth_slot) continue; - *pointer = cur_depth; + **depth_slot = cur_depth; } if (p->next) add_object_array(&p->item->object, NULL, &stack); else { commit = p->item; - cur_depth = *(int *)commit->util; + cur_depth = **commit_depth_at(&depths, commit); } } } + for (i = 0; i < depths.slab_count; i++) { + int j; + + for (j = 0; j < depths.slab_size; j++) + free(depths.slab[i][j]); + } + clear_commit_depth(&depths); return result; } @@@ -218,7 -234,7 +234,7 @@@ struct commit_list *get_shallow_commits static void check_shallow_file_for_update(void) { if (is_shallow == -1) - die("BUG: shallow must be initialized by now"); + BUG("shallow must be initialized by now"); if (!stat_validity_check(&shallow_stat, git_path_shallow())) die("shallow file has changed since we read it"); @@@ -353,7 -369,7 +369,7 @@@ void advertise_shallow_grafts(int fd */ void prune_shallow(int show_only) { - static struct lock_file shallow_lock; + struct lock_file shallow_lock = LOCK_INIT; struct strbuf sb = STRBUF_INIT; int fd; @@@ -446,7 -462,7 +462,7 @@@ static uint32_t *paint_alloc(struct pai void *p; if (!info->pool_count || size > info->end - info->free) { if (size > POOL_SIZE) - die("BUG: pool size too small for %d in paint_alloc()", + BUG("pool size too small for %d in paint_alloc()", size); info->pool_count++; REALLOC_ARRAY(info->pools, info->pool_count);