This keeps things a bit simpler when we add more fields, knowing that
default values are always zero.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
worktree.c | 14 ++------------
1 file changed, 2 insertions(+), 12 deletions(-)
This is no-op. But it helps reduce diff noise in the next patch.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
builtin/worktree.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
This is required by git-worktree.txt, stating that the main worktree is
the first line (especially in --porcelain mode when we can't just change
behavior at will).
There's only one case when get_worktrees() may skip main worktree, when
parse_ref() fails. Update the code so that we keep first item as main
worktree and return something sensible in this case:
- In user-friendly mode, since we're not constraint by anything,
returning "(error)" should do the job (we already show "(detached
HEAD)" which is not machine-friendly). Actually errors should be
printed on stderr by parse_ref() (*)
- In plumbing mode, we do not show neither 'bare', 'detached' or
'branch ...', which is possible by the format description if I read
it right.
Careful readers may realize that when the local variable "head_ref" in
get_main_worktree() is emptied, add_head_info() will do nothing to
wt->head_sha1. But that's ok because head_sha1 is zero-ized in the
previous patch.
(*) Well, it does not. But it's supposed to be a stop gap implementation
until we can reuse refs code to parse "ref: " stuff in HEAD, from
resolve_refs_unsafe(). Now may be the time since refs refactoring is
mostly done.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
builtin/worktree.c | 6 ++++--
t/t2027-worktree-list.sh | 21 +++++++++++++++++++++
worktree.c | 10 +++-------
3 files changed, 28 insertions(+), 9 deletions(-)
@@ -96,4 +96,25 @@ test_expect_success 'bare repo cleanup' 'rm-rfbare1'+test_expect_success'broken main worktree still at the top''+gitinitbroken-main&&+(+cdbroken-main&&+test_commitnew&&+gitworktreeaddlinked&&+cat>expected<<-EOF&&+worktree$(pwd)+HEAD$_z40++EOF+cdlinked&&+echo"worktree $(pwd)">expected&&+echo"ref: .broken">../.git/HEAD&&+gitworktreelist--porcelain|head-n3>actual&&+test_cmp../expectedactual&&+gitworktreelist|head-n1>actual.2&&+grep-F"(error)"actual.2+)+'+ test_done
It makes it easier to write tests for. But it should also be good for
the user since locating a worktree by eye would be easier once they
notice this.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
builtin/worktree.c | 2 +-
t/t2027-worktree-list.sh | 19 +++++++++++++++++++
worktree.c | 14 ++++++++++++++
worktree.h | 2 ++
4 files changed, 36 insertions(+), 1 deletion(-)
@@ -117,4 +117,23 @@ test_expect_success 'broken main worktree still at the top' ')'+test_expect_success'linked worktrees are sorted''+mkdirsorted&&+gitinitsorted/main&&+(+cdsorted/main&&+test_tick&&+test_commitnew&&+gitworktreeadd../first&&+gitworktreeadd../second&&+gitworktreelist--porcelain|grep^worktree>actual+)&&+cat>expected<<-EOF&&+worktree$(pwd)/sorted/main+worktree$(pwd)/sorted/first+worktree$(pwd)/sorted/second+EOF+test_cmpexpectedsorted/main/actual+'+ test_done
@@ -478,7 +478,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix)if(ac!=1)usage_with_options(worktree_usage,options);-worktrees=get_worktrees();+worktrees=get_worktrees(0);wt=find_worktree(worktrees,prefix,av[0]);if(!wt)die(_("'%s' is not a working tree"),av[0]);
@@ -511,7 +511,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)if(ac!=1)usage_with_options(worktree_usage,options);-worktrees=get_worktrees();+worktrees=get_worktrees(0);wt=find_worktree(worktrees,prefix,av[0]);if(!wt)die(_("'%s' is not a working tree"),av[0]);
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.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
worktree.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
worktree.h | 5 +++++
2 files changed, 68 insertions(+)
@@ -291,6 +291,69 @@ const char *is_worktree_locked(struct worktree *wt)returnwt->lock_reason;}+staticintreport(intquiet,constchar*fmt,...)+{+va_listparams;++if(quiet)+return-1;++va_start(params,fmt);+vfprintf(stderr,fmt,params);+fputc('\n',stderr);+va_end(params);+return-1;+}++intvalidate_worktree(conststructworktree*wt,intquiet)+{+structstrbufsb=STRBUF_INIT;+constchar*path;+interr;++if(is_main_worktree(wt)){+/*+*Mainworktreeusing.gitfiletopointtothe+*repositorywouldmakeitimpossibletoknowwhere+*theactualworktreeisifthisfunctionisexecuted+*fromanotherworktree.No.gitfilesupportfornow.+*/+strbuf_addf(&sb,"%s/.git",wt->path);+if(!is_directory(sb.buf)){+strbuf_release(&sb);+returnreport(quiet,_("'%s/.git' at main worktree is not the repository directory"),+wt->path);+}+return0;+}++/*+*Makesure"gitdir"filepointstoareal.gitfileandthat+*filepointsbackhere.+*/+if(!is_absolute_path(wt->path))+returnreport(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);+returnreport(quiet,_("'%s/.git' does not exist"),wt->path);+}++path=read_gitfile_gently(sb.buf,&err);+strbuf_release(&sb);+if(!path)+returnreport(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))))+returnreport(quiet,_("'%s' does not point back to"),+wt->path,git_common_path("worktrees/%s",wt->id));++return0;+}+intis_worktree_being_rebased(conststructworktree*wt,constchar*target){
There 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.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
Documentation/git-worktree.txt | 7 +++++-
builtin/worktree.c | 43 ++++++++++++++++++++++++++++++++++
contrib/completion/git-completion.bash | 2 +-
t/t2028-worktree-move.sh | 31 ++++++++++++++++++++++++
4 files changed, 81 insertions(+), 2 deletions(-)
@@ -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 ---
@@ -524,6 +525,46 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)returnret;}+staticintmove_worktree(intac,constchar**av,constchar*prefix)+{+structoptionoptions[]={+OPT_END()+};+structworktree**worktrees,*wt;+structstrbufdst=STRBUF_INIT;+constchar*reason;++ac=parse_options(ac,av,prefix,options,worktree_usage,0);+if(ac!=2)+usage_with_options(worktree_usage,options);++strbuf_addstr(&dst,prefix_filename(prefix,+strlen(prefix),+av[1]));+if(file_exists(dst.buf))+die(_("target '%s' already exists"),av[1]);++worktrees=get_worktrees(0);+wt=find_worktree(worktrees,prefix,av[0]);+if(!wt)+die(_("'%s' is not a working directory"),av[0]);+if(is_main_worktree(wt))+die(_("'%s' is a main working directory"),av[0]);+reason=is_worktree_locked(wt);+if(reason){+if(*reason)+die(_("already locked, reason: %s"),reason);+die(_("already locked, no reason"));+}+if(validate_worktree(wt,0))+return-1;++if(rename(wt->path,dst.buf)==-1)+die_errno(_("failed to move '%s' to '%s'"),wt->path,dst.buf);++returnupdate_worktree_location(wt,dst.buf);+}+intcmd_worktree(intac,constchar**av,constchar*prefix){structoptionoptions[]={
Similar 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.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
Signed-off-by: Junio C Hamano <redacted>
---
builtin/worktree.c | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
@@ -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)){+constchar*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);
@@ -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
@@ -605,6 +606,82 @@ static int move_worktree(int ac, const char **av, const char *prefix)returnupdate_worktree_location(wt,dst.buf);}+staticintremove_worktree(intac,constchar**av,constchar*prefix)+{+intforce=0;+structoptionoptions[]={+OPT_BOOL(0,"force",&force,+N_("force removing even if the worktree is dirty")),+OPT_END()+};+structworktree**worktrees,*wt;+structstrbufsb=STRBUF_INIT;+constchar*reason;+intret=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 directory"),av[0]);+if(is_main_worktree(wt))+die(_("'%s' is a main working directory"),av[0]);+reason=is_worktree_locked(wt);+if(reason){+if(*reason)+die(_("already locked, reason: %s"),reason);+die(_("already locked, no reason"));+}+if(validate_worktree(wt,0))+return-1;++if(!force){+structargv_arraychild_env=ARGV_ARRAY_INIT;+structchild_processcp;+charbuf[1];++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",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', code %d"),+av[0],ret);+ret=xread(cp.out,buf,sizeof(buf));+if(ret)+die(_("'%s' is dirty, use --force to delete it"),av[0]);+close(cp.out);+ret=finish_command(&cp);+if(ret)+die_errno(_("failed to run git-status on '%s', code %d"),+av[0],ret);+}+strbuf_addstr(&sb,wt->path);+if(remove_dir_recursively(&sb,0)){+error_errno(_("failed to delete '%s'"),sb.buf);+ret=-1;+}+strbuf_reset(&sb);+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);+free_worktrees(worktrees);+returnret;+}+intcmd_worktree(intac,constchar**av,constchar*prefix){structoptionoptions[]={
Submodules 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.
Signed-off-by: Nguyễn Thái Ngọc Duy <redacted>
---
builtin/worktree.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
@@ -525,6 +525,27 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)returnret;}+staticvoidvalidate_no_submodules(conststructworktree*wt)+{+structindex_stateistate={NULL};+inti,found_submodules=0;++if(read_index_from(&istate,worktree_git_path(wt,"index"))>0){+for(i=0;i<istate.cache_nr;i++){+structcache_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"));+}+staticintmove_worktree(intac,constchar**av,constchar*prefix){structoptionoptions[]={
I guess I mean that.
Given that this results in real data loss, it is surprising that this has
not made it even into `pu` yet!
Would you mind rebasing and re-submitting?
Thanks,
Johannes
I guess I mean that.
Given that this results in real data loss, it is surprising that this has
not made it even into `pu` yet!
I could rebase and clean it up a bit if you need it, but I don't
think it'll end up in 'pu' or anywhere near since Junio wanted a
cleaner approach [1]. That means (as far as I can see) a lot more work
around refs store and backend area before it's ready to handle "get
refs from this worktree store" (or "get refs from every reachable
stores").
[1] https://public-inbox.org/git/xmqqshwwzyee.fsf@gitster.mtv.corp.google.com/
--
Duy
I guess I mean that.
Given that this results in real data loss, it is surprising that this
has not made it even into `pu` yet!
I could rebase and clean it up a bit if you need it, but I don't think
it'll end up in 'pu' or anywhere near since Junio wanted a cleaner
approach [1]. That means (as far as I can see) a lot more work around
refs store and backend area before it's ready to handle "get refs from
this worktree store" (or "get refs from every reachable stores").
[1] https://public-inbox.org/git/xmqqshwwzyee.fsf@gitster.mtv.corp.google.com/
That is a big, big bummer.
We are talking about a data corrupting bug here, yes? It should be
possible to do that redesign work while having a small workaround in place
that unbreaks, say, me?
Ciao,
Johannes
I could rebase and clean it up a bit if you need it, but I don't
think it'll end up in 'pu' or anywhere near since Junio wanted a
cleaner approach [1]. That means (as far as I can see) a lot more work
around refs store and backend area before it's ready to handle "get
refs from this worktree store" (or "get refs from every reachable
stores").
[1] https://public-inbox.org/git/xmqqshwwzyee.fsf@gitster.mtv.corp.google.com/