Skip to contentSkip to conversation

Handle git_path() with lock files correctly in worktrees

PATCH Superseded Johannes Schindelin via GitGitGadget 5 participants 45 messages Oct 16, 2019 – Oct 29, 2019 Documentation

lore
Share & tools
lore
#0 lore raw

cover Handle git_path() with lock files correctly in worktrees

I stumbled over this during my recent work in Git GUI
[https://github.com/gitgitgadget/git/pull/361] that was originally really only intended to use the correct hooks directory.

Technically, the first patch is not needed (because I decided against adding a test to t1400 in the end, in favor of t1500), but it shouldn't hurt, either.

Johannes Schindelin (2):
t1400: wrap setup code in test case
git_path(): handle .lock files correctly

 path.c                |  4 ++--
 t/t1400-update-ref.sh | 18 ++++++++++--------
 t/t1500-rev-parse.sh  | 15 +++++++++++++++
 3 files changed, 27 insertions(+), 10 deletions(-)

base-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-401%2Fdscho%2Flock-files-in-worktrees-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-401/dscho/lock-files-in-worktrees-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/401

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/2 t1400: wrap setup code in test case

From: Johannes Schindelin <[email protected]>

Without this, you cannot use --run=<...> to skip that part, and a run with --run=0 (which is a common way to determine the test case number corresponding to a given test case title).

---
 t/t1400-update-ref.sh | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
t/t1400-update-ref.sh +10 -8
index 1fbd940408..69a7f27311 100755
@@ -344,14 +344,16 @@ test_expect_success "verifying $m's log (logged by config)" '
 	test_cmp expect .git/logs/$m
 '
 
-git update-ref $m $D
-cat >.git/logs/$m <<EOF
-$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
-$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
-$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
-$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
-$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
-EOF
+test_expect_success 'set up for querying the reflog' '
+	git update-ref $m $D &&
+	cat >.git/logs/$m <<-EOF
+	$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+	$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
+	$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+	$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+	$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+	EOF
+'
 
 ed="Thu, 26 May 2005 18:32:00 -0500"
 gd="Thu, 26 May 2005 18:33:00 -0500"
Signature · 1 line
gitgitgadget
#2 lore raw

PATCH 2/2 git_path(): handle `.lock` files correctly

In reply to Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the index. However, the wrong path is returned for index.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that index.lock file is constructed from git_path("index") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). And one of the paths it is looking for is... you guessed it... index.lock.

So let's make that work as script writers would expect it to.

---
 path.c               |  4 ++--
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)
path.c +2 -2
index e3da1f3c4e..ff85692b45 100644
@@ -268,7 +268,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 	int result;
 	struct trie *child;
 
-	if (!*key) {
+	if (!*key || !strcmp(key, ".lock")) {
 		/* we have reached the end of the key */
 		if (root->value && !root->len)
 			return fn(key, root->value, baton);
@@ -288,7 +288,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key || !strcmp(key, ".lock"))
 		/* End of key */
 		return fn(key, root->value, baton);
 
t/t1500-rev-parse.sh +15 -0
index 01abee533d..d318a1eeef 100755
@@ -116,6 +116,21 @@ test_expect_success 'git-path inside sub-dir' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git-path in worktree' '
+	test_tick &&
+	git commit --allow-empty -m empty &&
+	git worktree add --detach wt &&
+	test_write_lines >expect \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
+		"$(pwd)/.git/worktrees/wt/index" \
+		"$(pwd)/.git/worktrees/wt/index.lock" &&
+	git -C wt rev-parse >actual \
+		--git-path logs/HEAD --git-path logs/HEAD.lock \
+		--git-path index --git-path index.lock &&
+	test_cmp expect actual
+'
+
 test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
 	test_commit test_commit &&
 	echo true >expect &&
Signature · 1 line
gitgitgadget
#3 lore raw

Re: [PATCH 2/2] git_path(): handle `.lock` files correctly

Quoted 5 lines · Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the index. However, the wrong path is returned for index.lock.

Could you give an example where it returns the wrong path for 'index.lock'? I tried to reproduce this issue in a working tree, but no matter what I've tried, 'git rev-parse --git-dir index.lock' always returned the right path.

Quoted 51 lines

This does not matter as long as the Git executable is doing the asking, as the path for that index.lock file is constructed from git_path("index") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). And one of the paths it is looking for is... you guessed it... index.lock.

So let's make that work as script writers would expect it to.

---
 path.c               |  4 ++--
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)
path.c +2 -2
index e3da1f3c4e..ff85692b45 100644
@@ -268,7 +268,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 	int result;
 	struct trie *child;
 
-	if (!*key) {
+	if (!*key || !strcmp(key, ".lock")) {
 		/* we have reached the end of the key */
 		if (root->value && !root->len)
 			return fn(key, root->value, baton);
@@ -288,7 +288,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key || !strcmp(key, ".lock"))
 		/* End of key */
 		return fn(key, root->value, baton);
 
t/t1500-rev-parse.sh +3 -0
index 01abee533d..d318a1eeef 100755
@@ -116,6 +116,21 @@ test_expect_success 'git-path inside sub-dir' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git-path in worktree' '
+	test_tick &&
+	git commit --allow-empty -m empty &&

t/t1500-rev-parse.sh test_expect_success 'git-path inside sub-dir' ' · line 131

Quoted 10 lines
t/t1500-rev-parse.sh +10 -0
+	git worktree add --detach wt &&
+	test_write_lines >expect \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
+		"$(pwd)/.git/worktrees/wt/index" \
+		"$(pwd)/.git/worktrees/wt/index.lock" &&
+	git -C wt rev-parse >actual \
+		--git-path logs/HEAD --git-path logs/HEAD.lock \
+		--git-path index --git-path index.lock &&
+	test_cmp expect actual

Without the fix applied this test fails with:

+ test_cmp expect actual
--- expect 2019-10-16 10:20:31.047229423 +0000
+++ actual 2019-10-16 10:20:31.051229519 +0000
@@ -1,4 +1,4 @@
/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/logs/HEAD
-/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/logs/HEAD.lock +/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/logs/HEAD.lock
/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/index /home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/index.lock
error: last command exited with $?=1

So the path of 'index.lock' seems to be fine already, it's the path of the lockfile for HEAD's reflog that's indeed wrong and makes the test fail.

On a related note, I'm not sure whether the path of the reflogs directory is right while in a different working tree... Both with and without this patch I get a path pointing to the main working tree:

  $ ./git -C WT/ rev-parse --git-path logs
  /home/szeder/src/git/.git/logs

However, I'm not sure what the right path should be in the first place, given that each working tree has its own 'logs' directory, but only for HEAD's reflog, while everything else goes to the main working tree's 'logs' directory.

Quoted 7 lines
t/t1500-rev-parse.sh +2 -0
+'
+
 test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
 	test_commit test_commit &&
 	echo true >expect &&
Signature · 1 line
gitgitgadget
#4 lore raw

Re: [PATCH 2/2] git_path(): handle `.lock` files correctly

Quoted 4 lines · SZEDER Gábor

However, I'm not sure what the right path should be in the first place, given that each working tree has its own 'logs' directory, but only for HEAD's reflog, while everything else goes to the main working tree's 'logs' directory.

As all worktrees should share the same view of where 'master' (for example) branch points at, what commit it was pointing at before, etc., the reflogs should also be shared for refs. The exception is the HEAD where each worktree can point at its own commit (when detached) or a branch (when not).

The above "should" does not mean "I know the code works that way so if your git behaves differently your compiler is wrong"; it just means "that's the logical conclusion of the basic design, and if your git does not behave that way, we have a bug (or two)".

Thanks.

#5 lore raw

Re: [PATCH 2/2] git_path(): handle `.lock` files correctly

In reply to SZEDER Gábor

Hi Gábor,

Quoted 9 lines · SZEDER Gábor
Quoted 5 lines · Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the index. However, the wrong path is returned for index.lock.

Could you give an example where it returns the wrong path for 'index.lock'?

Oh wow, this was a left-over from an early draft, before I got the regression test to work... What I meant was of course logs/HEAD.lock. Will fix.

Quoted 3 lines

I tried to reproduce this issue in a working tree, but
no matter what I've tried, 'git rev-parse --git-dir index.lock' always returned the right path.

With s/--git-dir/--git-path/, I agree.

Quoted 75 lines
Quoted 51 lines

This does not matter as long as the Git executable is doing the asking, as the path for that index.lock file is constructed from git_path("index") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). And one of the paths it is looking for is... you guessed it... index.lock.

So let's make that work as script writers would expect it to.

---
 path.c               |  4 ++--
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)
path.c +2 -2
index e3da1f3c4e..ff85692b45 100644
@@ -268,7 +268,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 	int result;
 	struct trie *child;
 
