diff mbox series

[v3,1/3] worktree: refactor infer_backlink() to use *strbuf

Message ID 20241007-wt_relative_paths-v3-1-622cf18c45eb@pm.me (mailing list archive)
State New
Headers show
Series Link worktrees with relative paths | expand

Commit Message

Caleb White via B4 Relay Oct. 8, 2024, 3:12 a.m. UTC
From: Caleb White <cdwhite3@pm.me>

This lays the groundwork for the next patch, which needs the backlink
returned from infer_backlink() as a `strbuf`. It seemed inefficient to
convert from `strbuf` to `char*` and back to `strbuf` again.

This refactors infer_backlink() to return an integer result and use a
pre-allocated `strbuf` for the inferred backlink path, replacing the
previous `char*` return type and improving efficiency.

Signed-off-by: Caleb White <cdwhite3@pm.me>
---
 worktree.c | 48 ++++++++++++++++++++++++------------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

Comments

shejialuo Oct. 10, 2024, 3:52 p.m. UTC | #1
On Mon, Oct 07, 2024 at 10:12:30PM -0500, Caleb White via B4 Relay wrote:
> From: Caleb White <cdwhite3@pm.me>
> 
> This lays the groundwork for the next patch, which needs the backlink
> returned from infer_backlink() as a `strbuf`. It seemed inefficient to
> convert from `strbuf` to `char*` and back to `strbuf` again.
> 

I think here we should first talk about the current behavior of the
`infer_backlink`:

    `infer_backlink` initializes a "strbuf" for the inferred backlink
    and returns the result with type "char *" by detaching the "strbuf"

And then you should tell your intention like the following (The reader
like me does not know what is the intention of the next patch, you should
__explicitly__ mention this).

    Because we decide to link worktrees with relative paths, we need to
    convert the returned inferred backlink "char *" to "strbuf".
    However, it is a bad idea to convert from `strbuf` to `char*` and
    back to `strbuf` again. Instead, we should just let the
    `infer_backlink` to accept the "struct strbuf *" parameter to
    improve efficiency.

> This refactors infer_backlink() to return an integer result and use a
> pre-allocated `strbuf` for the inferred backlink path, replacing the
> previous `char*` return type and improving efficiency.
> 

I think you should also talk about that you make the
`repair_worktree_at_path` function to align with above refactor.

> Signed-off-by: Caleb White <cdwhite3@pm.me>
> ---
>  worktree.c | 48 ++++++++++++++++++++++++------------------------
>  1 file changed, 24 insertions(+), 24 deletions(-)
> 
> diff --git a/worktree.c b/worktree.c
> index ec95ea2986107b3bc12d38b0825d7c6e87402bc6..0cba0d6e6e9ad02ace04a0301104a04a07cbef65 100644
> --- a/worktree.c
> +++ b/worktree.c
> @@ -642,10 +642,9 @@ static int is_main_worktree_path(const char *path)
>   * be able to infer the gitdir by manually reading /path/to/worktree/.git,
>   * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
>   */
> -static char *infer_backlink(const char *gitfile)
> +static int infer_backlink(const char *gitfile, struct strbuf *inferred)
>  {
>  	struct strbuf actual = STRBUF_INIT;
> -	struct strbuf inferred = STRBUF_INIT;
>  	const char *id;
>  
>  	if (strbuf_read_file(&actual, gitfile, 0) < 0)
> @@ -658,17 +657,18 @@ static char *infer_backlink(const char *gitfile)
>  	id++; /* advance past '/' to point at <id> */
>  	if (!*id)
>  		goto error;
> -	strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
> -	if (!is_directory(inferred.buf))
> +	strbuf_reset(inferred);

Question here: should we use `strbuf_reset` here? I want to know the
reason why you design this. Is the caller's responsibility to clear the
"inferred" when calling this function?

> +	strbuf_git_common_path(inferred, the_repository, "worktrees/%s", id);
> +	if (!is_directory(inferred->buf))
>  		goto error;
>  
>  	strbuf_release(&actual);
> -	return strbuf_detach(&inferred, NULL);
> +	return 1;
>  
>  error:
>  	strbuf_release(&actual);
> -	strbuf_release(&inferred);
> -	return NULL;
> +	strbuf_reset(inferred); /* clear invalid path */
> +	return 0;
>  }

