project: allow src=. with symlinks

Some Android/Nest manifests are using <linkfile> with src="." to
create stable paths to specific projects.  Allow that specific
use case as it seems reasonable to support.

Bug: https://crbug.com/gerrit/11218
Change-Id: I16dbe8d9fe42ea45440afcb61404c753bff1930d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254330
Reviewed-by: Chanho Park <parkch98@gmail.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
diff --git a/project.py b/project.py
index 185507c..a305d72 100644
--- a/project.py
+++ b/project.py
@@ -382,7 +382,12 @@
     Handles wild cards on the src linking all of the files in the source in to
     the destination directory.
     """
-    src = _SafeExpandPath(self.git_worktree, self.src)
+    # Some people use src="." to create stable links to projects.  Lets allow
+    # that but reject all other uses of "." to keep things simple.
+    if self.src == '.':
+      src = self.git_worktree
+    else:
+      src = _SafeExpandPath(self.git_worktree, self.src)
 
     if os.path.exists(src):
       # Entity exists so just a simple one to one link operation.
diff --git a/tests/test_project.py b/tests/test_project.py
index 6d82da1..dc41f4c 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -314,6 +314,14 @@
     lf._Link()
     self.assertExists(os.path.join(self.topdir, 'foo'))
 
+  def test_src_self(self):
+    """Link to the project itself."""
+    dest = os.path.join(self.topdir, 'foo', 'bar')
+    lf = self.LinkFile('.', 'foo/bar')
+    lf._Link()
+    self.assertExists(dest)
+    self.assertEqual('../git-project', os.readlink(dest))
+
   def test_dest_subdir(self):
     """Link a file to a subdir of a checkout."""
     src = os.path.join(self.worktree, 'foo.txt')
@@ -323,6 +331,21 @@
     lf._Link()
     self.assertExists(os.path.join(self.topdir, 'sub', 'dir', 'foo', 'bar'))
 
+  def test_src_block_relative(self):
+    """Do not allow relative symlinks."""
+    BAD_SOURCES = (
+        './',
+        '..',
+        '../',
+        'foo/.',
+        'foo/./bar',
+        'foo/..',
+        'foo/../foo',
+    )
+    for src in BAD_SOURCES:
+      lf = self.LinkFile(src, 'foo')
+      self.assertRaises(error.ManifestInvalidPathError, lf._Link)
+
   def test_update(self):
     """Make sure changed targets get updated."""
     dest = os.path.join(self.topdir, 'sym')