Add using Memory-mapped index reader
[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.setupMemoryMappedIndexReading();
100     GitServerUtil.configureStreamFileThreshold(Integer.MAX_VALUE);
101
102     TransportFactory transportFactory = new TransportFactoryImpl(config, new EmptyVcsRootSshKeyManager(),
103                                                                  new GitTrustStoreProviderStatic(trustedCertificatesDir));
104     Transport tn = null;
105     try {
106       Repository repository = new RepositoryBuilder().setBare().setGitDir(repositoryDir).build();
107       workaroundRacyGit();
108       tn = transportFactory.createTransport(repository, new URIish(fetchUrl), auth);
109       try {
110         pruneRemovedBranches(config, repository, transportFactory, tn, new URIish(fetchUrl), auth);
111       } catch (Exception e) {
112         System.err.println("Error while pruning removed branches: " + e.getMessage());
113         e.printStackTrace(System.err);
114       }
115       FetchResult result = GitServerUtil.fetch(repository, new URIish(fetchUrl), auth, transportFactory, tn, progressMonitor,
116                                                parseRefspecs(refspecs), config.ignoreMissingRemoteRef());
117       GitServerUtil.checkFetchSuccessful(repository, result);
118       logFetchResults(result);
119     } finally {
120       if (tn != null)
121         tn.close();
122     }
123   }
124
125   private static void pruneRemovedBranches(@NotNull ServerPluginConfig config,
126                                            @NotNull Repository db,
127                                            @NotNull TransportFactory transportFactory,
128                                            @NotNull Transport tn,
129                                            @NotNull URIish uri,
130                                            @NotNull AuthSettings authSettings) throws IOException, VcsException {
131     GitServerUtil.pruneRemovedBranches(config, transportFactory, tn, db, uri, authSettings);
132   }
133
134   private static void logFetchResults(@NotNull FetchResult result) {
135     for (TrackingRefUpdate update : result.getTrackingRefUpdates()) {
136       StringBuilder msg = new StringBuilder();
137       msg.append("update ref remote name: ").append(update.getRemoteName())
138         .append(", local name: ").append(update.getLocalName())
139         .append(", old object id: ").append(update.getOldObjectId().name())
140         .append(", new object id: ").append(update.getNewObjectId().name())
141         .append(", result: ").append(update.getResult());
142       System.out.println(msg);
143     }
144     String additionalMsgs = result.getMessages();
145     if (additionalMsgs.length() > 0) {
146       System.out.println("Remote process messages: " + additionalMsgs);
147     }
148   }
149
150   private static boolean isImportant(Throwable t) {
151     return t instanceof NullPointerException ||
152            t instanceof Error ||
153            t instanceof InterruptedException ||
154            t instanceof InterruptedIOException;
155   }
156
157   /**
158    * Fetch could be so fast that even though it downloads some new packs
159    * a timestamp of objects/packs dir is not changed (at least on linux).
160    * If timestamp of that dir is not changed from the last read, jgit assumes
161    * there is nothing new there and could not find object even if it already
162    * exists in repository. This method sleeps for 1 second, so subsequent
163    * write to objects/pack dir will change its timestamp.
164    */
165   private static void workaroundRacyGit() {
166     try {
167       Thread.sleep(1000);
168     } catch (InterruptedException e) {
169       Thread.currentThread().interrupt();
170     }
171   }
172
173   private static Collection<RefSpec> parseRefspecs(String refspecs) {
174     String[] specs = refspecs.split(Constants.RECORD_SEPARATOR);
175     List<RefSpec> result = new ArrayList<RefSpec>();
176     for (String spec : specs) {
177       result.add(new RefSpec(spec).setForceUpdate(true));
178     }
179     return result;
180   }
181
182   private static class Monitoring implements Runnable {
183
184     private final File myFile;
185     private final ByteArrayOutputStream myGitOutput;
186
187     Monitoring(@NotNull String threadDumpFilePath, @NotNull ByteArrayOutputStream gitOutput) {
188       myFile = new File(threadDumpFilePath);
189       myGitOutput = gitOutput;
190     }
191
192     public void run() {
193       String threadDump = DiagnosticUtil.threadDumpToString();
194       String gitProgress = myGitOutput.toString();
195       FileUtil.writeFile(myFile, threadDump + "\ngit progress:\n" + gitProgress);
196     }
197   }
198 }