threads / discuss / 39723

Linked workdirs break typo-correction.

Subject: Linked workdirs break typo-correction.

## tl;dr

7 messages between Jun 25, 2015 and Jun 27, 2015.

replies: 6people: 4as markdown or json

Bjørnar Snoksrud· Jun 25, 2015, 14:15 UTC · lore
This is a weird one..

When standing in a folder inside a linked working copy, the typo-correction breaks all commands.

Repro:
~/git $ git --version
git version 2.4.4.600.g6397abd
~/git $ git init bar
Initialized empty Git repository in ~/git/bar/.git/
~/git $ cd bar
~/git/bar (master #) $ git commit -m init --allow-empty
[master (root-commit) 554ea84] init
~/git/bar (master) $ mkdir folder
~/git/bar (master) $ touch folder/file
~/git/bar (master) $ git add folder
~/git/bar (master +) $ git commit -m folder
[master 8c00ba8] folder
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 folder/file
~/git/bar (master) $ cd folder/
~/git/bar/folder (master) $ git shw
WARNING: You called a Git command named 'shw', which does not exist.
Continuing under the assumption that you meant 'show'
in 0.1 seconds automatically...
commit 8c00ba8d30cff0e0d1b9cf110a65ea8f33edf8b2
Author: Bjørnar Snoksrud <bsnoksru@cisco.com>
Date:   Thu Jun 25 16:08:01 2015 +0200
    folder
diff --git a/folder/file b/folder/file
new file mode 100644
index 0000000..e69de29
bsnoksru@calculon ~/git/bar/folder (master) $ git branch foo
bsnoksru@calculon ~/git/bar/folder (master) $ git checkout foo --to
~/git/foo
Enter /home/bsnoksru/git/foo (identifier foo)
Switched to branch 'foo'
bsnoksru@calculon ~/git/bar/folder (master) $ cd ../../foo/folder/
~/git/foo/folder (foo) $ git shw
WARNING: You called a Git command named 'shw', which does not exist.
Continuing under the assumption that you meant 'show'
in 0.1 seconds automatically...
fatal: internal error: work tree has already been set
Current worktree: ~/git/foo
New worktree: ~/git/foo/folder
~/git/foo/folder (foo) $ git brnch baz
WARNING: You called a Git command named 'brnch', which does not exist.
Continuing under the assumption that you meant 'branch'
in 0.1 seconds automatically...
fatal: internal error: work tree has already been set
Current worktree: /home/bsnoksru/git/foo
New worktree: /home/bsnoksru/git/foo/folder
-- 
bjornar@snoksrud.no
Nguyễn Thái Ngọc Duy· Jun 26, 2015, 10:37 UTC · re: Bjørnar Snoksrud · lore

[PATCH] setup: set env $GIT_WORK_TREE when work tree is set, like $GIT_DIR

In the test case, we run setup_git_dir_gently() the first time to read $GIT_DIR/config so that we can resolve aliases. We'll enter setup_discovered_git_dir() and may or may not call set_git_dir() near the end of the function, depending on whether the detected git dir is ".git" or not. This set_git_dir() will set env var $GIT_DIR.

For normal repo, git dir detected via setup_discovered_git_dir() will be ".git", and set_git_dir() is not called. If .git file is used however, the git dir can't be ".git" and set_git_dir() is called and $GIT_DIR set. This is the key of this problem.

If we expand an alias (or autocorrect command names), then setup_git_dir_gently() is run the second time. If $GIT_DIR is not set in the first run, we run the same setup_discovered_git_dir() as before. Nothing to see. If it is, however, we'll enter setup_explicit_git_dir() this time.

This is where the "fun" is. The legacy behavior is, if $GIT_WORK_TREE is not set but $GIT_DIR is, cwd is chosen as worktree's top. If you happen to stand at worktree's top when you do this, all is well. If you are in a subdir "foo/bar" (real worktree's top is "foo"), this behavior bites you: your detected worktree is now "foo/bar", but the first run correctly detected worktree as "foo". You get "internal error: work tree has already been set" as a result.

Bottom line is, when $GIT_DIR is set, $GIT_WORK_TREE should be set too unless there's no work tree. But setting $GIT_WORK_TREE inside set_git_dir() may backfire. We don't know at that point if work tree is already configured by the caller. So set it when work tree is detected. It does not harm if $GIT_WORK_TREE is set while $GIT_DIR is not.

Reported-by: Bjørnar Snoksrud <snoksrud@gmail.com>
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 environment.c      |  2 ++
 t/t0002-gitfile.sh | 17 +++++++++++++++++
 2 files changed, 19 insertions(+)
diff --git a/environment.c b/environment.c
index 61c685b..8f1b249 100644
--- a/environment.c
+++ b/environment.c
@@ -231,6 +231,8 @@ void set_git_work_tree(const char *new_work_tree)
 	}
 	git_work_tree_initialized = 1;
 	work_tree = xstrdup(real_path(new_work_tree));
+	if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
+		error("Could not set GIT_WORK_TREE to '%s'", work_tree);
 }
 
 const char *get_git_work_tree(void)
diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh
index 37e9396..9393322 100755
--- a/t/t0002-gitfile.sh
+++ b/t/t0002-gitfile.sh
@@ -99,4 +99,21 @@ test_expect_success 'check rev-list' '
 	test "$SHA" = "$(git rev-list HEAD)"
 '
 
+test_expect_success 'setup_git_dir twice in subdir' '
+	git init sgd &&
+	(
+		cd sgd &&
+		git config alias.lsfi ls-files &&
+		mv .git .realgit &&
+		echo "gitdir: .realgit" >.git &&
+		mkdir subdir &&
+		cd subdir &&
+		>foo &&
+		git add foo &&
+		git lsfi >actual &&
+		echo foo >expected &&
+		test_cmp expected actual
+	)
+'
+
 test_done
-- 
2.3.0.rc1.137.g477eb31
Jeff King· Jun 26, 2015, 11:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] setup: set env $GIT_WORK_TREE when work tree is set, like $GIT_DIR

On Fri, Jun 26, 2015 at 05:37:35PM +0700, Nguyễn Thái Ngọc Duy wrote:
Show 7 quoted lines
> This is where the "fun" is. The legacy behavior is, if $GIT_WORK_TREE is
> not set but $GIT_DIR is, cwd is chosen as worktree's top. If you happen
> to stand at worktree's top when you do this, all is well. If you are in
> a subdir "foo/bar" (real worktree's top is "foo"), this behavior bites
> you: your detected worktree is now "foo/bar", but the first run
> correctly detected worktree as "foo". You get "internal error: work tree
> has already been set" as a result.

I think this makes sense. I feel like we've dealt with this before, but the two previous rounds I found were basically:

  - we have GIT_IMPLICIT_WORK_TREE, but that is for the _opposite_ case.
    I.e., when we do not have a work tree and must communicate so to
    later code (including sub-processes).
  - a discussion about switching the "work tree defaults to '.' when
    $GIT_DIR is set" behavior yielded almost the identical patch:
      http://article.gmane.org/gmane.comp.version-control.git/219196
    but we were so wrapped up in the greater discussion we did not apply
    that simple fix. :)
Show 6 quoted lines
> Bottom line is, when $GIT_DIR is set, $GIT_WORK_TREE should be set too
> unless there's no work tree. But setting $GIT_WORK_TREE inside
> set_git_dir() may backfire. We don't know at that point if work tree is
> already configured by the caller. So set it when work tree is
> detected. It does not harm if $GIT_WORK_TREE is set while $GIT_DIR is
> not.

Yeah, it would be nicer if we could make sure we always set GIT_WORK_TREE along with GIT_DIR (since anything else is potentially dangerous), but I don't think it's feasible to do it in set_git_dir for the reasons you state.

I gave a quick peek through setup_explicit_git_dir to see if we missed other cases (spoiler: no):

  - if we have core.bare set, we set $GIT_DIR without setting the
    working tree. But that's OK, because we'll never look at
    $GIT_WORK_TREE in that case. Good.
  - if we have core.worktree set, then we call set_git_work_tree to
    match. Good.
  - If GIT_IMPLICIT_WORK_TREE is turned off, we set_git_dir but do not
    set GIT_WORK_TREE. Good, because otherwise it would take precedence.
  - there are some more calls to set_git_dir at the end, but I think in
    those cases we will already have set up the working tree (or decided
    we do not have one, by the above logic).

So that all seems fine. We also call set_git_dir from enter_repo, but in that case we have already moved into the .git directory itself, so that is OK. setup_discovered_git_dir also makes some calls, but either we are explicitly bare there, or we call set_git_work_tree(".").

So I do not see any cases that will regress, or that are uncovered. Of course, that is just from reading the code. Getting one million monkeys to bang on the keyboard may turn up any real problems. :)

> +	if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
> +		error("Could not set GIT_WORK_TREE to '%s'", work_tree);

Should this be die()? setenv() should basically never fail, but if it does, it could be confusing and/or dangerous to run without the variable set.

-Peff
Duy Nguyen· Jun 27, 2015, 05:57 UTC · re: Jeff King · lore

Re: [PATCH] setup: set env $GIT_WORK_TREE when work tree is set, like $GIT_DIR

