From 41fef97f02ec4ddec07f9fb5269ead500517ea70 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:11 +0100 Subject: [PATCH 01/63] fixup! stash: avoid unnecessary reset_tree() call In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/stash.c b/builtin/stash.c index 794fba6e2d..14dd9f442a 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -404,7 +404,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (refresh_cache(REFRESH_QUIET)) return -1; - if (write_cache_as_tree(&c_tree, 0, NULL)) + 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) { From c6f89165386877cf606eada6bba9654bcc858b34 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:11 +0100 Subject: [PATCH 02/63] fixup! stash: discard in-process cache after spawning index-changing processes In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 14dd9f442a..39f5a29668 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1333,7 +1333,6 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, goto done; } } - discard_cache(); if (ps.nr) { struct child_process cp_add = CHILD_PROCESS_INIT; struct child_process cp_diff = CHILD_PROCESS_INIT; @@ -1429,8 +1428,6 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, if (keep_index < 1) { struct child_process cp = CHILD_PROCESS_INIT; - discard_cache(); - cp.git_cmd = 1; argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); add_pathspecs(&cp.args, ps); From 017b95da3931eb565fe486d259044ea6ade54cdd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:12 +0100 Subject: [PATCH 03/63] fixup! strbuf_vinsertf: provide the correct buffer size to vsnprintf In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- strbuf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strbuf.c b/strbuf.c index 87ecf7f975..bfbbdadbf3 100644 --- a/strbuf.c +++ b/strbuf.c @@ -270,7 +270,7 @@ void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, va_list ap) memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos); /* vsnprintf() will append a NUL, overwriting one of our characters */ save = sb->buf[pos + len]; - len2 = vsnprintf(sb->buf + pos, len + 1, fmt, ap); + len2 = vsnprintf(sb->buf + pos, sb->alloc - sb->len, fmt, ap); sb->buf[pos + len] = save; if (len2 != len) BUG("your vsnprintf is broken (returns inconsistent lengths)"); From b95677fed3f05ba49038111b6603fdeb3382e459 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:12 +0100 Subject: [PATCH 04/63] fixup! stash: fix segmentation fault when files were added with intent In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash.c | 3 +-- t/t3903-stash.sh | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 39f5a29668..d5998316ea 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1050,8 +1050,6 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) struct strbuf diff_output = STRBUF_INIT; struct index_state istate = { NULL }; - init_revisions(&rev, NULL); - set_alternate_index_output(stash_index_path.buf); if (reset_tree(&info->i_tree, 0, 0)) { ret = -1; @@ -1059,6 +1057,7 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) } set_alternate_index_output(NULL); + init_revisions(&rev, NULL); rev.prune_data = ps; rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = add_diff_to_buf; diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7dfa3a8038..b67d7a1120 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -287,14 +287,6 @@ test_expect_success 'stash an added file' ' test new = "$(cat file3)" ' -test_expect_success 'stash --intent-to-add file' ' - git reset --hard && - echo new >file4 && - git add --intent-to-add file4 && - test_when_finished "git rm -f file4" && - test_must_fail git stash -' - test_expect_success 'stash rm then recreate' ' git reset --hard && git rm file && From 1acb791d83e7b9f9494b9c26e925b2bb4a2a1a14 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:12 +0100 Subject: [PATCH 05/63] fixup! tests: add a special setup where stash.useBuiltin is off In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash.c | 5 +---- t/README | 4 ---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index d5998316ea..c98d786a1c 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1520,10 +1520,7 @@ static int use_builtin_stash(void) { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf out = STRBUF_INIT; - int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1); - - if (env != -1) - return env; + int ret; argv_array_pushl(&cp.args, "config", "--bool", "stash.usebuiltin", NULL); diff --git a/t/README b/t/README index 51e63f4eec..886bbec5bc 100644 --- a/t/README +++ b/t/README @@ -383,10 +383,6 @@ GIT_TEST_REBASE_USE_BUILTIN=, when false, disables the builtin version of git-rebase. See 'rebase.useBuiltin' in git-config(1). -GIT_TEST_STASH_USE_BUILTIN=, when false, disables the -built-in version of git-stash. See 'stash.useBuiltin' in -git-config(1). - GIT_TEST_INDEX_THREADS= enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the From 1a179124f1f23ec3feee7ed684fc8e845259ed44 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:13 +0100 Subject: [PATCH 06/63] fixup! stash: optionally use the scripted version again In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- .gitignore | 1 - Makefile | 1 - builtin/stash.c | 35 ----------------------------- git-sh-setup.sh | 1 - git-legacy-stash.sh => git-stash.sh | 34 +++------------------------- git.c | 7 +----- 6 files changed, 4 insertions(+), 75 deletions(-) rename git-legacy-stash.sh => git-stash.sh (97%) diff --git a/.gitignore b/.gitignore index 766e80e65a..7374587f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,6 @@ /git-interpret-trailers /git-instaweb /git-legacy-rebase -/git-legacy-stash /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index 29120fbfaf..da60276963 100644 --- a/Makefile +++ b/Makefile @@ -633,7 +633,6 @@ SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-legacy-rebase.sh -SCRIPT_SH += git-legacy-stash.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-submodule.sh diff --git a/builtin/stash.c b/builtin/stash.c index c98d786a1c..b2b90d7634 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -14,7 +14,6 @@ #include "revision.h" #include "log-tree.h" #include "diffcore.h" -#include "exec-cmd.h" #define INCLUDE_ALL_FILES 2 @@ -1516,26 +1515,6 @@ static int save_stash(int argc, const char **argv, const char *prefix) return ret; } -static int use_builtin_stash(void) -{ - struct child_process cp = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - int ret; - - argv_array_pushl(&cp.args, - "config", "--bool", "stash.usebuiltin", NULL); - cp.git_cmd = 1; - if (capture_command(&cp, &out, 6)) { - strbuf_release(&out); - return 1; - } - - strbuf_trim(&out); - ret = !strcmp("true", out.buf); - strbuf_release(&out); - return ret; -} - int cmd_stash(int argc, const char **argv, const char *prefix) { int i = -1; @@ -1547,20 +1526,6 @@ int cmd_stash(int argc, const char **argv, const char *prefix) OPT_END() }; - if (!use_builtin_stash()) { - const char *path = mkpath("%s/git-legacy-stash", - git_exec_path()); - - if (sane_execvp(path, (char **)argv) < 0) - die_errno(_("could not exec %s"), path); - else - BUG("sane_execvp() returned???"); - } - - prefix = setup_git_directory(); - trace_repo_setup(prefix); - setup_work_tree(); - git_config(git_diff_basic_config, NULL); argc = parse_options(argc, argv, prefix, options, git_stash_usage, diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 10d9764185..378928518b 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -101,7 +101,6 @@ $LONG_USAGE")" case "$1" in -h) echo "$LONG_USAGE" - case "$0" in *git-legacy-stash) exit 129;; esac exit esac fi diff --git a/git-legacy-stash.sh b/git-stash.sh similarity index 97% rename from git-legacy-stash.sh rename to git-stash.sh index 8a8c4a9270..789ce2f41d 100755 --- a/git-legacy-stash.sh +++ b/git-stash.sh @@ -80,28 +80,6 @@ clear_stash () { fi } -maybe_quiet () { - case "$1" in - --keep-stdout) - shift - if test -n "$GIT_QUIET" - then - eval "$@" 2>/dev/null - else - eval "$@" - fi - ;; - *) - if test -n "$GIT_QUIET" - then - eval "$@" >/dev/null 2>&1 - else - eval "$@" - fi - ;; - esac -} - create_stash () { prepare_fallback_ident @@ -134,18 +112,15 @@ create_stash () { done git update-index -q --refresh - if maybe_quiet no_changes "$@" + if no_changes "$@" then exit 0 fi # state of the base commit - if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD) + if b_commit=$(git rev-parse --verify HEAD) then head=$(git rev-list --oneline -n 1 HEAD --) - elif test -n "$GIT_QUIET" - then - exit 1 else die "$(gettext "You do not have the initial commit yet")" fi @@ -340,7 +315,7 @@ push_stash () { test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1 git update-index -q --refresh - if maybe_quiet no_changes "$@" + if no_changes "$@" then say "$(gettext "No local changes to save")" exit 0 @@ -395,9 +370,6 @@ save_stash () { while test $# != 0 do case "$1" in - -q|--quiet) - GIT_QUIET=t - ;; --) shift break diff --git a/git.c b/git.c index 37a21c0b0a..725fd2ce3a 100644 --- a/git.c +++ b/git.c @@ -555,12 +555,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 }, - /* - * NEEDSWORK: Until the builtin stash is thoroughly robust and no - * longer needs redirection to the stash shell script this is kept as - * is, then should be changed to RUN_SETUP | NEED_WORK_TREE - */ - { "stash", cmd_stash }, + { "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 ab8c1b3341854a7c196ce42a410e28fbbb5f1727 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:13 +0100 Subject: [PATCH 07/63] fixup! stash: add back the original, scripted `git stash` In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- git-stash.sh | 769 --------------------------------------------------- 1 file changed, 769 deletions(-) delete mode 100755 git-stash.sh diff --git a/git-stash.sh b/git-stash.sh deleted file mode 100755 index 789ce2f41d..0000000000 --- a/git-stash.sh +++ /dev/null @@ -1,769 +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 - -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 -- "$@" -} - -prepare_fallback_ident () { - if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1 - then - GIT_AUTHOR_NAME="git stash" - GIT_AUTHOR_EMAIL=git@stash - GIT_COMMITTER_NAME="git stash" - GIT_COMMITTER_EMAIL=git@stash - export GIT_AUTHOR_NAME - export GIT_AUTHOR_EMAIL - export GIT_COMMITTER_NAME - export GIT_COMMITTER_EMAIL - fi -} - -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 () { - - prepare_fallback_ident - - 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")" -} - -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= - 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" -- "$@" - store_stash -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 -} - -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 "$@" - - 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 -} - -# -# 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" -# - -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")" - } -} - -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 () { - - 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 -} - -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 -} - -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' -# 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 - list_stash "$@" - ;; -show) - shift - show_stash "$@" - ;; -save) - shift - save_stash "$@" - ;; -push) - shift - push_stash "$@" - ;; -apply) - shift - apply_stash "$@" - ;; -clear) - shift - clear_stash "$@" - ;; -create) - shift - create_stash -m "$*" && echo "$w_commit" - ;; -store) - shift - store_stash "$@" - ;; -drop) - shift - drop_stash "$@" - ;; -pop) - shift - pop_stash "$@" - ;; -branch) - shift - apply_to_branch "$@" - ;; -*) - case $# in - 0) - push_stash && - say "$(gettext "(To restore them type \"git stash apply\")")" - ;; - *) - usage - esac - ;; -esac From 19ba7d4a3b3f1bd405a4f901c38010d8a4acc795 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:13 +0100 Subject: [PATCH 08/63] fixup! stash: convert `stash--helper.c` into `stash.c` In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- .gitignore | 1 + Makefile | 3 +- builtin.h | 2 +- builtin/{stash.c => stash--helper.c} | 154 ++++++++++++--------------- git-stash.sh | 153 ++++++++++++++++++++++++++ git.c | 2 +- 6 files changed, 224 insertions(+), 91 deletions(-) rename builtin/{stash.c => stash--helper.c} (91%) create mode 100755 git-stash.sh diff --git a/.gitignore b/.gitignore index 7374587f9d..32765a6ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -162,6 +162,7 @@ /git-show-ref /git-stage /git-stash +/git-stash--helper /git-status /git-stripspace /git-submodule diff --git a/Makefile b/Makefile index da60276963..5c4b6e6ae5 100644 --- a/Makefile +++ b/Makefile @@ -635,6 +635,7 @@ SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-legacy-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 @@ -1137,7 +1138,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.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 b78ab6e30b..ff4460aff7 100644 --- a/builtin.h +++ b/builtin.h @@ -225,7 +225,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(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.c b/builtin/stash--helper.c similarity index 91% rename from builtin/stash.c rename to builtin/stash--helper.c index b2b90d7634..a71bbfd80d 100644 --- a/builtin/stash.c +++ b/builtin/stash--helper.c @@ -17,70 +17,75 @@ #define INCLUDE_ALL_FILES 2 -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" +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" " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" " [--] [...]]"), - N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + N_("git stash--helper save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] []"), NULL }; -static const char * const git_stash_list_usage[] = { - N_("git stash list []"), +static const char * const git_stash_helper_list_usage[] = { + N_("git stash--helper list []"), NULL }; -static const char * const git_stash_show_usage[] = { - N_("git stash show [] []"), +static const char * const git_stash_helper_show_usage[] = { + N_("git stash--helper show [] []"), NULL }; -static const char * const git_stash_drop_usage[] = { - N_("git stash drop [-q|--quiet] []"), +static const char * const git_stash_helper_drop_usage[] = { + N_("git stash--helper drop [-q|--quiet] []"), NULL }; -static const char * const git_stash_pop_usage[] = { - N_("git stash pop [--index] [-q|--quiet] []"), +static const char * const git_stash_helper_pop_usage[] = { + N_("git stash--helper pop [--index] [-q|--quiet] []"), NULL }; -static const char * const git_stash_apply_usage[] = { - N_("git stash apply [--index] [-q|--quiet] []"), +static const char * const git_stash_helper_apply_usage[] = { + N_("git stash--helper apply [--index] [-q|--quiet] []"), NULL }; -static const char * const git_stash_branch_usage[] = { - N_("git stash branch []"), +static const char * const git_stash_helper_branch_usage[] = { + N_("git stash--helper branch []"), NULL }; -static const char * const git_stash_clear_usage[] = { - N_("git stash clear"), +static const char * const git_stash_helper_clear_usage[] = { + N_("git stash--helper clear"), NULL }; -static const char * const git_stash_store_usage[] = { - N_("git stash store [-m|--message ] [-q|--quiet] "), +static const char * const git_stash_helper_store_usage[] = { + N_("git stash--helper store [-m|--message ] [-q|--quiet] "), NULL }; -static const char * const git_stash_push_usage[] = { - N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" +static const char * const git_stash_helper_create_usage[] = { + N_("git stash--helper 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" " [-u|--include-untracked] [-a|--all] [-m|--message ]\n" " [--] [...]]"), NULL }; -static const char * const git_stash_save_usage[] = { - N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" +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 }; @@ -217,7 +222,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_clear_usage, + git_stash_helper_clear_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (argc) @@ -522,7 +527,7 @@ static int apply_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_apply_usage, 0); + git_stash_helper_apply_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; @@ -595,7 +600,7 @@ static int drop_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_drop_usage, 0); + git_stash_helper_drop_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; @@ -621,7 +626,7 @@ static int pop_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_pop_usage, 0); + git_stash_helper_pop_usage, 0); if (get_stash_info(&info, argc, argv)) return -1; @@ -648,7 +653,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_branch_usage, 0); + git_stash_helper_branch_usage, 0); if (!argc) { fprintf_ln(stderr, _("No branch name specified")); @@ -683,7 +688,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_list_usage, + git_stash_helper_list_usage, PARSE_OPT_KEEP_UNKNOWN); if (!ref_exists(ref_stash)) @@ -763,7 +768,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_show_usage, options); + usage_with_options(git_stash_helper_show_usage, options); } rev.diffopt.flags.recursive = 1; @@ -809,7 +814,7 @@ static int store_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_store_usage, + git_stash_helper_store_usage, PARSE_OPT_KEEP_UNKNOWN); if (argc != 1) { @@ -1224,18 +1229,29 @@ done: 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 strbuf stash_msg_buf = STRBUF_INIT; 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() + }; - /* Starting with argv[1], since argv[0] is "create" */ - strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_create_usage, + 0); memset(&ps, 0, sizeof(ps)); if (!check_changes_tracked_files(ps)) return 0; + strbuf_addstr(&stash_msg_buf, stash_msg); if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0))) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1465,10 +1481,9 @@ static int push_stash(int argc, const char **argv, const char *prefix) OPT_END() }; - if (argc) - argc = parse_options(argc, argv, prefix, options, - git_stash_push_usage, - 0); + 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, @@ -1501,7 +1516,7 @@ static int save_stash(int argc, const char **argv, const char *prefix) }; argc = parse_options(argc, argv, prefix, options, - git_stash_save_usage, + git_stash_helper_save_usage, PARSE_OPT_KEEP_DASHDASH); if (argc) @@ -1515,12 +1530,10 @@ static int save_stash(int argc, const char **argv, const char *prefix) return ret; } -int cmd_stash(int argc, const char **argv, const char *prefix) +int cmd_stash__helper(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() @@ -1528,16 +1541,16 @@ int cmd_stash(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); - argc = parse_options(argc, argv, prefix, options, git_stash_usage, + 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) - return !!push_stash(0, NULL, prefix); - else if (!strcmp(argv[0], "apply")) + if (argc < 1) + 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); @@ -1559,42 +1572,7 @@ int cmd_stash(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 (starts_with(argv[i], "-m") || - starts_with(argv[i], "--message=")) - continue; - - usage_with_options(git_stash_usage, options); - } - } - - argv_array_push(&args, "push"); - argv_array_pushv(&args, argv); - return !!push_stash(args.argc, args.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 new file mode 100755 index 0000000000..695f1feba3 --- /dev/null +++ b/git-stash.sh @@ -0,0 +1,153 @@ +#!/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 725fd2ce3a..c041c6e057 100644 --- a/git.c +++ b/git.c @@ -555,7 +555,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", cmd_stash, 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 7f224087fec9e2e8eb37590113dd0e47990e3dad Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:14 +0100 Subject: [PATCH 09/63] fixup! stash: replace all `write-tree` child processes with API calls In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index a71bbfd80d..d3c7748fd9 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -952,8 +952,9 @@ 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 index_state istate = { NULL }; + 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", @@ -968,11 +969,15 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, goto done; } - if (write_index_as_tree(&info->u_tree, &istate, stash_index_path.buf, 0, - NULL)) { + 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)) { @@ -981,8 +986,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; } @@ -991,10 +996,11 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, struct strbuf *out_patch, int quiet) { 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); @@ -1020,12 +1026,17 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, } /* State of the working tree. */ - if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, - NULL)) { + 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); @@ -1041,7 +1052,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, } done: - discard_index(&istate); + strbuf_release(&out); remove_path(stash_index_path.buf); return ret; } @@ -1051,8 +1062,9 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) int ret = 0; struct rev_info rev; 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 index_state istate = { NULL }; set_alternate_index_output(stash_index_path.buf); if (reset_tree(&info->i_tree, 0, 0)) { @@ -1091,15 +1103,20 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) goto done; } - if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, - NULL)) { + 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: - discard_index(&istate); UNLEAK(rev); + strbuf_release(&out); object_array_clear(&rev.pending); strbuf_release(&diff_output); remove_path(stash_index_path.buf); From 2b4813df50ef847876d95987d082482eac70732f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:14 +0100 Subject: [PATCH 10/63] fixup! stash: optimize `get_untracked_files()` and `check_changes()` In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 53 ++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index d3c7748fd9..dc4ed52c96 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -886,18 +886,18 @@ static int get_untracked_files(struct pathspec ps, int include_untracked, } /* - * The return value of `check_changes_tracked_files()` can be: + * 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_tracked_files(struct pathspec ps) +static int check_changes(struct pathspec ps, int include_untracked) { int result; struct rev_info rev; struct object_id dummy; + struct strbuf out = STRBUF_INIT; /* No initial commit. */ if (get_oid("HEAD", &dummy)) @@ -925,26 +925,14 @@ static int check_changes_tracked_files(struct pathspec ps) if (diff_result_code(&rev.diffopt, result)) return 1; - return 0; -} - -/* - * The function will fill `untracked_files` with the names of untracked files - * It will return 1 if there were any changes and 0 if there were not. - */ - -static int check_changes(struct pathspec ps, int include_untracked, - struct strbuf *untracked_files) -{ - int ret = 0; - if (check_changes_tracked_files(ps)) - ret = 1; - if (include_untracked && get_untracked_files(ps, include_untracked, - untracked_files)) - ret = 1; + &out)) { + strbuf_release(&out); + return 1; + } - return ret; + strbuf_release(&out); + return 0; } static int save_untracked_files(struct stash_info *info, struct strbuf *msg, @@ -1155,7 +1143,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, head_commit = lookup_commit(the_repository, &info->b_commit); } - if (!check_changes(ps, include_untracked, &untracked_files)) { + if (!check_changes(ps, include_untracked)) { ret = 1; goto done; } @@ -1180,7 +1168,8 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, goto done; } - if (include_untracked) { + if (include_untracked && get_untracked_files(ps, include_untracked, + &untracked_files)) { if (save_untracked_files(info, &msg, untracked_files)) { if (!quiet) fprintf_ln(stderr, _("Cannot save " @@ -1265,15 +1254,20 @@ static int create_stash(int argc, const char **argv, const char *prefix) 0); memset(&ps, 0, sizeof(ps)); - if (!check_changes_tracked_files(ps)) - return 0; - strbuf_addstr(&stash_msg_buf, stash_msg); - if (!(ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0))) + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info, + NULL, 0); + + if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); strbuf_release(&stash_msg_buf); - return ret; + + /* + * ret can be 1 if there were no changes. In this case, we should + * not error out. + */ + return ret < 0; } static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, @@ -1283,7 +1277,6 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, struct stash_info info; struct strbuf patch = STRBUF_INIT; struct strbuf stash_msg_buf = STRBUF_INIT; - struct strbuf untracked_files = STRBUF_INIT; if (patch_mode && keep_index == -1) keep_index = 1; @@ -1318,7 +1311,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, goto done; } - if (!check_changes(ps, include_untracked, &untracked_files)) { + if (!check_changes(ps, include_untracked)) { if (!quiet) printf_ln(_("No local changes to save")); goto done; From de276357c910aeb42ca7e04f49907a9398ed9cdf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:14 +0100 Subject: [PATCH 11/63] fixup! stash: convert save to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 50 ------ git-stash.sh | 328 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 326 insertions(+), 52 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index dc4ed52c96..228e7411de 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -27,8 +27,6 @@ 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 }; @@ -84,12 +82,6 @@ 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 struct strbuf stash_index_path = STRBUF_INIT; @@ -1500,46 +1492,6 @@ 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 keep_index = -1; - int patch_mode = 0; - int include_untracked = 0; - int quiet = 0; - int ret = 0; - const char *stash_msg = NULL; - struct pathspec ps; - struct strbuf stash_msg_buf = STRBUF_INIT; - struct option options[] = { - OPT_BOOL('k', "keep-index", &keep_index, - N_("keep index")), - 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, "message", - N_("stash message")), - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_helper_save_usage, - PARSE_OPT_KEEP_DASHDASH); - - if (argc) - stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' '); - - memset(&ps, 0, sizeof(ps)); - ret = do_push_stash(ps, stash_msg, quiet, keep_index, - patch_mode, include_untracked); - - strbuf_release(&stash_msg_buf); - return ret; -} - int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -1580,8 +1532,6 @@ 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 695f1feba3..51d7a06601 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -36,6 +36,331 @@ 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 -- "$@" +} + +prepare_fallback_ident () { + if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1 + then + GIT_AUTHOR_NAME="git stash" + GIT_AUTHOR_EMAIL=git@stash + GIT_COMMITTER_NAME="git stash" + GIT_COMMITTER_EMAIL=git@stash + export GIT_AUTHOR_NAME + export GIT_AUTHOR_EMAIL + export GIT_COMMITTER_NAME + export GIT_COMMITTER_EMAIL + fi +} + +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 () { + + prepare_fallback_ident + + 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} @@ -100,8 +425,7 @@ show) ;; save) shift - cd "$START_DIR" - git stash--helper save "$@" + save_stash "$@" ;; push) shift From 666ae6eea852012f60be6e50a2f2ef879367ade6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:15 +0100 Subject: [PATCH 12/63] fixup! stash: make push -q quiet In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 56 +++++++++++++++-------------------------- t/t3903-stash.sh | 23 ----------------- 2 files changed, 20 insertions(+), 59 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 228e7411de..31ee9c816b 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -973,7 +973,7 @@ done: } static int stash_patch(struct stash_info *info, struct pathspec ps, - struct strbuf *out_patch, int quiet) + struct strbuf *out_patch) { int ret = 0; struct strbuf out = STRBUF_INIT; @@ -1026,8 +1026,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, } if (!out_patch->len) { - if (!quiet) - fprintf_ln(stderr, _("No changes selected")); + fprintf_ln(stderr, _("No changes selected")); ret = 1; } @@ -1105,8 +1104,7 @@ done: static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, int include_untracked, int patch_mode, - struct stash_info *info, struct strbuf *patch, - int quiet) + struct stash_info *info, struct strbuf *patch) { int ret = 0; int flags = 0; @@ -1126,9 +1124,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, refresh_cache(REFRESH_QUIET); if (get_oid("HEAD", &info->b_commit)) { - if (!quiet) - fprintf_ln(stderr, _("You do not have " - "the initial commit yet")); + fprintf_ln(stderr, _("You do not have the initial commit yet")); ret = -1; goto done; } else { @@ -1153,9 +1149,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, 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)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current " - "index state")); + fprintf_ln(stderr, _("Cannot save the current index state")); ret = -1; goto done; } @@ -1163,29 +1157,26 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, if (include_untracked && get_untracked_files(ps, include_untracked, &untracked_files)) { if (save_untracked_files(info, &msg, untracked_files)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save " - "the untracked files")); + fprintf_ln(stderr, _("Cannot save " + "the untracked files")); ret = -1; goto done; } untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps, patch, quiet); + ret = stash_patch(info, ps, patch); if (ret < 0) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current " - "worktree state")); + fprintf_ln(stderr, _("Cannot save the current " + "worktree state")); goto done; } else if (ret > 0) { goto done; } } else { if (stash_working_tree(info, ps)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current " - "worktree state")); + fprintf_ln(stderr, _("Cannot save the current " + "worktree state")); ret = -1; goto done; } @@ -1211,9 +1202,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &info->w_tree, parents, &info->w_commit, NULL, NULL)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot record " - "working tree state")); + fprintf_ln(stderr, _("Cannot record working tree state")); ret = -1; goto done; } @@ -1248,7 +1237,7 @@ static int create_stash(int argc, const char **argv, const char *prefix) memset(&ps, 0, sizeof(ps)); strbuf_addstr(&stash_msg_buf, stash_msg); ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info, - NULL, 0); + NULL); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1311,29 +1300,26 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, if (!reflog_exists(ref_stash) && do_clear_stash()) { ret = -1; - if (!quiet) - fprintf_ln(stderr, _("Cannot initialize stash")); + fprintf_ln(stderr, _("Cannot initialize stash")); goto done; } if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, - &info, &patch, quiet)) { + &info, &patch)) { ret = -1; goto done; } if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) { ret = -1; - if (!quiet) - fprintf_ln(stderr, _("Cannot save the current status")); + fprintf_ln(stderr, _("Cannot save the current status")); goto done; } - if (!quiet) - printf_ln(_("Saved working directory and index state %s"), - stash_msg_buf.buf); + printf_ln(_("Saved working directory and index state %s"), + stash_msg_buf.buf); if (!patch_mode) { if (include_untracked && !ps.nr) { @@ -1434,9 +1420,7 @@ 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)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot remove " - "worktree changes")); + fprintf_ln(stderr, _("Cannot remove worktree changes")); ret = -1; goto done; } diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index b67d7a1120..98c25a671c 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1064,29 +1064,6 @@ 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 ca9031fea03a7fd90324639ba730fc190e22e58d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:15 +0100 Subject: [PATCH 13/63] fixup! stash: convert push to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 245 +--------------------------------------- git-stash.sh | 6 +- 2 files changed, 6 insertions(+), 245 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 31ee9c816b..d529d4b23b 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -24,9 +24,6 @@ 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 }; @@ -75,13 +72,6 @@ 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 struct strbuf stash_index_path = STRBUF_INIT; @@ -1104,7 +1094,7 @@ done: static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, int include_untracked, int patch_mode, - struct stash_info *info, struct strbuf *patch) + struct stash_info *info) { int ret = 0; int flags = 0; @@ -1117,6 +1107,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, struct strbuf msg = STRBUF_INIT; struct strbuf commit_tree_label = STRBUF_INIT; struct strbuf untracked_files = STRBUF_INIT; + struct strbuf patch = STRBUF_INIT; prepare_fallback_ident("git stash", "git@stash"); @@ -1165,7 +1156,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps, patch); + ret = stash_patch(info, ps, &patch); if (ret < 0) { fprintf_ln(stderr, _("Cannot save the current " "worktree state")); @@ -1236,8 +1227,7 @@ static int create_stash(int argc, const char **argv, const char *prefix) memset(&ps, 0, sizeof(ps)); strbuf_addstr(&stash_msg_buf, stash_msg); - ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info, - NULL); + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1251,231 +1241,6 @@ static int create_stash(int argc, const char **argv, const char *prefix) return ret < 0; } -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; - struct strbuf patch = STRBUF_INIT; - struct strbuf stash_msg_buf = STRBUF_INIT; - - 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")); - ret = -1; - goto done; - } - - 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++) - ce_path_match(&the_index, active_cache[i], &ps, - ps_matched); - - if (report_path_error(ps_matched, &ps, NULL)) { - fprintf_ln(stderr, _("Did you forget to 'git add'?")); - ret = -1; - free(ps_matched); - goto done; - } - free(ps_matched); - } - - if (refresh_cache(REFRESH_QUIET)) { - ret = -1; - goto done; - } - - if (!check_changes(ps, include_untracked)) { - if (!quiet) - printf_ln(_("No local changes to save")); - goto done; - } - - if (!reflog_exists(ref_stash) && do_clear_stash()) { - ret = -1; - fprintf_ln(stderr, _("Cannot initialize stash")); - goto done; - } - - if (stash_msg) - strbuf_addstr(&stash_msg_buf, stash_msg); - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, - &info, &patch)) { - ret = -1; - goto done; - } - - if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) { - ret = -1; - fprintf_ln(stderr, _("Cannot save the current status")); - goto done; - } - - printf_ln(_("Saved working directory and index state %s"), - stash_msg_buf.buf); - - 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 == INCLUDE_ALL_FILES) - argv_array_push(&cp.args, "-x"); - if (run_command(&cp)) { - ret = -1; - goto done; - } - } - if (ps.nr) { - struct child_process cp_add = CHILD_PROCESS_INIT; - struct child_process cp_diff = CHILD_PROCESS_INIT; - struct child_process cp_apply = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - - cp_add.git_cmd = 1; - argv_array_push(&cp_add.args, "add"); - if (!include_untracked) - argv_array_push(&cp_add.args, "-u"); - if (include_untracked == INCLUDE_ALL_FILES) - argv_array_push(&cp_add.args, "--force"); - argv_array_push(&cp_add.args, "--"); - add_pathspecs(&cp_add.args, ps); - if (run_command(&cp_add)) { - ret = -1; - goto done; - } - - cp_diff.git_cmd = 1; - argv_array_pushl(&cp_diff.args, "diff-index", "-p", - "--cached", "--binary", "HEAD", "--", - NULL); - add_pathspecs(&cp_diff.args, ps); - if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) { - ret = -1; - goto done; - } - - cp_apply.git_cmd = 1; - argv_array_pushl(&cp_apply.args, "apply", "--index", - "-R", NULL); - if (pipe_command(&cp_apply, 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 cp_ls = CHILD_PROCESS_INIT; - struct child_process cp_checkout = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - - if (reset_tree(&info.i_tree, 0, 1)) { - ret = -1; - goto done; - } - - cp_ls.git_cmd = 1; - argv_array_pushl(&cp_ls.args, "ls-files", "-z", - "--modified", "--", NULL); - - add_pathspecs(&cp_ls.args, ps); - if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) { - ret = -1; - goto done; - } - - cp_checkout.git_cmd = 1; - argv_array_pushl(&cp_checkout.args, "checkout-index", - "-z", "--force", "--stdin", NULL); - if (pipe_command(&cp_checkout, out.buf, out.len, NULL, - 0, NULL, 0)) { - ret = -1; - goto done; - } - } - 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) { - struct child_process cp = CHILD_PROCESS_INIT; - - cp.git_cmd = 1; - argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); - add_pathspecs(&cp.args, ps); - if (run_command(&cp)) { - ret = -1; - goto done; - } - } - goto done; - } - -done: - strbuf_release(&stash_msg_buf); - 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_BOOL('k', "keep-index", &keep_index, - N_("keep index")), - 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(); @@ -1514,8 +1279,6 @@ 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 51d7a06601..a9b3064ff0 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -429,8 +429,7 @@ save) ;; push) shift - cd "$START_DIR" - git stash--helper push "$@" + push_stash "$@" ;; apply) shift @@ -466,8 +465,7 @@ branch) *) case $# in 0) - cd "$START_DIR" - git stash--helper push && + push_stash && say "$(gettext "(To restore them type \"git stash apply\")")" ;; *) From c341b4ea07f13d9c0b4792c7b628e984aa91bac2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:15 +0100 Subject: [PATCH 14/63] fixup! stash: convert create to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 453 +--------------------------------------- git-stash.sh | 2 +- 2 files changed, 2 insertions(+), 453 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index d529d4b23b..5c53e0a4ec 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -13,9 +13,6 @@ #include "rerere.h" #include "revision.h" #include "log-tree.h" -#include "diffcore.h" - -#define INCLUDE_ALL_FILES 2 static const char * const git_stash_helper_usage[] = { N_("git stash--helper list []"), @@ -67,11 +64,6 @@ 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 struct strbuf stash_index_path = STRBUF_INIT; @@ -297,24 +289,6 @@ 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++) { - strbuf_addstr(data, q->queue[i]->one->path); - - /* - * The reason we add "0" at the end of this strbuf - * is because we will pass the output further to - * "git update-index -z ...". - */ - strbuf_addch(data, '\0'); - } -} - static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) { struct child_process cp = CHILD_PROCESS_INIT; @@ -818,429 +792,6 @@ static int store_stash(int argc, const char **argv, const char *prefix) return do_store_stash(&obj, stash_msg, quiet); } -static void add_pathspecs(struct argv_array *args, - struct pathspec ps) { - int i; - - for (i = 0; i < ps.nr; i++) - argv_array_push(args, ps.items[i].match); -} - -/* - * `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 *untracked_files) -{ - int i; - int max_len; - int found = 0; - char *seen; - struct dir_struct dir; - - memset(&dir, 0, sizeof(dir)); - if (include_untracked != INCLUDE_ALL_FILES) - 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)) { - found++; - strbuf_addstr(untracked_files, ent->name); - /* NUL-terminate: will be fed to update-index -z */ - strbuf_addch(untracked_files, 0); - } - free(ent); - } - - free(seen); - free(dir.entries); - free(dir.ignored); - clear_directory(&dir); - return found; -} - -/* - * 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; - struct rev_info rev; - struct object_id dummy; - struct strbuf out = STRBUF_INIT; - - /* No initial commit. */ - if (get_oid("HEAD", &dummy)) - return -1; - - if (read_cache() < 0) - return -1; - - init_revisions(&rev, NULL); - rev.prune_data = ps; - - rev.diffopt.flags.quick = 1; - rev.diffopt.flags.ignore_submodules = 1; - rev.abbrev = 0; - - add_head_to_pending(&rev); - diff_setup_done(&rev.diffopt); - - 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 files) -{ - 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, files.buf, files.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 int stash_patch(struct stash_info *info, struct pathspec ps, - struct strbuf *out_patch) -{ - 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; - } - - /* Find out what the user wants. */ - cp_add_i.git_cmd = 1; - argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", - "--", NULL); - add_pathspecs(&cp_add_i.args, ps); - 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; - } - - /* State of the working tree. */ - 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, out_patch, 0, NULL, 0)) { - ret = -1; - goto done; - } - - if (!out_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 rev_info rev; - 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; - - 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); - - 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, struct strbuf *stash_msg_buf, - int include_untracked, int patch_mode, - struct stash_info *info) -{ - int ret = 0; - int flags = 0; - int untracked_commit_option = 0; - 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 untracked_files = STRBUF_INIT; - struct strbuf patch = STRBUF_INIT; - - prepare_fallback_ident("git stash", "git@stash"); - - read_cache_preload(NULL); - refresh_cache(REFRESH_QUIET); - - if (get_oid("HEAD", &info->b_commit)) { - fprintf_ln(stderr, _("You do not have the initial commit yet")); - ret = -1; - goto done; - } else { - head_commit = lookup_commit(the_repository, &info->b_commit); - } - - if (!check_changes(ps, include_untracked)) { - ret = 1; - goto done; - } - - 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; - goto done; - } - - if (include_untracked && get_untracked_files(ps, include_untracked, - &untracked_files)) { - if (save_untracked_files(info, &msg, untracked_files)) { - fprintf_ln(stderr, _("Cannot save " - "the untracked files")); - ret = -1; - goto done; - } - untracked_commit_option = 1; - } - if (patch_mode) { - ret = stash_patch(info, ps, &patch); - 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; - goto done; - } - } - - if (!stash_msg_buf->len) - strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf); - else - strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name); - - /* - * `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_buf->buf, stash_msg_buf->len, &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(&untracked_files); - 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 strbuf stash_msg_buf = STRBUF_INIT; - 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)); - strbuf_addstr(&stash_msg_buf, stash_msg); - ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info); - - if (!ret) - printf_ln("%s", oid_to_hex(&info.w_commit)); - - strbuf_release(&stash_msg_buf); - - /* - * ret can be 1 if there were no changes. In this case, we should - * not error out. - */ - return ret < 0; -} - int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -1250,7 +801,7 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) OPT_END() }; - git_config(git_diff_basic_config, NULL); + 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); @@ -1277,8 +828,6 @@ 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 a9b3064ff0..ff5556ccb0 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -442,7 +442,7 @@ clear) ;; create) shift - git stash--helper create --message "$*" + create_stash -m "$*" && echo "$w_commit" ;; store) shift From 2b45b40686fd4f30ea0c771446ca38c98c3fc938 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:16 +0100 Subject: [PATCH 15/63] fixup! stash: convert store to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 63 ----------------------------------------- git-stash.sh | 43 ++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 65 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 5c53e0a4ec..1cb0bb586d 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -59,11 +59,6 @@ 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 struct strbuf stash_index_path = STRBUF_INIT; @@ -736,62 +731,6 @@ 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 struct object_id *w_commit, const char *stash_msg, - int quiet) -{ - if (!stash_msg) - stash_msg = "Created via \"git stash store\"."; - - if (update_ref(stash_msg, ref_stash, w_commit, NULL, - REF_FORCE_CREATE_REFLOG, - quiet ? UPDATE_REFS_QUIET_ON_ERR : - UPDATE_REFS_MSG_ON_ERR)) { - if (!quiet) { - fprintf_ln(stderr, _("Cannot update %s with %s"), - ref_stash, oid_to_hex(w_commit)); - } - return -1; - } - - return 0; -} - -static int store_stash(int argc, const char **argv, const char *prefix) -{ - int quiet = 0; - const char *stash_msg = NULL; - struct object_id obj; - struct object_context dummy; - struct option options[] = { - OPT__QUIET(&quiet, N_("be quiet")), - 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) { - if (!quiet) - fprintf_ln(stderr, _("\"git stash store\" requires one " - " argument")); - return -1; - } - - if (get_oid_with_context(the_repository, - argv[0], quiet ? GET_OID_QUIETLY : 0, &obj, - &dummy)) { - if (!quiet) - fprintf_ln(stderr, _("Cannot update %s with %s"), - ref_stash, argv[0]); - return -1; - } - - return do_store_stash(&obj, stash_msg, quiet); -} - int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -826,8 +765,6 @@ 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 ff5556ccb0..d0318f859e 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -208,6 +208,45 @@ 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= @@ -286,7 +325,7 @@ push_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 || + store_stash -m "$stash_msg" -q $w_commit || die "$(gettext "Cannot save the current status")" say "$(eval_gettext "Saved working directory and index state \$stash_msg")" @@ -446,7 +485,7 @@ create) ;; store) shift - git stash--helper store "$@" + store_stash "$@" ;; drop) shift From 6f1ce35dc4324b23da56870a81c0ec52002edf3e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:16 +0100 Subject: [PATCH 16/63] fixup! stash: convert show to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 87 -------------------------- git-stash.sh | 132 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 88 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 1cb0bb586d..77816e4873 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -11,12 +11,9 @@ #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 []"), @@ -29,11 +26,6 @@ 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 @@ -654,83 +646,6 @@ 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(); @@ -763,8 +678,6 @@ 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 d0318f859e..ab3992b59d 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -395,6 +395,35 @@ 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 @@ -436,6 +465,107 @@ 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' @@ -460,7 +590,7 @@ list) ;; show) shift - git stash--helper show "$@" + show_stash "$@" ;; save) shift From fd928add8d25774dc84d31ebc8c649061fa1579f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:16 +0100 Subject: [PATCH 17/63] fixup! stash: convert list to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 31 ------------------------------- git-stash.sh | 7 ++++++- 2 files changed, 6 insertions(+), 32 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 77816e4873..6da0a510b6 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -13,7 +13,6 @@ #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 []"), @@ -21,11 +20,6 @@ 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 @@ -623,29 +617,6 @@ 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(); @@ -676,8 +647,6 @@ 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 ab3992b59d..8a9f907aa9 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -399,6 +399,11 @@ 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 "$@" @@ -586,7 +591,7 @@ test -n "$seen_non_option" || set "push" "$@" case "$1" in list) shift - git stash--helper list "$@" + list_stash "$@" ;; show) shift From 566be3a203a331acb2c910f955456930c0aa5000 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:17 +0100 Subject: [PATCH 18/63] fixup! stash: convert pop to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 39 +--------------------------------- git-stash.sh | 47 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 40 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 6da0a510b6..296ac9d8a1 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -14,7 +14,7 @@ static const char * const git_stash_helper_usage[] = { N_("git stash--helper drop [-q|--quiet] []"), - N_("git stash--helper ( pop | apply ) [--index] [-q|--quiet] []"), + N_("git stash--helper apply [--index] [-q|--quiet] []"), N_("git stash--helper branch []"), N_("git stash--helper clear"), NULL @@ -25,11 +25,6 @@ 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 @@ -549,36 +544,6 @@ 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 ret; - int index = 0; - int quiet = 0; - 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, quiet))) - printf_ln(_("The stash entry is kept in case " - "you need it again.")); - else - ret = do_drop_stash(prefix, &info, quiet); - - free_stash_info(&info); - return ret; -} - static int branch_stash(int argc, const char **argv, const char *prefix) { int ret; @@ -643,8 +608,6 @@ 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 8a9f907aa9..67db321a4c 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -571,6 +571,50 @@ 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' @@ -628,8 +672,7 @@ drop) ;; pop) shift - cd "$START_DIR" - git stash--helper pop "$@" + pop_stash "$@" ;; branch) shift From 35eac1e64d54dcd073880dff7e7862346a8889c2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:17 +0100 Subject: [PATCH 19/63] fixup! stash: convert branch to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 46 ----------------------------------------- git-stash.sh | 17 +++++++++++++-- 2 files changed, 15 insertions(+), 48 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 296ac9d8a1..1e6b1924ab 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -15,7 +15,6 @@ 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 }; @@ -30,11 +29,6 @@ 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 @@ -544,44 +538,6 @@ 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) -{ - int ret; - const char *branch = NULL; - struct stash_info info; - struct child_process cp = CHILD_PROCESS_INIT; - struct option options[] = { - OPT_END() - }; - - argc = parse_options(argc, argv, prefix, options, - git_stash_helper_branch_usage, 0); - - if (!argc) { - fprintf_ln(stderr, _("No branch name specified")); - return -1; - } - - 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, 0); - if (!ret && info.is_stash_ref) - ret = do_drop_stash(prefix, &info, 0); - - free_stash_info(&info); - - return ret; -} - int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -608,8 +564,6 @@ 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 67db321a4c..b8f70230f9 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -615,6 +615,20 @@ 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' @@ -676,8 +690,7 @@ pop) ;; branch) shift - cd "$START_DIR" - git stash--helper branch "$@" + apply_to_branch "$@" ;; *) case $# in From 2b8b36b1e092b74a870b0bcac563c127d9ee2108 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:17 +0100 Subject: [PATCH 20/63] fixup! stash: convert drop and clear to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 117 ---------------------------------------- git-stash.sh | 4 +- 2 files changed, 2 insertions(+), 119 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 1e6b1924ab..a9d55b1598 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -13,14 +13,7 @@ #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 }; @@ -29,11 +22,6 @@ 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 struct strbuf stash_index_path = STRBUF_INIT; @@ -150,32 +138,6 @@ 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) { int nr_trees = 1; @@ -463,81 +425,6 @@ 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, int quiet) -{ - int ret; - struct child_process cp_reflog = CHILD_PROCESS_INIT; - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * 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) -{ - int ret; - int quiet = 0; - struct stash_info info; - 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, quiet); - free_stash_info(&info); - return ret; -} - int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -560,10 +447,6 @@ 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 b8f70230f9..366a082853 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -670,7 +670,7 @@ apply) ;; clear) shift - git stash--helper clear "$@" + clear_stash "$@" ;; create) shift @@ -682,7 +682,7 @@ store) ;; drop) shift - git stash--helper drop "$@" + drop_stash "$@" ;; pop) shift From f0adfd761f87bedb117cbae9af4365b93e8322cd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:18 +0100 Subject: [PATCH 21/63] fixup! stash: convert apply to builtin In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- .gitignore | 1 - Makefile | 1 - builtin.h | 1 - builtin/stash--helper.c | 453 ---------------------------------------- git-stash.sh | 78 ++++++- git.c | 1 - 6 files changed, 71 insertions(+), 464 deletions(-) delete mode 100644 builtin/stash--helper.c diff --git a/.gitignore b/.gitignore index 32765a6ccb..7374587f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -162,7 +162,6 @@ /git-show-ref /git-stage /git-stash -/git-stash--helper /git-status /git-stripspace /git-submodule diff --git a/Makefile b/Makefile index 5c4b6e6ae5..c5240942f2 100644 --- a/Makefile +++ b/Makefile @@ -1138,7 +1138,6 @@ 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 ff4460aff7..6538932e99 100644 --- a/builtin.h +++ b/builtin.h @@ -225,7 +225,6 @@ 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 deleted file mode 100644 index a9d55b1598..0000000000 --- a/builtin/stash--helper.c +++ /dev/null @@ -1,453 +0,0 @@ -#define USE_THE_INDEX_COMPATIBILITY_MACROS -#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 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)) - die(_("'%s' is not a stash-like commit"), revision); -} - -static int get_stash_info(struct stash_info *info, int argc, const char **argv) -{ - int ret; - char *end_of_rev; - char *expanded_ref; - const char *revision; - const char *commit = NULL; - struct object_id dummy; - struct strbuf symbolic = STRBUF_INIT; - - 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) -{ - int nr_trees = 1; - struct unpack_trees_options opts; - 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) -{ - int res; - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * 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, int quiet) -{ - int ret; - int has_index = index; - struct merge_options o; - struct object_id c_tree; - struct object_id index_tree; - struct commit *result; - const struct object_id *bases[1]; - - 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 (oideq(&info->b_tree, &info->i_tree) || - oideq(&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, the_repository); - - o.branch1 = "Updated upstream"; - o.branch2 = "Stashed changes"; - - if (oideq(&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 ret; - int quiet = 0; - int index = 0; - 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_apply_usage, 0); - - if (get_stash_info(&info, argc, argv)) - return -1; - - ret = do_apply_stash(prefix, &info, index, quiet); - 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 366a082853..789ce2f41d 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -583,11 +583,76 @@ assert_stash_ref() { } apply_stash () { - cd "$START_DIR" - git stash--helper apply "$@" - res=$? - cd_to_toplevel - return $res + + 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 } pop_stash() { @@ -665,8 +730,7 @@ push) ;; apply) shift - cd "$START_DIR" - git stash--helper apply "$@" + apply_stash "$@" ;; clear) shift diff --git a/git.c b/git.c index c041c6e057..2dd588674f 100644 --- a/git.c +++ b/git.c @@ -555,7 +555,6 @@ 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 82de2cee8f09a6c143411caf29c6b02763e533b3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:18 +0100 Subject: [PATCH 22/63] fixup! stash: mention options in `show` synopsis In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- Documentation/git-stash.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index e31ea7d303..7ef8c47911 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 From e4e2dfd6cda5042519fc26c0c1ea5880df20c519 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:18 +0100 Subject: [PATCH 23/63] fixup! stash: add tests for `git stash show` config In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- t/t3907-stash-show-config.sh | 83 ------------------------------------ 1 file changed, 83 deletions(-) delete mode 100755 t/t3907-stash-show-config.sh diff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh deleted file mode 100755 index 10914bba7b..0000000000 --- a/t/t3907-stash-show-config.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/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_unconfig stash.showStat - else - test_config stash.showStat "$1" - fi && - - if test "" = "$2" - then - test_unconfig stash.showPatch - else - test_config stash.showPatch "$2" - fi && - - shift 2 && - echo 2 >file.t && - if test $# != 0 - then - git diff "$@" >expect - fi && - git stash && - git stash show >actual && - - if test $# = 0 - 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 0e1bf9f41399cbad12ebe83e39d8cb15bd85cd2d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:19 +0100 Subject: [PATCH 24/63] fixup! stash: rename test cases to be more descriptive In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- 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 98c25a671c..4e83facf23 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 'drop: fail early if specified stash is not a stash ref' ' +test_expect_success 'stash drop - fail early if specified stash is not a stash reference' ' git stash clear && test_when_finished "git reset --hard HEAD && git stash clear" && git reset --hard && @@ -618,7 +618,7 @@ test_expect_success 'drop: fail early if specified stash is not a stash ref' ' git reset --hard HEAD ' -test_expect_success 'pop: fail early if specified stash is not a stash ref' ' +test_expect_success 'stash pop - fail early if specified stash is not a stash reference' ' 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 'branch: do not drop the stash if the branch exists' ' +test_expect_success 'stash branch should not drop the stash if the branch exists' ' git stash clear && echo foo >file && git add file && @@ -693,7 +693,7 @@ test_expect_success 'branch: do not drop the stash if the branch exists' ' git rev-parse stash@{0} -- ' -test_expect_success 'branch: should not drop the stash if the apply fails' ' +test_expect_success 'stash 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 'branch: should not drop the stash if the apply fails' ' git rev-parse stash@{0} -- ' -test_expect_success 'apply: show same status as git status (relative to ./)' ' +test_expect_success 'stash apply shows status same as git status (relative to current directory)' ' 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 'push : show no changes when there are none' ' +test_expect_success 'stash push with pathspec shows no changes when there are none' ' >foo && git add foo && git commit -m "tmp" && @@ -1058,7 +1058,7 @@ test_expect_success 'push : show no changes when there are none' ' test_i18ncmp expect actual ' -test_expect_success 'push: not in the repository errors out' ' +test_expect_success 'stash push with pathspec not in the repository errors out' ' >untracked && test_must_fail git stash push untracked && test_path_is_file untracked From 1acc1e9960dbbd4f4a39feaaf93af15e116822c8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:19 +0100 Subject: [PATCH 25/63] fixup! t3903: modernize style In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- t/t3903-stash.sh | 120 +++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 4e83facf23..ac55629737 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 < expect << EOF diff --git a/file b/file index 0cfbf08..00750ed 100644 --- a/file @@ -35,7 +35,7 @@ EOF test_expect_success 'parents of stash' ' test $(git rev-parse stash^) = $(git rev-parse HEAD) && - git diff stash^2..stash >output && + git diff stash^2..stash > output && test_cmp expect output ' @@ -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 >expected && - echo 7 >file && + git stash list > stashlist1 && + echo 7 > file && git stash && git stash drop && - git stash list >actual && - test_cmp expected actual && + git stash list > stashlist2 && + test_cmp stashlist1 stashlist2 && 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 < expect << EOF diff --git a/file2 b/file2 new file mode 100644 index 0000000..1fe912c @@ -170,7 +170,7 @@ index 0000000..1fe912c +bar2 EOF -cat >expect1 < expect1 << EOF diff --git a/file b/file index 257cc56..5716ca5 100644 --- a/file @@ -180,7 +180,7 @@ index 257cc56..5716ca5 100644 +bar EOF -cat >expect2 < expect2 << EOF diff --git a/file b/file index 7601807..5716ca5 100644 --- a/file @@ -198,79 +198,79 @@ index 0000000..1fe912c EOF test_expect_success 'stash branch' ' - echo foo >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 expect output && - git diff >output && + git diff > output && test_cmp expect1 output && 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,12 +486,11 @@ 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 ' @@ -499,15 +498,14 @@ 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 ' @@ -520,10 +518,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 && @@ -538,10 +536,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 && @@ -553,10 +551,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 && @@ -576,7 +574,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 && @@ -588,7 +586,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 && @@ -608,9 +606,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 && @@ -622,9 +620,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 && @@ -634,8 +632,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 && @@ -655,8 +653,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} && @@ -726,7 +724,7 @@ test_expect_success 'stash apply shows status same as git status (relative to cu test_i18ncmp expect actual ' -cat >expect < expect << EOF diff --git a/HEAD b/HEAD new file mode 100644 index 0000000..fe0cbee @@ -739,14 +737,14 @@ EOF test_expect_success 'stash where working directory contains "HEAD" file' ' git stash clear && git reset --hard && - echo file-not-a-ref >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 expect output ' From 6210c671e4eaf2293425fe25d8ea418992aa746b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:20 +0100 Subject: [PATCH 26/63] fixup! stash: improve option parsing test coverage In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- t/t3903-stash.sh | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index ac55629737..5f8272b6f9 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -444,36 +444,6 @@ 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" && @@ -509,11 +479,6 @@ 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 9902b5e6aa6f53806877bc82a4527f61ebc00242 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:20 +0100 Subject: [PATCH 27/63] fixup! ident: add the ability to provide a "fallback identity" In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- cache.h | 1 - ident.c | 20 -------------------- 2 files changed, 21 deletions(-) diff --git a/cache.h b/cache.h index 877697571c..99b7aa06f7 100644 --- a/cache.h +++ b/cache.h @@ -1518,7 +1518,6 @@ extern const char *git_sequence_editor(void); extern const char *git_pager(int stdout_is_tty); extern int is_terminal_dumb(void); extern int git_ident_config(const char *, const char *, void *); -void prepare_fallback_ident(const char *name, const char *email); extern void reset_ident_date(void); struct ident_split { diff --git a/ident.c b/ident.c index bce20e8652..33bcf40644 100644 --- a/ident.c +++ b/ident.c @@ -505,26 +505,6 @@ int git_ident_config(const char *var, const char *value, void *data) return 0; } -static void set_env_if(const char *key, const char *value, int *given, int bit) -{ - if ((*given & bit) || getenv(key)) - return; /* nothing to do */ - setenv(key, value, 0); - *given |= bit; -} - -void prepare_fallback_ident(const char *name, const char *email) -{ - set_env_if("GIT_AUTHOR_NAME", name, - &author_ident_explicitly_given, IDENT_NAME_GIVEN); - set_env_if("GIT_AUTHOR_EMAIL", email, - &author_ident_explicitly_given, IDENT_MAIL_GIVEN); - set_env_if("GIT_COMMITTER_NAME", name, - &committer_ident_explicitly_given, IDENT_NAME_GIVEN); - set_env_if("GIT_COMMITTER_EMAIL", email, - &committer_ident_explicitly_given, IDENT_MAIL_GIVEN); -} - static int buf_cmp(const char *a_begin, const char *a_end, const char *b_begin, const char *b_end) { From dd1d9a52d3f1aecb4c96dd109f49645bf2561776 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:20 +0100 Subject: [PATCH 28/63] fixup! strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- strbuf.c | 36 ------------------------------------ strbuf.h | 9 --------- 2 files changed, 45 deletions(-) diff --git a/strbuf.c b/strbuf.c index bfbbdadbf3..82e90f1dfe 100644 --- a/strbuf.c +++ b/strbuf.c @@ -249,42 +249,6 @@ void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len) strbuf_splice(sb, pos, 0, data, len); } -void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, va_list ap) -{ - int len, len2; - char save; - va_list cp; - - if (pos > sb->len) - die("`pos' is too far after the end of the buffer"); - va_copy(cp, ap); - len = vsnprintf(sb->buf + sb->len, 0, fmt, cp); - va_end(cp); - if (len < 0) - BUG("your vsnprintf is broken (returned %d)", len); - if (!len) - return; /* nothing to do */ - if (unsigned_add_overflows(sb->len, len)) - die("you want to use way too much memory"); - strbuf_grow(sb, len); - memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos); - /* vsnprintf() will append a NUL, overwriting one of our characters */ - save = sb->buf[pos + len]; - len2 = vsnprintf(sb->buf + pos, sb->alloc - sb->len, fmt, ap); - sb->buf[pos + len] = save; - if (len2 != len) - BUG("your vsnprintf is broken (returns inconsistent lengths)"); - strbuf_setlen(sb, sb->len + len); -} - -void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - strbuf_vinsertf(sb, pos, fmt, ap); - va_end(ap); -} - void strbuf_remove(struct strbuf *sb, size_t pos, size_t len) { strbuf_splice(sb, pos, len, "", 0); diff --git a/strbuf.h b/strbuf.h index 8f8fe01e68..be02150df3 100644 --- a/strbuf.h +++ b/strbuf.h @@ -244,15 +244,6 @@ void strbuf_addchars(struct strbuf *sb, int c, size_t n); */ void strbuf_insert(struct strbuf *sb, size_t pos, const void *, size_t); -/** - * Insert data to the given position of the buffer giving a printf format - * string. The contents will be shifted, not overwritten. - */ -void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, - va_list ap); - -void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...); - /** * Remove given amount of data from a given position of the buffer. */ From 02f8005b6703d49a175b6ac57805316f6cf2547a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:21 +0100 Subject: [PATCH 29/63] fixup! strbuf.c: add `strbuf_join_argv()` In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- strbuf.c | 15 --------------- strbuf.h | 7 ------- 2 files changed, 22 deletions(-) diff --git a/strbuf.c b/strbuf.c index 82e90f1dfe..f6a6cf78b9 100644 --- a/strbuf.c +++ b/strbuf.c @@ -268,21 +268,6 @@ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) strbuf_setlen(sb, sb->len + sb2->len); } -const char *strbuf_join_argv(struct strbuf *buf, - int argc, const char **argv, char delim) -{ - if (!argc) - return buf->buf; - - strbuf_addstr(buf, *argv); - while (--argc) { - strbuf_addch(buf, delim); - strbuf_addstr(buf, *(++argv)); - } - - return buf->buf; -} - void strbuf_addchars(struct strbuf *sb, int c, size_t n) { strbuf_grow(sb, n); diff --git a/strbuf.h b/strbuf.h index be02150df3..fc40873b65 100644 --- a/strbuf.h +++ b/strbuf.h @@ -288,13 +288,6 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) */ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); -/** - * Join the arguments into a buffer. `delim` is put between every - * two arguments. - */ -const char *strbuf_join_argv(struct strbuf *buf, int argc, - const char **argv, char delim); - /** * This function can be used to expand a format string containing * placeholders. To that end, it parses the string and calls the specified From 61e921a2a5c8d393349b5737eeae1f207343cc0f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:36:21 +0100 Subject: [PATCH 30/63] fixup! sha1-name.c: add `get_oidf()` which acts like `get_oid()` In preparation for a newer patch series. Signed-off-by: Johannes Schindelin --- cache.h | 1 - sha1-name.c | 19 ------------------- 2 files changed, 20 deletions(-) diff --git a/cache.h b/cache.h index 99b7aa06f7..27fe635f62 100644 --- a/cache.h +++ b/cache.h @@ -1353,7 +1353,6 @@ enum get_oid_result { }; 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 375fba94a1..6dda2c16df 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -1518,25 +1518,6 @@ int get_oid(const char *name, struct object_id *oid) return get_oid_with_context(the_repository, 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 aaede5ca6ac401f7f7577c9243dee33774025195 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:05 +0000 Subject: [PATCH 31/63] 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 27fe635f62..99b7aa06f7 100644 --- a/cache.h +++ b/cache.h @@ -1353,6 +1353,7 @@ enum get_oid_result { }; 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 6dda2c16df..375fba94a1 100644 --- a/sha1-name.c +++ b/sha1-name.c @@ -1518,6 +1518,25 @@ int get_oid(const char *name, struct object_id *oid) return get_oid_with_context(the_repository, 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 3422bdcfc3a4ecb577ad7c2e2f188d0dae645c1d Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:06 +0000 Subject: [PATCH 32/63] strbuf.c: add `strbuf_join_argv()` Implement `strbuf_join_argv()` to join arguments into a strbuf. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- strbuf.c | 15 +++++++++++++++ strbuf.h | 7 +++++++ 2 files changed, 22 insertions(+) diff --git a/strbuf.c b/strbuf.c index f6a6cf78b9..82e90f1dfe 100644 --- a/strbuf.c +++ b/strbuf.c @@ -268,6 +268,21 @@ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) strbuf_setlen(sb, sb->len + sb2->len); } +const char *strbuf_join_argv(struct strbuf *buf, + int argc, const char **argv, char delim) +{ + if (!argc) + return buf->buf; + + strbuf_addstr(buf, *argv); + while (--argc) { + strbuf_addch(buf, delim); + strbuf_addstr(buf, *(++argv)); + } + + return buf->buf; +} + void strbuf_addchars(struct strbuf *sb, int c, size_t n) { strbuf_grow(sb, n); diff --git a/strbuf.h b/strbuf.h index fc40873b65..be02150df3 100644 --- a/strbuf.h +++ b/strbuf.h @@ -288,6 +288,13 @@ static inline void strbuf_addstr(struct strbuf *sb, const char *s) */ void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2); +/** + * Join the arguments into a buffer. `delim` is put between every + * two arguments. + */ +const char *strbuf_join_argv(struct strbuf *buf, int argc, + const char **argv, char delim); + /** * This function can be used to expand a format string containing * placeholders. To that end, it parses the string and calls the specified From 7cfbaa502a5e427a3839a7b10d19a984a43bfd88 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:07 +0000 Subject: [PATCH 33/63] strbuf.c: add `strbuf_insertf()` and `strbuf_vinsertf()` Implement `strbuf_insertf()` and `strbuf_vinsertf()` to insert data using a printf format string. Original-idea-by: Johannes Schindelin Signed-off-by: Paul-Sebastian Ungureanu Helped-by: Johannes Sixt Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- strbuf.c | 36 ++++++++++++++++++++++++++++++++++++ strbuf.h | 9 +++++++++ 2 files changed, 45 insertions(+) diff --git a/strbuf.c b/strbuf.c index 82e90f1dfe..87ecf7f975 100644 --- a/strbuf.c +++ b/strbuf.c @@ -249,6 +249,42 @@ void strbuf_insert(struct strbuf *sb, size_t pos, const void *data, size_t len) strbuf_splice(sb, pos, 0, data, len); } +void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, va_list ap) +{ + int len, len2; + char save; + va_list cp; + + if (pos > sb->len) + die("`pos' is too far after the end of the buffer"); + va_copy(cp, ap); + len = vsnprintf(sb->buf + sb->len, 0, fmt, cp); + va_end(cp); + if (len < 0) + BUG("your vsnprintf is broken (returned %d)", len); + if (!len) + return; /* nothing to do */ + if (unsigned_add_overflows(sb->len, len)) + die("you want to use way too much memory"); + strbuf_grow(sb, len); + memmove(sb->buf + pos + len, sb->buf + pos, sb->len - pos); + /* vsnprintf() will append a NUL, overwriting one of our characters */ + save = sb->buf[pos + len]; + len2 = vsnprintf(sb->buf + pos, len + 1, fmt, ap); + sb->buf[pos + len] = save; + if (len2 != len) + BUG("your vsnprintf is broken (returns inconsistent lengths)"); + strbuf_setlen(sb, sb->len + len); +} + +void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + strbuf_vinsertf(sb, pos, fmt, ap); + va_end(ap); +} + void strbuf_remove(struct strbuf *sb, size_t pos, size_t len) { strbuf_splice(sb, pos, len, "", 0); diff --git a/strbuf.h b/strbuf.h index be02150df3..8f8fe01e68 100644 --- a/strbuf.h +++ b/strbuf.h @@ -244,6 +244,15 @@ void strbuf_addchars(struct strbuf *sb, int c, size_t n); */ void strbuf_insert(struct strbuf *sb, size_t pos, const void *, size_t); +/** + * Insert data to the given position of the buffer giving a printf format + * string. The contents will be shifted, not overwritten. + */ +void strbuf_vinsertf(struct strbuf *sb, size_t pos, const char *fmt, + va_list ap); + +void strbuf_insertf(struct strbuf *sb, size_t pos, const char *fmt, ...); + /** * Remove given amount of data from a given position of the buffer. */ From d3dc2ab2c07358b3cd0c144793e2fab592a9b828 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 25 Feb 2019 23:16:08 +0000 Subject: [PATCH 34/63] ident: add the ability to provide a "fallback identity" In 3bc2111fc2e9 (stash: tolerate missing user identity, 2018-11-18), `git stash` learned to provide a fallback identity for the case that no proper name/email was given (and `git stash` does not really care about a correct identity anyway, but it does want to create a commit object). In preparation for the same functionality in the upcoming built-in version of `git stash`, let's offer the same functionality as an API function. Signed-off-by: Johannes Schindelin [tg: add docs; make it a bug to call the function before other functions in the ident API] Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- cache.h | 5 +++++ ident.c | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/cache.h b/cache.h index 99b7aa06f7..d71f492c49 100644 --- a/cache.h +++ b/cache.h @@ -1518,6 +1518,11 @@ extern const char *git_sequence_editor(void); extern const char *git_pager(int stdout_is_tty); extern int is_terminal_dumb(void); extern int git_ident_config(const char *, const char *, void *); +/* + * Prepare an ident to fall back on if the user didn't configure it. + * Must be called before any other function from the ident API. + */ +void prepare_fallback_ident(const char *name, const char *email); extern void reset_ident_date(void); struct ident_split { diff --git a/ident.c b/ident.c index 33bcf40644..f30bd623f0 100644 --- a/ident.c +++ b/ident.c @@ -505,6 +505,28 @@ int git_ident_config(const char *var, const char *value, void *data) return 0; } +static void set_env_if(const char *key, const char *value, int *given, int bit) +{ + if (*given & bit) + BUG("%s was checked before prepare_fallback got called", key); + if (getenv(key)) + return; /* nothing to do */ + setenv(key, value, 0); + *given |= bit; +} + +void prepare_fallback_ident(const char *name, const char *email) +{ + set_env_if("GIT_AUTHOR_NAME", name, + &author_ident_explicitly_given, IDENT_NAME_GIVEN); + set_env_if("GIT_AUTHOR_EMAIL", email, + &author_ident_explicitly_given, IDENT_MAIL_GIVEN); + set_env_if("GIT_COMMITTER_NAME", name, + &committer_ident_explicitly_given, IDENT_NAME_GIVEN); + set_env_if("GIT_COMMITTER_EMAIL", email, + &committer_ident_explicitly_given, IDENT_MAIL_GIVEN); +} + static int buf_cmp(const char *a_begin, const char *a_end, const char *b_begin, const char *b_end) { From 5061ec10727eb51c5174bb695a59082252ce41e6 Mon Sep 17 00:00:00 2001 From: Thomas Gummerer Date: Wed, 6 Mar 2019 22:09:11 +0000 Subject: [PATCH 35/63] ident: don't require calling prepare_fallback_ident first In fd5a58477c ("ident: add the ability to provide a "fallback identity"", 2019-02-25) I made it a requirement to call prepare_fallback_ident as the first function in the ident API. However in stash we didn't actually end up following that. This leads to a BUG if user.email and user.name are set. It was not caught in the test suite because we only rely on environment variables for setting the user name and email instead of the config. Instead of making it a bug to call other functions in the ident API first, just return silently if the identity of a user was already set up. Reported-by: Denton Liu Helped-by: Jeff King Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- cache.h | 1 - ident.c | 4 +--- t/t3903-stash.sh | 6 ++++++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cache.h b/cache.h index d71f492c49..e5b9c24313 100644 --- a/cache.h +++ b/cache.h @@ -1520,7 +1520,6 @@ extern int is_terminal_dumb(void); extern int git_ident_config(const char *, const char *, void *); /* * Prepare an ident to fall back on if the user didn't configure it. - * Must be called before any other function from the ident API. */ void prepare_fallback_ident(const char *name, const char *email); extern void reset_ident_date(void); diff --git a/ident.c b/ident.c index f30bd623f0..bce20e8652 100644 --- a/ident.c +++ b/ident.c @@ -507,9 +507,7 @@ int git_ident_config(const char *var, const char *value, void *data) static void set_env_if(const char *key, const char *value, int *given, int bit) { - if (*given & bit) - BUG("%s was checked before prepare_fallback got called", key); - if (getenv(key)) + if ((*given & bit) || getenv(key)) return; /* nothing to do */ setenv(key, value, 0); *given |= bit; diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 5f8272b6f9..2115e8767e 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1096,6 +1096,12 @@ test_expect_success 'stash -- works with binary files' ' test_path_is_file subdir/untracked ' +test_expect_success 'stash with user.name and user.email set works' ' + test_config user.name "A U Thor" && + test_config user.email "a.u@thor" && + git stash +' + test_expect_success 'stash works when user.name and user.email are not set' ' git reset && >1 && From dca0c78b7573a9a36d8541dca903399f9cdba66b Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Mon, 25 Feb 2019 23:16:09 +0000 Subject: [PATCH 36/63] 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: Thomas Gummerer 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 2115e8767e..f29f513117 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 f198a7097a03b58976b6892da629f410763f0e13 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:10 +0000 Subject: [PATCH 37/63] t3903: modernize style Remove whitespaces after redirection operators and wrap long lines. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer 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 f29f513117..2fd4f01358 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 expect output ' @@ -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 expect output && - git diff > output && + git diff >output && test_cmp expect1 output && 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 expect output ' From 8ef273dc82fc1cbbda71943dfe2606e9565b0437 Mon Sep 17 00:00:00 2001 From: Matthew Kraai Date: Mon, 25 Feb 2019 23:16:11 +0000 Subject: [PATCH 38/63] t3903: add test for --intent-to-add file Add a test showing the 'git stash' behaviour with a file that has been added with 'git add --intent-to-add'. Stash fails to stash the file, so the purpose of this test is mainly to make sure git doesn't crash, but exits normally in this situation. This is in preparation for converting stash into a builtin. [tg: pulled the test out into a separate commit] Signed-off-by: Matthew Kraai Signed-off-by: Johannes Schindelin Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- t/t3903-stash.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 2fd4f01358..78d50bcf1e 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -287,6 +287,14 @@ test_expect_success 'stash an added file' ' test new = "$(cat file3)" ' +test_expect_success 'stash --intent-to-add file' ' + git reset --hard && + echo new >file4 && + git add --intent-to-add file4 && + test_when_finished "git rm -f file4" && + test_must_fail git stash +' + test_expect_success 'stash rm then recreate' ' git reset --hard && git rm file && From 18d0c35a273a6cbaefc94d3d1dfb5dd885215f9c Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:12 +0000 Subject: [PATCH 39/63] 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: Thomas Gummerer 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 78d50bcf1e..b66da42831 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -612,7 +612,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 && @@ -626,7 +626,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 && @@ -690,7 +690,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 && @@ -701,7 +701,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 && @@ -715,7 +715,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 && @@ -1056,7 +1056,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" && @@ -1066,7 +1066,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 9346a09195451223ac2cde7838171424360e7451 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:13 +0000 Subject: [PATCH 40/63] 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: Thomas Gummerer Signed-off-by: Junio C Hamano --- t/t3907-stash-show-config.sh | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 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 0000000000..10914bba7b --- /dev/null +++ b/t/t3907-stash-show-config.sh @@ -0,0 +1,83 @@ +#!/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_unconfig stash.showStat + else + test_config stash.showStat "$1" + fi && + + if test "" = "$2" + then + test_unconfig stash.showPatch + else + test_config stash.showPatch "$2" + fi && + + shift 2 && + echo 2 >file.t && + if test $# != 0 + then + git diff "$@" >expect + fi && + git stash && + git stash show >actual && + + if test $# = 0 + 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 58e1f63a9c8b559a0df11ffba83c233ba925814f Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:14 +0000 Subject: [PATCH 41/63] stash: mention options in `show` synopsis Mention in the documentation, that `show` accepts any option known to `git diff`. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- Documentation/git-stash.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 7ef8c47911..e31ea7d303 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 From 4f6218c91b1a1a146727914cf8ac1c4bf3729eb8 Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Mon, 25 Feb 2019 23:16:15 +0000 Subject: [PATCH 42/63] 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: Thomas Gummerer Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/stash--helper.c | 451 ++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 78 +------ git.c | 1 + 6 files changed, 462 insertions(+), 71 deletions(-) create mode 100644 builtin/stash--helper.c diff --git a/.gitignore b/.gitignore index 7374587f9d..32765a6ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -162,6 +162,7 @@ /git-show-ref /git-stage /git-stash +/git-stash--helper /git-status /git-stripspace /git-submodule diff --git a/Makefile b/Makefile index c5240942f2..5c4b6e6ae5 100644 --- a/Makefile +++ b/Makefile @@ -1138,6 +1138,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 6538932e99..ff4460aff7 100644 --- a/builtin.h +++ b/builtin.h @@ -225,6 +225,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 0000000000..03511f466b --- /dev/null +++ b/builtin/stash--helper.c @@ -0,0 +1,451 @@ +#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 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)) + die(_("'%s' is not a stash-like commit"), revision); +} + +static int get_stash_info(struct stash_info *info, int argc, const char **argv) +{ + int ret; + char *end_of_rev; + char *expanded_ref; + const char *revision; + const char *commit = NULL; + struct object_id dummy; + struct strbuf symbolic = STRBUF_INIT; + + 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) +{ + int nr_trees = 1; + struct unpack_trees_options opts; + 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) +{ + int res; + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * 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, int quiet) +{ + int ret; + int has_index = index; + struct merge_options o; + struct object_id c_tree; + struct object_id index_tree; + struct commit *result; + const struct object_id *bases[1]; + + read_cache_preload(NULL); + if (refresh_cache(REFRESH_QUIET)) + return -1; + + if (write_cache_as_tree(&c_tree, 0, NULL)) + return error(_("cannot apply a stash in the middle of a merge")); + + if (index) { + if (oideq(&info->b_tree, &info->i_tree) || + oideq(&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 (oideq(&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 ret; + int quiet = 0; + int index = 0; + 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_apply_usage, 0); + + if (get_stash_info(&info, argc, argv)) + return -1; + + ret = do_apply_stash(prefix, &info, index, quiet); + 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 789ce2f41d..366a082853 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -583,76 +583,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() { @@ -730,7 +665,8 @@ push) ;; apply) shift - apply_stash "$@" + cd "$START_DIR" + git stash--helper apply "$@" ;; clear) shift diff --git a/git.c b/git.c index 2dd588674f..c041c6e057 100644 --- a/git.c +++ b/git.c @@ -555,6 +555,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 73f47ba2a0d727641d5dbc50985405abc14297f7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:52:01 +0100 Subject: [PATCH 43/63] fixup: stash: convert apply to builtin This change was introduced upstream in an evil merge. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 1 + 1 file changed, 1 insertion(+) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 03511f466b..eaa0643378 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -1,3 +1,4 @@ +#define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" #include "parse-options.h" From c58e38df53862ec5438e976a6dadd02af07e6017 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:52:41 +0100 Subject: [PATCH 44/63] fixup: stash: convert apply to builtin This change was introduced upstream in an evil merge. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index eaa0643378..d1049ea4a1 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -329,7 +329,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (info->has_u && restore_untracked(&info->u_tree)) return error(_("could not restore untracked files from stash")); - init_merge_options(&o); + init_merge_options(&o, the_repository); o.branch1 = "Updated upstream"; o.branch2 = "Stashed changes"; From 4d733c4f1d148ad7f9f65a43034b5244c466c49a Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Mon, 25 Feb 2019 23:16:16 +0000 Subject: [PATCH 45/63] 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: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 117 ++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 4 +- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index d1049ea4a1..e2fd9272e5 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -13,7 +13,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 }; @@ -22,6 +29,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 struct strbuf stash_index_path = STRBUF_INIT; @@ -137,6 +149,32 @@ 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) { int nr_trees = 1; @@ -424,6 +462,81 @@ 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, int quiet) +{ + int ret; + struct child_process cp_reflog = CHILD_PROCESS_INIT; + struct child_process cp = CHILD_PROCESS_INIT; + + /* + * 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) { + error(_("'%s' is not a stash reference"), info->revision.buf); + free_stash_info(info); + exit(1); + } +} + +static int drop_stash(int argc, const char **argv, const char *prefix) +{ + int ret; + int quiet = 0; + struct stash_info info; + 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, quiet); + free_stash_info(&info); + return ret; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -446,6 +559,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 366a082853..b8f70230f9 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -670,7 +670,7 @@ apply) ;; clear) shift - clear_stash "$@" + git stash--helper clear "$@" ;; create) shift @@ -682,7 +682,7 @@ store) ;; drop) shift - drop_stash "$@" + git stash--helper drop "$@" ;; pop) shift From 6fe783f5a668c931f065df0875871fe82eef3cf1 Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Mon, 25 Feb 2019 23:16:17 +0000 Subject: [PATCH 46/63] 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: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 46 +++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 17 ++------------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index e2fd9272e5..b7eccec1fc 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -15,6 +15,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 }; @@ -29,6 +30,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 @@ -537,6 +543,44 @@ 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) +{ + int ret; + const char *branch = NULL; + struct stash_info info; + struct child_process cp = CHILD_PROCESS_INIT; + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_branch_usage, 0); + + if (!argc) { + fprintf_ln(stderr, _("No branch name specified")); + return -1; + } + + 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, 0); + if (!ret && info.is_stash_ref) + ret = do_drop_stash(prefix, &info, 0); + + free_stash_info(&info); + + return ret; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -563,6 +607,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 b8f70230f9..67db321a4c 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -615,20 +615,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' @@ -690,7 +676,8 @@ pop) ;; branch) shift - apply_to_branch "$@" + cd "$START_DIR" + git stash--helper branch "$@" ;; *) case $# in From c85d792c297d84c5f3468f70a33ead3a1458eb88 Mon Sep 17 00:00:00 2001 From: Joel Teichroeb Date: Mon, 25 Feb 2019 23:16:18 +0000 Subject: [PATCH 47/63] 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: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 39 +++++++++++++++++++++++++++++++++- git-stash.sh | 47 ++--------------------------------------- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index b7eccec1fc..79a48bb872 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -14,7 +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 ( pop | apply ) [--index] [-q|--quiet] []"), N_("git stash--helper branch []"), N_("git stash--helper clear"), NULL @@ -25,6 +25,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 @@ -543,6 +548,36 @@ 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 ret; + int index = 0; + int quiet = 0; + 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, quiet))) + printf_ln(_("The stash entry is kept in case " + "you need it again.")); + else + ret = do_drop_stash(prefix, &info, quiet); + + free_stash_info(&info); + return ret; +} + static int branch_stash(int argc, const char **argv, const char *prefix) { int ret; @@ -607,6 +642,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 67db321a4c..8a9f907aa9 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -571,50 +571,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' @@ -672,7 +628,8 @@ drop) ;; pop) shift - pop_stash "$@" + cd "$START_DIR" + git stash--helper pop "$@" ;; branch) shift From 1e3b9c2135b1cd7fb9963cc419ae0bdbc8205384 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:19 +0000 Subject: [PATCH 48/63] 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: Thomas Gummerer 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 79a48bb872..a8f9984349 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -13,6 +13,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 []"), @@ -20,6 +21,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 @@ -616,6 +622,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(); @@ -646,6 +675,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 8a9f907aa9..ab3992b59d 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -399,11 +399,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 "$@" @@ -591,7 +586,7 @@ test -n "$seen_non_option" || set "push" "$@" case "$1" in list) shift - list_stash "$@" + git stash--helper list "$@" ;; show) shift From bef7e56075624cbabcec373a2eceda69c4652ce8 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:20 +0000 Subject: [PATCH 49/63] 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: Thomas Gummerer 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 a8f9984349..abbe4afc80 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -11,9 +11,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 []"), @@ -26,6 +29,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 @@ -645,6 +653,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(); @@ -677,6 +762,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 ab3992b59d..d0318f859e 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -395,35 +395,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 @@ -465,107 +436,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' @@ -590,7 +460,7 @@ list) ;; show) shift - show_stash "$@" + git stash--helper show "$@" ;; save) shift From 06b5b8fa31006a1b53e54df431ada6d3a0e06534 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:21 +0000 Subject: [PATCH 50/63] 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: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 62 +++++++++++++++++++++++++++++++++++++++++ git-stash.sh | 43 ++-------------------------- 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index abbe4afc80..1b9dcaffb2 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -59,6 +59,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 struct strbuf stash_index_path = STRBUF_INIT; @@ -730,6 +735,61 @@ 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 struct object_id *w_commit, const char *stash_msg, + int quiet) +{ + if (!stash_msg) + stash_msg = "Created via \"git stash store\"."; + + if (update_ref(stash_msg, ref_stash, w_commit, NULL, + REF_FORCE_CREATE_REFLOG, + quiet ? UPDATE_REFS_QUIET_ON_ERR : + UPDATE_REFS_MSG_ON_ERR)) { + if (!quiet) { + fprintf_ln(stderr, _("Cannot update %s with %s"), + ref_stash, oid_to_hex(w_commit)); + } + return -1; + } + + return 0; +} + +static int store_stash(int argc, const char **argv, const char *prefix) +{ + int quiet = 0; + const char *stash_msg = NULL; + struct object_id obj; + struct object_context dummy; + struct option options[] = { + OPT__QUIET(&quiet, N_("be quiet")), + 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) { + if (!quiet) + fprintf_ln(stderr, _("\"git stash store\" requires one " + " argument")); + return -1; + } + + if (get_oid_with_context(argv[0], quiet ? GET_OID_QUIETLY : 0, &obj, + &dummy)) { + if (!quiet) + fprintf_ln(stderr, _("Cannot update %s with %s"), + ref_stash, argv[0]); + return -1; + } + + return do_store_stash(&obj, stash_msg, quiet); +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -764,6 +824,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 d0318f859e..ff5556ccb0 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -208,45 +208,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= @@ -325,7 +286,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")" @@ -485,7 +446,7 @@ create) ;; store) shift - store_stash "$@" + git stash--helper store "$@" ;; drop) shift From 9b6e423d50bdc66bdba22bfea2c6600de8f6fb3a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 11 Mar 2019 16:53:11 +0100 Subject: [PATCH 51/63] fixup: stash: convert store to builtin This change was introduced upstream in an evil merge. Signed-off-by: Johannes Schindelin --- builtin/stash--helper.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 1b9dcaffb2..58b795daa5 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -779,7 +779,8 @@ static int store_stash(int argc, const char **argv, const char *prefix) return -1; } - if (get_oid_with_context(argv[0], quiet ? GET_OID_QUIETLY : 0, &obj, + if (get_oid_with_context(the_repository, + argv[0], quiet ? GET_OID_QUIETLY : 0, &obj, &dummy)) { if (!quiet) fprintf_ln(stderr, _("Cannot update %s with %s"), From cad14d649ed47f42bc16de6c9c867f4c2bde080e Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:22 +0000 Subject: [PATCH 52/63] stash: convert create to builtin Add stash create to the helper. Signed-off-by: Paul-Sebastian Ungureanu Helped-by: Matthew Kraai Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 449 +++++++++++++++++++++++++++++++++++++++- git-stash.sh | 2 +- 2 files changed, 449 insertions(+), 2 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 58b795daa5..11fb5858ba 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -13,6 +13,9 @@ #include "rerere.h" #include "revision.h" #include "log-tree.h" +#include "diffcore.h" + +#define INCLUDE_ALL_FILES 2 static const char * const git_stash_helper_usage[] = { N_("git stash--helper list []"), @@ -64,6 +67,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 struct strbuf stash_index_path = STRBUF_INIT; @@ -288,6 +296,20 @@ 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++) { + strbuf_addstr(data, q->queue[i]->one->path); + + /* NUL-terminate: will be fed to update-index -z */ + strbuf_addch(data, '\0'); + } +} + static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) { struct child_process cp = CHILD_PROCESS_INIT; @@ -791,6 +813,429 @@ static int store_stash(int argc, const char **argv, const char *prefix) return do_store_stash(&obj, stash_msg, quiet); } +static void add_pathspecs(struct argv_array *args, + struct pathspec ps) { + int i; + + for (i = 0; i < ps.nr; i++) + argv_array_push(args, ps.items[i].match); +} + +/* + * `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 *untracked_files) +{ + int i; + int max_len; + int found = 0; + char *seen; + struct dir_struct dir; + + memset(&dir, 0, sizeof(dir)); + if (include_untracked != INCLUDE_ALL_FILES) + 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)) { + found++; + strbuf_addstr(untracked_files, ent->name); + /* NUL-terminate: will be fed to update-index -z */ + strbuf_addch(untracked_files, '\0'); + } + free(ent); + } + + free(seen); + free(dir.entries); + free(dir.ignored); + clear_directory(&dir); + return found; +} + +/* + * 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; + struct rev_info rev; + struct object_id dummy; + struct strbuf out = STRBUF_INIT; + + /* No initial commit. */ + if (get_oid("HEAD", &dummy)) + return -1; + + if (read_cache() < 0) + return -1; + + init_revisions(&rev, NULL); + rev.prune_data = ps; + + rev.diffopt.flags.quick = 1; + rev.diffopt.flags.ignore_submodules = 1; + rev.abbrev = 0; + + add_head_to_pending(&rev); + diff_setup_done(&rev.diffopt); + + 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 files) +{ + 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, files.buf, files.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 int stash_patch(struct stash_info *info, struct pathspec ps, + struct strbuf *out_patch) +{ + 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; + } + + /* Find out what the user wants. */ + cp_add_i.git_cmd = 1; + argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", + "--", NULL); + add_pathspecs(&cp_add_i.args, ps); + 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; + } + + /* State of the working tree. */ + 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, out_patch, 0, NULL, 0)) { + ret = -1; + goto done; + } + + if (!out_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 rev_info rev; + 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; + + init_revisions(&rev, NULL); + + 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); + + 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, struct strbuf *stash_msg_buf, + int include_untracked, int patch_mode, + struct stash_info *info) +{ + int ret = 0; + int flags = 0; + int untracked_commit_option = 0; + 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 untracked_files = STRBUF_INIT; + struct strbuf patch = STRBUF_INIT; + + prepare_fallback_ident("git stash", "git@stash"); + + read_cache_preload(NULL); + refresh_cache(REFRESH_QUIET); + + if (get_oid("HEAD", &info->b_commit)) { + fprintf_ln(stderr, _("You do not have the initial commit yet")); + ret = -1; + goto done; + } else { + head_commit = lookup_commit(the_repository, &info->b_commit); + } + + if (!check_changes(ps, include_untracked)) { + ret = 1; + goto done; + } + + 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; + goto done; + } + + if (include_untracked && get_untracked_files(ps, include_untracked, + &untracked_files)) { + if (save_untracked_files(info, &msg, untracked_files)) { + fprintf_ln(stderr, _("Cannot save " + "the untracked files")); + ret = -1; + goto done; + } + untracked_commit_option = 1; + } + if (patch_mode) { + ret = stash_patch(info, ps, &patch); + 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; + goto done; + } + } + + if (!stash_msg_buf->len) + strbuf_addf(stash_msg_buf, "WIP on %s", msg.buf); + else + strbuf_insertf(stash_msg_buf, 0, "On %s: ", branch_name); + + /* + * `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_buf->buf, stash_msg_buf->len, &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(&untracked_files); + 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 strbuf stash_msg_buf = STRBUF_INIT; + 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)); + strbuf_addstr(&stash_msg_buf, stash_msg); + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info); + if (!ret) + printf_ln("%s", oid_to_hex(&info.w_commit)); + + strbuf_release(&stash_msg_buf); + + /* + * ret can be 1 if there were no changes. In this case, we should + * not error out. + */ + return ret < 0; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -800,7 +1245,7 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) OPT_END() }; - git_config(git_default_config, NULL); + git_config(git_diff_basic_config, NULL); argc = parse_options(argc, argv, prefix, options, git_stash_helper_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); @@ -827,6 +1272,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 ff5556ccb0..a9b3064ff0 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -442,7 +442,7 @@ clear) ;; create) shift - create_stash -m "$*" && echo "$w_commit" + git stash--helper create --message "$*" ;; store) shift From ef1b5f497e04332a2fc920ed84837c8927f355fd Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:23 +0000 Subject: [PATCH 53/63] stash: convert push to builtin Add stash push to the helper. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 246 +++++++++++++++++++++++++++++++++++++++- git-stash.sh | 6 +- 2 files changed, 246 insertions(+), 6 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 11fb5858ba..0b44c684ee 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -24,6 +24,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 }; @@ -72,6 +75,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 struct strbuf stash_index_path = STRBUF_INIT; @@ -1090,7 +1100,7 @@ done: static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, int include_untracked, int patch_mode, - struct stash_info *info) + struct stash_info *info, struct strbuf *patch) { int ret = 0; int flags = 0; @@ -1103,7 +1113,6 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, struct strbuf msg = STRBUF_INIT; struct strbuf commit_tree_label = STRBUF_INIT; struct strbuf untracked_files = STRBUF_INIT; - struct strbuf patch = STRBUF_INIT; prepare_fallback_ident("git stash", "git@stash"); @@ -1152,7 +1161,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps, &patch); + ret = stash_patch(info, ps, patch); if (ret < 0) { fprintf_ln(stderr, _("Cannot save the current " "worktree state")); @@ -1223,7 +1232,8 @@ static int create_stash(int argc, const char **argv, const char *prefix) memset(&ps, 0, sizeof(ps)); strbuf_addstr(&stash_msg_buf, stash_msg); - ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info); + ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info, + NULL); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1236,6 +1246,232 @@ static int create_stash(int argc, const char **argv, const char *prefix) return ret < 0; } +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; + struct strbuf patch = STRBUF_INIT; + struct strbuf stash_msg_buf = STRBUF_INIT; + + 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")); + ret = -1; + goto done; + } + + 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++) + ce_path_match(&the_index, active_cache[i], &ps, + ps_matched); + + if (report_path_error(ps_matched, &ps, NULL)) { + fprintf_ln(stderr, _("Did you forget to 'git add'?")); + ret = -1; + free(ps_matched); + goto done; + } + free(ps_matched); + } + + if (refresh_cache(REFRESH_QUIET)) { + ret = -1; + goto done; + } + + if (!check_changes(ps, include_untracked)) { + if (!quiet) + printf_ln(_("No local changes to save")); + goto done; + } + + if (!reflog_exists(ref_stash) && do_clear_stash()) { + ret = -1; + fprintf_ln(stderr, _("Cannot initialize stash")); + goto done; + } + + if (stash_msg) + strbuf_addstr(&stash_msg_buf, stash_msg); + if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, + &info, &patch)) { + ret = -1; + goto done; + } + + if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) { + ret = -1; + fprintf_ln(stderr, _("Cannot save the current status")); + goto done; + } + + printf_ln(_("Saved working directory and index state %s"), + stash_msg_buf.buf); + + 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 == INCLUDE_ALL_FILES) + argv_array_push(&cp.args, "-x"); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + discard_cache(); + if (ps.nr) { + struct child_process cp_add = CHILD_PROCESS_INIT; + struct child_process cp_diff = CHILD_PROCESS_INIT; + struct child_process cp_apply = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + + cp_add.git_cmd = 1; + argv_array_push(&cp_add.args, "add"); + if (!include_untracked) + argv_array_push(&cp_add.args, "-u"); + if (include_untracked == INCLUDE_ALL_FILES) + argv_array_push(&cp_add.args, "--force"); + argv_array_push(&cp_add.args, "--"); + add_pathspecs(&cp_add.args, ps); + if (run_command(&cp_add)) { + ret = -1; + goto done; + } + + cp_diff.git_cmd = 1; + argv_array_pushl(&cp_diff.args, "diff-index", "-p", + "--cached", "--binary", "HEAD", "--", + NULL); + add_pathspecs(&cp_diff.args, ps); + if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) { + ret = -1; + goto done; + } + + cp_apply.git_cmd = 1; + argv_array_pushl(&cp_apply.args, "apply", "--index", + "-R", NULL); + if (pipe_command(&cp_apply, 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 cp_ls = CHILD_PROCESS_INIT; + struct child_process cp_checkout = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + + if (reset_tree(&info.i_tree, 0, 1)) { + ret = -1; + goto done; + } + + cp_ls.git_cmd = 1; + argv_array_pushl(&cp_ls.args, "ls-files", "-z", + "--modified", "--", NULL); + + add_pathspecs(&cp_ls.args, ps); + if (pipe_command(&cp_ls, NULL, 0, &out, 0, NULL, 0)) { + ret = -1; + goto done; + } + + cp_checkout.git_cmd = 1; + argv_array_pushl(&cp_checkout.args, "checkout-index", + "-z", "--force", "--stdin", NULL); + if (pipe_command(&cp_checkout, out.buf, out.len, NULL, + 0, NULL, 0)) { + ret = -1; + goto done; + } + } + 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) { + struct child_process cp = CHILD_PROCESS_INIT; + + cp.git_cmd = 1; + argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); + add_pathspecs(&cp.args, ps); + if (run_command(&cp)) { + ret = -1; + goto done; + } + } + goto done; + } + +done: + strbuf_release(&stash_msg_buf); + 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_BOOL('k', "keep-index", &keep_index, + N_("keep index")), + 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(); @@ -1274,6 +1510,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 a9b3064ff0..51d7a06601 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -429,7 +429,8 @@ save) ;; push) shift - push_stash "$@" + cd "$START_DIR" + git stash--helper push "$@" ;; apply) shift @@ -465,7 +466,8 @@ branch) *) case $# in 0) - push_stash && + cd "$START_DIR" + git stash--helper push && say "$(gettext "(To restore them type \"git stash apply\")")" ;; *) From fa8ae450e3e8d5b4255c364e67b10e1fc212dc2d Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:24 +0000 Subject: [PATCH 54/63] 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. Add tests for `--quiet`. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 56 ++++++++++++++++++++++++++--------------- t/t3903-stash.sh | 23 +++++++++++++++++ 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 0b44c684ee..cb0430c72f 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -968,7 +968,7 @@ done: } static int stash_patch(struct stash_info *info, struct pathspec ps, - struct strbuf *out_patch) + struct strbuf *out_patch, int quiet) { int ret = 0; struct strbuf out = STRBUF_INIT; @@ -1021,7 +1021,8 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, } if (!out_patch->len) { - fprintf_ln(stderr, _("No changes selected")); + if (!quiet) + fprintf_ln(stderr, _("No changes selected")); ret = 1; } @@ -1100,7 +1101,8 @@ done: static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, int include_untracked, int patch_mode, - struct stash_info *info, struct strbuf *patch) + struct stash_info *info, struct strbuf *patch, + int quiet) { int ret = 0; int flags = 0; @@ -1120,7 +1122,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, refresh_cache(REFRESH_QUIET); 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; goto done; } else { @@ -1145,7 +1149,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, 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; goto done; } @@ -1153,26 +1159,29 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, if (include_untracked && get_untracked_files(ps, include_untracked, &untracked_files)) { if (save_untracked_files(info, &msg, untracked_files)) { - fprintf_ln(stderr, _("Cannot save " - "the untracked files")); + if (!quiet) + fprintf_ln(stderr, _("Cannot save " + "the untracked files")); ret = -1; goto done; } untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps, patch); + ret = stash_patch(info, ps, patch, quiet); 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; goto done; } @@ -1198,7 +1207,9 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, if (commit_tree(stash_msg_buf->buf, stash_msg_buf->len, &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; } @@ -1233,7 +1244,7 @@ static int create_stash(int argc, const char **argv, const char *prefix) memset(&ps, 0, sizeof(ps)); strbuf_addstr(&stash_msg_buf, stash_msg); ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info, - NULL); + NULL, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1295,26 +1306,29 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, if (!reflog_exists(ref_stash) && do_clear_stash()) { ret = -1; - fprintf_ln(stderr, _("Cannot initialize stash")); + if (!quiet) + fprintf_ln(stderr, _("Cannot initialize stash")); goto done; } if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, - &info, &patch)) { + &info, &patch, quiet)) { ret = -1; goto done; } if (do_store_stash(&info.w_commit, stash_msg_buf.buf, 1)) { ret = -1; - fprintf_ln(stderr, _("Cannot save the current status")); + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current status")); goto done; } - printf_ln(_("Saved working directory and index state %s"), - stash_msg_buf.buf); + if (!quiet) + printf_ln(_("Saved working directory and index state %s"), + stash_msg_buf.buf); if (!patch_mode) { if (include_untracked && !ps.nr) { @@ -1416,7 +1430,9 @@ 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 b66da42831..97cc71fbaf 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1072,6 +1072,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 b8fad7d2200defd282f5d9ada008c78a33f4bcf7 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:25 +0000 Subject: [PATCH 55/63] 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()`). Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 50 ++++++ git-stash.sh | 328 +--------------------------------------- 2 files changed, 52 insertions(+), 326 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index cb0430c72f..8c98ac3eb5 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -27,6 +27,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 }; @@ -82,6 +84,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 struct strbuf stash_index_path = STRBUF_INIT; @@ -1488,6 +1496,46 @@ 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 keep_index = -1; + int patch_mode = 0; + int include_untracked = 0; + int quiet = 0; + int ret = 0; + const char *stash_msg = NULL; + struct pathspec ps; + struct strbuf stash_msg_buf = STRBUF_INIT; + struct option options[] = { + OPT_BOOL('k', "keep-index", &keep_index, + N_("keep index")), + 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, "message", + N_("stash message")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_stash_helper_save_usage, + PARSE_OPT_KEEP_DASHDASH); + + if (argc) + stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' '); + + memset(&ps, 0, sizeof(ps)); + ret = do_push_stash(ps, stash_msg, quiet, keep_index, + patch_mode, include_untracked); + + strbuf_release(&stash_msg_buf); + return ret; +} + int cmd_stash__helper(int argc, const char **argv, const char *prefix) { pid_t pid = getpid(); @@ -1528,6 +1576,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 51d7a06601..695f1feba3 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -36,331 +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 -- "$@" -} - -prepare_fallback_ident () { - if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1 - then - GIT_AUTHOR_NAME="git stash" - GIT_AUTHOR_EMAIL=git@stash - GIT_COMMITTER_NAME="git stash" - GIT_COMMITTER_EMAIL=git@stash - export GIT_AUTHOR_NAME - export GIT_AUTHOR_EMAIL - export GIT_COMMITTER_NAME - export GIT_COMMITTER_EMAIL - fi -} - -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 () { - - prepare_fallback_ident - - 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} @@ -425,7 +100,8 @@ show) ;; save) shift - save_stash "$@" + cd "$START_DIR" + git stash--helper save "$@" ;; push) shift From 714dd7221a6ac63d3a23e57720c12c072592ac40 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:26 +0000 Subject: [PATCH 56/63] 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. 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. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 48 ++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index 8c98ac3eb5..ccf653567b 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -881,18 +881,17 @@ static int get_untracked_files(struct pathspec ps, int include_untracked, } /* - * 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; struct rev_info rev; struct object_id dummy; - struct strbuf out = STRBUF_INIT; /* No initial commit. */ if (get_oid("HEAD", &dummy)) @@ -920,16 +919,27 @@ 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; } +/* + * The function will fill `untracked_files` with the names of untracked files + * It will return 1 if there were any changes and 0 if there were not. + */ +static int check_changes(struct pathspec ps, int include_untracked, + struct strbuf *untracked_files) +{ + int ret = 0; + if (check_changes_tracked_files(ps)) + ret = 1; + + if (include_untracked && get_untracked_files(ps, include_untracked, + untracked_files)) + ret = 1; + + return ret; +} + static int save_untracked_files(struct stash_info *info, struct strbuf *msg, struct strbuf files) { @@ -1139,7 +1149,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, head_commit = lookup_commit(the_repository, &info->b_commit); } - if (!check_changes(ps, include_untracked)) { + if (!check_changes(ps, include_untracked, &untracked_files)) { ret = 1; goto done; } @@ -1164,8 +1174,7 @@ static int do_create_stash(struct pathspec ps, struct strbuf *stash_msg_buf, goto done; } - if (include_untracked && get_untracked_files(ps, include_untracked, - &untracked_files)) { + if (include_untracked) { if (save_untracked_files(info, &msg, untracked_files)) { if (!quiet) fprintf_ln(stderr, _("Cannot save " @@ -1250,6 +1259,9 @@ static int create_stash(int argc, const char **argv, const char *prefix) 0); memset(&ps, 0, sizeof(ps)); + if (!check_changes_tracked_files(ps)) + return 0; + strbuf_addstr(&stash_msg_buf, stash_msg); ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info, NULL, 0); @@ -1257,12 +1269,7 @@ static int create_stash(int argc, const char **argv, const char *prefix) printf_ln("%s", oid_to_hex(&info.w_commit)); strbuf_release(&stash_msg_buf); - - /* - * ret can be 1 if there were no changes. In this case, we should - * not error out. - */ - return ret < 0; + return ret; } static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, @@ -1272,6 +1279,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, struct stash_info info; struct strbuf patch = STRBUF_INIT; struct strbuf stash_msg_buf = STRBUF_INIT; + struct strbuf untracked_files = STRBUF_INIT; if (patch_mode && keep_index == -1) keep_index = 1; @@ -1306,7 +1314,7 @@ static int do_push_stash(struct pathspec ps, const char *stash_msg, int quiet, goto done; } - if (!check_changes(ps, include_untracked)) { + if (!check_changes(ps, include_untracked, &untracked_files)) { if (!quiet) printf_ln(_("No local changes to save")); goto done; From b561b4aabdf64295a7f9fdc0bdea221d82e7d68d Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:27 +0000 Subject: [PATCH 57/63] stash: replace all `write-tree` child processes with API calls Avoid spawning write-tree child processes by replacing the calls with in-core API calls. Signed-off-by: Paul-Sebastian Ungureanu Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash--helper.c | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/builtin/stash--helper.c b/builtin/stash--helper.c index ccf653567b..645dfa0770 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash--helper.c @@ -945,9 +945,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", @@ -962,15 +961,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)) { @@ -979,8 +974,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; } @@ -989,11 +984,10 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, struct strbuf *out_patch, int quiet) { 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); @@ -1019,17 +1013,12 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, } /* State of the working tree. */ - 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); @@ -1045,7 +1034,7 @@ static int stash_patch(struct stash_info *info, struct pathspec ps, } done: - strbuf_release(&out); + discard_index(&istate); remove_path(stash_index_path.buf); return ret; } @@ -1055,9 +1044,8 @@ static int stash_working_tree(struct stash_info *info, struct pathspec ps) int ret = 0; struct rev_info rev; 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 index_state istate = { NULL }; init_revisions(&rev, NULL); @@ -1097,20 +1085,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 fade169bfa6673b9dbd8937e668058708eb5e981 Mon Sep 17 00:00:00 2001 From: Paul-Sebastian Ungureanu Date: Mon, 25 Feb 2019 23:16:28 +0000 Subject: [PATCH 58/63] 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: Thomas Gummerer Signed-off-by: Junio C Hamano --- .gitignore | 1 - Makefile | 3 +- builtin.h | 2 +- builtin/{stash--helper.c => stash.c} | 156 +++++++++++++++------------ git-stash.sh | 153 -------------------------- git.c | 2 +- 6 files changed, 92 insertions(+), 225 deletions(-) rename builtin/{stash--helper.c => stash.c} (91%) delete mode 100755 git-stash.sh diff --git a/.gitignore b/.gitignore index 32765a6ccb..7374587f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -162,7 +162,6 @@ /git-show-ref /git-stage /git-stash -/git-stash--helper /git-status /git-stripspace /git-submodule diff --git a/Makefile b/Makefile index 5c4b6e6ae5..da60276963 100644 --- a/Makefile +++ b/Makefile @@ -635,7 +635,6 @@ SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-legacy-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 @@ -1138,7 +1137,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 ff4460aff7..b78ab6e30b 100644 --- a/builtin.h +++ b/builtin.h @@ -225,7 +225,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 91% rename from builtin/stash--helper.c rename to builtin/stash.c index 645dfa0770..11c36d6df1 100644 --- a/builtin/stash--helper.c +++ b/builtin/stash.c @@ -17,75 +17,70 @@ #define INCLUDE_ALL_FILES 2 -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 []"), - 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 }; @@ -221,7 +216,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) @@ -522,7 +517,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; @@ -595,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; @@ -621,7 +616,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; @@ -648,7 +643,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) { fprintf_ln(stderr, _("No branch name specified")); @@ -683,7 +678,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)) @@ -763,7 +758,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; @@ -809,7 +804,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) { @@ -1223,30 +1218,19 @@ done: 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 strbuf stash_msg_buf = STRBUF_INIT; 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); + /* Starting with argv[1], since argv[0] is "create" */ + strbuf_join_argv(&stash_msg_buf, argc - 1, ++argv, ' '); memset(&ps, 0, sizeof(ps)); if (!check_changes_tracked_files(ps)) return 0; - strbuf_addstr(&stash_msg_buf, stash_msg); - ret = do_create_stash(ps, &stash_msg_buf, include_untracked, 0, &info, + ret = do_create_stash(ps, &stash_msg_buf, 0, 0, &info, NULL, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1478,9 +1462,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, @@ -1513,7 +1498,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, PARSE_OPT_KEEP_DASHDASH); if (argc) @@ -1527,10 +1512,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() @@ -1538,16 +1525,16 @@ int cmd_stash__helper(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_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) + 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); @@ -1569,7 +1556,42 @@ 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); - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_helper_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 (starts_with(argv[i], "-m") || + starts_with(argv[i], "--message=")) + continue; + + usage_with_options(git_stash_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 695f1feba3..0000000000 --- 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 c041c6e057..725fd2ce3a 100644 --- a/git.c +++ b/git.c @@ -555,7 +555,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 68c524aa908e51d8be77504758cfe032af7bbb21 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 25 Feb 2019 23:16:29 +0000 Subject: [PATCH 59/63] stash: add back the original, scripted `git stash` This simply copies the version as of sd/stash-wo-user-name verbatim. As of now, it is not hooked up. The next commit will change the builtin `stash` to hand off to the scripted `git stash` when `stash.useBuiltin=false`. Signed-off-by: Johannes Schindelin Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- git-stash.sh | 769 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 769 insertions(+) create mode 100755 git-stash.sh diff --git a/git-stash.sh b/git-stash.sh new file mode 100755 index 0000000000..789ce2f41d --- /dev/null +++ b/git-stash.sh @@ -0,0 +1,769 @@ +#!/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 + +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 -- "$@" +} + +prepare_fallback_ident () { + if ! git -c user.useconfigonly=yes var GIT_COMMITTER_IDENT >/dev/null 2>&1 + then + GIT_AUTHOR_NAME="git stash" + GIT_AUTHOR_EMAIL=git@stash + GIT_COMMITTER_NAME="git stash" + GIT_COMMITTER_EMAIL=git@stash + export GIT_AUTHOR_NAME + export GIT_AUTHOR_EMAIL + export GIT_COMMITTER_NAME + export GIT_COMMITTER_EMAIL + fi +} + +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 () { + + prepare_fallback_ident + + 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")" +} + +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= + 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" -- "$@" + store_stash -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 +} + +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 "$@" + + 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 +} + +# +# 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" +# + +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")" + } +} + +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 () { + + 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 +} + +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 +} + +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' +# 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 + list_stash "$@" + ;; +show) + shift + show_stash "$@" + ;; +save) + shift + save_stash "$@" + ;; +push) + shift + push_stash "$@" + ;; +apply) + shift + apply_stash "$@" + ;; +clear) + shift + clear_stash "$@" + ;; +create) + shift + create_stash -m "$*" && echo "$w_commit" + ;; +store) + shift + store_stash "$@" + ;; +drop) + shift + drop_stash "$@" + ;; +pop) + shift + pop_stash "$@" + ;; +branch) + shift + apply_to_branch "$@" + ;; +*) + case $# in + 0) + push_stash && + say "$(gettext "(To restore them type \"git stash apply\")")" + ;; + *) + usage + esac + ;; +esac From 5de54543b05adf5906dc515bf92a2c4dd86fe122 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 25 Feb 2019 23:16:30 +0000 Subject: [PATCH 60/63] stash: optionally use the scripted version again We recently converted the `git stash` command from Unix shell scripts to builtins. Let's end users a way out when they discover a bug in the builtin command: `stash.useBuiltin`. As the file name `git-stash` is already in use, let's rename the scripted backend to `git-legacy-stash`. To make the test suite pass with `stash.useBuiltin=false`, this commit also backports rudimentary support for `-q` (but only *just* enough to appease the test suite), and adds a super-ugly hack to force exit code 129 for `git stash -h`. Signed-off-by: Johannes Schindelin Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + builtin/stash.c | 35 +++++++++++++++++++++++++++++ git-stash.sh => git-legacy-stash.sh | 34 +++++++++++++++++++++++++--- git-sh-setup.sh | 1 + git.c | 7 +++++- 6 files changed, 75 insertions(+), 4 deletions(-) rename git-stash.sh => git-legacy-stash.sh (97%) diff --git a/.gitignore b/.gitignore index 7374587f9d..766e80e65a 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,7 @@ /git-interpret-trailers /git-instaweb /git-legacy-rebase +/git-legacy-stash /git-log /git-ls-files /git-ls-remote diff --git a/Makefile b/Makefile index da60276963..29120fbfaf 100644 --- a/Makefile +++ b/Makefile @@ -633,6 +633,7 @@ SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh SCRIPT_SH += git-quiltimport.sh SCRIPT_SH += git-legacy-rebase.sh +SCRIPT_SH += git-legacy-stash.sh SCRIPT_SH += git-remote-testgit.sh SCRIPT_SH += git-request-pull.sh SCRIPT_SH += git-submodule.sh diff --git a/builtin/stash.c b/builtin/stash.c index 11c36d6df1..6dfcce8ff0 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -14,6 +14,7 @@ #include "revision.h" #include "log-tree.h" #include "diffcore.h" +#include "exec-cmd.h" #define INCLUDE_ALL_FILES 2 @@ -1512,6 +1513,26 @@ static int save_stash(int argc, const char **argv, const char *prefix) return ret; } +static int use_builtin_stash(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf out = STRBUF_INIT; + int ret; + + argv_array_pushl(&cp.args, + "config", "--bool", "stash.usebuiltin", NULL); + cp.git_cmd = 1; + if (capture_command(&cp, &out, 6)) { + strbuf_release(&out); + return 1; + } + + strbuf_trim(&out); + ret = !strcmp("true", out.buf); + strbuf_release(&out); + return ret; +} + int cmd_stash(int argc, const char **argv, const char *prefix) { int i = -1; @@ -1523,6 +1544,20 @@ int cmd_stash(int argc, const char **argv, const char *prefix) OPT_END() }; + if (!use_builtin_stash()) { + const char *path = mkpath("%s/git-legacy-stash", + git_exec_path()); + + if (sane_execvp(path, (char **)argv) < 0) + die_errno(_("could not exec %s"), path); + else + BUG("sane_execvp() returned???"); + } + + prefix = setup_git_directory(); + trace_repo_setup(prefix); + setup_work_tree(); + git_config(git_diff_basic_config, NULL); argc = parse_options(argc, argv, prefix, options, git_stash_usage, diff --git a/git-stash.sh b/git-legacy-stash.sh similarity index 97% rename from git-stash.sh rename to git-legacy-stash.sh index 789ce2f41d..8a8c4a9270 100755 --- a/git-stash.sh +++ b/git-legacy-stash.sh @@ -80,6 +80,28 @@ clear_stash () { fi } +maybe_quiet () { + case "$1" in + --keep-stdout) + shift + if test -n "$GIT_QUIET" + then + eval "$@" 2>/dev/null + else + eval "$@" + fi + ;; + *) + if test -n "$GIT_QUIET" + then + eval "$@" >/dev/null 2>&1 + else + eval "$@" + fi + ;; + esac +} + create_stash () { prepare_fallback_ident @@ -112,15 +134,18 @@ create_stash () { done git update-index -q --refresh - if no_changes "$@" + if maybe_quiet no_changes "$@" then exit 0 fi # state of the base commit - if b_commit=$(git rev-parse --verify HEAD) + if b_commit=$(maybe_quiet --keep-stdout git rev-parse --verify HEAD) then head=$(git rev-list --oneline -n 1 HEAD --) + elif test -n "$GIT_QUIET" + then + exit 1 else die "$(gettext "You do not have the initial commit yet")" fi @@ -315,7 +340,7 @@ push_stash () { test -n "$untracked" || git ls-files --error-unmatch -- "$@" >/dev/null || exit 1 git update-index -q --refresh - if no_changes "$@" + if maybe_quiet no_changes "$@" then say "$(gettext "No local changes to save")" exit 0 @@ -370,6 +395,9 @@ save_stash () { while test $# != 0 do case "$1" in + -q|--quiet) + GIT_QUIET=t + ;; --) shift break diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 378928518b..10d9764185 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -101,6 +101,7 @@ $LONG_USAGE")" case "$1" in -h) echo "$LONG_USAGE" + case "$0" in *git-legacy-stash) exit 129;; esac exit esac fi diff --git a/git.c b/git.c index 725fd2ce3a..37a21c0b0a 100644 --- a/git.c +++ b/git.c @@ -555,7 +555,12 @@ 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", cmd_stash, RUN_SETUP | NEED_WORK_TREE }, + /* + * NEEDSWORK: Until the builtin stash is thoroughly robust and no + * longer needs redirection to the stash shell script this is kept as + * is, then should be changed to RUN_SETUP | NEED_WORK_TREE + */ + { "stash", cmd_stash }, { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE }, { "stripspace", cmd_stripspace }, { "submodule--helper", cmd_submodule__helper, RUN_SETUP | SUPPORT_SUPER_PREFIX | NO_PARSEOPT }, From a373469aa65f8bf5f8536d8423e8b2468fbb1d61 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 25 Feb 2019 23:16:31 +0000 Subject: [PATCH 61/63] tests: add a special setup where stash.useBuiltin is off Add a GIT_TEST_STASH_USE_BUILTIN=false test mode which is equivalent to running with stash.useBuiltin=false. This is needed to spot that we're not introducing any regressions in the legacy stash version while we're carrying both it and the new built-in version. This imitates the equivalent treatment for the built-in rebase in 62c23938fae5 (tests: add a special setup where rebase.useBuiltin is off, 2018-11-14). Signed-off-by: Johannes Schindelin Signed-off-by: Thomas Gummerer Signed-off-by: Junio C Hamano --- builtin/stash.c | 5 ++++- t/README | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/builtin/stash.c b/builtin/stash.c index 6dfcce8ff0..51df092633 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1517,7 +1517,10 @@ static int use_builtin_stash(void) { struct child_process cp = CHILD_PROCESS_INIT; struct strbuf out = STRBUF_INIT; - int ret; + int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1); + + if (env != -1) + return env; argv_array_pushl(&cp.args, "config", "--bool", "stash.usebuiltin", NULL); diff --git a/t/README b/t/README index 886bbec5bc..51e63f4eec 100644 --- a/t/README +++ b/t/README @@ -383,6 +383,10 @@ GIT_TEST_REBASE_USE_BUILTIN=, when false, disables the builtin version of git-rebase. See 'rebase.useBuiltin' in git-config(1). +GIT_TEST_STASH_USE_BUILTIN=, when false, disables the +built-in version of git-stash. See 'stash.useBuiltin' in +git-config(1). + GIT_TEST_INDEX_THREADS= enables exercising the multi-threaded loading of the index for the whole test suite by bypassing the default number of cache entries and thread minimums. Setting this to 1 will make the From ab15e05117d0afdacb9f4d33f256b5430a0b87b4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 7 Mar 2019 15:16:06 +0100 Subject: [PATCH 62/63] legacy stash: fix "rudimentary backport of -q" When this developer backported support for `--quiet` to the scripted version of `git stash` in 80590055ea (stash: optionally use the scripted version again, 2018-12-20), it looked like a sane choice to use `eval` to execute the command line passed in via the parameter list of `maybe_quiet`. However, that is not what we should have done, as that command-line was already in the correct shape. This can be seen very clearly when passing arguments with special characters, like git stash -- ':(glob)**/*.txt' Since this is exactly what we want to test in the next commit (where we fix this very incantation with the built-in stash), let's fix the legacy scripted version of `git stash` first. Signed-off-by: Johannes Schindelin --- git-legacy-stash.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git-legacy-stash.sh b/git-legacy-stash.sh index 8a8c4a9270..f60e9b3e87 100755 --- a/git-legacy-stash.sh +++ b/git-legacy-stash.sh @@ -86,17 +86,17 @@ maybe_quiet () { shift if test -n "$GIT_QUIET" then - eval "$@" 2>/dev/null + "$@" 2>/dev/null else - eval "$@" + "$@" fi ;; *) if test -n "$GIT_QUIET" then - eval "$@" >/dev/null 2>&1 + "$@" >/dev/null 2>&1 else - eval "$@" + "$@" fi ;; esac From 66af0b6eb83af35a7cc3c09064d0db09512dc99a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 7 Mar 2019 15:04:10 +0100 Subject: [PATCH 63/63] built-in stash: handle :(glob) pathspecs again When passing a list of pathspecs to, say, `git add`, we need to be careful to use the original form, not the parsed form of the pathspecs. This makes a difference e.g. when calling git stash -- ':(glob)**/*.txt' where the original form includes the `:(glob)` prefix while the parsed form does not. However, in the built-in `git stash`, we passed the parsed (i.e. incorrect) form, and `git add` would fail with the error message: fatal: pathspec '**/*.txt' did not match any files at the stage where `git stash` drops the changes from the worktree, even if `refs/stash` has been actually updated successfully. This fixes https://github.com/git-for-windows/git/issues/2037 Signed-off-by: Johannes Schindelin --- builtin/stash.c | 5 +++-- t/t3905-stash-include-untracked.sh | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/builtin/stash.c b/builtin/stash.c index 51df092633..f7428b08f5 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -832,7 +832,7 @@ static void add_pathspecs(struct argv_array *args, int i; for (i = 0; i < ps.nr; i++) - argv_array_push(args, ps.items[i].match); + argv_array_push(args, ps.items[i].original); } /* @@ -1468,7 +1468,8 @@ static int push_stash(int argc, const char **argv, const char *prefix) git_stash_push_usage, 0); - parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL, prefix, argv); + parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, + prefix, argv); return do_push_stash(ps, stash_msg, quiet, keep_index, patch_mode, include_untracked); } diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index cc1c8a7bb2..29ca76f2fb 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -283,4 +283,10 @@ test_expect_success 'stash -u -- shows no changes when there are test_i18ncmp expect actual ' +test_expect_success 'stash -u with globs' ' + >untracked.txt && + git stash -u -- ":(glob)**/*.txt" && + test_path_is_missing untracked.txt +' + test_done