-	if (!*key) {
+	if (!*key || !strcmp(key, ".lock")) {
 		/* we have reached the end of the key */
 		if (root->value && !root->len)
 			return fn(key, root->value, baton);
@@ -288,7 +288,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key || !strcmp(key, ".lock"))
 		/* End of key */
 		return fn(key, root->value, baton);
 
t/t1500-rev-parse.sh +3 -0
index 01abee533d..d318a1eeef 100755
@@ -116,6 +116,21 @@ test_expect_success 'git-path inside sub-dir' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git-path in worktree' '
+	test_tick &&
+	git commit --allow-empty -m empty &&

t/t1500-rev-parse.sh test_expect_success 'git-path inside sub-dir' ' · line 131

Quoted 10 lines
t/t1500-rev-parse.sh +10 -0
+	git worktree add --detach wt &&
+	test_write_lines >expect \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
+		"$(pwd)/.git/worktrees/wt/index" \
+		"$(pwd)/.git/worktrees/wt/index.lock" &&
+	git -C wt rev-parse >actual \
+		--git-path logs/HEAD --git-path logs/HEAD.lock \
+		--git-path index --git-path index.lock &&
+	test_cmp expect actual

Without the fix applied this test fails with:

+ test_cmp expect actual
--- expect 2019-10-16 10:20:31.047229423 +0000
+++ actual 2019-10-16 10:20:31.051229519 +0000
@@ -1,4 +1,4 @@
/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/logs/HEAD
-/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/logs/HEAD.lock +/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/logs/HEAD.lock
/home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/index /home/szeder/src/git/t/trash directory.t1500-rev-parse/.git/worktrees/wt/index.lock
error: last command exited with $?=1

Quoted 3 lines

So the path of 'index.lock' seems to be fine already, it's the path of the lockfile for HEAD's reflog that's indeed wrong and makes the test fail.

Indeed, and this makes this patch much less important than I previosly thought. It's not like it would break Git GUI in worktrees, which is what I thought, which in turn is the reason I sent this so close to -rc0.

Quoted 11 lines

On a related note, I'm not sure whether the path of the reflogs directory is right while in a different working tree... Both with and without this patch I get a path pointing to the main working tree:

  $ ./git -C WT/ rev-parse --git-path logs
  /home/szeder/src/git/.git/logs

However, I'm not sure what the right path should be in the first place, given that each working tree has its own 'logs' directory, but only for HEAD's reflog, while everything else goes to the main working tree's 'logs' directory.

It's like Junio said, the reflog for HEAD is special because HEAD is special. Look for common_list in path.c (it is a bit confusing, I admit, you have to look for the 3rd column of numbers: if it is a 1, then it is a worktree-specific path, if it is 0, it is supposed to live in the "commondir", i.e. in the gitdir of the main worktree).

Thanks,
Dscho

Quoted 9 lines
Quoted 8 lines
t/t1500-rev-parse.sh +2 -1
+'
+
 test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
 	test_commit test_commit &&
 	echo true >expect &&
--

gitgitgadget

#26 lore raw

v2 cover Handle git_path() with lock files correctly in worktrees

In reply to Johannes Schindelin via GitGitGadget

I stumbled over this during my recent work in Git GUI
[https://github.com/gitgitgadget/git/pull/361] that was originally really only intended to use the correct hooks directory.

It turns out that my fears that index.lock was mishandled were unfounded, hence this patch series has a lot lower priority for me than "OMG we must push this into v2.24.0!".

Technically, the first patch is not needed (because I decided against adding a test to t1400 in the end, in favor of t1500), but it shouldn't hurt, either.

Changes since v1:

* Clarified the commit message to state that index.lock is fine, it is logs/HEAD.lock that isn't.

Johannes Schindelin (2):
t1400: wrap setup code in test case
git_path(): handle .lock files correctly

 path.c                |  4 ++--
 t/t1400-update-ref.sh | 18 ++++++++++--------
 t/t1500-rev-parse.sh  | 15 +++++++++++++++
 3 files changed, 27 insertions(+), 10 deletions(-)

base-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-401%2Fdscho%2Flock-files-in-worktrees-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-401/dscho/lock-files-in-worktrees-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/401

Range-diff vs v1:

 1:  cf97c5182e = 1:  cf97c5182e t1400: wrap setup code in test case
 2:  f08c90ea02 ! 2:  93dba5a3a3 git_path(): handle `.lock` files correctly

@@ -3,8 +3,9 @@
git_path(): handle .lock files correctly

Ever since worktrees were introduced, the git_path() function _really_
- needed to be called e.g. to get at the index. However, the wrong path
- is returned for index.lock.
+ needed to be called e.g. to get at the path to logs/HEAD (HEAD is
+ specific to the worktree). However, the wrong path is returned for
+ logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that index.lock file is constructed from
@@ -13,10 +14,12 @@
However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using
- the correct paths in worktrees other than the main one). And one of the
- paths it is looking for is... you guessed it... index.lock.
+ the correct paths in worktrees other than the main one). While it does
+ not seem as if Git GUI in particular is asking for logs/HEAD.lock,
+ let's be safe rather than sorry.

- So let's make that work as script writers would expect it to.
+ Side note: Git GUI _does_ ask for index.lock, but that is already
+ resolved correctly.

Signature · 1 line
gitgitgadget
#27 lore raw

PATCH v2 1/2 t1400: wrap setup code in test case

From: Johannes Schindelin <[email protected]>

Without this, you cannot use --run=<...> to skip that part, and a run with --run=0 (which is a common way to determine the test case number corresponding to a given test case title).

---
 t/t1400-update-ref.sh | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
t/t1400-update-ref.sh +10 -8
index 1fbd940408..69a7f27311 100755
@@ -344,14 +344,16 @@ test_expect_success "verifying $m's log (logged by config)" '
 	test_cmp expect .git/logs/$m
 '
 
-git update-ref $m $D
-cat >.git/logs/$m <<EOF
-$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
-$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
-$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
-$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
-$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
-EOF
+test_expect_success 'set up for querying the reflog' '
+	git update-ref $m $D &&
+	cat >.git/logs/$m <<-EOF
+	$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+	$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
+	$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+	$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+	$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+	EOF
+'
 
 ed="Thu, 26 May 2005 18:32:00 -0500"
 gd="Thu, 26 May 2005 18:33:00 -0500"
Signature · 1 line
gitgitgadget
#28 lore raw

PATCH v2 2/2 git_path(): handle `.lock` files correctly

In reply to Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is specific to the worktree). However, the wrong path is returned for logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that index.lock file is constructed from git_path("index") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). While it does not seem as if Git GUI in particular is asking for logs/HEAD.lock, let's be safe rather than sorry.

Side note: Git GUI _does_ ask for index.lock, but that is already resolved correctly.

---
 path.c               |  4 ++--
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)
path.c +2 -2
index e3da1f3c4e..ff85692b45 100644
@@ -268,7 +268,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 	int result;
 	struct trie *child;
 
-	if (!*key) {
+	if (!*key || !strcmp(key, ".lock")) {
 		/* we have reached the end of the key */
 		if (root->value && !root->len)
 			return fn(key, root->value, baton);
@@ -288,7 +288,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key || !strcmp(key, ".lock"))
 		/* End of key */
 		return fn(key, root->value, baton);
 
t/t1500-rev-parse.sh +15 -0
index 01abee533d..d318a1eeef 100755
@@ -116,6 +116,21 @@ test_expect_success 'git-path inside sub-dir' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git-path in worktree' '
+	test_tick &&
+	git commit --allow-empty -m empty &&
+	git worktree add --detach wt &&
+	test_write_lines >expect \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
+		"$(pwd)/.git/worktrees/wt/index" \
+		"$(pwd)/.git/worktrees/wt/index.lock" &&
+	git -C wt rev-parse >actual \
+		--git-path logs/HEAD --git-path logs/HEAD.lock \
+		--git-path index --git-path index.lock &&
+	test_cmp expect actual
+'
+
 test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
 	test_commit test_commit &&
 	echo true >expect &&
Signature · 1 line
gitgitgadget
#29 lore raw

Re: [PATCH v2 2/2] git_path(): handle `.lock` files correctly

"Johannes Schindelin via GitGitGadget" <[email protected]>

Quoted 10 lines · writes:

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is specific to the worktree). However, the wrong path is returned for logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that index.lock file is constructed from git_path("index") by appending the .lock suffix.

Is this still git_path("index") or is it now HEAD?

Quoted 2 lines

Side note: Git GUI _does_ ask for index.lock, but that is already resolved correctly.

Is that s/but/and/?

