Merger: fully update repo on repo update

When a merger gets a request to update a repo for anything other
than a pre-merge job (eg, ref-updated, periodic, etc), it simply
updates the repository from upstream.  While it did update its
tags and remote branch references, it did not actually update
its local branch heads.  Switch to using the 'reset' method
so that happens (along with the update which was already happening).

This corrects a situation where a periodic or post job might pull
data from a repo on a zuul merger and end up with out of date
information.

Change-Id: Iccc691974ae5efedec9bfa5b553b451a1b5ab686
diff --git a/tests/fixtures/layout-cloner.yaml b/tests/fixtures/layout-cloner.yaml
index e840ed9..7429603 100644
--- a/tests/fixtures/layout-cloner.yaml
+++ b/tests/fixtures/layout-cloner.yaml
@@ -1,4 +1,16 @@
 pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        verified: 1
+    failure:
+      gerrit:
+        verified: -1
+
   - name: gate
     manager: DependentPipelineManager
     failure-message: Build failed.  For information on how to proceed, see http://wiki.example.org/Test_Failures
@@ -19,27 +31,44 @@
         verified: -2
 
 projects:
+  - name: org/project
+    check:
+      - integration
+    gate:
+      - integration
 
   - name: org/project1
+    check:
+      - integration
     gate:
-        - integration
+      - integration
 
   - name: org/project2
+    check:
+      - integration
     gate:
-        - integration
+      - integration
 
   - name: org/project3
+    check:
+      - integration
     gate:
-        - integration
+      - integration
 
   - name: org/project4
+    check:
+      - integration
     gate:
-        - integration
+      - integration
 
   - name: org/project5
+    check:
+      - integration
     gate:
-        - integration
+      - integration
 
   - name: org/project6
+    check:
+      - integration
     gate:
-        - integration
+      - integration
diff --git a/tests/test_cloner.py b/tests/test_cloner.py
index e3576bd..7fc8dfc 100644
--- a/tests/test_cloner.py
+++ b/tests/test_cloner.py
@@ -91,6 +91,7 @@
                 git_base_url=self.upstream_root,
                 projects=projects,
                 workspace=self.workspace_root,
+                zuul_project=build.parameters.get('ZUUL_PROJECT', None),
                 zuul_branch=build.parameters['ZUUL_BRANCH'],
                 zuul_ref=build.parameters['ZUUL_REF'],
                 zuul_url=self.git_root,
@@ -149,6 +150,7 @@
                 git_base_url=self.upstream_root,
                 projects=projects,
                 workspace=self.workspace_root,
+                zuul_project=build.parameters.get('ZUUL_PROJECT', None),
                 zuul_branch=build.parameters['ZUUL_BRANCH'],
                 zuul_ref=build.parameters['ZUUL_REF'],
                 zuul_url=self.git_root,
@@ -219,6 +221,7 @@
                 git_base_url=self.upstream_root,
                 projects=projects,
                 workspace=self.workspace_root,
+                zuul_project=build.parameters.get('ZUUL_PROJECT', None),
                 zuul_branch=build.parameters['ZUUL_BRANCH'],
                 zuul_ref=build.parameters['ZUUL_REF'],
                 zuul_url=self.git_root,
@@ -333,6 +336,7 @@
                 git_base_url=self.upstream_root,
                 projects=projects,
                 workspace=self.workspace_root,
+                zuul_project=build.parameters.get('ZUUL_PROJECT', None),
                 zuul_branch=build.parameters['ZUUL_BRANCH'],
                 zuul_ref=build.parameters['ZUUL_REF'],
                 zuul_url=self.git_root,
@@ -395,6 +399,7 @@
                 git_base_url=self.upstream_root,
                 projects=projects,
                 workspace=self.workspace_root,
+                zuul_project=build.parameters.get('ZUUL_PROJECT', None),
                 zuul_branch=build.parameters['ZUUL_BRANCH'],
                 zuul_ref=build.parameters['ZUUL_REF'],
                 zuul_url=self.git_root,
@@ -481,6 +486,7 @@
                 git_base_url=self.upstream_root,
                 projects=projects,
                 workspace=self.workspace_root,
