From 984913a210bb6d17fb3dd515975930661a00c3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 11 Feb 2018 16:43:26 +0700 Subject: [PATCH 1/3] am: add --show-current-patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pointing the user to $GIT_DIR/rebase-apply may encourage them to mess around in there, which is not a good thing. With this, the user does not have to keep the path around somewhere (because after a couple of commands, the path may be out of scrollback buffer) when they need to look at the patch. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-am.txt | 6 ++++- builtin/am.c | 32 ++++++++++++++++++++++---- contrib/completion/git-completion.bash | 2 +- t/t4150-am.sh | 5 ++++ 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 12879e4029..0f426ae874 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -16,7 +16,7 @@ SYNOPSIS [--exclude=] [--include=] [--reject] [-q | --quiet] [--[no-]scissors] [-S[]] [--patch-format=] [( | )...] -'git am' (--continue | --skip | --abort) +'git am' (--continue | --skip | --abort | --show-current-patch) DESCRIPTION ----------- @@ -167,6 +167,10 @@ default. You can use `--no-utf8` to override this. --abort:: Restore the original branch and abort the patching operation. +--show-current-patch:: + Show the patch being applied when "git am" is stopped because + of conflicts. + DISCUSSION ---------- diff --git a/builtin/am.c b/builtin/am.c index acfe9d3c8c..07abfb8f83 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1831,8 +1831,7 @@ static void am_run(struct am_state *state, int resume) git_config_get_bool("advice.amworkdir", &advice_amworkdir); if (advice_amworkdir) - printf_ln(_("The copy of the patch that failed is found in: %s"), - am_path(state, "patch")); + printf_ln(_("Use 'git am --show-current-patch' to see the failed patch")); die_user_resolve(state); } @@ -2121,6 +2120,23 @@ static void am_abort(struct am_state *state) am_destroy(state); } +static int show_patch(struct am_state *state) +{ + struct strbuf sb = STRBUF_INIT; + const char *patch_path; + int len; + + patch_path = am_path(state, msgnum(state)); + len = strbuf_read_file(&sb, patch_path, 0); + if (len < 0) + die_errno(_("failed to read '%s'"), patch_path); + + setup_pager(); + write_in_full(1, sb.buf, sb.len); + strbuf_release(&sb); + return 0; +} + /** * parse_options() callback that validates and sets opt->value to the * PATCH_FORMAT_* enum value corresponding to `arg`. @@ -2149,7 +2165,8 @@ enum resume_mode { RESUME_APPLY, RESUME_RESOLVED, RESUME_SKIP, - RESUME_ABORT + RESUME_ABORT, + RESUME_SHOW_PATCH }; static int git_am_config(const char *k, const char *v, void *cb) @@ -2171,6 +2188,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) int patch_format = PATCH_FORMAT_UNKNOWN; enum resume_mode resume = RESUME_FALSE; int in_progress; + int ret = 0; const char * const usage[] = { N_("git am [] [( | )...]"), @@ -2249,6 +2267,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "abort", &resume, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), + OPT_CMDMODE(0, "show-current-patch", &resume, + N_("show the patch being applied."), + RESUME_SHOW_PATCH), OPT_BOOL(0, "committer-date-is-author-date", &state.committer_date_is_author_date, N_("lie about committer date")), @@ -2359,11 +2380,14 @@ int cmd_am(int argc, const char **argv, const char *prefix) case RESUME_ABORT: am_abort(&state); break; + case RESUME_SHOW_PATCH: + ret = show_patch(&state); + break; default: die("BUG: invalid resume value"); } am_state_release(&state); - return 0; + return ret; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 3683c772c5..56ca445fa8 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1077,7 +1077,7 @@ _git_am () { __git_find_repo_path if [ -d "$__git_repo_path"/rebase-apply ]; then - __gitcomp "--skip --continue --resolved --abort" + __gitcomp "--skip --continue --resolved --abort --show-current-patch" return fi case "$cur" in diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 73b67b4280..23abf42abc 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -662,6 +662,11 @@ test_expect_success 'am pauses on conflict' ' test -d .git/rebase-apply ' +test_expect_success 'am --show-current-patch' ' + git am --show-current-patch >actual.patch && + test_cmp .git/rebase-apply/0001 actual.patch +' + test_expect_success 'am --skip works' ' echo goodbye >expected && git am --skip && From 66335298a47032267edd6d6e7a71cc337e46e995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 11 Feb 2018 16:43:27 +0700 Subject: [PATCH 2/3] rebase: add --show-current-patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is useful to see the full patch while resolving conflicts in a rebase. The only way to do it now is less .git/rebase-*/patch which could turn out to be a lot longer to type if you are in a linked worktree, or not at top-dir. On top of that, an ordinary user should not need to peek into .git directory. The new option is provided to examine the patch. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 6 ++++- builtin/am.c | 11 +++++++++ contrib/completion/git-completion.bash | 4 ++-- git-rebase--am.sh | 3 +++ git-rebase--interactive.sh | 3 +++ git-rebase--merge.sh | 3 +++ git-rebase.sh | 7 +++++- t/t3400-rebase.sh | 33 ++++++++++++++++++++++++++ t/t3404-rebase-interactive.sh | 5 ++++ 9 files changed, 71 insertions(+), 4 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 8a861c1e0d..7ef9577472 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -12,7 +12,7 @@ SYNOPSIS [ []] 'git rebase' [-i | --interactive] [options] [--exec ] [--onto ] --root [] -'git rebase' --continue | --skip | --abort | --quit | --edit-todo +'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch DESCRIPTION ----------- @@ -250,6 +250,10 @@ leave out at most one of A and B, in which case it defaults to HEAD. --edit-todo:: Edit the todo list during an interactive rebase. +--show-current-patch:: + Show the current patch in an interactive rebase or when rebase + is stopped because of conflicts. + -m:: --merge:: Use merging strategies to rebase. When the recursive (default) merge diff --git a/builtin/am.c b/builtin/am.c index 07abfb8f83..37219fceb0 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -2126,6 +2126,17 @@ static int show_patch(struct am_state *state) const char *patch_path; int len; + if (!is_null_oid(&state->orig_commit)) { + const char *av[4] = { "show", NULL, "--", NULL }; + char *new_oid_str; + int ret; + + av[1] = new_oid_str = xstrdup(oid_to_hex(&state->orig_commit)); + ret = run_command_v_opt(av, RUN_GIT_CMD); + free(new_oid_str); + return ret; + } + patch_path = am_path(state, msgnum(state)); len = strbuf_read_file(&sb, patch_path, 0); if (len < 0) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 56ca445fa8..2bd30d68cf 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1992,11 +1992,11 @@ _git_rebase () { __git_find_repo_path if [ -f "$__git_repo_path"/rebase-merge/interactive ]; then - __gitcomp "--continue --skip --abort --quit --edit-todo" + __gitcomp "--continue --skip --abort --quit --edit-todo --show-current-patch" return elif [ -d "$__git_repo_path"/rebase-apply ] || \ [ -d "$__git_repo_path"/rebase-merge ]; then - __gitcomp "--continue --skip --abort --quit" + __gitcomp "--continue --skip --abort --quit --show-current-patch" return fi __git_complete_strategy && return diff --git a/git-rebase--am.sh b/git-rebase--am.sh index 14c50782e0..c931891cbc 100644 --- a/git-rebase--am.sh +++ b/git-rebase--am.sh @@ -27,6 +27,9 @@ skip) move_to_original_branch return ;; +show-current-patch) + exec git am --show-current-patch + ;; esac if test -z "$rebase_root" diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index d47bd29593..0c0f8abbf9 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -840,6 +840,9 @@ To continue rebase after editing, run: exit ;; +show-current-patch) + exec git show "$(cat "$state_dir/stopped-sha")" -- + ;; esac comment_for_reflog start diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh index 06a4723d4d..0a96dfae37 100644 --- a/git-rebase--merge.sh +++ b/git-rebase--merge.sh @@ -137,6 +137,9 @@ skip) finish_rb_merge return ;; +show-current-patch) + exec git show "$(cat "$state_dir/current")" -- + ;; esac mkdir -p "$state_dir" diff --git a/git-rebase.sh b/git-rebase.sh index fd72a35c65..41c915d18c 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -45,6 +45,7 @@ abort! abort and check out the original branch skip! skip current patch and continue edit-todo! edit the todo list during an interactive rebase quit! abort but keep HEAD where it is +show-current-patch! show the patch file being applied or merged " . git-sh-setup set_reflog_action rebase @@ -245,7 +246,7 @@ do --verify) ok_to_skip_pre_rebase= ;; - --continue|--skip|--abort|--quit|--edit-todo) + --continue|--skip|--abort|--quit|--edit-todo|--show-current-patch) test $total_argc -eq 2 || usage action=${1##--} ;; @@ -412,6 +413,10 @@ quit) edit-todo) run_specific_rebase ;; +show-current-patch) + run_specific_rebase + die "BUG: run_specific_rebase is not supposed to return here" + ;; esac # Make sure no rebase is in progress diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 8ac58d5ea5..09943d6a9b 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -277,4 +277,37 @@ EOF test_cmp From_.msg out ' +test_expect_success 'rebase--am.sh and --show-current-patch' ' + test_create_repo conflict-apply && + ( + cd conflict-apply && + test_commit init && + echo one >>init.t && + git commit -a -m one && + echo two >>init.t && + git commit -a -m two && + git tag two && + test_must_fail git rebase --onto init HEAD^ && + GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && + grep "show.*$(git rev-parse two)" stderr + ) +' + +test_expect_success 'rebase--merge.sh and --show-current-patch' ' + test_create_repo conflict-merge && + ( + cd conflict-merge && + test_commit init && + echo one >>init.t && + git commit -a -m one && + echo two >>init.t && + git commit -a -m two && + git tag two && + test_must_fail git rebase --merge --onto init HEAD^ && + git rebase --show-current-patch >actual.patch && + GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && + grep "show.*$(git rev-parse two)" stderr + ) +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 481a350090..3af6f149a9 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -225,6 +225,11 @@ test_expect_success 'stop on conflicting pick' ' test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo) ' +test_expect_success 'show conflicted patch' ' + GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && + grep "show.*$(cat "$state_dir/stopped-sha")" stderr +' + test_expect_success 'abort' ' git rebase --abort && test $(git rev-parse new-branch1) = $(git rev-parse HEAD) && From fbd7a23237094c3fb2e249bbcacbbf1e858e79c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Sun, 11 Feb 2018 16:43:28 +0700 Subject: [PATCH 3/3] rebase: introduce and use pseudo-ref REBASE_HEAD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new command `git rebase --show-current-patch` is useful for seeing the commit related to the current rebase state. Some however may find the "git show" command behind it too limiting. You may want to increase context lines, do a diff that ignores whitespaces... For these advanced use cases, the user can execute any command they want with the new pseudo ref REBASE_HEAD. This also helps show where the stopped commit is from, which is hard to see from the previous patch which implements --show-current-patch. Helped-by: Tim Landscheidt Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 3 ++- builtin/am.c | 4 ++++ contrib/completion/git-completion.bash | 2 +- git-rebase--interactive.sh | 5 ++++- git-rebase--merge.sh | 4 +++- git-rebase.sh | 1 + sequencer.c | 4 ++++ t/t3400-rebase.sh | 3 ++- t/t3404-rebase-interactive.sh | 5 ++++- 9 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 7ef9577472..0b29e48221 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -252,7 +252,8 @@ leave out at most one of A and B, in which case it defaults to HEAD. --show-current-patch:: Show the current patch in an interactive rebase or when rebase - is stopped because of conflicts. + is stopped because of conflicts. This is the equivalent of + `git show REBASE_HEAD`. -m:: --merge:: diff --git a/builtin/am.c b/builtin/am.c index 37219fceb0..21aedec41f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1011,6 +1011,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, if (mkdir(state->dir, 0777) < 0 && errno != EEXIST) die_errno(_("failed to create directory '%s'"), state->dir); + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); if (split_mail(state, patch_format, paths, keep_cr) < 0) { am_destroy(state); @@ -1110,6 +1111,7 @@ static void am_next(struct am_state *state) oidclr(&state->orig_commit); unlink(am_path(state, "original-commit")); + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); if (!get_oid("HEAD", &head)) write_state_text(state, "abort-safety", oid_to_hex(&head)); @@ -1441,6 +1443,8 @@ static int parse_mail_rebase(struct am_state *state, const char *mail) oidcpy(&state->orig_commit, &commit_oid); write_state_text(state, "original-commit", oid_to_hex(&commit_oid)); + update_ref("am", "REBASE_HEAD", &commit_oid, + NULL, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); return 0; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2bd30d68cf..8777805c9f 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -439,7 +439,7 @@ __git_refs () track="" ;; *) - for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do + for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD; do case "$i" in $match*) if [ -e "$dir/$i" ]; then diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 0c0f8abbf9..a613156bcb 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -199,12 +199,14 @@ make_patch () { die_with_patch () { echo "$1" > "$state_dir"/stopped-sha + git update-ref REBASE_HEAD "$1" make_patch "$1" die "$2" } exit_with_patch () { echo "$1" > "$state_dir"/stopped-sha + git update-ref REBASE_HEAD "$1" make_patch $1 git rev-parse --verify HEAD > "$amend" gpg_sign_opt_quoted=${gpg_sign_opt:+$(git rev-parse --sq-quote "$gpg_sign_opt")} @@ -841,7 +843,7 @@ To continue rebase after editing, run: exit ;; show-current-patch) - exec git show "$(cat "$state_dir/stopped-sha")" -- + exec git show REBASE_HEAD -- ;; esac @@ -858,6 +860,7 @@ fi orig_head=$(git rev-parse --verify HEAD) || die "$(gettext "No HEAD?")" mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")" +rm -f "$(git rev-parse --git-path REBASE_HEAD)" : > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")" write_basic_state diff --git a/git-rebase--merge.sh b/git-rebase--merge.sh index 0a96dfae37..957688f236 100644 --- a/git-rebase--merge.sh +++ b/git-rebase--merge.sh @@ -57,6 +57,7 @@ call_merge () { echo "$msgnum" >"$state_dir/msgnum" cmt="$(cat "$state_dir/cmt.$msgnum")" echo "$cmt" > "$state_dir/current" + git update-ref REBASE_HEAD "$cmt" hd=$(git rev-parse --verify HEAD) cmt_name=$(git symbolic-ref HEAD 2> /dev/null || echo HEAD) eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' @@ -138,13 +139,14 @@ skip) return ;; show-current-patch) - exec git show "$(cat "$state_dir/current")" -- + exec git show REBASE_HEAD -- ;; esac mkdir -p "$state_dir" echo "$onto_name" > "$state_dir/onto_name" write_basic_state +rm -f "$(git rev-parse --git-path REBASE_HEAD)" msgnum=0 for cmt in $(git rev-list --reverse --no-merges "$revisions") diff --git a/git-rebase.sh b/git-rebase.sh index 41c915d18c..a13a581fe6 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -182,6 +182,7 @@ You can run "git stash pop" or "git stash drop" at any time. } finish_rebase () { + rm -f "$(git rev-parse --git-path REBASE_HEAD)" apply_autostash && { git gc --auto || true; } && rm -rf "$state_dir" diff --git a/sequencer.c b/sequencer.c index 4d3f60594c..f692221999 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1792,6 +1792,9 @@ static int make_patch(struct commit *commit, struct replay_opts *opts) p = short_commit_name(commit); if (write_message(p, strlen(p), rebase_path_stopped_sha(), 1) < 0) return -1; + if (update_ref("rebase", "REBASE_HEAD", &commit->object.oid, + NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) + res |= error(_("could not update %s"), "REBASE_HEAD"); strbuf_addf(&buf, "%s/patch", get_dir(opts)); memset(&log_tree_opt, 0, sizeof(log_tree_opt)); @@ -2043,6 +2046,7 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) unlink(rebase_path_author_script()); unlink(rebase_path_stopped_sha()); unlink(rebase_path_amend()); + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); } if (item->command <= TODO_SQUASH) { if (is_rebase_i(opts)) diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 09943d6a9b..72d9564747 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -306,7 +306,8 @@ test_expect_success 'rebase--merge.sh and --show-current-patch' ' test_must_fail git rebase --merge --onto init HEAD^ && git rebase --show-current-patch >actual.patch && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && - grep "show.*$(git rev-parse two)" stderr + grep "show.*REBASE_HEAD" stderr && + test "$(git rev-parse REBASE_HEAD)" = "$(git rev-parse two)" ) ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 3af6f149a9..23a54a4c49 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -227,7 +227,10 @@ test_expect_success 'stop on conflicting pick' ' test_expect_success 'show conflicted patch' ' GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && - grep "show.*$(cat "$state_dir/stopped-sha")" stderr + grep "show.*REBASE_HEAD" stderr && + # the original stopped-sha1 is abbreviated + stopped_sha1="$(git rev-parse $(cat ".git/rebase-merge/stopped-sha"))" && + test "$(git rev-parse REBASE_HEAD)" = "$stopped_sha1" ' test_expect_success 'abort' '