path.c static int trie_find(struct trie *root, const char *key, match_fn fn, · line 271

Quoted 10 lines
path.c +1 -1
index e3da1f3c4e..ff85692b45 100644
@@ -268,7 +268,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 	int result;
 	struct trie *child;
 
-	if (!*key) {
+	if (!*key || !strcmp(key, ".lock")) {

We only do strcmp for the tail part at the end of the path, so this should probably OK from performance point of view but semantically it is not very satisfying to see a special case for a single .suffix this deep in the callchain. I wonder if it is nicer to have the higher level callers notice ".lock" or whatever other suffixes they care about and ask the lower layer for a key with the suffix stripped?

Will queue.

Thanks.

#6 lore raw

Re: [PATCH 2/2] git_path(): handle `.lock` files correctly

In reply to Johannes Schindelin
Quoted 5 lines · Johannes Schindelin
Quoted 3 lines

I tried to reproduce this issue in a working tree, but
no matter what I've tried, 'git rev-parse --git-dir index.lock' always returned the right path.

With s/--git-dir/--git-path/, I agree.

Right. I mistyped it a few times on the command line as well, but then the command's output reminded me that I messed up. Alas, no such reminder when writing the email...

Quoted 12 lines
Quoted 11 lines

On a related note, I'm not sure whether the path of the reflogs directory is right while in a different working tree... Both with and without this patch I get a path pointing to the main working tree:

  $ ./git -C WT/ rev-parse --git-path logs
  /home/szeder/src/git/.git/logs

However, I'm not sure what the right path should be in the first place, given that each working tree has its own 'logs' directory, but only for HEAD's reflog, while everything else goes to the main working tree's 'logs' directory.

Quoted 5 lines

It's like Junio said, the reflog for HEAD is special because HEAD is special. Look for common_list in path.c (it is a bit confusing, I admit, you have to look for the 3rd column of numbers: if it is a 1, then it is a worktree-specific path, if it is 0, it is supposed to live in the "commondir", i.e. in the gitdir of the main worktree).

OK, got it.

I didn't look yesterday at all, but now I did, and, unfortunately, see two more bugs, and one of them is a "proper" bug leading to bogus output:

  $ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/worktrees/WT/logs/refs/
#7 lore raw

Re: [PATCH 2/2] git_path(): handle `.lock` files correctly

Quoted 20 lines · SZEDER Gábor
Quoted 12 lines
Quoted 11 lines

On a related note, I'm not sure whether the path of the reflogs directory is right while in a different working tree... Both with and without this patch I get a path pointing to the main working tree:

  $ ./git -C WT/ rev-parse --git-path logs
  /home/szeder/src/git/.git/logs

However, I'm not sure what the right path should be in the first place, given that each working tree has its own 'logs' directory, but only for HEAD's reflog, while everything else goes to the main working tree's 'logs' directory.

Quoted 5 lines

It's like Junio said, the reflog for HEAD is special because HEAD is special. Look for common_list in path.c (it is a bit confusing, I admit, you have to look for the 3rd column of numbers: if it is a 1, then it is a worktree-specific path, if it is 0, it is supposed to live in the "commondir", i.e. in the gitdir of the main worktree).

OK, got it.

Quoted 7 lines

I didn't look yesterday at all, but now I did, and, unfortunately, see two more bugs, and one of them is a "proper" bug leading to bogus output:

  $ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/worktrees/WT/logs/refs/

This one-liner below fixes it, but I haven't yet made up my mind about whether this is the right fix or whether there could be any fallout (at least the test suite doesn't show any).

  $ ./git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/logs/refs/
path.c +1 -1
index 04b69b9feb..9019169418 100644
@@ -335,7 +335,7 @@ static int check_common(const char *unmatched, void *value, void *baton)
 	struct common_dir *dir = value;
 
 	if (!dir)
-		return 0;
+		return -1;
 
 	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
 		return !dir->exclude;
#23 lore raw

PATCH 1/2 path.c: fix field name in 'struct common_dir'

In reply to SZEDER Gábor

An array of 'struct common_dir' instances is used to specify whether various paths in the '.git' directory are worktree-specific or belong to the main worktree. Confusingly, the path is recorded in the struct's 'dirname' field, even though it can be a regular file, e.g. 'gc.pid'.

Rename this 'dirname' field to 'path' to avoid future confusion.

---
 path.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
path.c +5 -5
index e3da1f3c4e..eeb43e1d25 100644
@@ -103,7 +103,7 @@ struct common_dir {
 	unsigned is_dir:1;
 	/* Not common even though its parent is */
 	unsigned exclude:1;
-	const char *dirname;
+	const char *path;
 };
 
 static struct common_dir common_list[] = {
@@ -320,8 +320,8 @@ static void init_common_trie(void)
 	if (common_trie_done_setup)
 		return;
 
-	for (p = common_list; p->dirname; p++)
-		add_to_trie(&common_trie, p->dirname, p);
+	for (p = common_list; p->path; p++)
+		add_to_trie(&common_trie, p->path, p);
 
 	common_trie_done_setup = 1;
 }
@@ -365,8 +365,8 @@ void report_linked_checkout_garbage(void)
 		return;
 	strbuf_addf(&sb, "%s/", get_git_dir());
 	len = sb.len;
-	for (p = common_list; p->dirname; p++) {
-		const char *path = p->dirname;
+	for (p = common_list; p->path; p++) {
+		const char *path = p->path;
 		if (p->ignore_garbage)
 			continue;
 		strbuf_setlen(&sb, len);
Signature · 1 line
2.23.0.1084.gae250eaa40
#24 lore raw

PATCH 2/2 path.c: mark 'logs/HEAD' in 'common_list' as file

In reply to SZEDER Gábor

'logs/HEAD', i.e. HEAD's reflog, is a file, but its entry in 'common_list' has the 'is_dir' bit set.

Unset that bit to make it consistent with what 'logs/HEAD' is supposed to be.

This doesn't make a difference in behavior: check_common() is the only function that looks at the 'is_dir' bit, and that function either returns 0, or '!exclude', which for 'logs/HEAD' results in 0 as well.

---
 path.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
path.c +1 -1
index eeb43e1d25..04b69b9feb 100644
@@ -113,7 +113,7 @@ static struct common_dir common_list[] = {
 	{ 0, 1, 0, "info" },
 	{ 0, 0, 1, "info/sparse-checkout" },
 	{ 1, 1, 0, "logs" },
-	{ 1, 1, 1, "logs/HEAD" },
+	{ 1, 0, 1, "logs/HEAD" },
 	{ 0, 1, 1, "logs/refs/bisect" },
 	{ 0, 1, 1, "logs/refs/rewritten" },
 	{ 0, 1, 1, "logs/refs/worktree" },
Signature · 1 line
2.23.0.1084.gae250eaa40
#22 lore raw

cover path.c: minor common_list fixes

In reply to SZEDER Gábor
Quoted 2 lines · SZEDER Gábor

I didn't look yesterday at all, but now I did, and, unfortunately, see two more bugs

The second patch is kind of a bugfix, though luckily the bug doesn't actually manifest in undesired behavior.
The first is a minor cleanup, so future readers won't have a 'Huh?' moment like I just did.

SZEDER Gábor (2):
path.c: fix field name in 'struct common_dir' path.c: mark 'logs/HEAD' in 'common_list' as file

 path.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Signature · 1 line
2.23.0.1084.gae250eaa40
#30 lore raw

Re: [PATCH v2 2/2] git_path(): handle `.lock` files correctly

In reply to Junio C Hamano
Quoted 8 lines · Junio C Hamano
Quoted 8 lines
path.c +0 -0
index e3da1f3c4e..ff85692b45 100644
@@ -268,7 +268,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 	int result;
 	struct trie *child;
 
Quoted 10 lines

path.c static int trie_find(struct trie *root, const char *key, match_fn fn, · line 271

Quoted 2 lines
path.c +1 -1
-	if (!*key) {
+	if (!*key || !strcmp(key, ".lock")) {

We only do strcmp for the tail part at the end of the path, so this should probably OK from performance point of view but semantically it is not very satisfying to see a special case for a single .suffix this deep in the callchain. I wonder if it is nicer to have the higher level callers notice ".lock" or whatever other suffixes they care about and ask the lower layer for a key with the suffix stripped?

I was wondering the same. Despite all of its functions being file static, this is a fairly general trie implementation for paths, which could potentially be useful in other parts of Git as well. Special casing any suffix within its implementation makes it less (re)usable in other cases.

#8 lore raw

cover path.c: a couple of common dir/trie fixes

In reply to SZEDER Gábor
Quoted 1 line · SZEDER Gábor
Quoted 1 line

unfortunately, see two more bugs,

And there are documentation bugs as well, both user-visible (i.e. in a man page) and in in-code comment.

Quoted 21 lines
Quoted 6 lines

and one of them is a "proper" bug leading to bogus output:

  $ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/worktrees/WT/logs/refs/

This one-liner below fixes it, but I haven't yet made up my mind about whether this is the right fix or whether there could be any fallout (at least the test suite doesn't show any).

  $ ./git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/logs/refs/
path.c +0 -0
index 04b69b9feb..9019169418 100644

path.c static int check_common(const char *unmatched, void *value, void *baton) · line 341

Quoted 8 lines
path.c +1 -1
@@ -335,7 +335,7 @@ static int check_common(const char *unmatched, void *value, void *baton)
      struct common_dir *dir = value;
 
      if (!dir)
-             return 0;
+             return -1;
 
      if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
              return !dir->exclude;

Now I made up mind: this isn't the right fix :) The proper fix is in the last patch of this series.

Cc-ing David Turner, the trie's author; if I misunderstood anything, then hopefully he can spot and clarify it.

SZEDER Gábor (5):
Documentation: mention more worktree-specific exceptions path.c: clarify trie_find()'s in-code comment
path.c: mark 'logs/HEAD' in 'common_list' as file path.c: clarify two field names in 'struct common_dir' path.c: don't call the match function without value in trie_find()

 Documentation/gitrepository-layout.txt |  10 +-
 path.c                                 | 122 ++++++++++++++-----------
 t/t0060-path-utils.sh                  |   2 +
 3 files changed, 74 insertions(+), 60 deletions(-)
Signature · 1 line
2.24.0.rc0.472.ga6f06c86b4
#9 lore raw

PATCH 1/5 Documentation: mention more worktree-specific exceptions

If a directory in $GIT_DIR is overridden when $GIT_COMMON_DIR is set, then usually all paths within that directory are overridden as well. There are a couple of exceptions, though, and two of them, namely 'refs/rewritten' and 'logs/HEAD' are not mentioned in 'gitrepository-layout'. Document them as well.

---
 Documentation/gitrepository-layout.txt | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
Documentation/gitrepository-layout.txt +5 -5
index d6388f10bb..1a2ef4c150 100644
@@ -96,9 +96,9 @@ refs::
 	directory.  The 'git prune' command knows to preserve
 	objects reachable from refs found in this directory and
 	its subdirectories.
-	This directory is ignored (except refs/bisect and
-	refs/worktree) if $GIT_COMMON_DIR is set and
-	"$GIT_COMMON_DIR/refs" will be used instead.
+	This directory is ignored (except refs/bisect,
+	refs/rewritten and refs/worktree) if $GIT_COMMON_DIR is
+	set and "$GIT_COMMON_DIR/refs" will be used instead.
 
 refs/heads/`name`::
 	records tip-of-the-tree commit objects of branch `name`
@@ -240,8 +240,8 @@ remotes::
 logs::
 	Records of changes made to refs are stored in this directory.
 	See linkgit:git-update-ref[1] for more information. This
-	directory is ignored if $GIT_COMMON_DIR is set and
-	"$GIT_COMMON_DIR/logs" will be used instead.
+	directory is ignored (except logs/HEAD) if $GIT_COMMON_DIR is
+	set and "$GIT_COMMON_DIR/logs" will be used instead.
 
 logs/refs/heads/`name`::
 	Records all changes made to the branch tip named `name`.
Signature · 1 line
2.24.0.rc0.472.ga6f06c86b4
#11 lore raw

PATCH 3/5 path.c: mark 'logs/HEAD' in 'common_list' as file

In reply to SZEDER Gábor

'logs/HEAD', i.e. HEAD's reflog, is a file, but its entry in 'common_list' has the 'is_dir' bit set.

Unset that bit to make it consistent with what 'logs/HEAD' is supposed to be.

This doesn't make a difference in behavior: check_common() is the only function that looks at the 'is_dir' bit, and that function either returns 0, or '!exclude', which for 'logs/HEAD' results in 0 as well.

---
 path.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
path.c +1 -1
index 4ac9a101f5..3fac5f5726 100644
@@ -113,7 +113,7 @@ static struct common_dir common_list[] = {
 	{ 0, 1, 0, "info" },
 	{ 0, 0, 1, "info/sparse-checkout" },
 	{ 1, 1, 0, "logs" },
-	{ 1, 1, 1, "logs/HEAD" },
+	{ 1, 0, 1, "logs/HEAD" },
 	{ 0, 1, 1, "logs/refs/bisect" },
 	{ 0, 1, 1, "logs/refs/rewritten" },
 	{ 0, 1, 1, "logs/refs/worktree" },
Signature · 1 line
2.24.0.rc0.472.ga6f06c86b4
#10 lore raw

PATCH 2/5 path.c: clarify trie_find()'s in-code comment

In reply to SZEDER Gábor

A fairly long comment describes trie_find()'s behavior and shows examples, but it's slightly incomplete/inaccurate. Update this comment to specify how trie_find() handles a negative return value from the given match function.

Furthermore, update the list of examples to include not only two but three levels of path components. This makes the examples slightly more complicated, but it can illustrate the behavior in more corner cases.

Finally, basically everything refers to the data stored for a key as "value", with two confusing exceptions:

- The type definition of the match function calls its corresponding parameter 'data'.
Rename that parameter to 'value'. (check_common(), the only function of this type already calls it 'value').

- The table of examples above trie_find() has a "val from node" column, which has nothing to do with the value stored in the trie:
it's a "prefix of the key for which the trie contains a value" that led to that node.
Rename that column header to "prefix to node".

Note that neither the original nor the updated description and examples correspond 100% to the current implementation, because the implementation is a bit buggy, but the comment describes the desired behavior. The bug will be fixed in the last patch of this series.

---
 path.c | 45 ++++++++++++++++++++++++++++-----------------
 1 file changed, 28 insertions(+), 17 deletions(-)
path.c +28 -17
index e3da1f3c4e..4ac9a101f5 100644
@@ -236,30 +236,41 @@ static void *add_to_trie(struct trie *root, const char *key, void *value)
 	return old;
 }
 
-typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
+typedef int (*match_fn)(const char *unmatched, void *value, void *baton);
 
 /*
  * Search a trie for some key.  Find the longest /-or-\0-terminated
- * prefix of the key for which the trie contains a value.  Call fn
- * with the unmatched portion of the key and the found value, and
- * return its return value.  If there is no such prefix, return -1.
+ * prefix of the key for which the trie contains a value.  If there is
+ * no such prefix, return -1.  Otherwise call fn with the unmatched
+ * portion of the key and the found value.  If fn returns 0 or
+ * positive, then return its return value.  If fn returns negative,
+ * then call fn with the next-longest /-terminated prefix of the key
+ * (i.e. a parent directory) for which the trie contains a value, and
+ * handle its return value the same way.  If there is no shorter
+ * /-terminated prefix with a value left, then return the negative
+ * return value of the most recent fn invocation.
  *
  * The key is partially normalized: consecutive slashes are skipped.
  *
- * For example, consider the trie containing only [refs,
- * refs/worktree] (both with values).
- *
- * | key             | unmatched  | val from node | return value |
- * |-----------------|------------|---------------|--------------|
- * | a               | not called | n/a           | -1           |
- * | refs            | \0         | refs          | as per fn    |
- * | refs/           | /          | refs          | as per fn    |
- * | refs/w          | /w         | refs          | as per fn    |
- * | refs/worktree   | \0         | refs/worktree | as per fn    |
- * | refs/worktree/  | /          | refs/worktree | as per fn    |
- * | refs/worktree/a | /a         | refs/worktree | as per fn    |
- * |-----------------|------------|---------------|--------------|
+ * For example, consider the trie containing only [logs,
+ * logs/refs/bisect], both with values, but not logs/refs.
  *
+ * | key                | unmatched      | prefix to node   | return value |
+ * |--------------------|----------------|------------------|--------------|
+ * | a                  | not called     | n/a              | -1           |
+ * | logstore           | not called     | n/a              | -1           |
+ * | logs               | \0             | logs             | as per fn    |
+ * | logs/              | /              | logs             | as per fn    |
+ * | logs/refs          | /refs          | logs             | as per fn    |
+ * | logs/refs/         | /refs/         | logs             | as per fn    |
+ * | logs/refs/b        | /refs/b        | logs             | as per fn    |
+ * | logs/refs/bisected | /refs/bisected | logs             | as per fn    |
+ * | logs/refs/bisect   | \0             | logs/refs/bisect | as per fn    |
+ * | logs/refs/bisect/  | /              | logs/refs/bisect | as per fn    |
+ * | logs/refs/bisect/a | /a             | logs/refs/bisect | as per fn    |
+ * | (If fn in the previous line returns -1, then fn is called once more:) |
+ * | logs/refs/bisect/a | /refs/bisect/a | logs             | as per fn    |
+ * |--------------------|----------------|------------------|--------------|
  */
 static int trie_find(struct trie *root, const char *key, match_fn fn,
 		     void *baton)
Signature · 1 line
2.24.0.rc0.472.ga6f06c86b4
#12 lore raw

PATCH 4/5 path.c: clarify two field names in 'struct common_dir'

In reply to SZEDER Gábor

An array of 'struct common_dir' instances is used to specify whether various paths in $GIT_DIR are specific to a worktree, or are common, i.e. belong to main worktree. The names of two fields in this struct are somewhat confusing or ambigious:

- The path is recorded in the struct's 'dirname' field, even though several entries are regular files e.g. 'gc.pid', 'packed-refs', etc.

Rename this field to 'path' to reduce confusion.

- The field 'exclude' tells whether the path is excluded... from where? Excluded from the common dir or from the worktree? It means the former, but it's ambigious.

Rename this field to 'is_common' to make it unambigious what it means. This, however, means the exact opposite of what 'exclude' meant, so we have to negate the field's value in all entries as well.

The diff is best viewed with '--color-words'.

---
 path.c | 66 +++++++++++++++++++++++++++++-----------------------------
 1 file changed, 33 insertions(+), 33 deletions(-)
path.c +33 -33
index 3fac5f5726..cf57bd52dd 100644
@@ -101,36 +101,36 @@ struct common_dir {
 	/* Not considered garbage for report_linked_checkout_garbage */
 	unsigned ignore_garbage:1;
 	unsigned is_dir:1;
-	/* Not common even though its parent is */
-	unsigned exclude:1;
-	const char *dirname;
+	/* Belongs to the common dir, though it may contain paths that don't */
+	unsigned is_common:1;
+	const char *path;
 };
 
 static struct common_dir common_list[] = {
-	{ 0, 1, 0, "branches" },
-	{ 0, 1, 0, "common" },
-	{ 0, 1, 0, "hooks" },
-	{ 0, 1, 0, "info" },
-	{ 0, 0, 1, "info/sparse-checkout" },
-	{ 1, 1, 0, "logs" },
-	{ 1, 0, 1, "logs/HEAD" },
-	{ 0, 1, 1, "logs/refs/bisect" },
-	{ 0, 1, 1, "logs/refs/rewritten" },
-	{ 0, 1, 1, "logs/refs/worktree" },
-	{ 0, 1, 0, "lost-found" },
-	{ 0, 1, 0, "objects" },
-	{ 0, 1, 0, "refs" },
-	{ 0, 1, 1, "refs/bisect" },
-	{ 0, 1, 1, "refs/rewritten" },
-	{ 0, 1, 1, "refs/worktree" },
-	{ 0, 1, 0, "remotes" },
-	{ 0, 1, 0, "worktrees" },
-	{ 0, 1, 0, "rr-cache" },
-	{ 0, 1, 0, "svn" },
-	{ 0, 0, 0, "config" },
-	{ 1, 0, 0, "gc.pid" },
-	{ 0, 0, 0, "packed-refs" },
-	{ 0, 0, 0, "shallow" },
+	{ 0, 1, 1, "branches" },
+	{ 0, 1, 1, "common" },
+	{ 0, 1, 1, "hooks" },
+	{ 0, 1, 1, "info" },
+	{ 0, 0, 0, "info/sparse-checkout" },
+	{ 1, 1, 1, "logs" },
+	{ 1, 0, 0, "logs/HEAD" },
+	{ 0, 1, 0, "logs/refs/bisect" },
+	{ 0, 1, 0, "logs/refs/rewritten" },
+	{ 0, 1, 0, "logs/refs/worktree" },
+	{ 0, 1, 1, "lost-found" },
+	{ 0, 1, 1, "objects" },
+	{ 0, 1, 1, "refs" },
+	{ 0, 1, 0, "refs/bisect" },
+	{ 0, 1, 0, "refs/rewritten" },
+	{ 0, 1, 0, "refs/worktree" },
+	{ 0, 1, 1, "remotes" },
+	{ 0, 1, 1, "worktrees" },
+	{ 0, 1, 1, "rr-cache" },
+	{ 0, 1, 1, "svn" },
+	{ 0, 0, 1, "config" },
+	{ 1, 0, 1, "gc.pid" },
+	{ 0, 0, 1, "packed-refs" },
+	{ 0, 0, 1, "shallow" },
 	{ 0, 0, 0, NULL }
 };
 
@@ -331,8 +331,8 @@ static void init_common_trie(void)
 	if (common_trie_done_setup)
 		return;
 
-	for (p = common_list; p->dirname; p++)
-		add_to_trie(&common_trie, p->dirname, p);
+	for (p = common_list; p->path; p++)
+		add_to_trie(&common_trie, p->path, p);
 
 	common_trie_done_setup = 1;
 }
@@ -349,10 +349,10 @@ static int check_common(const char *unmatched, void *value, void *baton)
 		return 0;
 
 	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
-		return !dir->exclude;
+		return dir->is_common;
 
 	if (!dir->is_dir && unmatched[0] == 0)
-		return !dir->exclude;
+		return dir->is_common;
 
 	return 0;
 }
@@ -376,8 +376,8 @@ void report_linked_checkout_garbage(void)
 		return;
 	strbuf_addf(&sb, "%s/", get_git_dir());
 	len = sb.len;
-	for (p = common_list; p->dirname; p++) {
-		const char *path = p->dirname;
+	for (p = common_list; p->path; p++) {
+		const char *path = p->path;
 		if (p->ignore_garbage)
 			continue;
 		strbuf_setlen(&sb, len);
Signature · 1 line
2.24.0.rc0.472.ga6f06c86b4
#13 lore raw

PATCH 5/5 path.c: don't call the match function without value in trie_find()

In reply to SZEDER Gábor

'logs/refs' is not a working tree-specific path, but since commit b9317d55a3 (Make sure refs/rewritten/ is per-worktree, 2019-03-07) 'git rev-parse --git-path' has been returning a bogus path if a trailing '/' is present:

  $ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/worktrees/WT/logs/refs/

We use a trie data structure to efficiently decide whether a path belongs to the common dir or is working tree-specific. As it happens b9317d55a3 triggered a bug that is as old as the trie implementation itself, added in 4e09cf2acf (path: optimize common dir checking, 2015-08-31).

- According to the comment describing trie_find(), it should only call the given match function 'fn' for a "/-or-\0-terminated prefix of the key for which the trie contains a value". This is not true: there are three places where trie_find() calls the match function, but one of them is missing the check for value's existence.

- b9317d55a3 added two new keys to the trie: 'logs/refs/rewritten' and 'logs/refs/worktree', next to the already existing 'logs/refs/bisect'. This resulted in a trie node with the path 'logs/refs', which didn't exist before, and which doesn't have a value attached. A query for 'logs/refs/' finds this node and then hits that one callsite of the match function which doesn't check for the value's existence, and thus invokes the match function with NULL as value.

- When the match function check_common() is invoked with a NULL value, it returns 0, which indicates that the queried path doesn't belong to the common directory, ultimately resulting the bogus path shown above.

Add the missing condition to trie_find() so it will never invoke the match function with a non-existing value. check_common() will then no longer have to check that it got a non-NULL value, so remove that condition.

I believe that there are no other paths that could cause similar bogus output. AFAICT the only other key resulting in the match function being called with a NULL value is 'co' (because of the keys 'common' and 'config'). However, as they are not in a directory that belongs to the common directory the resulting working tree-specific path is expected.

---
 path.c                | 11 ++++++-----
 t/t0060-path-utils.sh |  2 ++
 2 files changed, 8 insertions(+), 5 deletions(-)
path.c +6 -5
index cf57bd52dd..e21b00c4d4 100644
@@ -299,9 +299,13 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key) {
 		/* End of key */
-		return fn(key, root->value, baton);
+		if (root->value)
+			return fn(key, root->value, baton);
+		else
+			return -1;
+	}
 
 	/* Partial path normalization: skip consecutive slashes */
 	while (key[0] == '/' && key[1] == '/')
@@ -345,9 +349,6 @@ static int check_common(const char *unmatched, void *value, void *baton)
 {
 	struct common_dir *dir = value;
 
-	if (!dir)
-		return 0;
-
 	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
 		return dir->is_common;
 
t/t0060-path-utils.sh +2 -0
index c7b53e494b..501e1a288d 100755
@@ -288,6 +288,8 @@ test_git_path GIT_COMMON_DIR=bar index                    .git/index
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs                bar/logs/refs
+test_git_path GIT_COMMON_DIR=bar logs/refs/               bar/logs/refs/
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo      bar/logs/refs/bisectfoo
Signature · 1 line
2.24.0.rc0.472.ga6f06c86b4
#14 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

Quoted 5 lines · SZEDER Gábor

Add the missing condition to trie_find() so it will never invoke the match function with a non-existing value. check_common() will then no
longer have to check that it got a non-NULL value, so remove that condition.

...

path.c static int check_common(const char *unmatched, · line 350

Quoted 10 lines
path.c +0 -2
 	/* Partial path normalization: skip consecutive slashes */
 	while (key[0] == '/' && key[1] == '/')
@@ -345,9 +349,6 @@ static int check_common(const char *unmatched,
 void *value, void *baton)
 {
 	struct common_dir *dir = value;
 
-	if (!dir)
-		return 0;

Do we want to assert(dir) here?

Overall, LGTM. Thanks for the clean-up.

#25 lore raw

Re: [PATCH 0/2] path.c: minor common_list fixes

In reply to SZEDER Gábor

Hi Gábor,

Quoted 4 lines · SZEDER Gábor
Quoted 2 lines · SZEDER Gábor

I didn't look yesterday at all, but now I did, and, unfortunately, see two more bugs

Quoted 8 lines

The second patch is kind of a bugfix, though luckily the bug doesn't actually manifest in undesired behavior.
The first is a minor cleanup, so future readers won't have a 'Huh?' moment like I just did.

SZEDER Gábor (2):
path.c: fix field name in 'struct common_dir' path.c: mark 'logs/HEAD' in 'common_list' as file

Those patches look very reasonable to me.

Thanks,
Dscho

Quoted 8 lines
 path.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

--
2.23.0.1084.gae250eaa40

#31 lore raw

Re: [PATCH v2 2/2] git_path(): handle `.lock` files correctly

In reply to Junio C Hamano

Hi Junio,

Quoted 10 lines · Junio C Hamano

"Johannes Schindelin via GitGitGadget" <[email protected]>

Quoted 7 lines · writes:

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is specific to the worktree). However, the wrong path is returned for logs/HEAD.lock.

Quoted 5 lines
Quoted 3 lines

This does not matter as long as the Git executable is doing the asking, as the path for that index.lock file is constructed from git_path("index") by appending the .lock suffix.

Is this still git_path("index") or is it now HEAD?

Darn. It is "logs/HEAD", of course.

Quoted 4 lines
Quoted 2 lines

Side note: Git GUI _does_ ask for index.lock, but that is already resolved correctly.

Is that s/but/and/?

It sounds better with an "and", you're right.

Quoted 8 lines
Quoted 8 lines
path.c +0 -0
index e3da1f3c4e..ff85692b45 100644
@@ -268,7 +268,7 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 	int result;
 	struct trie *child;
 
Quoted 10 lines

path.c static int trie_find(struct trie *root, const char *key, match_fn fn, · line 271

Quoted 2 lines
path.c +1 -1
-	if (!*key) {
+	if (!*key || !strcmp(key, ".lock")) {

We only do strcmp for the tail part at the end of the path, so this should probably OK from performance point of view but semantically it is not very satisfying to see a special case for a single .suffix this deep in the callchain. I wonder if it is nicer to have the higher level callers notice ".lock" or whatever other suffixes they care about and ask the lower layer for a key with the suffix stripped?

Hmm. The parameter is provided as a const char *, so I cannot just edit it. My first attempt at fixing this resulted in this commit:

-- snip --
path: switch trie_find() to a counted string

Before this patch, the trie_find() function would be fed a regular, NUL-terminated string.

However, in the next commit, we want to strip any .lock suffix from the argument, and the argument is provided as a const char * (i.e. we do not want to modify it).

Let's use a string + length pair of parameters instead of a single NUL-terminated string to open the door for this kind of manipulation.

path.c +24 -19
index e3da1f3c4e2..b18d552c890 100644
@@ -236,7 +236,8 @@ static void *add_to_trie(struct trie *root, const char *key, void *value)
 	return old;
 }
 
-typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
+typedef int (*match_fn)(const char *unmatched, size_t unmatched_len,
+			void *data, void *baton);
 
 /*
  * Search a trie for some key.  Find the longest /-or-\0-terminated
@@ -261,22 +262,22 @@ typedef int (*match_fn)(const char *unmatched, void *data, void *baton);
  * |-----------------|------------|---------------|--------------|
  *
  */
-static int trie_find(struct trie *root, const char *key, match_fn fn,
-		     void *baton)
+static int trie_find(struct trie *root, const char *key, size_t key_len,
+		     match_fn fn, void *baton)
 {
 	int i;
 	int result;
 	struct trie *child;
 
-	if (!*key) {
+	if (!key_len) {
 		/* we have reached the end of the key */
 		if (root->value && !root->len)
-			return fn(key, root->value, baton);
+			return fn(key, key_len, root->value, baton);
 		else
 			return -1;
 	}
 
-	for (i = 0; i < root->len; i++) {
+	for (i = 0; i < key_len && i < root->len; i++) {
 		/* Partial path normalization: skip consecutive slashes. */
 		if (key[i] == '/' && key[i+1] == '/') {
 			key++;
@@ -288,24 +289,26 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	key_len -= i;
+
+	if (!key_len)
 		/* End of key */
-		return fn(key, root->value, baton);
+		return fn(key, key_len, root->value, baton);
 
 	/* Partial path normalization: skip consecutive slashes */
-	while (key[0] == '/' && key[1] == '/')
-		key++;
+	while (key_len > 0 && key[0] == '/' && key[1] == '/')
+		key++, key_len--;
 
-	child = root->children[(unsigned char)*key];
+	child = root->children[!key_len ? '0' : (unsigned char)*key];
 	if (child)
-		result = trie_find(child, key + 1, fn, baton);
+		result = trie_find(child, key + 1, key_len - 1, fn, baton);
 	else
 		result = -1;
 
-	if (result >= 0 || (*key != '/' && *key != 0))
+	if (result >= 0 || (key_len && *key != '/'))
 		return result;
 	if (root->value)
-		return fn(key, root->value, baton);
+		return fn(key, key_len, root->value, baton);
 	else
 		return -1;
 }
@@ -330,17 +333,18 @@ static void init_common_trie(void)
  * Helper function for update_common_dir: returns 1 if the dir
  * prefix is common.
  */
-static int check_common(const char *unmatched, void *value, void *baton)
+static int check_common(const char *unmatched, size_t unmatched_len,
+			void *value, void *baton)
 {
 	struct common_dir *dir = value;
 
 	if (!dir)
 		return 0;
 
-	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
+	if (dir->is_dir && (!unmatched_len || unmatched[0] == '/'))
 		return !dir->exclude;
 
-	if (!dir->is_dir && unmatched[0] == 0)
+	if (!dir->is_dir && !unmatched_len)
 		return !dir->exclude;
 
 	return 0;
@@ -350,8 +354,10 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len,
 			      const char *common_dir)
 {
 	char *base = buf->buf + git_dir_len;
+	size_t len = strlen(base);
+
 	init_common_trie();
-	if (trie_find(&common_trie, base, check_common, NULL) > 0)
+	if (trie_find(&common_trie, base, len, check_common, NULL) > 0)
 		replace_dir(buf, git_dir_len, common_dir);
 }
-- snap --

However, I think this is _really_ ugly and intrusive. The opposite of what my goals were.

So I think I'll just bite the bullet and use a temporary copy if the argument ends in .lock.

Ciao,
Dscho

#15 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

In reply to SZEDER Gábor
Quoted 22 lines · SZEDER Gábor

'logs/refs' is not a working tree-specific path, but since commit b9317d55a3 (Make sure refs/rewritten/ is per-worktree, 2019-03-07) 'git rev-parse --git-path' has been returning a bogus path if a trailing '/' is present:

  $ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/worktrees/WT/logs/refs/

We use a trie data structure to efficiently decide whether a path belongs to the common dir or is working tree-specific. As it happens b9317d55a3 triggered a bug that is as old as the trie implementation itself, added in 4e09cf2acf (path: optimize common dir checking, 2015-08-31).

- According to the comment describing trie_find(), it should only call the given match function 'fn' for a "/-or-\0-terminated prefix of the key for which the trie contains a value". This is not true: there are three places where trie_find() calls the match function, but one of them is missing the check for value's existence.

Quoted 4 lines

- b9317d55a3 added two new keys to the trie: 'logs/refs/rewritten' and 'logs/refs/worktree', next to the already existing 'logs/refs/bisect'. This resulted in a trie node with the path 'logs/refs', which didn't exist before, and which doesn't have a

Oops, I missed the trailing slash, that must be 'logs/refs/'!

Quoted 4 lines

value attached. A query for 'logs/refs/' finds this node and then hits that one callsite of the match function which doesn't check for the value's existence, and thus invokes the match function with NULL as value.

#34 lore raw

PATCH v3 1/2 t1400: wrap setup code in test case

In reply to Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Without this, you cannot use --run=<...> to skip that part, and a run with --run=0 (which is a common way to determine the test case number corresponding to a given test case title).

---
 t/t1400-update-ref.sh | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
t/t1400-update-ref.sh +10 -8
index 1fbd940408..69a7f27311 100755
@@ -344,14 +344,16 @@ test_expect_success "verifying $m's log (logged by config)" '
 	test_cmp expect .git/logs/$m
 '
 
-git update-ref $m $D
-cat >.git/logs/$m <<EOF
-$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
-$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
-$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
-$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
-$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
-EOF
+test_expect_success 'set up for querying the reflog' '
+	git update-ref $m $D &&
+	cat >.git/logs/$m <<-EOF
+	$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+	$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
+	$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+	$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+	$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+	EOF
+'
 
 ed="Thu, 26 May 2005 18:32:00 -0500"
 gd="Thu, 26 May 2005 18:33:00 -0500"
Signature · 1 line
gitgitgadget
#33 lore raw

v3 cover Handle git_path() with lock files correctly in worktrees

In reply to Johannes Schindelin via GitGitGadget

I stumbled over this during my recent work in Git GUI
[https://github.com/gitgitgadget/git/pull/361] that was originally really only intended to use the correct hooks directory.

It turns out that my fears that index.lock was mishandled were unfounded, hence this patch series has a lot lower priority for me than "OMG we must push this into v2.24.0!".

Technically, the first patch is not needed (because I decided against adding a test to t1400 in the end, in favor of t1500), but it shouldn't hurt, either.

Changes since v2:

* Adjusted the commit message to really not talk about index.lock.
* Instead of modifying the code inside trie_find() to special-case .lock, I now copy the string without the suffix .lock (if any) before handing it off to trie_find().

Changes since v1:

* Clarified the commit message to state that index.lock is fine, it is logs/HEAD.lock that isn't.

Johannes Schindelin (2):
t1400: wrap setup code in test case
git_path(): handle .lock files correctly

 path.c                |  8 +++++++-
 t/t1400-update-ref.sh | 18 ++++++++++--------
 t/t1500-rev-parse.sh  | 15 +++++++++++++++
 3 files changed, 32 insertions(+), 9 deletions(-)

base-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-401%2Fdscho%2Flock-files-in-worktrees-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-401/dscho/lock-files-in-worktrees-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/401

Range-diff vs v2:

 1:  cf97c5182e = 1:  cf97c5182e t1400: wrap setup code in test case
 2:  93dba5a3a3 ! 2:  8b1f4f089a git_path(): handle `.lock` files correctly

@@ -4,12 +4,12 @@

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is
- specific to the worktree). However, the wrong path is returned for
- logs/HEAD.lock.
+ specific to the worktree, and therefore so is its reflog). However, the
+ wrong path is returned for logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking,
- as the path for that index.lock file is constructed from
- git_path("index") by appending the .lock suffix.
+ as the path for that logs/HEAD.lock file is constructed from
+ git_path("logs/HEAD") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a
@@ -19,7 +19,8 @@
let's be safe rather than sorry.

Side note: Git GUI _does_ ask for index.lock, but that is already
- resolved correctly.
+ resolved correctly, due to update_common_dir() preferring to leave
+ unknown paths in the (worktree-specific) git directory.

@@ -27,23 +28,23 @@
--- a/path.c
+++ b/path.c
@@
- int result;
- struct trie *child;
-
-- if (!*key) {
-+ if (!*key || !strcmp(key, ".lock")) {
- /* we have reached the end of the key */
- if (root->value && !root->len)
- return fn(key, root->value, baton);
-@@
-
- /* Matched the entire compressed section */
- key += i;
-- if (!*key)
-+ if (!*key || !strcmp(key, ".lock"))
- /* End of key */
- return fn(key, root->value, baton);
+ static void update_common_dir(struct strbuf *buf, int git_dir_len,
+ const char *common_dir)
+ {
+- char *base = buf->buf + git_dir_len;
++ char *base = buf->buf + git_dir_len, *base2 = NULL;
++
++ if (ends_with(base, ".lock"))
++ base = base2 = xstrndup(base, strlen(base) - 5);
++
+ init_common_trie();
+ if (trie_find(&common_trie, base, check_common, NULL) > 0)
+ replace_dir(buf, git_dir_len, common_dir);
++
++ free(base2);
+ }

+ void report_linked_checkout_garbage(void)

diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh --- a/t/t1500-rev-parse.sh

Signature · 1 line
gitgitgadget
#35 lore raw

PATCH v3 2/2 git_path(): handle `.lock` files correctly

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is specific to the worktree, and therefore so is its reflog). However, the wrong path is returned for logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that logs/HEAD.lock file is constructed from git_path("logs/HEAD") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). While it does not seem as if Git GUI in particular is asking for logs/HEAD.lock, let's be safe rather than sorry.

Side note: Git GUI _does_ ask for index.lock, but that is already resolved correctly, due to update_common_dir() preferring to leave unknown paths in the (worktree-specific) git directory.

---
 path.c               |  8 +++++++-
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)
path.c +7 -1
index e3da1f3c4e..5ff64e7a8a 100644
@@ -349,10 +349,16 @@ static int check_common(const char *unmatched, void *value, void *baton)
 static void update_common_dir(struct strbuf *buf, int git_dir_len,
 			      const char *common_dir)
 {
-	char *base = buf->buf + git_dir_len;
+	char *base = buf->buf + git_dir_len, *base2 = NULL;
+
+	if (ends_with(base, ".lock"))
+		base = base2 = xstrndup(base, strlen(base) - 5);
+
 	init_common_trie();
 	if (trie_find(&common_trie, base, check_common, NULL) > 0)
 		replace_dir(buf, git_dir_len, common_dir);
+
+	free(base2);
 }
 
 void report_linked_checkout_garbage(void)
t/t1500-rev-parse.sh +15 -0
index 01abee533d..d318a1eeef 100755
@@ -116,6 +116,21 @@ test_expect_success 'git-path inside sub-dir' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git-path in worktree' '
+	test_tick &&
+	git commit --allow-empty -m empty &&
+	git worktree add --detach wt &&
+	test_write_lines >expect \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
+		"$(pwd)/.git/worktrees/wt/index" \
+		"$(pwd)/.git/worktrees/wt/index.lock" &&
+	git -C wt rev-parse >actual \
+		--git-path logs/HEAD --git-path logs/HEAD.lock \
+		--git-path index --git-path index.lock &&
+	test_cmp expect actual
+'
+
 test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
 	test_commit test_commit &&
 	echo true >expect &&
Signature · 1 line
gitgitgadget
#36 lore raw

Re: [PATCH v3 2/2] git_path(): handle `.lock` files correctly

Quoted 33 lines · Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is specific to the worktree, and therefore so is its reflog). However, the wrong path is returned for logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that logs/HEAD.lock file is constructed from git_path("logs/HEAD") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). While it does not seem as if Git GUI in particular is asking for logs/HEAD.lock, let's be safe rather than sorry.

Side note: Git GUI _does_ ask for index.lock, but that is already resolved correctly, due to update_common_dir() preferring to leave unknown paths in the (worktree-specific) git directory.

---
 path.c               |  8 +++++++-
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)
path.c +0 -0
index e3da1f3c4e..5ff64e7a8a 100644

path.c static int check_common(const char *unmatched, void *value, void *baton) · line 355

Quoted 8 lines
path.c +4 -1
@@ -349,10 +349,16 @@ static int check_common(const char *unmatched, void *value, void *baton)
 static void update_common_dir(struct strbuf *buf, int git_dir_len,
 			      const char *common_dir)
 {
-	char *base = buf->buf + git_dir_len;
+	char *base = buf->buf + git_dir_len, *base2 = NULL;
+
+	if (ends_with(base, ".lock"))
+		base = base2 = xstrndup(base, strlen(base) - 5);

Hm, this adds the magic number 5 and calls strlen(base) twice, because ends_with() calls strip_suffix(), which calls strlen(). Calling strip_suffix() directly would allow us to avoid the magic number and the second strlen():

  size_t len;
  if (strip_suffix(base, ".lock", &len))
          base = base2 = xstrndup(base, len);
Quoted 20 lines
path.c +2 -0
 	init_common_trie();
 	if (trie_find(&common_trie, base, check_common, NULL) > 0)
 		replace_dir(buf, git_dir_len, common_dir);
+
+	free(base2);
 }
 
 void report_linked_checkout_garbage(void)
t/t1500-rev-parse.sh +4 -0
index 01abee533d..d318a1eeef 100755
@@ -116,6 +116,21 @@ test_expect_success 'git-path inside sub-dir' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git-path in worktree' '
+	test_tick &&
+	git commit --allow-empty -m empty &&
+	git worktree add --detach wt &&

t/t1500-rev-parse.sh test_expect_success 'git-path inside sub-dir' ' · line 132

Quoted 10 lines
t/t1500-rev-parse.sh +10 -0
+	test_write_lines >expect \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
+		"$(pwd)/.git/worktrees/wt/index" \
+		"$(pwd)/.git/worktrees/wt/index.lock" &&
+	git -C wt rev-parse >actual \
+		--git-path logs/HEAD --git-path logs/HEAD.lock \
+		--git-path index --git-path index.lock &&
+	test_cmp expect actual
+'

I think this test better fits into 't0060-path-utils.sh': it already checks 'logs/HEAD' and 'index' in a different working tree (well, with GIT_COMMON_DIR set, but that's the same), and it has a helper function to make each of the two new .lock tests a one-liner.

Quoted 5 lines
t/t1500-rev-parse.sh +0 -0
 test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
 	test_commit test_commit &&
 	echo true >expect &&
Signature · 1 line
gitgitgadget
#32 lore raw

Re: [PATCH v2 2/2] git_path(): handle `.lock` files correctly

In reply to Johannes Schindelin
Quoted 5 lines · Johannes Schindelin

However, I think this is _really_ ugly and intrusive. The opposite of what my goals were.

So I think I'll just bite the bullet and use a temporary copy if the argument ends in .lock.

Sounds like a quite sensible design decision to me.

#37 lore raw

Re: [PATCH v3 2/2] git_path(): handle `.lock` files correctly

In reply to SZEDER Gábor
Quoted 10 lines · SZEDER Gábor

path.c static int check_common(const char *unmatched, void *value, void *baton) · line 355

Quoted 4 lines
path.c +4 -0
+	char *base = buf->buf + git_dir_len, *base2 = NULL;
+
+	if (ends_with(base, ".lock"))
+		base = base2 = xstrndup(base, strlen(base) - 5);

Hm, this adds the magic number 5 and calls strlen(base) twice, because ends_with() calls strip_suffix(), which calls strlen(). Calling strip_suffix() directly would allow us to avoid the magic number and the second strlen():

Quoted 3 lines
  size_t len;
  if (strip_suffix(base, ".lock", &len))
          base = base2 = xstrndup(base, len);

Makes sense, and is easy to squash in.

#16 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

In reply to SZEDER Gábor
Quoted 11 lines · SZEDER Gábor
Quoted 4 lines

- b9317d55a3 added two new keys to the trie: 'logs/refs/rewritten' and 'logs/refs/worktree', next to the already existing 'logs/refs/bisect'. This resulted in a trie node with the path 'logs/refs', which didn't exist before, and which doesn't have a

Oops, I missed the trailing slash, that must be 'logs/refs/'!

Quoted 4 lines

value attached. A query for 'logs/refs/' finds this node and then hits that one callsite of the match function which doesn't check for the value's existence, and thus invokes the match function with NULL as value.

Given that the trie is maintained by hand in common_list[], I wonder if we can mechanically catch errors like the one b9317d55a3 added, by perhaps having a self-test function that a t/helper/ program calls to perform consistency check after the "git" gets built.

Thanks.

#17 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

Quoted 14 lines · Junio C Hamano
Quoted 11 lines · SZEDER Gábor
Quoted 4 lines

- b9317d55a3 added two new keys to the trie: 'logs/refs/rewritten' and 'logs/refs/worktree', next to the already existing 'logs/refs/bisect'. This resulted in a trie node with the path 'logs/refs', which didn't exist before, and which doesn't have a

Oops, I missed the trailing slash, that must be 'logs/refs/'!

Quoted 4 lines

value attached. A query for 'logs/refs/' finds this node and then hits that one callsite of the match function which doesn't check for the value's existence, and thus invokes the match function with NULL as value.

Quoted 4 lines

Given that the trie is maintained by hand in common_list[], I wonder if we can mechanically catch errors like the one b9317d55a3 added, by perhaps having a self-test function that a t/helper/ program calls to perform consistency check after the "git" gets built.

I'm not sure what you mean by "consistency check". The resulting trie looked as expected both before and after b9317d55a3, i.e. each trie node had the right contents, value, and children, as far as I could tell. The issue was in the lookup function.

#18 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

Quoted 4 lines · SZEDER Gábor

I'm not sure what you mean by "consistency check". The resulting trie looked as expected both before and after b9317d55a3, i.e. each trie node had the right contents, value, and children, as far as I could tell. The issue was in the lookup function.

I saw the change to the code as a band-aid, which wouldn't have been necessary if we had the missing refs/ entry. Fully populating the leading levels explicitly may give the application more flexibility by yielding results different from hardcoded -1 like the patched code gives.

But perhaps treating a path that matches a missing intermediate level just like a path that did not match anything in the trie is what we want anyway, so I guess the code change was the right thing (as opposed to a band-aid).

Thanks.

#19 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

In reply to SZEDER Gábor

Hi Gábor,

Quoted 41 lines · SZEDER Gábor

'logs/refs' is not a working tree-specific path, but since commit b9317d55a3 (Make sure refs/rewritten/ is per-worktree, 2019-03-07) 'git rev-parse --git-path' has been returning a bogus path if a trailing '/' is present:

  $ git -C WT/ rev-parse --git-path logs/refs --git-path logs/refs/
  /home/szeder/src/git/.git/logs/refs
  /home/szeder/src/git/.git/worktrees/WT/logs/refs/

We use a trie data structure to efficiently decide whether a path belongs to the common dir or is working tree-specific. As it happens b9317d55a3 triggered a bug that is as old as the trie implementation itself, added in 4e09cf2acf (path: optimize common dir checking, 2015-08-31).

- According to the comment describing trie_find(), it should only call the given match function 'fn' for a "/-or-\0-terminated prefix of the key for which the trie contains a value". This is not true: there are three places where trie_find() calls the match function, but one of them is missing the check for value's existence.

- b9317d55a3 added two new keys to the trie: 'logs/refs/rewritten' and 'logs/refs/worktree', next to the already existing 'logs/refs/bisect'. This resulted in a trie node with the path 'logs/refs', which didn't exist before, and which doesn't have a value attached. A query for 'logs/refs/' finds this node and then hits that one callsite of the match function which doesn't check for the value's existence, and thus invokes the match function with NULL as value.

- When the match function check_common() is invoked with a NULL value, it returns 0, which indicates that the queried path doesn't belong to the common directory, ultimately resulting the bogus path shown above.

Add the missing condition to trie_find() so it will never invoke the match function with a non-existing value. check_common() will then no longer have to check that it got a non-NULL value, so remove that condition.

Quoted 8 lines

I believe that there are no other paths that could cause similar bogus output. AFAICT the only other key resulting in the match function being called with a NULL value is 'co' (because of the keys 'common' and 'config'). However, as they are not in a directory that belongs to the common directory the resulting working tree-specific path is expected.

Thank you for this entire patch series. Just one nit:

Quoted 6 lines
path.c +0 -0
index cf57bd52dd..e21b00c4d4 100644
@@ -299,9 +299,13 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 

path.c static int trie_find(struct trie *root, const char *key, match_fn fn, · line 307

Quoted 10 lines
path.c +5 -2
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key) {
 		/* End of key */
-		return fn(key, root->value, baton);
+		if (root->value)
+			return fn(key, root->value, baton);
+		else
+			return -1;

I would have preferred this:

+ if (!root->value)
+ return -1;
+ return fn(key, root->value, baton);

... as it would more accurately reflect my mental model of an "early out".

But as I said, this is just a nit-pick.

Thank you for working on this!
Dscho

Quoted 31 lines
path.c +1 -3
+	}
 
 	/* Partial path normalization: skip consecutive slashes */
 	while (key[0] == '/' && key[1] == '/')
@@ -345,9 +349,6 @@ static int check_common(const char *unmatched, void *value, void *baton)
 {
 	struct common_dir *dir = value;
 
-	if (!dir)
-		return 0;
-
 	if (dir->is_dir && (unmatched[0] == 0 || unmatched[0] == '/'))
 		return dir->is_common;
 
t/t0060-path-utils.sh +2 -1
index c7b53e494b..501e1a288d 100755
@@ -288,6 +288,8 @@ test_git_path GIT_COMMON_DIR=bar index                    .git/index
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
+test_git_path GIT_COMMON_DIR=bar logs/refs                bar/logs/refs
+test_git_path GIT_COMMON_DIR=bar logs/refs/               bar/logs/refs/
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisectfoo      bar/logs/refs/bisectfoo
--

2.24.0.rc0.472.ga6f06c86b4

#20 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

Quoted 33 lines · Johannes Schindelin
Quoted 6 lines

- According to the comment describing trie_find(), it should only call the given match function 'fn' for a "/-or-\0-terminated prefix of the key for which the trie contains a value". This is not true: there are three places where trie_find() calls the match function, but one of them is missing the check for value's existence.

Thank you for this entire patch series. Just one nit:

Quoted 6 lines
path.c +0 -0
index cf57bd52dd..e21b00c4d4 100644
@@ -299,9 +299,13 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 

path.c static int trie_find(struct trie *root, const char *key, match_fn fn, · line 307

Quoted 10 lines
path.c +5 -2
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key) {
 		/* End of key */
-		return fn(key, root->value, baton);
+		if (root->value)
+			return fn(key, root->value, baton);
+		else
+			return -1;

I would have preferred this:

(quoted diff) +3 -0
+		if (!root->value)
+			return -1;
+		return fn(key, root->value, baton);
Quoted 2 lines

... as it would more accurately reflect my mental model of an "early out".

The checks at the other two of those three callsites look like this, and I just followed suit for the sake of consistency.

#38 lore raw

Re: [PATCH v3 2/2] git_path(): handle `.lock` files correctly

In reply to SZEDER Gábor

Hi,

Quoted 48 lines · SZEDER Gábor
Quoted 33 lines · Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is specific to the worktree, and therefore so is its reflog). However, the wrong path is returned for logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that logs/HEAD.lock file is constructed from git_path("logs/HEAD") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). While it does not seem as if Git GUI in particular is asking for logs/HEAD.lock, let's be safe rather than sorry.

Side note: Git GUI _does_ ask for index.lock, but that is already resolved correctly, due to update_common_dir() preferring to leave unknown paths in the (worktree-specific) git directory.

---
 path.c               |  8 +++++++-
 t/t1500-rev-parse.sh | 15 +++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)
path.c +0 -0
index e3da1f3c4e..5ff64e7a8a 100644

path.c static int check_common(const char *unmatched, void *value, void *baton) · line 355

Quoted 8 lines
path.c +4 -1
@@ -349,10 +349,16 @@ static int check_common(const char *unmatched, void *value, void *baton)
 static void update_common_dir(struct strbuf *buf, int git_dir_len,
 			      const char *common_dir)
 {
-	char *base = buf->buf + git_dir_len;
+	char *base = buf->buf + git_dir_len, *base2 = NULL;
+
+	if (ends_with(base, ".lock"))
+		base = base2 = xstrndup(base, strlen(base) - 5);

Hm, this adds the magic number 5 and calls strlen(base) twice, because ends_with() calls strip_suffix(), which calls strlen(). Calling strip_suffix() directly would allow us to avoid the magic number and the second strlen():

Quoted 3 lines
  size_t len;
  if (strip_suffix(base, ".lock", &len))
          base = base2 = xstrndup(base, len);

Actually, we should probably avoid the extra allocation. Earlier, I was concerned about multi-threading issues when attempting to modify the strbuf. But then, we modify that strbuf a couple of lines later anyway, so my fears were totally unfounded. Therefore, my current version reads like this:

-- snip --

	int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX);

	init_common_trie();
	if (trie_find(&common_trie, base, check_common, NULL) > 0)
		replace_dir(buf, git_dir_len, common_dir);

	if (has_lock_suffix)
		strbuf_addstr(buf, LOCK_SUFFIX);

-- snap --

Quoted 28 lines
Quoted 28 lines
path.c +2 -0
 	init_common_trie();
 	if (trie_find(&common_trie, base, check_common, NULL) > 0)
 		replace_dir(buf, git_dir_len, common_dir);
+
+	free(base2);
 }
 
 void report_linked_checkout_garbage(void)
t/t1500-rev-parse.sh +11 -0
index 01abee533d..d318a1eeef 100755
@@ -116,6 +116,21 @@ test_expect_success 'git-path inside sub-dir' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git-path in worktree' '
+	test_tick &&
+	git commit --allow-empty -m empty &&
+	git worktree add --detach wt &&
+	test_write_lines >expect \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
+		"$(pwd)/.git/worktrees/wt/index" \
+		"$(pwd)/.git/worktrees/wt/index.lock" &&
+	git -C wt rev-parse >actual \
+		--git-path logs/HEAD --git-path logs/HEAD.lock \
Quoted 8 lines

t/t1500-rev-parse.sh test_expect_success 'git-path inside sub-dir' ' · line 132

Quoted 3 lines
t/t1500-rev-parse.sh +3 -0
+		--git-path index --git-path index.lock &&
+	test_cmp expect actual
+'

I think this test better fits into 't0060-path-utils.sh': it already checks 'logs/HEAD' and 'index' in a different working tree (well, with GIT_COMMON_DIR set, but that's the same), and it has a helper function to make each of the two new .lock tests a one-liner.

Excellent point. Thank you for helping me improve the patch!

Ciao,
Dscho

Quoted 7 lines
Quoted 6 lines
t/t1500-rev-parse.sh +0 -1
 test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
 	test_commit test_commit &&
 	echo true >expect &&
--

gitgitgadget

#39 lore raw

Re: [PATCH v3 2/2] git_path(): handle `.lock` files correctly

Quoted 15 lines · Johannes Schindelin
Quoted 15 lines

path.c static int check_common(const char *unmatched, void *value, void *baton) · line 355

Quoted 9 lines
path.c +4 -1
@@ -349,10 +349,16 @@ static int check_common(const char *unmatched, void *value, void *baton)
 static void update_common_dir(struct strbuf *buf, int git_dir_len,
 			      const char *common_dir)
 {
-	char *base = buf->buf + git_dir_len;
+	char *base = buf->buf + git_dir_len, *base2 = NULL;
+
+	if (ends_with(base, ".lock"))
+		base = base2 = xstrndup(base, strlen(base) - 5);

Hm, this adds the magic number 5 and calls strlen(base) twice, because ends_with() calls strip_suffix(), which calls strlen(). Calling strip_suffix() directly would allow us to avoid the magic number and the second strlen():

Quoted 8 lines
Quoted 3 lines
  size_t len;
  if (strip_suffix(base, ".lock", &len))
          base = base2 = xstrndup(base, len);

Actually, we should probably avoid the extra allocation. Earlier, I was concerned about multi-threading issues when attempting to modify the strbuf. But then, we modify that strbuf a couple of lines later anyway,

Do we? You're right, we do indeed, because in replace_dir(buf, ...) we call strbuf_splice(buf, ...).
OK, then your suggestion below is even better.

Quoted 13 lines

so my fears were totally unfounded. Therefore, my current version reads like this:

-- snip --

	int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX);

	init_common_trie();
	if (trie_find(&common_trie, base, check_common, NULL) > 0)
		replace_dir(buf, git_dir_len, common_dir);

	if (has_lock_suffix)
		strbuf_addstr(buf, LOCK_SUFFIX);

-- snap --

#41 lore raw

v4 cover Handle git_path() with lock files correctly in worktrees

In reply to Johannes Schindelin via GitGitGadget

I stumbled over this during my recent work in Git GUI
[https://github.com/gitgitgadget/git/pull/361] that was originally really only intended to use the correct hooks directory.

It turns out that my fears that index.lock was mishandled were unfounded, hence this patch series has a lot lower priority for me than "OMG we must push this into v2.24.0!".

Technically, the first patch is not needed (because I decided against adding a test to t1400 in the end, in favor of t1500), but it shouldn't hurt, either.

I verified locally that this patch pair does not conflict with sg/dir-trie-fixes.

Changes since v3:

* Instead of duplicating the path when it has a .lock suffix, we now use strbuf manipulation to strip the suffix temporarily.
* The test case in t1500 was scrapped in favor of a much simpler pair of test cases in t0060.

Changes since v2:

* Adjusted the commit message to really not talk about index.lock.
* Instead of modifying the code inside trie_find() to special-case .lock, I now copy the string without the suffix .lock (if any) before handing it off to trie_find().

Changes since v1:

* Clarified the commit message to state that index.lock is fine, it is logs/HEAD.lock that isn't.

Johannes Schindelin (2):
t1400: wrap setup code in test case
git_path(): handle .lock files correctly

 path.c                |  6 ++++++
 t/t0060-path-utils.sh |  2 ++
 t/t1400-update-ref.sh | 18 ++++++++++--------
 3 files changed, 18 insertions(+), 8 deletions(-)

base-commit: 108b97dc372828f0e72e56bbb40cae8e1e83ece6
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-401%2Fdscho%2Flock-files-in-worktrees-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-401/dscho/lock-files-in-worktrees-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/401

Range-diff vs v3:

 1:  cf97c5182e = 1:  cf97c5182e t1400: wrap setup code in test case
 2:  8b1f4f089a ! 2:  d98810875e git_path(): handle `.lock` files correctly
     @@ -28,46 +28,40 @@
       --- a/path.c
       +++ b/path.c
      @@
     - static void update_common_dir(struct strbuf *buf, int git_dir_len,
     + #include "path.h"
     + #include "packfile.h"
     + #include "object-store.h"
     ++#include "lockfile.h"
     + 
     + static int get_st_mode_bits(const char *path, int *mode)
     + {
     +@@
       			      const char *common_dir)
       {
     --	char *base = buf->buf + git_dir_len;
     -+	char *base = buf->buf + git_dir_len, *base2 = NULL;
     -+
     -+	if (ends_with(base, ".lock"))
     -+		base = base2 = xstrndup(base, strlen(base) - 5);
     + 	char *base = buf->buf + git_dir_len;
     ++	int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX);
      +
       	init_common_trie();
       	if (trie_find(&common_trie, base, check_common, NULL) > 0)
       		replace_dir(buf, git_dir_len, common_dir);
      +
     -+	free(base2);
     ++	if (has_lock_suffix)
     ++		strbuf_addstr(buf, LOCK_SUFFIX);
       }
       
       void report_linked_checkout_garbage(void)
      
     - diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
     - --- a/t/t1500-rev-parse.sh
     - +++ b/t/t1500-rev-parse.sh
     + diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
     + --- a/t/t0060-path-utils.sh
     + +++ b/t/t0060-path-utils.sh
      @@
     - 	test_cmp expect actual
     - '
     - 
     -+test_expect_success 'git-path in worktree' '
     -+	test_tick &&
     -+	git commit --allow-empty -m empty &&
     -+	git worktree add --detach wt &&
     -+	test_write_lines >expect \
     -+		"$(pwd)/.git/worktrees/wt/logs/HEAD" \
     -+		"$(pwd)/.git/worktrees/wt/logs/HEAD.lock" \
     -+		"$(pwd)/.git/worktrees/wt/index" \
     -+		"$(pwd)/.git/worktrees/wt/index.lock" &&
     -+	git -C wt rev-parse >actual \
     -+		--git-path logs/HEAD --git-path logs/HEAD.lock \
     -+		--git-path index --git-path index.lock &&
     -+	test_cmp expect actual
     -+'
     -+
     - test_expect_success 'rev-parse --is-shallow-repository in shallow repo' '
     - 	test_commit test_commit &&
     - 	echo true >expect &&
     + test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
     + test_expect_success 'setup common repository' 'git --git-dir=bar init'
     + test_git_path GIT_COMMON_DIR=bar index                    .git/index
     ++test_git_path GIT_COMMON_DIR=bar index.lock               .git/index.lock
     + test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
     + test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
     ++test_git_path GIT_COMMON_DIR=bar logs/HEAD.lock           .git/logs/HEAD.lock
     + test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
     + test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
     + test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
Signature · 1 line
gitgitgadget
#42 lore raw

PATCH v4 1/2 t1400: wrap setup code in test case

From: Johannes Schindelin <[email protected]>

Without this, you cannot use --run=<...> to skip that part, and a run with --run=0 (which is a common way to determine the test case number corresponding to a given test case title).

---
 t/t1400-update-ref.sh | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)
t/t1400-update-ref.sh +10 -8
index 1fbd940408..69a7f27311 100755
@@ -344,14 +344,16 @@ test_expect_success "verifying $m's log (logged by config)" '
 	test_cmp expect .git/logs/$m
 '
 
-git update-ref $m $D
-cat >.git/logs/$m <<EOF
-$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
-$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
-$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
-$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
-$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
-EOF
+test_expect_success 'set up for querying the reflog' '
+	git update-ref $m $D &&
+	cat >.git/logs/$m <<-EOF
+	$Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
+	$C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500
+	$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
+	$F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500
+	$Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500
+	EOF
+'
 
 ed="Thu, 26 May 2005 18:32:00 -0500"
 gd="Thu, 26 May 2005 18:33:00 -0500"
Signature · 1 line
gitgitgadget
#43 lore raw

PATCH v4 2/2 git_path(): handle `.lock` files correctly

In reply to Johannes Schindelin via GitGitGadget

From: Johannes Schindelin <[email protected]>

Ever since worktrees were introduced, the git_path() function _really_ needed to be called e.g. to get at the path to logs/HEAD (HEAD is specific to the worktree, and therefore so is its reflog). However, the wrong path is returned for logs/HEAD.lock.

This does not matter as long as the Git executable is doing the asking, as the path for that logs/HEAD.lock file is constructed from git_path("logs/HEAD") by appending the .lock suffix.

However, Git GUI just learned to use --git-path instead of appending relative paths to what git rev-parse --git-dir returns (and as a consequence not only using the correct hooks directory, but also using the correct paths in worktrees other than the main one). While it does not seem as if Git GUI in particular is asking for logs/HEAD.lock, let's be safe rather than sorry.

Side note: Git GUI _does_ ask for index.lock, but that is already resolved correctly, due to update_common_dir() preferring to leave unknown paths in the (worktree-specific) git directory.

---
 path.c                | 6 ++++++
 t/t0060-path-utils.sh | 2 ++
 2 files changed, 8 insertions(+)
path.c +6 -0
index e3da1f3c4e..198a867017 100644
@@ -11,6 +11,7 @@
 #include "path.h"
 #include "packfile.h"
 #include "object-store.h"
+#include "lockfile.h"
 
 static int get_st_mode_bits(const char *path, int *mode)
 {
@@ -350,9 +351,14 @@ static void update_common_dir(struct strbuf *buf, int git_dir_len,
 			      const char *common_dir)
 {
 	char *base = buf->buf + git_dir_len;
+	int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX);
+
 	init_common_trie();
 	if (trie_find(&common_trie, base, check_common, NULL) > 0)
 		replace_dir(buf, git_dir_len, common_dir);
+
+	if (has_lock_suffix)
+		strbuf_addstr(buf, LOCK_SUFFIX);
 }
 
 void report_linked_checkout_garbage(void)
t/t0060-path-utils.sh +2 -0
index c7b53e494b..2aca8ccff9 100755
@@ -285,8 +285,10 @@ test_git_path GIT_OBJECT_DIRECTORY=foo objects/foo foo/foo
 test_git_path GIT_OBJECT_DIRECTORY=foo objects2 .git/objects2
 test_expect_success 'setup common repository' 'git --git-dir=bar init'
 test_git_path GIT_COMMON_DIR=bar index                    .git/index
+test_git_path GIT_COMMON_DIR=bar index.lock               .git/index.lock
 test_git_path GIT_COMMON_DIR=bar HEAD                     .git/HEAD
 test_git_path GIT_COMMON_DIR=bar logs/HEAD                .git/logs/HEAD
+test_git_path GIT_COMMON_DIR=bar logs/HEAD.lock           .git/logs/HEAD.lock
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisect/foo     .git/logs/refs/bisect/foo
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec/foo      bar/logs/refs/bisec/foo
 test_git_path GIT_COMMON_DIR=bar logs/refs/bisec          bar/logs/refs/bisec
Signature · 1 line
gitgitgadget
#40 lore raw

Re: [PATCH v3 2/2] git_path(): handle `.lock` files correctly

In reply to Johannes Schindelin
Quoted 19 lines · Johannes Schindelin
Quoted 3 lines
  size_t len;
  if (strip_suffix(base, ".lock", &len))
          base = base2 = xstrndup(base, len);

Actually, we should probably avoid the extra allocation. Earlier, I was concerned about multi-threading issues when attempting to modify the strbuf. But then, we modify that strbuf a couple of lines later anyway, so my fears were totally unfounded. Therefore, my current version reads like this:

-- snip --

	int has_lock_suffix = strbuf_strip_suffix(buf, LOCK_SUFFIX);

	init_common_trie();
	if (trie_find(&common_trie, base, check_common, NULL) > 0)
		replace_dir(buf, git_dir_len, common_dir);

	if (has_lock_suffix)
		strbuf_addstr(buf, LOCK_SUFFIX);
Quoted 1 line

-- snap --

Makes sense. Thanks for thinking it through.

#21 lore raw

Re: [PATCH 5/5] path.c: don't call the match function without value in trie_find()

In reply to SZEDER Gábor

Hi Gábor,

Quoted 34 lines · SZEDER Gábor
Quoted 33 lines · Johannes Schindelin
Quoted 6 lines

- According to the comment describing trie_find(), it should only call the given match function 'fn' for a "/-or-\0-terminated prefix of the key for which the trie contains a value". This is not true: there are three places where trie_find() calls the match function, but one of them is missing the check for value's existence.

Thank you for this entire patch series. Just one nit:

Quoted 6 lines
path.c +0 -0
index cf57bd52dd..e21b00c4d4 100644
@@ -299,9 +299,13 @@ static int trie_find(struct trie *root, const char *key, match_fn fn,
 

path.c static int trie_find(struct trie *root, const char *key, match_fn fn, · line 307

Quoted 10 lines
path.c +5 -2
 	/* Matched the entire compressed section */
 	key += i;
-	if (!*key)
+	if (!*key) {
 		/* End of key */
-		return fn(key, root->value, baton);
+		if (root->value)
+			return fn(key, root->value, baton);
+		else
+			return -1;

I would have preferred this:

(quoted diff) +3 -0
+		if (!root->value)
+			return -1;
+		return fn(key, root->value, baton);
Quoted 5 lines
Quoted 2 lines

... as it would more accurately reflect my mental model of an "early out".

The checks at the other two of those three callsites look like this, and I just followed suit for the sake of consistency.

Oh, okay. Sorry for the noise, then.

Thanks,
Dscho

#44 lore raw
In reply to Johannes Schindelin via GitGitGadget

"Johannes Schindelin via GitGitGadget" <[email protected]>

Quoted 6 lines · writes:

Changes since v3:

* Instead of duplicating the path when it has a .lock suffix, we now use strbuf manipulation to strip the suffix temporarily.
* The test case in t1500 was scrapped in favor of a much simpler pair of test cases in t0060.

Makes sense. Will replace.

Thanks.