summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2022-01-06 05:42:24 -0500
committerMike Frysinger <vapier@google.com>2022-01-07 20:17:14 +0000
commit89ed8acdbe468fd76d531cd8b7b2ace5b414f0bd (patch)
treef0dcf219ba20bb8e4d74e551ebf3bc0572104a99
parent71e48b76728dcc50b3265d320ee1c2d1ea09a4f5 (diff)
downloadgit-repo-89ed8acdbe468fd76d531cd8b7b2ace5b414f0bd.tar.gz
project: abort a bit earlier before migrating .git/
Verify all the .git/ paths will be handled by the migration logic before starting the migration. This way we still abort & log an error, but the user gets to see it before we put the tree into a state that they have to manually recover. Also add a few more known-safe-to-clobber paths. Bug: https://crbug.com/gerrit/15273 Change-Id: If49d69b341bc960ddcafa30da333fb5ec7145b51 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/327557 Reviewed-by: Colin Cross <ccross@android.com> Tested-by: Mike Frysinger <vapier@google.com>
-rw-r--r--project.py38
-rw-r--r--tests/test_project.py28
2 files changed, 53 insertions, 13 deletions
diff --git a/project.py b/project.py
index de593c83..48287858 100644
--- a/project.py
+++ b/project.py
@@ -2818,24 +2818,40 @@ class Project(object):
2818 } 2818 }
2819 # Paths that we know will be in both, but are safe to clobber in .repo/projects/. 2819 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2820 SAFE_TO_CLOBBER = { 2820 SAFE_TO_CLOBBER = {
2821 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD', 2821 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'gitk.cache', 'index', 'ORIG_HEAD',
2822 } 2822 }
2823 2823
2824 # Now walk the paths and sync the .git/ to .repo/projects/. 2824 # First see if we'd succeed before starting the migration.
2825 unknown_paths = []
2825 for name in platform_utils.listdir(dotgit): 2826 for name in platform_utils.listdir(dotgit):
2827 # Ignore all temporary/backup names. These are common with vim & emacs.
2828 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2829 continue
2830
2826 dotgit_path = os.path.join(dotgit, name) 2831 dotgit_path = os.path.join(dotgit, name)
2827 if name in KNOWN_LINKS: 2832 if name in KNOWN_LINKS:
2828 if platform_utils.islink(dotgit_path): 2833 if not platform_utils.islink(dotgit_path):
2829 platform_utils.remove(dotgit_path) 2834 unknown_paths.append(f'{dotgit_path}: should be a symlink')
2830 else:
2831 raise GitError(f'{dotgit_path}: should be a symlink')
2832 else: 2835 else:
2833 gitdir_path = os.path.join(gitdir, name) 2836 gitdir_path = os.path.join(gitdir, name)
2834 if name in SAFE_TO_CLOBBER or not os.path.exists(gitdir_path): 2837 if name not in SAFE_TO_CLOBBER and os.path.exists(gitdir_path):
2835 platform_utils.remove(gitdir_path, missing_ok=True) 2838 unknown_paths.append(f'{dotgit_path}: unknown file; please file a bug')
2836 platform_utils.rename(dotgit_path, gitdir_path) 2839 if unknown_paths:
2837 else: 2840 raise GitError('Aborting migration: ' + '\n'.join(unknown_paths))
2838 raise GitError(f'{dotgit_path}: unknown file; please file a bug') 2841
2842 # Now walk the paths and sync the .git/ to .repo/projects/.
2843 for name in platform_utils.listdir(dotgit):
2844 dotgit_path = os.path.join(dotgit, name)
2845
2846 # Ignore all temporary/backup names. These are common with vim & emacs.
2847 if name.endswith('~') or (name[0] == '#' and name[-1] == '#'):
2848 platform_utils.remove(dotgit_path)
2849 elif name in KNOWN_LINKS:
2850 platform_utils.remove(dotgit_path)
2851 else:
2852 gitdir_path = os.path.join(gitdir, name)
2853 platform_utils.remove(gitdir_path, missing_ok=True)
2854 platform_utils.rename(dotgit_path, gitdir_path)
2839 2855
2840 # Now that the dir should be empty, clear it out, and symlink it over. 2856 # Now that the dir should be empty, clear it out, and symlink it over.
2841 platform_utils.rmdir(dotgit) 2857 platform_utils.rmdir(dotgit)
diff --git a/tests/test_project.py b/tests/test_project.py
index d578fe84..4f449227 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -347,6 +347,10 @@ class MigrateWorkTreeTests(unittest.TestCase):
347 } 347 }
348 _FILES = { 348 _FILES = {
349 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD', 349 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD',
350 'unknown-file-should-be-migrated',
351 }
352 _CLEAN_FILES = {
353 'a-vim-temp-file~', '#an-emacs-temp-file#',
350 } 354 }
351 355
352 @classmethod 356 @classmethod
@@ -365,10 +369,9 @@ class MigrateWorkTreeTests(unittest.TestCase):
365 dotgit.mkdir(parents=True) 369 dotgit.mkdir(parents=True)
366 for name in cls._SYMLINKS: 370 for name in cls._SYMLINKS:
367 (dotgit / name).symlink_to(f'../../../.repo/projects/src/test.git/{name}') 371 (dotgit / name).symlink_to(f'../../../.repo/projects/src/test.git/{name}')
368 for name in cls._FILES: 372 for name in cls._FILES | cls._CLEAN_FILES:
369 (dotgit / name).write_text(name) 373 (dotgit / name).write_text(name)
370 374
371 subprocess.run(['tree', '-a', str(dotgit)])
372 yield tempdir 375 yield tempdir
373 376
374 def test_standard(self): 377 def test_standard(self):
@@ -385,3 +388,24 @@ class MigrateWorkTreeTests(unittest.TestCase):
385 gitdir = tempdir / '.repo/projects/src/test.git' 388 gitdir = tempdir / '.repo/projects/src/test.git'
386 for name in self._FILES: 389 for name in self._FILES:
387 self.assertEqual(name, (gitdir / name).read_text()) 390 self.assertEqual(name, (gitdir / name).read_text())
391 # Make sure files were removed.
392 for name in self._CLEAN_FILES:
393 self.assertFalse((gitdir / name).exists())
394
395 def test_unknown(self):
396 """A checkout with unknown files should abort."""
397 with self._simple_layout() as tempdir:
398 dotgit = tempdir / 'src/test/.git'
399 (tempdir / '.repo/projects/src/test.git/random-file').write_text('one')
400 (dotgit / 'random-file').write_text('two')
401 with self.assertRaises(error.GitError):
402 project.Project._MigrateOldWorkTreeGitDir(str(dotgit))
403
404 # Make sure no content was actually changed.
405 self.assertTrue(dotgit.is_dir())
406 for name in self._FILES:
407 self.assertTrue((dotgit / name).is_file())
408 for name in self._CLEAN_FILES:
409 self.assertTrue((dotgit / name).is_file())
410 for name in self._SYMLINKS:
411 self.assertTrue((dotgit / name).is_symlink())