From: Junio C Hamano Date: Sat, 4 Dec 2010 00:13:06 +0000 (-0800) Subject: Merge branch 'pn/commit-autosquash' X-Git-Tag: v1.7.4-rc0~90 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/9bec60d3a52fa10b276214f8d255d6ff05a04b77?ds=inline;hp=-c Merge branch 'pn/commit-autosquash' * pn/commit-autosquash: add tests of commit --squash commit: --squash option for use with rebase --autosquash add tests of commit --fixup commit: --fixup option for use with rebase --autosquash pretty.c: teach format_commit_message() to reencode the output commit: helper methods to reduce redundant blocks of code Conflicts: Documentation/git-commit.txt t/t3415-rebase-autosquash.sh --- 9bec60d3a52fa10b276214f8d255d6ff05a04b77 diff --combined Documentation/git-commit.txt index ec7b577b85,6e4c220f20..b586c0f442 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@@ -9,10 -9,10 +9,10 @@@ SYNOPSI -------- [verse] 'git commit' [-a | --interactive] [-s] [-v] [-u] [--amend] [--dry-run] - [(-c | -C) ] [-F | -m ] [--reset-author] - [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=] - [--date=] [--cleanup=] [--status | --no-status] - [-i | -o] [--] [...] + [(-c | -C | --fixup | --squash) ] [-F | -m ] + [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] + [-e] [--author=] [--date=] [--cleanup=] - [--status | --no-status] [--] [[-i | -o ]...] ++ [--status | --no-status] [-i | -o] [--] [...] DESCRIPTION ----------- @@@ -70,6 -70,19 +70,19 @@@ OPTION Like '-C', but with '-c' the editor is invoked, so that the user can further edit the commit message. + --fixup=:: + Construct a commit message for use with `rebase --autosquash`. + The commit message will be the subject line from the specified + commit with a prefix of "fixup! ". See linkgit:git-rebase[1] + for details. + + --squash=:: + Construct a commit message for use with `rebase --autosquash`. + The commit message subject line is taken from the specified + commit with a prefix of "squash! ". Can be used with additional + commit message options (`-m`/`-c`/`-C`/`-F`). See + linkgit:git-rebase[1] for details. + --reset-author:: When used with -C/-c/--amend options, declare that the authorship of the resulting commit now belongs of the committer. diff --combined builtin/commit.c index 4fd1a1692f,05c2c8129e..6d867d4018 --- a/builtin/commit.c +++ b/builtin/commit.c @@@ -69,6 -69,7 +69,7 @@@ static enum static const char *logfile, *force_author; static const char *template_file; static char *edit_message, *use_message; + static char *fixup_message, *squash_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; @@@ -114,8 -115,8 +115,8 @@@ static int opt_parse_m(const struct opt } static struct option builtin_commit_options[] = { - OPT__QUIET(&quiet), - OPT__VERBOSE(&verbose), + OPT__QUIET(&quiet, "suppress summary after successful commit"), + OPT__VERBOSE(&verbose, "show diff in commit message template"), OPT_GROUP("Commit message options"), OPT_FILENAME('F', "file", &logfile, "read log from file"), @@@ -124,6 -125,8 +125,8 @@@ OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), + OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"), + OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"), OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_FILENAME('t', "template", &template_file, "use specified template file"), @@@ -565,6 -568,25 +568,25 @@@ static int prepare_to_commit(const cha if (!no_verify && run_hook(index_file, "pre-commit", NULL)) return 0; + if (squash_message) { + /* + * Insert the proper subject line before other commit + * message options add their content. + */ + if (use_message && !strcmp(use_message, squash_message)) + strbuf_addstr(&sb, "squash! "); + else { + struct pretty_print_context ctx = {0}; + struct commit *c; + c = lookup_commit_reference_by_name(squash_message); + if (!c) + die("could not lookup commit %s", squash_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(c, "squash! %s\n\n", &sb, + &ctx); + } + } + if (message.len) { strbuf_addbuf(&sb, &message); hook_arg1 = "message"; @@@ -586,6 -608,16 +608,16 @@@ strbuf_add(&sb, buffer + 2, strlen(buffer + 2)); hook_arg1 = "commit"; hook_arg2 = use_message; + } else if (fixup_message) { + struct pretty_print_context ctx = {0}; + struct commit *commit; + commit = lookup_commit_reference_by_name(fixup_message); + if (!commit) + die("could not lookup commit %s", fixup_message); + ctx.output_encoding = get_commit_output_encoding(); + format_commit_message(commit, "fixup! %s\n\n", + &sb, &ctx); + hook_arg1 = "message"; } else if (!stat(git_path("MERGE_MSG"), &statbuf)) { if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0) die_errno("could not read MERGE_MSG"); @@@ -607,6 -639,16 +639,16 @@@ else if (in_merge) hook_arg1 = "merge"; + if (squash_message) { + /* + * If squash_commit was used for the commit subject, + * then we're possibly hijacking other commit log options. + * Reset the hook args to tell the real story. + */ + hook_arg1 = "message"; + hook_arg2 = ""; + } + fp = fopen(git_path(commit_editmsg), "w"); if (fp == NULL) die_errno("could not open '%s'", git_path(commit_editmsg)); @@@ -863,7 -905,7 +905,7 @@@ static int parse_and_validate_options(i if (force_author && renew_authorship) die("Using both --reset-author and --author does not make sense"); - if (logfile || message.len || use_message) + if (logfile || message.len || use_message || fixup_message) use_editor = 0; if (edit_flag) use_editor = 1; @@@ -878,48 -920,35 +920,35 @@@ die("You have nothing to amend."); if (amend && in_merge) die("You are in the middle of a merge -- cannot amend."); - + if (fixup_message && squash_message) + die("Options --squash and --fixup cannot be used together"); if (use_message) f++; if (edit_message) f++; + if (fixup_message) + f++; if (logfile) f++; if (f > 1) - die("Only one of -c/-C/-F can be used."); + die("Only one of -c/-C/-F/--fixup can be used."); if (message.len && f > 0) - die("Option -m cannot be combined with -c/-C/-F."); + die("Option -m cannot be combined with -c/-C/-F/--fixup."); if (edit_message) use_message = edit_message; - if (amend && !use_message) + if (amend && !use_message && !fixup_message) use_message = "HEAD"; if (!use_message && renew_authorship) die("--reset-author can be used only with -C, -c or --amend."); if (use_message) { - unsigned char sha1[20]; - static char utf8[] = "UTF-8"; const char *out_enc; - char *enc, *end; struct commit *commit; - if (get_sha1(use_message, sha1)) + commit = lookup_commit_reference_by_name(use_message); + if (!commit) die("could not lookup commit %s", use_message); - commit = lookup_commit_reference(sha1); - if (!commit || parse_commit(commit)) - die("could not parse commit %s", use_message); - - enc = strstr(commit->buffer, "\nencoding"); - if (enc) { - end = strchr(enc + 10, '\n'); - enc = xstrndup(enc + 10, end - (enc + 10)); - } else { - enc = utf8; - } - out_enc = git_commit_encoding ? git_commit_encoding : utf8; - - if (strcmp(out_enc, enc)) - use_message_buffer = - reencode_string(commit->buffer, out_enc, enc); + out_enc = get_commit_output_encoding(); + use_message_buffer = logmsg_reencode(commit, out_enc); /* * If we failed to reencode the buffer, just copy it @@@ -929,8 -958,6 +958,6 @@@ */ if (use_message_buffer == NULL) use_message_buffer = xstrdup(commit->buffer); - if (enc != utf8) - free(enc); } if (!!also + !!only + !!all + !!interactive > 1) @@@ -1048,7 -1075,7 +1075,7 @@@ int cmd_status(int argc, const char **a int fd; unsigned char sha1[20]; static struct option builtin_status_options[] = { - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_SET_INT('s', "short", &status_format, "show status concisely", STATUS_FORMAT_SHORT), OPT_BOOLEAN('b', "branch", &status_show_branch, diff --combined builtin/log.c index d0297a1c5e,90e05aca9d..4191d9c4e5 --- a/builtin/log.c +++ b/builtin/log.c @@@ -329,8 -329,7 +329,7 @@@ static void show_tagger(char *buf, int struct strbuf out = STRBUF_INIT; pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, - git_log_output_encoding ? - git_log_output_encoding: git_commit_encoding); + get_log_output_encoding()); printf("%s", out.buf); strbuf_release(&out); } @@@ -1365,7 -1364,7 +1364,7 @@@ int cmd_cherry(int argc, const char **a struct option options[] = { OPT__ABBREV(&abbrev), - OPT__VERBOSE(&verbose), + OPT__VERBOSE(&verbose, "be verbose"), OPT_END() }; diff --combined cache.h index e56a0a23ae,7d498056b2..e83bc2d3bb --- a/cache.h +++ b/cache.h @@@ -445,7 -445,7 +445,7 @@@ extern int init_db(const char *template * at least 'nr' entries; the number of entries currently allocated * is 'alloc', using the standard growing factor alloc_nr() macro. * - * DO NOT USE any expression with side-effect for 'x' or 'alloc'. + * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'. */ #define ALLOC_GROW(x, nr, alloc) \ do { \ @@@ -545,7 -545,6 +545,7 @@@ extern int assume_unchanged extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; +extern int unique_abbrev_extra_length; extern int shared_repository; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; @@@ -860,7 -859,7 +860,7 @@@ struct cache_def extern int has_symlink_leading_path(const char *name, int len); extern int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); -extern int has_symlink_or_noent_leading_path(const char *name, int len); +extern int check_leading_path(const char *name, int len); extern int has_dirs_only_path(const char *name, int len, int prefix_len); extern void schedule_dir_for_removal(const char *name, int len); extern void remove_scheduled_dirs(void); @@@ -1004,6 -1003,9 +1004,9 @@@ extern int git_env_bool(const char *, i extern int git_config_system(void); extern int git_config_global(void); extern int config_error_nonbool(const char *); + extern const char *get_log_output_encoding(void); + extern const char *get_commit_output_encoding(void); + extern const char *config_exclusive_filename; #define MAX_GITNAME (1000) diff --combined commit.c index 2d9265d9fe,5ed9ccd723..b21335ee4c --- a/commit.c +++ b/commit.c @@@ -49,6 -49,19 +49,19 @@@ struct commit *lookup_commit(const unsi return check_commit(obj, sha1, 0); } + struct commit *lookup_commit_reference_by_name(const char *name) + { + unsigned char sha1[20]; + struct commit *commit; + + if (get_sha1(name, sha1)) + return NULL; + commit = lookup_commit_reference(sha1); + if (!commit || parse_commit(commit)) + return NULL; + return commit; + } + static unsigned long parse_commit_date(const char *buf, const char *tail) { const char *dateptr; @@@ -137,8 -150,12 +150,8 @@@ struct commit_graft *read_graft_line(ch buf[--len] = '\0'; if (buf[0] == '#' || buf[0] == '\0') return NULL; - if ((len + 1) % 41) { - bad_graft_data: - error("bad graft data: %s", buf); - free(graft); - return NULL; - } + if ((len + 1) % 41) + goto bad_graft_data; i = (len + 1) / 41 - 1; graft = xmalloc(sizeof(*graft) + 20 * i); graft->nr_parent = i; @@@ -151,11 -168,6 +164,11 @@@ goto bad_graft_data; } return graft; + +bad_graft_data: + error("bad graft data: %s", buf); + free(graft); + return NULL; } static int read_graft_file(const char *graft_file) diff --combined environment.c index 92e16b19b2,a9d44a28a7..76e4dee273 --- a/environment.c +++ b/environment.c @@@ -21,7 -21,6 +21,7 @@@ int prefer_symlink_refs int is_bare_repository_cfg = -1; /* unspecified */ int log_all_ref_updates = -1; /* unspecified */ int warn_ambiguous_refs = 1; +int unique_abbrev_extra_length; int repository_format_version; const char *git_commit_encoding; const char *git_log_output_encoding; @@@ -193,3 -192,14 +193,14 @@@ int set_git_dir(const char *path setup_git_env(); return 0; } + + const char *get_log_output_encoding(void) + { + return git_log_output_encoding ? git_log_output_encoding + : get_commit_output_encoding(); + } + + const char *get_commit_output_encoding(void) + { + return git_commit_encoding ? git_commit_encoding : "UTF-8"; + } diff --combined t/t3415-rebase-autosquash.sh index ca16b70373,0028533e0b..b38be8e937 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@@ -14,6 -14,7 +14,7 @@@ test_expect_success setup git add . && test_tick && git commit -m "first commit" && + git tag first-commit && echo 3 >file3 && git add . && test_tick && @@@ -21,7 -22,7 +22,7 @@@ git tag base ' - test_auto_fixup() { + test_auto_fixup () { git reset --hard base && echo 1 >file1 && git add -u && @@@ -50,7 -51,7 +51,7 @@@ test_expect_success 'auto fixup (config test_must_fail test_auto_fixup final-fixup-config-false ' - test_auto_squash() { + test_auto_squash () { git reset --hard base && echo 1 >file1 && git add -u && @@@ -94,78 -95,28 +95,102 @@@ test_expect_success 'misspelled auto sq test 0 = $(git rev-list final-missquash...HEAD | wc -l) ' +test_expect_success 'auto squash that matches 2 commits' ' + git reset --hard base && + echo 4 >file4 && + git add file4 && + test_tick && + git commit -m "first new commit" && + echo 1 >file1 && + git add -u && + test_tick && + git commit -m "squash! first" && + git tag final-multisquash && + test_tick && + git rebase --autosquash -i HEAD~4 && + git log --oneline >actual && + test 4 = $(wc -l file1 && + git add -u && + test_tick && + git commit -m "squash! third" && + echo 4 >file4 && + git add file4 && + test_tick && + git commit -m "third commit" && + git tag final-presquash && + test_tick && + git rebase --autosquash -i HEAD~4 && + git log --oneline >actual && + test 5 = $(wc -l file1 && + git add -u && + test_tick && + git commit -m "squash! $(git rev-parse --short HEAD^)" && + git tag final-shasquash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l file1 && + git add -u && + test_tick && + git commit -m "squash! $(git rev-parse --short=11 HEAD^)" && + git tag final-longshasquash && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l file1 && + git add -u && + test_tick && + git commit --$1 first-commit && + git tag final-commit-$1 && + test_tick && + git rebase --autosquash -i HEAD^^^ && + git log --oneline >actual && + test 3 = $(wc -l