manifest: allow toplevel project checkouts

Re-allow checking out projects to the top of the repo client checkout.
We add checks to prevent checking out files under .repo/ as that path
is only managed by us, and projects cannot inject content or settings
into it.

Bug: https://crbug.com/gerrit/14156
Bug: https://crbug.com/gerrit/14200
Change-Id: Id6bf9e882f5be748442b2c35bbeaee3549410b25
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/299623
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
This commit is contained in:
Mike Frysinger 2021-03-10 23:35:44 -05:00
parent 68d5d4dfe5
commit 0458faa502
3 changed files with 45 additions and 4 deletions

View File

@ -1039,7 +1039,8 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
if not path: if not path:
path = name path = name
else: else:
msg = self._CheckLocalPath(path, dir_ok=True) # NB: The "." project is handled specially in Project.Sync_LocalHalf.
msg = self._CheckLocalPath(path, dir_ok=True, cwd_dot_ok=True)
if msg: if msg:
raise ManifestInvalidPathError( raise ManifestInvalidPathError(
'<project> invalid "path": %s: %s' % (path, msg)) '<project> invalid "path": %s: %s' % (path, msg))
@ -1227,7 +1228,9 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md
# our constructed logic here. Especially since manifest authors only use # our constructed logic here. Especially since manifest authors only use
# / in their paths. # / in their paths.
resep = re.compile(r'[/%s]' % re.escape(os.path.sep)) resep = re.compile(r'[/%s]' % re.escape(os.path.sep))
parts = resep.split(path) # Strip off trailing slashes as those only produce '' elements, and we use
# parts to look for individual bad components.
parts = resep.split(path.rstrip('/'))
# Some people use src="." to create stable links to projects. Lets allow # Some people use src="." to create stable links to projects. Lets allow
# that but reject all other uses of "." to keep things simple. # that but reject all other uses of "." to keep things simple.

View File

@ -1227,6 +1227,18 @@ class Project(object):
self.CleanPublishedCache(all_refs) self.CleanPublishedCache(all_refs)
revid = self.GetRevisionId(all_refs) revid = self.GetRevisionId(all_refs)
# Special case the root of the repo client checkout. Make sure it doesn't
# contain files being checked out to dirs we don't allow.
if self.relpath == '.':
PROTECTED_PATHS = {'.repo'}
paths = set(self.work_git.ls_tree('-z', '--name-only', '--', revid).split('\0'))
bad_paths = paths & PROTECTED_PATHS
if bad_paths:
syncbuf.fail(self,
'Refusing to checkout project that writes to protected '
'paths: %s' % (', '.join(bad_paths),))
return
def _doff(): def _doff():
self._FastForward(revid) self._FastForward(revid)
self._CopyAndLinkFiles() self._CopyAndLinkFiles()

View File

@ -32,6 +32,7 @@ INVALID_FS_PATHS = (
'..', '..',
'../', '../',
'./', './',
'.//',
'foo/', 'foo/',
'./foo', './foo',
'../foo', '../foo',
@ -427,6 +428,28 @@ class ProjectElementTests(ManifestParseTestCase):
self.assertEqual(manifest.projects[0].objdir, self.assertEqual(manifest.projects[0].objdir,
os.path.join(self.tempdir, '.repo/project-objects/a/path.git')) os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
manifest = parse('a/path', 'foo//////')
self.assertEqual(manifest.projects[0].gitdir,
os.path.join(self.tempdir, '.repo/projects/foo.git'))
self.assertEqual(manifest.projects[0].objdir,
os.path.join(self.tempdir, '.repo/project-objects/a/path.git'))
def test_toplevel_path(self):
"""Check handling of path=. specially."""
def parse(name, path):
return self.getXmlManifest(f"""
<manifest>
<remote name="default-remote" fetch="http://localhost" />
<default remote="default-remote" revision="refs/heads/main" />
<project name="{name}" path="{path}" />
</manifest>
""")
for path in ('.', './', './/', './//'):
manifest = parse('server/path', path)
self.assertEqual(manifest.projects[0].gitdir,
os.path.join(self.tempdir, '.repo/projects/..git'))
def test_bad_path_name_checks(self): def test_bad_path_name_checks(self):
"""Check handling of bad path & name attributes.""" """Check handling of bad path & name attributes."""
def parse(name, path): def parse(name, path):
@ -454,8 +477,11 @@ class ProjectElementTests(ManifestParseTestCase):
with self.assertRaises(error.ManifestInvalidPathError): with self.assertRaises(error.ManifestInvalidPathError):
parse(path, 'ok') parse(path, 'ok')
with self.assertRaises(error.ManifestInvalidPathError):
parse('ok', path) # We have a dedicated test for path=".".
if path not in {'.'}:
with self.assertRaises(error.ManifestInvalidPathError):
parse('ok', path)
class SuperProjectElementTests(ManifestParseTestCase): class SuperProjectElementTests(ManifestParseTestCase):