1 /*
2 * Copyright (C) 2008-2010, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44 package org.eclipse.jgit.transport;
45
46 import static org.eclipse.jgit.lib.Constants.HEAD;
47 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
48 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
49 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
50
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.OutputStream;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.List;
57 import java.util.Map;
58 import java.util.Set;
59
60 import org.eclipse.jgit.annotations.Nullable;
61 import org.eclipse.jgit.errors.UnpackException;
62 import org.eclipse.jgit.lib.ConfigConstants;
63 import org.eclipse.jgit.lib.Constants;
64 import org.eclipse.jgit.lib.NullProgressMonitor;
65 import org.eclipse.jgit.lib.ObjectId;
66 import org.eclipse.jgit.lib.Ref;
67 import org.eclipse.jgit.lib.Repository;
68 import org.eclipse.jgit.revwalk.RevWalk;
69 import org.eclipse.jgit.transport.ReceiveCommand.Result;
70 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
71
72 /**
73 * Implements the server side of a push connection, receiving objects.
74 */
75 public class ReceivePack extends BaseReceivePack {
76 /** Hook to validate the update commands before execution. */
77 private PreReceiveHook preReceive;
78
79 /** Hook to report on the commands after execution. */
80 private PostReceiveHook postReceive;
81
82 /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */
83 private boolean reportStatus;
84
85 /** Whether the client intends to use push options. */
86 private boolean usePushOptions;
87 private List<String> pushOptions;
88
89 /**
90 * Create a new pack receive for an open repository.
91 *
92 * @param into
93 * the destination repository.
94 */
95 public ReceivePack(Repository into) {
96 super(into);
97 preReceive = PreReceiveHook.NULL;
98 postReceive = PostReceiveHook.NULL;
99 }
100
101 /**
102 * Get the repository this receive completes into.
103 *
104 * @return the repository this receive completes into.
105 */
106 @Override
107 public final Repository getRepository() {
108 return db;
109 }
110
111 /**
112 * Get the RevWalk instance used by this connection.
113 *
114 * @return the RevWalk instance used by this connection.
115 */
116 @Override
117 public final RevWalk getRevWalk() {
118 return walk;
119 }
120
121 /**
122 * Get refs which were advertised to the client.
123 *
124 * @return all refs which were advertised to the client, or null if
125 * {@link #setAdvertisedRefs(Map, Set)} has not been called yet.
126 */
127 @Override
128 public final Map<String, Ref> getAdvertisedRefs() {
129 return refs;
130 }
131
132 /**
133 * Set the refs advertised by this ReceivePack.
134 * <p>
135 * Intended to be called from a
136 * {@link org.eclipse.jgit.transport.PreReceiveHook}.
137 *
138 * @param allRefs
139 * explicit set of references to claim as advertised by this
140 * ReceivePack instance. This overrides any references that may
141 * exist in the source repository. The map is passed to the
142 * configured {@link #getRefFilter()}. If null, assumes all refs
143 * were advertised.
144 * @param additionalHaves
145 * explicit set of additional haves to claim as advertised. If
146 * null, assumes the default set of additional haves from the
147 * repository.
148 */
149 @Override
150 public void setAdvertisedRefs(Map<String, Ref> allRefs, Set<ObjectId> additionalHaves) {
151 refs = allRefs != null ? allRefs : db.getAllRefs();
152 refs = refFilter.filter(refs);
153 advertisedHaves.clear();
154
155 Ref head = refs.get(HEAD);
156 if (head != null && head.isSymbolic()) {
157 refs.remove(HEAD);
158 }
159
160 for (Ref ref : refs.values()) {
161 if (ref.getObjectId() != null) {
162 advertisedHaves.add(ref.getObjectId());
163 }
164 }
165 if (additionalHaves != null) {
166 advertisedHaves.addAll(additionalHaves);
167 } else {
168 advertisedHaves.addAll(db.getAdditionalHaves());
169 }
170 }
171
172 /**
173 * Get the push certificate used to verify the pusher's identity.
174 * <p>
175 * Only valid after commands are read from the wire.
176 *
177 * @return the parsed certificate, or null if push certificates are disabled
178 * or no cert was presented by the client.
179 * @since 4.1
180 */
181 @Override
182 public PushCertificate getPushCertificate() {
183 return pushCert;
184 }
185
186 /**
187 * Set the push certificate used to verify the pusher's identity.
188 * <p>
189 * Should only be called if reconstructing an instance without going through
190 * the normal {@link #recvCommands()} flow.
191 *
192 * @param cert
193 * the push certificate to set.
194 * @since 4.1
195 */
196 @Override
197 public void setPushCertificate(PushCertificate cert) {
198 pushCert = cert;
199 }
200
201 /**
202 * Gets an unmodifiable view of the option strings associated with the push.
203 *
204 * @return an unmodifiable view of pushOptions, or null (if pushOptions is).
205 * @since 4.5
206 */
207 @Nullable
208 public List<String> getPushOptions() {
209 if (isAllowPushOptions() && usePushOptions) {
210 return Collections.unmodifiableList(pushOptions);
211 }
212
213 // The client doesn't support push options. Return null to
214 // distinguish this from the case where the client declared support
215 // for push options and sent an empty list of them.
216 return null;
217 }
218
219 /**
220 * Set the push options supplied by the client.
221 * <p>
222 * Should only be called if reconstructing an instance without going through
223 * the normal {@link #recvCommands()} flow.
224 *
225 * @param options
226 * the list of options supplied by the client. The
227 * {@code ReceivePack} instance takes ownership of this list.
228 * Callers are encouraged to first create a copy if the list may
229 * be modified later.
230 * @since 4.5
231 */
232 public void setPushOptions(@Nullable List<String> options) {
233 usePushOptions = options != null;
234 pushOptions = options;
235 }
236
237 /**
238 * Get the hook invoked before updates occur.
239 *
240 * @return the hook invoked before updates occur.
241 */
242 public PreReceiveHook getPreReceiveHook() {
243 return preReceive;
244 }
245
246 /**
247 * Set the hook which is invoked prior to commands being executed.
248 * <p>
249 * Only valid commands (those which have no obvious errors according to the
250 * received input and this instance's configuration) are passed into the
251 * hook. The hook may mark a command with a result of any value other than
252 * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} to
253 * block its execution.
254 * <p>
255 * The hook may be called with an empty command collection if the current
256 * set is completely invalid.
257 *
258 * @param h
259 * the hook instance; may be null to disable the hook.
260 */
261 public void setPreReceiveHook(PreReceiveHook h) {
262 preReceive = h != null ? h : PreReceiveHook.NULL;
263 }
264
265 /**
266 * Get the hook invoked after updates occur.
267 *
268 * @return the hook invoked after updates occur.
269 */
270 public PostReceiveHook getPostReceiveHook() {
271 return postReceive;
272 }
273
274 /**
275 * Set the hook which is invoked after commands are executed.
276 * <p>
277 * Only successful commands (type is
278 * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK}) are passed
279 * into the hook. The hook may be called with an empty command collection if
280 * the current set all resulted in an error.
281 *
282 * @param h
283 * the hook instance; may be null to disable the hook.
284 */
285 public void setPostReceiveHook(PostReceiveHook h) {
286 postReceive = h != null ? h : PostReceiveHook.NULL;
287 }
288
289 /**
290 * Set whether this class will report command failures as warning messages
291 * before sending the command results.
292 *
293 * @param echo
294 * if true this class will report command failures as warning
295 * messages before sending the command results. This is usually
296 * not necessary, but may help buggy Git clients that discard the
297 * errors when all branches fail.
298 * @deprecated no widely used Git versions need this any more
299 */
300 @Deprecated
301 public void setEchoCommandFailures(boolean echo) {
302 // No-op.
303 }
304
305 /**
306 * Execute the receive task on the socket.
307 *
308 * @param input
309 * raw input to read client commands and pack data from. Caller
310 * must ensure the input is buffered, otherwise read performance
311 * may suffer.
312 * @param output
313 * response back to the Git network client. Caller must ensure
314 * the output is buffered, otherwise write performance may
315 * suffer.
316 * @param messages
317 * secondary "notice" channel to send additional messages out
318 * through. When run over SSH this should be tied back to the
319 * standard error channel of the command execution. For most
320 * other network connections this should be null.
321 * @throws java.io.IOException
322 */
323 public void receive(final InputStream input, final OutputStream output,
324 final OutputStream messages) throws IOException {
325 init(input, output, messages);
326 try {
327 service();
328 } finally {
329 try {
330 close();
331 } finally {
332 release();
333 }
334 }
335 }
336
337 /** {@inheritDoc} */
338 @Override
339 protected void enableCapabilities() {
340 reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS);
341 usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
342 super.enableCapabilities();
343 }
344
345 @Override
346 void readPostCommands(PacketLineIn in) throws IOException {
347 if (usePushOptions) {
348 pushOptions = new ArrayList<>(4);
349 for (;;) {
350 String option = in.readString();
351 if (option == PacketLineIn.END) {
352 break;
353 }
354 pushOptions.add(option);
355 }
356 }
357 }
358
359 private void service() throws IOException {
360 if (isBiDirectionalPipe()) {
361 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
362 pckOut.flush();
363 } else
364 getAdvertisedOrDefaultRefs();
365 if (hasError())
366 return;
367 recvCommands();
368 if (hasCommands()) {
369 Throwable unpackError = null;
370 if (needPack()) {
371 try {
372 receivePackAndCheckConnectivity();
373 } catch (IOException | RuntimeException | Error err) {
374 unpackError = err;
375 }
376 }
377
378 try {
379 if (unpackError == null) {
380 boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
381 setAtomic(atomic);
382
383 validateCommands();
384 if (atomic && anyRejects()) {
385 failPendingCommands();
386 }
387
388 preReceive.onPreReceive(
389 this, filterCommands(Result.NOT_ATTEMPTED));
390 if (atomic && anyRejects()) {
391 failPendingCommands();
392 }
393 executeCommands();
394 }
395 } finally {
396 unlockPack();
397 }
398
399 if (reportStatus) {
400 sendStatusReport(true, unpackError, new Reporter() {
401 @Override
402 void sendString(String s) throws IOException {
403 pckOut.writeString(s + "\n"); //$NON-NLS-1$
404 }
405 });
406 pckOut.end();
407 } else if (msgOut != null) {
408 sendStatusReport(false, unpackError, new Reporter() {
409 @Override
410 void sendString(String s) throws IOException {
411 msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
412 }
413 });
414 }
415
416 if (unpackError != null) {
417 // we already know which exception to throw. Ignore
418 // potential additional exceptions raised in postReceiveHooks
419 try {
420 postReceive.onPostReceive(this, filterCommands(Result.OK));
421 } catch (Throwable e) {
422 // empty
423 }
424 throw new UnpackException(unpackError);
425 }
426 postReceive.onPostReceive(this, filterCommands(Result.OK));
427 autoGc();
428 }
429 }
430
431 private void autoGc() {
432 Repository repo = getRepository();
433 if (!repo.getConfig().getBoolean(ConfigConstants.CONFIG_RECEIVE_SECTION,
434 ConfigConstants.CONFIG_KEY_AUTOGC, true)) {
435 return;
436 }
437 repo.autoGC(NullProgressMonitor.INSTANCE);
438 }
439
440 /** {@inheritDoc} */
441 @Override
442 protected String getLockMessageProcessName() {
443 return "jgit receive-pack"; //$NON-NLS-1$
444 }
445 }