On Fri, Jun 26, 2015 at 6:56 PM, Jeff King <peff@peff.net> wrote:
Show 24 quoted lines
> On Fri, Jun 26, 2015 at 05:37:35PM +0700, Nguyễn Thái Ngọc Duy wrote:
>
>> This is where the "fun" is. The legacy behavior is, if $GIT_WORK_TREE is
>> not set but $GIT_DIR is, cwd is chosen as worktree's top. If you happen
>> to stand at worktree's top when you do this, all is well. If you are in
>> a subdir "foo/bar" (real worktree's top is "foo"), this behavior bites
>> you: your detected worktree is now "foo/bar", but the first run
>> correctly detected worktree as "foo". You get "internal error: work tree
>> has already been set" as a result.
>
> I think this makes sense. I feel like we've dealt with this before, but
> the two previous rounds I found were basically:
>
>   - we have GIT_IMPLICIT_WORK_TREE, but that is for the _opposite_ case.
>     I.e., when we do not have a work tree and must communicate so to
>     later code (including sub-processes).
>
>   - a discussion about switching the "work tree defaults to '.' when
>     $GIT_DIR is set" behavior yielded almost the identical patch:
>
>       http://article.gmane.org/gmane.comp.version-control.git/219196
>
>     but we were so wrapped up in the greater discussion we did not apply
>     that simple fix. :)

There's also the patch "[PATCH v2] setup.c: set workdir when gitdir is not default" from Max Kirillov last year (gmane down, no link), so we have one patch about this every year since 2013 :)

Junio if you are worried about unnecessary setenv, I think Max's approach is safer as he solves a specific use case. If this problem pops up again in another use case, we can deal with that again.

Show 6 quoted lines
>> +     if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
>> +             error("Could not set GIT_WORK_TREE to '%s'", work_tree);
>
> Should this be die()? setenv() should basically never fail, but if it
> does, it could be confusing and/or dangerous to run without the variable
> set.

It's a straight copy from set_git_dir() but I guess the situation is a bit different. If setenv fails in gitdir, no repo is found but if it fails here we may select a wrong worktree and could wipe it out by mistake. Will fix if Junio chooses this patch instead of Max's.

-- 
Duy
Junio C Hamano· Jun 26, 2015, 17:15 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] setup: set env $GIT_WORK_TREE when work tree is set, like $GIT_DIR

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
Show 6 quoted lines
> Bottom line is, when $GIT_DIR is set, $GIT_WORK_TREE should be set too
> unless there's no work tree. But setting $GIT_WORK_TREE inside
> set_git_dir() may backfire. We don't know at that point if work tree is
> already configured by the caller. So set it when work tree is
> detected. It does not harm if $GIT_WORK_TREE is set while $GIT_DIR is
> not.

Hmmm, setting GIT_WORK_TREE locally without exporting it to our subprocesses would not hurt even when we do not see GIT_DIR, so I agree "It does not harm" is true for _us_, the process that runs this code itself.

But I am not sure if it is true for our children (e.g. hooks, filters etc. that is spawned by us). With this change, they inherit GIT_WORK_TREE and no GIT_DIR, in such a case. If they set GIT_DIR themselves for their own use, perhaps arranging to work in somewhere else they know by chdir'ing there, they did not have to set GIT_WORK_TREE=. before runing git in there, but now they do, because we start exporting GIT_WORK_TREE to interfere what they have been doing.

"It does not harm" is probably false for our children, I would think. The children can do new things to avoid the harm, though.

Show 46 quoted lines
> Reported-by: Bjørnar Snoksrud <snoksrud@gmail.com>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  environment.c      |  2 ++
>  t/t0002-gitfile.sh | 17 +++++++++++++++++
>  2 files changed, 19 insertions(+)
>
> diff --git a/environment.c b/environment.c
> index 61c685b..8f1b249 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -231,6 +231,8 @@ void set_git_work_tree(const char *new_work_tree)
>  	}
>  	git_work_tree_initialized = 1;
>  	work_tree = xstrdup(real_path(new_work_tree));
> +	if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
> +		error("Could not set GIT_WORK_TREE to '%s'", work_tree);
>  }
>  
>  const char *get_git_work_tree(void)
> diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh
> index 37e9396..9393322 100755
> --- a/t/t0002-gitfile.sh
> +++ b/t/t0002-gitfile.sh
> @@ -99,4 +99,21 @@ test_expect_success 'check rev-list' '
>  	test "$SHA" = "$(git rev-list HEAD)"
>  '
>  
> +test_expect_success 'setup_git_dir twice in subdir' '
> +	git init sgd &&
> +	(
> +		cd sgd &&
> +		git config alias.lsfi ls-files &&
> +		mv .git .realgit &&
> +		echo "gitdir: .realgit" >.git &&
> +		mkdir subdir &&
> +		cd subdir &&
> +		>foo &&
> +		git add foo &&
> +		git lsfi >actual &&
> +		echo foo >expected &&
> +		test_cmp expected actual
> +	)
> +'
> +
>  test_done
Junio C Hamano· Jun 26, 2015, 19:02 UTC · re: Junio C Hamano · lore

