diff options
-rw-r--r-- | project.py | 116 | ||||
-rw-r--r-- | subcmds/sync.py | 85 |
2 files changed, 120 insertions, 81 deletions
@@ -1832,6 +1832,122 @@ class Project(object): | |||
1832 | patch_id, | 1832 | patch_id, |
1833 | self.bare_git.rev_parse('FETCH_HEAD')) | 1833 | self.bare_git.rev_parse('FETCH_HEAD')) |
1834 | 1834 | ||
1835 | def DeleteWorktree(self, quiet=False, force=False): | ||
1836 | """Delete the source checkout and any other housekeeping tasks. | ||
1837 | |||
1838 | This currently leaves behind the internal .repo/ cache state. This helps | ||
1839 | when switching branches or manifest changes get reverted as we don't have | ||
1840 | to redownload all the git objects. But we should do some GC at some point. | ||
1841 | |||
1842 | Args: | ||
1843 | quiet: Whether to hide normal messages. | ||
1844 | force: Always delete tree even if dirty. | ||
1845 | |||
1846 | Returns: | ||
1847 | True if the worktree was completely cleaned out. | ||
1848 | """ | ||
1849 | if self.IsDirty(): | ||
1850 | if force: | ||
1851 | print('warning: %s: Removing dirty project: uncommitted changes lost.' % | ||
1852 | (self.relpath,), file=sys.stderr) | ||
1853 | else: | ||
1854 | print('error: %s: Cannot remove project: uncommitted changes are ' | ||
1855 | 'present.\n' % (self.relpath,), file=sys.stderr) | ||
1856 | return False | ||
1857 | |||
1858 | if not quiet: | ||
1859 | print('%s: Deleting obsolete checkout.' % (self.relpath,)) | ||
1860 | |||
1861 | # Unlock and delink from the main worktree. We don't use git's worktree | ||
1862 | # remove because it will recursively delete projects -- we handle that | ||
1863 | # ourselves below. https://crbug.com/git/48 | ||
1864 | if self.use_git_worktrees: | ||
1865 | needle = platform_utils.realpath(self.gitdir) | ||
1866 | # Find the git worktree commondir under .repo/worktrees/. | ||
1867 | output = self.bare_git.worktree('list', '--porcelain').splitlines()[0] | ||
1868 | assert output.startswith('worktree '), output | ||
1869 | commondir = output[9:] | ||
1870 | # Walk each of the git worktrees to see where they point. | ||
1871 | configs = os.path.join(commondir, 'worktrees') | ||
1872 | for name in os.listdir(configs): | ||
1873 | gitdir = os.path.join(configs, name, 'gitdir') | ||
1874 | with open(gitdir) as fp: | ||
1875 | relpath = fp.read().strip() | ||
1876 | # Resolve the checkout path and see if it matches this project. | ||
1877 | fullpath = platform_utils.realpath(os.path.join(configs, name, relpath)) | ||
1878 | if fullpath == needle: | ||
1879 | platform_utils.rmtree(os.path.join(configs, name)) | ||
1880 | |||
1881 | # Delete the .git directory first, so we're less likely to have a partially | ||
1882 | # working git repository around. There shouldn't be any git projects here, | ||
1883 | # so rmtree works. | ||
1884 | |||
1885 | # Try to remove plain files first in case of git worktrees. If this fails | ||
1886 | # for any reason, we'll fall back to rmtree, and that'll display errors if | ||
1887 | # it can't remove things either. | ||
1888 | try: | ||
1889 | platform_utils.remove(self.gitdir) | ||
1890 | except OSError: | ||
1891 | pass | ||
1892 | try: | ||
1893 | platform_utils.rmtree(self.gitdir) | ||
1894 | except OSError as e: | ||
1895 | if e.errno != errno.ENOENT: | ||
1896 | print('error: %s: %s' % (self.gitdir, e), file=sys.stderr) | ||
1897 | print('error: %s: Failed to delete obsolete checkout; remove manually, ' | ||
1898 | 'then run `repo sync -l`.' % (self.relpath,), file=sys.stderr) | ||
1899 | return False | ||
1900 | |||
1901 | # Delete everything under the worktree, except for directories that contain | ||
1902 | # another git project. | ||
1903 | dirs_to_remove = [] | ||
1904 | failed = False | ||
1905 | for root, dirs, files in platform_utils.walk(self.worktree): | ||
1906 | for f in files: | ||
1907 | path = os.path.join(root, f) | ||
1908 | try: | ||
1909 | platform_utils.remove(path) | ||
1910 | except OSError as e: | ||
1911 | if e.errno != errno.ENOENT: | ||
1912 | print('error: %s: Failed to remove: %s' % (path, e), file=sys.stderr) | ||
1913 | failed = True | ||
1914 | dirs[:] = [d for d in dirs | ||
1915 | if not os.path.lexists(os.path.join(root, d, '.git'))] | ||
1916 | dirs_to_remove += [os.path.join(root, d) for d in dirs | ||
1917 | if os.path.join(root, d) not in dirs_to_remove] | ||
1918 | for d in reversed(dirs_to_remove): | ||
1919 | if platform_utils.islink(d): | ||
1920 | try: | ||
1921 | platform_utils.remove(d) | ||
1922 | except OSError as e: | ||
1923 | if e.errno != errno.ENOENT: | ||
1924 | print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr) | ||
1925 | failed = True | ||
1926 | elif not platform_utils.listdir(d): | ||
1927 | try: | ||
1928 | platform_utils.rmdir(d) | ||
1929 | except OSError as e: | ||
1930 | if e.errno != errno.ENOENT: | ||
1931 | print('error: %s: Failed to remove: %s' % (d, e), file=sys.stderr) | ||
1932 | failed = True | ||
1933 | if failed: | ||
1934 | print('error: %s: Failed to delete obsolete checkout.' % (self.relpath,), | ||
1935 | file=sys.stderr) | ||
1936 | print(' Remove manually, then run `repo sync -l`.', file=sys.stderr) | ||
1937 | return False | ||
1938 | |||
1939 | # Try deleting parent dirs if they are empty. | ||
1940 | path = self.worktree | ||
1941 | while path != self.manifest.topdir: | ||
1942 | try: | ||
1943 | platform_utils.rmdir(path) | ||
1944 | except OSError as e: | ||
1945 | if e.errno != errno.ENOENT: | ||
1946 | break | ||
1947 | path = os.path.dirname(path) | ||
1948 | |||
1949 | return True | ||
1950 | |||
1835 | # Branch Management ## | 1951 | # Branch Management ## |
1836 | def GetHeadPath(self): | 1952 | def GetHeadPath(self): |
1837 | """Return the full path to the HEAD ref.""" | 1953 | """Return the full path to the HEAD ref.""" |
diff --git a/subcmds/sync.py b/subcmds/sync.py index eada76a7..f2af0ec3 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -16,7 +16,6 @@ | |||
16 | 16 | ||
17 | from __future__ import print_function | 17 | from __future__ import print_function |
18 | 18 | ||
19 | import errno | ||
20 | import json | 19 | import json |
21 | import netrc | 20 | import netrc |
22 | from optparse import SUPPRESS_HELP | 21 | from optparse import SUPPRESS_HELP |
@@ -633,74 +632,6 @@ later is required to fix a server side protocol bug. | |||
633 | else: | 632 | else: |
634 | self.manifest._Unload() | 633 | self.manifest._Unload() |
635 | 634 | ||
636 | def _DeleteProject(self, path): | ||
637 | print('Deleting obsolete path %s' % path, file=sys.stderr) | ||
638 | |||
639 | # Delete the .git directory first, so we're less likely to have a partially | ||
640 | # working git repository around. There shouldn't be any git projects here, | ||
641 | # so rmtree works. | ||
642 | dotgit = os.path.join(path, '.git') | ||
643 | # Try to remove plain files first in case of git worktrees. If this fails | ||
644 | # for any reason, we'll fall back to rmtree, and that'll display errors if | ||
645 | # it can't remove things either. | ||
646 | try: | ||
647 | platform_utils.remove(dotgit) | ||
648 | except OSError: | ||
649 | pass | ||
650 | try: | ||
651 | platform_utils.rmtree(dotgit) | ||
652 | except OSError as e: | ||
653 | if e.errno != errno.ENOENT: | ||
654 | print('error: %s: %s' % (dotgit, str(e)), file=sys.stderr) | ||
655 | print('error: %s: Failed to delete obsolete path; remove manually, then ' | ||
656 | 'run sync again' % (path,), file=sys.stderr) | ||
657 | return 1 | ||
658 | |||
659 | # Delete everything under the worktree, except for directories that contain | ||
660 | # another git project | ||
661 | dirs_to_remove = [] | ||
662 | failed = False | ||
663 | for root, dirs, files in platform_utils.walk(path): | ||
664 | for f in files: | ||
665 | try: | ||
666 | platform_utils.remove(os.path.join(root, f)) | ||
667 | except OSError as e: | ||
668 | print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr) | ||
669 | failed = True | ||
670 | dirs[:] = [d for d in dirs | ||
671 | if not os.path.lexists(os.path.join(root, d, '.git'))] | ||
672 | dirs_to_remove += [os.path.join(root, d) for d in dirs | ||
673 | if os.path.join(root, d) not in dirs_to_remove] | ||
674 | for d in reversed(dirs_to_remove): | ||
675 | if platform_utils.islink(d): | ||
676 | try: | ||
677 | platform_utils.remove(d) | ||
678 | except OSError as e: | ||
679 | print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr) | ||
680 | failed = True | ||
681 | elif len(platform_utils.listdir(d)) == 0: | ||
682 | try: | ||
683 | platform_utils.rmdir(d) | ||
684 | except OSError as e: | ||
685 | print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr) | ||
686 | failed = True | ||
687 | continue | ||
688 | if failed: | ||
689 | print('error: Failed to delete obsolete path %s' % path, file=sys.stderr) | ||
690 | print(' remove manually, then run sync again', file=sys.stderr) | ||
691 | return 1 | ||
692 | |||
693 | # Try deleting parent dirs if they are empty | ||
694 | project_dir = path | ||
695 | while project_dir != self.manifest.topdir: | ||
696 | if len(platform_utils.listdir(project_dir)) == 0: | ||
697 | platform_utils.rmdir(project_dir) | ||
698 | else: | ||
699 | break | ||
700 | project_dir = os.path.dirname(project_dir) | ||
701 | |||
702 | return 0 | ||
703 | |||
704 | def UpdateProjectList(self, opt): | 635 | def UpdateProjectList(self, opt): |
705 | new_project_paths = [] | 636 | new_project_paths = [] |
706 | for project in self.GetProjects(None, missing_ok=True): | 637 | for project in self.GetProjects(None, missing_ok=True): |
@@ -727,23 +658,15 @@ later is required to fix a server side protocol bug. | |||
727 | remote=RemoteSpec('origin'), | 658 | remote=RemoteSpec('origin'), |
728 | gitdir=gitdir, | 659 | gitdir=gitdir, |
729 | objdir=gitdir, | 660 | objdir=gitdir, |
661 | use_git_worktrees=os.path.isfile(gitdir), | ||
730 | worktree=os.path.join(self.manifest.topdir, path), | 662 | worktree=os.path.join(self.manifest.topdir, path), |
731 | relpath=path, | 663 | relpath=path, |
732 | revisionExpr='HEAD', | 664 | revisionExpr='HEAD', |
733 | revisionId=None, | 665 | revisionId=None, |
734 | groups=None) | 666 | groups=None) |
735 | 667 | if not project.DeleteWorktree( | |
736 | if project.IsDirty() and opt.force_remove_dirty: | 668 | quiet=opt.quiet, |
737 | print('WARNING: Removing dirty project "%s": uncommitted changes ' | 669 | force=opt.force_remove_dirty): |
738 | 'erased' % project.relpath, file=sys.stderr) | ||
739 | self._DeleteProject(project.worktree) | ||
740 | elif project.IsDirty(): | ||
741 | print('error: Cannot remove project "%s": uncommitted changes ' | ||
742 | 'are present' % project.relpath, file=sys.stderr) | ||
743 | print(' commit changes, then run sync again', | ||
744 | file=sys.stderr) | ||
745 | return 1 | ||
746 | elif self._DeleteProject(project.worktree): | ||
747 | return 1 | 670 | return 1 |
748 | 671 | ||
749 | new_project_paths.sort() | 672 | new_project_paths.sort() |