"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()
'git worktree add' [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
'git worktree list' [--porcelain]
'git worktree lock' [--reason <string>] <worktree>
+ 'git worktree move' <worktree> <new-path>
'git worktree prune' [-n] [-v] [--expire <expire>]
+ 'git worktree remove' [--force] <worktree>
'git worktree unlock' <worktree>
DESCRIPTION
`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
directory specific files such as HEAD, index, etc. `-` may also be
specified as `<commit-ish>`; it is synonymous with `@{-1}`.
+
-If <commit-ish> is a branch name (call it `<branch>` and is not found,
+If <commit-ish> is a branch name (call it `<branch>`) 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 `<remote>`)
-with a matching name, treat as equivalent to
+with a matching name, treat as equivalent to:
++
------------
$ git worktree add --track -b <branch> <path> <remote>/<branch>
------------
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.
-f::
--force::
- By default, `add` refuses to create a new working tree when `<commit-ish>` 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
+ `<commit-ish>` 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 <new-branch>::
-B <new-branch>::
$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
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
#include "worktree.h"
static const char * const worktree_usage[] = {
- N_("git worktree add [<options>] <path> [<branch>]"),
+ N_("git worktree add [<options>] <path> [<commit-ish>]"),
N_("git worktree list [<options>]"),
N_("git worktree lock [<options>] <path>"),
+ N_("git worktree move <worktree> <new-path>"),
N_("git worktree prune [<options>]"),
+ N_("git worktree remove [<options>] <worktree>"),
N_("git worktree unlock <path>"),
NULL
};
* 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);
const char *branch;
const char *opt_track = NULL;
struct option options[] = {
- OPT__FORCE(&opts.force, N_("checkout <branch> even if already checked out in other worktree")),
+ OPT__FORCE(&opts.force,
+ N_("checkout <branch> 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"),
return ret;
}
- if (read_index_from(&istate, worktree_git_path(wt, "index")) > 0) {
+ 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"),
++ 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[] = {
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);
}
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 ()
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
__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/.*://
}
__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
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
}
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
}
{
case "$cur" in
--*)
- __gitcomp "
- --interactive --refresh --patch --update --dry-run
- --ignore-errors --intent-to-add --force --edit --chmod=
- "
+ __gitcomp_builtin add
return
esac
__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
"
;;
*)
__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
__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
{
case "$cur" in
--*)
- __gitcomp "--dry-run --quiet"
+ __gitcomp_builtin clean
return
;;
esac
{
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
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
{
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
--dirstat --dirstat= --dirstat-by-file
--dirstat-by-file= --cumulative
--diff-algorithm=
- --submodule --submodule=
+ --submodule --submodule= --ignore-submodules
"
_git_diff ()
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
__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
return
;;
--*)
- __gitcomp "$__git_fetch_options"
+ __gitcomp_builtin fetch "--no-tags"
return
;;
esac
{
case "$cur" in
--*)
- __gitcomp "
- --tags --root --unreachable --cache --no-reflogs --full
- --strict --verbose --lost-found --name-objects
- "
+ __gitcomp_builtin fsck "--no-reflogs"
return
;;
esac
{
case "$cur" in
--*)
- __gitcomp "--prune --aggressive"
+ __gitcomp_builtin gc
return
;;
esac
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
{
case "$cur" in
--*)
- __gitcomp "--all --guides --info --man --web"
+ __gitcomp_builtin help
return
;;
esac
return
;;
--*)
- __gitcomp "--quiet --bare --template= --shared --shared="
+ __gitcomp_builtin init
return
;;
esac
{
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
{
case "$cur" in
--*)
- __gitcomp "--heads --tags --refs --get-url --symref"
+ __gitcomp_builtin ls-remote
return
;;
esac
__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
{
case "$cur" in
--*)
- __gitcomp "--octopus --independent --is-ancestor --fork-point"
+ __gitcomp_builtin merge-base
return
;;
esac
{
case "$cur" in
--*)
- __gitcomp "--dry-run"
+ __gitcomp_builtin mv
return
;;
esac
_git_name_rev ()
{
- __gitcomp "--tags --all --stdin"
+ __gitcomp_builtin name-rev
}
_git_notes ()
case "$subcommand,$cur" in
,--*)
- __gitcomp '--ref'
+ __gitcomp_builtin notes
;;
,*)
case "$prev" in
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,*)
;;
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
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
{
__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
return
;;
--*)
- __gitcomp "
- --short --branch --porcelain --long --verbose
- --untracked-files= --ignore-submodules= --ignored
- --column= --no-column
- "
+ __gitcomp_builtin status "--no-column"
return
;;
esac
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.*.*)
if [ -z "$subcommand" ]; then
case "$cur" in
--*)
- __gitcomp "--verbose"
+ __gitcomp_builtin remote
;;
*)
__gitcomp "$subcommands"
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)"
{
case "$cur" in
--*)
- __gitcomp "--edit --graft --format= --list --delete"
+ __gitcomp_builtin replace
return
;;
esac
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
{
case "$cur" in
--*)
- __gitcomp "--cached --dry-run --ignore-unmatch --quiet"
+ __gitcomp_builtin rm
return
;;
esac
{
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
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
}
_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
strbuf_rtrim(sb);
strbuf_ltrim(sb);
}
+
void strbuf_rtrim(struct strbuf *sb)
{
while (sb->len > 0 && isspace((unsigned char)sb->buf[sb->len - 1]))
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;
{
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;
}