Index: core/src/main/java/hudson/model/AbstractProject.java
===================================================================
--- core/src/main/java/hudson/model/AbstractProject.java	(revision 15370)
+++ core/src/main/java/hudson/model/AbstractProject.java	(working copy)
@@ -27,6 +27,7 @@
 import hudson.FeedAdapter;
 import hudson.FilePath;
 import hudson.Launcher;
+import hudson.Util;
 import hudson.maven.MavenModule;
 import hudson.model.Cause.UserCause;
 import hudson.model.Cause.LegacyCodeCause;
@@ -745,21 +746,48 @@
         return Collections.emptySet();
     }
 
-    public boolean checkout(AbstractBuild build, Launcher launcher, BuildListener listener, File changelogFile) throws IOException {
-        SCM scm = getScm();
+    public boolean checkout(final AbstractBuild build, final Launcher launcher, final BuildListener listener, final File changelogFile) throws IOException {
+        final SCM scm = getScm();
         if(scm==null)
             return true;    // no SCM
 
+        final boolean[] result = new boolean[1];
+        final Exception[] exception = new Exception[1];
+        Runnable task = new Runnable() {
+            public void run() {
         try {
             FilePath workspace = getWorkspace();
             workspace.mkdirs();
 
-            return scm.checkout(build, launcher, workspace, listener, changelogFile);
+                    result[0] = scm.checkout(build, launcher, workspace, listener, changelogFile);
+                } catch (IOException e) {
+                    exception[0] = e;
         } catch (InterruptedException e) {
+                    exception[0] = e;
+                }
+            }
+        };
+
+        // Acquire resource for SCMTrigger so poll won't run while we checkout/update
+        SCMTrigger scmt = getTrigger(SCMTrigger.class);
+        if (scmt==null)
+            task.run();
+        else try {
+            Hudson.getInstance().getQueue().execute(task, scmt);
+        }
+        catch (InterruptedException e) {
+            exception[0] = e;
+        }
+
+        if (exception[0] instanceof IOException) {
+            throw (IOException)exception[0];
+        }
+        else if (exception[0] instanceof InterruptedException) {
             listener.getLogger().println(Messages.AbstractProject_ScmAborted());
-            LOGGER.log(Level.INFO,build.toString()+" aborted",e);
-            return false;
+            LOGGER.log(Level.INFO,build.toString()+" aborted",exception[0]);
+            result[0] = false;
         }
+        return result[0];
     }
 
     /**
Index: core/src/main/java/hudson/triggers/SCMTrigger.java
===================================================================
--- core/src/main/java/hudson/triggers/SCMTrigger.java	(revision 15370)
+++ core/src/main/java/hudson/triggers/SCMTrigger.java	(working copy)
@@ -31,6 +31,9 @@
 import hudson.model.Hudson;
 import hudson.model.Item;
 import hudson.model.Project;
+import hudson.model.Resource;
+import hudson.model.ResourceActivity;
+import hudson.model.ResourceList;
 import hudson.model.SCMedItem;
 import hudson.model.AdministrativeMonitor;
 import hudson.util.StreamTaskListener;
@@ -61,7 +64,7 @@
  *
  * @author Kohsuke Kawaguchi
  */
-public class SCMTrigger extends Trigger<SCMedItem> {
+public class SCMTrigger extends Trigger<SCMedItem> implements ResourceActivity {
     /**
      * If we'd like to run another polling run, this is set to true.
      *
@@ -411,6 +414,16 @@
         }
 
         public void run() {
+            try {
+                // Acquire resource so we don't poll while checkout/update is running
+                Hudson.getInstance().getQueue().execute(runTask, SCMTrigger.this);
+            } catch (InterruptedException ex) {
+                LOGGER.info("Aborted");
+            }
+        }
+
+        private Runnable runTask = new Runnable() {
+          public void run() {
             String threadName = Thread.currentThread().getName();
             Thread.currentThread().setName("SCM polling for "+job);
             try {
@@ -421,11 +434,11 @@
                         if(pollingScheduled) {
                             pollingScheduled = false;
                             startTime = System.currentTimeMillis();
-                            getDescriptor().items.add(this);
+                            getDescriptor().items.add(Runner.this);
                             try {
                                 foundChanges = runPolling();
                             } finally {
-                                getDescriptor().items.remove(this);
+                                getDescriptor().items.remove(Runner.this);
                             }
                         }
                     } finally {
@@ -447,8 +460,24 @@
                 Thread.currentThread().setName(threadName);
             }
         }
+        };
     }
 
+    private transient Resource myResource;
+
+    @Override public void start(SCMedItem p, boolean newInstance) {
+        super.start(p, newInstance);
+        myResource = new Resource(null, "SCMTrigger for " + p.asProject().getDisplayName());
+    }
+
+    public ResourceList getResourceList() {
+        return new ResourceList().w(myResource);
+    }
+
+    public String getDisplayName() {
+        return Messages.SCMTrigger_DisplayName();
+    }
+
     public static class SCMTriggerCause extends Cause {
         @Override
         public String getShortDescription() {
Index: test/src/test/java/hudson/triggers/SCMTriggerTest.java
===================================================================
--- test/src/test/java/hudson/triggers/SCMTriggerTest.java	(revision 0)
+++ test/src/test/java/hudson/triggers/SCMTriggerTest.java	(revision 0)
@@ -0,0 +1,89 @@
+/*
+ * The MIT License
+ * 
+ * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
+ * 
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ * 
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.triggers;
+
+import hudson.FilePath;
+import hudson.Launcher;
+import hudson.model.AbstractBuild;
+import hudson.model.AbstractProject;
+import hudson.model.BuildListener;
+import hudson.model.Cause;
+import hudson.model.FreeStyleBuild;
+import hudson.model.FreeStyleProject;
+import hudson.model.Hudson;
+import hudson.model.TaskListener;
+import hudson.scm.NullSCM;
+import org.jvnet.hudson.test.Bug;
+import org.jvnet.hudson.test.HudsonTestCase;
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.Future;
+
+/**
+ * @author Alan Harder
+ */
+public class SCMTriggerTest extends HudsonTestCase {
+    /**
+     * Make sure that SCMTrigger doesn't trigger another build when a build has just started,
+     * but not yet completed its SCM update.
+     */
+    @Bug(2671)
+    public void testSimultaneousPollAndBuild() throws Exception {
+        FreeStyleProject p = createFreeStyleProject();
+        final Object notifier = new Object();
+        p.setScm(new TestSCM(notifier));
+        SCMTrigger.DESCRIPTOR.synchronousPolling = true;
+        SCMTrigger trigger = new SCMTrigger("0 0 1 1 0");
+        p.addTrigger(trigger);
+        trigger.start(p, true);
+
+        Future<FreeStyleBuild> build = p.scheduleBuild2(0, new Cause.UserCause());
+        // pollSCM as soon as build starts its checkout/update
+        synchronized (notifier) { notifier.wait(); }
+        trigger.run();
+        boolean result = Hudson.getInstance().getQueue().cancel(p);
+        build.get();  // let mock build finish
+        assertFalse("SCM-poll after build has started should wait until that build finishes SCM-update", result);
+    }
+
+    private static class TestSCM extends NullSCM {
+        private int myRev = 1;
+        private Object notifier;
+
+        private TestSCM(Object notifier) { this.notifier = notifier; }
+
+        @Override synchronized
+        public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath dir, TaskListener listener) throws IOException {
+            return myRev < 2;
+        }
+
+        @Override
+        public boolean checkout(AbstractBuild build, Launcher launcher, FilePath remoteDir, BuildListener listener, File changeLogFile) throws IOException, InterruptedException {
+            synchronized (notifier) { notifier.notify(); }
+            Thread.sleep(400);  // processing time for mock update
+            synchronized (this) { if (myRev < 2) myRev = 2; }
+            return super.checkout(build, launcher, remoteDir, listener, changeLogFile);
+        }
+    }
+}

Property changes on: test/src/test/java/hudson/triggers/SCMTriggerTest.java
___________________________________________________________________
Name: svn:eol-style
   + native