summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2021-11-13 23:29:42 -0500
committerMike Frysinger <vapier@google.com>2021-12-01 15:27:16 +0000
commit2a089cfee4a3eb0c28cfb441861fc1fcb05797d3 (patch)
tree6e876b45921426afa477472aeb5291e9e31071f0 /project.py
parent4a478edb443864561089b2699c9e65c85fc5e036 (diff)
downloadgit-repo-2a089cfee4a3eb0c28cfb441861fc1fcb05797d3.tar.gz
project: migrate worktree .git/ dirs to symlinksv2.19
Historically we created a .git/ subdir in each source checkout and symlinked individual files to the .repo/projects/ paths. This layer of indirection isn't actually needed: the .repo/projects/ paths are guaranteed to only ever have a 1-to-1 mapping with the actual git checkout. So we don't need to worry about having files in .git/ be isolated. To that end, change how we manage the actual project checkouts from a dir full of symlinks (and a few files) to a symlink to the internal .repo/projects/ dir. This makes the code simpler & faster. The directory structure we have today is: .repo/ project-objects/chromiumos/third_party/kernel.git/ <paths omitted as not relevant to this change> projects/src/third_party/kernel/ v3.8.git/ config description -> …/project-objects/…/config FETCH_HEAD HEAD hooks/ -> …/project-objects/…/hooks/ info/ -> …/project-objects/…/info/ logs/ objects/ -> …/project-objects/…/objects/ packed-refs refs/ rr-cache/ -> …/project-objects/…/rr-cache/ src/third_party/kernel/ v3.8/ .git/ config -> …/projects/…/v3.8.git/config description -> …/project-objects/…/v3.8.git/description HEAD hooks/ -> …/project-objects/…/v3.8.git/hooks/ index info/ -> …/project-objects/…/v3.8.git/info/ logs/ -> …/projects/…/v3.8.git/logs/ objects/ -> …/project-objects/…/v3.8.git/objects/ packed-refs -> …/projects/…/v3.8.git/packed-refs refs/ -> …/projects/…/v3.8.git/refs/ rr-cache/ -> …/project-objects/…/v3.8.git/rr-cache/ The directory structure we have after this commit: .repo/ <nothing changes> src/third_party/kernel/ v3.8/ .git -> …/projects/…/v3.8.git Bug: https://crbug.com/gerrit/15273 Change-Id: I9dd8def23fbfb2f4cb209a93f8b1b2b24002a444 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/323695 Reviewed-by: Mike Nichols <mikenichols@google.com> Reviewed-by: Xin Li <delphij@google.com> Tested-by: Mike Frysinger <vapier@google.com>
Diffstat (limited to 'project.py')
-rw-r--r--project.py117
1 files changed, 81 insertions, 36 deletions
diff --git a/project.py b/project.py
index 4b85bb98..e0d645bb 100644
--- a/project.py
+++ b/project.py
@@ -2781,50 +2781,95 @@ class Project(object):
2781 self._InitMRef() 2781 self._InitMRef()
2782 2782
2783 def _InitWorkTree(self, force_sync=False, submodules=False): 2783 def _InitWorkTree(self, force_sync=False, submodules=False):
2784 realdotgit = os.path.join(self.worktree, '.git') 2784 """Setup the worktree .git path.
2785 tmpdotgit = realdotgit + '.tmp' 2785
2786 init_dotgit = not os.path.exists(realdotgit) 2786 This is the user-visible path like src/foo/.git/.
2787 if init_dotgit: 2787
2788 if self.use_git_worktrees: 2788 With non-git-worktrees, this will be a symlink to the .repo/projects/ path.
2789 With git-worktrees, this will be a .git file using "gitdir: ..." syntax.
2790
2791 Older checkouts had .git/ directories. If we see that, migrate it.
2792
2793 This also handles changes in the manifest. Maybe this project was backed
2794 by "foo/bar" on the server, but now it's "new/foo/bar". We have to update
2795 the path we point to under .repo/projects/ to match.
2796 """
2797 dotgit = os.path.join(self.worktree, '.git')
2798
2799 # If using an old layout style (a directory), migrate it.
2800 if not platform_utils.islink(dotgit) and platform_utils.isdir(dotgit):
2801 self._MigrateOldWorkTreeGitDir(dotgit)
2802
2803 init_dotgit = not os.path.exists(dotgit)
2804 if self.use_git_worktrees:
2805 if init_dotgit:
2789 self._InitGitWorktree() 2806 self._InitGitWorktree()
2790 self._CopyAndLinkFiles() 2807 self._CopyAndLinkFiles()
2791 return
2792
2793 dotgit = tmpdotgit
2794 platform_utils.rmtree(tmpdotgit, ignore_errors=True)
2795 os.makedirs(tmpdotgit)
2796 self._ReferenceGitDir(self.gitdir, tmpdotgit, share_refs=True,
2797 copy_all=False)
2798 else: 2808 else:
2799 dotgit = realdotgit 2809 if not init_dotgit:
2810 # See if the project has changed.
2811 if platform_utils.realpath(self.gitdir) != platform_utils.realpath(dotgit):
2812 platform_utils.remove(dotgit)
2800 2813
2801 try: 2814 if init_dotgit or not os.path.exists(dotgit):
2802 self._CheckDirReference(self.gitdir, dotgit, share_refs=True) 2815 os.makedirs(self.worktree, exist_ok=True)
2803 except GitError as e: 2816 platform_utils.symlink(os.path.relpath(self.gitdir, self.worktree), dotgit)
2804 if force_sync and not init_dotgit:
2805 try:
2806 platform_utils.rmtree(dotgit)
2807 return self._InitWorkTree(force_sync=False, submodules=submodules)
2808 except Exception:
2809 raise e
2810 raise e
2811 2817
2812 if init_dotgit: 2818 if init_dotgit:
2813 _lwrite(os.path.join(tmpdotgit, HEAD), '%s\n' % self.GetRevisionId()) 2819 _lwrite(os.path.join(dotgit, HEAD), '%s\n' % self.GetRevisionId())
2814 2820
2815 # Now that the .git dir is fully set up, move it to its final home. 2821 # Finish checking out the worktree.
2816 platform_utils.rename(tmpdotgit, realdotgit) 2822 cmd = ['read-tree', '--reset', '-u', '-v', HEAD]
2823 if GitCommand(self, cmd).Wait() != 0:
2824 raise GitError('Cannot initialize work tree for ' + self.name)
2817 2825
2818 # Finish checking out the worktree. 2826 if submodules:
2819 cmd = ['read-tree', '--reset', '-u'] 2827 self._SyncSubmodules(quiet=True)
2820 cmd.append('-v') 2828 self._CopyAndLinkFiles()
2821 cmd.append(HEAD)
2822 if GitCommand(self, cmd).Wait() != 0:
2823 raise GitError('Cannot initialize work tree for ' + self.name)
2824 2829
2825 if submodules: 2830 @classmethod
2826 self._SyncSubmodules(quiet=True) 2831 def _MigrateOldWorkTreeGitDir(cls, dotgit):
2827 self._CopyAndLinkFiles() 2832 """Migrate the old worktree .git/ dir style to a symlink.
2833
2834 This logic specifically only uses state from |dotgit| to figure out where to
2835 move content and not |self|. This way if the backing project also changed
2836 places, we only do the .git/ dir to .git symlink migration here. The path
2837 updates will happen independently.
2838 """
2839 # Figure out where in .repo/projects/ it's pointing to.
2840 if not os.path.islink(os.path.join(dotgit, 'refs')):
2841 raise GitError(f'{dotgit}: unsupported checkout state')
2842 gitdir = os.path.dirname(os.path.realpath(os.path.join(dotgit, 'refs')))
2843
2844 # Remove known symlink paths that exist in .repo/projects/.
2845 KNOWN_LINKS = {
2846 'config', 'description', 'hooks', 'info', 'logs', 'objects',
2847 'packed-refs', 'refs', 'rr-cache', 'shallow', 'svn',
2848 }
2849 # Paths that we know will be in both, but are safe to clobber in .repo/projects/.
2850 SAFE_TO_CLOBBER = {
2851 'COMMIT_EDITMSG', 'FETCH_HEAD', 'HEAD', 'index', 'ORIG_HEAD',
2852 }
2853
2854 # Now walk the paths and sync the .git/ to .repo/projects/.
2855 for name in platform_utils.listdir(dotgit):
2856 dotgit_path = os.path.join(dotgit, name)
2857 if name in KNOWN_LINKS:
2858 if platform_utils.islink(dotgit_path):
2859 platform_utils.remove(dotgit_path)
2860 else:
2861 raise GitError(f'{dotgit_path}: should be a symlink')
2862 else:
2863 gitdir_path = os.path.join(gitdir, name)
2864 if name in SAFE_TO_CLOBBER or not os.path.exists(gitdir_path):
2865 platform_utils.remove(gitdir_path, missing_ok=True)
2866 platform_utils.rename(dotgit_path, gitdir_path)
2867 else:
2868 raise GitError(f'{dotgit_path}: unknown file; please file a bug')
2869
2870 # Now that the dir should be empty, clear it out, and symlink it over.
2871 platform_utils.rmdir(dotgit)
2872 platform_utils.symlink(os.path.relpath(gitdir, os.path.dirname(dotgit)), dotgit)
2828 2873
2829 def _get_symlink_error_message(self): 2874 def _get_symlink_error_message(self):
2830 if platform_utils.isWindows(): 2875 if platform_utils.isWindows():