git worktree move/remove
cover git worktree move/remove
This version is the same as nd/worktree-move but with the recursive directory copy code removed. We rely on rename() to move directories.
Nguyễn Thái Ngọc Duy (6):
worktree.c: add validate_worktree() worktree.c: add update_worktree_location() worktree move: new command
worktree move: accept destination as directory worktree move: refuse to move worktrees with submodules worktree remove: new command
Documentation/git-worktree.txt | 28 ++++-- builtin/worktree.c | 162 +++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 +- t/t2028-worktree-move.sh | 56 ++++++++++++ worktree.c | 84 +++++++++++++++++ worktree.h | 11 +++ 6 files changed, 335 insertions(+), 11 deletions(-)
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 1/6 worktree.c: add validate_worktree()
This function is later used by "worktree move" and "worktree remove" to ensure that we have a good connection between the repository and the worktree. For example, if a worktree is moved manually, the worktree location recorded in $GIT_DIR/worktrees/.../gitdir is incorrect and we should not move that one.
--- worktree.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ worktree.h | 5 +++++ 2 files changed, 68 insertions(+)
worktree.c +63 -0
@@ -291,6 +291,69 @@ const char *is_worktree_locked(struct worktree *wt)return wt->lock_reason; } +static int report (int quiet,const char *fmt, ...) +{ +va_list params; + +if (quiet) +return -1 ; + +va_start (params, fmt); +vfprintf (stderr, fmt, params); +fputc ('\n' , stderr); +va_end (params); +return -1 ; +} + +int validate_worktree (const struct worktree *wt,int quiet) +{ +struct strbuf sb =STRBUF_INIT ; +const char *path; +int err; + +if (is_main_worktree (wt)) { +/* +* Main worktree using .git file to point to the +* repository would make it impossible to know where +* the actual worktree is if this function is executed +* from another worktree. No .git file support for now. +*/ +strbuf_addf (&sb,"%s/.git" , wt->path); +if (!is_directory (sb.buf)) { +strbuf_release (&sb); +return report (quiet,_ ("'%s/.git' at main worktree is not the repository directory" ), + wt->path); + } +return 0 ; + } + +/* +* Make sure "gitdir" file points to a real .git file and that +* file points back here. +*/ +if (!is_absolute_path (wt->path)) +return report (quiet,_ ("'%s' file does not contain absolute path to the worktree location" ), +git_common_path ("worktrees/%s/gitdir" , wt->id)); + +strbuf_addf (&sb,"%s/.git" , wt->path); +if (!file_exists (sb.buf)) { +strbuf_release (&sb); +return report (quiet,_ ("'%s/.git' does not exist" ), wt->path); + } + + path =read_gitfile_gently (sb.buf, &err); +strbuf_release (&sb); +if (!path) +return report (quiet,_ ("'%s/.git' is not a .git file, error code %d" ), + wt->path, err); + +if (fspathcmp (path,real_path (git_common_path ("worktrees/%s" , wt->id)))) +return report (quiet,_ ("'%s' does not point back to" ), + wt->path,git_common_path ("worktrees/%s" , wt->id)); + +return 0 ; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +5 -0
@@ -52,6 +52,11 @@ extern int is_main_worktree(const struct worktree *wt);*/ extern const char *is_worktree_locked (struct worktree *wt); +/* +* Return zero if the worktree is in good condition. +*/ +extern int validate_worktree (const struct worktree *wt,int quiet); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 2/6 worktree.c: add update_worktree_location()
In reply to Nguyễn Thái Ngọc Duy--- worktree.c | 21 +++++++++++++++++++++ worktree.h | 6 ++++++ 2 files changed, 27 insertions(+)
worktree.c +21 -0
@@ -354,6 +354,27 @@ int validate_worktree(const struct worktree *wt, int quiet)return 0 ; } +int update_worktree_location (struct worktree *wt,const char *path_) +{ +struct strbuf path =STRBUF_INIT ; +int ret =0 ; + +if (is_main_worktree (wt)) +return 0 ; + +strbuf_add_absolute_path (&path, path_); +if (fspathcmp (wt->path, path.buf)) { +write_file (git_common_path ("worktrees/%s/gitdir" , + wt->id), +"%s/.git" ,real_path (path.buf)); +free (wt->path); + wt->path =strbuf_detach (&path,NULL ); + ret =0 ; + } +strbuf_release (&path); +return ret; +} +int is_worktree_being_rebased (const struct worktree *wt,const char *target) {
worktree.h +6 -0
@@ -57,6 +57,12 @@ extern const char *is_worktree_locked(struct worktree *wt);*/ extern int validate_worktree (const struct worktree *wt,int quiet); +/* +* Update worktrees/xxx/gitdir with the new path. +*/ +extern int update_worktree_location (struct worktree *wt, +const char *path_); +/* * Free up the memory for worktree(s) */
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 3/6 worktree move: new command
In reply to Nguyễn Thái Ngọc DuyThere are two options to move the main worktree, but both have complications, so it's not implemented yet. Anyway the options are:
- convert the main worktree to a linked one and move it away, leave the git repository where it is. The repo essentially becomes bare after this move.
- move the repository with the main worktree. The tricky part is make sure all file descriptors to the repository are closed, or it may fail on Windows.
--- Documentation/git-worktree.txt | 7 +++++- builtin/worktree.c | 43 ++++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 2 +- t/t2028-worktree-move.sh | 30 ++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-)
Documentation/git-worktree.txt +6 -1
@@ -12,6 +12,7 @@ SYNOPSIS 'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] '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 unlock' <worktree>
@@ -71,6 +72,11 @@ files from being pruned automatically. This also prevents it from 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 +cannot be moved yet. + prune:: Prune working tree information in $GIT_DIR/worktrees.
@@ -252,7 +258,6 @@ 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 ---
builtin/worktree.c +43 -0
60 lines not on this page · raw message
contrib/completion/git-completion.bash +1 -1
@@ -2715,7 +2715,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list lock prune unlock" + local subcommands="add list lock move prune unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
t/t2028-worktree-move.sh +30 -0
@@ -59,4 +59,34 @@ test_expect_success 'unlock worktree twice' ' test_path_is_missing .git/worktrees/source/locked ' +test_expect_success 'move non-worktree' ' + mkdir abc && + test_must_fail git worktree move abc def +' + +test_expect_success 'move locked worktree' ' + git worktree lock source && + test_must_fail git worktree move source destination && + git worktree unlock source +' + +test_expect_success 'move worktree' ' + git worktree move source destination && + test_path_is_missing source && + git worktree list --porcelain | grep "^worktree" >actual && + cat <<-EOF >expected && + worktree $TRASH_DIRECTORY + worktree $TRASH_DIRECTORY/destination + worktree $TRASH_DIRECTORY/elsewhere + EOF + test_cmp expected actual && + git -C destination log --format=%s >actual2 && + echo init >expected2 && + test_cmp expected2 actual2 +' + +test_expect_success 'move main worktree' ' + test_must_fail git worktree move . def +' + test_done
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 4/6 worktree move: accept destination as directory
In reply to Nguyễn Thái Ngọc DuySimilar to "mv a b/", which is actually "mv a b/a", we extract basename of source worktree and create a directory of the same name at destination if dst path is a directory.
--- builtin/worktree.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-)
builtin/worktree.c +18 -1
@@ -541,7 +541,13 @@ static int move_worktree(int ac, const char **av, const char *prefix)strbuf_addstr (&dst,prefix_filename (prefix,strlen (prefix), av[1 ])); -if (file_exists (dst.buf)) +if (is_directory (dst.buf)) +/* +* keep going, dst will be appended after we get the +* source's absolute path +*/ + ; +else if (file_exists (dst.buf))die (_ ("target '%s' already exists" ), av[1 ]); worktrees =get_worktrees (0 );
@@ -559,6 +565,17 @@ static int move_worktree(int ac, const char **av, const char *prefix)if (validate_worktree (wt,0 ))return -1 ; +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_addstr (&dst, sep); +if (file_exists (dst.buf)) +die (_ ("target '%s' already exists" ), dst.buf); + } +if (rename (wt->path, dst.buf) ==-1 )die_errno (_ ("failed to move '%s' to '%s'" ), wt->path, dst.buf);
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 5/6 worktree move: refuse to move worktrees with submodules
In reply to Nguyễn Thái Ngọc DuySubmodules contains .git files with relative paths. After a worktree move, these files need to be updated or they may point to nowhere.
This is a bandage patch to make sure "worktree move" don't break people's worktrees by accident. When .git file update code is in place, this validate_no_submodules() could be removed.
--- builtin/worktree.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
builtin/worktree.c +23 -0
@@ -525,6 +525,27 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)return ret; } +static void validate_no_submodules (const struct worktree *wt) +{ +struct index_state istate = {0 }; +int i, found_submodules =0 ; + +if (read_index_from (&istate,worktree_git_path (wt,"index" )) >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 (_ ("This working tree contains submodules and cannot be moved yet" )); +} +static int move_worktree (int ac,const char **av,const char *prefix) {struct option options[] = {
@@ -565,6 +586,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)if (validate_worktree (wt,0 ))return -1 ; +validate_no_submodules (wt); +if (is_directory (dst.buf)) {const char *sep =find_last_dir_sep (wt->path);
Signature · 1 line
2.8.2.524.g6ff3d78
PATCH 6/6 worktree remove: new command
In reply to Nguyễn Thái Ngọc Duy--- Documentation/git-worktree.txt | 21 +++++---- builtin/worktree.c | 79 ++++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 ++- t/t2028-worktree-move.sh | 26 +++++++++++ 4 files changed, 121 insertions(+), 10 deletions(-)
Documentation/git-worktree.txt +12 -9
@@ -14,6 +14,7 @@ SYNOPSIS '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
@@ -81,6 +82,13 @@ 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 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.
@@ -90,9 +98,10 @@ OPTIONS -f:: --force:: - By default, `add` refuses to create a new working tree when `<branch>` - is already checked out by another working tree. This option overrides - that safeguard. + By default, `add` refuses to create a new working tree when + `<branch>` 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>::
@@ -253,12 +262,6 @@ Multiple checkout in general is still experimental, and the support 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) - GIT --- Part of the linkgit:git[1] suite
builtin/worktree.c +79 -0
96 lines not on this page · raw message
contrib/completion/git-completion.bash +4 -1
@@ -2715,7 +2715,7 @@ _git_whatchanged () _git_worktree () { - local subcommands="add list lock move prune unlock" + local subcommands="add list lock move prune remove unlock" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands"
@@ -2733,6 +2733,9 @@ _git_worktree () prune,--*) __gitcomp "--dry-run --expire --verbose" ;; + remove,--*) + __gitcomp "--force" + ;; *) ;; esac
t/t2028-worktree-move.sh +26 -0
@@ -89,4 +89,30 @@ test_expect_success 'move main worktree' ' test_must_fail git worktree move . def ' +test_expect_success 'remove main worktree' ' + test_must_fail git worktree remove . +' + +test_expect_success 'remove locked worktree' ' + git worktree lock destination && + test_must_fail git worktree remove destination && + git worktree unlock destination +' + +test_expect_success 'remove worktree with dirty tracked file' ' + echo dirty >>destination/init.t && + test_must_fail git worktree remove destination +' + +test_expect_success 'remove worktree with untracked file' ' + git -C destination checkout init.t && + : >destination/untracked && + test_must_fail git worktree remove destination +' + +test_expect_success 'force remove worktree with untracked file' ' + git worktree remove --force destination && + test_path_is_missing destination +' + test_done
Signature · 1 line
2.8.2.524.g6ff3d78
Quoted 2 lines · Nguyễn Thái Ngọc Duy
This version is the same as nd/worktree-move but with the recursive directory copy code removed. We rely on
rename()to move directories.
Much simpler ;-)
Will replace; thanks.
Quoted 15 lines
Nguyễn Thái Ngọc Duy (6):
worktree.c: addvalidate_worktree()worktree.c: addupdate_worktree_location()worktree move: new command
worktree move: accept destination as directory worktree move: refuse to move worktrees with submodules worktree remove: new commandDocumentation/git-worktree.txt | 28 ++++-- builtin/worktree.c | 162 +++++++++++++++++++++++++++++++++ contrib/completion/git-completion.bash | 5 +- t/t2028-worktree-move.sh | 56 ++++++++++++ worktree.c | 84 +++++++++++++++++ worktree.h | 11 +++ 6 files changed, 335 insertions(+), 11 deletions(-)