214ccf0e46e5ed5f74bac9d7dc5cc88691614cd2
[teamcity/git-plugin.git] / git-server / src / jetbrains / buildServer / buildTriggers / vcs / git / Fetcher.java
1 /*
2  * Copyright 2000-2018 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package jetbrains.buildServer.buildTriggers.vcs.git;
18
19 import jetbrains.buildServer.util.DiagnosticUtil;
20 import jetbrains.buildServer.util.FileUtil;
21 import jetbrains.buildServer.vcs.VcsException;
22 import jetbrains.buildServer.vcs.VcsUtil;
23 import org.eclipse.jgit.lib.ProgressMonitor;
24 import org.eclipse.jgit.lib.Repository;
25 import org.eclipse.jgit.lib.RepositoryBuilder;
26 import org.eclipse.jgit.transport.*;
27 import org.jetbrains.annotations.NotNull;
28
29 import java.io.*;
30 import java.net.URISyntaxException;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.concurrent.Executors;
36 import java.util.concurrent.ScheduledExecutorService;
37 import java.util.concurrent.TimeUnit;
38
39 /**
40  * Method main of this class is supposed to be run in separate process to avoid OutOfMemoryExceptions in server's process
41  *
42  * @author dmitry.neverov
43  */
44 public class Fetcher {
45
46   public static void main(String[] args) throws IOException, VcsException, URISyntaxException {
47     boolean debug = false;
48     ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
49     final long start = System.currentTimeMillis();
50     try {
51       Map<String, String> properties = VcsUtil.stringToProperties(GitServerUtil.readInput());
52       String threadDumpFilePath = properties.remove(Constants.THREAD_DUMP_FILE);
53       String repositoryPath = properties.remove(Constants.REPOSITORY_DIR_PROPERTY_NAME);
54       debug = "true".equals(properties.remove(Constants.VCS_DEBUG_ENABLED));
55
56       GitServerUtil.configureExternalProcessLogger(debug);
57
58       String internalPropsFile = properties.remove(Constants.FETCHER_INTERNAL_PROPERTIES_FILE);
59       GitServerUtil.configureInternalProperties(new File(internalPropsFile));
60
61       ByteArrayOutputStream output = new ByteArrayOutputStream();
62       FetchProgressMonitor progress = new FetchProgressMonitor(new PrintStream(output));
63       exec.scheduleAtFixedRate(new Monitoring(threadDumpFilePath, output), 10, 10, TimeUnit.SECONDS);
64       fetch(new File(repositoryPath), properties, progress);
65
66       if (System.currentTimeMillis() - start <= new PluginConfigImpl().getMonitoringFileThresholdMillis()) {
67         FileUtil.delete(new File(threadDumpFilePath));
68       }
69     } catch (Throwable t) {
70       if (debug || isImportant(t)) {
71         t.printStackTrace(System.err);
72       } else {
73         System.err.println(t.getMessage());
74       }
75       System.exit(1);
76     } finally {
77       exec.shutdown();
78     }
79   }
80
81   /**
82    * Do fetch in directory <code>repositoryDir</code> with vcsRootProperties from <code>vcsRootProperties</code>
83    *
84    * @param repositoryDir     directory where run fetch
85    * @param vcsRootProperties properties of vcsRoot
86    * @throws IOException
87    * @throws VcsException
88    * @throws URISyntaxException
89    */
90   private static void fetch(@NotNull File repositoryDir,
91                             @NotNull Map<String, String> vcsRootProperties,
92                             @NotNull ProgressMonitor progressMonitor) throws IOException, VcsException, URISyntaxException {
93     final String fetchUrl = vcsRootProperties.get(Constants.FETCH_URL);
94     final String refspecs = vcsRootProperties.get(Constants.REFSPEC);
95     final String trustedCertificatesDir = vcsRootProperties.get(Constants.GIT_TRUST_STORE_PROVIDER);
96     AuthSettings auth = new AuthSettings(vcsRootProperties);
97     PluginConfigImpl config = new PluginConfigImpl();
98
99     GitServerUtil.configureStreamFileThreshold(Integer.MAX_VALUE);
100
101     TransportFactory transportFactory = new TransportFactoryImpl(config, new EmptyVcsRootSshKeyManager(),
102                                                                  new GitTrustStoreProviderStatic(trustedCertificatesDir));
103     Transport tn = null;
104     try {
105       Repository repository = new RepositoryBuilder().setBare().setGitDir(repositoryDir).build();
106       workaroundRacyGit();
107       tn = transportFactory.createTransport(repository, new URIish(fetchUrl), auth);
108       try {
109         pruneRemovedBranches(config, repository, transportFactory, tn, new URIish(fetchUrl), auth);
110       } catch (Exception e) {
111         System.err.println("Error while pruning removed branches: " + e.getMessage());
112         e.printStackTrace(System.err);
113       }
114       FetchResult result = GitServerUtil.fetch(repository, new URIish(fetchUrl), auth, transportFactory, tn, progressMonitor,
115                                                parseRefspecs(refspecs), config.ignoreMissingRemoteRef());
116       GitServerUtil.checkFetchSuccessful(repository, result);
117       logFetchResults(result);
118     } finally {
119       if (tn != null)
120         tn.close();
121     }
122   }
123
124   private static void pruneRemovedBranches(@NotNull ServerPluginConfig config,
125                                            @NotNull Repository db,
126                                            @NotNull TransportFactory transportFactory,
127                                            @NotNull Transport tn,
128                                            @NotNull URIish uri,
129                                            @NotNull AuthSettings authSettings) throws IOException, VcsException {
130     GitServerUtil.pruneRemovedBranches(config, transportFactory, tn, db, uri, authSettings);
131   }
132
133   private static void logFetchResults(@NotNull FetchResult result) {
134     for (TrackingRefUpdate update : result.getTrackingRefUpdates()) {
135       StringBuilder msg = new StringBuilder();
136       msg.append("update ref remote name: ").append(update.getRemoteName())
137         .append(", local name: ").append(update.getLocalName())
138         .append(", old object id: ").append(update.getOldObjectId().name())
139         .append(", new object id: ").append(update.getNewObjectId().name())
140         .append(", result: ").append(update.getResult());
141       System.out.println(msg);
142     }
143     String additionalMsgs = result.getMessages();
144     if (additionalMsgs.length() > 0) {
145       System.out.println("Remote process messages: " + additionalMsgs);
146     }
147   }
148
149   private static boolean isImportant(Throwable t) {
150     return t instanceof NullPointerException ||
151            t instanceof Error ||
152            t instanceof InterruptedException ||
153            t instanceof InterruptedIOException;
154   }
155
156   /**
157    * Fetch could be so fast that even though it downloads some new packs
158    * a timestamp of objects/packs dir is not changed (at least on linux).
159    * If timestamp of that dir is not changed from the last read, jgit assumes
160    * there is nothing new there and could not find object even if it already
161    * exists in repository. This method sleeps for 1 second, so subsequent
162    * write to objects/pack dir will change its timestamp.
163    */
164   private static void workaroundRacyGit() {
165     try {
166       Thread.sleep(1000);
167     } catch (InterruptedException e) {
168       Thread.currentThread().interrupt();
169     }
170   }
171
172   private static Collection<RefSpec> parseRefspecs(String refspecs) {
173     String[] specs = refspecs.split(Constants.RECORD_SEPARATOR);
174     List<RefSpec> result = new ArrayList<RefSpec>();
175     for (String spec : specs) {
176       result.add(new RefSpec(spec).setForceUpdate(true));
177     }
178     return result;
179   }
180
181   private static class Monitoring implements Runnable {
182
183     private final File myFile;
184     private final ByteArrayOutputStream myGitOutput;
185
186     Monitoring(@NotNull String threadDumpFilePath, @NotNull ByteArrayOutputStream gitOutput) {
187       myFile = new File(threadDumpFilePath);
188       myGitOutput = gitOutput;
189     }
190
191     public void run() {
192       String threadDump = DiagnosticUtil.threadDumpToString();
193       String gitProgress = myGitOutput.toString();
194       FileUtil.writeFile(myFile, threadDump + "\ngit progress:\n" + gitProgress);
195     }
196   }
197 }