From: Junio C Hamano Date: Fri, 2 Mar 2018 20:18:23 +0000 (-0800) Subject: Merge branch 'nd/worktree-move' into next X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/5c514dfc923dcb0a40549f80cca4f5f88f22cc81?ds=inline;hp=-c Merge branch 'nd/worktree-move' into next "git worktree" learned move and remove subcommands. * nd/worktree-move: worktree remove: allow it when $GIT_WORK_TREE is already gone worktree remove: new command worktree move: refuse to move worktrees with submodules worktree move: accept destination as directory worktree move: new command worktree.c: add update_worktree_location() worktree.c: add validate_worktree() --- 5c514dfc923dcb0a40549f80cca4f5f88f22cc81 diff --combined Documentation/git-worktree.txt index 5ac3f68ab5,d322acbc67..e7eb24ab85 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@@ -12,7 -12,9 +12,9 @@@ SYNOPSI 'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b ] [] 'git worktree list' [--porcelain] 'git worktree lock' [--reason ] + 'git worktree move' 'git worktree prune' [-n] [-v] [--expire ] + 'git worktree remove' [--force] 'git worktree unlock' DESCRIPTION @@@ -34,10 -36,6 +36,6 @@@ The working tree's administrative file `git worktree prune` in the main or any linked working tree to clean up any stale administrative files. - If you move a linked working tree, you need to manually update the - administrative files so that they do not get pruned automatically. See - section "DETAILS" for more information. - If a linked working tree is stored on a portable device or network share which is not always mounted, you can prevent its administrative files from being pruned by issuing the `git worktree lock` command, optionally @@@ -52,11 -50,10 +50,11 @@@ is linked to the current repository, sh directory specific files such as HEAD, index, etc. `-` may also be specified as ``; it is synonymous with `@{-1}`. + -If is a branch name (call it `` and is not found, +If is a branch name (call it ``) and is not found, and neither `-b` nor `-B` nor `--detach` are used, but there does exist a tracking branch in exactly one remote (call it ``) -with a matching name, treat as equivalent to +with a matching name, treat as equivalent to: ++ ------------ $ git worktree add --track -b / ------------ @@@ -80,10 -77,22 +78,22 @@@ files from being pruned automatically. being moved or deleted. Optionally, specify a reason for the lock with `--reason`. + move:: + + Move a working tree to a new location. Note that the main working tree + or linked working trees containing submodules cannot be moved. + prune:: Prune working tree information in $GIT_DIR/worktrees. + remove:: + + Remove a working tree. Only clean working trees (no untracked files + and no modification in tracked files) can be removed. Unclean working + trees or ones with submodules can be removed with `--force`. The main + working tree cannot be removed. + unlock:: Unlock a working tree, allowing it to be pruned, moved or deleted. @@@ -93,9 -102,10 +103,10 @@@ OPTION -f:: --force:: - By default, `add` refuses to create a new working tree when `` is a branch name and - is already checked out by another working tree. This option overrides - that safeguard. + By default, `add` refuses to create a new working tree when + `` is a branch name and is already checked out by + another working tree and `remove` refuses to remove an unclean + working tree. This option overrides that safeguard. -b :: -B :: @@@ -197,7 -207,7 +208,7 @@@ thumb is do not make any assumption abo $GIT_DIR or $GIT_COMMON_DIR when you need to directly access something inside $GIT_DIR. Use `git rev-parse --git-path` to get the final path. - If you move a linked working tree, you need to update the 'gitdir' file + If you manually move a linked working tree, you need to update the 'gitdir' file in the entry's directory. For example, if a linked working tree is moved to `/newpath/test-next` and its `.git` file points to `/path/main/.git/worktrees/test-next`, then update @@@ -277,13 -287,6 +288,6 @@@ Multiple checkout in general is still e for submodules is incomplete. It is NOT recommended to make multiple checkouts of a superproject. - git-worktree could provide more automation for tasks currently - performed manually, such as: - - - `remove` to remove a linked working tree and its administrative files (and - warn if the working tree is dirty) - - `mv` to move or rename a working tree and update its administrative files - GIT --- Part of the linkgit:git[1] suite diff --combined builtin/worktree.c index 0cea8ca868,f77ef994c4..670555dedd --- a/builtin/worktree.c +++ b/builtin/worktree.c @@@ -14,10 -14,12 +14,12 @@@ #include "worktree.h" static const char * const worktree_usage[] = { - N_("git worktree add [] []"), + N_("git worktree add [] []"), N_("git worktree list []"), N_("git worktree lock [] "), + N_("git worktree move "), N_("git worktree prune []"), + N_("git worktree remove [] "), N_("git worktree unlock "), NULL }; @@@ -345,23 -347,9 +347,23 @@@ done * Hook failure does not warrant worktree deletion, so run hook after * is_junk is cleared, but do return appropriate code when hook fails. */ - if (!ret && opts->checkout) - ret = run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid), - oid_to_hex(&commit->object.oid), "1", NULL); + if (!ret && opts->checkout) { + const char *hook = find_hook("post-checkout"); + if (hook) { + const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL }; + cp.git_cmd = 0; + cp.no_stdin = 1; + cp.stdout_to_stderr = 1; + cp.dir = path; + cp.env = env; + cp.argv = NULL; + argv_array_pushl(&cp.args, absolute_path(hook), + oid_to_hex(&null_oid), + oid_to_hex(&commit->object.oid), + "1", NULL); + ret = run_command(&cp); + } + } argv_array_clear(&child_env); strbuf_release(&sb); @@@ -379,9 -367,7 +381,9 @@@ static int add(int ac, const char **av const char *branch; const char *opt_track = NULL; struct option options[] = { - OPT__FORCE(&opts.force, N_("checkout even if already checked out in other worktree")), + OPT__FORCE(&opts.force, + N_("checkout even if already checked out in other worktree"), + PARSE_OPT_NOCOMPLETE), OPT_STRING('b', NULL, &opts.new_branch, N_("branch"), N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), @@@ -621,6 -607,219 +623,220 @@@ static int unlock_worktree(int ac, cons return ret; } + static void validate_no_submodules(const struct worktree *wt) + { + struct index_state istate = { NULL }; + int i, found_submodules = 0; + - if (read_index_from(&istate, worktree_git_path(wt, "index")) > 0) { ++ if (read_index_from(&istate, worktree_git_path(wt, "index"), ++ get_worktree_git_dir(wt)) > 0) { + for (i = 0; i < istate.cache_nr; i++) { + struct cache_entry *ce = istate.cache[i]; + + if (S_ISGITLINK(ce->ce_mode)) { + found_submodules = 1; + break; + } + } + } + discard_index(&istate); + + if (found_submodules) + die(_("working trees containing submodules cannot be moved or removed")); + } + + static int move_worktree(int ac, const char **av, const char *prefix) + { + struct option options[] = { + OPT_END() + }; + struct worktree **worktrees, *wt; + struct strbuf dst = STRBUF_INIT; + struct strbuf errmsg = STRBUF_INIT; + const char *reason; + char *path; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 2) + usage_with_options(worktree_usage, options); + + path = prefix_filename(prefix, av[1]); + strbuf_addstr(&dst, path); + free(path); + + worktrees = get_worktrees(0); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + if (is_main_worktree(wt)) + die(_("'%s' is a main working tree"), av[0]); + if (is_directory(dst.buf)) { + const char *sep = find_last_dir_sep(wt->path); + + if (!sep) + die(_("could not figure out destination name from '%s'"), + wt->path); + strbuf_trim_trailing_dir_sep(&dst); + strbuf_addstr(&dst, sep); + } + if (file_exists(dst.buf)) + die(_("target '%s' already exists"), dst.buf); + + validate_no_submodules(wt); + + reason = is_worktree_locked(wt); + if (reason) { + if (*reason) + die(_("cannot move a locked working tree, lock reason: %s"), + reason); + die(_("cannot move a locked working tree")); + } + if (validate_worktree(wt, &errmsg, 0)) + die(_("validation failed, cannot move working tree: %s"), + errmsg.buf); + strbuf_release(&errmsg); + + if (rename(wt->path, dst.buf) == -1) + die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf); + + update_worktree_location(wt, dst.buf); + + strbuf_release(&dst); + free_worktrees(worktrees); + return 0; + } + + /* + * Note, "git status --porcelain" is used to determine if it's safe to + * delete a whole worktree. "git status" does not ignore user + * configuration, so if a normal "git status" shows "clean" for the + * user, then it's ok to remove it. + * + * This assumption may be a bad one. We may want to ignore + * (potentially bad) user settings and only delete a worktree when + * it's absolutely safe to do so from _our_ point of view because we + * know better. + */ + static void check_clean_worktree(struct worktree *wt, + const char *original_path) + { + struct argv_array child_env = ARGV_ARRAY_INIT; + struct child_process cp; + char buf[1]; + int ret; + + /* + * Until we sort this out, all submodules are "dirty" and + * will abort this function. + */ + validate_no_submodules(wt); + + argv_array_pushf(&child_env, "%s=%s/.git", + GIT_DIR_ENVIRONMENT, wt->path); + argv_array_pushf(&child_env, "%s=%s", + GIT_WORK_TREE_ENVIRONMENT, wt->path); + memset(&cp, 0, sizeof(cp)); + argv_array_pushl(&cp.args, "status", + "--porcelain", "--ignore-submodules=none", + NULL); + cp.env = child_env.argv; + cp.git_cmd = 1; + cp.dir = wt->path; + cp.out = -1; + ret = start_command(&cp); + if (ret) + die_errno(_("failed to run 'git status' on '%s'"), + original_path); + ret = xread(cp.out, buf, sizeof(buf)); + if (ret) + die(_("'%s' is dirty, use --force to delete it"), + original_path); + close(cp.out); + ret = finish_command(&cp); + if (ret) + die_errno(_("failed to run 'git status' on '%s', code %d"), + original_path, ret); + } + + static int delete_git_work_tree(struct worktree *wt) + { + struct strbuf sb = STRBUF_INIT; + int ret = 0; + + strbuf_addstr(&sb, wt->path); + if (remove_dir_recursively(&sb, 0)) { + error_errno(_("failed to delete '%s'"), sb.buf); + ret = -1; + } + strbuf_release(&sb); + return ret; + } + + static int delete_git_dir(struct worktree *wt) + { + struct strbuf sb = STRBUF_INIT; + int ret = 0; + + strbuf_addstr(&sb, git_common_path("worktrees/%s", wt->id)); + if (remove_dir_recursively(&sb, 0)) { + error_errno(_("failed to delete '%s'"), sb.buf); + ret = -1; + } + strbuf_release(&sb); + return ret; + } + + static int remove_worktree(int ac, const char **av, const char *prefix) + { + int force = 0; + struct option options[] = { + OPT_BOOL(0, "force", &force, + N_("force removing even if the worktree is dirty")), + OPT_END() + }; + struct worktree **worktrees, *wt; + struct strbuf errmsg = STRBUF_INIT; + const char *reason; + int ret = 0; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + if (ac != 1) + usage_with_options(worktree_usage, options); + + worktrees = get_worktrees(0); + wt = find_worktree(worktrees, prefix, av[0]); + if (!wt) + die(_("'%s' is not a working tree"), av[0]); + if (is_main_worktree(wt)) + die(_("'%s' is a main working tree"), av[0]); + reason = is_worktree_locked(wt); + if (reason) { + if (*reason) + die(_("cannot remove a locked working tree, lock reason: %s"), + reason); + die(_("cannot remove a locked working tree")); + } + if (validate_worktree(wt, &errmsg, WT_VALIDATE_WORKTREE_MISSING_OK)) + die(_("validation failed, cannot remove working tree: %s"), + errmsg.buf); + strbuf_release(&errmsg); + + if (file_exists(wt->path)) { + if (!force) + check_clean_worktree(wt, av[0]); + + ret |= delete_git_work_tree(wt); + } + /* + * continue on even if ret is non-zero, there's no going back + * from here. + */ + ret |= delete_git_dir(wt); + + free_worktrees(worktrees); + return ret; + } + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { @@@ -643,5 -842,9 +859,9 @@@ return lock_worktree(ac - 1, av + 1, prefix); if (!strcmp(av[1], "unlock")) return unlock_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "move")) + return move_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "remove")) + return remove_worktree(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); } diff --combined contrib/completion/git-completion.bash index 4943922ca7,ff4a39631e..58b95ec2fd --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@@ -280,39 -280,6 +280,39 @@@ __gitcomp ( esac } +# This function is equivalent to +# +# __gitcomp "$(git xxx --git-completion-helper) ..." +# +# except that the output is cached. Accept 1-3 arguments: +# 1: the git command to execute, this is also the cache key +# 2: extra options to be added on top (e.g. negative forms) +# 3: options to be excluded +__gitcomp_builtin () +{ + # spaces must be replaced with underscore for multi-word + # commands, e.g. "git remote add" becomes remote_add. + local cmd="$1" + local incl="$2" + local excl="$3" + + local var=__gitcomp_builtin_"${cmd/-/_}" + local options + eval "options=\$$var" + + if [ -z "$options" ]; then + # leading and trailing spaces are significant to make + # option removal work correctly. + options=" $(__git ${cmd/_/ } --git-completion-helper) $incl " + for i in $excl; do + options="${options/ $i / }" + done + eval "$var=\"$options\"" + fi + + __gitcomp "$options" +} + # Variation of __gitcomp_nl () that appends to the existing list of # completion candidates, COMPREPLY. __gitcomp_nl_append () @@@ -472,7 -439,7 +472,7 @@@ __git_refs ( track="" ;; *) - for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do + for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD; do case "$i" in $match*) if [ -e "$dir/$i" ]; then @@@ -627,7 -594,7 +627,7 @@@ __git_is_configured_remote ( __git_list_merge_strategies () { - git merge -s help 2>&1 | + LANG=C LC_ALL=C git merge -s help 2>&1 | sed -n -e '/[Aa]vailable strategies are: /,/^$/{ s/\.$// s/.*:// @@@ -1105,13 -1072,12 +1105,13 @@@ __git_count_arguments ( } __git_whitespacelist="nowarn warn error error-all fix" +__git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch" _git_am () { __git_find_repo_path if [ -d "$__git_repo_path"/rebase-apply ]; then - __gitcomp "--skip --continue --resolved --abort" + __gitcomp "$__git_am_inprogress_options" return fi case "$cur" in @@@ -1120,8 -1086,12 +1120,8 @@@ return ;; --*) - __gitcomp " - --3way --committer-date-is-author-date --ignore-date - --ignore-whitespace --ignore-space-change - --interactive --keep --no-utf8 --signoff --utf8 - --whitespace= --scissors - " + __gitcomp_builtin am "--no-utf8" \ + "$__git_am_inprogress_options" return esac } @@@ -1134,7 -1104,14 +1134,7 @@@ _git_apply ( return ;; --*) - __gitcomp " - --stat --numstat --summary --check --index - --cached --index-info --reverse --reject --unidiff-zero - --apply --no-add --exclude= - --ignore-whitespace --ignore-space-change - --whitespace= --inaccurate-eof --verbose - --recount --directory= - " + __gitcomp_builtin apply return esac } @@@ -1143,7 -1120,10 +1143,7 @@@ _git_add ( { case "$cur" in --*) - __gitcomp " - --interactive --refresh --patch --update --dry-run - --ignore-errors --intent-to-add --force --edit --chmod= - " + __gitcomp_builtin add return esac @@@ -1220,8 -1200,12 +1220,8 @@@ _git_branch ( __git_complete_refs --cur="${cur##--set-upstream-to=}" ;; --*) - __gitcomp " - --color --no-color --verbose --abbrev= --no-abbrev - --track --no-track --contains --no-contains --merged --no-merged - --set-upstream-to= --edit-description --list - --unset-upstream --delete --move --copy --remotes - --column --no-column --sort= --points-at + __gitcomp_builtin branch "--no-color --no-abbrev + --no-track --no-column " ;; *) @@@ -1263,7 -1247,11 +1263,7 @@@ _git_checkout ( __gitcomp "diff3 merge" "" "${cur##--conflict=}" ;; --*) - __gitcomp " - --quiet --ours --theirs --track --no-track --merge - --conflict= --orphan --patch --detach --ignore-skip-worktree-bits - --recurse-submodules --no-recurse-submodules - " + __gitcomp_builtin checkout "--no-track --no-recurse-submodules" ;; *) # check if --track, --no-track, or --no-guess was specified @@@ -1283,19 -1271,16 +1283,19 @@@ _git_cherry ( __git_complete_refs } +__git_cherry_pick_inprogress_options="--continue --quit --abort" + _git_cherry_pick () { __git_find_repo_path if [ -f "$__git_repo_path"/CHERRY_PICK_HEAD ]; then - __gitcomp "--continue --quit --abort" + __gitcomp "$__git_cherry_pick_inprogress_options" return fi case "$cur" in --*) - __gitcomp "--edit --no-commit --signoff --strategy= --mainline" + __gitcomp_builtin cherry-pick "" \ + "$__git_cherry_pick_inprogress_options" ;; *) __git_complete_refs @@@ -1307,7 -1292,7 +1307,7 @@@ _git_clean ( { case "$cur" in --*) - __gitcomp "--dry-run --quiet" + __gitcomp_builtin clean return ;; esac @@@ -1320,7 -1305,26 +1320,7 @@@ _git_clone ( { case "$cur" in --*) - __gitcomp " - --local - --no-hardlinks - --shared - --reference - --quiet - --no-checkout - --bare - --mirror - --origin - --upload-pack - --template= - --depth - --single-branch - --no-tags - --branch - --recurse-submodules - --no-single-branch - --shallow-submodules - " + __gitcomp_builtin clone "--no-single-branch" return ;; esac @@@ -1353,7 -1357,16 +1353,7 @@@ _git_commit ( return ;; --*) - __gitcomp " - --all --author= --signoff --verify --no-verify - --edit --no-edit - --amend --include --only --interactive - --dry-run --reuse-message= --reedit-message= - --reset-author --file= --message= --template= - --cleanup= --untracked-files --untracked-files= - --verbose --quiet --fixup= --squash= - --patch --short --date --allow-empty - " + __gitcomp_builtin commit "--no-edit --verify" return esac @@@ -1369,7 -1382,11 +1369,7 @@@ _git_describe ( { case "$cur" in --*) - __gitcomp " - --all --tags --contains --abbrev= --candidates= - --exact-match --debug --long --match --always --first-parent - --exclude --dirty --broken - " + __gitcomp_builtin describe return esac __git_complete_refs @@@ -1394,7 -1411,7 +1394,7 @@@ __git_diff_common_options="--stat --num --dirstat --dirstat= --dirstat-by-file --dirstat-by-file= --cumulative --diff-algorithm= - --submodule --submodule= + --submodule --submodule= --ignore-submodules " _git_diff () @@@ -1435,11 -1452,11 +1435,11 @@@ _git_difftool ( return ;; --*) - __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex - --base --ours --theirs - --no-renames --diff-filter= --find-copies-harder - --relative --ignore-submodules - --tool=" + __gitcomp_builtin difftool "$__git_diff_common_options + --base --cached --ours --theirs + --pickaxe-all --pickaxe-regex + --relative --staged + " return ;; esac @@@ -1448,6 -1465,12 +1448,6 @@@ __git_fetch_recurse_submodules="yes on-demand no" -__git_fetch_options=" - --quiet --verbose --append --upload-pack --force --keep --depth= - --tags --no-tags --all --prune --dry-run --recurse-submodules= - --unshallow --update-shallow -" - _git_fetch () { case "$cur" in @@@ -1456,7 -1479,7 +1456,7 @@@ return ;; --*) - __gitcomp "$__git_fetch_options" + __gitcomp_builtin fetch "--no-tags" return ;; esac @@@ -1493,7 -1516,10 +1493,7 @@@ _git_fsck ( { case "$cur" in --*) - __gitcomp " - --tags --root --unreachable --cache --no-reflogs --full - --strict --verbose --lost-found --name-objects - " + __gitcomp_builtin fsck "--no-reflogs" return ;; esac @@@ -1503,7 -1529,7 +1503,7 @@@ _git_gc ( { case "$cur" in --*) - __gitcomp "--prune --aggressive" + __gitcomp_builtin gc return ;; esac @@@ -1559,7 -1585,21 +1559,7 @@@ _git_grep ( case "$cur" in --*) - __gitcomp " - --cached - --text --ignore-case --word-regexp --invert-match - --full-name --line-number - --extended-regexp --basic-regexp --fixed-strings - --perl-regexp - --threads - --files-with-matches --name-only - --files-without-match - --max-depth - --count - --and --or --not --all-match - --break --heading --show-function --function-context - --untracked --no-index - " + __gitcomp_builtin grep return ;; esac @@@ -1577,7 -1617,7 +1577,7 @@@ _git_help ( { case "$cur" in --*) - __gitcomp "--all --guides --info --man --web" + __gitcomp_builtin help return ;; esac @@@ -1600,7 -1640,7 +1600,7 @@@ _git_init ( return ;; --*) - __gitcomp "--quiet --bare --template= --shared --shared=" + __gitcomp_builtin init return ;; esac @@@ -1610,7 -1650,13 +1610,7 @@@ _git_ls_files ( { case "$cur" in --*) - __gitcomp "--cached --deleted --modified --others --ignored - --stage --directory --no-empty-directory --unmerged - --killed --exclude= --exclude-from= - --exclude-per-directory= --exclude-standard - --error-unmatch --with-tree= --full-name - --abbrev --ignored --exclude-per-directory - " + __gitcomp_builtin ls-files "--no-empty-directory" return ;; esac @@@ -1624,7 -1670,7 +1624,7 @@@ _git_ls_remote ( { case "$cur" in --*) - __gitcomp "--heads --tags --refs --get-url --symref" + __gitcomp_builtin ls-remote return ;; esac @@@ -1748,19 -1794,22 +1748,19 @@@ _git_log ( __git_complete_revlist } -# Common merge options shared by git-merge(1) and git-pull(1). -__git_merge_options=" - --no-commit --no-stat --log --no-log --squash --strategy - --commit --stat --no-squash --ff --no-ff --ff-only --edit --no-edit - --verify-signatures --no-verify-signatures --gpg-sign - --quiet --verbose --progress --no-progress -" - _git_merge () { __git_complete_strategy && return case "$cur" in --*) - __gitcomp "$__git_merge_options - --rerere-autoupdate --no-rerere-autoupdate --abort --continue" + __gitcomp_builtin merge "--rerere-autoupdate + --no-rerere-autoupdate + --no-commit --no-edit --no-ff + --no-log --no-progress + --no-squash --no-stat + --no-verify-signatures + " return esac __git_complete_refs @@@ -1784,7 -1833,7 +1784,7 @@@ _git_merge_base ( { case "$cur" in --*) - __gitcomp "--octopus --independent --is-ancestor --fork-point" + __gitcomp_builtin merge-base return ;; esac @@@ -1795,7 -1844,7 +1795,7 @@@ _git_mv ( { case "$cur" in --*) - __gitcomp "--dry-run" + __gitcomp_builtin mv return ;; esac @@@ -1811,7 -1860,7 +1811,7 @@@ _git_name_rev () { - __gitcomp "--tags --all --stdin" + __gitcomp_builtin name-rev } _git_notes () @@@ -1821,7 -1870,7 +1821,7 @@@ case "$subcommand,$cur" in ,--*) - __gitcomp '--ref' + __gitcomp_builtin notes ;; ,*) case "$prev" in @@@ -1837,17 -1886,15 +1837,17 @@@ add,--reedit-message=*|append,--reedit-message=*) __git_complete_refs --cur="${cur#*=}" ;; - add,--*|append,--*) - __gitcomp '--file= --message= --reedit-message= - --reuse-message=' + add,--*) + __gitcomp_builtin notes_add + ;; + append,--*) + __gitcomp_builtin notes_append ;; copy,--*) - __gitcomp '--stdin' + __gitcomp_builtin notes_copy ;; prune,--*) - __gitcomp '--dry-run --verbose' + __gitcomp_builtin notes_prune ;; prune,*) ;; @@@ -1873,11 -1920,12 +1873,11 @@@ _git_pull ( return ;; --*) - __gitcomp " - --rebase --no-rebase - --autostash --no-autostash - $__git_merge_options - $__git_fetch_options - " + __gitcomp_builtin pull "--no-autostash --no-commit --no-edit + --no-ff --no-log --no-progress --no-rebase + --no-squash --no-stat --no-tags + --no-verify-signatures" + return ;; esac @@@ -1928,7 -1976,12 +1928,7 @@@ _git_push ( return ;; --*) - __gitcomp " - --all --mirror --tags --dry-run --force --verbose - --quiet --prune --delete --follow-tags - --receive-pack= --repo= --set-upstream - --force-with-lease --force-with-lease= --recurse-submodules= - " + __gitcomp_builtin push return ;; esac @@@ -1939,11 -1992,11 +1939,11 @@@ _git_rebase ( { __git_find_repo_path if [ -f "$__git_repo_path"/rebase-merge/interactive ]; then - __gitcomp "--continue --skip --abort --quit --edit-todo" + __gitcomp "--continue --skip --abort --quit --edit-todo --show-current-patch" return elif [ -d "$__git_repo_path"/rebase-apply ] || \ [ -d "$__git_repo_path"/rebase-merge ]; then - __gitcomp "--continue --skip --abort --quit" + __gitcomp "--continue --skip --abort --quit --show-current-patch" return fi __git_complete_strategy && return @@@ -2066,7 -2119,11 +2066,7 @@@ _git_status ( return ;; --*) - __gitcomp " - --short --branch --porcelain --long --verbose - --untracked-files= --ignore-submodules= --ignored - --column= --no-column - " + __gitcomp_builtin status "--no-column" return ;; esac @@@ -2208,7 -2265,14 +2208,7 @@@ _git_config ( esac case "$cur" in --*) - __gitcomp " - --system --global --local --file= - --list --replace-all - --get --get-all --get-regexp - --add --unset --unset-all - --remove-section --rename-section - --name-only - " + __gitcomp_builtin config return ;; branch.*.*) @@@ -2608,7 -2672,7 +2608,7 @@@ _git_remote ( if [ -z "$subcommand" ]; then case "$cur" in --*) - __gitcomp "--verbose" + __gitcomp_builtin remote ;; *) __gitcomp "$subcommands" @@@ -2619,33 -2683,33 +2619,33 @@@ case "$subcommand,$cur" in add,--*) - __gitcomp "--track --master --fetch --tags --no-tags --mirror=" + __gitcomp_builtin remote_add "--no-tags" ;; add,*) ;; set-head,--*) - __gitcomp "--auto --delete" + __gitcomp_builtin remote_set-head ;; set-branches,--*) - __gitcomp "--add" + __gitcomp_builtin remote_set-branches ;; set-head,*|set-branches,*) __git_complete_remote_or_refspec ;; update,--*) - __gitcomp "--prune" + __gitcomp_builtin remote_update ;; update,*) __gitcomp "$(__git_get_config_variables "remotes")" ;; set-url,--*) - __gitcomp "--push --add --delete" + __gitcomp_builtin remote_set-url ;; get-url,--*) - __gitcomp "--push --all" + __gitcomp_builtin remote_get-url ;; prune,--*) - __gitcomp "--dry-run" + __gitcomp_builtin remote_prune ;; *) __gitcomp_nl "$(__git_remotes)" @@@ -2657,7 -2721,7 +2657,7 @@@ _git_replace ( { case "$cur" in --*) - __gitcomp "--edit --graft --format= --list --delete" + __gitcomp_builtin replace return ;; esac @@@ -2681,26 -2745,26 +2681,26 @@@ _git_reset ( case "$cur" in --*) - __gitcomp "--merge --mixed --hard --soft --patch --keep" + __gitcomp_builtin reset return ;; esac __git_complete_refs } +__git_revert_inprogress_options="--continue --quit --abort" + _git_revert () { __git_find_repo_path if [ -f "$__git_repo_path"/REVERT_HEAD ]; then - __gitcomp "--continue --quit --abort" + __gitcomp "$__git_revert_inprogress_options" return fi case "$cur" in --*) - __gitcomp " - --edit --mainline --no-edit --no-commit --signoff - --strategy= --strategy-option= - " + __gitcomp_builtin revert "--no-edit" \ + "$__git_revert_inprogress_options" return ;; esac @@@ -2711,7 -2775,7 +2711,7 @@@ _git_rm ( { case "$cur" in --*) - __gitcomp "--cached --dry-run --ignore-unmatch --quiet" + __gitcomp_builtin rm return ;; esac @@@ -2769,7 -2833,12 +2769,7 @@@ _git_show_branch ( { case "$cur" in --*) - __gitcomp " - --all --remotes --topo-order --date-order --current --more= - --list --independent --merge-base --no-name - --color --no-color - --sha1-name --sparse --topics --reflog - " + __gitcomp_builtin show-branch "--no-color" return ;; esac @@@ -3002,7 -3071,11 +3002,7 @@@ _git_tag ( case "$cur" in --*) - __gitcomp " - --list --delete --verify --annotate --message --file - --sign --cleanup --local-user --force --column --sort= - --contains --no-contains --points-at --merged --no-merged --create-reflog - " + __gitcomp_builtin tag ;; esac } @@@ -3014,24 -3087,27 +3014,27 @@@ _git_whatchanged ( _git_worktree () { - local subcommands="add list lock prune unlock" + local subcommands="add list lock move prune remove unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" else case "$subcommand,$cur" in add,--*) - __gitcomp "--detach" + __gitcomp_builtin worktree_add ;; list,--*) - __gitcomp "--porcelain" + __gitcomp_builtin worktree_list ;; lock,--*) - __gitcomp "--reason" + __gitcomp_builtin worktree_lock ;; prune,--*) - __gitcomp "--dry-run --expire --verbose" + __gitcomp_builtin worktree_prune ;; + remove,--*) + __gitcomp "--force" + ;; *) ;; esac diff --combined strbuf.c index 5f138ed3c8,46930ad027..0759590b3e --- a/strbuf.c +++ b/strbuf.c @@@ -95,6 -95,7 +95,7 @@@ void strbuf_trim(struct strbuf *sb strbuf_rtrim(sb); strbuf_ltrim(sb); } + void strbuf_rtrim(struct strbuf *sb) { while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1])) @@@ -102,6 -103,13 +103,13 @@@ sb->buf[sb->len] = '\0'; } + void strbuf_trim_trailing_dir_sep(struct strbuf *sb) + { + while (sb->len > 0 && is_dir_sep((unsigned char)sb->buf[sb->len - 1])) + sb->len--; + sb->buf[sb->len] = '\0'; + } + void strbuf_ltrim(struct strbuf *sb) { char *b = sb->buf; @@@ -612,18 -620,14 +620,18 @@@ ssize_t strbuf_read_file(struct strbuf { int fd; ssize_t len; + int saved_errno; fd = open(path, O_RDONLY); if (fd < 0) return -1; len = strbuf_read(sb, fd, hint); + saved_errno = errno; close(fd); - if (len < 0) + if (len < 0) { + errno = saved_errno; return -1; + } return len; }