+                zuul_project=build.parameters.get('ZUUL_PROJECT', None),
                 zuul_branch=build.parameters['ZUUL_BRANCH'],
                 zuul_ref=build.parameters['ZUUL_REF'],
                 zuul_url=self.git_root,
@@ -546,6 +552,7 @@
                 git_base_url=self.upstream_root,
                 projects=projects,
                 workspace=self.workspace_root,
+                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_url=self.git_root,
@@ -567,6 +574,87 @@
         self.worker.release()
         self.waitUntilSettled()
 
+    def test_periodic_update(self):
+        # Test that the merger correctly updates its local repository
+        # before running a periodic job.
+
+        # Prime the merger with the current state
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+
+        # Merge a different change
+        B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
+        B.setMerged()
+
+        # Start a periodic job
+        self.worker.hold_jobs_in_build = True
+        self.launcher.negative_function_cache_ttl = 0
+        self.config.set('zuul', 'layout_config',
+                        'tests/fixtures/layout-timer.yaml')
+        self.sched.reconfigure(self.config)
+        self.registerJobs()
+
+        # The pipeline triggers every second, so we should have seen
+        # several by now.
+        time.sleep(5)
+        self.waitUntilSettled()
+
+        builds = self.builds[:]
+
+        self.worker.hold_jobs_in_build = False
+        # Stop queuing timer triggered jobs so that the assertions
+        # below don't race against more jobs being queued.
+        self.config.set('zuul', 'layout_config',
+                        'tests/fixtures/layout-no-timer.yaml')
+        self.sched.reconfigure(self.config)
+        self.registerJobs()
+        self.worker.release()
+        self.waitUntilSettled()
+
+        projects = ['org/project']
+
+        self.assertEquals(2, len(builds), "Two builds are running")
+
+        upstream = self.getUpstreamRepos(projects)
+        self.assertEqual(upstream['org/project'].commit('master').hexsha,
+                         B.patchsets[0]['revision'])
+        states = [
+            {'org/project':
+                str(upstream['org/project'].commit('master')),
+             },
+            {'org/project':
+                str(upstream['org/project'].commit('master')),
+             },
+        ]
+
+        for number, build in enumerate(builds):
+            self.log.debug("Build parameters: %s", build.parameters)
+            cloner = zuul.lib.cloner.Cloner(
+                git_base_url=self.upstream_root,
+                projects=projects,
+                workspace=self.workspace_root,
+                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_url=self.git_root,
+            )
+            cloner.execute()
+            work = self.getWorkspaceRepos(projects)
+            state = states[number]
+
+            for project in projects:
+                self.assertEquals(state[project],
+                                  str(work[project].commit('HEAD')),
+                                  'Project %s commit for build %s should '
+                                  'be correct' % (project, number))
+
+            shutil.rmtree(self.workspace_root)
+
+        self.worker.hold_jobs_in_build = False
+        self.worker.release()
+        self.waitUntilSettled()
+
     def test_post_checkout(self):
         project = "org/project"
         path = os.path.join(self.upstream_root, project)
@@ -581,10 +669,10 @@
             git_base_url=self.upstream_root,
             projects=[project],
             workspace=self.workspace_root,
+            zuul_project='org/project',
             zuul_branch=None,
             zuul_ref='master',
             zuul_url=self.git_root,
-            zuul_project=project,
             zuul_newrev=newRev,
         )
         cloner.execute()
@@ -607,10 +695,10 @@
             git_base_url=self.upstream_root,
             projects=[project, master_project],
             workspace=self.workspace_root,
+            zuul_project='org/project1',
             zuul_branch=None,
             zuul_ref='master',
             zuul_url=self.git_root,
-            zuul_project=project,
             zuul_newrev=newRev
         )
         cloner.execute()
diff --git a/zuul/merger/merger.py b/zuul/merger/merger.py
index b3cfaca..b82a7de 100644
--- a/zuul/merger/merger.py
+++ b/zuul/merger/merger.py
@@ -246,7 +246,7 @@
         repo = self.getRepo(project, url)
         try:
             self.log.info("Updating local repository %s", project)
-            repo.update()
+            repo.reset()
         except Exception:
             self.log.exception("Unable to update %s", project)