Re: [PATCH] setup: set env $GIT_WORK_TREE when work tree is set, like $GIT_DIR

Junio C Hamano <gitster@pobox.com> writes:
Show 11 quoted lines
> But I am not sure if it is true for our children (e.g. hooks,
> filters etc. that is spawned by us).  With this change, they inherit
> GIT_WORK_TREE and no GIT_DIR, in such a case.  If they set GIT_DIR
> themselves for their own use, perhaps arranging to work in somewhere
> else they know by chdir'ing there, they did not have to set
> GIT_WORK_TREE=. before runing git in there, but now they do, because
> we start exporting GIT_WORK_TREE to interfere what they have been
> doing.
>
> "It does not harm" is probably false for our children, I would
> think.  The children can do new things to avoid the harm, though.

Actually, I think I am overly and unnecessarily worried in the above.

We do not do anything special wrt existing GIT_ environment variables the user exported into us when spawning hooks or filters; if existing scripts wanted to visit a different repository and interact with it while they do their work, they would have already been seeing these variables in their environment inherited from us, and they should have been protecting themselves from these settings anyway. So I do not think this patch breaks anything (other than such scripts that are already broken).

So, sorry for a false alarm.  Queued.
Junio C Hamano· Jun 26, 2015, 17:30 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH] setup: set env $GIT_WORK_TREE when work tree is set, like $GIT_DIR

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
> This is where the "fun" is. The legacy behavior is, if $GIT_WORK_TREE is
> not set but $GIT_DIR is, cwd is chosen as worktree's top. If you happen
> to stand at worktree's top when you do this, all is well.

It is not legacy, though. It is how things were designed to be used, and how things are supposed to work in the future.

Until we do the deprecation dance to force people to update existing scripts to explicitly export GIT_WORK_TREE=$(pwd) or something like that, that is.

And I wouldn't be opposed to such a transition plan; something along the lines of what Peff outlined in that old thread.

http://thread.gmane.org/gmane.comp.version-control.git/219096/focus=219197
Show 6 quoted lines
> Bottom line is, when $GIT_DIR is set, $GIT_WORK_TREE should be set too
> unless there's no work tree. But setting $GIT_WORK_TREE inside
> set_git_dir() may backfire. We don't know at that point if work tree is
> already configured by the caller. So set it when work tree is
> detected. It does not harm if $GIT_WORK_TREE is set while $GIT_DIR is
> not.

I am inclined to queue this on 'next' per "experimental" basis so that other people with different workflows (especially those who use existing scripts around Git plumbing heavily) can see if this does not have any unintended fallouts. I cannot convince myself that it is generally a safe thing to do to muck with environment partially, hoping nobody would care.

Thanks for looking into it.
Show 43 quoted lines
>  environment.c      |  2 ++
>  t/t0002-gitfile.sh | 17 +++++++++++++++++
>  2 files changed, 19 insertions(+)
>
> diff --git a/environment.c b/environment.c
> index 61c685b..8f1b249 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -231,6 +231,8 @@ void set_git_work_tree(const char *new_work_tree)
>  	}
>  	git_work_tree_initialized = 1;
>  	work_tree = xstrdup(real_path(new_work_tree));
> +	if (setenv(GIT_WORK_TREE_ENVIRONMENT, work_tree, 1))
> +		error("Could not set GIT_WORK_TREE to '%s'", work_tree);
>  }
>  
>  const char *get_git_work_tree(void)
> diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh
> index 37e9396..9393322 100755
> --- a/t/t0002-gitfile.sh
> +++ b/t/t0002-gitfile.sh
> @@ -99,4 +99,21 @@ test_expect_success 'check rev-list' '
>  	test "$SHA" = "$(git rev-list HEAD)"
>  '
>  
> +test_expect_success 'setup_git_dir twice in subdir' '
> +	git init sgd &&
> +	(
> +		cd sgd &&
> +		git config alias.lsfi ls-files &&
> +		mv .git .realgit &&
> +		echo "gitdir: .realgit" >.git &&
> +		mkdir subdir &&
> +		cd subdir &&
> +		>foo &&
> +		git add foo &&
> +		git lsfi >actual &&
> +		echo foo >expected &&
> +		test_cmp expected actual
> +	)
> +'
> +
>  test_done

← back to recent threads