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.""" |
