Merge "Cloner: use zuul_url always when project set"
diff --git a/tests/base.py b/tests/base.py
index a14b4a9..2559eb4 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -262,6 +262,25 @@
                  "comment": "This is a comment"}
         return event
 
+    def getRefUpdatedEvent(self):
+        path = os.path.join(self.upstream_root, self.project)
+        repo = git.Repo(path)
+        oldrev = repo.heads[self.branch].commit.hexsha
+
+        event = {
+            "type": "ref-updated",
+            "submitter": {
+                "name": "User Name",
+            },
+            "refUpdate": {
+                "oldRev": oldrev,
+                "newRev": self.patchsets[-1]['revision'],
+                "refName": self.branch,
+                "project": self.project,
+            }
+        }
+        return event
+
     def addApproval(self, category, value, username='reviewer_john',
                     granted_on=None, message=''):
         if not granted_on:
diff --git a/tests/fixtures/layout-cloner.yaml b/tests/fixtures/layout-cloner.yaml
index 7429603..e8b5dde 100644
--- a/tests/fixtures/layout-cloner.yaml
+++ b/tests/fixtures/layout-cloner.yaml
@@ -30,6 +30,13 @@
       gerrit:
         verified: -2
 
+  - name: post
+    manager: IndependentPipelineManager
+    trigger:
+      gerrit:
+        - event: ref-updated
+          ref: ^(?!refs/).*$
+
 projects:
   - name: org/project
     check:
@@ -42,6 +49,8 @@
       - integration
     gate:
       - integration
+    post:
+      - postjob
 
   - name: org/project2
     check:
diff --git a/tests/test_cloner.py b/tests/test_cloner.py
index 7fc8dfc..896fcba 100644
--- a/tests/test_cloner.py
+++ b/tests/test_cloner.py
@@ -108,11 +108,34 @@
                                   'be correct' % (project, number))
 
         work = self.getWorkspaceRepos(projects)
