project: fix m/ pseudo ref handling with git worktrees

Since most ref namespaces are shared among all worktrees, trying to
set the pseudo m/<branch> in the common git repo ends up clobbering
each other when using shared checkouts.  For example, in CrOS:
  <project path="src/third_party/kernel/v3.8"
           name="chromiumos/third_party/kernel"
           revision="refs/heads/chromeos-3.8" />
  <project path="src/third_party/kernel/v3.10"
           name="chromiumos/third_party/kernel"
           revision="refs/heads/chromeos-3.10" />

Trying to set m/master in chromiumos/third_party/kernel.git/ will
keep clobbering the other.

Instead, when using git worktrees, lets set the m/ pseudo ref to
point into the refs/worktree/ namespace which is unique to each
git worktree.  So we have in the common dir:
  chromiumos/third_party/kernel.git/:
    refs/remotes/m/master:
      ref: refs/worktree/m/master
And then in each worktree we point refs/worktree/m/master to the
respective manifest revision expression.  Now people can use the
m/master in each git worktree and have it resolve to the right
commit for that worktree.

Bug: https://crbug.com/gerrit/12404
Change-Id: I78814bdd5dd67bb13218c4c6ccd64f8a15dd0a52
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256952
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
diff --git a/git_refs.py b/git_refs.py
index 02b98cb..e2b62ab 100644
--- a/git_refs.py
+++ b/git_refs.py
@@ -23,6 +23,8 @@
 R_HEADS = 'refs/heads/'
 R_TAGS = 'refs/tags/'
 R_PUB = 'refs/published/'
+R_WORKTREE = 'refs/worktree/'
+R_WORKTREE_M = R_WORKTREE + 'm/'
 R_M = 'refs/remotes/m/'
 
 
diff --git a/project.py b/project.py
index b93dcd5..fe55371 100644
--- a/project.py
+++ b/project.py
@@ -42,7 +42,7 @@
 import progress
 from repo_trace import IsTrace, Trace
 
-from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M
+from git_refs import GitRefs, HEAD, R_HEADS, R_TAGS, R_PUB, R_M, R_WORKTREE_M
 
 from pyversion import is_python3
 if is_python3():
@@ -2741,10 +2741,19 @@
         os.makedirs(self.objdir)
         self.bare_objdir.init()
 
-        # Enable per-worktree config file support if possible.  This is more a
-        # nice-to-have feature for users rather than a hard requirement.
-        if self.use_git_worktrees and git_require((2, 19, 0)):
-          self.EnableRepositoryExtension('worktreeConfig')
+        if self.use_git_worktrees:
+          # Set up the m/ space to point to the worktree-specific ref space.
+          # We'll update the worktree-specific ref space on each checkout.
+          if self.manifest.branch:
+            self.bare_git.symbolic_ref(
+                '-m', 'redirecting to worktree scope',
+                R_M + self.manifest.branch,
+                R_WORKTREE_M + self.manifest.branch)
+
+          # Enable per-worktree config file support if possible.  This is more a
+          # nice-to-have feature for users rather than a hard requirement.
+          if git_require((2, 19, 0)):
+            self.EnableRepositoryExtension('worktreeConfig')
 
       # If we have a separate directory to hold refs, initialize it as well.
       if self.objdir != self.gitdir:
@@ -2879,25 +2888,37 @@
 
   def _InitMRef(self):
     if self.manifest.branch:
-      self._InitAnyMRef(R_M + self.manifest.branch)
+      if self.use_git_worktrees:
+        # We can't update this ref with git worktrees until it exists.
+        # We'll wait until the initial checkout to set it.
+        if not os.path.exists(self.worktree):
+          return
+
+        base = R_WORKTREE_M
+        active_git = self.work_git
+      else:
+        base = R_M
+        active_git = self.bare_git
+
+      self._InitAnyMRef(base + self.manifest.branch, active_git)
 
   def _InitMirrorHead(self):
-    self._InitAnyMRef(HEAD)
+    self._InitAnyMRef(HEAD, self.bare_git)
 
-  def _InitAnyMRef(self, ref):
+  def _InitAnyMRef(self, ref, active_git):
     cur = self.bare_ref.symref(ref)
 
     if self.revisionId:
       if cur != '' or self.bare_ref.get(ref) != self.revisionId:
         msg = 'manifest set to %s' % self.revisionId
         dst = self.revisionId + '^0'
-        self.bare_git.UpdateRef(ref, dst, message=msg, detach=True)
+        active_git.UpdateRef(ref, dst, message=msg, detach=True)
     else:
       remote = self.GetRemote(self.remote.name)
       dst = remote.ToLocal(self.revisionExpr)
       if cur != dst:
         msg = 'manifest set to %s' % self.revisionExpr
-        self.bare_git.symbolic_ref('-m', msg, ref, dst)
+        active_git.symbolic_ref('-m', msg, ref, dst)
 
   def _CheckDirReference(self, srcdir, destdir, share_refs):
     # Git worktrees don't use symlinks to share at all.
@@ -3028,6 +3049,8 @@
     with open(os.path.join(git_worktree_path, 'gitdir'), 'w') as fp:
       print(os.path.relpath(dotgit, git_worktree_path), file=fp)
 
+    self._InitMRef()
+
   def _InitWorkTree(self, force_sync=False, submodules=False):
     realdotgit = os.path.join(self.worktree, '.git')
     tmpdotgit = realdotgit + '.tmp'