summaryrefslogtreecommitdiffstats
path: root/project.py
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2020-02-09 02:28:34 -0500
committerMike Frysinger <vapier@google.com>2020-02-19 18:11:33 +0000
commit979d5bdc3ebe45998a76dbbaff46c33d4e59683b (patch)
tree97c639570a60dd02a6ee9b712dff091a83ac8110 /project.py
parent56ce3468b4f2faa1cccfea01dc91e7db73fb3843 (diff)
downloadgit-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.py70
1 files changed, 59 insertions, 11 deletions
diff --git a/project.py b/project.py
index 86c9ef00..3a7ac9e8 100644
--- a/project.py
+++ b/project.py
@@ -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)