diff options
author | Mike Frysinger <vapier@google.com> | 2021-11-13 23:29:42 -0500 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2021-12-01 15:27:16 +0000 |
commit | 2a089cfee4a3eb0c28cfb441861fc1fcb05797d3 (patch) | |
tree | 6e876b45921426afa477472aeb5291e9e31071f0 /project.py | |
parent | 4a478edb443864561089b2699c9e65c85fc5e036 (diff) | |
download | git-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.py | 117 |
1 files changed, 81 insertions, 36 deletions
@@ -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(): |