Local clones updater: if server does not have responsibility to collect changes,...
authorpavel.sher <pavel.sher@jetbrains.com>
Tue, 13 Nov 2018 21:44:03 +0000 (22:44 +0100)
committerpavel.sher <pavel.sher@jetbrains.com>
Tue, 13 Nov 2018 21:44:03 +0000 (22:44 +0100)
git-server-tc/src/META-INF/build-server-plugin-git-tc.xml
git-server-tc/src/jetbrains/buildServer/buildTriggers/vcs/git/GitClonesUpdater.java [new file with mode: 0644]

index 4e761ce1a74c501fb14de7102296307b184c74a8..2802cff5a50f8052b0663799b362d72ecdc689e4 100644 (file)
@@ -16,4 +16,8 @@
   <bean class="jetbrains.buildServer.buildTriggers.vcs.git.CleanupCustomizer"/>
   <bean class="jetbrains.buildServer.buildTriggers.vcs.git.TrustedCertificatesInitializer"/>
   <bean class="jetbrains.buildServer.buildTriggers.vcs.git.GitUrlSupportInitializer"/>
+  <bean class="jetbrains.buildServer.buildTriggers.vcs.git.GitClonesUpdater">
+    <constructor-arg index="0" ref="repositoryStateEventDispatcher"/>
+    <constructor-arg index="1" ref="serverDispatcher"/>
+  </bean>
 </beans>
\ No newline at end of file
diff --git a/git-server-tc/src/jetbrains/buildServer/buildTriggers/vcs/git/GitClonesUpdater.java b/git-server-tc/src/jetbrains/buildServer/buildTriggers/vcs/git/GitClonesUpdater.java
new file mode 100644 (file)
index 0000000..8eb464d
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2000-2018 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package jetbrains.buildServer.buildTriggers.vcs.git;
+
+import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.serverSide.BuildServerAdapter;
+import jetbrains.buildServer.serverSide.BuildServerListener;
+import jetbrains.buildServer.serverSide.ServerResponsibility;
+import jetbrains.buildServer.serverSide.TeamCityProperties;
+import jetbrains.buildServer.util.EventDispatcher;
+import jetbrains.buildServer.util.ThreadUtil;
+import jetbrains.buildServer.util.executors.ExecutorsFactory;
+import jetbrains.buildServer.vcs.*;
+import org.eclipse.jgit.lib.Repository;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+
+public class GitClonesUpdater {
+  private final ConcurrentHashMap<VcsRoot, RepositoryStateData> myScheduledForUpdate = new ConcurrentHashMap<>();
+  private final GitVcsSupport myVcs;
+  private final RepositoryManager myRepositoryManager;
+  private volatile ExecutorService myExecutor;
+
+  public GitClonesUpdater(@NotNull EventDispatcher<RepositoryStateListener> eventDispatcher,
+                          @NotNull EventDispatcher<BuildServerListener> serverEventDispatcher,
+                          @NotNull ServerResponsibility serverResponsibility,
+                          @NotNull GitVcsSupport gitVcsSupport,
+                          @NotNull RepositoryManager repositoryManager) {
+    myVcs = gitVcsSupport;
+    myRepositoryManager = repositoryManager;
+
+    eventDispatcher.addListener(new RepositoryStateListenerAdapter() {
+      @Override
+      public void repositoryStateChanged(@NotNull final VcsRoot root,
+                                         @NotNull final RepositoryState oldState,
+                                         @NotNull final RepositoryState newState) {
+        if (serverResponsibility.canCheckForChanges()) return;
+
+        if (!TeamCityProperties.getBoolean("teamcity.git.localClones.updateIfNoCheckingForChangesResponsibility")) return;
+
+        if (root.getVcsName().equals(Constants.VCS_NAME)) {
+          myScheduledForUpdate.put(root, RepositoryStateFactory.toData(newState));
+          synchronized (myScheduledForUpdate) {
+            if (myExecutor == null) {
+              myExecutor = ExecutorsFactory.newFixedDaemonExecutor("Git local clones updater",
+                                                                   TeamCityProperties.getInteger("teamcity.git.localClones.maxParallelUpdateThreads", 2));
+            }
+          }
+          myExecutor.submit(GitClonesUpdater.this::processVcsRootsScheduledForUpdate);
+        }
+      }
+    });
+
+    serverEventDispatcher.addListener(new BuildServerAdapter() {
+      @Override
+      public void serverShutdown() {
+        if (myExecutor != null) {
+          ThreadUtil.shutdownGracefully(myExecutor, "Git local clones updater");
+        }
+      }
+    });
+  }
+
+  private void processVcsRootsScheduledForUpdate() {
+    Set<VcsRoot> vcsRoots = new HashSet<>(myScheduledForUpdate.keySet());
+    for (VcsRoot root: vcsRoots) {
+      RepositoryStateData state = myScheduledForUpdate.remove(root);
+      if (state == null) continue;
+
+      OperationContext context = myVcs.createContext(root, "updating local clone");
+      try {
+        GitVcsRoot gitRoot = context.getGitRoot();
+        myRepositoryManager.runWithDisabledRemove(gitRoot.getRepositoryDir(), () -> {
+          Repository repo = context.getRepository();
+          try {
+            myVcs.getCollectChangesPolicy().ensureRepositoryStateLoadedFor(context, repo, true, state);
+          } catch (Exception e1) {
+            throw new VcsException(e1);
+          }
+        });
+      } catch (VcsException e1) {
+        Loggers.VCS.warnAndDebugDetails("Could not update local clone for: " + LogUtil.describe(root), e1);
+      }
+    }
+  }
+}