diff mbox series

[v5] describe: refresh the index when 'broken' flag is used

Message ID 20240626065223.28154-1-abhijeet.nkt@gmail.com (mailing list archive)
State Superseded
Headers show
Series [v5] describe: refresh the index when 'broken' flag is used | expand

Commit Message

Abhijeet Sonar June 26, 2024, 6:52 a.m. UTC
When describe is run with 'dirty' flag, we refresh the index
to make sure it is in sync with the filesystem before
determining if the working tree is dirty.  However, this is
not done for the codepath where the 'broken' flag is used.

This causes `git describe --broken --dirty` to false
positively report the worktree being dirty if a file has
different stat info than what is recorded in the index.
Running `git update-index -q --refresh` to refresh the index
before running diff-index fixes the problem.

Also add tests to deliberately update stat info of a
file before running describe to verify it behaves correctly.

Reported-by: Paul Millar <paul.millar@desy.de>
Suggested-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Phillip Wood <phillip.wood123@gmail.com>
Signed-off-by: Abhijeet Sonar <abhijeet.nkt@gmail.com>
---
 builtin/describe.c  | 11 +++++++++++
 t/t6120-describe.sh | 24 ++++++++++++++++++++++++
 2 files changed, 35 insertions(+)


Range-diff against v4:
1:  1da5fa48d9 ! 1:  52f590b70f describe: refresh the index when 'broken' flag is used
    @@ builtin/describe.c: int cmd_describe(int argc, const char **argv, const char *pr
     +			cp.git_cmd = 1;
     +			cp.no_stdin = 1;
     +			cp.no_stdout = 1;
    -+			run_command(&cp);
    -+			strvec_clear(&cp.args);
    ++			if (run_command(&cp))
    ++				child_process_clear(&cp);
     +
      			strvec_pushv(&cp.args, diff_index_args);
      			cp.git_cmd = 1;

Comments

Karthik Nayak June 26, 2024, 11:30 a.m. UTC | #1
Abhijeet Sonar <abhijeet.nkt@gmail.com> writes:

> When describe is run with 'dirty' flag, we refresh the index
> to make sure it is in sync with the filesystem before
> determining if the working tree is dirty.  However, this is
> not done for the codepath where the 'broken' flag is used.
>
> This causes `git describe --broken --dirty` to false
> positively report the worktree being dirty if a file has
> different stat info than what is recorded in the index.
> Running `git update-index -q --refresh` to refresh the index
> before running diff-index fixes the problem.
>
> Also add tests to deliberately update stat info of a
> file before running describe to verify it behaves correctly.
>
> Reported-by: Paul Millar <paul.millar@desy.de>
> Suggested-by: Junio C Hamano <gitster@pobox.com>
> Helped-by: Junio C Hamano <gitster@pobox.com>
> Helped-by: Phillip Wood <phillip.wood123@gmail.com>
> Signed-off-by: Abhijeet Sonar <abhijeet.nkt@gmail.com>
> ---
>  builtin/describe.c  | 11 +++++++++++
>  t/t6120-describe.sh | 24 ++++++++++++++++++++++++
>  2 files changed, 35 insertions(+)
>
> diff --git a/builtin/describe.c b/builtin/describe.c
> index e5287eddf2..7cb9d50b36 100644
> --- a/builtin/describe.c
> +++ b/builtin/describe.c
> @@ -53,6 +53,10 @@ static const char *diff_index_args[] = {
>  	"diff-index", "--quiet", "HEAD", "--", NULL
>  };
>
> +static const char *update_index_args[] = {
> +	"update-index", "--unmerged", "-q", "--refresh", NULL
> +};
> +
>  struct commit_name {
>  	struct hashmap_entry entry;
>  	struct object_id peeled;
> @@ -645,6 +649,13 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
>  	if (argc == 0) {
>  		if (broken) {
>  			struct child_process cp = CHILD_PROCESS_INIT;
> +			strvec_pushv(&cp.args, update_index_args);
> +			cp.git_cmd = 1;
> +			cp.no_stdin = 1;
> +			cp.no_stdout = 1;
> +			if (run_command(&cp))
> +				child_process_clear(&cp);
> +
>  			strvec_pushv(&cp.args, diff_index_args);
>  			cp.git_cmd = 1;
>  			cp.no_stdin = 1;
> diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
> index e78315d23d..6c396e7abc 100755
> --- a/t/t6120-describe.sh
> +++ b/t/t6120-describe.sh
> @@ -671,4 +671,28 @@ test_expect_success 'setup misleading taggerdates' '
>
>  check_describe newer-tag-older-commit~1 --contains unique-file~2
>
> +test_expect_success 'describe --dirty with a file with changed stat' '
> +	git init stat-dirty &&
> +	(
> +		cd stat-dirty &&
> +
> +		echo A >file &&
> +		git add file &&
> +		git commit -m A &&
> +		git tag A -a -m A &&
> +
> +		cat file >file.new &&
> +		mv file.new file &&
> +		git describe --dirty >actual &&
> +		echo "A" >expected &&
> +		test_cmp expected actual &&
> +
> +		cat file >file.new &&
> +		mv file.new file &&
> +		git describe --dirty --broken >actual &&
> +		echo "A" >expected &&
> +		test_cmp expected actual

Not worth a reroll, but you don't have to create file.new twice.

diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 6c396e7abc..6c4b20fec7 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -683,14 +683,12 @@ test_expect_success 'describe --dirty with a
file with changed stat' '

 		cat file >file.new &&
 		mv file.new file &&
-		git describe --dirty >actual &&
 		echo "A" >expected &&
+
+		git describe --dirty >actual &&
 		test_cmp expected actual &&

-		cat file >file.new &&
-		mv file.new file &&
 		git describe --dirty --broken >actual &&
-		echo "A" >expected &&
 		test_cmp expected actual
 	)
 '

> +	)
> +'
> +
>  test_done
>
> Range-diff against v4:
> 1:  1da5fa48d9 ! 1:  52f590b70f describe: refresh the index when 'broken' flag is used
>     @@ builtin/describe.c: int cmd_describe(int argc, const char **argv, const char *pr
>      +			cp.git_cmd = 1;
>      +			cp.no_stdin = 1;
>      +			cp.no_stdout = 1;
>     -+			run_command(&cp);
>     -+			strvec_clear(&cp.args);
>     ++			if (run_command(&cp))
>     ++				child_process_clear(&cp);
>      +
>       			strvec_pushv(&cp.args, diff_index_args);
>       			cp.git_cmd = 1;
> --
> 2.45.2.606.g9005149a4a.dirty

Other than this, this looks good to me.

Thanks!
Abhijeet Sonar June 26, 2024, 12:06 p.m. UTC | #2
On 26/06/24 17:00, Karthik Nayak wrote:
> Not worth a reroll, but you don't have to create file.new twice.

Actually, now that I think of it, those two were better off being 
separate tests.  It might so happen the first call to describe refreshes 
the index, due to which the second call with the --broken option does 
not bug-out in the way it would if the command was run by itself. 
Having them separate would give them enough isolation so that previous 
command does not interfere with the later.

>> Range-diff against v4:
>> 1:  1da5fa48d9 ! 1:  52f590b70f describe: refresh the index when 'broken' flag is used
>>      @@ builtin/describe.c: int cmd_describe(int argc, const char **argv, const char *pr
>>       +			cp.git_cmd = 1;
>>       +			cp.no_stdin = 1;
>>       +			cp.no_stdout = 1;
>>      -+			run_command(&cp);
>>      -+			strvec_clear(&cp.args);
>>      ++			if (run_command(&cp))
>>      ++				child_process_clear(&cp);
>>       +
>>        			strvec_pushv(&cp.args, diff_index_args);
>>        			cp.git_cmd = 1;
>> --
>> 2.45.2.606.g9005149a4a.dirty
> 
> Other than this, this looks good to me.
I am not sure if I follow this one.  Am I expected to not share the 
struct child_process between the two sub-process calls?

Thanks
Junio C Hamano June 26, 2024, 2:59 p.m. UTC | #3
Karthik Nayak <karthik.188@gmail.com> writes:

>> +		cat file >file.new &&
>> +		mv file.new file &&
>> +		git describe --dirty >actual &&
>> +		echo "A" >expected &&
>> +		test_cmp expected actual &&
>> +
>> +		cat file >file.new &&
>> +		mv file.new file &&
>> +		git describe --dirty --broken >actual &&
>> +		echo "A" >expected &&
>> +		test_cmp expected actual
>
> Not worth a reroll, but you don't have to create file.new twice.

I think you have to make the "file" stat-dirty again after the first
test, as a successful first test _should_ refresh the index (and
that was why my manual illustration in an earlier message
<xmqqsex2b4ti.fsf@gitster.g> did "--dirty --broken" first before
"--dirty" alone, knowing that the former fails to refresh the
index).

You are right to point out that the expected results for "--dirty"
and "--dirty --broken" are the same and we do not have to create it
twice, though.

If the filesystem has a usable inode number, then replacing "file"
with a copy, i.e. "cat file >file.new && mv file.new file", is an
excellent way to ensure that the stat data becomes dirty even when
it is done as a part of a quick series of commands.  But not all
filesystems we run our tests on may have usable inode number, so it
may not be the best thing to do in an automated test.  We could use
"test-tool chmtime" instead, perhaps like:

    ... make the index and the working tree are clean wrt HEAD ...
    # we do not expect -dirty suffix in the output
    echo A >expect &&

    # make "file" stat-dirty
    test-tool chmtime -10 file &&
    # "describe --dirty" refreshes and does not get fooled
    git describe --dirty >actual &&
    test_cmp expect actual &&

    # make "file" stat-dirty again
    test-tool chmtime -10 file &&
    # "describe --dirty --broken" refreshes and does not get fooled
    git describe --dirty --broken >actual &&
    test_cmp expect actual

with the extra comments stripped (I added them only to explain what
is going on to the readers of this e-mail message).
Junio C Hamano June 26, 2024, 6:31 p.m. UTC | #4
Abhijeet Sonar <abhijeet.nkt@gmail.com> writes:

> When describe is run with 'dirty' flag, we refresh the index
> to make sure it is in sync with the filesystem before
> determining if the working tree is dirty.  However, this is
> not done for the codepath where the 'broken' flag is used.
>
> This causes `git describe --broken --dirty` to false
> positively report the worktree being dirty if a file has
> different stat info than what is recorded in the index.
> Running `git update-index -q --refresh` to refresh the index
> before running diff-index fixes the problem.
>
> Also add tests to deliberately update stat info of a
> file before running describe to verify it behaves correctly.
>
> Reported-by: Paul Millar <paul.millar@desy.de>
> Suggested-by: Junio C Hamano <gitster@pobox.com>
> Helped-by: Junio C Hamano <gitster@pobox.com>
> Helped-by: Phillip Wood <phillip.wood123@gmail.com>
> Signed-off-by: Abhijeet Sonar <abhijeet.nkt@gmail.com>
> ---

As I screwed up with the suggestion to do child_process_clear()
after a failed run_command(), let me fix that up.  A suggested
change that can be squashed into this patch is attached at the end.

 * (style) a blank line between a block of variable declarations and
   the first statement;

 * run_command(&cp) cleans cp so no need for separate
   child_process_clear(&cp);

 * but child_process_init(&cp) is needed, just as 4d0984be (fsck: do
   not reuse child_process structs, 2018-11-12) explains, before
   reusing the structure for a separate call.

 * instead of "replace with a different file" which relies on having
   a working inum, use "test-tool chmtime" to reliably force dirty
   mtime.

 * everybody else compares "actual" against "expect", not
   "expected".  imitate them.

 * test "--dirty" and "--dirty --broken" separately in two separate
   tests.

Thanks.


 builtin/describe.c  |  5 +++--
 t/t6120-describe.sh | 28 ++++++++++++++++++++--------
 2 files changed, 23 insertions(+), 10 deletions(-)

diff --git c/builtin/describe.c w/builtin/describe.c
index a58f6134f0..e936d2c19f 100644
--- c/builtin/describe.c
+++ w/builtin/describe.c
@@ -649,13 +649,14 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
 	if (argc == 0) {
 		if (broken) {
 			struct child_process cp = CHILD_PROCESS_INIT;
+
 			strvec_pushv(&cp.args, update_index_args);
 			cp.git_cmd = 1;
 			cp.no_stdin = 1;
 			cp.no_stdout = 1;
-			if (run_command(&cp))
-				child_process_clear(&cp);
+			run_command(&cp);
 
+			child_process_init(&cp);
 			strvec_pushv(&cp.args, diff_index_args);
 			cp.git_cmd = 1;
 			cp.no_stdin = 1;
diff --git c/t/t6120-describe.sh w/t/t6120-describe.sh
index 6c396e7abc..79e0f19deb 100755
--- c/t/t6120-describe.sh
+++ w/t/t6120-describe.sh
@@ -672,6 +672,7 @@ test_expect_success 'setup misleading taggerdates' '
 check_describe newer-tag-older-commit~1 --contains unique-file~2
 
 test_expect_success 'describe --dirty with a file with changed stat' '
+	test_when_finished "rm -fr stat-dirty" &&
 	git init stat-dirty &&
 	(
 		cd stat-dirty &&
@@ -680,18 +681,29 @@ test_expect_success 'describe --dirty with a file with changed stat' '
 		git add file &&
 		git commit -m A &&
 		git tag A -a -m A &&
+		echo "A" >expect &&
 
-		cat file >file.new &&
-		mv file.new file &&
+		test-tool chmtime -10 file &&
 		git describe --dirty >actual &&
-		echo "A" >expected &&
-		test_cmp expected actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe --broken --dirty with a file with changed stat' '
+	test_when_finished "rm -fr stat-dirty" &&
+	git init stat-dirty &&
+	(
+		cd stat-dirty &&
 
-		cat file >file.new &&
-		mv file.new file &&
+		echo A >file &&
+		git add file &&
+		git commit -m A &&
+		git tag A -a -m A &&
+		echo "A" >expect &&
+
+		test-tool chmtime -10 file &&
 		git describe --dirty --broken >actual &&
-		echo "A" >expected &&
-		test_cmp expected actual
+		test_cmp expect actual
 	)
 '
diff mbox series

Patch

diff --git a/builtin/describe.c b/builtin/describe.c
index e5287eddf2..7cb9d50b36 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -53,6 +53,10 @@  static const char *diff_index_args[] = {
 	"diff-index", "--quiet", "HEAD", "--", NULL
 };
 
+static const char *update_index_args[] = {
+	"update-index", "--unmerged", "-q", "--refresh", NULL
+};
+
 struct commit_name {
 	struct hashmap_entry entry;
 	struct object_id peeled;
@@ -645,6 +649,13 @@  int cmd_describe(int argc, const char **argv, const char *prefix)
 	if (argc == 0) {
 		if (broken) {
 			struct child_process cp = CHILD_PROCESS_INIT;
+			strvec_pushv(&cp.args, update_index_args);
+			cp.git_cmd = 1;
+			cp.no_stdin = 1;
+			cp.no_stdout = 1;
+			if (run_command(&cp))
+				child_process_clear(&cp);
+
 			strvec_pushv(&cp.args, diff_index_args);
 			cp.git_cmd = 1;
 			cp.no_stdin = 1;
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index e78315d23d..6c396e7abc 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -671,4 +671,28 @@  test_expect_success 'setup misleading taggerdates' '
 
 check_describe newer-tag-older-commit~1 --contains unique-file~2
 
+test_expect_success 'describe --dirty with a file with changed stat' '
+	git init stat-dirty &&
+	(
+		cd stat-dirty &&
+
+		echo A >file &&
+		git add file &&
+		git commit -m A &&
+		git tag A -a -m A &&
+
+		cat file >file.new &&
+		mv file.new file &&
+		git describe --dirty >actual &&
+		echo "A" >expected &&
+		test_cmp expected actual &&
+
+		cat file >file.new &&
+		mv file.new file &&
+		git describe --dirty --broken >actual &&
+		echo "A" >expected &&
+		test_cmp expected actual
+	)
+'
+
 test_done