diff options
Diffstat (limited to 'project.py')
-rw-r--r-- | project.py | 116 |
1 files changed, 116 insertions, 0 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.""" |