diff options
author | Mike Frysinger <vapier@google.com> | 2020-02-09 02:28:34 -0500 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2020-02-19 18:11:33 +0000 |
commit | 979d5bdc3ebe45998a76dbbaff46c33d4e59683b (patch) | |
tree | 97c639570a60dd02a6ee9b712dff091a83ac8110 /project.py | |
parent | 56ce3468b4f2faa1cccfea01dc91e7db73fb3843 (diff) | |
download | git-repo-979d5bdc3ebe45998a76dbbaff46c33d4e59683b.tar.gz |
add experimental git worktree support
This provides initial support for using git worktrees internally
instead of our own ad-hoc symlink tree. It's been lightly tested
which is why it's not currently exposed via --help.
When people opt-in to worktrees in an existing repo client checkout,
no projects are migrated. Instead, only new projects will use the
worktree method. This allows for limited testing/opting in without
having to completely blow things away or get a second checkout.
Bug: https://crbug.com/gerrit/11486
Change-Id: Ic3ff891b30940a6ba497b406b2a387e0a8517ed8
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254075
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Diffstat (limited to 'project.py')
-rw-r--r-- | project.py | 70 |
1 files changed, 59 insertions, 11 deletions
@@ -866,6 +866,7 @@ class Project(object): | |||
866 | clone_depth=None, | 866 | clone_depth=None, |
867 | upstream=None, | 867 | upstream=None, |
868 | parent=None, | 868 | parent=None, |
869 | use_git_worktrees=False, | ||
869 | is_derived=False, | 870 | is_derived=False, |
870 | dest_branch=None, | 871 | dest_branch=None, |
871 | optimized_fetch=False, | 872 | optimized_fetch=False, |
@@ -889,6 +890,7 @@ class Project(object): | |||
889 | sync_tags: The `sync-tags` attribute of manifest.xml's project element. | 890 | sync_tags: The `sync-tags` attribute of manifest.xml's project element. |
890 | upstream: The `upstream` attribute of manifest.xml's project element. | 891 | upstream: The `upstream` attribute of manifest.xml's project element. |
891 | parent: The parent Project object. | 892 | parent: The parent Project object. |
893 | use_git_worktrees: Whether to use `git worktree` for this project. | ||
892 | is_derived: False if the project was explicitly defined in the manifest; | 894 | is_derived: False if the project was explicitly defined in the manifest; |
893 | True if the project is a discovered submodule. | 895 | True if the project is a discovered submodule. |
894 | dest_branch: The branch to which to push changes for review by default. | 896 | dest_branch: The branch to which to push changes for review by default. |
@@ -923,6 +925,10 @@ class Project(object): | |||
923 | self.clone_depth = clone_depth | 925 | self.clone_depth = clone_depth |
924 | self.upstream = upstream | 926 | self.upstream = upstream |
925 | self.parent = parent | 927 | self.parent = parent |
928 | # NB: Do not use this setting in __init__ to change behavior so that the | ||
929 | # manifest.git checkout can inspect & change it after instantiating. See | ||
930 | # the XmlManifest init code for more info. | ||
931 | self.use_git_worktrees = use_git_worktrees | ||
926 | self.is_derived = is_derived | 932 | self.is_derived = is_derived |
927 | self.optimized_fetch = optimized_fetch | 933 | self.optimized_fetch = optimized_fetch |
928 | self.subprojects = [] | 934 | self.subprojects = [] |
@@ -1872,15 +1878,19 @@ class Project(object): | |||
1872 | except KeyError: | 1878 | except KeyError: |
1873 | head = None | 1879 | head = None |
1874 | if revid and head and revid == head: | 1880 | if revid and head and revid == head: |
1875 | ref = os.path.join(self.gitdir, R_HEADS + name) | 1881 | if self.use_git_worktrees: |
1876 | try: | 1882 | self.work_git.update_ref(HEAD, revid) |
1877 | os.makedirs(os.path.dirname(ref)) | 1883 | branch.Save() |
1878 | except OSError: | 1884 | else: |
1879 | pass | 1885 | ref = os.path.join(self.gitdir, R_HEADS + name) |
1880 | _lwrite(ref, '%s\n' % revid) | 1886 | try: |
1881 | _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name)) | 1887 | os.makedirs(os.path.dirname(ref)) |
1882 | branch.Save() | 1888 | except OSError: |
1883 | return True | 1889 | pass |
1890 | _lwrite(ref, '%s\n' % revid) | ||
1891 | _lwrite(self.GetHeadPath(), 'ref: %s%s\n' % (R_HEADS, name)) | ||
1892 | branch.Save() | ||
1893 | return True | ||
1884 | 1894 | ||
1885 | if GitCommand(self, | 1895 | if GitCommand(self, |
1886 | ['checkout', '-b', branch.name, revid], | 1896 | ['checkout', '-b', branch.name, revid], |
@@ -2617,6 +2627,11 @@ class Project(object): | |||
2617 | os.makedirs(self.objdir) | 2627 | os.makedirs(self.objdir) |
2618 | self.bare_objdir.init() | 2628 | self.bare_objdir.init() |
2619 | 2629 | ||
2630 | # Enable per-worktree config file support if possible. This is more a | ||
2631 | # nice-to-have feature for users rather than a hard requirement. | ||
2632 | if self.use_git_worktrees and git_require((2, 19, 0)): | ||
2633 | self.config.SetString('extensions.worktreeConfig', 'true') | ||
2634 | |||
2620 | # If we have a separate directory to hold refs, initialize it as well. | 2635 | # If we have a separate directory to hold refs, initialize it as well. |
2621 | if self.objdir != self.gitdir: | 2636 | if self.objdir != self.gitdir: |
2622 | if init_git_dir: | 2637 | if init_git_dir: |
@@ -2651,13 +2666,15 @@ class Project(object): | |||
2651 | mirror_git = os.path.join(ref_dir, self.name + '.git') | 2666 | mirror_git = os.path.join(ref_dir, self.name + '.git') |
2652 | repo_git = os.path.join(ref_dir, '.repo', 'projects', | 2667 | repo_git = os.path.join(ref_dir, '.repo', 'projects', |
2653 | self.relpath + '.git') | 2668 | self.relpath + '.git') |
2669 | worktrees_git = os.path.join(ref_dir, '.repo', 'worktrees', | ||
2670 | self.name + '.git') | ||
2654 | 2671 | ||
2655 | if os.path.exists(mirror_git): | 2672 | if os.path.exists(mirror_git): |
2656 | ref_dir = mirror_git | 2673 | ref_dir = mirror_git |
2657 | |||
2658 | elif os.path.exists(repo_git): | 2674 | elif os.path.exists(repo_git): |
2659 | ref_dir = repo_git | 2675 | ref_dir = repo_git |
2660 | 2676 | elif os.path.exists(worktrees_git): | |
2677 | ref_dir = worktrees_git | ||
2661 | else: | 2678 | else: |
2662 | ref_dir = None | 2679 | ref_dir = None |
2663 | 2680 | ||
@@ -2765,6 +2782,10 @@ class Project(object): | |||
2765 | self.bare_git.symbolic_ref('-m', msg, ref, dst) | 2782 | self.bare_git.symbolic_ref('-m', msg, ref, dst) |
2766 | 2783 | ||
2767 | def _CheckDirReference(self, srcdir, destdir, share_refs): | 2784 | def _CheckDirReference(self, srcdir, destdir, share_refs): |
2785 | # Git worktrees don't use symlinks to share at all. | ||
2786 | if self.use_git_worktrees: | ||
2787 | return | ||
2788 | |||
2768 | symlink_files = self.shareable_files[:] | 2789 | symlink_files = self.shareable_files[:] |
2769 | symlink_dirs = self.shareable_dirs[:] | 2790 | symlink_dirs = self.shareable_dirs[:] |
2770 | if share_refs: | 2791 | if share_refs: |
@@ -2864,11 +2885,38 @@ class Project(object): | |||
2864 | else: | 2885 | else: |
2865 | raise | 2886 | raise |
2866 | 2887 | ||
2888 | def _InitGitWorktree(self): | ||
2889 | """Init the project using git worktrees.""" | ||
2890 | self.bare_git.worktree('prune') | ||
2891 | self.bare_git.worktree('add', '-ff', '--checkout', '--detach', '--lock', | ||
2892 | self.worktree, self.GetRevisionId()) | ||
2893 | |||
2894 | # Rewrite the internal state files to use relative paths between the | ||
2895 | # checkouts & worktrees. | ||
2896 | dotgit = os.path.join(self.worktree, '.git') | ||
2897 | with open(dotgit, 'r') as fp: | ||
2898 | # Figure out the checkout->worktree path. | ||
2899 | setting = fp.read() | ||
2900 | assert setting.startswith('gitdir:') | ||
2901 | git_worktree_path = setting.split(':', 1)[1].strip() | ||
2902 | # Use relative path from checkout->worktree. | ||
2903 | with open(dotgit, 'w') as fp: | ||
2904 | print('gitdir:', os.path.relpath(git_worktree_path, self.worktree), | ||
2905 | file=fp) | ||
2906 | # Use relative path from worktree->checkout. | ||
2907 | with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp: | ||
2908 | print(os.path.relpath(dotgit, git_worktree_path), file=fp) | ||
2909 | |||
2867 | def _InitWorkTree(self, force_sync=False, submodules=False): | 2910 | def _InitWorkTree(self, force_sync=False, submodules=False): |
2868 | realdotgit = os.path.join(self.worktree, '.git') | 2911 | realdotgit = os.path.join(self.worktree, '.git') |
2869 | tmpdotgit = realdotgit + '.tmp' | 2912 | tmpdotgit = realdotgit + '.tmp' |
2870 | init_dotgit = not os.path.exists(realdotgit) | 2913 | init_dotgit = not os.path.exists(realdotgit) |
2871 | if init_dotgit: | 2914 | if init_dotgit: |
2915 | if self.use_git_worktrees: | ||
2916 | self._InitGitWorktree() | ||
2917 | self._CopyAndLinkFiles() | ||
2918 | return | ||
2919 | |||
2872 | dotgit = tmpdotgit | 2920 | dotgit = tmpdotgit |
2873 | platform_utils.rmtree(tmpdotgit, ignore_errors=True) | 2921 | platform_utils.rmtree(tmpdotgit, ignore_errors=True) |
2874 | os.makedirs(tmpdotgit) | 2922 | os.makedirs(tmpdotgit) |