mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-05-30 06:29:52 +00:00
init: Add --use-local-gitdirs for standard Git layouts
Introduce --use-local-gitdirs to bypass repo's symlink-based layouts in favor of standard local .git directories. Bug: 513329573 Bug: 508146070 Change-Id: I53d1602e61be0b86964529bcbea3dc801471f9c9 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/569001 Tested-by: Gavin Mak <gavinmak@google.com> Commit-Queue: Gavin Mak <gavinmak@google.com> Reviewed-by: Dan Willemsen <dwillemsen@google.com>
This commit is contained in:
committed by
gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com
parent
2d54384a5e
commit
b8531133de
+5
-1
@@ -1,5 +1,5 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||
.TH REPO "1" "September 2024" "repo init" "Repo Manual"
|
||||
.TH REPO "1" "April 2026" "repo init" "Repo Manual"
|
||||
.SH NAME
|
||||
repo \- repo init - manual page for repo init
|
||||
.SH SYNOPSIS
|
||||
@@ -80,6 +80,10 @@ each project. See git archive.
|
||||
.TP
|
||||
\fB\-\-worktree\fR
|
||||
use git\-worktree to manage projects
|
||||
.TP
|
||||
\fB\-\-use\-local\-gitdirs\fR
|
||||
bypass .repo/projects/ and use standard Git layout in
|
||||
working tree
|
||||
.SS Project checkout optimizations:
|
||||
.TP
|
||||
\fB\-\-reference\fR=\fI\,DIR\/\fR
|
||||
|
||||
+18
-8
@@ -1055,6 +1055,10 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
def UseGitWorktrees(self):
|
||||
return self.manifestProject.use_worktree
|
||||
|
||||
@property
|
||||
def UseLocalGitDirs(self):
|
||||
return self.manifestProject.use_local_gitdirs
|
||||
|
||||
@property
|
||||
def IsArchive(self):
|
||||
return self.manifestProject.archive
|
||||
@@ -2042,15 +2046,21 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
||||
else:
|
||||
namepath = f"{name}.git"
|
||||
worktree = os.path.join(self.topdir, path).replace("\\", "/")
|
||||
gitdir = os.path.join(self.subdir, "projects", "%s.git" % path)
|
||||
# We allow people to mix git worktrees & non-git worktrees for now.
|
||||
# This allows for in situ migration of repo clients.
|
||||
if os.path.exists(gitdir) or not self.UseGitWorktrees:
|
||||
objdir = os.path.join(self.repodir, "project-objects", namepath)
|
||||
else:
|
||||
use_git_worktrees = True
|
||||
gitdir = os.path.join(self.repodir, "worktrees", namepath)
|
||||
if self.UseLocalGitDirs:
|
||||
gitdir = os.path.join(worktree, ".git")
|
||||
objdir = gitdir
|
||||
else:
|
||||
gitdir = os.path.join(self.subdir, "projects", "%s.git" % path)
|
||||
# We allow people to mix git worktrees & non-git worktrees for
|
||||
# now. This allows for in situ migration of repo clients.
|
||||
if os.path.exists(gitdir) or not self.UseGitWorktrees:
|
||||
objdir = os.path.join(
|
||||
self.repodir, "project-objects", namepath
|
||||
)
|
||||
else:
|
||||
use_git_worktrees = True
|
||||
gitdir = os.path.join(self.repodir, "worktrees", namepath)
|
||||
objdir = gitdir
|
||||
return relpath, worktree, gitdir, objdir, use_git_worktrees
|
||||
|
||||
def GetProjectsWithName(self, name, all_manifests=False):
|
||||
|
||||
+44
-1
@@ -3686,7 +3686,11 @@ class Project:
|
||||
self._MigrateOldSubmoduleDir()
|
||||
|
||||
# If using an old layout style (a directory), migrate it.
|
||||
if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
|
||||
if (
|
||||
not platform_utils.islink(dotgit)
|
||||
and platform_utils.isdir(dotgit)
|
||||
and not self.manifest.UseLocalGitDirs
|
||||
):
|
||||
self._MigrateOldWorkTreeGitDir(dotgit, project=self.name)
|
||||
|
||||
init_dotgit = not os.path.lexists(dotgit)
|
||||
@@ -3735,6 +3739,11 @@ class Project:
|
||||
For submodule projects, create a '.git' file using the gitfile
|
||||
mechanism, and for the rest, create a symbolic link.
|
||||
"""
|
||||
if self.manifest.UseLocalGitDirs and os.path.normpath(
|
||||
self.gitdir
|
||||
) == os.path.normpath(dotgit):
|
||||
return
|
||||
|
||||
os.makedirs(self.worktree, exist_ok=True)
|
||||
if self.parent:
|
||||
_lwrite(
|
||||
@@ -4488,6 +4497,11 @@ class ManifestProject(MetaProject):
|
||||
"""Whether we use worktree."""
|
||||
return self.config.GetBoolean("repo.worktree")
|
||||
|
||||
@property
|
||||
def use_local_gitdirs(self):
|
||||
"""Whether we use local gitdirs."""
|
||||
return self.config.GetBoolean("repo.uselocalgitdirs")
|
||||
|
||||
@property
|
||||
def clone_bundle(self):
|
||||
"""Whether we use clone_bundle."""
|
||||
@@ -4642,6 +4656,7 @@ class ManifestProject(MetaProject):
|
||||
this_manifest_only=False,
|
||||
outer_manifest=True,
|
||||
clone_filter_for_depth=None,
|
||||
use_local_gitdirs=False,
|
||||
):
|
||||
"""Sync the manifest and all submanifests.
|
||||
|
||||
@@ -4872,6 +4887,34 @@ class ManifestProject(MetaProject):
|
||||
self.use_git_worktrees = True
|
||||
logger.warning("warning: --worktree is experimental!")
|
||||
|
||||
if use_local_gitdirs:
|
||||
if mirror:
|
||||
logger.error(
|
||||
"fatal: --mirror and --use-local-gitdirs are incompatible"
|
||||
)
|
||||
return False
|
||||
|
||||
if worktree:
|
||||
logger.error(
|
||||
"fatal: --worktree and --use-local-gitdirs are incompatible"
|
||||
)
|
||||
return False
|
||||
|
||||
if archive:
|
||||
logger.error(
|
||||
"fatal: --archive and --use-local-gitdirs are incompatible"
|
||||
)
|
||||
return False
|
||||
|
||||
if not is_new and not self.use_local_gitdirs:
|
||||
logger.error(
|
||||
"fatal: --use-local-gitdirs is only supported when "
|
||||
"initializing a new workspace."
|
||||
)
|
||||
return False
|
||||
|
||||
self.config.SetBoolean("repo.uselocalgitdirs", use_local_gitdirs)
|
||||
|
||||
if archive:
|
||||
if is_new:
|
||||
self.config.SetBoolean("repo.archive", archive)
|
||||
|
||||
@@ -379,6 +379,11 @@ def InitParser(parser):
|
||||
action="store_true",
|
||||
help="use git-worktree to manage projects",
|
||||
)
|
||||
group.add_option(
|
||||
"--use-local-gitdirs",
|
||||
action="store_true",
|
||||
help="bypass .repo/projects/ and use standard Git layout in working tree",
|
||||
)
|
||||
|
||||
# These are fundamentally different ways of structuring the checkout.
|
||||
group = parser.add_option_group("Project checkout optimizations")
|
||||
|
||||
@@ -169,6 +169,7 @@ to update the working directory files.
|
||||
depth=opt.depth,
|
||||
git_event_log=self.git_event_log,
|
||||
manifest_name=opt.manifest_name,
|
||||
use_local_gitdirs=opt.use_local_gitdirs,
|
||||
):
|
||||
manifest_name = opt.manifest_name
|
||||
raise UpdateManifestError(
|
||||
|
||||
@@ -819,6 +819,26 @@ class TestProjectElement:
|
||||
str(repo_client.topdir), ".repo", "projects", "..git"
|
||||
)
|
||||
|
||||
def test_get_project_paths_local_gitdirs(
|
||||
self, repo_client: RepoClient
|
||||
) -> None:
|
||||
"""Check GetProjectPaths with UseLocalGitDirs."""
|
||||
manifest = repo_client.get_xml_manifest(
|
||||
'<?xml version="1.0" encoding="UTF-8"?><manifest></manifest>'
|
||||
)
|
||||
manifest.manifestProject.config.SetBoolean("repo.uselocalgitdirs", True)
|
||||
|
||||
relpath, worktree, gitdir, objdir, use_git_worktrees = (
|
||||
manifest.GetProjectPaths("foo", "bar", "origin")
|
||||
)
|
||||
|
||||
assert os.path.normpath(gitdir) == os.path.normpath(
|
||||
os.path.join(str(repo_client.topdir), "bar", ".git")
|
||||
)
|
||||
assert os.path.normpath(objdir) == os.path.normpath(
|
||||
os.path.join(str(repo_client.topdir), "bar", ".git")
|
||||
)
|
||||
|
||||
def test_bad_path_name_checks(self, repo_client: RepoClient) -> None:
|
||||
"""Check handling of bad path & name attributes."""
|
||||
|
||||
|
||||
@@ -657,6 +657,9 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
|
||||
fakeproj.config.SetBoolean("repo.worktree", False)
|
||||
self.assertFalse(fakeproj.use_worktree)
|
||||
|
||||
fakeproj.config.SetBoolean("repo.uselocalgitdirs", False)
|
||||
self.assertFalse(fakeproj.use_local_gitdirs)
|
||||
|
||||
fakeproj.config.SetBoolean("repo.clonebundle", False)
|
||||
self.assertFalse(fakeproj.clone_bundle)
|
||||
|
||||
@@ -691,6 +694,54 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
|
||||
fakeproj.config.SetString("manifest.platform", "auto")
|
||||
self.assertEqual(fakeproj.manifest_platform, "auto")
|
||||
|
||||
def test_sync_use_local_gitdirs_worktree_conflict(self):
|
||||
"""Test that --use-local-gitdirs conflicts with --worktree."""
|
||||
with utils_for_test.TempGitTree() as tempdir:
|
||||
fakeproj = self.setUpManifest(tempdir)
|
||||
|
||||
class DummyManifest:
|
||||
is_submanifest = False
|
||||
|
||||
def GetDefaultGroupsStr(self, with_platform=False):
|
||||
return ""
|
||||
|
||||
fakeproj.manifest = DummyManifest()
|
||||
|
||||
result = fakeproj.Sync(use_local_gitdirs=True, worktree=True)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_sync_use_local_gitdirs_archive_conflict(self):
|
||||
"""Test that --use-local-gitdirs conflicts with --archive."""
|
||||
with utils_for_test.TempGitTree() as tempdir:
|
||||
fakeproj = self.setUpManifest(tempdir)
|
||||
|
||||
class DummyManifest:
|
||||
is_submanifest = False
|
||||
|
||||
def GetDefaultGroupsStr(self, with_platform=False):
|
||||
return ""
|
||||
|
||||
fakeproj.manifest = DummyManifest()
|
||||
|
||||
result = fakeproj.Sync(use_local_gitdirs=True, archive=True)
|
||||
self.assertFalse(result)
|
||||
|
||||
def test_sync_use_local_gitdirs_mirror_conflict(self):
|
||||
"""Test that --use-local-gitdirs conflicts with --mirror."""
|
||||
with utils_for_test.TempGitTree() as tempdir:
|
||||
fakeproj = self.setUpManifest(tempdir)
|
||||
|
||||
class DummyManifest:
|
||||
is_submanifest = False
|
||||
|
||||
def GetDefaultGroupsStr(self, with_platform=False):
|
||||
return ""
|
||||
|
||||
fakeproj.manifest = DummyManifest()
|
||||
|
||||
result = fakeproj.Sync(use_local_gitdirs=True, mirror=True)
|
||||
self.assertFalse(result)
|
||||
|
||||
|
||||
class StatelessSyncTests(unittest.TestCase):
|
||||
"""Tests for stateless sync strategy."""
|
||||
|
||||
Reference in New Issue
Block a user