-        upstream_repo_path = os.path.join(self.upstream_root, 'org/project1')
-        self.assertEquals(
+        # project1 is the zuul_project so the origin should be set to the
+        # zuul_url since that is the most up to date.
+        cache_repo_path = os.path.join(cache_root, 'org/project1')
+        self.assertNotEqual(
             work['org/project1'].remotes.origin.url,
+            cache_repo_path,
+            'workspace repo origin should not be the cache'
+        )
+        zuul_url_repo_path = os.path.join(self.git_root, 'org/project1')
+        self.assertEqual(
+            work['org/project1'].remotes.origin.url,
+            zuul_url_repo_path,
+            'workspace repo origin should be the zuul url'
+        )
+
+        # project2 is not the zuul_project so the origin should be set
+        # to upstream since that is the best we can do
+        cache_repo_path = os.path.join(cache_root, 'org/project2')
+        self.assertNotEqual(
+            work['org/project2'].remotes.origin.url,
+            cache_repo_path,
+            'workspace repo origin should not be the cache'
+        )
+        upstream_repo_path = os.path.join(self.upstream_root, 'org/project2')
+        self.assertEqual(
+            work['org/project2'].remotes.origin.url,
             upstream_repo_path,
-            'workspace repo origin should be upstream, not cache'
+            'workspace repo origin should be the upstream url'
         )
 
         self.worker.hold_jobs_in_build = False
@@ -656,55 +679,76 @@
         self.waitUntilSettled()
 
     def test_post_checkout(self):
-        project = "org/project"
-        path = os.path.join(self.upstream_root, project)
-        repo = git.Repo(path)
-        repo.head.reference = repo.heads['master']
-        commits = []
-        for i in range(0, 3):
-            commits.append(self.create_commit(project))
-        newRev = commits[1]
+        self.worker.hold_jobs_in_build = True
+        project = "org/project1"
+
+        A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
+        event = A.getRefUpdatedEvent()
+        A.setMerged()
+        self.fake_gerrit.addEvent(event)
+        self.waitUntilSettled()
+
+        build = self.builds[0]
+        state = {'org/project1': build.parameters['ZUUL_COMMIT']}
+
+        build.release()
+        self.waitUntilSettled()
 
         cloner = zuul.lib.cloner.Cloner(
             git_base_url=self.upstream_root,
             projects=[project],
             workspace=self.workspace_root,
-            zuul_project='org/project',
-            zuul_branch=None,
-            zuul_ref='master',
+            zuul_project=build.parameters.get('ZUUL_PROJECT', None),
+            zuul_branch=build.parameters.get('ZUUL_BRANCH', None),
+            zuul_ref=build.parameters.get('ZUUL_REF', None),
+            zuul_newrev=build.parameters.get('ZUUL_NEWREV', None),
             zuul_url=self.git_root,
-            zuul_newrev=newRev,
         )
         cloner.execute()
-        repos = self.getWorkspaceRepos([project])
-        cloned_sha = repos[project].rev_parse('HEAD').hexsha
-        self.assertEqual(newRev, cloned_sha)
+        work = self.getWorkspaceRepos([project])
+        self.assertEquals(state[project],
+                          str(work[project].commit('HEAD')),
+                          'Project %s commit for build %s should '
+                          'be correct' % (project, 0))
+        shutil.rmtree(self.workspace_root)
 
     def test_post_and_master_checkout(self):
-        project = "org/project1"
-        master_project = "org/project2"
-        path = os.path.join(self.upstream_root, project)
-        repo = git.Repo(path)
-        repo.head.reference = repo.heads['master']
-        commits = []
-        for i in range(0, 3):
-            commits.append(self.create_commit(project))
-        newRev = commits[1]
+        self.worker.hold_jobs_in_build = True
+        projects = ["org/project1", "org/project2"]
+
+        A = self.fake_gerrit.addFakeChange(projects[0], 'master', 'A')
+        event = A.getRefUpdatedEvent()
+        A.setMerged()
+        self.fake_gerrit.addEvent(event)
+        self.waitUntilSettled()
+
+        build = self.builds[0]
+        upstream = self.getUpstreamRepos(projects)
+        state = {'org/project1':
+                 build.parameters['ZUUL_COMMIT'],
+                 'org/project2':
+                 str(upstream['org/project2'].commit('master')),
+                 }
+
+        build.release()
+        self.waitUntilSettled()
 
         cloner = zuul.lib.cloner.Cloner(
             git_base_url=self.upstream_root,
-            projects=[project, master_project],
+            projects=projects,
             workspace=self.workspace_root,
-            zuul_project='org/project1',
-            zuul_branch=None,
-            zuul_ref='master',
+            zuul_project=build.parameters.get('ZUUL_PROJECT', None),
+            zuul_branch=build.parameters.get('ZUUL_BRANCH', None),
+            zuul_ref=build.parameters.get('ZUUL_REF', None),
+            zuul_newrev=build.parameters.get('ZUUL_NEWREV', None),
             zuul_url=self.git_root,
-            zuul_newrev=newRev
         )
         cloner.execute()
-        repos = self.getWorkspaceRepos([project, master_project])
-        cloned_sha = repos[project].rev_parse('HEAD').hexsha
-        self.assertEqual(newRev, cloned_sha)
-        self.assertEqual(
-            repos[master_project].rev_parse('HEAD').hexsha,
-            repos[master_project].rev_parse('master').hexsha)
+        work = self.getWorkspaceRepos(projects)
+
+        for project in projects:
+            self.assertEquals(state[project],
+                              str(work[project].commit('HEAD')),
+                              'Project %s commit for build %s should '
+                              'be correct' % (project, 0))
+        shutil.rmtree(self.workspace_root)
diff --git a/zuul/lib/cloner.py b/zuul/lib/cloner.py
index 197c426..6e50eda 100644
--- a/zuul/lib/cloner.py
+++ b/zuul/lib/cloner.py
@@ -46,6 +46,8 @@
         self.zuul_branch = zuul_branch or ''
         self.zuul_ref = zuul_ref or ''
         self.zuul_url = zuul_url
+        self.zuul_project = zuul_project
+
         self.project_branches = project_branches or {}
         self.project_revisions = {}
 
@@ -77,7 +79,18 @@
     def cloneUpstream(self, project, dest):
         # Check for a cached git repo first
         git_cache = '%s/%s' % (self.cache_dir, project)
-        git_upstream = '%s/%s' % (self.git_url, project)
+
+        # Then, if we are cloning the repo for the zuul_project, then
+        # set its origin to be the zuul merger, as it is guaranteed to
+        # be correct and up to date even if mirrors haven't updated
+        # yet.  Otherwise, we can not be sure about the state of the
+        # project, so our best chance to get the most current state is
+        # by setting origin to the git_url.
+        if (self.zuul_url and project == self.zuul_project):
+            git_upstream = '%s/%s' % (self.zuul_url, project)
+        else:
+            git_upstream = '%s/%s' % (self.git_url, project)
+
         repo_is_cloned = os.path.exists(os.path.join(dest, '.git'))
         if (self.cache_dir and
             os.path.exists(git_cache) and
@@ -104,23 +117,35 @@
 
         return repo
 
-    def fetchFromZuul(self, repo, project, ref):
-        zuul_remote = '%s/%s' % (self.zuul_url, project)
+    def fetchRef(self, repo, project, ref):
+        # If we are fetching a zuul ref, the only place to get it is
+        # from the zuul merger (and it is guaranteed to be correct).
+        # Otherwise, the only way we can be certain that the ref
+        # (which, since it is not a zuul ref, is a branch or tag) is
+        # correct is in the case that it matches zuul_project.  If
+        # neither of those two conditions are met, we are most likely
+        # to get the correct state from the git_url.
+        if (ref.startswith('refs/zuul') or
+            project == self.zuul_project):
+
+            remote = '%s/%s' % (self.zuul_url, project)
+        else:
+            remote = '%s/%s' % (self.git_url, project)
 
         try:
-            repo.fetchFrom(zuul_remote, ref)
-            self.log.debug("Fetched ref %s from %s", ref, project)
+            repo.fetchFrom(remote, ref)
+            self.log.debug("Fetched ref %s from %s", ref, remote)
             return True
         except ValueError:
-            self.log.debug("Project %s in Zuul does not have ref %s",
-                           project, ref)
+            self.log.debug("Repo %s does not have ref %s",
+                           remote, ref)
             return False
         except GitCommandError as error:
             # Bail out if fetch fails due to infrastructure reasons
             if error.stderr.startswith('fatal: unable to access'):
                 raise
-            self.log.debug("Project %s in Zuul does not have ref %s",
-                           project, ref)
+            self.log.debug("Repo %s does not have ref %s",
+                           remote, ref)
             return False
 
     def prepareRepo(self, project, dest):
@@ -192,7 +217,7 @@
             self.log.info("Attempting to check out revision %s for "
                           "project %s", indicated_revision, project)
             try:
-                self.fetchFromZuul(repo, project, self.zuul_ref)
+                self.fetchRef(repo, project, self.zuul_ref)
                 commit = repo.checkout(indicated_revision)
             except (ValueError, GitCommandError):
                 raise exceptions.RevNotFound(project, indicated_revision)
@@ -201,10 +226,10 @@
         # If we have a non empty zuul_ref to use, use it. Otherwise we fall
         # back to checking out the branch.
         elif ((override_zuul_ref and
-              self.fetchFromZuul(repo, project, override_zuul_ref)) or
+              self.fetchRef(repo, project, override_zuul_ref)) or
               (fallback_zuul_ref and
                fallback_zuul_ref != override_zuul_ref and
-              self.fetchFromZuul(repo, project, fallback_zuul_ref))):
+              self.fetchRef(repo, project, fallback_zuul_ref))):
             # Work around a bug in GitPython which can not parse FETCH_HEAD
             gitcmd = git.Git(dest)
             fetch_head = gitcmd.rev_parse('FETCH_HEAD')