summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2020-02-19 19:19:18 -0500
committerMike Frysinger <vapier@google.com>2020-02-20 00:51:42 +0000
commitc0d1866b35d3e8d0bee3760a9f52574fa2ab8491 (patch)
treec6be761e1dbf5e93dfe499e8f53d90386a2d6769 /project.py
parentf81c72ed7727ca408d1859a86c56b532a8de8d59 (diff)
downloadgit-repo-c0d1866b35d3e8d0bee3760a9f52574fa2ab8491.tar.gz
project/sync: move DeleteProject helper to Project
Since deleting a source checkout involves a good bit of internal knowledge of .repo/, move the DeleteProject helper out of the sync code and into the Project class itself. This allows us to add git worktree support to it so we can unlock/unlink project checkouts. Change-Id: If9af8bd4a9c7e29743827d8166bc3db81547ca50 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256072 Reviewed-by: Jonathan Nieder <jrn@google.com> Tested-by: Mike Frysinger <vapier@google.com>
Diffstat (limited to 'project.py')
-rw-r--r--project.py116
1 files changed, 116 insertions, 0 deletions
diff --git a/project.py b/project.py
index 5bead641..a5d35bf3 100644
--- a/project.py
+++ b/project.py
@@ -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."""