From patchwork Sun Oct 6 06:00:57 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Caleb White X-Patchwork-Id: 13823680 Received: from mail-40131.protonmail.ch (mail-40131.protonmail.ch [185.70.40.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 054E6163A9B for ; Sun, 6 Oct 2024 06:01:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.40.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194465; cv=none; b=R2HdUaePGAZ2NijdkNFcEaiandFbxWSycbCdbn3qKNqSWAKECMxHegQ+y8ZsLN51GQ4zKoMzDjWydgRhovwqWePhr3mCD6xszwS+II0dKieD+E1EinsuM63oT/M/WCx50+VaZPuR8a3++yAdqNu29AzEJxPjR+rUfm+2JvkbKNQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194465; c=relaxed/simple; bh=yncxUMvM1boNXQ72pQuqOxrJnH/46jpzD2MtAAEPkY8=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=C9fJc65xiTpzxIr6l+XIQT2oUdMnrksKlo3/ZOIyolurr4/h1iqSGukHCDXEOCWzEpclab80W5v+rLbyMoZ9GA0RLUeR5Z/4aaOq+bU2ADpAzqsxsaBUxDGRFVCdzo0wEmIH9o3pxtmSZYEPgaLeUG0ELr8SjXXhMsm1k+1tLog= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me; spf=pass smtp.mailfrom=pm.me; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b=oEFwsbgT; arc=none smtp.client-ip=185.70.40.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pm.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b="oEFwsbgT" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail3; t=1728194462; x=1728453662; bh=MjkHgnxPKozAVrN1/nWuPp5mTSkkvWOxBWlf4QYeeNI=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=oEFwsbgTNw9F90WDVe2GMEu5u3ti5CZlSifal/kQPdYfig5blNQf1nQT3CioWze6K M662GKFSIveUU21MafXvZQhTIRVmP/b4WsijQJ9pdHD852O5cVw/F0k49b/kjZTOZ0 H7xJYiVFH278Sp5zsHttHEG2XayMWXfO6g2iMQyDILUEOxo3+MEMYqAkhRZuk35PNG f0kvE/bKDB8u8eEYYcIJ6g7Lx+FdPINKQTQVVeZxxiCnpT007k+qD9f/tzBK9SMxDp pQsz6sPYeKBfa6DqzOkPbOkQ4UeilRZcqfrydNXVaQ1cu/Zsm4tgGb828fz/2OrQcO bbcs46vf1CPew== Date: Sun, 06 Oct 2024 06:00:57 +0000 To: git@vger.kernel.org From: Caleb White Cc: Caleb White Subject: [PATCH v2 1/4] worktree: refactor infer_backlink() to use *strbuf Message-ID: <20241006060017.171788-2-cdwhite3@pm.me> In-Reply-To: <20241006060017.171788-1-cdwhite3@pm.me> References: <20241006060017.171788-1-cdwhite3@pm.me> Feedback-ID: 31210263:user:proton X-Pm-Message-ID: a4be7a95a4e1e4f6fea8b43dcb2c53bd32a21fc2 Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This refactors the `infer_backlink` function to return an integer result and use a pre-allocated `strbuf` for the inferred backlink path, replacing the previous `char*` return type. This lays the groundwork for the next patch, which needs the resultant backlink as a `strbuf`. There was no need to go from `strbuf -> char* -> strbuf` again. This change also aligns the function's signature with other `strbuf`-based functions. Signed-off-by: Caleb White --- worktree.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) if (strbuf_read_file(&actual, gitfile, 0) < 0) @@ -658,17 +657,16 @@ static char *infer_backlink(const char *gitfile) id++; /* advance past '/' to point at */ if (!*id) goto error; - strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id); - if (!is_directory(inferred.buf)) + 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 0; error: strbuf_release(&actual); - strbuf_release(&inferred); - return NULL; + return 1; } /* @@ -680,9 +678,10 @@ 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 gitd ir = STRBUF_INIT; struct strbuf olddotgit = STRBUF_INIT; - char *backlink = NULL; + char *git_contents = NULL; const char *repair = NULL; int err; @@ -698,21 +697,23 @@ void repair_worktree_at_path(const char *path, goto done; } - backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err)); + git_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err)); 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 (!(backlink = infer_backlink(realdotgit.buf))) { + if (infer_backlink(&backlink, realdotgit.buf)) { fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data); goto done; } } else if (err) { fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data); goto done; + } else if (git_conte nts) { + strbuf_addstr(&backlink, git_contents); } - 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 { @@ -726,8 +727,9 @@ void repair_worktree_at_path(const char *path, write_file(gitdir.buf, "%s", realdotgit.buf); } done: - free(backlink); + free(git_contents); strbuf_release(&olddotgit); + strbuf_release(&backlink); strbuf_release(&gitdir); strbuf_release(&realdotgit); strbuf_release(&dotgit); diff --git a/worktree.c b/worktree.c index 0f032cc..c6d2ede 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 , and checking if /worktrees/ exists. */ -static char *infer_backlink(const char *gitfile) +static int infer_backlink(st ruct strbuf *inferred, const char *gitfile) { struct strbuf actual = STRBUF_INIT; - struct strbuf inferred = STRBUF_INIT; const char *id; From patchwork Sun Oct 6 06:01:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Caleb White X-Patchwork-Id: 13823681 Received: from mail-4316.protonmail.ch (mail-4316.protonmail.ch [185.70.43.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C536814A4D6 for ; Sun, 6 Oct 2024 06:01:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.43.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194485; cv=none; b=Rzi/cRxwODy8CiT+mSY04nzMUYPXJnVL8k2CRZxS7hiLChl+lb3xxYh9vrC5zkvv9j07uXiZxn/6WQQ1gADiRY8QdAYdwe8m0P4biXYgceHtzu4hhKl2JyKRj8FGLFdk28gfyR3fWDBg/RRMF+o+gctgrHcGZyyQMbtMQneVm8w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194485; c=relaxed/simple; bh=1d7N+6nC/0WYCVjUmbAWzui8OPL5H26yoiPov/z26H8=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=pF0vMQdLSf0hVvVmd+qryo2nZKeuL1JAOje0CrpKicKu04US+l9lS6+jPg9OkbIeUoibtsFNecFdhH0jVMtAA+lsg/ItMOcoNK+mL3G/8dqCyg3D5/aspWf5NVfNpQr2khzqpdEcEX5yKCVRgrzvKtfezfLVLtN/NZLlJ1UF/eo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me; spf=pass smtp.mailfrom=pm.me; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b=KUua4TKJ; arc=none smtp.client-ip=185.70.43.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pm.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b="KUua4TKJ" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail3; t=1728194481; x=1728453681; bh=wNu0mRfCyvi38NIoStDMXCEzRnCBm6c/H0qr59feXck=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=KUua4TKJaYRbJ6W84ev/+COjup3Akl/3+zjzrrstYckn42Oc1QinimNh7k+UhQEK9 scVv26eEIpt16tJNsqs4WJ2ibhXw21I7fn4d535nes/aat7edWGN9X9O6k499Apop1 6O4LLn0nIuO/Hn8QQwGP0DbCuG2pS57i3wp+mNYEMsBCelj6R/wyaMgDCZQKDkYJf5 72ojre14q3DU3r708tw0ux7mmjqUVn6fnLkxV7klGoDBMjJwZh97DvmX+16y4hEjd4 4FTSTArz4BE+JGhHlLmJpnze1IyGea4hWEcKGJTovM3HiDmeDfDHGHoQZ0zZKkLX8Q N9nRV3Fr6ZB5Q== Date: Sun, 06 Oct 2024 06:01:17 +0000 To: git@vger.kernel.org From: Caleb White Cc: Caleb White Subject: [PATCH v2 2/4] worktree: link worktrees with relative paths Message-ID: <20241006060017.171788-3-cdwhite3@pm.me> In-Reply-To: <20241006060017.171788-1-cdwhite3@pm.me> References: <20241006060017.171788-1-cdwhite3@pm.me> Feedback-ID: 31210263:user:proton X-Pm-Message-ID: 8b501fcc67c5500084019ec27a07465653414354 Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This modifies Git’s handling of worktree linking to use relative paths instead of absolute paths. Previously, when creating a worktree, Git would store the absolute paths to both the main repository and the linked worktrees. These hardcoded absolute paths cause breakages such as when the repository is moved to a different directory or during containerized development where the absolute differs between systems. By switching to relative paths, we help ensure that worktree links are more resilient when the repository is moved. While links external to the repository may still break, Git still automatically handles many common scenarios, reducing the need for manual repair. This is particularly useful in containerized or portable development environments, where the absolute path to the repository can differ between systems. Developers no longer need to reinitialize or repair worktrees after relocating the repository, improving workflow efficiency and reducing breakages. For self-contained repositories (such as using a bare repository with worktrees), where both the repository and its worktrees are located within the same directory structure, using relative paths guarantees all links remain functional regardless of where the directory is located. Signed-off-by: Caleb White --- builtin/worktree.c | 17 ++-- t/t2408-worktree-relative.sh | 39 +++++++++ worktree.c | 152 +++++++++++++++++++++++++---------- 3 files changed, 159 insertions(+), 49 deletions(-) create mode 100755 t/t2408-worktree-relative.sh diff --git a/builtin/worktree.c b/builtin/worktree.c index fc31d07..99cee56 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -414,7 +414,8 @@ static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; - struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT; + struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT; const char *name; struct strvec child_env = STRVEC_INIT; unsigned int counter = 0; @@ -490,11 +491,11 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - strbuf_realpath(&realpath, sb_git.buf, 1); - write_file(sb.buf, "%s", realpath.buf); - strbuf_realpath(&realpath, repo_get_common_dir(the_repository), 1); - write_file(sb_git.buf, "gitdir: %s/worktrees/%s", - realpath.buf, name); + strbuf_realpath(&sb_path_realpath, path, 1); + strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1); + write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp)); + strbuf_reset(&sb_tmp); + write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp)); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); @@ -578,11 +579,13 @@ static int add_worktree(const char *path, const char *refname, strvec_clear(&child_env); strbuf_release(&sb); + strbuf_release(&sb_tmp); strbuf_release(&symref); strbuf_release(&sb_repo); + strbuf_release(&sb_repo_realpath); strbuf_release(&sb_git); + strbuf_release(&sb_path_realpath); strbuf_release(&sb_name); - strbuf_release(&realpath); free_worktree(wt); return ret; } diff --git a/t/t2408-worktree-relative.sh b/t/t2408-worktree-relative.sh new file mode 100755 index 0000000..a3136db --- /dev/null +++ b/t/t2408-worktree-relative.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +test_description='test worktrees linked with relative paths' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'links worktrees with relative paths' ' + test_when_finished rm -rf repo && + git init repo && + ( + cd repo && + test_commit initial && + git worktree add wt1 && + echo "../../../wt1/.git" >expected_gitdir && + cat .git/worktrees/wt1/gitdir >actual_gitdir && + echo "gitdir: ../.git/worktrees/wt1" >expected_git && + cat wt1/.git >actual_git && + test_cmp expected_gitdir actual_gitdir && + test_cmp expected_git actual_git + ) +' + +test_expect_success 'move repo without breaking relative internal links' ' + test_when_finished rm -rf repo moved && + git init repo && + ( + cd repo && + test_commit initial && + git worktree add wt1 && + cd .. && + mv repo moved && + cd moved/wt1 && + git status >out 2>err && + test_must_be_empty err + ) +' + +test_done diff --git a/worktree.c b/worktree.c index c6d2ede..fc14e9a 100644 --- a/worktree.c +++ b/worktree.c @@ -110,6 +110,12 @@ struct worktree *get_linked_worktree(const char *id, strbuf_rtrim(&worktree_path); strbuf_strip_suffix(&worktree_path, "/.git"); + if (!is_absolute_path(worktree_path.buf)) { + strbuf_strip_suffix(&path, "gitdir"); + strbuf_addbuf(&path, &worktree_path); + strbuf_realpath_forgiving(&worktree_path, path.buf, 0); + } + CALLOC_ARRAY(worktree, 1); worktree->repo = the_repository; worktree->path = strbuf_detach(&worktree_path, NULL); @@ -373,18 +379,30 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg, void update_worktree_location(struct worktree *wt, const char *path_) { struct strbuf path = STRBUF_INIT; + struct strbuf repo = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + char *file = NULL; if (is_main_worktree(wt)) BUG("can't relocate main worktree"); + strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); strbuf_realpath(&path, path_, 1); if (fspathcmp(wt->path, path.buf)) { - write_file(git_common_path("worktrees/%s/gitdir", wt->id), - "%s/.git", path.buf); + file = xstrfmt("%s/gitdir", repo.buf); + write_file(file, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); + free(file); + strbuf_reset(&tmp); + file = xstrfmt("%s/.git", path.buf); + write_file(file, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); + free(wt->path); wt->path = strbuf_detach(&path, NULL); } + free(file); strbuf_release(&path); + strbuf_release(&repo); + strbuf_release(&tmp); } int is_worktree_being_rebased(const struct worktree *wt, @@ -564,38 +582,52 @@ static void repair_gitfile(struct worktree *wt, { struct strbuf dotgit = STRBUF_INIT; struct strbuf repo = STRBUF_INIT; - char *backlink; + struct strbuf backlink = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + char *git_contents = NULL; const char *repair = NULL; int err; /* missing worktree can't be repaired */ if (!file_exists(wt->path)) - return; + goto done; if (!is_directory(wt->path)) { fn(1, wt->path, _("not a directory"), cb_data); - return; + goto done; } strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); strbuf_addf(&dotgit, "%s/.git", wt->path); - backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); + git_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); + + if (git_contents) { + if (is_absolute_path(git_contents)) { + strbuf_addstr(&backlink, git_contents); + } else { + strbuf_addf(&backlink, "%s/%s", wt->path, git_contents); + strbuf_realpath_forgiving(&backlink, backlink.buf, 0); + } + } if (err == READ_GITFILE_ERR_NOT_A_FILE) fn(1, wt->path, _(".git is not a file"), cb_data); else if (err) repair = _(".git file broken"); - else if (fspathcmp(backlink, repo.buf)) + else if (fspathcmp(backlink.buf, repo.buf)) repair = _(".git file incorrect"); if (repair) { fn(0, wt->path, repair, cb_data); - write_file(dotgit.buf, "gitdir: %s", repo.buf); + write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, wt->path, &tmp)); } - free(backlink); +done: + free(git_contents); strbuf_release(&repo); strbuf_release(&dotgit); + strbuf_release(&backlink); + strbuf_release(&tmp); } static void repair_noop(int iserr UNUSED, @@ -681,6 +713,8 @@ void repair_worktree_at_path(const char *path, struct strbuf backlink = STRBUF_INIT; struct strbuf gitdir = STRBUF_INIT; struct strbuf olddotgit = STRBUF_INIT; + struct strbuf realolddotgit = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; char *git_contents = NULL; const char *repair = NULL; int err; @@ -710,97 +744,131 @@ void repair_worktree_at_path(const char *path, fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data); goto done; } else if (git_contents) { - strbuf_addstr(&backlink, git_contents); + if (is_absolute_path(git_contents)) { + strbuf_addstr(&backlink, git_contents); + } else { + strbuf_addbuf(&backlink, &realdotgit); + strbuf_strip_suffix(&backlink, ".git"); + strbuf_addstr(&backlink, git_contents); + } } + strbuf_realpath_forgiving(&backlink, backlink.buf, 0); strbuf_addf(&gitdir, "%s/gitdir", backlink.buf); if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) repair = _("gitdir unreadable"); else { strbuf_rtrim(&olddotgit); - if (fspathcmp(olddotgit.buf, realdotgit.buf)) + if (is_absolute_path(olddotgit.buf)) { + strbuf_addbuf(&realolddotgit, &olddotgit); + } else { + strbuf_addf(&realolddotgit, "%s/%s", backlink.buf, olddotgit.buf); + strbuf_realpath_forgiving(&realolddotgit, realolddotgit.buf, 0); + } + if (fspathcmp(realolddotgit.buf, realdotgit.buf)) repair = _("gitdir incorrect"); } if (repair) { fn(0, gitdir.buf, repair, cb_data); - write_file(gitdir.buf, "%s", realdotgit.buf); + write_file(gitdir.buf, "%s", relative_path(realdotgit.buf, backlink.buf, &tmp)); } done: free(git_contents); strbuf_release(&olddotgit); + strbuf_release(&realolddotgit); strbuf_release(&backlink); strbuf_release(&gitdir); strbuf_release(&realdotgit); strbuf_release(&dotgit); + strbuf_release(&tmp); } int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire) { struct stat st; - char *path; + struct strbuf dotgit = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; + struct strbuf repo = STRBUF_INIT; + char *path = NULL; + char *file = NULL; + int rc = 0; int fd; size_t len; ssize_t read_result; *wtpath = NULL; - if (!is_directory(git_path("worktrees/%s", id))) { + strbuf_realpath(&repo, git_common_path("worktrees/%s", id), 1); + strbuf_addf(&gitdir, "%s/gitdir", repo.buf); + if (!is_directory(repo.buf)) { strbuf_addstr(reason, _("not a valid directory")); - return 1; + rc = 1; + goto done; } - if (file_exists(git_path("worktrees/%s/locked", id))) - return 0; - if (stat(git_path("worktrees/%s/gitdir", id), &st)) { + file = xstrfmt("%s/locked", repo.buf); + if (file_exists(file)) { + goto done; + } + if (stat(gitdir.buf, &st)) { strbuf_addstr(reason, _("gitdir file does not exist")); - return 1; + rc = 1; + goto done; } - fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); + fd = open(gitdir.buf, O_RDONLY); if (fd < 0) { strbuf_addf(reason, _("unable to read gitdir file (%s)"), strerror(errno)); - return 1; + rc = 1; + goto done; } len = xsize_t(st.st_size); path = xmallocz(len); read_result = read_in_full(fd, path, len); + close(fd); if (read_result < 0) { strbuf_addf(reason, _("unable to read gitdir file (%s)"), strerror(errno)); - close(fd); - free(path); - return 1; - } - close(fd); - - if (read_result != len) { + rc = 1; + goto done; + } else if (read_result != len) { strbuf_addf(reason, _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), (uintmax_t)len, (uintmax_t)read_result); - free(path); - return 1; + rc = 1; + goto done; } while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) len--; if (!len) { strbuf_addstr(reason, _("invalid gitdir file")); - free(path); - return 1; + rc = 1; + goto done; } path[len] = '\0'; - if (!file_exists(path)) { - if (stat(git_path("worktrees/%s/index", id), &st) || - st.st_mtime <= expire) { + if (is_absolute_path(path)) { + strbuf_addstr(&dotgit, path); + } else { + strbuf_addf(&dotgit, "%s/%s", repo.buf, path); + strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0); + } + if (!file_exists(dotgit.buf)) { + free(file); + file = xstrfmt("%s/index", repo.buf); + if (stat(file, &st) || st.st_mtime <= expire) { strbuf_addstr(reason, _("gitdir file points to non-existent location")); - free(path); - return 1; - } else { - *wtpath = path; - return 0; + rc = 1; + goto done; } } - *wtpath = path; - return 0; + *wtpath = strbuf_detach(&dotgit, NULL); +done: + free(path); + free(file); + strbuf_release(&dotgit); + strbuf_release(&gitdir); + strbuf_release(&repo); + return rc; } static int move_config_setting(const char *key, const char *value, From patchwork Sun Oct 6 06:01:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Caleb White X-Patchwork-Id: 13823682 Received: from mail-40131.protonmail.ch (mail-40131.protonmail.ch [185.70.40.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BB3D24A23 for ; Sun, 6 Oct 2024 06:01:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.40.131 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194498; cv=none; b=KBTRYi0qyCKEqqDUn3i4qzfFfBFipJs8tFHcbEmUmPXih2fM6IHOvm9R4aZ0+kYpka+Nb9vGm87k5I74rYEpTLJ8lIwb9AtgMiJaheUe24K7KKxmGAi6FwTxxx+sciuMcuwzPzkUnaD7xMbQHmJ/mCPPq6Y9Vf5HsaRutrUbaMA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194498; c=relaxed/simple; bh=fX8ivhZfe+voBSZmwwoX6PCUzJn04KDnuB7lHlrWcT0=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=eJsmjV62u2PsBOPzyKzypFvoYnu1S1toAnUF62WyKBkGXg2Q3SMI2BJdeWre35Lgs2ICy7G/o9IqZFRZiTiEApgBFME+0gjgFCsgBxP8z/n8hHQMu+w22Z2PklAHzrDNr3BunaNyczt1KFXmUQVrLitnjBsDLtVIK05I8b5lD5w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me; spf=pass smtp.mailfrom=pm.me; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b=Qc7BdUQm; arc=none smtp.client-ip=185.70.40.131 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pm.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b="Qc7BdUQm" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail3; t=1728194490; x=1728453690; bh=ZXY4pROrQnDAV+Vu0m601bbwlw/upTrHPMxMCouzSl4=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=Qc7BdUQmxGChjMcwCP6RteHfZrfqQj9fcraKnkSXrwLl5ozfylza4aP56vS9FMUGV 3DOzCKlqAmMgXPSwcPg+lgvHWcMKYMyFDdlhlFIfhEZi0dJ5vd8PQSyKF9qvxUGQe+ EEPi+KIopuD8jyw5iwYEnkB73AuLpE42CX1y6cK70c5aRxVWq39U2meh5ySiWbShkE o03jrU2WPOs1nXVL0h5lVkoBaMHW4NF+jK3la2GmE/vgrCV33Qc3018qe4NMDGnvaS lL/ynrw8ICN5vg7Fn/mqjC25Bo7ass43tYubeuFhwOuLgDvfzRaguUoZzszxxtcSPw fu4SwghxIUGBA== Date: Sun, 06 Oct 2024 06:01:27 +0000 To: git@vger.kernel.org From: Caleb White Cc: Caleb White Subject: [PATCH v2 3/4] worktree: sync worktree paths after gitdir move Message-ID: <20241006060017.171788-4-cdwhite3@pm.me> In-Reply-To: <20241006060017.171788-1-cdwhite3@pm.me> References: <20241006060017.171788-1-cdwhite3@pm.me> Feedback-ID: 31210263:user:proton X-Pm-Message-ID: b70ef24bbff843f8e9063fb9268dfe8f0a11ca32 Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 When re-initializing a repository with a separate gitdir (the original gitdir is moved to a new location), any linked worktrees become broken and must be repaired to reflect the new gitdir location. For absolute paths, this breakage is one-way, but is both ways for relative paths (the `/.git` and the `/worktrees//gitdir`). Previously, `repair_worktrees` was being called which loops through all the worktrees in the repository and updates the `/.git` files to point to the new gitdir. However, when both sides of the worktrees are broken, the previous gitdir location is required to reestablish the link. To fix this, the function `repair_worktrees_after_gitdir_move` is introduced. It takes the old gitdir path as an argument and repairs both sides of the worktree. This change fixes the following test cases in t0001-init.sh: - re-init to move gitdir with linked worktrees - re-init to move gitdir within linked worktree Signed-off-by: Caleb White --- setup.c | 2 +- worktree.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ worktree.h | 10 ++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) * The worktree's .git file pointing at the repository must be intact for the diff --git a/setup.c b/setup.c index 94e79b2..7b648de 100644 --- a/setup.c +++ b/setup.c @@ -2420,7 +2420,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link) if (rename(src, git_dir)) die_errno(_("unable to move %s to %s"), src, git_dir); - repair_worktrees(NULL, NULL); + repair_worktrees_after_gitdir_move(src); } write_file(git_link, "gitdir: %s", git_dir); diff --git a/worktree.c b/worktree.c index fc14e9a..b08ecce 100644 --- a/worktree.c +++ b/worktree.c @@ -650,6 +650,60 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data) free_worktrees(worktrees); } +void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path) +{ + struct strbuf path = STRBUF_INIT; + struct strbuf repo = STRBUF_ INIT; + struct strbuf gitdir = STRBUF_INIT; + struct strbuf dotgit = STRBUF_INIT; + struct strbuf olddotgit = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + + if (is_main_worktree(wt)) + goto done; + + strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); + strbuf_addf(&gitdir, "%s/gitdir", repo.buf); + + if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) + goto done; + + strbuf_rtrim(&olddotgit); + if (is_absolute_path(olddotgit.buf)) { + strbuf_addbuf(&dotgit, &olddotgit); + } else { + strbuf_addf(&dotgit, "%s/worktrees/%s/%s", old_path, wt->id, olddotgit.buf); + strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0); + } + + if (!file_exists(dotgit.buf)) + goto done; + + strbuf_addbuf(&path, &dotgit); + strbuf_strip_suffix(&path, "/.git"); + + write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); + strbuf_reset(&tmp); + write_file(gitdir.buf, "%s", relative_path(dotgit.buf, repo.buf, &tmp)); +done: + strbu f_release(&path); + strbuf_release(&repo); + strbuf_release(&gitdir); + strbuf_release(&dotgit); + strbuf_release(&olddotgit); + strbuf_release(&tmp); +} + +void repair_worktrees_after_gitdir_move(const char *old_path) +{ + struct worktree **worktrees = get_worktrees_internal(1); + struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ + + for (; *wt; wt++) + repair_worktree_after_gitdir_move(*wt, old_path); + free_worktrees(worktrees); +} + static int is_main_worktree_path(const char *path) { struct strbuf target = STRBUF_INIT; diff --git a/worktree.h b/worktree.h index 11279d0..e961186 100644 --- a/worktree.h +++ b/worktree.h @@ -131,6 +131,16 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path, */ void repair_worktrees(worktree_repair_fn, void *cb_data); +/* + * Repair the linked worktrees after the gitdir has been moved. + */ +void repair_worktrees_after_gitdir_move(const char *old_path); + +/* + * Repair the l inked worktree after the gitdir has been moved. + */ +void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path); + /* * Repair administrative files corresponding to the worktree at the given path. From patchwork Sun Oct 6 06:01:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Caleb White X-Patchwork-Id: 13823683 Received: from mail-4316.protonmail.ch (mail-4316.protonmail.ch [185.70.43.16]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0BD9716B75C for ; Sun, 6 Oct 2024 06:01:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.70.43.16 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194502; cv=none; b=TWsaxWSwaEQdgyCS6cqPwUT38LMA/3pBEe6TsF1blIa4TLi5S/p8U+XZEeV4JbyiCp9IKzMZ4/gDdp7GpFsNVhXwEQBuYbtpce2acuCWkr4NOmVd8SElcOVoGhJ4o/Utj/LLedCLurVAYJyda0HH7maKDz2SbwHt1MQ2zQH90pg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1728194502; c=relaxed/simple; bh=hmxG7654v9pL/lC/2kRtpa2/HmKluFMZkNq5wJRpDi8=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=phHGA81T1owSGlB22M2jigxeNp14gHeUn31ZniGXfdqYcfft3AnABdLQwNSyFzlLTcx+lZm0C14fydetkuBNIB7/FbNzw0BwdcrHWzanMDPLOq3chotp9tAFIcEET4DzzWBLrx2YHr4t/3cmxjTzEjG0LQYc4ShtRs9BgyOoanw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me; spf=pass smtp.mailfrom=pm.me; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b=nFE+e5YC; arc=none smtp.client-ip=185.70.43.16 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=pm.me Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pm.me Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=pm.me header.i=@pm.me header.b="nFE+e5YC" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=pm.me; s=protonmail3; t=1728194499; x=1728453699; bh=eJAYFAmyEddl+GzJ3o1Du0u3FEXKNwKwbZkbjU99GNk=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=nFE+e5YCHJMxBVF40MFrBa3YfCLgq4R2e+qcsrAQnks8TgEhs26Oq6cPUdfBPoXYk jFTO7sV5L3d+M9VhZTOYVcwDJVPBxj2nvxzMF8ymFMuPhkfkFjLU6vldBqw8+mpuPb xpkqsCcptY5mKKSdVzgrO+P1dazO2zPHtEQ0bncTOTbVWQIIR4V01EXUR3ZfDZYC9D YHC2ZAeyEcJDe3R0mmI+WahsAFZRPJJHGOvaMOkEYrw+0nSwul6Bb05+BFG3T9fFlC oPBCpPa2hDy16pZbeCbE/TVAGRXHrwOM47xd77j3W+ocv9n1JUw/WrKGtXseytjHnK PymarbpeGL3SQ== Date: Sun, 06 Oct 2024 06:01:35 +0000 To: git@vger.kernel.org From: Caleb White Cc: Caleb White Subject: [PATCH v2 4/4] worktree: prevent null pointer dereference Message-ID: <20241006060017.171788-5-cdwhite3@pm.me> In-Reply-To: <20241006060017.171788-1-cdwhite3@pm.me> References: <20241006060017.171788-1-cdwhite3@pm.me> Feedback-ID: 31210263:user:proton X-Pm-Message-ID: b785527e36168ce301cd77bddf838212ba32ae9f Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 If worktrees is NULL, free_worktrees() should return immediately to prevent a null pointer dereference. Signed-off-by: Caleb White --- worktree.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/worktree.c b/worktree.c index b08ecce..1cf15b0 100644 --- a/worktree.c +++ b/worktree.c @@ -28,8 +28,9 @@ void free_worktree(struct worktree *worktree) void free_worktrees(struct worktree **worktrees) { - int i = 0; - for (i = 0; worktrees[i]; i++) + if (!worktrees) + return; + for (int i = 0; worktrees[i]; i++) free_worktree(worktrees[i]); free (worktrees); }