mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-05-30 22:49:48 +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.
|
.\" 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
|
.SH NAME
|
||||||
repo \- repo init - manual page for repo init
|
repo \- repo init - manual page for repo init
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -80,6 +80,10 @@ each project. See git archive.
|
|||||||
.TP
|
.TP
|
||||||
\fB\-\-worktree\fR
|
\fB\-\-worktree\fR
|
||||||
use git\-worktree to manage projects
|
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:
|
.SS Project checkout optimizations:
|
||||||
.TP
|
.TP
|
||||||
\fB\-\-reference\fR=\fI\,DIR\/\fR
|
\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):
|
def UseGitWorktrees(self):
|
||||||
return self.manifestProject.use_worktree
|
return self.manifestProject.use_worktree
|
||||||
|
|
||||||
|
@property
|
||||||
|
def UseLocalGitDirs(self):
|
||||||
|
return self.manifestProject.use_local_gitdirs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def IsArchive(self):
|
def IsArchive(self):
|
||||||
return self.manifestProject.archive
|
return self.manifestProject.archive
|
||||||
@@ -2042,15 +2046,21 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
|
|||||||
else:
|
else:
|
||||||
namepath = f"{name}.git"
|
namepath = f"{name}.git"
|
||||||
worktree = os.path.join(self.topdir, path).replace("\\", "/")
|
worktree = os.path.join(self.topdir, path).replace("\\", "/")
|
||||||
gitdir = os.path.join(self.subdir, "projects", "%s.git" % path)
|
if self.UseLocalGitDirs:
|
||||||
# We allow people to mix git worktrees & non-git worktrees for now.
|
gitdir = os.path.join(worktree, ".git")
|
||||||
# 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
|
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
|
return relpath, worktree, gitdir, objdir, use_git_worktrees
|
||||||
|
|
||||||
def GetProjectsWithName(self, name, all_manifests=False):
|
def GetProjectsWithName(self, name, all_manifests=False):
|
||||||
|
|||||||
+44
-1
@@ -3686,7 +3686,11 @@ class Project:
|
|||||||
self._MigrateOldSubmoduleDir()
|
self._MigrateOldSubmoduleDir()
|
||||||
|
|
||||||
# If using an old layout style (a directory), migrate it.
|
# 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)
|
self._MigrateOldWorkTreeGitDir(dotgit, project=self.name)
|
||||||
|
|
||||||
init_dotgit = not os.path.lexists(dotgit)
|
init_dotgit = not os.path.lexists(dotgit)
|
||||||
@@ -3735,6 +3739,11 @@ class Project:
|
|||||||
For submodule projects, create a '.git' file using the gitfile
|
For submodule projects, create a '.git' file using the gitfile
|
||||||
mechanism, and for the rest, create a symbolic link.
|
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)
|
os.makedirs(self.worktree, exist_ok=True)
|
||||||
if self.parent:
|
if self.parent:
|
||||||
_lwrite(
|
_lwrite(
|
||||||
@@ -4488,6 +4497,11 @@ class ManifestProject(MetaProject):
|
|||||||
"""Whether we use worktree."""
|
"""Whether we use worktree."""
|
||||||
return self.config.GetBoolean("repo.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
|
@property
|
||||||
def clone_bundle(self):
|
def clone_bundle(self):
|
||||||
"""Whether we use clone_bundle."""
|
"""Whether we use clone_bundle."""
|
||||||
@@ -4642,6 +4656,7 @@ class ManifestProject(MetaProject):
|
|||||||
this_manifest_only=False,
|
this_manifest_only=False,
|
||||||
outer_manifest=True,
|
outer_manifest=True,
|
||||||
clone_filter_for_depth=None,
|
clone_filter_for_depth=None,
|
||||||
|
use_local_gitdirs=False,
|
||||||
):
|
):
|
||||||
"""Sync the manifest and all submanifests.
|
"""Sync the manifest and all submanifests.
|
||||||
|
|
||||||
@@ -4872,6 +4887,34 @@ class ManifestProject(MetaProject):
|
|||||||
self.use_git_worktrees = True
|
self.use_git_worktrees = True
|
||||||
logger.warning("warning: --worktree is experimental!")
|
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 archive:
|
||||||
if is_new:
|
if is_new:
|
||||||
self.config.SetBoolean("repo.archive", archive)
|
self.config.SetBoolean("repo.archive", archive)
|
||||||
|
|||||||
@@ -379,6 +379,11 @@ def InitParser(parser):
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="use git-worktree to manage projects",
|
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.
|
# These are fundamentally different ways of structuring the checkout.
|
||||||
group = parser.add_option_group("Project checkout optimizations")
|
group = parser.add_option_group("Project checkout optimizations")
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ to update the working directory files.
|
|||||||
depth=opt.depth,
|
depth=opt.depth,
|
||||||
git_event_log=self.git_event_log,
|
git_event_log=self.git_event_log,
|
||||||
manifest_name=opt.manifest_name,
|
manifest_name=opt.manifest_name,
|
||||||
|
use_local_gitdirs=opt.use_local_gitdirs,
|
||||||
):
|
):
|
||||||
manifest_name = opt.manifest_name
|
manifest_name = opt.manifest_name
|
||||||
raise UpdateManifestError(
|
raise UpdateManifestError(
|
||||||
|
|||||||
@@ -819,6 +819,26 @@ class TestProjectElement:
|
|||||||
str(repo_client.topdir), ".repo", "projects", "..git"
|
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:
|
def test_bad_path_name_checks(self, repo_client: RepoClient) -> None:
|
||||||
"""Check handling of bad path & name attributes."""
|
"""Check handling of bad path & name attributes."""
|
||||||
|
|
||||||
|
|||||||
@@ -657,6 +657,9 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
|
|||||||
fakeproj.config.SetBoolean("repo.worktree", False)
|
fakeproj.config.SetBoolean("repo.worktree", False)
|
||||||
self.assertFalse(fakeproj.use_worktree)
|
self.assertFalse(fakeproj.use_worktree)
|
||||||
|
|
||||||
|
fakeproj.config.SetBoolean("repo.uselocalgitdirs", False)
|
||||||
|
self.assertFalse(fakeproj.use_local_gitdirs)
|
||||||
|
|
||||||
fakeproj.config.SetBoolean("repo.clonebundle", False)
|
fakeproj.config.SetBoolean("repo.clonebundle", False)
|
||||||
self.assertFalse(fakeproj.clone_bundle)
|
self.assertFalse(fakeproj.clone_bundle)
|
||||||
|
|
||||||
@@ -691,6 +694,54 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
|
|||||||
fakeproj.config.SetString("manifest.platform", "auto")
|
fakeproj.config.SetString("manifest.platform", "auto")
|
||||||
self.assertEqual(fakeproj.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):
|
class StatelessSyncTests(unittest.TestCase):
|
||||||
"""Tests for stateless sync strategy."""
|
"""Tests for stateless sync strategy."""
|
||||||
|
|||||||
Reference in New Issue
Block a user