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(final 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 /** @return the hook invoked before updates occur. */
134 public PreReceiveHook getPreReceiveHook() {
135 return preReceive;
136 }
137
138 /**
139 * Set the hook which is invoked prior to commands being executed.
140 * <p>
141 * Only valid commands (those which have no obvious errors according to the
142 * received input and this instance's configuration) are passed into the
143 * hook. The hook may mark a command with a result of any value other than
144 * {@link Result#NOT_ATTEMPTED} to block its execution.
145 * <p>
146 * The hook may be called with an empty command collection if the current
147 * set is completely invalid.
148 *
149 * @param h
150 * the hook instance; may be null to disable the hook.
151 */
152 public void setPreReceiveHook(final PreReceiveHook h) {
153 preReceive = h != null ? h : PreReceiveHook.NULL;
154 }
155
156 /** @return the hook invoked after updates occur. */
157 public PostReceiveHook getPostReceiveHook() {
158 return postReceive;
159 }
160
161 /**
162 * Set the hook which is invoked after commands are executed.
163 * <p>
164 * Only successful commands (type is {@link Result#OK}) are passed into the
165 * hook. The hook may be called with an empty command collection if the
166 * current set all resulted in an error.
167 *
168 * @param h
169 * the hook instance; may be null to disable the hook.
170 */
171 public void setPostReceiveHook(final PostReceiveHook h) {
172 postReceive = h != null ? h : PostReceiveHook.NULL;
173 }
174
175 /**
176 * @param echo
177 * if true this class will report command failures as warning
178 * messages before sending the command results. This is usually
179 * not necessary, but may help buggy Git clients that discard the
180 * errors when all branches fail.
181 */
182 public void setEchoCommandFailures(boolean echo) {
183 echoCommandFailures = echo;
184 }
185
186 /**
187 * Execute the receive task on the socket.
188 *
189 * @param input
190 * raw input to read client commands and pack data from. Caller
191 * must ensure the input is buffered, otherwise read performance
192 * may suffer.
193 * @param output
194 * response back to the Git network client. Caller must ensure
195 * the output is buffered, otherwise write performance may
196 * suffer.
197 * @param messages
198 * secondary "notice" channel to send additional messages out
199 * through. When run over SSH this should be tied back to the
200 * standard error channel of the command execution. For most
201 * other network connections this should be null.
202 * @throws IOException
203 */
204 public void receive(final InputStream input, final OutputStream output,
205 final OutputStream messages) throws IOException {
206 init(input, output, messages);
207 try {
208 service();
209 } finally {
210 try {
211 close();
212 } finally {
213 release();
214 }
215 }
216 }
217
218 @Override
219 protected void enableCapabilities() {
220 reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS);
221 usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
222 super.enableCapabilities();
223 }
224
225 @Override
226 void readPostCommands(PacketLineIn in) throws IOException {
227 if (usePushOptions) {
228 pushOptions = new ArrayList<>(4);
229 for (;;) {
230 String option = in.readString();
231 if (option == PacketLineIn.END) {
232 break;
233 }
234 pushOptions.add(option);
235 }
236 }
237 }
238
239 private void service() throws IOException {
240 if (isBiDirectionalPipe()) {
241 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
242 pckOut.flush();
243 } else
244 getAdvertisedOrDefaultRefs();
245 if (hasError())
246 return;
247 recvCommands();
248 if (hasCommands()) {
249 Throwable unpackError = null;
250 if (needPack()) {
251 try {
252 receivePackAndCheckConnectivity();
253 } catch (IOException | RuntimeException | Error err) {
254 unpackError = err;
255 }
256 }
257
258 if (unpackError == null) {
259 boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
260 setAtomic(atomic);
261
262 validateCommands();
263 if (atomic && anyRejects())
264 failPendingCommands();
265
266 preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
267 if (atomic && anyRejects())
268 failPendingCommands();
269 executeCommands();
270 }
271 unlockPack();
272
273 if (reportStatus) {
274 if (echoCommandFailures && msgOut != null) {
275 sendStatusReport(false, unpackError, new Reporter() {
276 @Override
277 void sendString(final String s) throws IOException {
278 msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
279 }
280 });
281 msgOut.flush();
282 try {
283 Thread.sleep(500);
284 } catch (InterruptedException wakeUp) {
285 // Ignore an early wake up.
286 }
287 }
288 sendStatusReport(true, unpackError, new Reporter() {
289 @Override
290 void sendString(final String s) throws IOException {
291 pckOut.writeString(s + "\n"); //$NON-NLS-1$
292 }
293 });
294 pckOut.end();
295 } else if (msgOut != null) {
296 sendStatusReport(false, unpackError, new Reporter() {
297 @Override
298 void sendString(final String s) throws IOException {
299 msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
300 }
301 });
302 }
303
304 if (unpackError != null) {
305 // we already know which exception to throw. Ignore
306 // potential additional exceptions raised in postReceiveHooks
307 try {
308 postReceive.onPostReceive(this, filterCommands(Result.OK));
309 } catch (Throwable e) {
310 // empty
311 }
312 throw new UnpackException(unpackError);
313 }
314 postReceive.onPostReceive(this, filterCommands(Result.OK));
315 autoGc();
316 }
317 }
318
319 private void autoGc() {
320 Repository repo = getRepository();
321 if (!repo.getConfig().getBoolean(ConfigConstants.CONFIG_RECEIVE_SECTION,
322 ConfigConstants.CONFIG_KEY_AUTOGC, true)) {
323 return;
324 }
325 repo.autoGC(NullProgressMonitor.INSTANCE);
326 }
327
328 @Override
329 protected String getLockMessageProcessName() {
330 return "jgit receive-pack"; //$NON-NLS-1$
331 }
332 }