From bf3acb9c1ff9c64406d90f7b06e0b44a7d57347d Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:31 +0300 Subject: [PATCH 01/93] sha1-name.c: add `get_oidf()` which acts like `get_oid()` Compared to `get_oid()`, `get_oidf()` has as parameters a pointer to `object_id`, a printf format string and additional arguments. This will help simplify the code in subsequent commits. Original-idea-by: Johannes Schindelin Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- cache.h | 1 + sha1-name.c | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/cache.h b/cache.h index 4d014541ab7bc7..0276fae1c20079 100644 --- a/cache.h +++ b/cache.h @@ -1319,6 +1319,7 @@ struct object_context { GET_OID_BLOB) extern int get_oid(const char *str, struct object_id *oid); +extern int get_oidf(struct object_id *oid, const char *fmt, ...); extern int get_oid_commit(const char *str, struct object_id *oid); extern int get_oid_committish(const char *str, struct object_id *oid); extern int get_oid_tree(const char *str, struct object_id *oid); diff --git a/sha1-name.c b/sha1-name.c index c9cc1318b7394e..261b960bbd2b24 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -1471,6 +1471,25 @@ int get_oid(const char *name, struct object_id *oid) return get_oid_with_context(name, 0, oid, &unused); } +/* + * This returns a non-zero value if the string (built using printf + * format and the given arguments) is not a valid object. + */ +int get_oidf(struct object_id *oid, const char *fmt, ...) +{ + va_list ap; + int ret; + struct strbuf sb = STRBUF_INIT; + + va_start(ap, fmt); + strbuf_vaddf(&sb, fmt, ap); + va_end(ap); + + ret = get_oid(sb.buf, oid); + strbuf_release(&sb); + + return ret; +} /* * Many callers know that the user meant to name a commit-ish by From f8d47b5f153847b09b240a119edb4bd21bce3953 Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Fri, 31 Aug 2018 00:40:32 +0300 Subject: [PATCH 02/93] stash: improve option parsing test coverage In preparation for converting the stash command incrementally to a builtin command, this patch improves test coverage of the option parsing. Both for having too many parameters, or too few. Signed-off-by: Joel Teichroeb Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- t/t3903-stash.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 6450bc669860f0..3eaca6505387d8 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -444,6 +444,36 @@ test_expect_failure 'stash file to directory' ' test foo = "$(cat file/file)" ' +test_expect_success 'giving too many ref arguments does not modify files' ' + git stash clear && + test_when_finished "git reset --hard HEAD" && + echo foo >file2 && + git stash && + echo bar >file2 && + git stash && + test-tool chmtime =123456789 file2 && + for type in apply pop "branch stash-branch" + do + test_must_fail git stash $type stash@{0} stash@{1} 2>err && + test_i18ngrep "Too many revisions" err && + test 123456789 = $(test-tool chmtime -g file2) || return 1 + done +' + +test_expect_success 'drop: too many arguments errors out (does nothing)' ' + git stash list >expect && + test_must_fail git stash drop stash@{0} stash@{1} 2>err && + test_i18ngrep "Too many revisions" err && + git stash list >actual && + test_cmp expect actual +' + +test_expect_success 'show: too many arguments errors out (does nothing)' ' + test_must_fail git stash show stash@{0} stash@{1} 2>err 1>out && + test_i18ngrep "Too many revisions" err && + test_must_be_empty out +' + test_expect_success 'stash create - no changes' ' git stash clear && test_when_finished "git reset --hard HEAD" && @@ -479,6 +509,11 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' ' test $(git ls-files --modified | wc -l) -eq 1 ' +test_expect_success 'stash branch complains with no arguments' ' + test_must_fail git stash branch 2>err && + test_i18ngrep "No branch name specified" err +' + test_expect_success 'stash show format defaults to --stat' ' git stash clear && test_when_finished "git reset --hard HEAD" && From 9f260c2522a2e7f091f492bd70533c8df9ee7503 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:33 +0300 Subject: [PATCH 03/93] stash: update test cases conform to coding guidelines Removed whitespaces after redirection operators. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- t/t3903-stash.sh | 120 ++++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 59 deletions(-) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 3eaca6505387d8..8fc182c77d6c4f 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -8,22 +8,22 @@ test_description='Test git stash' . ./test-lib.sh test_expect_success 'stash some dirty working directory' ' - echo 1 > file && + echo 1 >file && git add file && echo unrelated >other-file && git add other-file && test_tick && git commit -m initial && - echo 2 > file && + echo 2 >file && git add file && - echo 3 > file && + echo 3 >file && test_tick && git stash && git diff-files --quiet && git diff-index --cached --quiet HEAD ' -cat > expect << EOF +cat >expect < output && + git diff stash^2..stash >output && test_cmp output expect ' @@ -74,7 +74,7 @@ test_expect_success 'apply stashed changes' ' test_expect_success 'apply stashed changes (including index)' ' git reset --hard HEAD^ && - echo 6 > other-file && + echo 6 >other-file && git add other-file && test_tick && git commit -m other-file && @@ -99,12 +99,12 @@ test_expect_success 'stash drop complains of extra options' ' test_expect_success 'drop top stash' ' git reset --hard && - git stash list > stashlist1 && - echo 7 > file && + git stash list >expected && + echo 7 >file && git stash && git stash drop && - git stash list > stashlist2 && - test_cmp stashlist1 stashlist2 && + git stash list >actual && + test_cmp expected actual && git stash apply && test 3 = $(cat file) && test 1 = $(git show :file) && @@ -113,9 +113,9 @@ test_expect_success 'drop top stash' ' test_expect_success 'drop middle stash' ' git reset --hard && - echo 8 > file && + echo 8 >file && git stash && - echo 9 > file && + echo 9 >file && git stash && git stash drop stash@{1} && test 2 = $(git stash list | wc -l) && @@ -160,7 +160,7 @@ test_expect_success 'stash pop' ' test 0 = $(git stash list | wc -l) ' -cat > expect << EOF +cat >expect < expect1 << EOF +cat >expect1 < expect2 << EOF +cat >expect2 < file && + echo foo >file && git commit file -m first && - echo bar > file && - echo bar2 > file2 && + echo bar >file && + echo bar2 >file2 && git add file2 && git stash && - echo baz > file && + echo baz >file && git commit file -m second && git stash branch stashbranch && test refs/heads/stashbranch = $(git symbolic-ref HEAD) && test $(git rev-parse HEAD) = $(git rev-parse master^) && - git diff --cached > output && + git diff --cached >output && test_cmp output expect && - git diff > output && + git diff >output && test_cmp output expect1 && git add file && git commit -m alternate\ second && - git diff master..stashbranch > output && + git diff master..stashbranch >output && test_cmp output expect2 && test 0 = $(git stash list | wc -l) ' test_expect_success 'apply -q is quiet' ' - echo foo > file && + echo foo >file && git stash && - git stash apply -q > output.out 2>&1 && + git stash apply -q >output.out 2>&1 && test_must_be_empty output.out ' test_expect_success 'save -q is quiet' ' - git stash save --quiet > output.out 2>&1 && + git stash save --quiet >output.out 2>&1 && test_must_be_empty output.out ' test_expect_success 'pop -q is quiet' ' - git stash pop -q > output.out 2>&1 && + git stash pop -q >output.out 2>&1 && test_must_be_empty output.out ' test_expect_success 'pop -q --index works and is quiet' ' - echo foo > file && + echo foo >file && git add file && git stash save --quiet && - git stash pop -q --index > output.out 2>&1 && + git stash pop -q --index >output.out 2>&1 && test foo = "$(git show :file)" && test_must_be_empty output.out ' test_expect_success 'drop -q is quiet' ' git stash && - git stash drop -q > output.out 2>&1 && + git stash drop -q >output.out 2>&1 && test_must_be_empty output.out ' test_expect_success 'stash -k' ' - echo bar3 > file && - echo bar4 > file2 && + echo bar3 >file && + echo bar4 >file2 && git add file2 && git stash -k && test bar,bar4 = $(cat file),$(cat file2) ' test_expect_success 'stash --no-keep-index' ' - echo bar33 > file && - echo bar44 > file2 && + echo bar33 >file && + echo bar44 >file2 && git add file2 && git stash --no-keep-index && test bar,bar2 = $(cat file),$(cat file2) ' test_expect_success 'stash --invalid-option' ' - echo bar5 > file && - echo bar6 > file2 && + echo bar5 >file && + echo bar6 >file2 && git add file2 && test_must_fail git stash --invalid-option && test_must_fail git stash save --invalid-option && @@ -486,11 +486,12 @@ test_expect_success 'stash branch - no stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && git reset --hard && - echo foo >> file && + echo foo >>file && STASH_ID=$(git stash create) && git reset --hard && git stash branch stash-branch ${STASH_ID} && - test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" && + test_when_finished "git reset --hard HEAD && git checkout master && + git branch -D stash-branch" && test $(git ls-files --modified | wc -l) -eq 1 ' @@ -498,14 +499,15 @@ test_expect_success 'stash branch - stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && git reset --hard && - echo foo >> file && + echo foo >>file && git stash && test_when_finished "git stash drop" && - echo bar >> file && + echo bar >>file && STASH_ID=$(git stash create) && git reset --hard && git stash branch stash-branch ${STASH_ID} && - test_when_finished "git reset --hard HEAD && git checkout master && git branch -D stash-branch" && + test_when_finished "git reset --hard HEAD && git checkout master && + git branch -D stash-branch" && test $(git ls-files --modified | wc -l) -eq 1 ' @@ -518,10 +520,10 @@ test_expect_success 'stash show format defaults to --stat' ' git stash clear && test_when_finished "git reset --hard HEAD" && git reset --hard && - echo foo >> file && + echo foo >>file && git stash && test_when_finished "git stash drop" && - echo bar >> file && + echo bar >>file && STASH_ID=$(git stash create) && git reset --hard && cat >expected <<-EOF && @@ -536,10 +538,10 @@ test_expect_success 'stash show - stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && git reset --hard && - echo foo >> file && + echo foo >>file && git stash && test_when_finished "git stash drop" && - echo bar >> file && + echo bar >>file && STASH_ID=$(git stash create) && git reset --hard && echo "1 0 file" >expected && @@ -551,10 +553,10 @@ test_expect_success 'stash show -p - stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && git reset --hard && - echo foo >> file && + echo foo >>file && git stash && test_when_finished "git stash drop" && - echo bar >> file && + echo bar >>file && STASH_ID=$(git stash create) && git reset --hard && cat >expected <<-EOF && @@ -574,7 +576,7 @@ test_expect_success 'stash show - no stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && git reset --hard && - echo foo >> file && + echo foo >>file && STASH_ID=$(git stash create) && git reset --hard && echo "1 0 file" >expected && @@ -586,7 +588,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' ' git stash clear && test_when_finished "git reset --hard HEAD" && git reset --hard && - echo foo >> file && + echo foo >>file && STASH_ID=$(git stash create) && git reset --hard && cat >expected <<-EOF && @@ -606,9 +608,9 @@ test_expect_success 'stash drop - fail early if specified stash is not a stash r git stash clear && test_when_finished "git reset --hard HEAD && git stash clear" && git reset --hard && - echo foo > file && + echo foo >file && git stash && - echo bar > file && + echo bar >file && git stash && test_must_fail git stash drop $(git rev-parse stash@{0}) && git stash pop && @@ -620,9 +622,9 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re git stash clear && test_when_finished "git reset --hard HEAD && git stash clear" && git reset --hard && - echo foo > file && + echo foo >file && git stash && - echo bar > file && + echo bar >file && git stash && test_must_fail git stash pop $(git rev-parse stash@{0}) && git stash pop && @@ -632,8 +634,8 @@ test_expect_success 'stash pop - fail early if specified stash is not a stash re test_expect_success 'ref with non-existent reflog' ' git stash clear && - echo bar5 > file && - echo bar6 > file2 && + echo bar5 >file && + echo bar6 >file2 && git add file2 && git stash && test_must_fail git rev-parse --quiet --verify does-not-exist && @@ -653,8 +655,8 @@ test_expect_success 'ref with non-existent reflog' ' test_expect_success 'invalid ref of the form stash@{n}, n >= N' ' git stash clear && test_must_fail git stash drop stash@{0} && - echo bar5 > file && - echo bar6 > file2 && + echo bar5 >file && + echo bar6 >file2 && git add file2 && git stash && test_must_fail git stash drop stash@{1} && @@ -724,7 +726,7 @@ test_expect_success 'stash apply shows status same as git status (relative to cu test_i18ncmp expect actual ' -cat > expect << EOF +cat >expect < HEAD && + echo file-not-a-ref >HEAD && git add HEAD && test_tick && git stash && git diff-files --quiet && git diff-index --cached --quiet HEAD && test "$(git rev-parse stash^)" = "$(git rev-parse HEAD)" && - git diff stash^..stash > output && + git diff stash^..stash >output && test_cmp output expect ' From 0e7f275cc45684cf51a3358baad29d03b908a07e Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:34 +0300 Subject: [PATCH 04/93] stash: rename test cases to be more descriptive Rename some test cases' labels to be more descriptive and under 80 characters per line. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- t/t3903-stash.sh | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 8fc182c77d6c4f..4fe3717675b742 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -604,7 +604,7 @@ test_expect_success 'stash show -p - no stashes on stack, stash-like argument' ' test_cmp expected actual ' -test_expect_success 'stash drop - fail early if specified stash is not a stash reference' ' +test_expect_success 'drop: fail early if specified stash is not a stash ref' ' git stash clear && test_when_finished "git reset --hard HEAD && git stash clear" && git reset --hard && @@ -618,7 +618,7 @@ test_expect_success 'stash drop - fail early if specified stash is not a stash r git reset --hard HEAD ' -test_expect_success 'stash pop - fail early if specified stash is not a stash reference' ' +test_expect_success 'pop: fail early if specified stash is not a stash ref' ' git stash clear && test_when_finished "git reset --hard HEAD && git stash clear" && git reset --hard && @@ -682,7 +682,7 @@ test_expect_success 'invalid ref of the form "n", n >= N' ' git stash drop ' -test_expect_success 'stash branch should not drop the stash if the branch exists' ' +test_expect_success 'branch: do not drop the stash if the branch exists' ' git stash clear && echo foo >file && git add file && @@ -693,7 +693,7 @@ test_expect_success 'stash branch should not drop the stash if the branch exists git rev-parse stash@{0} -- ' -test_expect_success 'stash branch should not drop the stash if the apply fails' ' +test_expect_success 'branch: should not drop the stash if the apply fails' ' git stash clear && git reset HEAD~1 --hard && echo foo >file && @@ -707,7 +707,7 @@ test_expect_success 'stash branch should not drop the stash if the apply fails' git rev-parse stash@{0} -- ' -test_expect_success 'stash apply shows status same as git status (relative to current directory)' ' +test_expect_success 'apply: show same status as git status (relative to ./)' ' git stash clear && echo 1 >subdir/subfile1 && echo 2 >subdir/subfile2 && @@ -1048,7 +1048,7 @@ test_expect_success 'stash push -p with pathspec shows no changes only once' ' test_i18ncmp expect actual ' -test_expect_success 'stash push with pathspec shows no changes when there are none' ' +test_expect_success 'push : show no changes when there are none' ' >foo && git add foo && git commit -m "tmp" && @@ -1058,7 +1058,7 @@ test_expect_success 'stash push with pathspec shows no changes when there are no test_i18ncmp expect actual ' -test_expect_success 'stash push with pathspec not in the repository errors out' ' +test_expect_success 'push: not in the repository errors out' ' >untracked && test_must_fail git stash push untracked && test_path_is_file untracked From e4db448178dcda8b8221c496b70177470897abbf Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:35 +0300 Subject: [PATCH 05/93] stash: add tests for `git stash show` config This commit introduces tests for `git stash show` config. It tests all the cases where `stash.showStat` and `stash.showPatch` are unset or set to true / false. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- t/t3907-stash-show-config.sh | 81 ++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100755 t/t3907-stash-show-config.sh diff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh new file mode 100755 index 00000000000000..8fe369c1a11d44 --- /dev/null +++ b/t/t3907-stash-show-config.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='Test git stash show configuration.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit file +' + +# takes three parameters: +# 1. the stash.showStat value (or "") +# 2. the stash.showPatch value (or "") +# 3. the diff options of the expected output (or nothing for no output) +test_stat_and_patch () { + if test "" = "$1" + then + test_might_fail git config --unset stash.showStat + else + test_config stash.showStat "$1" + fi && + + if test "" = "$2" + then + test_might_fail git config --unset stash.showPatch + else + test_config stash.showPatch "$2" + fi && + + shift && + shift && + echo 2 >file.t && + git diff "$@" >expect && + git stash && + git stash show >actual && + + if test -z "$1" + then + test_must_be_empty actual + else + test_cmp expect actual + fi +} + +test_expect_success 'showStat unset showPatch unset' ' + test_stat_and_patch "" "" --stat +' + +test_expect_success 'showStat unset showPatch false' ' + test_stat_and_patch "" false --stat +' + +test_expect_success 'showStat unset showPatch true' ' + test_stat_and_patch "" true --stat -p +' + +test_expect_success 'showStat false showPatch unset' ' + test_stat_and_patch false "" +' + +test_expect_success 'showStat false showPatch false' ' + test_stat_and_patch false false +' + +test_expect_success 'showStat false showPatch true' ' + test_stat_and_patch false true -p +' + +test_expect_success 'showStat true showPatch unset' ' + test_stat_and_patch true "" --stat +' + +test_expect_success 'showStat true showPatch false' ' + test_stat_and_patch true false --stat +' + +test_expect_success 'showStat true showPatch true' ' + test_stat_and_patch true true --stat -p +' + +test_done From ec238ee57c592f51f6f91e9c33253447cfcfd259 Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Fri, 31 Aug 2018 00:40:36 +0300 Subject: [PATCH 06/93] stash: convert apply to builtin Add a builtin helper for performing stash commands. Converting all at once proved hard to review, so starting with just apply lets conversion get started without the other commands being finished. The helper is being implemented as a drop in replacement for stash so that when it is complete it can simply be renamed and the shell script deleted. Delete the contents of the apply_stash shell function and replace it with a call to stash--helper apply until pop is also converted. Signed-off-by: Joel Teichroeb Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/stash--helper.c | 453 ++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 78 +------ git.c | 1 + 6 files changed, 464 insertions(+), 71 deletions(-) create mode 100644 builtin/stash--helper.c diff --git a/.gitignore b/.gitignore index ffceea7d59fd21..b59661cb884688 100644 --- a/.gitignore +++ b/.gitignore @@ -157,6 +157,7 @@ /git-show-ref /git-stage /git-stash +/git-stash--helper /git-status /git-stripspace /git-submodule diff --git a/Makefile b/Makefile index 5a969f5830a410..482196f4718f45 100644 --- a/Makefile +++ b/Makefile @@ -1093,6 +1093,7 @@ BUILTIN_OBJS += builtin/shortlog.o BUILTIN_OBJS += builtin/show-branch.o BUILTIN_OBJS += builtin/show-index.o BUILTIN_OBJS += builtin/show-ref.o +BUILTIN_OBJS += builtin/stash--helper.o BUILTIN_OBJS += builtin/stripspace.o BUILTIN_OBJS += builtin/submodule--helper.o BUILTIN_OBJS += builtin/symbolic-ref.o diff --git a/builtin.h b/builtin.h index 99206df4bd43fc..317bc338f729f2 100644 --- a/builtin.h +++ b/builtin.h @@ -223,6 +223,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_show_index(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); +extern int cmd_stash__helper(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c new file mode 100644 index 00000000000000..684d905c0f9a27 --- /dev/null +++ b/builtin/stash--helper.c @@ -0,0 +1,453 @@ +#include "builtin.h" +#include "config.h" +#include "parse-options.h" +#include "refs.h" +#include "lockfile.h" +#include "cache-tree.h" +#include "unpack-trees.h" +#include "merge-recursive.h" +#include "argv-array.h" +#include "run-command.h" +#include "dir.h" +#include "rerere.h" + +static const char * const git_stash_helper_usage[] = { + N_("git stash--helper apply [--index] [-q|--quiet] []"), + NULL +}; + +static const char * const git_stash_helper_apply_usage[] = { + N_("git stash--helper apply [--index] [-q|--quiet] []"), + NULL +}; + +static const char *ref_stash = "refs/stash"; +static int quiet; +static struct strbuf stash_index_path = STRBUF_INIT; + +/* + * w_commit is set to the commit containing the working tree + * b_commit is set to the base commit + * i_commit is set to the commit containing the index tree + * u_commit is set to the commit containing the untracked files tree + * w_tree is set to the working tree + * b_tree is set to the base tree + * i_tree is set to the index tree + * u_tree is set to the untracked files tree + */ + +struct stash_info { + struct object_id w_commit; + struct object_id b_commit; + struct object_id i_commit; + struct object_id u_commit; + struct object_id w_tree; + struct object_id b_tree; + struct object_id i_tree; + struct object_id u_tree; + struct strbuf revision; + int is_stash_ref; + int has_u; +}; + +static void free_stash_info(struct stash_info *info) +{ + strbuf_release(&info->revision); +} + +static void assert_stash_like(struct stash_info *info, const char *revision) +{ + if (get_oidf(&info->b_commit, "%s^1", revision) || + get_oidf(&info->w_tree, "%s:", revision) || + get_oidf(&info->b_tree, "%s^1:", revision) || + get_oidf(&info->i_tree, "%s^2:", revision)) { + free_stash_info(info); + error(_("'%s' is not a stash-like commit"), revision); + exit(128); + } +} + +static int get_stash_info(struct stash_info *info, int argc, const char **argv) +{ + struct strbuf symbolic = STRBUF_INIT; + int ret; + const char *revision; + const char *commit = NULL; + char *end_of_rev; + char *expanded_ref; + struct object_id dummy; + + if (argc > 1) { + int i; + struct strbuf refs_msg = STRBUF_INIT; + for (i = 0; i < argc; ++i) + strbuf_addf(&refs_msg, " '%s'", argv[i]); + + fprintf_ln(stderr, _("Too many revisions specified:%s"), + refs_msg.buf); + strbuf_release(&refs_msg); + + return -1; + } + + if (argc == 1) + commit = argv[0]; + + strbuf_init(&info->revision, 0); + if (!commit) { + if (!ref_exists(ref_stash)) { + free_stash_info(info); + fprintf_ln(stderr, _("No stash entries found.")); + return -1; + } + + strbuf_addf(&info->revision, "%s@{0}", ref_stash); + } else if (strspn(commit, "0123456789") == strlen(commit)) { + strbuf_addf(&info->revision, "%s@{%s}", ref_stash, commit); + } else { + strbuf_addstr(&info->revision, commit); + } + + revision = info->revision.buf; + + if (get_oid(revision, &info->w_commit)) { + error(_("%s is not a valid reference"), revision); + free_stash_info(info); + return -1; + } + + assert_stash_like(info, revision); + + info->has_u = !get_oidf(&info->u_tree, "%s^3:", revision); + + end_of_rev = strchrnul(revision, '@'); + strbuf_add(&symbolic, revision, end_of_rev - revision); + + ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref); + strbuf_release(&symbolic); + switch (ret) { + case 0: /* Not found, but valid ref */ + info->is_stash_ref = 0; + break; + case 1: + info->is_stash_ref = !strcmp(expanded_ref, ref_stash); + break; + default: /* Invalid or ambiguous */ + free_stash_info(info); + } + + free(expanded_ref); + return !(ret == 0 || ret == 1); +} + +static int reset_tree(struct object_id *i_tree, int update, int reset) +{ + struct unpack_trees_options opts; + int nr_trees = 1; + struct tree_desc t[MAX_UNPACK_TREES]; + struct tree *tree; + struct lock_file lock_file = LOCK_INIT; + + read_cache_preload(NULL); + if (refresh_cache(REFRESH_QUIET)) + return -1; + + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + + memset(&opts, 0, sizeof(opts)); + + tree = parse_tree_indirect(i_tree); + if (parse_tree(tree)) + return -1; + + init_tree_desc(t, tree->buffer, tree->size); + + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.merge = 1; + opts.reset = reset; + opts.update = update; + opts.fn = oneway_merge; + + if (unpack_trees(nr_trees, t, &opts)) + return -1; + + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + return error(_("unable to write new index file")); + + return 0; +} + +static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) +{ + struct child_process cp = CHILD_PROCESS_INIT; + const char *w_commit_hex = oid_to_hex(w_commit); + + /* + * Diff-tree would not be very hard to replace with a native function, + * however it should be done together with apply_cached. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL); + argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); + + return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); +} + +static int apply_cached(struct strbuf *out) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Apply currently only reads either from stdin or a file, thus + * apply_all_patches would have to be updated to optionally take a + * buffer. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "apply", "--cached", NULL); + return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); +} + +static int reset_head(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Reset is overall quite simple, however there is no current public + * API for resetting. + */ + cp.git_cmd = 1; + argv_array_push(&cp.args, "reset"); + + return run_command(&cp); +} + +static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) +{ + struct child_process cp = CHILD_PROCESS_INIT; + const char *c_tree_hex = oid_to_hex(c_tree); + + /* + * diff-index is very similar to diff-tree above, and should be + * converted together with update_index. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only", + "--diff-filter=A", NULL); + argv_array_push(&cp.args, c_tree_hex); + return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); +} + +static int update_index(struct strbuf *out) +{ + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Update-index is very complicated and may need to have a public + * function exposed in order to remove this forking. + */ + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); + return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); +} + +static int restore_untracked(struct object_id *u_tree) +{ + struct child_process cp = CHILD_PROCESS_INIT; + int res; + + /* + * We need to run restore files from a given index, but without + * affecting the current index, so we use GIT_INDEX_FILE with + * run_command to fork processes that will not interfere. + */ + cp.git_cmd = 1; + argv_array_push(&cp.args, "read-tree"); + argv_array_push(&cp.args, oid_to_hex(u_tree)); + argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (run_command(&cp)) { + remove_path(stash_index_path.buf); + return -1; + } + + child_process_init(&cp); + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "checkout-index", "--all", NULL); + argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + + res = run_command(&cp); + remove_path(stash_index_path.buf); + return res; +} + +static int do_apply_stash(const char *prefix, struct stash_info *info, + int index) +{ + struct merge_options o; + struct object_id c_tree; + struct object_id index_tree; + const struct object_id *bases[1]; + struct commit *result; + int ret; + int has_index = index; + + read_cache_preload(NULL); + if (refresh_cache(REFRESH_QUIET)) + return -1; + + if (write_cache_as_tree(&c_tree, 0, NULL) || reset_tree(&c_tree, 0, 0)) + return error(_("Cannot apply a stash in the middle of a merge")); + + if (index) { + if (!oidcmp(&info->b_tree, &info->i_tree) || !oidcmp(&c_tree, + &info->i_tree)) { + has_index = 0; + } else { + struct strbuf out = STRBUF_INIT; + + if (diff_tree_binary(&out, &info->w_commit)) { + strbuf_release(&out); + return error(_("Could not generate diff %s^!."), + oid_to_hex(&info->w_commit)); + } + + ret = apply_cached(&out); + strbuf_release(&out); + if (ret) + return error(_("Conflicts in index. Try without --index.")); + + discard_cache(); + read_cache(); + if (write_cache_as_tree(&index_tree, 0, NULL)) + return error(_("Could not save index tree")); + + reset_head(); + } + } + + if (info->has_u && restore_untracked(&info->u_tree)) + return error(_("Could not restore untracked files from stash")); + + init_merge_options(&o); + + o.branch1 = "Updated upstream"; + o.branch2 = "Stashed changes"; + + if (!oidcmp(&info->b_tree, &c_tree)) + o.branch1 = "Version stash was based on"; + + if (quiet) + o.verbosity = 0; + + if (o.verbosity >= 3) + printf_ln(_("Merging %s with %s"), o.branch1, o.branch2); + + bases[0] = &info->b_tree; + + ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, + &result); + if (ret) { + rerere(0); + + if (index) + fprintf_ln(stderr, _("Index was not unstashed.")); + + return ret; + } + + if (has_index) { + if (reset_tree(&index_tree, 0, 0)) + return -1; + } else { + struct strbuf out = STRBUF_INIT; + + if (get_newly_staged(&out, &c_tree)) { + strbuf_release(&out); + return -1; + } + + if (reset_tree(&c_tree, 0, 1)) { + strbuf_release(&out); + return -1; + } + + ret = update_index(&out); + strbuf_release(&out); + if (ret) + return -1; + + discard_cache(); + } + + if (quiet) { + if (refresh_cache(REFRESH_QUIET)) + warning("could not refresh index"); + } else { + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * Status is quite simple and could be replaced with calls to + * wt_status in the future, but it adds complexities which may + * require more tests. + */ + cp.git_cmd = 1; + cp.dir = prefix; + argv_array_push(&cp.args, "status"); + run_command(&cp); + } + + return 0; +} + +static int apply_stash(int argc, const char **argv, const char *prefix) +{ + int index = 0; + struct stash_info info; + int ret; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_BOOL(0, "index", &index, + N_("attempt to recreate the index")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_apply_usage, 0); + + if (get_stash_info(&info, argc, argv)) + return -1; + + ret = do_apply_stash(prefix, &info, index); + free_stash_info(&info); + return ret; +} + +int cmd_stash__helper(int argc, const char **argv, const char *prefix) +{ + pid_t pid = getpid(); + const char *index_file; + + struct option options[] = { + OPT_END() + }; + + git_config(git_default_config, NULL); + + argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage, + PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + + index_file = get_index_file(); + strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, + (uintmax_t)pid); + + if (argc < 1) + usage_with_options(git_stash_helper_usage, options); + if (!strcmp(argv[0], "apply")) + return !!apply_stash(argc, argv, prefix); + + usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), + git_stash_helper_usage, options); +} diff --git a/git-stash.sh b/git-stash.sh index 94793c1a913abf..809b1c2d1d3ac7 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -566,76 +566,11 @@ assert_stash_ref() { } apply_stash () { - - assert_stash_like "$@" - - git update-index -q --refresh || die "$(gettext "unable to refresh index")" - - # current index state - c_tree=$(git write-tree) || - die "$(gettext "Cannot apply a stash in the middle of a merge")" - - unstashed_index_tree= - if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" && - test "$c_tree" != "$i_tree" - then - git diff-tree --binary $s^2^..$s^2 | git apply --cached - test $? -ne 0 && - die "$(gettext "Conflicts in index. Try without --index.")" - unstashed_index_tree=$(git write-tree) || - die "$(gettext "Could not save index tree")" - git reset - fi - - if test -n "$u_tree" - then - GIT_INDEX_FILE="$TMPindex" git read-tree "$u_tree" && - GIT_INDEX_FILE="$TMPindex" git checkout-index --all && - rm -f "$TMPindex" || - die "$(gettext "Could not restore untracked files from stash entry")" - fi - - eval " - GITHEAD_$w_tree='Stashed changes' && - GITHEAD_$c_tree='Updated upstream' && - GITHEAD_$b_tree='Version stash was based on' && - export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree - " - - if test -n "$GIT_QUIET" - then - GIT_MERGE_VERBOSITY=0 && export GIT_MERGE_VERBOSITY - fi - if git merge-recursive $b_tree -- $c_tree $w_tree - then - # No conflict - if test -n "$unstashed_index_tree" - then - git read-tree "$unstashed_index_tree" - else - a="$TMP-added" && - git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" && - git read-tree --reset $c_tree && - git update-index --add --stdin <"$a" || - die "$(gettext "Cannot unstage modified files")" - rm -f "$a" - fi - squelch= - if test -n "$GIT_QUIET" - then - squelch='>/dev/null 2>&1' - fi - (cd "$START_DIR" && eval "git status $squelch") || : - else - # Merge conflict; keep the exit status from merge-recursive - status=$? - git rerere - if test -n "$INDEX_OPTION" - then - gettextln "Index was not unstashed." >&2 - fi - exit $status - fi + cd "$START_DIR" + git stash--helper apply "$@" + res=$? + cd_to_toplevel + return $res } pop_stash() { @@ -713,7 +648,8 @@ push) ;; apply) shift - apply_stash "$@" + cd "$START_DIR" + git stash--helper apply "$@" ;; clear) shift diff --git a/git.c b/git.c index c27c38738b2a9d..3c0e762d7d2011 100644 --- a/git.c +++ b/git.c @@ -544,6 +544,7 @@ static struct cmd_struct commands[] = { { "show-index", cmd_show_index }, { "show-ref", cmd_show_ref, RUN_SETUP }, { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, + { "stash--helper", cmd_stash__helper, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT }, From a11d685bb6a894da62f5b371b7505ca59cac8484 Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Fri, 31 Aug 2018 00:40:37 +0300 Subject: [PATCH 07/93] stash: convert drop and clear to builtin Add the drop and clear commands to the builtin helper. These two are each simple, but are being added together as they are quite related. We have to unfortunately keep the drop and clear functions in the shell script as functions are called with parameters internally that are not valid when the commands are called externally. Once pop is converted they can both be removed. Signed-off-by: Joel Teichroeb Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 115 ++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 4 +- 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 684d905c0f9a27..b884fe9b0e35c7 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -12,7 +12,14 @@ #include "rerere.h" static const char * const git_stash_helper_usage[] = { + N_("git stash--helper drop [-q|--quiet] []"), N_("git stash--helper apply [--index] [-q|--quiet] []"), + N_("git stash--helper clear"), + NULL +}; + +static const char * const git_stash_helper_drop_usage[] = { + N_("git stash--helper drop [-q|--quiet] []"), NULL }; @@ -21,6 +28,11 @@ static const char * const git_stash_helper_apply_usage[] = { NULL }; +static const char * const git_stash_helper_clear_usage[] = { + N_("git stash--helper clear"), + NULL +}; + static const char *ref_stash = "refs/stash"; static int quiet; static struct strbuf stash_index_path = STRBUF_INIT; @@ -140,6 +152,31 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) return !(ret == 0 || ret == 1); } +static int do_clear_stash(void) +{ + struct object_id obj; + if (get_oid(ref_stash, &obj)) + return 0; + + return delete_ref(NULL, ref_stash, &obj, 0); +} + +static int clear_stash(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_clear_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc) + return error(_("git stash clear with parameters is unimplemented")); + + return do_clear_stash(); +} + static int reset_tree(struct object_id *i_tree, int update, int reset) { struct unpack_trees_options opts; @@ -425,6 +462,80 @@ static int apply_stash(int argc, const char **argv, const char *prefix) return ret; } +static int do_drop_stash(const char *prefix, struct stash_info *info) +{ + struct child_process cp_reflog = CHILD_PROCESS_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + int ret; + + /* + * reflog does not provide a simple function for deleting refs. One will + * need to be added to avoid implementing too much reflog code here + */ + + cp_reflog.git_cmd = 1; + argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", + "--rewrite", NULL); + argv_array_push(&cp_reflog.args, info->revision.buf); + ret = run_command(&cp_reflog); + if (!ret) { + if (!quiet) + printf_ln(_("Dropped %s (%s)"), info->revision.buf, + oid_to_hex(&info->w_commit)); + } else { + return error(_("%s: Could not drop stash entry"), + info->revision.buf); + } + + /* + * This could easily be replaced by get_oid, but currently it will throw + * a fatal error when a reflog is empty, which we can not recover from. + */ + cp.git_cmd = 1; + /* Even though --quiet is specified, rev-parse still outputs the hash */ + cp.no_stdout = 1; + argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL); + argv_array_pushf(&cp.args, "%s@{0}", ref_stash); + ret = run_command(&cp); + + /* do_clear_stash if we just dropped the last stash entry */ + if (ret) + do_clear_stash(); + + return 0; +} + +static void assert_stash_ref(struct stash_info *info) +{ + if (!info->is_stash_ref) { + free_stash_info(info); + error(_("'%s' is not a stash reference"), info->revision.buf); + exit(128); + } +} + +static int drop_stash(int argc, const char **argv, const char *prefix) +{ + struct stash_info info; + int ret; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_drop_usage, 0); + + if (get_stash_info(&info, argc, argv)) + return -1; + + assert_stash_ref(&info); + + ret = do_drop_stash(prefix, &info); + free_stash_info(&info); + return ret; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -447,6 +558,10 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) usage_with_options(git_stash_helper_usage, options); if (!strcmp(argv[0], "apply")) return !!apply_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "clear")) + return !!clear_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "drop")) + return !!drop_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index 809b1c2d1d3ac7..a99d5dc9e5d655 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -653,7 +653,7 @@ apply) ;; clear) shift - clear_stash "$@" + git stash--helper clear "$@" ;; create) shift @@ -665,7 +665,7 @@ store) ;; drop) shift - drop_stash "$@" + git stash--helper drop "$@" ;; pop) shift From 188f8e615204505c7b193610eef6fe40d1200b0a Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Fri, 31 Aug 2018 00:40:38 +0300 Subject: [PATCH 08/93] stash: convert branch to builtin Add stash branch to the helper and delete the apply_to_branch function from the shell script. Checkout does not currently provide a function for checking out a branch as cmd_checkout does a large amount of sanity checks first that we require here. Signed-off-by: Joel Teichroeb Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 44 +++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 17 ++-------------- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index b884fe9b0e35c7..0ed17f34c736fb 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -14,6 +14,7 @@ static const char * const git_stash_helper_usage[] = { N_("git stash--helper drop [-q|--quiet] []"), N_("git stash--helper apply [--index] [-q|--quiet] []"), + N_("git stash--helper branch []"), N_("git stash--helper clear"), NULL }; @@ -28,6 +29,11 @@ static const char * const git_stash_helper_apply_usage[] = { NULL }; +static const char * const git_stash_helper_branch_usage[] = { + N_("git stash--helper branch []"), + NULL +}; + static const char * const git_stash_helper_clear_usage[] = { N_("git stash--helper clear"), NULL @@ -536,6 +542,42 @@ static int drop_stash(int argc, const char **argv, const char *prefix) return ret; } +static int branch_stash(int argc, const char **argv, const char *prefix) +{ + const char *branch = NULL; + int ret; + struct child_process cp = CHILD_PROCESS_INIT; + struct stash_info info; + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_branch_usage, 0); + + if (!argc) + return error(_("No branch name specified")); + + branch = argv[0]; + + if (get_stash_info(&info, argc - 1, argv + 1)) + return -1; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "checkout", "-b", NULL); + argv_array_push(&cp.args, branch); + argv_array_push(&cp.args, oid_to_hex(&info.b_commit)); + ret = run_command(&cp); + if (!ret) + ret = do_apply_stash(prefix, &info, 1); + if (!ret && info.is_stash_ref) + ret = do_drop_stash(prefix, &info); + + free_stash_info(&info); + + return ret; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -562,6 +604,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!clear_stash(argc, argv, prefix); else if (!strcmp(argv[0], "drop")) return !!drop_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "branch")) + return !!branch_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index a99d5dc9e5d655..29d9f442559c5e 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -598,20 +598,6 @@ drop_stash () { clear_stash } -apply_to_branch () { - test -n "$1" || die "$(gettext "No branch name specified")" - branch=$1 - shift 1 - - set -- --index "$@" - assert_stash_like "$@" - - git checkout -b $branch $REV^ && - apply_stash "$@" && { - test -z "$IS_STASH_REF" || drop_stash "$@" - } -} - test "$1" = "-p" && set "push" "$@" PARSE_CACHE='--not-parsed' @@ -673,7 +659,8 @@ pop) ;; branch) shift - apply_to_branch "$@" + cd "$START_DIR" + git stash--helper branch "$@" ;; *) case $# in From be3762cae462e1056f9478534074af1f3104ad7a Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Fri, 31 Aug 2018 00:40:39 +0300 Subject: [PATCH 09/93] stash: convert pop to builtin Add stash pop to the helper and delete the pop_stash, drop_stash, assert_stash_ref functions from the shell script now that they are no longer needed. Signed-off-by: Joel Teichroeb Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 36 ++++++++++++++++++++++++++++++- git-stash.sh | 47 ++--------------------------------------- 2 files changed, 37 insertions(+), 46 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 0ed17f34c736fb..1ecb663caa6b06 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -13,7 +13,7 @@ static const char * const git_stash_helper_usage[] = { N_("git stash--helper drop [-q|--quiet] []"), - N_("git stash--helper apply [--index] [-q|--quiet] []"), + N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] []"), N_("git stash--helper branch []"), N_("git stash--helper clear"), NULL @@ -24,6 +24,11 @@ static const char * const git_stash_helper_drop_usage[] = { NULL }; +static const char * const git_stash_helper_pop_usage[] = { + N_("git stash--helper pop [--index] [-q|--quiet] []"), + NULL +}; + static const char * const git_stash_helper_apply_usage[] = { N_("git stash--helper apply [--index] [-q|--quiet] []"), NULL @@ -542,6 +547,33 @@ static int drop_stash(int argc, const char **argv, const char *prefix) return ret; } +static int pop_stash(int argc, const char **argv, const char *prefix) +{ + int index = 0, ret; + struct stash_info info; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_BOOL(0, "index", &index, + N_("attempt to recreate the index")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_pop_usage, 0); + + if (get_stash_info(&info, argc, argv)) + return -1; + + assert_stash_ref(&info); + if ((ret = do_apply_stash(prefix, &info, index))) + printf_ln(_("The stash entry is kept in case you need it again.")); + else + ret = do_drop_stash(prefix, &info); + + free_stash_info(&info); + return ret; +} + static int branch_stash(int argc, const char **argv, const char *prefix) { const char *branch = NULL; @@ -604,6 +636,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!clear_stash(argc, argv, prefix); else if (!strcmp(argv[0], "drop")) return !!drop_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "pop")) + return !!pop_stash(argc, argv, prefix); else if (!strcmp(argv[0], "branch")) return !!branch_stash(argc, argv, prefix); diff --git a/git-stash.sh b/git-stash.sh index 29d9f442559c5e..8f2640fe902b21 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -554,50 +554,6 @@ assert_stash_like() { } } -is_stash_ref() { - is_stash_like "$@" && test -n "$IS_STASH_REF" -} - -assert_stash_ref() { - is_stash_ref "$@" || { - args="$*" - die "$(eval_gettext "'\$args' is not a stash reference")" - } -} - -apply_stash () { - cd "$START_DIR" - git stash--helper apply "$@" - res=$? - cd_to_toplevel - return $res -} - -pop_stash() { - assert_stash_ref "$@" - - if apply_stash "$@" - then - drop_stash "$@" - else - status=$? - say "$(gettext "The stash entry is kept in case you need it again.")" - exit $status - fi -} - -drop_stash () { - assert_stash_ref "$@" - - git reflog delete --updateref --rewrite "${REV}" && - say "$(eval_gettext "Dropped \${REV} (\$s)")" || - die "$(eval_gettext "\${REV}: Could not drop stash entry")" - - # clear_stash if we just dropped the last stash entry - git rev-parse --verify --quiet "$ref_stash@{0}" >/dev/null || - clear_stash -} - test "$1" = "-p" && set "push" "$@" PARSE_CACHE='--not-parsed' @@ -655,7 +611,8 @@ drop) ;; pop) shift - pop_stash "$@" + cd "$START_DIR" + git stash--helper pop "$@" ;; branch) shift From ba210265790482a2b2908e69f7573986206babc7 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:40 +0300 Subject: [PATCH 10/93] stash: convert list to builtin Add stash list to the helper and delete the list_stash function from the shell script. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 31 +++++++++++++++++++++++++++++++ git-stash.sh | 7 +------ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 1ecb663caa6b06..b592d668137552 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -12,6 +12,7 @@ #include "rerere.h" static const char * const git_stash_helper_usage[] = { + N_("git stash--helper list []"), N_("git stash--helper drop [-q|--quiet] []"), N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] []"), N_("git stash--helper branch []"), @@ -19,6 +20,11 @@ static const char * const git_stash_helper_usage[] = { NULL }; +static const char * const git_stash_helper_list_usage[] = { + N_("git stash--helper list []"), + NULL +}; + static const char * const git_stash_helper_drop_usage[] = { N_("git stash--helper drop [-q|--quiet] []"), NULL @@ -610,6 +616,29 @@ static int branch_stash(int argc, const char **argv, const char *prefix) return ret; } +static int list_stash(int argc, const char **argv, const char *prefix) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_list_usage, + PARSE_OPT_KEEP_UNKNOWN); + + if (!ref_exists(ref_stash)) + return 0; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g", + "--first-parent", "-m", NULL); + argv_array_pushv(&cp.args, argv); + argv_array_push(&cp.args, ref_stash); + argv_array_push(&cp.args, "--"); + return run_command(&cp); +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -640,6 +669,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!pop_stash(argc, argv, prefix); else if (!strcmp(argv[0], "branch")) return !!branch_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "list")) + return !!list_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index 8f2640fe902b21..6052441aa2f790 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -382,11 +382,6 @@ have_stash () { git rev-parse --verify --quiet $ref_stash >/dev/null } -list_stash () { - have_stash || return 0 - git log --format="%gd: %gs" -g --first-parent -m "$@" $ref_stash -- -} - show_stash () { ALLOW_UNKNOWN_FLAGS=t assert_stash_like "$@" @@ -574,7 +569,7 @@ test -n "$seen_non_option" || set "push" "$@" case "$1" in list) shift - list_stash "$@" + git stash--helper list "$@" ;; show) shift From de84e9bd83428eee260ae801b75e0f325c739a1a Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:41 +0300 Subject: [PATCH 11/93] stash: convert show to builtin Add stash show to the helper and delete the show_stash, have_stash, assert_stash_like, is_stash_like and parse_flags_and_rev functions from the shell script now that they are no longer needed. In shell version, although `git stash show` accepts `--index` and `--quiet` options, it ignores them. In C, both options are passed further to `git diff`. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 87 ++++++++++++++++++++++++++ git-stash.sh | 132 +--------------------------------------- 2 files changed, 88 insertions(+), 131 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index b592d668137552..21481a88817ee5 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -10,9 +10,12 @@ #include "run-command.h" #include "dir.h" #include "rerere.h" +#include "revision.h" +#include "log-tree.h" static const char * const git_stash_helper_usage[] = { N_("git stash--helper list []"), + N_("git stash--helper show []"), N_("git stash--helper drop [-q|--quiet] []"), N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] []"), N_("git stash--helper branch []"), @@ -25,6 +28,11 @@ static const char * const git_stash_helper_list_usage[] = { NULL }; +static const char * const git_stash_helper_show_usage[] = { + N_("git stash--helper show []"), + NULL +}; + static const char * const git_stash_helper_drop_usage[] = { N_("git stash--helper drop [-q|--quiet] []"), NULL @@ -639,6 +647,83 @@ static int list_stash(int argc, const char **argv, const char *prefix) return run_command(&cp); } +static int show_stat = 1; +static int show_patch; + +static int git_stash_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "stash.showstat")) { + show_stat = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "stash.showpatch")) { + show_patch = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); +} + +static int show_stash(int argc, const char **argv, const char *prefix) +{ + int i; + int opts = 0; + int ret = 0; + struct stash_info info; + struct rev_info rev; + struct argv_array stash_args = ARGV_ARRAY_INIT; + struct option options[] = { + OPT_END() + }; + + init_diff_ui_defaults(); + git_config(git_diff_ui_config, NULL); + init_revisions(&rev, prefix); + + for (i = 1; i < argc; ++i) { + if (argv[i][0] != '-') + argv_array_push(&stash_args, argv[i]); + else + opts++; + } + + ret = get_stash_info(&info, stash_args.argc, stash_args.argv); + argv_array_clear(&stash_args); + if (ret) + return -1; + + /* + * The config settings are applied only if there are not passed + * any options. + */ + if (!opts) { + git_config(git_stash_config, NULL); + if (show_stat) + rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; + + if (show_patch) + rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + + if (!show_stat && !show_patch) { + free_stash_info(&info); + return 0; + } + } + + argc = setup_revisions(argc, argv, &rev, NULL); + if (argc > 1) { + free_stash_info(&info); + usage_with_options(git_stash_helper_show_usage, options); + } + + rev.diffopt.flags.recursive = 1; + setup_diff_pager(&rev.diffopt); + diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + log_tree_diff_flush(&rev); + + free_stash_info(&info); + return diff_result_code(&rev.diffopt, 0); +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -671,6 +756,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!branch_stash(argc, argv, prefix); else if (!strcmp(argv[0], "list")) return !!list_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "show")) + return !!show_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index 6052441aa2f790..0d05cbc1e53f34 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -378,35 +378,6 @@ save_stash () { fi } -have_stash () { - git rev-parse --verify --quiet $ref_stash >/dev/null -} - -show_stash () { - ALLOW_UNKNOWN_FLAGS=t - assert_stash_like "$@" - - if test -z "$FLAGS" - then - if test "$(git config --bool stash.showStat || echo true)" = "true" - then - FLAGS=--stat - fi - - if test "$(git config --bool stash.showPatch || echo false)" = "true" - then - FLAGS=${FLAGS}${FLAGS:+ }-p - fi - - if test -z "$FLAGS" - then - return 0 - fi - fi - - git diff ${FLAGS} $b_commit $w_commit -} - show_help () { exec git help stash exit 1 @@ -448,107 +419,6 @@ show_help () { # * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t" # -parse_flags_and_rev() -{ - test "$PARSE_CACHE" = "$*" && return 0 # optimisation - PARSE_CACHE="$*" - - IS_STASH_LIKE= - IS_STASH_REF= - INDEX_OPTION= - s= - w_commit= - b_commit= - i_commit= - u_commit= - w_tree= - b_tree= - i_tree= - u_tree= - - FLAGS= - REV= - for opt - do - case "$opt" in - -q|--quiet) - GIT_QUIET=-t - ;; - --index) - INDEX_OPTION=--index - ;; - --help) - show_help - ;; - -*) - test "$ALLOW_UNKNOWN_FLAGS" = t || - die "$(eval_gettext "unknown option: \$opt")" - FLAGS="${FLAGS}${FLAGS:+ }$opt" - ;; - *) - REV="${REV}${REV:+ }'$opt'" - ;; - esac - done - - eval set -- $REV - - case $# in - 0) - have_stash || die "$(gettext "No stash entries found.")" - set -- ${ref_stash}@{0} - ;; - 1) - : - ;; - *) - die "$(eval_gettext "Too many revisions specified: \$REV")" - ;; - esac - - case "$1" in - *[!0-9]*) - : - ;; - *) - set -- "${ref_stash}@{$1}" - ;; - esac - - REV=$(git rev-parse --symbolic --verify --quiet "$1") || { - reference="$1" - die "$(eval_gettext "\$reference is not a valid reference")" - } - - i_commit=$(git rev-parse --verify --quiet "$REV^2") && - set -- $(git rev-parse "$REV" "$REV^1" "$REV:" "$REV^1:" "$REV^2:" 2>/dev/null) && - s=$1 && - w_commit=$1 && - b_commit=$2 && - w_tree=$3 && - b_tree=$4 && - i_tree=$5 && - IS_STASH_LIKE=t && - test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" && - IS_STASH_REF=t - - u_commit=$(git rev-parse --verify --quiet "$REV^3") && - u_tree=$(git rev-parse "$REV^3:" 2>/dev/null) -} - -is_stash_like() -{ - parse_flags_and_rev "$@" - test -n "$IS_STASH_LIKE" -} - -assert_stash_like() { - is_stash_like "$@" || { - args="$*" - die "$(eval_gettext "'\$args' is not a stash-like commit")" - } -} - test "$1" = "-p" && set "push" "$@" PARSE_CACHE='--not-parsed' @@ -573,7 +443,7 @@ list) ;; show) shift - show_stash "$@" + git stash--helper show "$@" ;; save) shift From d737399c7aaf4120e6bc340d59ecc8ce46511202 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:42 +0300 Subject: [PATCH 12/93] stash: mention options in `show` synopsis Mention in the usage text and in the documentation, that `show` accepts any option known to `git diff`. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- Documentation/git-stash.txt | 4 ++-- builtin/stash--helper.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 7ef8c4791177b2..e31ea7d3037d55 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git stash' list [] -'git stash' show [] +'git stash' show [] [] 'git stash' drop [-q|--quiet] [] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [] 'git stash' branch [] @@ -106,7 +106,7 @@ stash@{1}: On master: 9cc0589... Add git-stash The command takes options applicable to the 'git log' command to control what is shown and how. See linkgit:git-log[1]. -show []:: +show [] []:: Show the changes recorded in the stash entry as a diff between the stashed contents and the commit back when the stash entry was first diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 21481a88817ee5..9ebf703622e672 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -15,7 +15,7 @@ static const char * const git_stash_helper_usage[] = { N_("git stash--helper list []"), - N_("git stash--helper show []"), + N_("git stash--helper show [] []"), N_("git stash--helper drop [-q|--quiet] []"), N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] []"), N_("git stash--helper branch []"), @@ -29,7 +29,7 @@ static const char * const git_stash_helper_list_usage[] = { }; static const char * const git_stash_helper_show_usage[] = { - N_("git stash--helper show []"), + N_("git stash--helper show [] []"), NULL }; From b5c613841784ad863ef713a3ca0c121d212604e2 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:43 +0300 Subject: [PATCH 13/93] stash: convert store to builtin Add stash store to the helper and delete the store_stash function from the shell script. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 55 +++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 43 ++------------------------------ 2 files changed, 57 insertions(+), 41 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 9ebf703622e672..93778309f75ada 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -58,6 +58,11 @@ static const char * const git_stash_helper_clear_usage[] = { NULL }; +static const char * const git_stash_helper_store_usage[] = { + N_("git stash--helper store [-m|--message ] [-q|--quiet] "), + NULL +}; + static const char *ref_stash = "refs/stash"; static int quiet; static struct strbuf stash_index_path = STRBUF_INIT; @@ -724,6 +729,54 @@ static int show_stash(int argc, const char **argv, const char *prefix) return diff_result_code(&rev.diffopt, 0); } +static int do_store_stash(const char *w_commit, const char *stash_msg, + int quiet) +{ + int ret = 0; + int need_to_free = 0; + struct object_id obj; + + if (!stash_msg) { + need_to_free = 1; + stash_msg = xstrdup("Created via \"git stash store\"."); + } + + ret = get_oid(w_commit, &obj); + if (!ret) { + ret = update_ref(stash_msg, ref_stash, &obj, NULL, + REF_FORCE_CREATE_REFLOG, + quiet ? UPDATE_REFS_QUIET_ON_ERR : + UPDATE_REFS_MSG_ON_ERR); + } + if (ret && !quiet) + fprintf_ln(stderr, _("Cannot update %s with %s"), + ref_stash, w_commit); + if (need_to_free) + free((char *) stash_msg); + return ret; +} + +static int store_stash(int argc, const char **argv, const char *prefix) +{ + const char *stash_msg = NULL; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_STRING('m', "message", &stash_msg, "message", N_("stash message")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_store_usage, + PARSE_OPT_KEEP_UNKNOWN); + + if (argc != 1) { + fprintf_ln(stderr, _("\"git stash store\" requires one argument")); + return -1; + } + + return do_store_stash(argv[0], stash_msg, quiet); +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -758,6 +811,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!list_stash(argc, argv, prefix); else if (!strcmp(argv[0], "show")) return !!show_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "store")) + return !!store_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index 0d05cbc1e53f34..5739c515275cff 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -191,45 +191,6 @@ create_stash () { die "$(gettext "Cannot record working tree state")" } -store_stash () { - while test $# != 0 - do - case "$1" in - -m|--message) - shift - stash_msg="$1" - ;; - -m*) - stash_msg=${1#-m} - ;; - --message=*) - stash_msg=${1#--message=} - ;; - -q|--quiet) - quiet=t - ;; - *) - break - ;; - esac - shift - done - test $# = 1 || - die "$(eval_gettext "\"$dashless store\" requires one argument")" - - w_commit="$1" - if test -z "$stash_msg" - then - stash_msg="Created via \"git stash store\"." - fi - - git update-ref --create-reflog -m "$stash_msg" $ref_stash $w_commit - ret=$? - test $ret != 0 && test -z "$quiet" && - die "$(eval_gettext "Cannot update \$ref_stash with \$w_commit")" - return $ret -} - push_stash () { keep_index= patch_mode= @@ -308,7 +269,7 @@ push_stash () { clear_stash || die "$(gettext "Cannot initialize stash")" create_stash -m "$stash_msg" -u "$untracked" -- "$@" - store_stash -m "$stash_msg" -q $w_commit || + git stash--helper store -m "$stash_msg" -q $w_commit || die "$(gettext "Cannot save the current status")" say "$(eval_gettext "Saved working directory and index state \$stash_msg")" @@ -468,7 +429,7 @@ create) ;; store) shift - store_stash "$@" + git stash--helper store "$@" ;; drop) shift From 05db4803bee813b8fbd6e004813dec3605546e6b Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:44 +0300 Subject: [PATCH 14/93] stash: convert create to builtin Add stash create to the helper. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 430 ++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 2 +- 2 files changed, 431 insertions(+), 1 deletion(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 93778309f75ada..293e37be614d9b 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -12,6 +12,7 @@ #include "rerere.h" #include "revision.h" #include "log-tree.h" +#include "diffcore.h" static const char * const git_stash_helper_usage[] = { N_("git stash--helper list []"), @@ -63,6 +64,11 @@ static const char * const git_stash_helper_store_usage[] = { NULL }; +static const char * const git_stash_helper_create_usage[] = { + N_("git stash--helper create []"), + NULL +}; + static const char *ref_stash = "refs/stash"; static int quiet; static struct strbuf stash_index_path = STRBUF_INIT; @@ -290,6 +296,18 @@ static int reset_head(void) return run_command(&cp); } +static void add_diff_to_buf(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + strbuf_addstr(data, p->one->path); + strbuf_addch(data, 0); + } +} + static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) { struct child_process cp = CHILD_PROCESS_INIT; @@ -777,6 +795,416 @@ static int store_stash(int argc, const char **argv, const char *prefix) return do_store_stash(argv[0], stash_msg, quiet); } +/* + * `out` will be filled with the names of untracked files. The return value is: + * + * = 0 if there are not any untracked files + * > 0 if there are untracked files + */ +static int get_untracked_files(struct pathspec ps, int include_untracked, + struct strbuf *out) +{ + int max_len; + int i; + char *seen; + struct dir_struct dir; + + memset(&dir, 0, sizeof(dir)); + if (include_untracked != 2) + setup_standard_excludes(&dir); + + seen = xcalloc(ps.nr, 1); + + max_len = fill_directory(&dir, the_repository->index, &ps); + for (i = 0; i < dir.nr; i++) { + struct dir_entry *ent = dir.entries[i]; + if (!dir_path_match(&the_index, ent, &ps, max_len, seen)) { + free(ent); + continue; + } + strbuf_addf(out, "%s%c", ent->name, '\0'); + free(ent); + } + + free(dir.entries); + free(dir.ignored); + clear_directory(&dir); + free(seen); + return out->len; +} + +/* + * The return value of `check_changes()` can be: + * + * < 0 if there was an error + * = 0 if there are no changes. + * > 0 if there are changes. + */ +static int check_changes(struct pathspec ps, int include_untracked) +{ + int result; + int ret = 0; + struct rev_info rev; + struct object_id dummy; + struct strbuf out = STRBUF_INIT; + + init_revisions(&rev, NULL); + rev.prune_data = ps; + + rev.diffopt.flags.quick = 1; + rev.diffopt.flags.ignore_submodules = 1; + rev.abbrev = 0; + + /* No initial commit. */ + if (get_oid("HEAD", &dummy)) + return -1; + + add_head_to_pending(&rev); + diff_setup_done(&rev.diffopt); + + if (read_cache() < 0) + return 1; + result = run_diff_index(&rev, 1); + if (diff_result_code(&rev.diffopt, result)) + return 1; + + object_array_clear(&rev.pending); + result = run_diff_files(&rev, 0); + if (diff_result_code(&rev.diffopt, result)) + return 1; + + if (include_untracked && get_untracked_files(ps, include_untracked, + &out)) { + strbuf_release(&out); + return 1; + } + + strbuf_release(&out); + return 0; +} + +static int save_untracked_files(struct stash_info *info, struct strbuf *msg, + struct strbuf *in) +{ + int ret = 0; + struct strbuf untracked_msg = STRBUF_INIT; + struct strbuf out = STRBUF_INIT; + struct child_process cp_upd_index = CHILD_PROCESS_INIT; + struct child_process cp_write_tree = CHILD_PROCESS_INIT; + + cp_upd_index.git_cmd = 1; + argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", + "--remove", "--stdin", NULL); + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + + strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); + if (pipe_command(&cp_upd_index, in->buf, in->len, NULL, 0, NULL, 0)) { + ret = -1; + goto done; + } + + cp_write_tree.git_cmd = 1; + argv_array_push(&cp_write_tree.args, "write-tree"); + argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) { + ret = -1; + goto done; + } + get_oid_hex(out.buf, &info->u_tree); + + if (commit_tree(untracked_msg.buf, untracked_msg.len, + &info->u_tree, NULL, &info->u_commit, NULL, NULL)) { + ret = -1; + goto done; + } + +done: + strbuf_release(&untracked_msg); + strbuf_release(&out); + remove_path(stash_index_path.buf); + return ret; +} + +static struct strbuf patch = STRBUF_INIT; + +static int stash_patch(struct stash_info *info, struct pathspec ps) +{ + int i; + int ret = 0; + struct strbuf out = STRBUF_INIT; + struct child_process cp_read_tree = CHILD_PROCESS_INIT; + struct child_process cp_add_i = CHILD_PROCESS_INIT; + struct child_process cp_write_tree = CHILD_PROCESS_INIT; + struct child_process cp_diff_tree = CHILD_PROCESS_INIT; + + remove_path(stash_index_path.buf); + + cp_read_tree.git_cmd = 1; + argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); + argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (run_command(&cp_read_tree)) { + ret = -1; + goto done; + } + + cp_add_i.git_cmd = 1; + argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", + "--", NULL); + for (i = 0; i < ps.nr; ++i) + argv_array_push(&cp_add_i.args, ps.items[i].match); + argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (run_command(&cp_add_i)) { + ret = -1; + goto done; + } + + cp_write_tree.git_cmd = 1; + argv_array_push(&cp_write_tree.args, "write-tree"); + argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) { + ret = -1; + goto done; + } + + get_oid_hex(out.buf, &info->w_tree); + + cp_diff_tree.git_cmd = 1; + argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD", + oid_to_hex(&info->w_tree), "--", NULL); + if (pipe_command(&cp_diff_tree, NULL, 0, &patch, 0, NULL, 0)) { + ret = -1; + goto done; + } + + if (!patch.len) { + fprintf_ln(stderr, _("No changes selected")); + ret = 1; + } + +done: + strbuf_release(&out); + remove_path(stash_index_path.buf); + return ret; +} + +static int stash_working_tree(struct stash_info *info, struct pathspec ps) +{ + int ret = 0; + struct child_process cp_upd_index = CHILD_PROCESS_INIT; + struct child_process cp_write_tree = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + struct strbuf diff_output = STRBUF_INIT; + struct rev_info rev; + + set_alternate_index_output(stash_index_path.buf); + if (reset_tree(&info->i_tree, 0, 0)) { + ret = -1; + goto done; + } + set_alternate_index_output(NULL); + + git_config(git_diff_basic_config, NULL); + init_revisions(&rev, NULL); + rev.prune_data = ps; + rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = add_diff_to_buf; + rev.diffopt.format_callback_data = &diff_output; + + if (read_cache_preload(&rev.diffopt.pathspec) < 0) { + ret = -1; + goto done; + } + + add_pending_object(&rev, parse_object(the_repository, &info->b_commit), ""); + if (run_diff_index(&rev, 0)) { + ret = -1; + goto done; + } + + cp_upd_index.git_cmd = 1; + argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", + "--remove", "--stdin", NULL); + argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + + if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len, + NULL, 0, NULL, 0)) { + ret = -1; + goto done; + } + + cp_write_tree.git_cmd = 1; + argv_array_push(&cp_write_tree.args, "write-tree"); + argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); + if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) { + ret = -1; + goto done; + } + + get_oid_hex(out.buf, &info->w_tree); + +done: + UNLEAK(rev); + strbuf_release(&out); + object_array_clear(&rev.pending); + strbuf_release(&diff_output); + remove_path(stash_index_path.buf); + return ret; +} + +static int do_create_stash(struct pathspec ps, const char **stash_msg, + int include_untracked, int patch_mode, + struct stash_info *info) +{ + int untracked_commit_option = 0; + int ret = 0; + int flags; + const char *head_short_sha1 = NULL; + const char *branch_ref = NULL; + const char *branch_name = "(no branch)"; + struct commit *head_commit = NULL; + struct commit_list *parents = NULL; + struct strbuf msg = STRBUF_INIT; + struct strbuf commit_tree_label = STRBUF_INIT; + struct strbuf out = STRBUF_INIT; + struct strbuf stash_msg_buf = STRBUF_INIT; + + read_cache_preload(NULL); + refresh_cache(REFRESH_QUIET); + + if (!check_changes(ps, include_untracked)) { + ret = 1; + *stash_msg = NULL; + goto done; + } + + if (get_oid("HEAD", &info->b_commit)) { + fprintf_ln(stderr, _("You do not have the initial commit yet")); + ret = -1; + *stash_msg = NULL; + goto done; + } else { + head_commit = lookup_commit(the_repository, &info->b_commit); + } + + branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags); + if (flags & REF_ISSYMREF) + branch_name = strrchr(branch_ref, '/') + 1; + head_short_sha1 = find_unique_abbrev(&head_commit->object.oid, + DEFAULT_ABBREV); + strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1); + pp_commit_easy(CMIT_FMT_ONELINE, head_commit, &msg); + + strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf); + commit_list_insert(head_commit, &parents); + if (write_cache_as_tree(&info->i_tree, 0, NULL) || + commit_tree(commit_tree_label.buf, commit_tree_label.len, + &info->i_tree, parents, &info->i_commit, NULL, NULL)) { + fprintf_ln(stderr, _("Cannot save the current index state")); + ret = -1; + *stash_msg = NULL; + goto done; + } + + if (include_untracked && get_untracked_files(ps, include_untracked, + &out)) { + if (save_untracked_files(info, &msg, &out)) { + fprintf_ln(stderr, _("Cannot save the untracked files")); + ret = -1; + *stash_msg = NULL; + goto done; + } + untracked_commit_option = 1; + } + if (patch_mode) { + ret = stash_patch(info, ps); + *stash_msg = NULL; + if (ret < 0) { + fprintf_ln(stderr, _("Cannot save the current worktree state")); + goto done; + } else if (ret > 0) { + goto done; + } + } else { + if (stash_working_tree(info, ps)) { + fprintf_ln(stderr, _("Cannot save the current worktree state")); + ret = -1; + *stash_msg = NULL; + goto done; + } + } + + if (!*stash_msg || !strlen(*stash_msg)) + strbuf_addf(&stash_msg_buf, "WIP on %s", msg.buf); + else + strbuf_addf(&stash_msg_buf, "On %s: %s", branch_name, + *stash_msg); + *stash_msg = strbuf_detach(&stash_msg_buf, NULL); + + /* + * `parents` will be empty after calling `commit_tree()`, so there is + * no need to call `free_commit_list()` + */ + parents = NULL; + if (untracked_commit_option) + commit_list_insert(lookup_commit(the_repository, &info->u_commit), &parents); + commit_list_insert(lookup_commit(the_repository, &info->i_commit), &parents); + commit_list_insert(head_commit, &parents); + + if (commit_tree(*stash_msg, strlen(*stash_msg), &info->w_tree, + parents, &info->w_commit, NULL, NULL)) { + fprintf_ln(stderr, _("Cannot record working tree state")); + ret = -1; + goto done; + } + +done: + strbuf_release(&commit_tree_label); + strbuf_release(&msg); + strbuf_release(&out); + strbuf_release(&stash_msg_buf); + return ret; +} + +static int create_stash(int argc, const char **argv, const char *prefix) +{ + int include_untracked = 0; + int ret = 0; + const char *stash_msg = NULL; + struct stash_info info; + struct pathspec ps; + struct option options[] = { + OPT_BOOL('u', "include-untracked", &include_untracked, + N_("include untracked files in stash")), + OPT_STRING('m', "message", &stash_msg, N_("message"), + N_("stash message")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_create_usage, + 0); + + memset(&ps, 0, sizeof(ps)); + ret = do_create_stash(ps, &stash_msg, include_untracked, 0, &info); + + if (!ret) + printf_ln("%s", oid_to_hex(&info.w_commit)); + + /* + * ret can be 1 if there were no changes. In this case, we should + * not error out. + */ + free((char *) stash_msg); + return ret < 0; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -813,6 +1241,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!show_stash(argc, argv, prefix); else if (!strcmp(argv[0], "store")) return !!store_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "create")) + return !!create_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index 5739c515275cff..ab06e4ffb8a62c 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -425,7 +425,7 @@ clear) ;; create) shift - create_stash -m "$*" && echo "$w_commit" + git stash--helper create --message "$*" ;; store) shift From f8d93b2520438852e2a0805b62c12b038c6c93b0 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:45 +0300 Subject: [PATCH 15/93] stash: convert push to builtin Add stash push to the helper. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 229 ++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 6 +- 2 files changed, 233 insertions(+), 2 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 293e37be614d9b..5c5660189cd99c 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -21,6 +21,9 @@ static const char * const git_stash_helper_usage[] = { N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] []"), N_("git stash--helper branch []"), N_("git stash--helper clear"), + N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" + " [--] [...]]"), NULL }; @@ -69,6 +72,13 @@ static const char * const git_stash_helper_create_usage[] = { NULL }; +static const char * const git_stash_helper_push_usage[] = { + N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" + " [--] [...]]"), + NULL +}; + static const char *ref_stash = "refs/stash"; static int quiet; static struct strbuf stash_index_path = STRBUF_INIT; @@ -1205,6 +1215,223 @@ static int create_stash(int argc, const char **argv, const char *prefix) return ret < 0; } +static void add_ps_items_to_argv_array(struct argv_array *args, + struct pathspec ps) { + int i; + for (i = 0; i < ps.nr; ++i) + argv_array_push(args, ps.items[i].match); +} + +static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, + int keep_index, int patch_mode, int include_untracked) +{ + int ret = 0; + struct stash_info info; + if (patch_mode && keep_index == -1) + keep_index = 1; + + if (patch_mode && include_untracked) { + fprintf_ln(stderr, _("Can't use --patch and --include-untracked or --all at the same time")); + return -1; + } + + read_cache_preload(NULL); + if (!include_untracked && ps.nr) { + int i; + char *ps_matched = xcalloc(ps.nr, 1); + + for (i = 0; i < active_nr; ++i) { + const struct cache_entry *ce = active_cache[i]; + ce_path_match(&the_index, ce, &ps, ps_matched); + } + + if (report_path_error(ps_matched, &ps, NULL)) { + fprintf_ln(stderr, _("Did you forget to 'git add'?")); + return -1; + } + free(ps_matched); + } + + if (refresh_cache(REFRESH_QUIET)) + return -1; + + if (!check_changes(ps, include_untracked)) { + printf_ln(_("No local changes to save")); + return 0; + } + + if (!reflog_exists(ref_stash) && do_clear_stash()) { + fprintf_ln(stderr, _("Cannot initialize stash")); + return -1; + } + + if (do_create_stash(ps, &stash_msg, include_untracked, patch_mode, + &info)) { + ret = -1; + goto done; + } + + if (do_store_stash(oid_to_hex(&info.w_commit), stash_msg, 1)) { + fprintf(stderr, _("Cannot save the current status")); + ret = -1; + goto done; + } + + printf_ln(_("Saved working directory and index state %s"), stash_msg); + + if (!patch_mode) { + if (include_untracked && !ps.nr) { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "clean", "--force", + "--quiet", "-d", NULL); + if (include_untracked == 2) + argv_array_push(&cp.args, "-x"); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + if (ps.nr) { + struct child_process cp1 = CHILD_PROCESS_INIT; + struct child_process cp2 = CHILD_PROCESS_INIT; + struct child_process cp3 = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + + cp1.git_cmd = 1; + argv_array_push(&cp1.args, "add"); + if (!include_untracked) + argv_array_push(&cp1.args, "-u"); + if (include_untracked == 2) + argv_array_push(&cp1.args, "--force"); + argv_array_push(&cp1.args, "--"); + add_ps_items_to_argv_array(&cp1.args, ps); + if (run_command(&cp1)) { + ret = -1; + goto done; + } + + cp2.git_cmd = 1; + argv_array_pushl(&cp2.args, "diff-index", "-p", + "--cached", "--binary", "HEAD", "--", + NULL); + add_ps_items_to_argv_array(&cp2.args, ps); + if (pipe_command(&cp2, NULL, 0, &out, 0, NULL, 0)) { + ret = -1; + goto done; + } + + cp3.git_cmd = 1; + argv_array_pushl(&cp3.args, "apply", "--index", "-R", + NULL); + if (pipe_command(&cp3, out.buf, out.len, NULL, 0, NULL, + 0)) { + ret = -1; + goto done; + } + } else { + struct child_process cp = CHILD_PROCESS_INIT; + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "reset", "--hard", "-q", + NULL); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + + if (keep_index == 1 && !is_null_oid(&info.i_tree)) { + struct child_process cp1 = CHILD_PROCESS_INIT; + struct child_process cp2 = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + + if (reset_tree(&info.i_tree, 0, 1)) { + ret = -1; + goto done; + } + + cp1.git_cmd = 1; + argv_array_pushl(&cp1.args, "ls-files", "-z", + "--modified", "--", NULL); + add_ps_items_to_argv_array(&cp1.args, ps); + if (pipe_command(&cp1, NULL, 0, &out, 0, NULL, 0)) { + ret = -1; + goto done; + } + + cp2.git_cmd = 1; + argv_array_pushl(&cp2.args, "checkout-index", "-z", + "--force", "--stdin", NULL); + if (pipe_command(&cp2, out.buf, out.len, NULL, 0, NULL, + 0)) { + ret = -1; + goto done; + } + } + } else { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "apply", "-R", NULL); + + if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) { + fprintf_ln(stderr, _("Cannot remove worktree changes")); + ret = -1; + goto done; + } + + if (keep_index < 1) { + int i; + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); + for (i = 0; i < ps.nr; ++i) + argv_array_push(&cp.args, ps.items[i].match); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + } +done: + free((char *) stash_msg); + return ret; +} + +static int push_stash(int argc, const char **argv, const char *prefix) +{ + int keep_index = -1; + int patch_mode = 0; + int include_untracked = 0; + int quiet = 0; + const char *stash_msg = NULL; + struct pathspec ps; + struct option options[] = { + OPT_SET_INT('k', "keep-index", &keep_index, + N_("keep index"), 1), + OPT_BOOL('p', "patch", &patch_mode, + N_("stash in patch mode")), + OPT__QUIET(&quiet, N_("quiet mode")), + OPT_BOOL('u', "include-untracked", &include_untracked, + N_("include untracked files in stash")), + OPT_SET_INT('a', "all", &include_untracked, + N_("include ignore files"), 2), + OPT_STRING('m', "message", &stash_msg, N_("message"), + N_("stash message")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_push_usage, + 0); + + parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv); + return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, + include_untracked); +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -1243,6 +1470,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!store_stash(argc, argv, prefix); else if (!strcmp(argv[0], "create")) return !!create_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "push")) + return !!push_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index ab06e4ffb8a62c..c3146f62ab57d0 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -412,7 +412,8 @@ save) ;; push) shift - push_stash "$@" + cd "$START_DIR" + git stash--helper push "$@" ;; apply) shift @@ -448,7 +449,8 @@ branch) *) case $# in 0) - push_stash && + cd "$START_DIR" + git stash--helper push && say "$(gettext "(To restore them type \"git stash apply\")")" ;; *) From e57a7a0838b458a185bcb62f7490867bf157d6d8 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:46 +0300 Subject: [PATCH 16/93] stash: make push -q quiet There is a change in behaviour with this commit. When there was no initial commit, the shell version of stash would still display a message. This commit makes `push` to not display any message if `--quiet` or `-q` is specified. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 47 ++++++++++++++++++++++++++--------------- t/t3903-stash.sh | 23 ++++++++++++++++++++ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 5c5660189cd99c..7e65058f0be18a 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -939,7 +939,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, static struct strbuf patch = STRBUF_INIT; -static int stash_patch(struct stash_info *info, struct pathspec ps) +static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) { int i; int ret = 0; @@ -992,7 +992,8 @@ static int stash_patch(struct stash_info *info, struct pathspec ps) } if (!patch.len) { - fprintf_ln(stderr, _("No changes selected")); + if (!quiet) + fprintf_ln(stderr, _("No changes selected")); ret = 1; } @@ -1070,7 +1071,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) static int do_create_stash(struct pathspec ps, const char **stash_msg, int include_untracked, int patch_mode, - struct stash_info *info) + struct stash_info *info, int quiet) { int untracked_commit_option = 0; int ret = 0; @@ -1095,7 +1096,8 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, } if (get_oid("HEAD", &info->b_commit)) { - fprintf_ln(stderr, _("You do not have the initial commit yet")); + if (!quiet) + fprintf_ln(stderr, _("You do not have the initial commit yet")); ret = -1; *stash_msg = NULL; goto done; @@ -1116,7 +1118,8 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, if (write_cache_as_tree(&info->i_tree, 0, NULL) || commit_tree(commit_tree_label.buf, commit_tree_label.len, &info->i_tree, parents, &info->i_commit, NULL, NULL)) { - fprintf_ln(stderr, _("Cannot save the current index state")); + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current index state")); ret = -1; *stash_msg = NULL; goto done; @@ -1125,7 +1128,8 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, if (include_untracked && get_untracked_files(ps, include_untracked, &out)) { if (save_untracked_files(info, &msg, &out)) { - fprintf_ln(stderr, _("Cannot save the untracked files")); + if (!quiet) + fprintf_ln(stderr, _("Cannot save the untracked files")); ret = -1; *stash_msg = NULL; goto done; @@ -1133,17 +1137,19 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps); + ret = stash_patch(info, ps, quiet); *stash_msg = NULL; if (ret < 0) { - fprintf_ln(stderr, _("Cannot save the current worktree state")); + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current worktree state")); goto done; } else if (ret > 0) { goto done; } } else { if (stash_working_tree(info, ps)) { - fprintf_ln(stderr, _("Cannot save the current worktree state")); + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current worktree state")); ret = -1; *stash_msg = NULL; goto done; @@ -1169,7 +1175,8 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, if (commit_tree(*stash_msg, strlen(*stash_msg), &info->w_tree, parents, &info->w_commit, NULL, NULL)) { - fprintf_ln(stderr, _("Cannot record working tree state")); + if (!quiet) + fprintf_ln(stderr, _("Cannot record working tree state")); ret = -1; goto done; } @@ -1202,7 +1209,7 @@ static int create_stash(int argc, const char **argv, const char *prefix) 0); memset(&ps, 0, sizeof(ps)); - ret = do_create_stash(ps, &stash_msg, include_untracked, 0, &info); + ret = do_create_stash(ps, &stash_msg, include_untracked, 0, &info, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1256,28 +1263,33 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, return -1; if (!check_changes(ps, include_untracked)) { - printf_ln(_("No local changes to save")); + if (!quiet) + printf_ln(_("No local changes to save")); return 0; } if (!reflog_exists(ref_stash) && do_clear_stash()) { - fprintf_ln(stderr, _("Cannot initialize stash")); + if (!quiet) + fprintf_ln(stderr, _("Cannot initialize stash")); return -1; } if (do_create_stash(ps, &stash_msg, include_untracked, patch_mode, - &info)) { + &info, quiet)) { ret = -1; goto done; } if (do_store_stash(oid_to_hex(&info.w_commit), stash_msg, 1)) { - fprintf(stderr, _("Cannot save the current status")); + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current status")); ret = -1; goto done; } - printf_ln(_("Saved working directory and index state %s"), stash_msg); + if (!quiet) + printf_ln(_("Saved working directory and index state %s"), + stash_msg); if (!patch_mode) { if (include_untracked && !ps.nr) { @@ -1376,7 +1388,8 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, argv_array_pushl(&cp.args, "apply", "-R", NULL); if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) { - fprintf_ln(stderr, _("Cannot remove worktree changes")); + if (!quiet) + fprintf_ln(stderr, _("Cannot remove worktree changes")); ret = -1; goto done; } diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 4fe3717675b742..241d06f47b48dc 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1064,6 +1064,29 @@ test_expect_success 'push: not in the repository errors out' ' test_path_is_file untracked ' +test_expect_success 'push: -q is quiet with changes' ' + >foo && + git add foo && + git stash push -q >output 2>&1 && + test_must_be_empty output +' + +test_expect_success 'push: -q is quiet with no changes' ' + git stash push -q >output 2>&1 && + test_must_be_empty output +' + +test_expect_success 'push: -q is quiet even if there is no initial commit' ' + git init foo_dir && + test_when_finished rm -rf foo_dir && + ( + cd foo_dir && + >bar && + test_must_fail git stash push -q >output 2>&1 && + test_must_be_empty output + ) +' + test_expect_success 'untracked files are left in place when -u is not given' ' >file && git add file && From 8fba8f5a1d74461878083b7c5251aa7f4e7a9414 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:47 +0300 Subject: [PATCH 17/93] stash: convert save to builtin Add stash save to the helper and delete functions which are no longer needed (`show_help()`, `save_stash()`, `push_stash()`, `create_stash()`, `clear_stash()`, `untracked_files()` and `no_changes()`). The `-m` option is no longer supported as it might not make sense to have two ways of passing a message. Even if this is a change in behaviour, the documentation remains the same because the `-m` parameter was omitted before. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 52 +++++++ git-stash.sh | 311 +--------------------------------------- 2 files changed, 54 insertions(+), 309 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 7e65058f0be18a..08e2dc8cead4ff 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -24,6 +24,8 @@ static const char * const git_stash_helper_usage[] = { N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" " [--] [...]]"), + N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] []"), NULL }; @@ -79,6 +81,12 @@ static const char * const git_stash_helper_push_usage[] = { NULL }; +static const char * const git_stash_helper_save_usage[] = { + N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + " [-u|--include-untracked] [-a|--all] []"), + NULL +}; + static const char *ref_stash = "refs/stash"; static int quiet; static struct strbuf stash_index_path = STRBUF_INIT; @@ -1445,6 +1453,48 @@ static int push_stash(int argc, const char **argv, const char *prefix) include_untracked); } +static int save_stash(int argc, const char **argv, const char *prefix) +{ + int i; + int keep_index = -1; + int patch_mode = 0; + int include_untracked = 0; + int quiet = 0; + int ret = 0; + const char *stash_msg = NULL; + char *to_free = NULL; + struct strbuf stash_msg_buf = STRBUF_INIT; + struct pathspec ps; + struct option options[] = { + OPT_SET_INT('k', "keep-index", &keep_index, + N_("keep index"), 1), + OPT_BOOL('p', "patch", &patch_mode, + N_("stash in patch mode")), + OPT__QUIET(&quiet, N_("quiet mode")), + OPT_BOOL('u', "include-untracked", &include_untracked, + N_("include untracked files in stash")), + OPT_SET_INT('a', "all", &include_untracked, + N_("include ignore files"), 2), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_save_usage, + 0); + + for (i = 0; i < argc; ++i) + strbuf_addf(&stash_msg_buf, "%s ", argv[i]); + stash_msg = strbuf_detach(&stash_msg_buf, NULL); + to_free = (char *) stash_msg; + + memset(&ps, 0, sizeof(ps)); + ret = do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, + include_untracked); + + free(to_free); + return ret; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -1485,6 +1535,8 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!create_stash(argc, argv, prefix); else if (!strcmp(argv[0], "push")) return !!push_stash(argc, argv, prefix); + else if (!strcmp(argv[0], "save")) + return !!save_stash(argc, argv, prefix); usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_helper_usage, options); diff --git a/git-stash.sh b/git-stash.sh index c3146f62ab57d0..695f1feba3dde8 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -36,314 +36,6 @@ else reset_color= fi -no_changes () { - git diff-index --quiet --cached HEAD --ignore-submodules -- "$@" && - git diff-files --quiet --ignore-submodules -- "$@" && - (test -z "$untracked" || test -z "$(untracked_files "$@")") -} - -untracked_files () { - if test "$1" = "-z" - then - shift - z=-z - else - z= - fi - excl_opt=--exclude-standard - test "$untracked" = "all" && excl_opt= - git ls-files -o $z $excl_opt -- "$@" -} - -clear_stash () { - if test $# != 0 - then - die "$(gettext "git stash clear with parameters is unimplemented")" - fi - if current=$(git rev-parse --verify --quiet $ref_stash) - then - git update-ref -d $ref_stash $current - fi -} - -create_stash () { - stash_msg= - untracked= - while test $# != 0 - do - case "$1" in - -m|--message) - shift - stash_msg=${1?"BUG: create_stash () -m requires an argument"} - ;; - -m*) - stash_msg=${1#-m} - ;; - --message=*) - stash_msg=${1#--message=} - ;; - -u|--include-untracked) - shift - untracked=${1?"BUG: create_stash () -u requires an argument"} - ;; - --) - shift - break - ;; - esac - shift - done - - git update-index -q --refresh - if no_changes "$@" - then - exit 0 - fi - - # state of the base commit - if b_commit=$(git rev-parse --verify HEAD) - then - head=$(git rev-list --oneline -n 1 HEAD --) - else - die "$(gettext "You do not have the initial commit yet")" - fi - - if branch=$(git symbolic-ref -q HEAD) - then - branch=${branch#refs/heads/} - else - branch='(no branch)' - fi - msg=$(printf '%s: %s' "$branch" "$head") - - # state of the index - i_tree=$(git write-tree) && - i_commit=$(printf 'index on %s\n' "$msg" | - git commit-tree $i_tree -p $b_commit) || - die "$(gettext "Cannot save the current index state")" - - if test -n "$untracked" - then - # Untracked files are stored by themselves in a parentless commit, for - # ease of unpacking later. - u_commit=$( - untracked_files -z "$@" | ( - GIT_INDEX_FILE="$TMPindex" && - export GIT_INDEX_FILE && - rm -f "$TMPindex" && - git update-index -z --add --remove --stdin && - u_tree=$(git write-tree) && - printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree && - rm -f "$TMPindex" - ) ) || die "$(gettext "Cannot save the untracked files")" - - untracked_commit_option="-p $u_commit"; - else - untracked_commit_option= - fi - - if test -z "$patch_mode" - then - - # state of the working tree - w_tree=$( ( - git read-tree --index-output="$TMPindex" -m $i_tree && - GIT_INDEX_FILE="$TMPindex" && - export GIT_INDEX_FILE && - git diff-index --name-only -z HEAD -- "$@" >"$TMP-stagenames" && - git update-index -z --add --remove --stdin <"$TMP-stagenames" && - git write-tree && - rm -f "$TMPindex" - ) ) || - die "$(gettext "Cannot save the current worktree state")" - - else - - rm -f "$TMP-index" && - GIT_INDEX_FILE="$TMP-index" git read-tree HEAD && - - # find out what the user wants - GIT_INDEX_FILE="$TMP-index" \ - git add--interactive --patch=stash -- "$@" && - - # state of the working tree - w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) || - die "$(gettext "Cannot save the current worktree state")" - - git diff-tree -p HEAD $w_tree -- >"$TMP-patch" && - test -s "$TMP-patch" || - die "$(gettext "No changes selected")" - - rm -f "$TMP-index" || - die "$(gettext "Cannot remove temporary index (can't happen)")" - - fi - - # create the stash - if test -z "$stash_msg" - then - stash_msg=$(printf 'WIP on %s' "$msg") - else - stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg") - fi - w_commit=$(printf '%s\n' "$stash_msg" | - git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) || - die "$(gettext "Cannot record working tree state")" -} - -push_stash () { - keep_index= - patch_mode= - untracked= - stash_msg= - while test $# != 0 - do - case "$1" in - -k|--keep-index) - keep_index=t - ;; - --no-keep-index) - keep_index=n - ;; - -p|--patch) - patch_mode=t - # only default to keep if we don't already have an override - test -z "$keep_index" && keep_index=t - ;; - -q|--quiet) - GIT_QUIET=t - ;; - -u|--include-untracked) - untracked=untracked - ;; - -a|--all) - untracked=all - ;; - -m|--message) - shift - test -z ${1+x} && usage - stash_msg=$1 - ;; - -m*) - stash_msg=${1#-m} - ;; - --message=*) - stash_msg=${1#--message=} - ;; - --help) - show_help - ;; - --) - shift - break - ;; - -*) - option="$1" - eval_gettextln "error: unknown option for 'stash push': \$option" - usage - ;; - *) - break - ;; - esac - shift - done - - eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")" - - if test -n "$patch_mode" && test -n "$untracked" - then - die "$(gettext "Can't use --patch and --include-untracked or --all at the same time")" - fi - - test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1 - - git update-index -q --refresh - if no_changes "$@" - then - say "$(gettext "No local changes to save")" - exit 0 - fi - - git reflog exists $ref_stash || - clear_stash || die "$(gettext "Cannot initialize stash")" - - create_stash -m "$stash_msg" -u "$untracked" -- "$@" - git stash--helper store -m "$stash_msg" -q $w_commit || - die "$(gettext "Cannot save the current status")" - say "$(eval_gettext "Saved working directory and index state \$stash_msg")" - - if test -z "$patch_mode" - then - test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION= - if test -n "$untracked" && test $# = 0 - then - git clean --force --quiet -d $CLEAN_X_OPTION - fi - - if test $# != 0 - then - test -z "$untracked" && UPDATE_OPTION="-u" || UPDATE_OPTION= - test "$untracked" = "all" && FORCE_OPTION="--force" || FORCE_OPTION= - git add $UPDATE_OPTION $FORCE_OPTION -- "$@" - git diff-index -p --cached --binary HEAD -- "$@" | - git apply --index -R - else - git reset --hard -q - fi - - if test "$keep_index" = "t" && test -n "$i_tree" - then - git read-tree --reset $i_tree - git ls-files -z --modified -- "$@" | - git checkout-index -z --force --stdin - fi - else - git apply -R < "$TMP-patch" || - die "$(gettext "Cannot remove worktree changes")" - - if test "$keep_index" != "t" - then - git reset -q -- "$@" - fi - fi -} - -save_stash () { - push_options= - while test $# != 0 - do - case "$1" in - --) - shift - break - ;; - -*) - # pass all options through to push_stash - push_options="$push_options $1" - ;; - *) - break - ;; - esac - shift - done - - stash_msg="$*" - - if test -z "$stash_msg" - then - push_stash $push_options - else - push_stash $push_options -m "$stash_msg" - fi -} - -show_help () { - exec git help stash - exit 1 -} - # # Parses the remaining options looking for flags and # at most one revision defaulting to ${ref_stash}@{0} @@ -408,7 +100,8 @@ show) ;; save) shift - save_stash "$@" + cd "$START_DIR" + git stash--helper save "$@" ;; push) shift From df329b058d42698ddff1254cedc8c53940e73b4a Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:48 +0300 Subject: [PATCH 18/93] stash: convert `stash--helper.c` into `stash.c` The old shell script `git-stash.sh` was removed and replaced entirely by `builtin/stash.c`. In order to do that, `create` and `push` were adapted to work without `stash.sh`. For example, before this commit, `git stash create` called `git stash--helper create --message "$*"`. If it called `git stash--helper create "$@"`, then some of these changes wouldn't have been necessary. This commit also removes the word `helper` since now stash is called directly and not by a shell script. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- .gitignore | 1 - Makefile | 3 +- builtin.h | 2 +- builtin/{stash--helper.c => stash.c} | 161 +++++++++++++++++---------- git-stash.sh | 153 ------------------------- git.c | 2 +- 6 files changed, 106 insertions(+), 216 deletions(-) rename builtin/{stash--helper.c => stash.c} (90%) delete mode 100755 git-stash.sh diff --git a/.gitignore b/.gitignore index b59661cb884688..ffceea7d59fd21 100644 --- a/.gitignore +++ b/.gitignore @@ -157,7 +157,6 @@ /git-show-ref /git-stage /git-stash -/git-stash--helper /git-status /git-stripspace /git-submodule diff --git a/Makefile b/Makefile index 482196f4718f45..f064d5eed3f1ab 100644 --- a/Makefile +++ b/Makefile @@ -617,7 +617,6 @@ SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh -SCRIPT_SH += git-stash.sh SCRIPT_SH += git-submodule.sh SCRIPT_SH += git-web--browse.sh @@ -1093,7 +1092,7 @@ BUILTIN_OBJS += builtin/shortlog.o BUILTIN_OBJS += builtin/show-branch.o BUILTIN_OBJS += builtin/show-index.o BUILTIN_OBJS += builtin/show-ref.o -BUILTIN_OBJS += builtin/stash--helper.o +BUILTIN_OBJS += builtin/stash.o BUILTIN_OBJS += builtin/stripspace.o BUILTIN_OBJS += builtin/submodule--helper.o BUILTIN_OBJS += builtin/symbolic-ref.o diff --git a/builtin.h b/builtin.h index 317bc338f729f2..e60f0fc58ffb97 100644 --- a/builtin.h +++ b/builtin.h @@ -223,7 +223,7 @@ extern int cmd_show(int argc, const char **argv, const char *prefix); extern int cmd_show_branch(int argc, const char **argv, const char *prefix); extern int cmd_show_index(int argc, const char **argv, const char *prefix); extern int cmd_status(int argc, const char **argv, const char *prefix); -extern int cmd_stash__helper(int argc, const char **argv, const char *prefix); +extern int cmd_stash(int argc, const char **argv, const char *prefix); extern int cmd_stripspace(int argc, const char **argv, const char *prefix); extern int cmd_submodule__helper(int argc, const char **argv, const char *prefix); extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); diff --git a/builtin/stash--helper.c b/builtin/stash.c similarity index 90% rename from builtin/stash--helper.c rename to builtin/stash.c index 08e2dc8cead4ff..f542487c5c7fa8 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash.c @@ -14,75 +14,75 @@ #include "log-tree.h" #include "diffcore.h" -static const char * const git_stash_helper_usage[] = { - N_("git stash--helper list []"), - N_("git stash--helper show [] []"), - N_("git stash--helper drop [-q|--quiet] []"), - N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] []"), - N_("git stash--helper branch []"), - N_("git stash--helper clear"), - N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" +static const char * const git_stash_usage[] = { + N_("git stash list []"), + N_("git stash show [] []"), + N_("git stash drop [-q|--quiet] []"), + N_("git stash ( pop | apply ) [--index] [-q|--quiet] []"), + N_("git stash branch []"), + N_("git stash clear"), + N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" " [--] [...]]"), - N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] []"), NULL }; -static const char * const git_stash_helper_list_usage[] = { - N_("git stash--helper list []"), +static const char * const git_stash_list_usage[] = { + N_("git stash list []"), NULL }; -static const char * const git_stash_helper_show_usage[] = { - N_("git stash--helper show [] []"), +static const char * const git_stash_show_usage[] = { + N_("git stash show [] []"), NULL }; -static const char * const git_stash_helper_drop_usage[] = { - N_("git stash--helper drop [-q|--quiet] []"), +static const char * const git_stash_drop_usage[] = { + N_("git stash drop [-q|--quiet] []"), NULL }; -static const char * const git_stash_helper_pop_usage[] = { - N_("git stash--helper pop [--index] [-q|--quiet] []"), +static const char * const git_stash_pop_usage[] = { + N_("git stash pop [--index] [-q|--quiet] []"), NULL }; -static const char * const git_stash_helper_apply_usage[] = { - N_("git stash--helper apply [--index] [-q|--quiet] []"), +static const char * const git_stash_apply_usage[] = { + N_("git stash apply [--index] [-q|--quiet] []"), NULL }; -static const char * const git_stash_helper_branch_usage[] = { - N_("git stash--helper branch []"), +static const char * const git_stash_branch_usage[] = { + N_("git stash branch []"), NULL }; -static const char * const git_stash_helper_clear_usage[] = { - N_("git stash--helper clear"), +static const char * const git_stash_clear_usage[] = { + N_("git stash clear"), NULL }; -static const char * const git_stash_helper_store_usage[] = { - N_("git stash--helper store [-m|--message ] [-q|--quiet] "), +static const char * const git_stash_store_usage[] = { + N_("git stash store [-m|--message ] [-q|--quiet] "), NULL }; -static const char * const git_stash_helper_create_usage[] = { - N_("git stash--helper create []"), +static const char * const git_stash_create_usage[] = { + N_("git stash create []"), NULL }; -static const char * const git_stash_helper_push_usage[] = { - N_("git stash--helper [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" +static const char * const git_stash_push_usage[] = { + N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" " [--] [...]]"), NULL }; -static const char * const git_stash_helper_save_usage[] = { - N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" +static const char * const git_stash_save_usage[] = { + N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] []"), NULL }; @@ -222,7 +222,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_clear_usage, + git_stash_clear_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (argc) @@ -518,7 +518,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_apply_usage, 0); + git_stash_apply_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; @@ -590,7 +590,7 @@ static int drop_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_drop_usage, 0); + git_stash_drop_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; @@ -614,7 +614,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_pop_usage, 0); + git_stash_pop_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; @@ -640,7 +640,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_branch_usage, 0); + git_stash_branch_usage, 0); if (!argc) return error(_("No branch name specified")); @@ -673,7 +673,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_list_usage, + git_stash_list_usage, PARSE_OPT_KEEP_UNKNOWN); if (!ref_exists(ref_stash)) @@ -753,7 +753,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); if (argc > 1) { free_stash_info(&info); - usage_with_options(git_stash_helper_show_usage, options); + usage_with_options(git_stash_show_usage, options); } rev.diffopt.flags.recursive = 1; @@ -802,7 +802,7 @@ static int store_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_store_usage, + git_stash_store_usage, PARSE_OPT_KEEP_UNKNOWN); if (argc != 1) { @@ -1199,34 +1199,39 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, static int create_stash(int argc, const char **argv, const char *prefix) { - int include_untracked = 0; + int i; int ret = 0; + char *to_free = NULL; const char *stash_msg = NULL; struct stash_info info; struct pathspec ps; + struct strbuf stash_msg_buf = STRBUF_INIT; struct option options[] = { - OPT_BOOL('u', "include-untracked", &include_untracked, - N_("include untracked files in stash")), - OPT_STRING('m', "message", &stash_msg, N_("message"), - N_("stash message")), OPT_END() }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_create_usage, + git_stash_create_usage, 0); + for (i = 0; i < argc; ++i) + strbuf_addf(&stash_msg_buf, "%s ", argv[i]); + stash_msg = strbuf_detach(&stash_msg_buf, NULL); + to_free = (char *) stash_msg; + memset(&ps, 0, sizeof(ps)); - ret = do_create_stash(ps, &stash_msg, include_untracked, 0, &info, 0); + ret = do_create_stash(ps, &stash_msg, 0, 0, &info, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); + free(to_free); + free((char *) stash_msg); + /* * ret can be 1 if there were no changes. In this case, we should * not error out. */ - free((char *) stash_msg); return ret < 0; } @@ -1444,9 +1449,10 @@ static int push_stash(int argc, const char **argv, const char *prefix) OPT_END() }; - argc = parse_options(argc, argv, prefix, options, - git_stash_helper_push_usage, - 0); + if (argc) + argc = parse_options(argc, argv, prefix, options, + git_stash_push_usage, + 0); parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv); return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, @@ -1479,7 +1485,7 @@ static int save_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_helper_save_usage, + git_stash_save_usage, 0); for (i = 0; i < argc; ++i) @@ -1495,10 +1501,12 @@ static int save_stash(int argc, const char **argv, const char *prefix) return ret; } -int cmd_stash__helper(int argc, const char **argv, const char *prefix) +int cmd_stash(int argc, const char **argv, const char *prefix) { + int i = -1; pid_t pid = getpid(); const char *index_file; + struct argv_array args = ARGV_ARRAY_INIT; struct option options[] = { OPT_END() @@ -1506,16 +1514,16 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage, + argc = parse_options(argc, argv, prefix, options, git_stash_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); index_file = get_index_file(); strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, (uintmax_t)pid); - if (argc < 1) - usage_with_options(git_stash_helper_usage, options); - if (!strcmp(argv[0], "apply")) + if (argc == 0) + return !!push_stash(0, NULL, prefix); + else if (!strcmp(argv[0], "apply")) return !!apply_stash(argc, argv, prefix); else if (!strcmp(argv[0], "clear")) return !!clear_stash(argc, argv, prefix); @@ -1537,7 +1545,44 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) return !!push_stash(argc, argv, prefix); else if (!strcmp(argv[0], "save")) return !!save_stash(argc, argv, prefix); + else if (*argv[0] != '-') + usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), + git_stash_usage, options); + + if (strcmp(argv[0], "-p")) { + while (++i < argc && strcmp(argv[i], "--")) { + /* + * `akpqu` is a string which contains all short options, + * except `-m` which is verified separately. + */ + if ((strlen(argv[i]) == 2) && *argv[i] == '-' && + strchr("akpqu", argv[i][1])) + continue; + + if (!strcmp(argv[i], "--all") || + !strcmp(argv[i], "--keep-index") || + !strcmp(argv[i], "--no-keep-index") || + !strcmp(argv[i], "--patch") || + !strcmp(argv[i], "--quiet") || + !strcmp(argv[i], "--include-untracked")) + continue; + + /* + * `-m` and `--message=` are verified separately because + * they need to be immediately followed by a string + * (i.e.`-m"foobar"` or `--message="foobar"`). + */ + if ((strlen(argv[i]) > 2 && + !strncmp(argv[i], "-m", 2)) || + (strlen(argv[i]) > 10 && + !strncmp(argv[i], "--message=", 10))) + continue; + + usage_with_options(git_stash_usage, options); + } + } - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_helper_usage, options); + argv_array_push(&args, "push"); + argv_array_pushv(&args, argv); + return !!push_stash(args.argc, args.argv, prefix); } diff --git a/git-stash.sh b/git-stash.sh deleted file mode 100755 index 695f1feba3dde8..00000000000000 --- a/git-stash.sh +++ /dev/null @@ -1,153 +0,0 @@ -#!/bin/sh -# Copyright (c) 2007, Nanako Shiraishi - -dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="list [] - or: $dashless show [] - or: $dashless drop [-q|--quiet] [] - or: $dashless ( pop | apply ) [--index] [-q|--quiet] [] - or: $dashless branch [] - or: $dashless save [--patch] [-k|--[no-]keep-index] [-q|--quiet] - [-u|--include-untracked] [-a|--all] [] - or: $dashless [push [--patch] [-k|--[no-]keep-index] [-q|--quiet] - [-u|--include-untracked] [-a|--all] [-m ] - [-- ...]] - or: $dashless clear" - -SUBDIRECTORY_OK=Yes -OPTIONS_SPEC= -START_DIR=$(pwd) -. git-sh-setup -require_work_tree -prefix=$(git rev-parse --show-prefix) || exit 1 -cd_to_toplevel - -TMP="$GIT_DIR/.git-stash.$$" -TMPindex=${GIT_INDEX_FILE-"$(git rev-parse --git-path index)"}.stash.$$ -trap 'rm -f "$TMP-"* "$TMPindex"' 0 - -ref_stash=refs/stash - -if git config --get-colorbool color.interactive; then - help_color="$(git config --get-color color.interactive.help 'red bold')" - reset_color="$(git config --get-color '' reset)" -else - help_color= - reset_color= -fi - -# -# Parses the remaining options looking for flags and -# at most one revision defaulting to ${ref_stash}@{0} -# if none found. -# -# Derives related tree and commit objects from the -# revision, if one is found. -# -# stash records the work tree, and is a merge between the -# base commit (first parent) and the index tree (second parent). -# -# REV is set to the symbolic version of the specified stash-like commit -# IS_STASH_LIKE is non-blank if ${REV} looks like a stash -# IS_STASH_REF is non-blank if the ${REV} looks like a stash ref -# s is set to the SHA1 of the stash commit -# w_commit is set to the commit containing the working tree -# b_commit is set to the base commit -# i_commit is set to the commit containing the index tree -# u_commit is set to the commit containing the untracked files tree -# w_tree is set to the working tree -# b_tree is set to the base tree -# i_tree is set to the index tree -# u_tree is set to the untracked files tree -# -# GIT_QUIET is set to t if -q is specified -# INDEX_OPTION is set to --index if --index is specified. -# FLAGS is set to the remaining flags (if allowed) -# -# dies if: -# * too many revisions specified -# * no revision is specified and there is no stash stack -# * a revision is specified which cannot be resolve to a SHA1 -# * a non-existent stash reference is specified -# * unknown flags were set and ALLOW_UNKNOWN_FLAGS is not "t" -# - -test "$1" = "-p" && set "push" "$@" - -PARSE_CACHE='--not-parsed' -# The default command is "push" if nothing but options are given -seen_non_option= -for opt -do - case "$opt" in - --) break ;; - -*) ;; - *) seen_non_option=t; break ;; - esac -done - -test -n "$seen_non_option" || set "push" "$@" - -# Main command set -case "$1" in -list) - shift - git stash--helper list "$@" - ;; -show) - shift - git stash--helper show "$@" - ;; -save) - shift - cd "$START_DIR" - git stash--helper save "$@" - ;; -push) - shift - cd "$START_DIR" - git stash--helper push "$@" - ;; -apply) - shift - cd "$START_DIR" - git stash--helper apply "$@" - ;; -clear) - shift - git stash--helper clear "$@" - ;; -create) - shift - git stash--helper create --message "$*" - ;; -store) - shift - git stash--helper store "$@" - ;; -drop) - shift - git stash--helper drop "$@" - ;; -pop) - shift - cd "$START_DIR" - git stash--helper pop "$@" - ;; -branch) - shift - cd "$START_DIR" - git stash--helper branch "$@" - ;; -*) - case $# in - 0) - cd "$START_DIR" - git stash--helper push && - say "$(gettext "(To restore them type \"git stash apply\")")" - ;; - *) - usage - esac - ;; -esac diff --git a/git.c b/git.c index 3c0e762d7d2011..78548397cf1771 100644 --- a/git.c +++ b/git.c @@ -544,7 +544,7 @@ static struct cmd_struct commands[] = { { "show-index", cmd_show_index }, { "show-ref", cmd_show_ref, RUN_SETUP }, { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, - { "stash--helper", cmd_stash__helper, RUN_SETUP | NEED_WORK_TREE }, + { "stash", cmd_stash, RUN_SETUP | NEED_WORK_TREE }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT }, From b97be0c06ec3b99a93b9f50ef6e2228a55d3f393 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:49 +0300 Subject: [PATCH 19/93] stash: optimize `get_untracked_files()` and `check_changes()` This commits introduces a optimization by avoiding calling the same functions again. For example, `git stash push -u` would call at some points the following functions: * `check_changes()` (inside `do_push_stash()`) * `do_create_stash()`, which calls: `check_changes()` and `get_untracked_files()` Note that `check_changes()` also calls `get_untracked_files()`. So, `check_changes()` is called 2 times and `get_untracked_files()` 3 times. `get_untracked_files()` has now only two parameters and it will fill a global strbuf called `untracked_files`. The old function `check_changes()` now consists of two functions: `get_untracked_files()` and `check_changes_tracked_files()`. These are the call chains for `push` and `create`: * `push_stash()` -> `do_push_stash()` -> `do_create_stash()` * `create_stash()` -> `do_create_stash()` To prevent calling the same functions over and over again, `check_changes()` inside `do_create_stash()` is now placed in the caller functions (`create_stash()` and `do_push_stash()`). This way `check_changes()` and `get_untracked files()` are called only one time. https://public-inbox.org/git/20180818223329.GJ11326@hank.intra.tgummerer.com/ Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash.c | 73 ++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 40 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index f542487c5c7fa8..196007f09c6f26 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -814,13 +814,15 @@ static int store_stash(int argc, const char **argv, const char *prefix) } /* - * `out` will be filled with the names of untracked files. The return value is: + * `untracked_files` will be filled with the names of untracked files. + * The return value is: * * = 0 if there are not any untracked files * > 0 if there are untracked files */ -static int get_untracked_files(struct pathspec ps, int include_untracked, - struct strbuf *out) +static struct strbuf untracked_files = STRBUF_INIT; + +static int get_untracked_files(struct pathspec ps, int include_untracked) { int max_len; int i; @@ -840,7 +842,7 @@ static int get_untracked_files(struct pathspec ps, int include_untracked, free(ent); continue; } - strbuf_addf(out, "%s%c", ent->name, '\0'); + strbuf_addf(&untracked_files, "%s%c", ent->name, '\0'); free(ent); } @@ -848,23 +850,22 @@ static int get_untracked_files(struct pathspec ps, int include_untracked, free(dir.ignored); clear_directory(&dir); free(seen); - return out->len; + return untracked_files.len; } /* - * The return value of `check_changes()` can be: + * The return value of `check_changes_tracked_files()` can be: * * < 0 if there was an error * = 0 if there are no changes. * > 0 if there are changes. */ -static int check_changes(struct pathspec ps, int include_untracked) + +static int check_changes_tracked_files(struct pathspec ps) { int result; - int ret = 0; struct rev_info rev; struct object_id dummy; - struct strbuf out = STRBUF_INIT; init_revisions(&rev, NULL); rev.prune_data = ps; @@ -891,18 +892,22 @@ static int check_changes(struct pathspec ps, int include_untracked) if (diff_result_code(&rev.diffopt, result)) return 1; - if (include_untracked && get_untracked_files(ps, include_untracked, - &out)) { - strbuf_release(&out); - return 1; - } - - strbuf_release(&out); return 0; } -static int save_untracked_files(struct stash_info *info, struct strbuf *msg, - struct strbuf *in) +static int check_changes(struct pathspec ps, int include_untracked) +{ + int ret = 0; + if (check_changes_tracked_files(ps)) + ret = 1; + + if (include_untracked && get_untracked_files(ps, include_untracked)) + ret = 1; + + return ret; +} + +static int save_untracked_files(struct stash_info *info, struct strbuf *msg) { int ret = 0; struct strbuf untracked_msg = STRBUF_INIT; @@ -917,7 +922,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, stash_index_path.buf); strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); - if (pipe_command(&cp_upd_index, in->buf, in->len, NULL, 0, NULL, 0)) { + if (pipe_command(&cp_upd_index, untracked_files.buf, untracked_files.len, + NULL, 0, NULL, 0)) { ret = -1; goto done; } @@ -1091,18 +1097,11 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, struct commit_list *parents = NULL; struct strbuf msg = STRBUF_INIT; struct strbuf commit_tree_label = STRBUF_INIT; - struct strbuf out = STRBUF_INIT; struct strbuf stash_msg_buf = STRBUF_INIT; read_cache_preload(NULL); refresh_cache(REFRESH_QUIET); - if (!check_changes(ps, include_untracked)) { - ret = 1; - *stash_msg = NULL; - goto done; - } - if (get_oid("HEAD", &info->b_commit)) { if (!quiet) fprintf_ln(stderr, _("You do not have the initial commit yet")); @@ -1133,9 +1132,8 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, goto done; } - if (include_untracked && get_untracked_files(ps, include_untracked, - &out)) { - if (save_untracked_files(info, &msg, &out)) { + if (include_untracked) { + if (save_untracked_files(info, &msg)) { if (!quiet) fprintf_ln(stderr, _("Cannot save the untracked files")); ret = -1; @@ -1192,7 +1190,6 @@ static int do_create_stash(struct pathspec ps, const char **stash_msg, done: strbuf_release(&commit_tree_label); strbuf_release(&msg); - strbuf_release(&out); strbuf_release(&stash_msg_buf); return ret; } @@ -1214,25 +1211,21 @@ static int create_stash(int argc, const char **argv, const char *prefix) git_stash_create_usage, 0); + memset(&ps, 0, sizeof(ps)); + if (!check_changes_tracked_files(ps)) + return 0; + for (i = 0; i < argc; ++i) strbuf_addf(&stash_msg_buf, "%s ", argv[i]); stash_msg = strbuf_detach(&stash_msg_buf, NULL); to_free = (char *) stash_msg; - memset(&ps, 0, sizeof(ps)); - ret = do_create_stash(ps, &stash_msg, 0, 0, &info, 0); - - if (!ret) + if (!(ret = do_create_stash(ps, &stash_msg, 0, 0, &info, 0))) printf_ln("%s", oid_to_hex(&info.w_commit)); free(to_free); free((char *) stash_msg); - - /* - * ret can be 1 if there were no changes. In this case, we should - * not error out. - */ - return ret < 0; + return ret; } static void add_ps_items_to_argv_array(struct argv_array *args, From 5c9bdfeeada771fd7ef9b2c8fa082d1f22eccc19 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 7 Aug 2018 01:16:09 +0545 Subject: [PATCH 20/93] rebase: start implementing it as a builtin This commit imitates the strategy that was used to convert the difftool to a builtin. We start by renaming the shell script `git-rebase.sh` to `git-legacy-rebase.sh` and introduce a `builtin/rebase.c` that simply executes the shell script version, unless the config setting `rebase.useBuiltin` is set to `true`. The motivation behind this is to rewrite all the functionality of the shell script version in the aforementioned `rebase.c`, one by one and be able to conveniently test new features by configuring `rebase.useBuiltin`. In the original difftool conversion, if sane_execvp() that attempts to run the legacy scripted version returned with non-negative status, the command silently exited without doing anything with success, but sane_execvp() should not return with non-negative status in the first place, so we use die() to notice such an abnormal case. We intentionally avoid reading the config directly to avoid messing up the GIT_* environment variables when we need to fall back to exec()ing the shell script. The test of builtin rebase can be done by `git -c rebase.useBuiltin=true rebase ...` Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 3 +- builtin.h | 1 + builtin/rebase.c | 58 +++++++++++++++++++++++++++ git-rebase.sh => git-legacy-rebase.sh | 0 git.c | 6 +++ 6 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 builtin/rebase.c rename git-rebase.sh => git-legacy-rebase.sh (100%) diff --git a/.gitignore b/.gitignore index ffceea7d59fd21..9afa700ba128fc 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-legacy-rebase /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 5a969f5830a410..7e503758a5b223 100644 --- a/Makefile +++ b/Makefile @@ -614,7 +614,7 @@ SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh -SCRIPT_SH += git-rebase.sh +SCRIPT_SH += git-legacy-rebase.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-stash.sh @@ -1073,6 +1073,7 @@ BUILTIN_OBJS += builtin/pull.o BUILTIN_OBJS += builtin/push.o BUILTIN_OBJS += builtin/range-diff.o BUILTIN_OBJS += builtin/read-tree.o +BUILTIN_OBJS += builtin/rebase.o BUILTIN_OBJS += builtin/rebase--helper.o BUILTIN_OBJS += builtin/receive-pack.o BUILTIN_OBJS += builtin/reflog.o diff --git a/builtin.h b/builtin.h index 99206df4bd43fc..db5767a39755ea 100644 --- a/builtin.h +++ b/builtin.h @@ -203,6 +203,7 @@ extern int cmd_pull(int argc, const char **argv, const char *prefix); extern int cmd_push(int argc, const char **argv, const char *prefix); extern int cmd_range_diff(int argc, const char **argv, const char *prefix); extern int cmd_read_tree(int argc, const char **argv, const char *prefix); +extern int cmd_rebase(int argc, const char **argv, const char *prefix); extern int cmd_rebase__helper(int argc, const char **argv, const char *prefix); extern int cmd_receive_pack(int argc, const char **argv, const char *prefix); extern int cmd_reflog(int argc, const char **argv, const char *prefix); diff --git a/builtin/rebase.c b/builtin/rebase.c new file mode 100644 index 00000000000000..c44addb2a4c609 --- /dev/null +++ b/builtin/rebase.c @@ -0,0 +1,58 @@ +/* + * "git rebase" builtin command + * + * Copyright (c) 2018 Pratik Karki + */ + +#include "builtin.h" +#include "run-command.h" +#include "exec-cmd.h" +#include "argv-array.h" +#include "dir.h" + +static int use_builtin_rebase(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "rebase.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) { + strbuf_release(&out); + return 0; + } + + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + +int cmd_rebase(int argc, const char **argv, const char *prefix) +{ + /* + * NEEDSWORK: Once the builtin rebase has been tested enough + * and git-legacy-rebase.sh is retired to contrib/, this preamble + * can be removed. + */ + + if (!use_builtin_rebase()) { + const char *path = mkpath("%s/git-legacy-rebase", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno(_("could not exec %s"), path); + else + BUG("sane_execvp() returned???"); + } + + if (argc != 2) + die(_("Usage: %s "), argv[0]); + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + + die("TODO"); +} diff --git a/git-rebase.sh b/git-legacy-rebase.sh similarity index 100% rename from git-rebase.sh rename to git-legacy-rebase.sh diff --git a/git.c b/git.c index c27c38738b2a9d..adb587f75e6833 100644 --- a/git.c +++ b/git.c @@ -522,6 +522,12 @@ static struct cmd_struct commands[] = { { "push", cmd_push, RUN_SETUP }, { "range-diff", cmd_range_diff, RUN_SETUP | USE_PAGER }, { "read-tree", cmd_read_tree, RUN_SETUP | SUPPORT_SUPER_PREFIX}, + /* + * NEEDSWORK: Until the rebase is independent and needs no redirection + * to rebase shell script this is kept as is, then should be changed to + * RUN_SETUP | NEED_WORK_TREE + */ + { "rebase", cmd_rebase }, { "rebase--helper", cmd_rebase__helper, RUN_SETUP | NEED_WORK_TREE }, { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, From b40f27c1a490a3ac3be5c76583f4eb70337861d5 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Fri, 31 Aug 2018 00:40:50 +0300 Subject: [PATCH 21/93] stash: replace all `write-tree` child processes with API calls This commit replaces spawning `git write-tree` with API calls. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Junio C Hamano --- builtin/stash.c | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 196007f09c6f26..571bac329f1e16 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -911,9 +911,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg) { int ret = 0; struct strbuf untracked_msg = STRBUF_INIT; - struct strbuf out = STRBUF_INIT; struct child_process cp_upd_index = CHILD_PROCESS_INIT; - struct child_process cp_write_tree = CHILD_PROCESS_INIT; + struct index_state istate = { NULL }; cp_upd_index.git_cmd = 1; argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", @@ -928,15 +927,11 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg) goto done; } - cp_write_tree.git_cmd = 1; - argv_array_push(&cp_write_tree.args, "write-tree"); - argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) { + if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0, + NULL)) { ret = -1; goto done; } - get_oid_hex(out.buf, &info->u_tree); if (commit_tree(untracked_msg.buf, untracked_msg.len, &info->u_tree, NULL, &info->u_commit, NULL, NULL)) { @@ -945,8 +940,8 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg) } done: + discard_index(&istate); strbuf_release(&untracked_msg); - strbuf_release(&out); remove_path(stash_index_path.buf); return ret; } @@ -957,11 +952,10 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) { int i; int ret = 0; - struct strbuf out = STRBUF_INIT; struct child_process cp_read_tree = CHILD_PROCESS_INIT; struct child_process cp_add_i = CHILD_PROCESS_INIT; - struct child_process cp_write_tree = CHILD_PROCESS_INIT; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; + struct index_state istate = { NULL }; remove_path(stash_index_path.buf); @@ -986,17 +980,12 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) goto done; } - cp_write_tree.git_cmd = 1; - argv_array_push(&cp_write_tree.args, "write-tree"); - argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) { + if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, + NULL)) { ret = -1; goto done; } - get_oid_hex(out.buf, &info->w_tree); - cp_diff_tree.git_cmd = 1; argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); @@ -1012,7 +1001,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, int quiet) } done: - strbuf_release(&out); + discard_index(&istate); remove_path(stash_index_path.buf); return ret; } @@ -1021,10 +1010,9 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) { int ret = 0; struct child_process cp_upd_index = CHILD_PROCESS_INIT; - struct child_process cp_write_tree = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; struct strbuf diff_output = STRBUF_INIT; struct rev_info rev; + struct index_state istate = { NULL }; set_alternate_index_output(stash_index_path.buf); if (reset_tree(&info->i_tree, 0, 0)) { @@ -1063,20 +1051,15 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) goto done; } - cp_write_tree.git_cmd = 1; - argv_array_push(&cp_write_tree.args, "write-tree"); - argv_array_pushf(&cp_write_tree.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (pipe_command(&cp_write_tree, NULL, 0, &out, 0,NULL, 0)) { + if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, + NULL)) { ret = -1; goto done; } - get_oid_hex(out.buf, &info->w_tree); - done: + discard_index(&istate); UNLEAK(rev); - strbuf_release(&out); object_array_clear(&rev.pending); strbuf_release(&diff_output); remove_path(stash_index_path.buf); From 7ec9d9b12831fbe7c72633a3dfe37b63e181bdbc Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:07 -0700 Subject: [PATCH 22/93] builtin rebase: support --onto The `--onto` option is important, as it allows to rebase a range of commits onto a different base commit (which gave the command its odd name: "rebase"). This commit introduces options parsing so that different options can be added in future commits. Note: As this commit introduces to the parse_options() call (which "eats" argv[0]), the argc is now expected to be lower by one after this patch, compared to before this patch: argv[0] no longer refers to the command name, but to the first (non-option) command-line parameter. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index e695d8a430f155..742ed3149824b8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -16,6 +16,16 @@ #include "cache-tree.h" #include "unpack-trees.h" #include "lockfile.h" +#include "parse-options.h" + +static char const * const builtin_rebase_usage[] = { + N_("git rebase [-i] [options] [--exec ] [--onto ] " + "[] []"), + N_("git rebase [-i] [options] [--exec ] [--onto ] " + "--root []"), + N_("git rebase --continue | --abort | --skip | --edit-todo"), + NULL +}; static GIT_PATH_FUNC(apply_dir, "rebase-apply") static GIT_PATH_FUNC(merge_dir, "rebase-merge") @@ -301,6 +311,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ret, flags; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct option builtin_rebase_options[] = { + OPT_STRING(0, "onto", &options.onto_name, + N_("revision"), + N_("rebase onto given branch instead of upstream")), + OPT_END(), + }; /* * NEEDSWORK: Once the builtin rebase has been tested enough @@ -318,13 +334,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) BUG("sane_execvp() returned???"); } - if (argc != 2) - die(_("Usage: %s "), argv[0]); + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + prefix = setup_git_directory(); trace_repo_setup(prefix); setup_work_tree(); git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, + builtin_rebase_options, + builtin_rebase_usage, 0); + + if (argc > 2) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); switch (options.type) { case REBASE_MERGE: @@ -343,10 +368,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (!options.root) { - if (argc < 2) + if (argc < 1) die("TODO: handle @{upstream}"); else { - options.upstream_name = argv[1]; + options.upstream_name = argv[0]; argc--; argv++; if (!strcmp(options.upstream_name, "-")) @@ -377,7 +402,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * orig_head -- commit object name of tip of the branch before rebasing * head_name -- refs/heads/ or "detached HEAD" */ - if (argc > 1) + if (argc > 0) die("TODO: handle switch_to"); else { /* Do not need to switch branches, we are already on it. */ From 5958227d6e1cc14b60b9333443ce35b1fce3b2f0 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:09 -0700 Subject: [PATCH 23/93] builtin rebase: support `git rebase --onto A...B` This commit implements support for an --onto argument that is actually a "symmetric range" i.e. `...`. The equivalent shell script version of the code offers two different error messages for the cases where there is no merge base vs more than one merge base. Though it would be nice to retain this distinction, dropping it makes it possible to simply use the `get_oid_mb()` function. Besides, it happens rarely in real-world scenarios. Therefore, in the interest of keeping the code less complex, let's just use that function, and live with an error message that does not distinguish between those two error conditions. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 742ed3149824b8..38c496dd10baea 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -17,6 +17,7 @@ #include "unpack-trees.h" #include "lockfile.h" #include "parse-options.h" +#include "commit.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -311,6 +312,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ret, flags; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct object_id merge_base; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -387,7 +389,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - die("TODO"); + if (get_oid_mb(options.onto_name, &merge_base) < 0) + die(_("'%s': need exactly one merge base"), + options.onto_name); + options.onto = lookup_commit_or_die(&merge_base, + options.onto_name); } else { options.onto = peel_committish(options.onto_name); if (!options.onto) From ddae21208b066afbee7dcc303846d3ca8ecf63c5 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:10 -0700 Subject: [PATCH 24/93] builtin rebase: handle the pre-rebase hook and --no-verify This commit converts the equivalent part of the shell script `git-legacy-rebase.sh` to run the pre-rebase hook (unless disabled), and to interrupt the rebase with error if the hook fails. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 38c496dd10baea..b79f9b0a9fe4e7 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -70,6 +70,7 @@ struct rebase_options { const char *state_dir; struct commit *upstream; const char *upstream_name; + const char *upstream_arg; char *head_name; struct object_id orig_head; struct commit *onto; @@ -310,6 +311,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags; + int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; struct object_id merge_base; @@ -317,6 +319,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, + N_("allow pre-rebase hook to run")), OPT_END(), }; @@ -382,6 +386,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.upstream = peel_committish(options.upstream_name); if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); + options.upstream_arg = options.upstream_name; } else die("TODO: upstream for --root"); @@ -430,6 +435,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not resolve HEAD to a revision")); } + /* If a hook exists, give it a chance to interrupt*/ + if (!ok_to_skip_pre_rebase && + run_hook_le(NULL, "pre-rebase", options.upstream_arg, + argc ? argv[0] : NULL, NULL)) + die(_("The pre-rebase hook refused to rebase.")); + strbuf_addf(&msg, "rebase: checkout %s", options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, 1)) die(_("Could not detach HEAD")); From c970bb02ddf327dede99717c61b8f1f30690bc02 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 7 Aug 2018 01:16:10 +0545 Subject: [PATCH 25/93] rebase: refactor common shell functions into their own file The functions present in `git-legacy-rebase.sh` are used by the rebase backends as they are implemented as shell script functions in the `git-rebase--` files. To make the `builtin/rebase.c` work, we have to provide support via a Unix shell script snippet that uses these functions and so, we want to use the rebase backends *directly* from the builtin rebase without going through `git-legacy-rebase.sh`. This commit extracts the functions to a separate file, `git-rebase--common`, that will be read by `git-legacy-rebase.sh` and by the shell script snippets which will be used extensively in the following commits. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + git-legacy-rebase.sh | 69 ++----------------------------------------- git-rebase--common.sh | 68 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 67 deletions(-) create mode 100644 git-rebase--common.sh diff --git a/.gitignore b/.gitignore index 9afa700ba128fc..3a1eae853567f7 100644 --- a/.gitignore +++ b/.gitignore @@ -118,6 +118,7 @@ /git-read-tree /git-rebase /git-rebase--am +/git-rebase--common /git-rebase--helper /git-rebase--interactive /git-rebase--merge diff --git a/Makefile b/Makefile index 7e503758a5b223..318979ab509ef1 100644 --- a/Makefile +++ b/Makefile @@ -624,6 +624,7 @@ SCRIPT_SH += git-web--browse.sh SCRIPT_LIB += git-mergetool--lib SCRIPT_LIB += git-parse-remote SCRIPT_LIB += git-rebase--am +SCRIPT_LIB += git-rebase--common SCRIPT_LIB += git-rebase--interactive SCRIPT_LIB += git-rebase--preserve-merges SCRIPT_LIB += git-rebase--merge diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index 797344764510e0..af2cdfef03d1a7 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -57,12 +57,7 @@ cd_to_toplevel LF=' ' ok_to_skip_pre_rebase= -resolvemsg=" -$(gettext 'Resolve all conflicts manually, mark them as resolved with -"git add/rm ", then run "git rebase --continue". -You can instead skip this commit: run "git rebase --skip". -To abort and get back to the state before "git rebase", run "git rebase --abort".') -" + squash_onto= unset onto unset restrict_revision @@ -102,6 +97,7 @@ case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; *) gpg_sign_opt= ;; esac +. git-rebase--common read_basic_state () { test -f "$state_dir/head-name" && @@ -132,67 +128,6 @@ read_basic_state () { } } -write_basic_state () { - echo "$head_name" > "$state_dir"/head-name && - echo "$onto" > "$state_dir"/onto && - echo "$orig_head" > "$state_dir"/orig-head && - echo "$GIT_QUIET" > "$state_dir"/quiet && - test t = "$verbose" && : > "$state_dir"/verbose - test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy - test -n "$strategy_opts" && echo "$strategy_opts" > \ - "$state_dir"/strategy_opts - test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ - "$state_dir"/allow_rerere_autoupdate - test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt - test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff -} - -output () { - case "$verbose" in - '') - output=$("$@" 2>&1 ) - status=$? - test $status != 0 && printf "%s\n" "$output" - return $status - ;; - *) - "$@" - ;; - esac -} - -move_to_original_branch () { - case "$head_name" in - refs/*) - message="rebase finished: $head_name onto $onto" - git update-ref -m "$message" \ - $head_name $(git rev-parse HEAD) $orig_head && - git symbolic-ref \ - -m "rebase finished: returning to $head_name" \ - HEAD $head_name || - die "$(eval_gettext "Could not move back to \$head_name")" - ;; - esac -} - -apply_autostash () { - if test -f "$state_dir/autostash" - then - stash_sha1=$(cat "$state_dir/autostash") - if git stash apply $stash_sha1 >/dev/null 2>&1 - then - echo "$(gettext 'Applied autostash.')" >&2 - else - git stash store -m "autostash" -q $stash_sha1 || - die "$(eval_gettext "Cannot store \$stash_sha1")" - gettext 'Applying autostash resulted in conflicts. -Your changes are safe in the stash. -You can run "git stash pop" or "git stash drop" at any time. -' >&2 - fi - fi -} - finish_rebase () { rm -f "$(git rev-parse --git-path REBASE_HEAD)" apply_autostash && diff --git a/git-rebase--common.sh b/git-rebase--common.sh new file mode 100644 index 00000000000000..7e39d228717899 --- /dev/null +++ b/git-rebase--common.sh @@ -0,0 +1,68 @@ + +resolvemsg=" +$(gettext 'Resolve all conflicts manually, mark them as resolved with +"git add/rm ", then run "git rebase --continue". +You can instead skip this commit: run "git rebase --skip". +To abort and get back to the state before "git rebase", run "git rebase --abort".') +" + +write_basic_state () { + echo "$head_name" > "$state_dir"/head-name && + echo "$onto" > "$state_dir"/onto && + echo "$orig_head" > "$state_dir"/orig-head && + echo "$GIT_QUIET" > "$state_dir"/quiet && + test t = "$verbose" && : > "$state_dir"/verbose + test -n "$strategy" && echo "$strategy" > "$state_dir"/strategy + test -n "$strategy_opts" && echo "$strategy_opts" > \ + "$state_dir"/strategy_opts + test -n "$allow_rerere_autoupdate" && echo "$allow_rerere_autoupdate" > \ + "$state_dir"/allow_rerere_autoupdate + test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt + test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff +} + +apply_autostash () { + if test -f "$state_dir/autostash" + then + stash_sha1=$(cat "$state_dir/autostash") + if git stash apply $stash_sha1 >/dev/null 2>&1 + then + echo "$(gettext 'Applied autostash.')" >&2 + else + git stash store -m "autostash" -q $stash_sha1 || + die "$(eval_gettext "Cannot store \$stash_sha1")" + gettext 'Applying autostash resulted in conflicts. +Your changes are safe in the stash. +You can run "git stash pop" or "git stash drop" at any time. +' >&2 + fi + fi +} + +move_to_original_branch () { + case "$head_name" in + refs/*) + message="rebase finished: $head_name onto $onto" + git update-ref -m "$message" \ + $head_name $(git rev-parse HEAD) $orig_head && + git symbolic-ref \ + -m "rebase finished: returning to $head_name" \ + HEAD $head_name || + die "$(eval_gettext "Could not move back to \$head_name")" + ;; + esac +} + +output () { + case "$verbose" in + '') + output=$("$@" 2>&1 ) + status=$? + test $status != 0 && printf "%s\n" "$output" + return $status + ;; + *) + "$@" + ;; + esac +} From 28427e510a9c95bb686b208a7913c70c02cff3b1 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:12 -0700 Subject: [PATCH 26/93] builtin rebase: support --quiet This commit introduces a rebase option `--quiet`. While `--quiet` is commonly perceived as opposite to `--verbose`, this is not the case for the rebase command: both `--quiet` and `--verbose` default to `false` if neither `--quiet` nor `--verbose` is present. Despite the default being `false` for both verbose and quiet mode, passing the `--quiet` option will turn off verbose mode, and `--verbose` will turn off quiet mode. This patch introduces the `flags` bit field, with `REBASE_NO_QUIET` as first user (with many more to come). We do *not* use `REBASE_QUIET` here for an important reason: To keep the implementation simple, this commit introduces `--no-quiet` instead of `--quiet`, so that a single `OPT_NEGBIT()` can turn on quiet mode and turn off verbose and diffstat mode at the same time. Likewise, the companion commit which will introduce support for `--verbose` will have a single `OPT_BIT()` that turns off quiet mode and turns on verbose and diffstat mode at the same time. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index b79f9b0a9fe4e7..19fa4d3fc488c9 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -79,6 +79,10 @@ struct rebase_options { int root; struct commit *restrict_revision; int dont_finish_rebase; + enum { + REBASE_NO_QUIET = 1<<0, + } flags; + struct strbuf git_am_opt; }; /* Returns the filename prefixed by the state_dir */ @@ -159,6 +163,9 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "revisions", opts->revisions); add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? oid_to_hex(&opts->restrict_revision->object.oid) : NULL); + add_var(&script_snippet, "GIT_QUIET", + opts->flags & REBASE_NO_QUIET ? "" : "t"); + add_var(&script_snippet, "git_am_opt", opts->git_am_opt.buf); switch (opts->type) { case REBASE_AM: @@ -308,6 +315,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { .type = REBASE_UNSPECIFIED, + .flags = REBASE_NO_QUIET, + .git_am_opt = STRBUF_INIT, }; const char *branch_name; int ret, flags; @@ -321,6 +330,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("rebase onto given branch instead of upstream")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), + OPT_NEGBIT('q', "quiet", &options.flags, + N_("be quiet. implies --no-stat"), + REBASE_NO_QUIET), OPT_END(), }; @@ -357,6 +369,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (!(options.flags & REBASE_NO_QUIET)) + strbuf_addstr(&options.git_am_opt, " -q"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From ffceb50d23c6ba16634a87a3ab940051d2dc85f6 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 7 Aug 2018 01:16:11 +0545 Subject: [PATCH 27/93] builtin/rebase: support running "git rebase " This patch gives life to the skeleton added in the previous patches: With this change, we can perform a elementary rebase (without any options). It can be tested thusly by: git -c rebase.usebuiltin=true rebase HEAD~2 The rebase backends (i.e. the shell script functions defined in `git-rebase--`) are still at work here and the "builtin rebase"'s purpose is simply to parse the options and set everything up so that those rebase backends can do their work. Note: We take an alternative approach here which is *not* to set the environment variables via `run_command_v_opt_cd_env()` because those variables would then be visible by processes spawned from the rebase backends. Instead, we work hard to set them in the shell script snippet. On Windows, some of the tests fail merely due to core.fileMode not being heeded that's why the core.*config variables is parsed here. The `reset_head()` function is currently only used to detach the HEAD to onto by way of starting the rebase, but it offers additional functionality that subsequent patches will need like moving to the original branch (possibly updating it) and also to do the equivalent of `git reset --hard`. The next commits will handle and support all the wonderful rebase options. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 364 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c44addb2a4c609..e695d8a430f155 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -9,6 +9,24 @@ #include "exec-cmd.h" #include "argv-array.h" #include "dir.h" +#include "packfile.h" +#include "refs.h" +#include "quote.h" +#include "config.h" +#include "cache-tree.h" +#include "unpack-trees.h" +#include "lockfile.h" + +static GIT_PATH_FUNC(apply_dir, "rebase-apply") +static GIT_PATH_FUNC(merge_dir, "rebase-merge") + +enum rebase_type { + REBASE_UNSPECIFIED = -1, + REBASE_AM, + REBASE_MERGE, + REBASE_INTERACTIVE, + REBASE_PRESERVE_MERGES +}; static int use_builtin_rebase(void) { @@ -30,8 +48,260 @@ static int use_builtin_rebase(void) return ret; } +static int apply_autostash(void) +{ + warning("TODO"); + return 0; +} + +struct rebase_options { + enum rebase_type type; + const char *state_dir; + struct commit *upstream; + const char *upstream_name; + char *head_name; + struct object_id orig_head; + struct commit *onto; + const char *onto_name; + const char *revisions; + int root; + struct commit *restrict_revision; + int dont_finish_rebase; +}; + +/* Returns the filename prefixed by the state_dir */ +static const char *state_dir_path(const char *filename, struct rebase_options *opts) +{ + static struct strbuf path = STRBUF_INIT; + static size_t prefix_len; + + if (!prefix_len) { + strbuf_addf(&path, "%s/", opts->state_dir); + prefix_len = path.len; + } + + strbuf_setlen(&path, prefix_len); + strbuf_addstr(&path, filename); + return path.buf; +} + +static int finish_rebase(struct rebase_options *opts) +{ + struct strbuf dir = STRBUF_INIT; + const char *argv_gc_auto[] = { "gc", "--auto", NULL }; + + delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); + apply_autostash(); + close_all_packs(the_repository->objects); + /* + * We ignore errors in 'gc --auto', since the + * user should see them. + */ + run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + strbuf_addstr(&dir, opts->state_dir); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); + + return 0; +} + +static struct commit *peel_committish(const char *name) +{ + struct object *obj; + struct object_id oid; + + if (get_oid(name, &oid)) + return NULL; + obj = parse_object(the_repository, &oid); + return (struct commit *)peel_to_type(name, 0, obj, OBJ_COMMIT); +} + +static void add_var(struct strbuf *buf, const char *name, const char *value) +{ + if (!value) + strbuf_addf(buf, "unset %s; ", name); + else { + strbuf_addf(buf, "%s=", name); + sq_quote_buf(buf, value); + strbuf_addstr(buf, "; "); + } +} + +static int run_specific_rebase(struct rebase_options *opts) +{ + const char *argv[] = { NULL, NULL }; + struct strbuf script_snippet = STRBUF_INIT; + int status; + const char *backend, *backend_func; + + add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir())); + add_var(&script_snippet, "state_dir", opts->state_dir); + + add_var(&script_snippet, "upstream_name", opts->upstream_name); + add_var(&script_snippet, "upstream", + oid_to_hex(&opts->upstream->object.oid)); + add_var(&script_snippet, "head_name", opts->head_name); + add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head)); + add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid)); + add_var(&script_snippet, "onto_name", opts->onto_name); + add_var(&script_snippet, "revisions", opts->revisions); + add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? + oid_to_hex(&opts->restrict_revision->object.oid) : NULL); + + switch (opts->type) { + case REBASE_AM: + backend = "git-rebase--am"; + backend_func = "git_rebase__am"; + break; + case REBASE_INTERACTIVE: + backend = "git-rebase--interactive"; + backend_func = "git_rebase__interactive"; + break; + case REBASE_MERGE: + backend = "git-rebase--merge"; + backend_func = "git_rebase__merge"; + break; + case REBASE_PRESERVE_MERGES: + backend = "git-rebase--preserve-merges"; + backend_func = "git_rebase__preserve_merges"; + break; + default: + BUG("Unhandled rebase type %d", opts->type); + break; + } + + strbuf_addf(&script_snippet, + ". git-sh-setup && . git-rebase--common &&" + " . %s && %s", backend, backend_func); + argv[0] = script_snippet.buf; + + status = run_command_v_opt(argv, RUN_USING_SHELL); + if (opts->dont_finish_rebase) + ; /* do nothing */ + else if (status == 0) { + if (!file_exists(state_dir_path("stopped-sha", opts))) + finish_rebase(opts); + } else if (status == 2) { + struct strbuf dir = STRBUF_INIT; + + apply_autostash(); + strbuf_addstr(&dir, opts->state_dir); + remove_dir_recursively(&dir, 0); + strbuf_release(&dir); + die("Nothing to do"); + } + + strbuf_release(&script_snippet); + + return status ? -1 : 0; +} + +#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" + +static int reset_head(struct object_id *oid, const char *action, + const char *switch_to_branch, int detach_head) +{ + struct object_id head_oid; + struct tree_desc desc; + struct lock_file lock = LOCK_INIT; + struct unpack_trees_options unpack_tree_opts; + struct tree *tree; + const char *reflog_action; + struct strbuf msg = STRBUF_INIT; + size_t prefix_len; + struct object_id *orig = NULL, oid_orig, + *old_orig = NULL, oid_old_orig; + int ret = 0; + + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) + return -1; + + if (!oid) { + if (get_oid("HEAD", &head_oid)) { + rollback_lock_file(&lock); + return error(_("could not determine HEAD revision")); + } + oid = &head_oid; + } + + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(&unpack_tree_opts, action); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = the_repository->index; + unpack_tree_opts.dst_index = the_repository->index; + unpack_tree_opts.fn = oneway_merge; + unpack_tree_opts.update = 1; + unpack_tree_opts.merge = 1; + if (!detach_head) + unpack_tree_opts.reset = 1; + + if (read_index_unmerged(the_repository->index) < 0) { + rollback_lock_file(&lock); + return error(_("could not read index")); + } + + 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); + return -1; + } + + if (unpack_trees(1, &desc, &unpack_tree_opts)) { + rollback_lock_file(&lock); + free((void *)desc.buffer); + return -1; + } + + tree = parse_tree_indirect(oid); + prime_cache_tree(the_repository->index, tree); + + if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) + ret = error(_("could not write index")); + free((void *)desc.buffer); + + if (ret) + return ret; + + reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); + prefix_len = msg.len; + + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; + strbuf_addstr(&msg, "updating ORIG_HEAD"); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + if (!switch_to_branch) + ret = update_ref(msg.buf, "HEAD", oid, orig, REF_NO_DEREF, + UPDATE_REFS_MSG_ON_ERR); + else { + ret = create_symref("HEAD", switch_to_branch, msg.buf); + if (!ret) + ret = update_ref(msg.buf, "HEAD", oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); + } + + strbuf_release(&msg); + return ret; +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { + struct rebase_options options = { + .type = REBASE_UNSPECIFIED, + }; + const char *branch_name; + int ret, flags; + struct strbuf msg = STRBUF_INIT; + struct strbuf revisions = STRBUF_INIT; + /* * NEEDSWORK: Once the builtin rebase has been tested enough * and git-legacy-rebase.sh is retired to contrib/, this preamble @@ -54,5 +324,98 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) trace_repo_setup(prefix); setup_work_tree(); - die("TODO"); + git_config(git_default_config, NULL); + + switch (options.type) { + case REBASE_MERGE: + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + options.state_dir = merge_dir(); + break; + case REBASE_AM: + options.state_dir = apply_dir(); + break; + default: + /* the default rebase backend is `--am` */ + options.type = REBASE_AM; + options.state_dir = apply_dir(); + break; + } + + if (!options.root) { + if (argc < 2) + die("TODO: handle @{upstream}"); + else { + options.upstream_name = argv[1]; + argc--; + argv++; + if (!strcmp(options.upstream_name, "-")) + options.upstream_name = "@{-1}"; + } + options.upstream = peel_committish(options.upstream_name); + if (!options.upstream) + die(_("invalid upstream '%s'"), options.upstream_name); + } else + die("TODO: upstream for --root"); + + /* Make sure the branch to rebase onto is valid. */ + if (!options.onto_name) + options.onto_name = options.upstream_name; + if (strstr(options.onto_name, "...")) { + die("TODO"); + } else { + options.onto = peel_committish(options.onto_name); + if (!options.onto) + die(_("Does not point to a valid commit '%s'"), + options.onto_name); + } + + /* + * If the branch to rebase is given, that is the branch we will rebase + * branch_name -- branch/commit being rebased, or + * HEAD (already detached) + * orig_head -- commit object name of tip of the branch before rebasing + * head_name -- refs/heads/ or "detached HEAD" + */ + if (argc > 1) + die("TODO: handle switch_to"); + else { + /* Do not need to switch branches, we are already on it. */ + options.head_name = + xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL, + &flags)); + if (!options.head_name) + die(_("No such ref: %s"), "HEAD"); + if (flags & REF_ISSYMREF) { + if (!skip_prefix(options.head_name, + "refs/heads/", &branch_name)) + branch_name = options.head_name; + + } else { + options.head_name = xstrdup("detached HEAD"); + branch_name = "HEAD"; + } + if (get_oid("HEAD", &options.orig_head)) + die(_("Could not resolve HEAD to a revision")); + } + + strbuf_addf(&msg, "rebase: checkout %s", options.onto_name); + if (reset_head(&options.onto->object.oid, "checkout", NULL, 1)) + die(_("Could not detach HEAD")); + strbuf_release(&msg); + + strbuf_addf(&revisions, "%s..%s", + options.root ? oid_to_hex(&options.onto->object.oid) : + (options.restrict_revision ? + oid_to_hex(&options.restrict_revision->object.oid) : + oid_to_hex(&options.upstream->object.oid)), + oid_to_hex(&options.orig_head)); + + options.revisions = revisions.buf; + + ret = !!run_specific_rebase(&options); + + strbuf_release(&revisions); + free(options.head_name); + return ret; } From 94dc35246c43144aeb5d4e58417113a6a4a8fde7 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:13 -0700 Subject: [PATCH 28/93] builtin rebase: support the `verbose` and `diffstat` options This commit introduces support for the `-v` and `--stat` options of rebase. The --stat option can also be configured via the Git config setting rebase.stat. To support this, we also add a custom rebase_config() function in this commit that will be used instead of (and falls back to calling) git_default_config(). Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 19fa4d3fc488c9..2d3f1d65fb5e1c 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -18,6 +18,7 @@ #include "lockfile.h" #include "parse-options.h" #include "commit.h" +#include "diff.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -81,6 +82,8 @@ struct rebase_options { int dont_finish_rebase; enum { REBASE_NO_QUIET = 1<<0, + REBASE_VERBOSE = 1<<1, + REBASE_DIFFSTAT = 1<<2, } flags; struct strbuf git_am_opt; }; @@ -166,6 +169,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "GIT_QUIET", opts->flags & REBASE_NO_QUIET ? "" : "t"); add_var(&script_snippet, "git_am_opt", opts->git_am_opt.buf); + add_var(&script_snippet, "verbose", + opts->flags & REBASE_VERBOSE ? "t" : ""); + add_var(&script_snippet, "diffstat", + opts->flags & REBASE_DIFFSTAT ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -311,6 +318,21 @@ static int reset_head(struct object_id *oid, const char *action, return ret; } +static int rebase_config(const char *var, const char *value, void *data) +{ + struct rebase_options *opts = data; + + if (!strcmp(var, "rebase.stat")) { + if (git_config_bool(var, value)) + opts->flags |= REBASE_DIFFSTAT; + else + opts->flags &= !REBASE_DIFFSTAT; + return 0; + } + + return git_default_config(var, value, data); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -332,7 +354,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, N_("be quiet. implies --no-stat"), - REBASE_NO_QUIET), + REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT), + OPT_BIT('v', "verbose", &options.flags, + N_("display a diffstat of what changed upstream"), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), + {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, + N_("do not show diffstat of what changed upstream"), + PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_END(), }; @@ -360,7 +388,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) trace_repo_setup(prefix); setup_work_tree(); - git_config(git_default_config, NULL); + git_config(rebase_config, &options); + argc = parse_options(argc, argv, prefix, builtin_rebase_options, builtin_rebase_usage, 0); @@ -456,6 +485,33 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) argc ? argv[0] : NULL, NULL)) die(_("The pre-rebase hook refused to rebase.")); + if (options.flags & REBASE_DIFFSTAT) { + struct diff_options opts; + + if (options.flags & REBASE_VERBOSE) + printf(_("Changes from %s to %s:\n"), + oid_to_hex(&merge_base), + oid_to_hex(&options.onto->object.oid)); + + /* We want color (if set), but no pager */ + diff_setup(&opts); + opts.stat_width = -1; /* use full terminal width */ + opts.stat_graph_width = -1; /* respect statGraphWidth config */ + opts.output_format |= + DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT; + opts.detect_rename = DIFF_DETECT_RENAME; + diff_setup_done(&opts); + diff_tree_oid(&merge_base, &options.onto->object.oid, + "", &opts); + diffcore_std(&opts); + diff_flush(&opts); + } + + /* Detach HEAD and reset the tree */ + if (options.flags & REBASE_NO_QUIET) + printf(_("First, rewinding head to replay your work on top of " + "it...\n")); + strbuf_addf(&msg, "rebase: checkout %s", options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, 1)) die(_("Could not detach HEAD")); From 6ac92d6f26ba84602ca6c8338660d13ab783a471 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:14 -0700 Subject: [PATCH 29/93] builtin rebase: require a clean worktree This commit reads the index of the repository for rebase and checks whether the repository is ready for rebase. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2d3f1d65fb5e1c..afef0b00462239 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -19,6 +19,7 @@ #include "parse-options.h" #include "commit.h" #include "diff.h" +#include "wt-status.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -479,6 +480,15 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not resolve HEAD to a revision")); } + if (read_index(the_repository->index) < 0) + die(_("could not read index")); + + if (require_clean_work_tree("rebase", + _("Please commit or stash them."), 1, 1)) { + ret = 1; + goto cleanup; + } + /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && run_hook_le(NULL, "pre-rebase", options.upstream_arg, @@ -528,6 +538,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ret = !!run_specific_rebase(&options); +cleanup: strbuf_release(&revisions); free(options.head_name); return ret; From 2935c89141c1d6d239ce00d5b6f1fdfd9965845c Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:16 -0700 Subject: [PATCH 30/93] builtin rebase: try to fast forward when possible In this commit, we add support to fast forward. Note: we will need the merge base later, therefore the call to can_fast_forward() really needs to be the first one when testing whether we can skip the rebase entirely (otherwise, it would make more sense to skip the possibly expensive operation if, say, running an interactive rebase). Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index afef0b00462239..d67df28efc9a94 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -20,6 +20,7 @@ #include "commit.h" #include "diff.h" #include "wt-status.h" +#include "revision.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -89,6 +90,12 @@ struct rebase_options { struct strbuf git_am_opt; }; +static int is_interactive(struct rebase_options *opts) +{ + return opts->type == REBASE_INTERACTIVE || + opts->type == REBASE_PRESERVE_MERGES; +} + /* Returns the filename prefixed by the state_dir */ static const char *state_dir_path(const char *filename, struct rebase_options *opts) { @@ -334,6 +341,46 @@ static int rebase_config(const char *var, const char *value, void *data) return git_default_config(var, value, data); } +/* + * Determines whether the commits in from..to are linear, i.e. contain + * no merge commits. This function *expects* `from` to be an ancestor of + * `to`. + */ +static int is_linear_history(struct commit *from, struct commit *to) +{ + while (to && to != from) { + parse_commit(to); + if (!to->parents) + return 1; + if (to->parents->next) + return 0; + to = to->parents->item; + } + return 1; +} + +static int can_fast_forward(struct commit *onto, struct object_id *head_oid, + struct object_id *merge_base) +{ + struct commit *head = lookup_commit(the_repository, head_oid); + struct commit_list *merge_bases; + int res; + + if (!head) + return 0; + + merge_bases = get_merge_bases(onto, head); + if (merge_bases && !merge_bases->next) { + oidcpy(merge_base, &merge_bases->item->object.oid); + res = !oidcmp(merge_base, &onto->object.oid); + } else { + oidcpy(merge_base, &null_oid); + res = 0; + } + free_commit_list(merge_bases); + return res && is_linear_history(onto, head); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -489,6 +536,31 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) goto cleanup; } + /* + * Now we are rebasing commits upstream..orig_head (or with --root, + * everything leading up to orig_head) on top of onto. + */ + + /* + * Check if we are already based on onto with linear history, + * but this should be done only when upstream and onto are the same + * and if this is not an interactive rebase. + */ + if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && + !is_interactive(&options) && !options.restrict_revision && + !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + int flag; + + if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && + resolve_ref_unsafe("HEAD", 0, NULL, &flag)) + puts(_("HEAD is up to date, rebase forced.")); + else + printf(_("Current branch %s is up to date, rebase " + "forced.\n"), branch_name); + } + /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && run_hook_le(NULL, "pre-rebase", options.upstream_arg, From f010ad7153466b03dbe3fbcb3b6aaa035e22a209 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:17 -0700 Subject: [PATCH 31/93] builtin rebase: support --force-rebase In this commit, we add support to `--force-rebase` option. The equivalent part of the shell script found in `git-legacy-rebase.sh` is converted as faithfully as possible to C. The --force-rebase option ensures that the rebase does not simply fast-forward even if it could. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index d67df28efc9a94..8a7bf3d468bab6 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -86,6 +86,7 @@ struct rebase_options { REBASE_NO_QUIET = 1<<0, REBASE_VERBOSE = 1<<1, REBASE_DIFFSTAT = 1<<2, + REBASE_FORCE = 1<<3, } flags; struct strbuf git_am_opt; }; @@ -181,6 +182,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_VERBOSE ? "t" : ""); add_var(&script_snippet, "diffstat", opts->flags & REBASE_DIFFSTAT ? "t" : ""); + add_var(&script_snippet, "force_rebase", + opts->flags & REBASE_FORCE ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -409,6 +412,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BIT('f', "force-rebase", &options.flags, + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), + OPT_BIT(0, "no-ff", &options.flags, + N_("cherry-pick all commits, even if unchanged"), + REBASE_FORCE), OPT_END(), }; @@ -551,7 +560,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { int flag; - if (!(options.flags & REBASE_NO_QUIET)) + if (!(options.flags & REBASE_FORCE)) { + if (!(options.flags & REBASE_NO_QUIET)) + ; /* be quiet */ + else if (!strcmp(branch_name, "HEAD") && + resolve_ref_unsafe("HEAD", 0, NULL, &flag)) + puts(_("HEAD is up to date.")); + else + printf(_("Current branch %s is up to date.\n"), + branch_name); + ret = !!finish_rebase(&options); + goto cleanup; + } else if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ else if (!strcmp(branch_name, "HEAD") && resolve_ref_unsafe("HEAD", 0, NULL, &flag)) From 2ed2dcdb2947fc5ff0d744ebcaa38d12bda6a3cc Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:18 -0700 Subject: [PATCH 32/93] builtin rebase: start a new rebase only if none is in progress To run a new rebase, there needs to be a check to assure that no other rebase is in progress. New rebase operation cannot start until an ongoing rebase operation completes or is terminated. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 8a7bf3d468bab6..d45f8f9008182f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -87,6 +87,7 @@ struct rebase_options { REBASE_VERBOSE = 1<<1, REBASE_DIFFSTAT = 1<<2, REBASE_FORCE = 1<<3, + REBASE_INTERACTIVE_EXPLICIT = 1<<4, } flags; struct strbuf git_am_opt; }; @@ -392,10 +393,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .git_am_opt = STRBUF_INIT, }; const char *branch_name; - int ret, flags; + int ret, flags, in_progress = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; struct object_id merge_base; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -447,6 +449,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) git_config(rebase_config, &options); + if (is_directory(apply_dir())) { + options.type = REBASE_AM; + options.state_dir = apply_dir(); + } else if (is_directory(merge_dir())) { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/rewritten", merge_dir()); + if (is_directory(buf.buf)) { + options.type = REBASE_PRESERVE_MERGES; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/interactive", merge_dir()); + if(file_exists(buf.buf)) { + options.type = REBASE_INTERACTIVE; + options.flags |= REBASE_INTERACTIVE_EXPLICIT; + } else + options.type = REBASE_MERGE; + } + options.state_dir = merge_dir(); + } + + if (options.type != REBASE_UNSPECIFIED) + in_progress = 1; + argc = parse_options(argc, argv, prefix, builtin_rebase_options, builtin_rebase_usage, 0); @@ -455,6 +481,26 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + /* Make sure no rebase is in progress */ + if (in_progress) { + const char *last_slash = strrchr(options.state_dir, '/'); + const char *state_dir_base = + last_slash ? last_slash + 1 : options.state_dir; + const char *cmd_live_rebase = + "git rebase (--continue | --abort | --skip)"; + strbuf_reset(&buf); + strbuf_addf(&buf, "rm -fr \"%s\"", options.state_dir); + die(_("It seems that there is already a %s directory, and\n" + "I wonder if you are in the middle of another rebase. " + "If that is the\n" + "case, please try\n\t%s\n" + "If that is not the case, please\n\t%s\n" + "and run me again. I am stopping in case you still " + "have something\n" + "valuable there.\n"), + state_dir_base, cmd_live_rebase, buf.buf); + } + if (!(options.flags & REBASE_NO_QUIET)) strbuf_addstr(&options.git_am_opt, " -q"); From a3aa8a58cfc5154012632345dd26db900296203f Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:20 -0700 Subject: [PATCH 33/93] builtin rebase: only store fully-qualified refs in `options.head_name` When running a rebase on a detached HEAD, we currently store the string "detached HEAD" in options.head_name. That is a faithful translation of the shell script version, and we still kind of need it for the purposes of the scripted backends. It is poor style for C, though, where we would really only want a valid, fully-qualified ref name as value, and NULL for detached HEADs, using "detached HEAD" for display only. Make it so. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index d45f8f9008182f..afc75fe7314e6d 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -169,7 +169,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "upstream_name", opts->upstream_name); add_var(&script_snippet, "upstream", oid_to_hex(&opts->upstream->object.oid)); - add_var(&script_snippet, "head_name", opts->head_name); + add_var(&script_snippet, "head_name", + opts->head_name ? opts->head_name : "detached HEAD"); add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head)); add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid)); add_var(&script_snippet, "onto_name", opts->onto_name); @@ -251,6 +252,9 @@ static int reset_head(struct object_id *oid, const char *action, *old_orig = NULL, oid_old_orig; int ret = 0; + if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) + BUG("Not a fully qualified branch: '%s'", switch_to_branch); + if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) return -1; @@ -558,7 +562,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * branch_name -- branch/commit being rebased, or * HEAD (already detached) * orig_head -- commit object name of tip of the branch before rebasing - * head_name -- refs/heads/ or "detached HEAD" + * head_name -- refs/heads/ or NULL (detached HEAD) */ if (argc > 0) die("TODO: handle switch_to"); @@ -575,7 +579,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) branch_name = options.head_name; } else { - options.head_name = xstrdup("detached HEAD"); + free(options.head_name); + options.head_name = NULL; branch_name = "HEAD"; } if (get_oid("HEAD", &options.orig_head)) From 8cbfc38650154f80e56687815aef06b76a80f77a Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:27:21 -0700 Subject: [PATCH 34/93] builtin rebase: support `git rebase ` This commit adds support for `switch-to` which is used to switch to the target branch if needed. The equivalent codes found in shell script `git-legacy-rebase.sh` is converted to builtin `rebase.c`. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index afc75fe7314e6d..e817956d96fe50 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -79,6 +79,7 @@ struct rebase_options { struct commit *onto; const char *onto_name; const char *revisions; + const char *switch_to; int root; struct commit *restrict_revision; int dont_finish_rebase; @@ -186,6 +187,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_DIFFSTAT ? "t" : ""); add_var(&script_snippet, "force_rebase", opts->flags & REBASE_FORCE ? "t" : ""); + if (opts->switch_to) + add_var(&script_snippet, "switch_to", opts->switch_to); switch (opts->type) { case REBASE_AM: @@ -564,9 +567,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * orig_head -- commit object name of tip of the branch before rebasing * head_name -- refs/heads/ or NULL (detached HEAD) */ - if (argc > 0) - die("TODO: handle switch_to"); - else { + if (argc == 1) { + /* Is it "rebase other branchname" or "rebase other commit"? */ + branch_name = argv[0]; + options.switch_to = argv[0]; + + /* Is it a local branch? */ + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/heads/%s", branch_name); + if (!read_ref(buf.buf, &options.orig_head)) + options.head_name = xstrdup(buf.buf); + /* If not is it a valid ref (branch or commit)? */ + else if (!get_oid(branch_name, &options.orig_head)) + options.head_name = NULL; + else + die(_("fatal: no such branch/commit '%s'"), + branch_name); + } else if (argc == 0) { /* Do not need to switch branches, we are already on it. */ options.head_name = xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL, @@ -585,7 +602,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (get_oid("HEAD", &options.orig_head)) die(_("Could not resolve HEAD to a revision")); - } + } else + BUG("unexpected number of arguments left to parse"); if (read_index(the_repository->index) < 0) die(_("could not read index")); @@ -612,6 +630,28 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int flag; if (!(options.flags & REBASE_FORCE)) { + /* Lazily switch to the target branch if needed... */ + if (options.switch_to) { + struct object_id oid; + + if (get_oid(options.switch_to, &oid) < 0) { + ret = !!error(_("could not parse '%s'"), + options.switch_to); + goto cleanup; + } + + strbuf_reset(&buf); + strbuf_addf(&buf, "rebase: checkout %s", + options.switch_to); + if (reset_head(&oid, "checkout", + options.head_name, 0) < 0) { + ret = !!error(_("could not switch to " + "%s"), + options.switch_to); + goto cleanup; + } + } + if (!(options.flags & REBASE_NO_QUIET)) ; /* be quiet */ else if (!strcmp(branch_name, "HEAD") && From e2075323402e553ba32aa17aa002746a461bfa7b Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 20:51:16 +0545 Subject: [PATCH 35/93] builtin rebase: support --continue This commit adds the option `--continue` which is used to resume rebase after merge conflicts. The code tries to stay as close to the equivalent shell scripts found in `git-legacy-rebase.sh` as possible. When continuing a rebase, the state variables are read from state_dir. Some of the state variables are not actually stored there, such as `upstream`. The shell script version simply does not set them, but for consistency, we unset them in the builtin version. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 115 +++++++++++++++++++++++++++++++++++++++++++++-- strbuf.c | 9 ++++ strbuf.h | 3 ++ 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index e817956d96fe50..f112d91d678b05 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -91,6 +91,7 @@ struct rebase_options { REBASE_INTERACTIVE_EXPLICIT = 1<<4, } flags; struct strbuf git_am_opt; + const char *action; }; static int is_interactive(struct rebase_options *opts) @@ -115,6 +116,62 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o return path.buf; } +/* Read one file, then strip line endings */ +static int read_one(const char *path, struct strbuf *buf) +{ + if (strbuf_read_file(buf, path, 0) < 0) + return error_errno(_("could not read '%s'"), path); + strbuf_trim_trailing_newline(buf); + return 0; +} + +/* Initialize the rebase options from the state directory. */ +static int read_basic_state(struct rebase_options *opts) +{ + struct strbuf head_name = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; + struct object_id oid; + + if (read_one(state_dir_path("head-name", opts), &head_name) || + read_one(state_dir_path("onto", opts), &buf)) + return -1; + opts->head_name = starts_with(head_name.buf, "refs/") ? + xstrdup(head_name.buf) : NULL; + strbuf_release(&head_name); + if (get_oid(buf.buf, &oid)) + return error(_("could not get 'onto': '%s'"), buf.buf); + opts->onto = lookup_commit_or_die(&oid, buf.buf); + + /* + * We always write to orig-head, but interactive rebase used to write to + * head. Fall back to reading from head to cover for the case that the + * user upgraded git with an ongoing interactive rebase. + */ + strbuf_reset(&buf); + if (file_exists(state_dir_path("orig-head", opts))) { + if (read_one(state_dir_path("orig-head", opts), &buf)) + return -1; + } else if (read_one(state_dir_path("head", opts), &buf)) + return -1; + if (get_oid(buf.buf, &opts->orig_head)) + return error(_("invalid orig-head: '%s'"), buf.buf); + + strbuf_reset(&buf); + if (read_one(state_dir_path("quiet", opts), &buf)) + return -1; + if (buf.len) + opts->flags &= ~REBASE_NO_QUIET; + else + opts->flags |= REBASE_NO_QUIET; + + if (file_exists(state_dir_path("verbose", opts))) + opts->flags |= REBASE_VERBOSE; + + strbuf_release(&buf); + + return 0; +} + static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; @@ -168,12 +225,13 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "state_dir", opts->state_dir); add_var(&script_snippet, "upstream_name", opts->upstream_name); - add_var(&script_snippet, "upstream", - oid_to_hex(&opts->upstream->object.oid)); + add_var(&script_snippet, "upstream", opts->upstream ? + oid_to_hex(&opts->upstream->object.oid) : NULL); add_var(&script_snippet, "head_name", opts->head_name ? opts->head_name : "detached HEAD"); add_var(&script_snippet, "orig_head", oid_to_hex(&opts->orig_head)); - add_var(&script_snippet, "onto", oid_to_hex(&opts->onto->object.oid)); + add_var(&script_snippet, "onto", opts->onto ? + oid_to_hex(&opts->onto->object.oid) : NULL); add_var(&script_snippet, "onto_name", opts->onto_name); add_var(&script_snippet, "revisions", opts->revisions); add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? @@ -189,6 +247,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->flags & REBASE_FORCE ? "t" : ""); if (opts->switch_to) add_var(&script_snippet, "switch_to", opts->switch_to); + add_var(&script_snippet, "action", opts->action ? opts->action : ""); switch (opts->type) { case REBASE_AM: @@ -400,12 +459,16 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .git_am_opt = STRBUF_INIT, }; const char *branch_name; - int ret, flags, in_progress = 0; + int ret, flags, total_argc, in_progress = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; struct object_id merge_base; + enum { + NO_ACTION, + ACTION_CONTINUE, + } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -427,6 +490,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BIT(0, "no-ff", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), + OPT_CMDMODE(0, "continue", &action, N_("continue"), + ACTION_CONTINUE), OPT_END(), }; @@ -480,14 +545,55 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.type != REBASE_UNSPECIFIED) in_progress = 1; + total_argc = argc; argc = parse_options(argc, argv, prefix, builtin_rebase_options, builtin_rebase_usage, 0); + if (action != NO_ACTION && total_argc != 2) { + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + } + if (argc > 2) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + switch (action) { + case ACTION_CONTINUE: { + struct object_id head; + struct lock_file lock_file = LOCK_INIT; + int fd; + + options.action = "continue"; + + /* Sanity check */ + if (get_oid("HEAD", &head)) + die(_("Cannot read HEAD")); + + fd = hold_locked_index(&lock_file, 0); + if (read_index(the_repository->index) < 0) + die(_("could not read index")); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, + NULL); + if (0 <= fd) + update_index_if_able(the_repository->index, + &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(1)) { + puts(_("You must edit all merge conflicts and then\n" + "mark them as resolved using git add")); + exit(1); + } + if (read_basic_state(&options)) + exit(1); + goto run_rebase; + } + default: + die("TODO"); + } + /* Make sure no rebase is in progress */ if (in_progress) { const char *last_slash = strrchr(options.state_dir, '/'); @@ -719,6 +825,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.revisions = revisions.buf; +run_rebase: ret = !!run_specific_rebase(&options); cleanup: diff --git a/strbuf.c b/strbuf.c index 64041c3c249b15..f6a6cf78b9426a 100644 --- a/strbuf.c +++ b/strbuf.c @@ -120,6 +120,15 @@ void strbuf_trim_trailing_dir_sep(struct strbuf *sb) sb->buf[sb->len] = '\0'; } +void strbuf_trim_trailing_newline(struct strbuf *sb) +{ + if (sb->len > 0 && sb->buf[sb->len - 1] == '\n') { + if (--sb->len > 0 && sb->buf[sb->len - 1] == '\r') + --sb->len; + sb->buf[sb->len] = '\0'; + } +} + void strbuf_ltrim(struct strbuf *sb) { char *b = sb->buf; diff --git a/strbuf.h b/strbuf.h index 60a35aef165ca1..b7aea8a9660160 100644 --- a/strbuf.h +++ b/strbuf.h @@ -190,6 +190,9 @@ extern void strbuf_ltrim(struct strbuf *); /* Strip trailing directory separators */ extern void strbuf_trim_trailing_dir_sep(struct strbuf *); +/* Strip trailing LF or CR/LF */ +extern void strbuf_trim_trailing_newline(struct strbuf *sb); + /** * Replace the contents of the strbuf with a reencoded form. Returns -1 * on error, 0 on success. From 855da9850d392272b9044a76e24b7f109d11b376 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 20:51:17 +0545 Subject: [PATCH 36/93] builtin rebase: support --skip This commit adds the option `--skip` which is used to restart rebase after skipping the current patch. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index f112d91d678b05..c477108f796fbb 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -21,6 +21,7 @@ #include "diff.h" #include "wt-status.h" #include "revision.h" +#include "rerere.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec ] [--onto ] " @@ -468,6 +469,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) enum { NO_ACTION, ACTION_CONTINUE, + ACTION_SKIP, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -492,6 +494,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) REBASE_FORCE), OPT_CMDMODE(0, "continue", &action, N_("continue"), ACTION_CONTINUE), + OPT_CMDMODE(0, "skip", &action, + N_("skip current patch and continue"), ACTION_SKIP), OPT_END(), }; @@ -590,6 +594,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) exit(1); goto run_rebase; } + case ACTION_SKIP: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + + options.action = "skip"; + + rerere_clear(&merge_rr); + string_list_clear(&merge_rr, 1); + + if (reset_head(NULL, "reset", NULL, 0) < 0) + die(_("could not discard worktree changes")); + if (read_basic_state(&options)) + exit(1); + goto run_rebase; + } default: die("TODO"); } From e663fedba527b9f7cf0717e39ddf4cf555b3459c Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 20:51:18 +0545 Subject: [PATCH 37/93] builtin rebase: support --abort This commit teaches the builtin rebase the "abort" action, which a user can call to roll back a rebase that is in progress. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index c477108f796fbb..ceb786d3a052a5 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -470,6 +470,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) NO_ACTION, ACTION_CONTINUE, ACTION_SKIP, + ACTION_ABORT, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -496,6 +497,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_CONTINUE), OPT_CMDMODE(0, "skip", &action, N_("skip current patch and continue"), ACTION_SKIP), + OPT_CMDMODE(0, "abort", &action, + N_("abort and check out the original branch"), + ACTION_ABORT), OPT_END(), }; @@ -608,6 +612,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) exit(1); goto run_rebase; } + case ACTION_ABORT: { + struct string_list merge_rr = STRING_LIST_INIT_DUP; + options.action = "abort"; + + rerere_clear(&merge_rr); + string_list_clear(&merge_rr, 1); + + if (read_basic_state(&options)) + exit(1); + if (reset_head(&options.orig_head, "reset", + options.head_name, 0) < 0) + die(_("could not move back to %s"), + oid_to_hex(&options.orig_head)); + ret = finish_rebase(&options); + goto cleanup; + } default: die("TODO"); } From 4e5e8427c6e426cd967991bd9e8d8de631e89431 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 20:51:19 +0545 Subject: [PATCH 38/93] builtin rebase: support --quit With this patch, the builtin rebase handles the `--quit` action which can be used to abort a rebase without rolling back any changes performed during the rebase (this is useful when a user forgot that they were in the middle of a rebase and continued working normally). Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index ceb786d3a052a5..f64fa42422feb4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -471,6 +471,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_CONTINUE, ACTION_SKIP, ACTION_ABORT, + ACTION_QUIT, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -500,6 +501,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "abort", &action, N_("abort and check out the original branch"), ACTION_ABORT), + OPT_CMDMODE(0, "quit", &action, + N_("abort but keep HEAD where it is"), ACTION_QUIT), OPT_END(), }; @@ -628,6 +631,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ret = finish_rebase(&options); goto cleanup; } + case ACTION_QUIT: { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.state_dir); + ret = !!remove_dir_recursively(&buf, 0); + if (ret) + die(_("could not remove '%s'"), options.state_dir); + goto cleanup; + } default: die("TODO"); } From 40be0d69395b4d6ba7da0e8fda7a6e1cb0eba2d5 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 20:51:20 +0545 Subject: [PATCH 39/93] builtin rebase: support --edit-todo and --show-current-patch While these sub-commands are very different in spirit, their implementation is almost identical, so we convert them in one go. And since those are the last sub-commands that needed to be converted, now we can also turn that `default:` case into a bug (because we should now handle all the actions). Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index f64fa42422feb4..12d34848bd979c 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -472,6 +472,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SKIP, ACTION_ABORT, ACTION_QUIT, + ACTION_EDIT_TODO, + ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -503,6 +505,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_ABORT), OPT_CMDMODE(0, "quit", &action, N_("abort but keep HEAD where it is"), ACTION_QUIT), + OPT_CMDMODE(0, "edit-todo", &action, N_("edit the todo list " + "during an interactive rebase"), ACTION_EDIT_TODO), + OPT_CMDMODE(0, "show-current-patch", &action, + N_("show the patch file being applied or merged"), + ACTION_SHOW_CURRENT_PATCH), OPT_END(), }; @@ -570,6 +577,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (action == ACTION_EDIT_TODO && !is_interactive(&options)) + die(_("The --edit-todo action can only be used during " + "interactive rebase.")); + switch (action) { case ACTION_CONTINUE: { struct object_id head; @@ -639,8 +650,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not remove '%s'"), options.state_dir); goto cleanup; } + case ACTION_EDIT_TODO: + options.action = "edit-todo"; + options.dont_finish_rebase = 1; + goto run_rebase; + case ACTION_SHOW_CURRENT_PATCH: + options.action = "show-current-patch"; + options.dont_finish_rebase = 1; + goto run_rebase; + case NO_ACTION: + break; default: - die("TODO"); + BUG("action: %d", action); } /* Make sure no rebase is in progress */ From 1319c8a7a347ea66bee27636358b26f908edd5af Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 20:51:21 +0545 Subject: [PATCH 40/93] builtin rebase: actions require a rebase in progress This commit prevents actions (such as --continue, --skip) from running when there is no rebase in progress. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 12d34848bd979c..9238757443832d 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -577,6 +577,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (action != NO_ACTION && !in_progress) + die(_("No rebase in progress?")); + if (action == ACTION_EDIT_TODO && !is_interactive(&options)) die(_("The --edit-todo action can only be used during " "interactive rebase.")); From c74c47f1c6fc6e9eaf97fea93c697f893141e243 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 20:51:22 +0545 Subject: [PATCH 41/93] builtin rebase: stop if `git am` is in progress This commit checks for the file `applying` used by `git am` in `rebase-apply/` and if the file is present it means `git am` is in progress so it errors out. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 9238757443832d..2165656ae24a87 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -539,6 +539,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) git_config(rebase_config, &options); + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/applying", apply_dir()); + if(file_exists(buf.buf)) + die(_("It looks like 'git am' is in progress. Cannot rebase.")); + if (is_directory(apply_dir())) { options.type = REBASE_AM; options.state_dir = apply_dir(); From 414c8f48f267cd8af073bc33509f6fd1d33eeaac Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:49 -0700 Subject: [PATCH 42/93] builtin rebase: allow selecting the rebase "backend" With this commit the builtin rebase supports selecting the "rebase backends" (or "type") `interactive`, `preserve-merges`, and `merge`. The `state_dir` was already handled according to the rebase type in a previous commit. Note that there is one quirk in the shell script: `--interactive` followed by `--merge` won't reset the type to "merge" but keeps the type as "interactive". And as t3418 tests this explicitly, we have to support it in the builtin rebase, too. Likewise, `--interactive` followed by `--preserve-merges` makes it an "explicitly interactive" rebase, i.e. a rebase that should show the todo list, while `--preserve-merges` alone is not interactive (and t5520 tests for this via `git pull --rebase=preserve`). Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2165656ae24a87..61695ac7b954de 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -452,6 +452,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, return res && is_linear_history(onto, head); } +/* -i followed by -m is still -i */ +static int parse_opt_merge(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + if (!is_interactive(opts)) + opts->type = REBASE_MERGE; + + return 0; +} + +/* -i followed by -p is still explicitly interactive, but -p alone is not */ +static int parse_opt_interactive(const struct option *opt, const char *arg, + int unset) +{ + struct rebase_options *opts = opt->value; + + opts->type = REBASE_INTERACTIVE; + opts->flags |= REBASE_INTERACTIVE_EXPLICIT; + + return 0; +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -510,6 +533,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "show-current-patch", &action, N_("show the patch file being applied or merged"), ACTION_SHOW_CURRENT_PATCH), + { OPTION_CALLBACK, 'm', "merge", &options, NULL, + N_("use merging strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_merge }, + { OPTION_CALLBACK, 'i', "interactive", &options, NULL, + N_("let the user edit the list of commits to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_interactive }, + OPT_SET_INT('p', "preserve-merges", &options.type, + N_("try to recreate merges instead of ignoring " + "them"), REBASE_PRESERVE_MERGES), OPT_END(), }; @@ -884,6 +918,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) diff_flush(&opts); } + if (is_interactive(&options)) + goto run_rebase; + /* Detach HEAD and reset the tree */ if (options.flags & REBASE_NO_QUIET) printf(_("First, rewinding head to replay your work on top of " From b92306390f5d9980df44b3fee6ccd545ebb69fea Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:50 -0700 Subject: [PATCH 43/93] builtin rebase: support --signoff This commit adds support for `--signoff` which is used to add a `Signed-off-by` trailer to all the rebased commits. The actual handling is left to the rebase backends. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 61695ac7b954de..3c8b29d82cd932 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -93,6 +93,7 @@ struct rebase_options { } flags; struct strbuf git_am_opt; const char *action; + int signoff; }; static int is_interactive(struct rebase_options *opts) @@ -168,6 +169,11 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("verbose", opts))) opts->flags |= REBASE_VERBOSE; + if (file_exists(state_dir_path("signoff", opts))) { + opts->signoff = 1; + opts->flags |= REBASE_FORCE; + } + strbuf_release(&buf); return 0; @@ -249,6 +255,7 @@ static int run_specific_rebase(struct rebase_options *opts) if (opts->switch_to) add_var(&script_snippet, "switch_to", opts->switch_to); add_var(&script_snippet, "action", opts->action ? opts->action : ""); + add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); switch (opts->type) { case REBASE_AM: @@ -513,6 +520,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "signoff", &options.signoff, + N_("add a Signed-off-by: line to each commit")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -745,6 +754,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; } + if (options.signoff) { + if (options.type == REBASE_PRESERVE_MERGES) + die("cannot combine '--signoff' with " + "'--preserve-merges'"); + strbuf_addstr(&options.git_am_opt, " --signoff"); + options.flags |= REBASE_FORCE; + } + if (!options.root) { if (argc < 1) die("TODO: handle @{upstream}"); From 3f70abd6da6c94ff734d720612344458ef3c17b4 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:52 -0700 Subject: [PATCH 44/93] builtin rebase: support --rerere-autoupdate The `--rerere-autoupdate` option allows rerere to update the index with resolved conflicts. This commit follows closely the equivalent part of `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 3c8b29d82cd932..24dc646ea5b6b3 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -94,6 +94,7 @@ struct rebase_options { struct strbuf git_am_opt; const char *action; int signoff; + int allow_rerere_autoupdate; }; static int is_interactive(struct rebase_options *opts) @@ -174,6 +175,21 @@ static int read_basic_state(struct rebase_options *opts) opts->flags |= REBASE_FORCE; } + if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("allow_rerere_autoupdate", opts), + &buf)) + return -1; + if (!strcmp(buf.buf, "--rerere-autoupdate")) + opts->allow_rerere_autoupdate = 1; + else if (!strcmp(buf.buf, "--no-rerere-autoupdate")) + opts->allow_rerere_autoupdate = 0; + else + warning(_("ignoring invalid allow_rerere_autoupdate: " + "'%s'"), buf.buf); + } else + opts->allow_rerere_autoupdate = -1; + strbuf_release(&buf); return 0; @@ -256,6 +272,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "switch_to", opts->switch_to); add_var(&script_snippet, "action", opts->action ? opts->action : ""); add_var(&script_snippet, "signoff", opts->signoff ? "--signoff" : ""); + add_var(&script_snippet, "allow_rerere_autoupdate", + opts->allow_rerere_autoupdate < 0 ? "" : + opts->allow_rerere_autoupdate ? + "--rerere-autoupdate" : "--no-rerere-autoupdate"); switch (opts->type) { case REBASE_AM: @@ -488,6 +508,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .type = REBASE_UNSPECIFIED, .flags = REBASE_NO_QUIET, .git_am_opt = STRBUF_INIT, + .allow_rerere_autoupdate = -1, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -553,6 +574,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_SET_INT('p', "preserve-merges", &options.type, N_("try to recreate merges instead of ignoring " "them"), REBASE_PRESERVE_MERGES), + OPT_BOOL(0, "rerere-autoupdate", + &options.allow_rerere_autoupdate, + N_("allow rerere to update index with resolved " + "conflict")), OPT_END(), }; From 0983c95fafcae540e3869e1626118249b6fb58c3 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:53 -0700 Subject: [PATCH 45/93] builtin rebase: support --committer-date-is-author-date This option is simply handed down to `git am` by way of setting the `git_am_opt` variable that is handled by the `git-rebase--am` backend. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 24dc646ea5b6b3..6ce50eae0f2ea7 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -526,6 +526,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_EDIT_TODO, ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; + int committer_date_is_author_date = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -543,6 +544,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), + OPT_BOOL(0, "committer-date-is-author-date", + &committer_date_is_author_date, + N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -763,6 +767,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!(options.flags & REBASE_NO_QUIET)) strbuf_addstr(&options.git_am_opt, " -q"); + if (committer_date_is_author_date) { + strbuf_addstr(&options.git_am_opt, + " --committer-date-is-author-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From 5702b6a150a84b938706f1c8d116fbaac051003e Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:54 -0700 Subject: [PATCH 46/93] builtin rebase: support `ignore-whitespace` option This commit adds support for the `--ignore-whitespace` option of the rebase command. This option is simply passed to the `--am` backend. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 6ce50eae0f2ea7..f023fb4339c3ad 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -527,6 +527,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; int committer_date_is_author_date = 0; + int ignore_whitespace = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -542,6 +543,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL, N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, + OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace, + N_("passed to 'git apply'")), OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), OPT_BOOL(0, "committer-date-is-author-date", @@ -773,6 +776,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } + if (ignore_whitespace) + strbuf_addstr(&options.git_am_opt, " --ignore-whitespace"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From daeb68afc35af2c31a27d28087a972e2d5a7bbe0 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:56 -0700 Subject: [PATCH 47/93] builtin rebase: support `ignore-date` option This commit adds support for `--ignore-date` which is passed to `git am` to easily change the dates of the rebased commits. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index f023fb4339c3ad..0f3e156daca6a5 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -527,6 +527,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) ACTION_SHOW_CURRENT_PATCH, } action = NO_ACTION; int committer_date_is_author_date = 0; + int ignore_date = 0; int ignore_whitespace = 0; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, @@ -550,6 +551,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "committer-date-is-author-date", &committer_date_is_author_date, N_("passed to 'git am'")), + OPT_BOOL(0, "ignore-date", &ignore_date, + N_("passed to 'git am'")), OPT_BIT('f', "force-rebase", &options.flags, N_("cherry-pick all commits, even if unchanged"), REBASE_FORCE), @@ -779,6 +782,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (ignore_whitespace) strbuf_addstr(&options.git_am_opt, " --ignore-whitespace"); + if (ignore_date) { + strbuf_addstr(&options.git_am_opt, " --ignore-date"); + options.flags |= REBASE_FORCE; + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From e9f6781293a535bb17082da96414e95b911d70a2 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:57 -0700 Subject: [PATCH 48/93] builtin rebase: support `keep-empty` option The `--keep-empty` option can be used to keep the commits that do not change anything from its parents in the result. While the scripted version uses `interactive_rebase=implied` to indicate that the rebase needs to use the `git-rebase--interactive` backend in non-interactive mode as fallback when figuring out which backend to use, the C version needs to use a different route because the backend will already be chosen during the `parse_options()` call. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 0f3e156daca6a5..2336b6b9793d39 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -95,6 +95,7 @@ struct rebase_options { const char *action; int signoff; int allow_rerere_autoupdate; + int keep_empty; }; static int is_interactive(struct rebase_options *opts) @@ -103,6 +104,23 @@ static int is_interactive(struct rebase_options *opts) opts->type == REBASE_PRESERVE_MERGES; } +static void imply_interactive(struct rebase_options *opts, const char *option) +{ + switch (opts->type) { + case REBASE_AM: + die(_("%s requires an interactive rebase"), option); + break; + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + break; + case REBASE_MERGE: + /* we silently *upgrade* --merge to --interactive if needed */ + default: + opts->type = REBASE_INTERACTIVE; /* implied */ + break; + } +} + /* Returns the filename prefixed by the state_dir */ static const char *state_dir_path(const char *filename, struct rebase_options *opts) { @@ -276,6 +294,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->allow_rerere_autoupdate < 0 ? "" : opts->allow_rerere_autoupdate ? "--rerere-autoupdate" : "--no-rerere-autoupdate"); + add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); switch (opts->type) { case REBASE_AM: @@ -588,6 +607,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) &options.allow_rerere_autoupdate, N_("allow rerere to update index with resolved " "conflict")), + OPT_BOOL('k', "keep-empty", &options.keep_empty, + N_("preserve empty commits during rebase")), OPT_END(), }; @@ -787,6 +808,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } + if (options.keep_empty) + imply_interactive(&options, "--keep-empty"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From 38c3ec782072a5222a6eae9341ac1640ac5c1a97 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 14:59:58 -0700 Subject: [PATCH 49/93] builtin rebase: support `--autosquash` This commit adds support for the `--autosquash` option which is used to automatically squash the commits marked as `squash` or `fixup` in their messages. This is converted following `git-legacy-rebase.sh` closely. This option can also be configured via the Git config setting rebase.autosquash. To support this, we also add a custom rebase_config() function in this commit that will be used instead (and falls back to) git_default_config(). Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2336b6b9793d39..e2b3fc2154cc00 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -96,6 +96,7 @@ struct rebase_options { int signoff; int allow_rerere_autoupdate; int keep_empty; + int autosquash; }; static int is_interactive(struct rebase_options *opts) @@ -295,6 +296,7 @@ static int run_specific_rebase(struct rebase_options *opts) opts->allow_rerere_autoupdate ? "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); + add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -455,6 +457,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.autosquash")) { + opts->autosquash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -609,6 +616,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "conflict")), OPT_BOOL('k', "keep-empty", &options.keep_empty, N_("preserve empty commits during rebase")), + OPT_BOOL(0, "autosquash", &options.autosquash, + N_("move commits that begin with " + "squash!/fixup! under -i")), OPT_END(), }; From 1db2f99f603e97438edfbdbff332d17ec8d5175f Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:00 -0700 Subject: [PATCH 50/93] builtin rebase: support `--gpg-sign` option This commit introduces support for `--gpg-sign` option which is used to GPG-sign commits. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index e2b3fc2154cc00..23a5bb30030b52 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -97,6 +97,7 @@ struct rebase_options { int allow_rerere_autoupdate; int keep_empty; int autosquash; + char *gpg_sign_opt; }; static int is_interactive(struct rebase_options *opts) @@ -209,6 +210,15 @@ static int read_basic_state(struct rebase_options *opts) } else opts->allow_rerere_autoupdate = -1; + if (file_exists(state_dir_path("gpg_sign_opt", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("gpg_sign_opt", opts), + &buf)) + return -1; + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = xstrdup(buf.buf); + } + strbuf_release(&buf); return 0; @@ -297,6 +307,7 @@ static int run_specific_rebase(struct rebase_options *opts) "--rerere-autoupdate" : "--no-rerere-autoupdate"); add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); + add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); switch (opts->type) { case REBASE_AM: @@ -462,6 +473,13 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "commit.gpgsign")) { + free(opts->gpg_sign_opt); + opts->gpg_sign_opt = git_config_bool(var, value) ? + xstrdup("-S") : NULL; + return 0; + } + return git_default_config(var, value, data); } @@ -555,6 +573,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int committer_date_is_author_date = 0; int ignore_date = 0; int ignore_whitespace = 0; + const char *gpg_sign = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -619,6 +638,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " "squash!/fixup! under -i")), + { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), + N_("GPG-sign commits"), + PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, OPT_END(), }; @@ -821,6 +843,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.keep_empty) imply_interactive(&options, "--keep-empty"); + if (gpg_sign) { + free(options.gpg_sign_opt); + options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1046,5 +1073,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) cleanup: strbuf_release(&revisions); free(options.head_name); + free(options.gpg_sign_opt); return ret; } From 5af547f795ee8fff5951b21b44425f31e26037fe Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:01 -0700 Subject: [PATCH 51/93] builtin rebase: support `-C` and `--whitespace=` This commit converts more code from the shell script version to the builtin rebase. In this instance, we just have to be careful to keep support for passing multiple `--whitespace` options, as the shell script version does so, too. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin --- builtin/rebase.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 23a5bb30030b52..c2f28cdd9e32ca 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -574,6 +574,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int ignore_date = 0; int ignore_whitespace = 0; const char *gpg_sign = NULL; + int opt_c = -1; + struct string_list whitespace = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -641,6 +643,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_STRING_LIST(0, "whitespace", &whitespace, + N_("whitespace"), N_("passed to 'git apply'")), + OPT_SET_INT('C', NULL, &opt_c, N_("passed to 'git apply'"), + REBASE_AM), OPT_END(), }; @@ -848,6 +854,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); } + if (opt_c >= 0) + strbuf_addf(&options.git_am_opt, " -C%d", opt_c); + + if (whitespace.nr) { + int i; + + for (i = 0; i < whitespace.nr; i++) { + const char *item = whitespace.items[i].string; + + strbuf_addf(&options.git_am_opt, " --whitespace=%s", + item); + + if ((!strcmp(item, "fix")) || (!strcmp(item, "strip"))) + options.flags |= REBASE_FORCE; + } + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From 09bb4edda02ca2a916ebcab0e9d708099825eed1 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:02 -0700 Subject: [PATCH 52/93] builtin rebase: support `--autostash` option To support `--autostash` we introduce a function `apply_autostash()` just like in `git-legacy-rebase.sh`. Rather than refactoring and using the same function that exists in `sequencer.c`, we go a different route here, to avoid clashes with the sister GSoC project that turns the interactive rebase into a builtin. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 117 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c2f28cdd9e32ca..07d8dc6d08b75b 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -63,12 +63,6 @@ static int use_builtin_rebase(void) return ret; } -static int apply_autostash(void) -{ - warning("TODO"); - return 0; -} - struct rebase_options { enum rebase_type type; const char *state_dir; @@ -98,6 +92,7 @@ struct rebase_options { int keep_empty; int autosquash; char *gpg_sign_opt; + int autostash; }; static int is_interactive(struct rebase_options *opts) @@ -224,13 +219,56 @@ static int read_basic_state(struct rebase_options *opts) return 0; } +static int apply_autostash(struct rebase_options *opts) +{ + const char *path = state_dir_path("autostash", opts); + struct strbuf autostash = STRBUF_INIT; + struct child_process stash_apply = CHILD_PROCESS_INIT; + + if (!file_exists(path)) + return 0; + + if (read_one(state_dir_path("autostash", opts), &autostash)) + return error(_("Could not read '%s'"), path); + argv_array_pushl(&stash_apply.args, + "stash", "apply", autostash.buf, NULL); + stash_apply.git_cmd = 1; + stash_apply.no_stderr = stash_apply.no_stdout = + stash_apply.no_stdin = 1; + if (!run_command(&stash_apply)) + printf(_("Applied autostash.\n")); + else { + struct argv_array args = ARGV_ARRAY_INIT; + int res = 0; + + argv_array_pushl(&args, + "stash", "store", "-m", "autostash", "-q", + autostash.buf, NULL); + if (run_command_v_opt(args.argv, RUN_GIT_CMD)) + res = error(_("Cannot store %s"), autostash.buf); + argv_array_clear(&args); + strbuf_release(&autostash); + if (res) + return res; + + fprintf(stderr, + _("Applying autostash resulted in conflicts.\n" + "Your changes are safe in the stash.\n" + "You can run \"git stash pop\" or \"git stash drop\" " + "at any time.\n")); + } + + strbuf_release(&autostash); + return 0; +} + static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; const char *argv_gc_auto[] = { "gc", "--auto", NULL }; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - apply_autostash(); + apply_autostash(opts); close_all_packs(the_repository->objects); /* * We ignore errors in 'gc --auto', since the @@ -345,7 +383,7 @@ static int run_specific_rebase(struct rebase_options *opts) } else if (status == 2) { struct strbuf dir = STRBUF_INIT; - apply_autostash(); + apply_autostash(opts); strbuf_addstr(&dir, opts->state_dir); remove_dir_recursively(&dir, 0); strbuf_release(&dir); @@ -480,6 +518,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.autostash")) { + opts->autostash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -647,6 +690,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("whitespace"), N_("passed to 'git apply'")), OPT_SET_INT('C', NULL, &opt_c, N_("passed to 'git apply'"), REBASE_AM), + OPT_BOOL(0, "autostash", &options.autostash, + N_("automatically stash/stash pop before and after")), OPT_END(), }; @@ -976,6 +1021,62 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_index(the_repository->index) < 0) die(_("could not read index")); + if (options.autostash) { + struct lock_file lock_file = LOCK_INIT; + int fd; + + fd = hold_locked_index(&lock_file, 0); + refresh_cache(REFRESH_QUIET); + if (0 <= fd) + update_index_if_able(&the_index, &lock_file); + rollback_lock_file(&lock_file); + + if (has_unstaged_changes(0) || has_uncommitted_changes(0)) { + const char *autostash = + state_dir_path("autostash", &options); + struct child_process stash = CHILD_PROCESS_INIT; + struct object_id oid; + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + + argv_array_pushl(&stash.args, + "stash", "create", "autostash", NULL); + stash.git_cmd = 1; + stash.no_stdin = 1; + strbuf_reset(&buf); + if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) + die(_("Cannot autostash")); + strbuf_trim_trailing_newline(&buf); + if (get_oid(buf.buf, &oid)) + die(_("Unexpected stash response: '%s'"), + buf.buf); + strbuf_reset(&buf); + strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); + + if (safe_create_leading_directories_const(autostash)) + die(_("Could not create directory for '%s'"), + options.state_dir); + write_file(autostash, "%s", buf.buf); + printf(_("Created autostash: %s\n"), buf.buf); + if (reset_head(&head->object.oid, "reset --hard", + NULL, 0) < 0) + die(_("could not reset --hard")); + printf(_("HEAD is now at %s"), + find_unique_abbrev(&head->object.oid, + DEFAULT_ABBREV)); + strbuf_reset(&buf); + pp_commit_easy(CMIT_FMT_ONELINE, head, &buf); + if (buf.len > 0) + printf(" %s", buf.buf); + putchar('\n'); + + if (discard_index(the_repository->index) < 0 || + read_index(the_repository->index) < 0) + die(_("could not read index")); + } + } + if (require_clean_work_tree("rebase", _("Please commit or stash them."), 1, 1)) { ret = 1; From cd96ca59917b7ca4c88dee18a151da6e4176184c Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:04 -0700 Subject: [PATCH 53/93] builtin rebase: support `--exec` This commit adds support for the `--exec` option which takes a shell command-line as argument. This argument will be appended as an `exec ` command after each line in the todo list that creates a commit in the final history. commands. Note: while the shell script version of `git rebase` assigned the empty string to `cmd` by default, we *unset* it here because the code looks nicer and it does not change the behavior. The `--exec` option requires `--interactive` machinery. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 07d8dc6d08b75b..2c73cc8a61f0c3 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -93,6 +93,7 @@ struct rebase_options { int autosquash; char *gpg_sign_opt; int autostash; + char *cmd; }; static int is_interactive(struct rebase_options *opts) @@ -346,6 +347,7 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); + add_var(&script_snippet, "cmd", opts->cmd); switch (opts->type) { case REBASE_AM: @@ -619,6 +621,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *gpg_sign = NULL; int opt_c = -1; struct string_list whitespace = STRING_LIST_INIT_NODUP; + struct string_list exec = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -692,6 +695,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) REBASE_AM), OPT_BOOL(0, "autostash", &options.autostash, N_("automatically stash/stash pop before and after")), + OPT_STRING_LIST('x', "exec", &exec, N_("exec"), + N_("add exec lines after each commit of the " + "editable list")), OPT_END(), }; @@ -916,6 +922,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } + if (exec.nr) { + int i; + + imply_interactive(&options, "--exec"); + + strbuf_reset(&buf); + for (i = 0; i < exec.nr; i++) + strbuf_addf(&buf, "exec %s\n", exec.items[i].string); + options.cmd = xstrdup(buf.buf); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1198,5 +1215,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_release(&revisions); free(options.head_name); free(options.gpg_sign_opt); + free(options.cmd); return ret; } From c7154a0806ab929f1855f4541b86ac918e66abae Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:05 -0700 Subject: [PATCH 54/93] builtin rebase: support `--allow-empty-message` option This commit introduces the `--allow-empty-message` option to `builtin/rebase.c`. The motivation behind this option is: if there are empty messages (which is not allowed in Git by default, but can be imported from different version control systems), the rebase will fail. Using `--allow-empty-message` overrides that behaviour which will allow the commits having empty messages to continue in rebase operation. Note: a very recent change made this the default in the shell scripted `git rebase`, therefore the builtin rebase does the same. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2c73cc8a61f0c3..730aefb7a9ea3c 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -94,6 +94,7 @@ struct rebase_options { char *gpg_sign_opt; int autostash; char *cmd; + int allow_empty_message; }; static int is_interactive(struct rebase_options *opts) @@ -348,6 +349,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(&script_snippet, "cmd", opts->cmd); + add_var(&script_snippet, "allow_empty_message", + opts->allow_empty_message ? "--allow-empty-message" : ""); switch (opts->type) { case REBASE_AM: @@ -598,6 +601,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .flags = REBASE_NO_QUIET, .git_am_opt = STRBUF_INIT, .allow_rerere_autoupdate = -1, + .allow_empty_message = 1, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -698,6 +702,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), + OPT_BOOL(0, "allow-empty-message", + &options.allow_empty_message, + N_("allow rebasing commits with empty messages")), OPT_END(), }; From 1b2177e0ab79c7c8a2c9024e44ca8abde8000951 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:07 -0700 Subject: [PATCH 55/93] builtin rebase: support --rebase-merges[=[no-]rebase-cousins] The mode to rebase non-linear branches is now supported by the builtin rebase, too. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 730aefb7a9ea3c..43b46998da0bc9 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -95,6 +95,7 @@ struct rebase_options { int autostash; char *cmd; int allow_empty_message; + int rebase_merges, rebase_cousins; }; static int is_interactive(struct rebase_options *opts) @@ -351,6 +352,10 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "cmd", opts->cmd); add_var(&script_snippet, "allow_empty_message", opts->allow_empty_message ? "--allow-empty-message" : ""); + add_var(&script_snippet, "rebase_merges", + opts->rebase_merges ? "t" : ""); + add_var(&script_snippet, "rebase_cousins", + opts->rebase_cousins ? "t" : ""); switch (opts->type) { case REBASE_AM: @@ -626,6 +631,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int opt_c = -1; struct string_list whitespace = STRING_LIST_INIT_NODUP; struct string_list exec = STRING_LIST_INIT_NODUP; + const char *rebase_merges = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -705,6 +711,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "allow-empty-message", &options.allow_empty_message, N_("allow rebasing commits with empty messages")), + {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, + N_("mode"), + N_("try to rebase merges instead of skipping them"), + PARSE_OPT_OPTARG, NULL, (intptr_t)""}, OPT_END(), }; @@ -940,6 +950,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.cmd = xstrdup(buf.buf); } + if (rebase_merges) { + if (!*rebase_merges) + ; /* default mode; do nothing */ + else if (!strcmp("rebase-cousins", rebase_merges)) + options.rebase_cousins = 1; + else if (strcmp("no-rebase-cousins", rebase_merges)) + die(_("Unknown mode: %s"), rebase_merges); + options.rebase_merges = 1; + imply_interactive(&options, "--rebase-merges"); + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From 7204beda817a9ac544a9be357662c348a9729243 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:08 -0700 Subject: [PATCH 56/93] merge-base --fork-point: extract libified function We need this functionality in the builtin rebase. Note: to make this function truly reusable, we have to switch the call get_merges_many_dirty() to get_merges_many() because we want the commit flags to be reset (otherwise, subsequent get_merge_bases() calls would obtain incorrect results). This did not matter when the function was called in `git rev-parse --fork-point` because in that command, the process definitely did not traverse any commits before exiting. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/merge-base.c | 81 ++++---------------------------------------- commit.c | 81 ++++++++++++++++++++++++++++++++++++++++++++ commit.h | 2 ++ 3 files changed, 89 insertions(+), 75 deletions(-) diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 08d91b1f0c0172..790ceaeed68fae 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -110,54 +110,12 @@ static int handle_is_ancestor(int argc, const char **argv) return 1; } -struct rev_collect { - struct commit **commit; - int nr; - int alloc; - unsigned int initial : 1; -}; - -static void add_one_commit(struct object_id *oid, struct rev_collect *revs) -{ - struct commit *commit; - - if (is_null_oid(oid)) - return; - - commit = lookup_commit(the_repository, oid); - if (!commit || - (commit->object.flags & TMP_MARK) || - parse_commit(commit)) - return; - - ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); - revs->commit[revs->nr++] = commit; - commit->object.flags |= TMP_MARK; -} - -static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *ident, timestamp_t timestamp, - int tz, const char *message, void *cbdata) -{ - struct rev_collect *revs = cbdata; - - if (revs->initial) { - revs->initial = 0; - add_one_commit(ooid, revs); - } - add_one_commit(noid, revs); - return 0; -} - static int handle_fork_point(int argc, const char **argv) { struct object_id oid; char *refname; + struct commit *derived, *fork_point; const char *commitname; - struct rev_collect revs; - struct commit *derived; - struct commit_list *bases; - int i, ret = 0; switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) { case 0: @@ -173,41 +131,14 @@ static int handle_fork_point(int argc, const char **argv) die("Not a valid object name: '%s'", commitname); derived = lookup_commit_reference(the_repository, &oid); - memset(&revs, 0, sizeof(revs)); - revs.initial = 1; - for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); - if (!revs.nr && !get_oid(refname, &oid)) - add_one_commit(&oid, &revs); + fork_point = get_fork_point(refname, derived); - for (i = 0; i < revs.nr; i++) - revs.commit[i]->object.flags &= ~TMP_MARK; - - bases = get_merge_bases_many_dirty(derived, revs.nr, revs.commit); - - /* - * There should be one and only one merge base, when we found - * a common ancestor among reflog entries. - */ - if (!bases || bases->next) { - ret = 1; - goto cleanup_return; - } - - /* And the found one must be one of the reflog entries */ - for (i = 0; i < revs.nr; i++) - if (&bases->item->object == &revs.commit[i]->object) - break; /* found */ - if (revs.nr <= i) { - ret = 1; /* not found */ - goto cleanup_return; - } - - printf("%s\n", oid_to_hex(&bases->item->object.oid)); + if (!fork_point) + return 1; -cleanup_return: - free_commit_list(bases); - return ret; + printf("%s\n", oid_to_hex(&fork_point->object.oid)); + return 0; } int cmd_merge_base(int argc, const char **argv, const char *prefix) diff --git a/commit.c b/commit.c index 449c1f4920cff6..2d94e0b19984e2 100644 --- a/commit.c +++ b/commit.c @@ -17,6 +17,7 @@ #include "sha1-lookup.h" #include "wt-status.h" #include "advice.h" +#include "refs.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -961,6 +962,86 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co return result; } +struct rev_collect { + struct commit **commit; + int nr; + int alloc; + unsigned int initial : 1; +}; + +static void add_one_commit(struct object_id *oid, struct rev_collect *revs) +{ + struct commit *commit; + + if (is_null_oid(oid)) + return; + + commit = lookup_commit(the_repository, oid); + if (!commit || + (commit->object.flags & TMP_MARK) || + parse_commit(commit)) + return; + + ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); + revs->commit[revs->nr++] = commit; + commit->object.flags |= TMP_MARK; +} + +static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *ident, timestamp_t timestamp, + int tz, const char *message, void *cbdata) +{ + struct rev_collect *revs = cbdata; + + if (revs->initial) { + revs->initial = 0; + add_one_commit(ooid, revs); + } + add_one_commit(noid, revs); + return 0; +} + +struct commit *get_fork_point(const char *refname, struct commit *commit) +{ + struct object_id oid; + struct rev_collect revs; + struct commit_list *bases; + int i; + struct commit *ret = NULL; + + memset(&revs, 0, sizeof(revs)); + revs.initial = 1; + for_each_reflog_ent(refname, collect_one_reflog_ent, &revs); + + if (!revs.nr && !get_oid(refname, &oid)) + add_one_commit(&oid, &revs); + + for (i = 0; i < revs.nr; i++) + revs.commit[i]->object.flags &= ~TMP_MARK; + + bases = get_merge_bases_many(commit, revs.nr, revs.commit); + + /* + * There should be one and only one merge base, when we found + * a common ancestor among reflog entries. + */ + if (!bases || bases->next) + goto cleanup_return; + + /* And the found one must be one of the reflog entries */ + for (i = 0; i < revs.nr; i++) + if (&bases->item->object == &revs.commit[i]->object) + break; /* found */ + if (revs.nr <= i) + goto cleanup_return; + + ret = bases->item; + +cleanup_return: + free_commit_list(bases); + return ret; +} + struct commit_list *get_octopus_merge_bases(struct commit_list *in) { struct commit_list *i, *j, *k, *ret = NULL; diff --git a/commit.h b/commit.h index da0db36eba2bf1..b34240017f616d 100644 --- a/commit.h +++ b/commit.h @@ -211,6 +211,8 @@ extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); /* To be used only when object flags after this call no longer matter */ extern struct commit_list *get_merge_bases_many_dirty(struct commit *one, int n, struct commit **twos); +struct commit *get_fork_point(const char *refname, struct commit *commit); + /* largest positive number a signed 32-bit integer can contain */ #define INFINITE_DEPTH 0x7fffffff From 4c76986fa60dde59d478a6ef9f34df0c62bcf8bf Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:09 -0700 Subject: [PATCH 57/93] builtin rebase: support `fork-point` option This commit adds support for `--fork-point` and `--no-fork-point`. This is converted as-is from `git-legacy-rebase.sh`. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 43b46998da0bc9..04d830408bc0f7 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -632,6 +632,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list whitespace = STRING_LIST_INIT_NODUP; struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; + int fork_point = -1; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -715,6 +716,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("mode"), N_("try to rebase merges instead of skipping them"), PARSE_OPT_OPTARG, NULL, (intptr_t)""}, + OPT_BOOL(0, "fork-point", &fork_point, + N_("use 'merge-base --fork-point' to refine upstream")), OPT_END(), }; @@ -1063,6 +1066,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } else BUG("unexpected number of arguments left to parse"); + if (fork_point > 0) { + struct commit *head = + lookup_commit_reference(the_repository, + &options.orig_head); + options.restrict_revision = + get_fork_point(options.upstream_name, head); + } + if (read_index(the_repository->index) < 0) die(_("could not read index")); From 9539517ea58e7cd80d1dfdf99760ee96a13a8f6c Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 21:21:30 +0545 Subject: [PATCH 58/93] builtin rebase: optionally auto-detect the upstream The `git rebase` command, when called without the `` command-line argument, automatically looks for the upstream branch configured for the current branch. With this commit, the builtin rebase learned that trick, too. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index b2cf779f1b42a5..7e2f68eb282254 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -622,6 +622,36 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, return 0; } +static void NORETURN error_on_missing_default_upstream(void) +{ + struct branch *current_branch = branch_get(NULL); + + printf(_("%s\n" + "Please specify which branch you want to rebase against.\n" + "See git-rebase(1) for details.\n" + "\n" + " git rebase ''\n" + "\n"), + current_branch ? _("There is no tracking information for " + "the current branch.") : + _("You are not currently on a branch.")); + + if (current_branch) { + const char *remote = current_branch->remote_name; + + if (!remote) + remote = _(""); + + printf(_("If you wish to set tracking information for this " + "branch you can do so with:\n" + "\n" + " git branch --set-upstream-to=%s/ %s\n" + "\n"), + remote, current_branch->name); + } + exit(1); +} + int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = { @@ -1057,9 +1087,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (!options.root) { - if (argc < 1) - die("TODO: handle @{upstream}"); - else { + if (argc < 1) { + struct branch *branch; + + branch = branch_get(NULL); + options.upstream_name = branch_get_upstream(branch, + NULL); + if (!options.upstream_name) + error_on_missing_default_upstream(); + if (fork_point < 0) + fork_point = 1; + } else { options.upstream_name = argv[0]; argc--; argv++; From df3490d91f8a5c5b193033e2ba1fe9cefad6a4b2 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 21:21:31 +0545 Subject: [PATCH 59/93] builtin rebase: optionally pass custom reflogs to reset_head() In the next patch, we will make use of that in the code that fast-forwards to `onto` whenever possible. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7e2f68eb282254..49856d9bf76387 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -431,7 +431,8 @@ static int run_specific_rebase(struct rebase_options *opts) #define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" static int reset_head(struct object_id *oid, const char *action, - const char *switch_to_branch, int detach_head) + const char *switch_to_branch, int detach_head, + const char *reflog_orig_head, const char *reflog_head) { struct object_id head_oid; struct tree_desc desc; @@ -506,20 +507,26 @@ static int reset_head(struct object_id *oid, const char *action, old_orig = &oid_old_orig; if (!get_oid("HEAD", &oid_orig)) { orig = &oid_orig; - strbuf_addstr(&msg, "updating ORIG_HEAD"); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, UPDATE_REFS_MSG_ON_ERR); } else if (old_orig) delete_ref(NULL, "ORIG_HEAD", old_orig, 0); - strbuf_setlen(&msg, prefix_len); - strbuf_addstr(&msg, "updating HEAD"); + if (!reflog_head) { + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + reflog_head = msg.buf; + } if (!switch_to_branch) - ret = update_ref(msg.buf, "HEAD", oid, orig, REF_NO_DEREF, + ret = update_ref(reflog_head, "HEAD", oid, orig, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR); else { ret = create_symref("HEAD", switch_to_branch, msg.buf); if (!ret) - ret = update_ref(msg.buf, "HEAD", oid, NULL, 0, + ret = update_ref(reflog_head, "HEAD", oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); } @@ -900,7 +907,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(&merge_rr); string_list_clear(&merge_rr, 1); - if (reset_head(NULL, "reset", NULL, 0) < 0) + if (reset_head(NULL, "reset", NULL, 0, NULL, NULL) < 0) die(_("could not discard worktree changes")); if (read_basic_state(&options)) exit(1); @@ -916,7 +923,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); if (reset_head(&options.orig_head, "reset", - options.head_name, 0) < 0) + options.head_name, 0, NULL, NULL) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); ret = finish_rebase(&options); @@ -1236,7 +1243,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) write_file(autostash, "%s", buf.buf); printf(_("Created autostash: %s\n"), buf.buf); if (reset_head(&head->object.oid, "reset --hard", - NULL, 0) < 0) + NULL, 0, NULL, NULL) < 0) die(_("could not reset --hard")); printf(_("HEAD is now at %s"), find_unique_abbrev(&head->object.oid, @@ -1290,7 +1297,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&buf, "rebase: checkout %s", options.switch_to); if (reset_head(&oid, "checkout", - options.head_name, 0) < 0) { + options.head_name, 0, + NULL, NULL) < 0) { ret = !!error(_("could not switch to " "%s"), options.switch_to); @@ -1355,7 +1363,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "it...\n")); strbuf_addf(&msg, "rebase: checkout %s", options.onto_name); - if (reset_head(&options.onto->object.oid, "checkout", NULL, 1)) + if (reset_head(&options.onto->object.oid, "checkout", NULL, 1, + NULL, msg.buf)) die(_("Could not detach HEAD")); strbuf_release(&msg); From 90f98a3be6338eb5723f0dbcee7370d5bdb2f7af Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 21:21:32 +0545 Subject: [PATCH 60/93] builtin rebase: fast-forward to onto if it is a proper descendant When trying to rebase onto a direct descendant of HEAD, we can take a shortcut and fast-forward instead. This commit makes it so. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 49856d9bf76387..7522be837c8951 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1368,6 +1368,24 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("Could not detach HEAD")); strbuf_release(&msg); + /* + * If the onto is a proper descendant of the tip of the branch, then + * we just fast-forwarded. + */ + strbuf_reset(&msg); + if (!oidcmp(&merge_base, &options.orig_head)) { + printf(_("Fast-forwarded %s to %s. \n"), + branch_name, options.onto_name); + strbuf_addf(&msg, "rebase finished: %s onto %s", + options.head_name ? options.head_name : "detached HEAD", + oid_to_hex(&options.onto->object.oid)); + reset_head(NULL, "Fast-forwarded", options.head_name, 0, + "HEAD", msg.buf); + strbuf_release(&msg); + ret = !!finish_rebase(&options); + goto cleanup; + } + strbuf_addf(&revisions, "%s..%s", options.root ? oid_to_hex(&options.onto->object.oid) : (options.restrict_revision ? From 3920b7d1361d9c5ac4d16d647ccfd9913a671caf Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 21:21:33 +0545 Subject: [PATCH 61/93] builtin rebase: show progress when connected to a terminal In this commit, we pass `--progress` to the `format-patch` command if stderr is connected to an interactive terminal, unless we're in quiet mode. This `--progress` option will be used in `format-patch` to show progress reports on stderr as patches are generated. Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7522be837c8951..600788bf725ff5 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -98,6 +98,7 @@ struct rebase_options { int allow_empty_message; int rebase_merges, rebase_cousins; char *strategy, *strategy_opts; + struct strbuf git_format_patch_opt; }; static int is_interactive(struct rebase_options *opts) @@ -379,6 +380,8 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); add_var(&script_snippet, "squash_onto", opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); + add_var(&script_snippet, "git_format_patch_opt", + opts->git_format_patch_opt.buf); switch (opts->type) { case REBASE_AM: @@ -667,6 +670,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) .git_am_opt = STRBUF_INIT, .allow_rerere_autoupdate = -1, .allow_empty_message = 1, + .git_format_patch_opt = STRBUF_INIT, }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; @@ -1069,6 +1073,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.root && !options.onto_name) imply_interactive(&options, "--root without --onto"); + if (isatty(2) && options.flags & REBASE_NO_QUIET) + strbuf_addstr(&options.git_format_patch_opt, " --progress"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From 89efc5751196e2e3749cab0fa3a6e944acb1bf10 Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:11 -0700 Subject: [PATCH 62/93] builtin rebase: add support for custom merge strategies When running a rebase in non-am mode, it uses the recursive merge to cherry-pick the commits, and the rebase command allows to configure the merge strategy to be used in this operation. This commit adds that support to the builtin rebase. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 04d830408bc0f7..c64d46afd53f3e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -96,6 +96,7 @@ struct rebase_options { char *cmd; int allow_empty_message; int rebase_merges, rebase_cousins; + char *strategy, *strategy_opts; }; static int is_interactive(struct rebase_options *opts) @@ -217,6 +218,22 @@ static int read_basic_state(struct rebase_options *opts) opts->gpg_sign_opt = xstrdup(buf.buf); } + if (file_exists(state_dir_path("strategy", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy", opts), &buf)) + return -1; + free(opts->strategy); + opts->strategy = xstrdup(buf.buf); + } + + if (file_exists(state_dir_path("strategy_opts", opts))) { + strbuf_reset(&buf); + if (read_one(state_dir_path("strategy_opts", opts), &buf)) + return -1; + free(opts->strategy_opts); + opts->strategy_opts = xstrdup(buf.buf); + } + strbuf_release(&buf); return 0; @@ -356,6 +373,8 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_merges ? "t" : ""); add_var(&script_snippet, "rebase_cousins", opts->rebase_cousins ? "t" : ""); + add_var(&script_snippet, "strategy", opts->strategy); + add_var(&script_snippet, "strategy_opts", opts->strategy_opts); switch (opts->type) { case REBASE_AM: @@ -633,6 +652,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; int fork_point = -1; + struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -718,6 +738,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)""}, OPT_BOOL(0, "fork-point", &fork_point, N_("use 'merge-base --fork-point' to refine upstream")), + OPT_STRING('s', "strategy", &options.strategy, + N_("strategy"), N_("use the given merge strategy")), + OPT_STRING_LIST('X', "strategy-option", &strategy_options, + N_("option"), + N_("pass the argument through to the merge " + "strategy")), OPT_END(), }; @@ -964,6 +990,37 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) imply_interactive(&options, "--rebase-merges"); } + if (strategy_options.nr) { + int i; + + if (!options.strategy) + options.strategy = "recursive"; + + strbuf_reset(&buf); + for (i = 0; i < strategy_options.nr; i++) + strbuf_addf(&buf, " --%s", + strategy_options.items[i].string); + options.strategy_opts = xstrdup(buf.buf); + } + + if (options.strategy) { + options.strategy = xstrdup(options.strategy); + switch (options.type) { + case REBASE_AM: + die(_("--strategy requires --merge or --interactive")); + case REBASE_MERGE: + case REBASE_INTERACTIVE: + case REBASE_PRESERVE_MERGES: + /* compatible */ + break; + case REBASE_UNSPECIFIED: + options.type = REBASE_MERGE; + break; + default: + BUG("unhandled rebase type (%d)", options.type); + } + } + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: From fabb7122a151d40bbd54790129a0bc44cc34685c Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Wed, 8 Aug 2018 21:21:34 +0545 Subject: [PATCH 63/93] builtin rebase: use no-op editor when interactive is "implied" Some options are only handled by the git-rebase--interactive backend, even if run non-interactively. For this awkward situation (run non-interactively, but use the interactive backend), the shell scripted version of `git rebase` introduced the concept of an "implied interactive rebase". All it does is to replace the editor by a dummy one (`:` is the Unix command that takes arbitrary command-line parameters, ignores them and simply exits with success). Signed-off-by: Pratik Karki Signed-off-by: Junio C Hamano --- builtin/rebase.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/builtin/rebase.c b/builtin/rebase.c index 600788bf725ff5..36d311b42fb9eb 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -383,6 +383,13 @@ static int run_specific_rebase(struct rebase_options *opts) add_var(&script_snippet, "git_format_patch_opt", opts->git_format_patch_opt.buf); + if (is_interactive(opts) && + !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { + strbuf_addstr(&script_snippet, + "GIT_EDITOR=:; export GIT_EDITOR; "); + opts->autosquash = 0; + } + switch (opts->type) { case REBASE_AM: backend = "git-rebase--am"; From 2ace22f362a52c8a6456a0c98d76da63d599db7b Mon Sep 17 00:00:00 2001 From: Pratik Karki Date: Tue, 4 Sep 2018 15:00:12 -0700 Subject: [PATCH 64/93] builtin rebase: support --root This option allows to rebase entire histories up to, and including, the root commit. The conversion from the shell script is straight-forward, apart from the fact that we do not have to write an empty tree in C. Signed-off-by: Pratik Karki Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- builtin/rebase.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c64d46afd53f3e..b2cf779f1b42a5 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -76,6 +76,7 @@ struct rebase_options { const char *revisions; const char *switch_to; int root; + struct object_id *squash_onto; struct commit *restrict_revision; int dont_finish_rebase; enum { @@ -375,6 +376,9 @@ static int run_specific_rebase(struct rebase_options *opts) opts->rebase_cousins ? "t" : ""); add_var(&script_snippet, "strategy", opts->strategy); add_var(&script_snippet, "strategy_opts", opts->strategy_opts); + add_var(&script_snippet, "rebase_root", opts->root ? "t" : ""); + add_var(&script_snippet, "squash_onto", + opts->squash_onto ? oid_to_hex(opts->squash_onto) : ""); switch (opts->type) { case REBASE_AM: @@ -653,6 +657,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *rebase_merges = NULL; int fork_point = -1; struct string_list strategy_options = STRING_LIST_INIT_NODUP; + struct object_id squash_onto; + char *squash_onto_name = NULL; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -744,6 +750,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("option"), N_("pass the argument through to the merge " "strategy")), + OPT_BOOL(0, "root", &options.root, + N_("rebase all reachable commits up to the root(s)")), OPT_END(), }; @@ -1021,6 +1029,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } + if (options.root && !options.onto_name) + imply_interactive(&options, "--root without --onto"); + switch (options.type) { case REBASE_MERGE: case REBASE_INTERACTIVE: @@ -1059,8 +1070,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!options.upstream) die(_("invalid upstream '%s'"), options.upstream_name); options.upstream_arg = options.upstream_name; - } else - die("TODO: upstream for --root"); + } else { + if (!options.onto_name) { + if (commit_tree("", 0, the_hash_algo->empty_tree, NULL, + &squash_onto, NULL, NULL) < 0) + die(_("Could not create new root commit")); + options.squash_onto = &squash_onto; + options.onto_name = squash_onto_name = + xstrdup(oid_to_hex(&squash_onto)); + } + options.upstream_name = NULL; + options.upstream = NULL; + if (argc > 1) + usage_with_options(builtin_rebase_usage, + builtin_rebase_options); + options.upstream_arg = "--root"; + } /* Make sure the branch to rebase onto is valid. */ if (!options.onto_name) @@ -1208,6 +1233,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) */ if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && !is_interactive(&options) && !options.restrict_revision && + options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { int flag; @@ -1312,5 +1338,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) free(options.head_name); free(options.gpg_sign_opt); free(options.cmd); + free(squash_onto_name); return ret; } From ed457e265673f47bcc940a4706432a769ed90fef Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 10 Aug 2018 18:51:28 +0200 Subject: [PATCH 65/93] sequencer: make three functions and an enum from sequencer.c public This makes rebase_path_todo(), get_missing_commit_check_level(), write_message() and the enum check_level accessible outside sequencer.c, renames check_level to missing_commit_check_level, and prefixes its value names by MISSING_COMMIT_ to avoid namespace pollution. This function and this enum will eventually be moved to rebase-interactive.c and become static again, so no special attention was given to the naming. This will be needed for the rewrite of append_todo_help() from shell to C, as it will be in a new library source file, rebase-interactive.c. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- sequencer.c | 26 +++++++++++--------------- sequencer.h | 11 +++++++++++ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/sequencer.c b/sequencer.c index dc2c58d464c14b..8f65432a92dd51 100644 --- a/sequencer.c +++ b/sequencer.c @@ -52,7 +52,7 @@ static GIT_PATH_FUNC(rebase_path, "rebase-merge") * the lines are processed, they are removed from the front of this * file and written to the tail of 'done'. */ -static GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") +GIT_PATH_FUNC(rebase_path_todo, "rebase-merge/git-rebase-todo") /* * The rebase command lines that have already been processed. A line * is moved here when it is first handled, before any associated user @@ -373,8 +373,8 @@ static void print_advice(int show_hint, struct replay_opts *opts) } } -static int write_message(const void *buf, size_t len, const char *filename, - int append_eol) +int write_message(const void *buf, size_t len, const char *filename, + int append_eol) { struct lock_file msg_file = LOCK_INIT; @@ -4407,24 +4407,20 @@ int transform_todos(unsigned flags) return i; } -enum check_level { - CHECK_IGNORE = 0, CHECK_WARN, CHECK_ERROR -}; - -static enum check_level get_missing_commit_check_level(void) +enum missing_commit_check_level get_missing_commit_check_level(void) { const char *value; if (git_config_get_value("rebase.missingcommitscheck", &value) || !strcasecmp("ignore", value)) - return CHECK_IGNORE; + return MISSING_COMMIT_CHECK_IGNORE; if (!strcasecmp("warn", value)) - return CHECK_WARN; + return MISSING_COMMIT_CHECK_WARN; if (!strcasecmp("error", value)) - return CHECK_ERROR; + return MISSING_COMMIT_CHECK_ERROR; warning(_("unrecognized setting %s for option " "rebase.missingCommitsCheck. Ignoring."), value); - return CHECK_IGNORE; + return MISSING_COMMIT_CHECK_IGNORE; } define_commit_slab(commit_seen, unsigned char); @@ -4436,7 +4432,7 @@ define_commit_slab(commit_seen, unsigned char); */ int check_todo_list(void) { - enum check_level check_level = get_missing_commit_check_level(); + enum missing_commit_check_level check_level = get_missing_commit_check_level(); struct strbuf todo_file = STRBUF_INIT; struct todo_list todo_list = TODO_LIST_INIT; struct strbuf missing = STRBUF_INIT; @@ -4453,7 +4449,7 @@ int check_todo_list(void) advise_to_edit_todo = res = parse_insn_buffer(todo_list.buf.buf, &todo_list); - if (res || check_level == CHECK_IGNORE) + if (res || check_level == MISSING_COMMIT_CHECK_IGNORE) goto leave_check; /* Mark the commits in git-rebase-todo as seen */ @@ -4488,7 +4484,7 @@ int check_todo_list(void) if (!missing.len) goto leave_check; - if (check_level == CHECK_ERROR) + if (check_level == MISSING_COMMIT_CHECK_ERROR) advise_to_edit_todo = res = 1; fprintf(stderr, diff --git a/sequencer.h b/sequencer.h index c751c9d6e4f78e..63e18a10351928 100644 --- a/sequencer.h +++ b/sequencer.h @@ -8,6 +8,7 @@ struct commit; const char *git_path_commit_editmsg(void); const char *git_path_seq_dir(void); +const char *rebase_path_todo(void); #define APPEND_SIGNOFF_DEDUP (1u << 0) @@ -62,6 +63,15 @@ struct replay_opts { }; #define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT } +enum missing_commit_check_level { + MISSING_COMMIT_CHECK_IGNORE = 0, + MISSING_COMMIT_CHECK_WARN, + MISSING_COMMIT_CHECK_ERROR +}; + +int write_message(const void *buf, size_t len, const char *filename, + int append_eol); + /* Call this to setup defaults before parsing command line options */ void sequencer_init_config(struct replay_opts *opts); int sequencer_pick_revisions(struct replay_opts *opts); @@ -84,6 +94,7 @@ int sequencer_make_script(FILE *out, int argc, const char **argv, int sequencer_add_exec_commands(const char *command); int transform_todos(unsigned flags); +enum missing_commit_check_level get_missing_commit_check_level(void); int check_todo_list(void); int skip_unnecessary_picks(void); int rearrange_squash(void); From ba4d032836de657ab6c277ca4b4d6fd79523ca9e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 10 Aug 2018 18:51:29 +0200 Subject: [PATCH 66/93] rebase -i: rewrite append_todo_help() in C This rewrites append_todo_help() from shell to C. It also incorporates some parts of initiate_action() and complete_action() that also write help texts to the todo file. This also introduces the source file rebase-interactive.c. This file will contain functions necessary for interactive rebase that are too specific for the sequencer, and is part of libgit.a. Two flags are added to rebase--helper.c: one to call append_todo_help() (`--append-todo-help`), and another one to tell append_todo_help() to write the help text suited for the edit-todo mode (`--write-edit-todo`). Finally, append_todo_help() is removed from git-rebase--interactive.sh to use `rebase--helper --append-todo-help` instead. Signed-off-by: Alban Gruin Signed-off-by: Junio C Hamano --- Makefile | 1 + builtin/rebase--helper.c | 11 ++++-- git-rebase--interactive.sh | 52 ++--------------------------- rebase-interactive.c | 68 ++++++++++++++++++++++++++++++++++++++ rebase-interactive.h | 6 ++++ 5 files changed, 86 insertions(+), 52 deletions(-) create mode 100644 rebase-interactive.c create mode 100644 rebase-interactive.h diff --git a/Makefile b/Makefile index 5a969f5830a410..e819e4dcb62c71 100644 --- a/Makefile +++ b/Makefile @@ -935,6 +935,7 @@ LIB_OBJS += quote.o LIB_OBJS += range-diff.o LIB_OBJS += reachable.o LIB_OBJS += read-cache.o +LIB_OBJS += rebase-interactive.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += refs/files-backend.o diff --git a/builtin/rebase--helper.c b/builtin/rebase--helper.c index f7c2a5fdc815a8..05e73e71d42b09 100644 --- a/builtin/rebase--helper.c +++ b/builtin/rebase--helper.c @@ -3,6 +3,7 @@ #include "config.h" #include "parse-options.h" #include "sequencer.h" +#include "rebase-interactive.h" static const char * const builtin_rebase_helper_usage[] = { N_("git rebase--helper []"), @@ -12,12 +13,12 @@ static const char * const builtin_rebase_helper_usage[] = { int cmd_rebase__helper(int argc, const char **argv, const char *prefix) { struct replay_opts opts = REPLAY_OPTS_INIT; - unsigned flags = 0, keep_empty = 0, rebase_merges = 0; + unsigned flags = 0, keep_empty = 0, rebase_merges = 0, write_edit_todo = 0; int abbreviate_commands = 0, rebase_cousins = -1; enum { CONTINUE = 1, ABORT, MAKE_SCRIPT, SHORTEN_OIDS, EXPAND_OIDS, CHECK_TODO_LIST, SKIP_UNNECESSARY_PICKS, REARRANGE_SQUASH, - ADD_EXEC + ADD_EXEC, APPEND_TODO_HELP } command = 0; struct option options[] = { OPT_BOOL(0, "ff", &opts.allow_ff, N_("allow fast-forward")), @@ -27,6 +28,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "rebase-merges", &rebase_merges, N_("rebase merge commits")), OPT_BOOL(0, "rebase-cousins", &rebase_cousins, N_("keep original branch points of cousins")), + OPT_BOOL(0, "write-edit-todo", &write_edit_todo, + N_("append the edit-todo message to the todo-list")), OPT_CMDMODE(0, "continue", &command, N_("continue rebase"), CONTINUE), OPT_CMDMODE(0, "abort", &command, N_("abort rebase"), @@ -45,6 +48,8 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) N_("rearrange fixup/squash lines"), REARRANGE_SQUASH), OPT_CMDMODE(0, "add-exec-commands", &command, N_("insert exec commands in todo list"), ADD_EXEC), + OPT_CMDMODE(0, "append-todo-help", &command, + N_("insert the help in the todo list"), APPEND_TODO_HELP), OPT_END() }; @@ -84,5 +89,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix) return !!rearrange_squash(); if (command == ADD_EXEC && argc == 2) return !!sequencer_add_exec_commands(argv[1]); + if (command == APPEND_TODO_HELP && argc == 1) + return !!append_todo_help(write_edit_todo, keep_empty); usage_with_options(builtin_rebase_helper_usage, options); } diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 299ded21375ed3..94c23a7af2291f 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -39,38 +39,6 @@ comment_for_reflog () { esac } -append_todo_help () { - gettext " -Commands: -p, pick = use commit -r, reword = use commit, but edit the commit message -e, edit = use commit, but stop for amending -s, squash = use commit, but meld into previous commit -f, fixup = like \"squash\", but discard this commit's log message -x, exec = run command (the rest of the line) using shell -d, drop = remove commit -l, label