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.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
47 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_PUSH_OPTIONS;
48 import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
49
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.OutputStream;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.List;
56
57 import org.eclipse.jgit.annotations.Nullable;
58 import org.eclipse.jgit.errors.UnpackException;
59 import org.eclipse.jgit.lib.ConfigConstants;
60 import org.eclipse.jgit.lib.Constants;
61 import org.eclipse.jgit.lib.NullProgressMonitor;
62 import org.eclipse.jgit.lib.Repository;
63 import org.eclipse.jgit.transport.ReceiveCommand.Result;
64 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
65
66 /**
67 * Implements the server side of a push connection, receiving objects.
68 */
69 public class ReceivePack extends BaseReceivePack {
70 /** Hook to validate the update commands before execution. */
71 private PreReceiveHook preReceive;
72
73 /** Hook to report on the commands after execution. */
74 private PostReceiveHook postReceive;
75
76 /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */
77 private boolean reportStatus;
78
79 private boolean echoCommandFailures;
80
81 /** Whether the client intends to use push options. */
82 private boolean usePushOptions;
83 private List<String> pushOptions;
84
85 /**
86 * Create a new pack receive for an open repository.
87 *
88 * @param into
89 * the destination repository.
90 */
91 public ReceivePack(Repository into) {
92 super(into);
93 preReceive = PreReceiveHook.NULL;
94 postReceive = PostReceiveHook.NULL;
95 }
96
97 /**
98 * Gets an unmodifiable view of the option strings associated with the push.
99 *
100 * @return an unmodifiable view of pushOptions, or null (if pushOptions is).
101 * @since 4.5
102 */
103 @Nullable
104 public List<String> getPushOptions() {
105 if (isAllowPushOptions() && usePushOptions) {
106 return Collections.unmodifiableList(pushOptions);
107 }
108
109 // The client doesn't support push options. Return null to
110 // distinguish this from the case where the client declared support
111 // for push options and sent an empty list of them.
112 return null;
113 }
114
115 /**
116 * Set the push options supplied by the client.
117 * <p>
118 * Should only be called if reconstructing an instance without going through
119 * the normal {@link #recvCommands()} flow.
120 *
121 * @param options
122 * the list of options supplied by the client. The
123 * {@code ReceivePack} instance takes ownership of this list.
124 * Callers are encouraged to first create a copy if the list may
125 * be modified later.
126 * @since 4.5
127 */
128 public void setPushOptions(@Nullable List<String> options) {
129 usePushOptions = options != null;
130 pushOptions = options;
131 }
132
133 /**
134 * Get the hook invoked before updates occur.
135 *
136 * @return the hook invoked before updates occur.
137 */
138 public PreReceiveHook getPreReceiveHook() {
139 return preReceive;
140 }
141
142 /**
143 * Set the hook which is invoked prior to commands being executed.
144 * <p>
145 * Only valid commands (those which have no obvious errors according to the
146 * received input and this instance's configuration) are passed into the
147 * hook. The hook may mark a command with a result of any value other than
148 * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#NOT_ATTEMPTED} to
149 * block its execution.
150 * <p>
151 * The hook may be called with an empty command collection if the current
152 * set is completely invalid.
153 *
154 * @param h
155 * the hook instance; may be null to disable the hook.
156 */
157 public void setPreReceiveHook(PreReceiveHook h) {
158 preReceive = h != null ? h : PreReceiveHook.NULL;
159 }
160
161 /**
162 * Get the hook invoked after updates occur.
163 *
164 * @return the hook invoked after updates occur.
165 */
166 public PostReceiveHook getPostReceiveHook() {
167 return postReceive;
168 }
169
170 /**
171 * Set the hook which is invoked after commands are executed.
172 * <p>
173 * Only successful commands (type is
174 * {@link org.eclipse.jgit.transport.ReceiveCommand.Result#OK}) are passed
175 * into the hook. The hook may be called with an empty command collection if
176 * the current set all resulted in an error.
177 *
178 * @param h
179 * the hook instance; may be null to disable the hook.
180 */
181 public void setPostReceiveHook(PostReceiveHook h) {
182 postReceive = h != null ? h : PostReceiveHook.NULL;
183 }
184
185 /**
186 * Set whether this class will report command failures as warning messages
187 * before sending the command results.
188 *
189 * @param echo
190 * if true this class will report command failures as warning
191 * messages before sending the command results. This is usually
192 * not necessary, but may help buggy Git clients that discard the
193 * errors when all branches fail.
194 */
195 public void setEchoCommandFailures(boolean echo) {
196 echoCommandFailures = echo;
197 }
198
199 /**
200 * Execute the receive task on the socket.
201 *
202 * @param input
203 * raw input to read client commands and pack data from. Caller
204 * must ensure the input is buffered, otherwise read performance
205 * may suffer.
206 * @param output
207 * response back to the Git network client. Caller must ensure
208 * the output is buffered, otherwise write performance may
209 * suffer.
210 * @param messages
211 * secondary "notice" channel to send additional messages out
212 * through. When run over SSH this should be tied back to the
213 * standard error channel of the command execution. For most
214 * other network connections this should be null.
215 * @throws java.io.IOException
216 */
217 public void receive(final InputStream input, final OutputStream output,
218 final OutputStream messages) throws IOException {
219 init(input, output, messages);
220 try {
221 service();
222 } finally {
223 try {
224 close();
225 } finally {
226 release();
227 }
228 }
229 }
230
231 /** {@inheritDoc} */
232 @Override
233 protected void enableCapabilities() {
234 reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS);
235 usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
236 super.enableCapabilities();
237 }
238
239 @Override
240 void readPostCommands(PacketLineIn in) throws IOException {
241 if (usePushOptions) {
242 pushOptions = new ArrayList<>(4);
243 for (;;) {
244 String option = in.readString();
245 if (option == PacketLineIn.END) {
246 break;
247 }
248 pushOptions.add(option);
249 }
250 }
251 }
252
253 private void service() throws IOException {
254 if (isBiDirectionalPipe()) {
255 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
256 pckOut.flush();
257 } else
258 getAdvertisedOrDefaultRefs();
259 if (hasError())
260 return;
261 recvCommands();
262 if (hasCommands()) {
263 Throwable unpackError = null;
264 if (needPack()) {
265 try {
266 receivePackAndCheckConnectivity();
267 } catch (IOException | RuntimeException | Error err) {
268 unpackError = err;
269 }
270 }
271
272 if (unpackError == null) {
273 boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
274 setAtomic(atomic);
275
276 validateCommands();
277 if (atomic && anyRejects())
278 failPendingCommands();
279
280 preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
281 if (atomic && anyRejects())
282 failPendingCommands();
283 executeCommands();
284 }
285 unlockPack();
286
287 if (reportStatus) {
288 if (echoCommandFailures && msgOut != null) {
289 sendStatusReport(false, unpackError, new Reporter() {
290 @Override
291 void sendString(String s) throws IOException {
292 msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
293 }
294 });
295 msgOut.flush();
296 try {
297 Thread.sleep(500);
298 } catch (InterruptedException wakeUp) {
299 // Ignore an early wake up.
300 }
301 }
302 sendStatusReport(true, unpackError, new Reporter() {
303 @Override
304 void sendString(String s) throws IOException {
305 pckOut.writeString(s + "\n"); //$NON-NLS-1$
306 }
307 });
308 pckOut.end();
309 } else if (msgOut != null) {
310 sendStatusReport(false, unpackError, new Reporter() {
311 @Override
312 void sendString(String s) throws IOException {
313 msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
314 }
315 });
316 }
317
318 if (unpackError != null) {
319 // we already know which exception to throw. Ignore
320 // potential additional exceptions raised in postReceiveHooks
321 try {
322 postReceive.onPostReceive(this, filterCommands(Result.OK));
323 } catch (Throwable e) {
324 // empty
325 }
326 throw new UnpackException(unpackError);
327 }
328 postReceive.onPostReceive(this, filterCommands(Result.OK));
329 autoGc();
330 }
331 }
332
333 private void autoGc() {
334 Repository repo = getRepository();
335 if (!repo.getConfig().getBoolean(ConfigConstants.CONFIG_RECEIVE_SECTION,
336 ConfigConstants.CONFIG_KEY_AUTOGC, true)) {
337 return;
338 }
339 repo.autoGC(NullProgressMonitor.INSTANCE);
340 }
341
342 /** {@inheritDoc} */
343 @Override
344 protected String getLockMessageProcessName() {
345 return "jgit receive-pack"; //$NON-NLS-1$
346 }
347 }