Design question, when calling `infer_backlink` successfully, we will
return 1, if not, we will return 0. But in the later code, we use the
"inferred.buf.len" to indicate whether we could get the inferred
backlink successfully.

We have two signals to indicate the success. I think it's a bad idea to
use "inferred.buf.len". Let me give you an example here:

    struct strbuf inferred_backlink = STRBUF_INIT;
    inferred_backlink = infer_backlink(realdotgit.buf);

    // What if I wrongly use the following statements?
    strbuf_addstr(&inferred_backlink, "foo");

    if (inferred_backlink.buf.len) {

    }

If you insist using "inferred_backlink.buf.len" as the result, this
function should return `void`. And you should add some comments for this
function.

>  
>  /*
> @@ -680,10 +680,11 @@ void repair_worktree_at_path(const char *path,
>  {
>  	struct strbuf dotgit = STRBUF_INIT;
>  	struct strbuf realdotgit = STRBUF_INIT;
> +	struct strbuf backlink = STRBUF_INIT;
> +	struct strbuf inferred_backlink = STRBUF_INIT;
>  	struct strbuf gitdir = STRBUF_INIT;
>  	struct strbuf olddotgit = STRBUF_INIT;
> -	char *backlink = NULL;
> -	char *inferred_backlink = NULL;
> +	char *dotgit_contents = NULL;
>  	const char *repair = NULL;
>  	int err;
>  
> @@ -699,23 +700,23 @@ void repair_worktree_at_path(const char *path,
>  		goto done;
>  	}
>  
> -	inferred_backlink = infer_backlink(realdotgit.buf);
> -	backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
> -	if (err == READ_GITFILE_ERR_NOT_A_FILE) {
> +	infer_backlink(realdotgit.buf, &inferred_backlink);
> +	dotgit_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
> +	if (dotgit_contents) {
> +		strbuf_addstr(&backlink, dotgit_contents);
> +	} else if (err == READ_GITFILE_ERR_NOT_A_FILE) {
>  		fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
>  		goto done;
>  	} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
> -		if (inferred_backlink) {
> +		if (inferred_backlink.len) {
>  			/*
>  			 * Worktree's .git file does not point at a repository
>  			 * but we found a .git/worktrees/<id> in this
>  			 * repository with the same <id> as recorded in the
>  			 * worktree's .git file so make the worktree point at
> -			 * the discovered .git/worktrees/<id>. (Note: backlink
> -			 * is already NULL, so no need to free it first.)
> +			 * the discovered .git/worktrees/<id>.
>  			 */
> -			backlink = inferred_backlink;
> -			inferred_backlink = NULL;
> +			strbuf_swap(&backlink, &inferred_backlink);
>  		} else {
>  			fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
>  			goto done;
> @@ -743,13 +744,11 @@ void repair_worktree_at_path(const char *path,
>  	 * in the "copy" repository. In this case, point the "copy" worktree's
>  	 * .git file at the "copy" repository.
>  	 */
> -	if (inferred_backlink && fspathcmp(backlink, inferred_backlink)) {
> -		free(backlink);
> -		backlink = inferred_backlink;
> -		inferred_backlink = NULL;
> +	if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
> +		strbuf_swap(&backlink, &inferred_backlink);
>  	}

For single line statement after "if", we should not use `{`.

>  
> -	strbuf_addf(&gitdir, "%s/gitdir", backlink);
> +	strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
>  	if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
>  		repair = _("gitdir unreadable");
>  	else {
> @@ -763,9 +762,10 @@ void repair_worktree_at_path(const char *path,
>  		write_file(gitdir.buf, "%s", realdotgit.buf);
>  	}
>  done:
> -	free(backlink);
> -	free(inferred_backlink);
> +	free(dotgit_contents);
>  	strbuf_release(&olddotgit);
> +	strbuf_release(&backlink);
> +	strbuf_release(&inferred_backlink);
>  	strbuf_release(&gitdir);
>  	strbuf_release(&realdotgit);
>  	strbuf_release(&dotgit);
> 
> -- 
> 2.46.2
> 
> 

Sorry, I could not review other patches today (I need to go sleep).

Thanks,
Jialuo
Caleb White Oct. 10, 2024, 4:41 p.m. UTC | #2
On Thursday, October 10th, 2024 at 10:52, shejialuo <shejialuo@gmail.com> wrote:

> On Mon, Oct 07, 2024 at 10:12:30PM -0500, Caleb White via B4 Relay wrote:
> 

> > From: Caleb White cdwhite3@pm.me
> > 

> > This lays the groundwork for the next patch, which needs the backlink
> > returned from infer_backlink() as a `strbuf`. It seemed inefficient to
> > convert from `strbuf` to `char*` and back to `strbuf` again.
> 

> 

> I think here we should first talk about the current behavior of the
> `infer_backlink`:
> 

> `infer_backlink` initializes a "strbuf" for the inferred backlink
> and returns the result with type "char *" by detaching the "strbuf"
> 

> And then you should tell your intention like the following (The reader
> like me does not know what is the intention of the next patch, you should
> explicitly mention this).
> 

> Because we decide to link worktrees with relative paths, we need to
> convert the returned inferred backlink "char *" to "strbuf".
> However, it is a bad idea to convert from `strbuf` to `char*` and
> back to `strbuf` again. Instead, we should just let the
> `infer_backlink` to accept the "struct strbuf *" parameter to
> improve efficiency.
> 

> > This refactors infer_backlink() to return an integer result and use a
> > pre-allocated `strbuf` for the inferred backlink path, replacing the
> > previous `char*` return type and improving efficiency.
> 

> 

> I think you should also talk about that you make the
> `repair_worktree_at_path` function to align with above refactor.

Thank you for the feedback, I'll add these changes.

> > if (strbuf_read_file(&actual, gitfile, 0) < 0)
> > @@ -658,17 +657,18 @@ static char *infer_backlink(const char gitfile)
> > id++; / advance past '/' to point at <id> */
> > if (!*id)
> > goto error;
> > - strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
> > - if (!is_directory(inferred.buf))
> > + strbuf_reset(inferred);
> 

> 

> Question here: should we use `strbuf_reset` here? I want to know the
> reason why you design this. Is the caller's responsibility to clear the
> "inferred" when calling this function?

Yes we should, sure it is the caller's responsibility but this just helps
prevent bugs. There's plenty of functions that reset the strbuf that's
passed to the function before modifying it.


> > + strbuf_git_common_path(inferred, the_repository, "worktrees/%s", id);
> > + if (!is_directory(inferred->buf))
> > goto error;
> > 

> > strbuf_release(&actual);
> > - return strbuf_detach(&inferred, NULL);
> > + return 1;
> > 

> > error:
> > strbuf_release(&actual);
> > - strbuf_release(&inferred);
> > - return NULL;
> > + strbuf_reset(inferred); /* clear invalid path */
> > + return 0;
> > }
> 

> 

> Design question, when calling `infer_backlink` successfully, we will
> return 1, if not, we will return 0. But in the later code, we use the
> "inferred.buf.len" to indicate whether we could get the inferred
> backlink successfully.

Originally, this function call was inside an `if` condition, and it made
sense to keep the boolean return. However, the dependent patch that I later
rebased on refactored this to be a standalone call. I didn't feel like
introducing another variable just to capture the return value when I could
glean the same information from the existing strbuf.

> We have two signals to indicate the success. I think it's a bad idea to
> use "inferred.buf.len". Let me give you an example here:

I don't see a problem with this---the two "signals" are guaranteed to
always be in sync (either the return is 1 and len is > 0, or return is
0 and len is 0). Having the boolean return gives you flexibility in how
you can call the function (if it can be placed inside an if condition).

> struct strbuf inferred_backlink = STRBUF_INIT;
> inferred_backlink = infer_backlink(realdotgit.buf);
> 

> // What if I wrongly use the following statements?
> strbuf_addstr(&inferred_backlink, "foo");
> 

> if (inferred_backlink.buf.len) {
> 

> }

I'm sorry, but this example doesn't make sense. This will fail to compile
for several reasons:
- infer_backlink() requires two args
- you cannot assign an `int` to a `struct strbuf`
- even if inferred_backlink became an int then the strbuf_addstr()
  would fail because you can't pass an `int*` to a `struct strbuf*`
- `inferred_backlink.buf.len` doesn't exist, it's `inferred_backlink.len`
  (probably just a typo)

> If you insist using "inferred_backlink.buf.len" as the result, this
> function should return `void`. And you should add some comments for this
> function.

I can add comments, and I can change the return type to `void` if there's
consensus, but I really don't see any issue with leaving it as-is.

> > - if (inferred_backlink && fspathcmp(backlink, inferred_backlink)) {
> > - free(backlink);
> > - backlink = inferred_backlink;
> > - inferred_backlink = NULL;
> > + if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
> > + strbuf_swap(&backlink, &inferred_backlink);
> > }
> 

> 

> For single line statement after "if", we should not use `{`.

The brackets were introduced by the patch that my series depends on.
I can remove them, but perhaps it would be better to address that
on the dependent patch?

Best,
shejialuo Oct. 10, 2024, 5:26 p.m. UTC | #3
On Thu, Oct 10, 2024 at 04:41:03PM +0000, Caleb White wrote:

[snip]

> > > @@ -658,17 +657,18 @@ static char *infer_backlink(const char gitfile)
> > > id++; / advance past '/' to point at <id> */
> > > if (!*id)
> > > goto error;
> > > - strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
> > > - if (!is_directory(inferred.buf))
> > > + strbuf_reset(inferred);
> > 
> 
> > 
> 
> > Question here: should we use `strbuf_reset` here? I want to know the
> > reason why you design this. Is the caller's responsibility to clear the
> > "inferred" when calling this function?
> 
> Yes we should, sure it is the caller's responsibility but this just helps
> prevent bugs. There's plenty of functions that reset the strbuf that's
> passed to the function before modifying it.
> 

Yes, that make senses.

[snip]

> > We have two signals to indicate the success. I think it's a bad idea to
> > use "inferred.buf.len". Let me give you an example here:
> 
> I don't see a problem with this---the two "signals" are guaranteed to
> always be in sync (either the return is 1 and len is > 0, or return is
> 0 and len is 0). Having the boolean return gives you flexibility in how
> you can call the function (if it can be placed inside an if condition).
> 

Yes, there is nothing wrong with this. But we also introduce a burden here,
when we need to change/refactor `infer_backlink`, the developer should
have the knowledge "when the return is 1 and len is >0 or return is 0
and len is 0".

If so, why not just return "infer_backlink.len"?

> > struct strbuf inferred_backlink = STRBUF_INIT;
> > inferred_backlink = infer_backlink(realdotgit.buf);
> > 
> 
> > // What if I wrongly use the following statements?
> > strbuf_addstr(&inferred_backlink, "foo");
> > 
> 
> > if (inferred_backlink.buf.len) {
> > 
> 
> > }
> 
> I'm sorry, but this example doesn't make sense. This will fail to compile
> for several reasons:
> - infer_backlink() requires two args
> - you cannot assign an `int` to a `struct strbuf`
> - even if inferred_backlink became an int then the strbuf_addstr()
>   would fail because you can't pass an `int*` to a `struct strbuf*`
> - `inferred_backlink.buf.len` doesn't exist, it's `inferred_backlink.len`
>   (probably just a typo)
> 

I am sorry for this, I gave a wrong example here, it should be the
following (I copied the wrong line in the previous email):

    struct strbuf inferred_backlink = STRBUF_INIT;
    infer_backlink(realdotgit.buf, &inferred_backlink);

    // What if I wronly use the following statements?
    strbuf_addstr(&inferred_backlink, "foo");

    if (inferred_backlink.len) {
        ...
    }

Actually, I am not against your way. Instead, you should mention why you
choose "inferred_backlink.len" as the signal in the commit message.
That's the main reason why I think we may add some comments here. The
caller may do not know we should use "inferred_backlink.len" to indicate
that we have successfully found the inferred backlink especially there
is already a return code in the function.

> > If you insist using "inferred_backlink.buf.len" as the result, this
> > function should return `void`. And you should add some comments for this
> > function.
> 
> I can add comments, and I can change the return type to `void` if there's
> consensus, but I really don't see any issue with leaving it as-is.
> 

I agree with you that this function is NOT harmful. Actually, I do not
think using "void" as the return type is a good idea. If we decide to
use two signals, let's leave it as-is. Some comments should be enough.

> > > - if (inferred_backlink && fspathcmp(backlink, inferred_backlink)) {
> > > - free(backlink);
> > > - backlink = inferred_backlink;
> > > - inferred_backlink = NULL;
> > > + if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
> > > + strbuf_swap(&backlink, &inferred_backlink);
> > > }
> > 
> 
> > 
> 
> > For single line statement after "if", we should not use `{`.
> 
> The brackets were introduced by the patch that my series depends on.
> I can remove them, but perhaps it would be better to address that
> on the dependent patch?
> 

The original patch has three lines. So it should use `{`. After your
change, it only has one line, isn't it?

You could refer to this to see the code style.

  https://github.com/git/git/blob/master/Documentation/CodingGuidelines

> Best,

Thanks,
Jialuo
Caleb White Oct. 10, 2024, 5:52 p.m. UTC | #4
On Thursday, October 10th, 2024 at 12:26, shejialuo <shejialuo@gmail.com> wrote:

> > > We have two signals to indicate the success. I think it's a bad idea to
> > > use "inferred.buf.len". Let me give you an example here:
> > 

> > I don't see a problem with this---the two "signals" are guaranteed to
> > always be in sync (either the return is 1 and len is > 0, or return is
> > 0 and len is 0). Having the boolean return gives you flexibility in how
> > you can call the function (if it can be placed inside an if condition).
> 

> Yes, there is nothing wrong with this. But we also introduce a burden here,
> when we need to change/refactor `infer_backlink`, the developer should
> have the knowledge "when the return is 1 and len is >0 or return is 0
> and len is 0".
> 

> If so, why not just return "infer_backlink.len"?

I would say because the purpose of the return is a boolean,
either the call was successful or it wasn't. The developer
knowledge that you speak of should be a given---if the
function returned true then there's obviously a path
in the strbuf.

> I am sorry for this, I gave a wrong example here, it should be the
> following (I copied the wrong line in the previous email):
> 

> struct strbuf inferred_backlink = STRBUF_INIT;
> infer_backlink(realdotgit.buf, &inferred_backlink);
> 

> // What if I wronly use the following statements?
> strbuf_addstr(&inferred_backlink, "foo");

There's nothing wrong with this, it's on the developer
to check the error condition before proceeding to use
the strbuf.

> Actually, I am not against your way. Instead, you should mention why you
> choose "inferred_backlink.len" as the signal in the commit message.
> That's the main reason why I think we may add some comments here. The
> caller may do not know we should use "inferred_backlink.len" to indicate
> that we have successfully found the inferred backlink especially there
> is already a return code in the function.

I think the intent is fairly self-evident and does not warrant a
comment in the commit message? If the strbuf does not have a
length then it should be obvious there is no inferred backlink?

> > > If you insist using "inferred_backlink.buf.len" as the result, this
> > > function should return `void`. And you should add some comments for this
> > > function.
> > 

> > I can add comments, and I can change the return type to `void` if there's
> > consensus, but I really don't see any issue with leaving it as-is.
> 

> I agree with you that this function is NOT harmful. Actually, I do not
> think using "void" as the return type is a good idea. If we decide to
> use two signals, let's leave it as-is. Some comments should be enough.

I've added a comment to the function docs.

> The original patch has three lines. So it should use `{`. After your
> change, it only has one line, isn't it?
> 

> You could refer to this to see the code style.
> 

> https://github.com/git/git/blob/master/Documentation/CodingGuidelines

Ah I'm sorry, you're right---I'll adjust.

Best,
Junio C Hamano Oct. 10, 2024, 7:14 p.m. UTC | #5
Caleb White <cdwhite3@pm.me> writes:

>> Question here: should we use `strbuf_reset` here? I want to know the
>> reason why you design this. Is the caller's responsibility to clear the
>> "inferred" when calling this function?
>
> Yes we should, sure it is the caller's responsibility but this just helps
> prevent bugs. There's plenty of functions that reset the strbuf that's
> passed to the function before modifying it.

If the API says that it _appends_ what it found in the supplied
strbuf, then it is the caller's responsibility to ensure that the
strbuf being passed is empty, if the caller wants the resulting
strbuf to hold only what the function found.

If the API says it _returns_ what it found in the supplied strbuf,
then the function is responsible to discard what the supplied strbuf
originally has.

As an API design, if it is rarely useful for callers to be able to
append, then the latter is simpler to use, but there are many cases
where the "append" semantics is useful (see strbuf.c to find
examples).

In this particular case, it is rarely useful for the caller to get
the result appended to a string it already has before calling this
function, so unconditionally discarding what the caller had, without
telling the caller that it is responsible for passing an empty buffer,
is the right thing, I wuld think.

Thanks.
Junio C Hamano Oct. 10, 2024, 8:03 p.m. UTC | #6
Caleb White <cdwhite3@pm.me> writes:

>> If so, why not just return "infer_backlink.len"?
>
> I would say because the purpose of the return is a boolean,
> either the call was successful or it wasn't. The developer
> knowledge that you speak of should be a given---if the
> function returned true then there's obviously a path
> in the strbuf.

That does not say what should be left in the strbuf when the request
failed.  If it is undefined, then using its .len (or .buf) is not
quite right, without relying on too much implementation details of
the called function.

Usually, this project uses 0 to signal a success, while errors are
signalled by returning a negative value (and if there can be
multiple ways to fail, such a design leaves different negative
values as a possibility).

If the result the caller finds useful is always positive value (or
non-negative value), however, it is perfectly fine and often more
convenient if you used the "positive (or non-negative) value gives
the intended result and signals a success, negative (or
non-positive) value signals an error" convention.  The caller can

    if (0 < do_the_thing())
	; /* success */

just fine, instead of the boolean "0 is success, negative values are
various errors",

    if (!do_the_thing())
	; /* success */

when it does not care how/why the thing failed.
Caleb White Oct. 10, 2024, 8:24 p.m. UTC | #7
On Thursday, October 10th, 2024 at 15:03, Junio C Hamano <gitster@pobox.com> wrote:

> That does not say what should be left in the strbuf when the request
> failed. If it is undefined, then using its .len (or .buf) is not
> quite right, without relying on too much implementation details of
> the called function.
> 

> Usually, this project uses 0 to signal a success, while errors are
> signalled by returning a negative value (and if there can be
> multiple ways to fail, such a design leaves different negative
> values as a possibility).
> 

> If the result the caller finds useful is always positive value (or
> non-negative value), however, it is perfectly fine and often more
> convenient if you used the "positive (or non-negative) value gives
> the intended result and signals a success, negative (or
> non-positive) value signals an error" convention. The caller can
> 

> if (0 < do_the_thing())
> ; /* success /
> 

> just fine, instead of the boolean "0 is success, negative values are
> various errors",
> 

> if (!do_the_thing())
> ; / success */
> 

> when it does not care how/why the thing failed.

That makes sense. I can update the function to return -1 on error
and the strbuf.len on success.
diff mbox series

Patch

diff --git a/worktree.c b/worktree.c
index ec95ea2986107b3bc12d38b0825d7c6e87402bc6..0cba0d6e6e9ad02ace04a0301104a04a07cbef65 100644
--- a/worktree.c
+++ b/worktree.c
@@ -642,10 +642,9 @@  static int is_main_worktree_path(const char *path)
  * be able to infer the gitdir by manually reading /path/to/worktree/.git,
  * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
  */
-static char *infer_backlink(const char *gitfile)
+static int infer_backlink(const char *gitfile, struct strbuf *inferred)
 {
 	struct strbuf actual = STRBUF_INIT;
-	struct strbuf inferred = STRBUF_INIT;
 	const char *id;
 
 	if (strbuf_read_file(&actual, gitfile, 0) < 0)
@@ -658,17 +657,18 @@  static char *infer_backlink(const char *gitfile)
 	id++; /* advance past '/' to point at <id> */
 	if (!*id)
 		goto error;
-	strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
-	if (!is_directory(inferred.buf))
+	strbuf_reset(inferred);
+	strbuf_git_common_path(inferred, the_repository, "worktrees/%s", id);
+	if (!is_directory(inferred->buf))
 		goto error;
 
 	strbuf_release(&actual);
-	return strbuf_detach(&inferred, NULL);
+	return 1;
 
 error:
 	strbuf_release(&actual);
-	strbuf_release(&inferred);
-	return NULL;
+	strbuf_reset(inferred); /* clear invalid path */
+	return 0;
 }
 
 /*
@@ -680,10 +680,11 @@  void repair_worktree_at_path(const char *path,
 {
 	struct strbuf dotgit = STRBUF_INIT;
 	struct strbuf realdotgit = STRBUF_INIT;
+	struct strbuf backlink = STRBUF_INIT;
+	struct strbuf inferred_backlink = STRBUF_INIT;
 	struct strbuf gitdir = STRBUF_INIT;
 	struct strbuf olddotgit = STRBUF_INIT;
-	char *backlink = NULL;
-	char *inferred_backlink = NULL;
+	char *dotgit_contents = NULL;
 	const char *repair = NULL;
 	int err;
 
@@ -699,23 +700,23 @@  void repair_worktree_at_path(const char *path,
 		goto done;
 	}
 
-	inferred_backlink = infer_backlink(realdotgit.buf);
-	backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
-	if (err == READ_GITFILE_ERR_NOT_A_FILE) {
+	infer_backlink(realdotgit.buf, &inferred_backlink);
+	dotgit_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
+	if (dotgit_contents) {
+		strbuf_addstr(&backlink, dotgit_contents);
+	} else if (err == READ_GITFILE_ERR_NOT_A_FILE) {
 		fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
 		goto done;
 	} else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
-		if (inferred_backlink) {
+		if (inferred_backlink.len) {
 			/*
 			 * Worktree's .git file does not point at a repository
 			 * but we found a .git/worktrees/<id> in this
 			 * repository with the same <id> as recorded in the
 			 * worktree's .git file so make the worktree point at
-			 * the discovered .git/worktrees/<id>. (Note: backlink
-			 * is already NULL, so no need to free it first.)
+			 * the discovered .git/worktrees/<id>.
 			 */
-			backlink = inferred_backlink;
-			inferred_backlink = NULL;
+			strbuf_swap(&backlink, &inferred_backlink);
 		} else {
 			fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
 			goto done;
@@ -743,13 +744,11 @@  void repair_worktree_at_path(const char *path,
 	 * in the "copy" repository. In this case, point the "copy" worktree's
 	 * .git file at the "copy" repository.
 	 */
-	if (inferred_backlink && fspathcmp(backlink, inferred_backlink)) {
-		free(backlink);
-		backlink = inferred_backlink;
-		inferred_backlink = NULL;
+	if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) {
+		strbuf_swap(&backlink, &inferred_backlink);
 	}
 
-	strbuf_addf(&gitdir, "%s/gitdir", backlink);
+	strbuf_addf(&gitdir, "%s/gitdir", backlink.buf);
 	if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
 		repair = _("gitdir unreadable");
 	else {
@@ -763,9 +762,10 @@  void repair_worktree_at_path(const char *path,
 		write_file(gitdir.buf, "%s", realdotgit.buf);
 	}
 done:
-	free(backlink);
-	free(inferred_backlink);
+	free(dotgit_contents);
 	strbuf_release(&olddotgit);
+	strbuf_release(&backlink);
+	strbuf_release(&inferred_backlink);
 	strbuf_release(&gitdir);
 	strbuf_release(&realdotgit);
 	strbuf_release(&dotgit);