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_REPORT_STATUS;
48
49 import java.io.IOException;
50 import java.io.InputStream;
51 import java.io.OutputStream;
52
53 import org.eclipse.jgit.errors.UnpackException;
54 import org.eclipse.jgit.lib.Constants;
55 import org.eclipse.jgit.lib.Repository;
56 import org.eclipse.jgit.transport.ReceiveCommand.Result;
57 import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
58
59 /**
60 * Implements the server side of a push connection, receiving objects.
61 */
62 public class ReceivePack extends BaseReceivePack {
63 /** Hook to validate the update commands before execution. */
64 private PreReceiveHook preReceive;
65
66 /** Hook to report on the commands after execution. */
67 private PostReceiveHook postReceive;
68
69 /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */
70 private boolean reportStatus;
71
72 private boolean echoCommandFailures;
73
74 /**
75 * Create a new pack receive for an open repository.
76 *
77 * @param into
78 * the destination repository.
79 */
80 public ReceivePack(final Repository into) {
81 super(into);
82 preReceive = PreReceiveHook.NULL;
83 postReceive = PostReceiveHook.NULL;
84 }
85
86 /** @return the hook invoked before updates occur. */
87 public PreReceiveHook getPreReceiveHook() {
88 return preReceive;
89 }
90
91 /**
92 * Set the hook which is invoked prior to commands being executed.
93 * <p>
94 * Only valid commands (those which have no obvious errors according to the
95 * received input and this instance's configuration) are passed into the
96 * hook. The hook may mark a command with a result of any value other than
97 * {@link Result#NOT_ATTEMPTED} to block its execution.
98 * <p>
99 * The hook may be called with an empty command collection if the current
100 * set is completely invalid.
101 *
102 * @param h
103 * the hook instance; may be null to disable the hook.
104 */
105 public void setPreReceiveHook(final PreReceiveHook h) {
106 preReceive = h != null ? h : PreReceiveHook.NULL;
107 }
108
109 /** @return the hook invoked after updates occur. */
110 public PostReceiveHook getPostReceiveHook() {
111 return postReceive;
112 }
113
114 /**
115 * Set the hook which is invoked after commands are executed.
116 * <p>
117 * Only successful commands (type is {@link Result#OK}) are passed into the
118 * hook. The hook may be called with an empty command collection if the
119 * current set all resulted in an error.
120 *
121 * @param h
122 * the hook instance; may be null to disable the hook.
123 */
124 public void setPostReceiveHook(final PostReceiveHook h) {
125 postReceive = h != null ? h : PostReceiveHook.NULL;
126 }
127
128 /**
129 * @param echo
130 * if true this class will report command failures as warning
131 * messages before sending the command results. This is usually
132 * not necessary, but may help buggy Git clients that discard the
133 * errors when all branches fail.
134 */
135 public void setEchoCommandFailures(boolean echo) {
136 echoCommandFailures = echo;
137 }
138
139 /**
140 * Execute the receive task on the socket.
141 *
142 * @param input
143 * raw input to read client commands and pack data from. Caller
144 * must ensure the input is buffered, otherwise read performance
145 * may suffer.
146 * @param output
147 * response back to the Git network client. Caller must ensure
148 * the output is buffered, otherwise write performance may
149 * suffer.
150 * @param messages
151 * secondary "notice" channel to send additional messages out
152 * through. When run over SSH this should be tied back to the
153 * standard error channel of the command execution. For most
154 * other network connections this should be null.
155 * @throws IOException
156 */
157 public void receive(final InputStream input, final OutputStream output,
158 final OutputStream messages) throws IOException {
159 init(input, output, messages);
160 try {
161 service();
162 } finally {
163 try {
164 close();
165 } finally {
166 release();
167 }
168 }
169 }
170
171 @Override
172 protected void enableCapabilities() {
173 reportStatus = isCapabilityEnabled(CAPABILITY_REPORT_STATUS);
174 super.enableCapabilities();
175 }
176
177 private void service() throws IOException {
178 if (isBiDirectionalPipe()) {
179 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
180 pckOut.flush();
181 } else
182 getAdvertisedOrDefaultRefs();
183 if (hasError())
184 return;
185 recvCommands();
186 if (hasCommands()) {
187 enableCapabilities();
188
189 Throwable unpackError = null;
190 if (needPack()) {
191 try {
192 receivePackAndCheckConnectivity();
193 } catch (IOException err) {
194 unpackError = err;
195 } catch (RuntimeException err) {
196 unpackError = err;
197 } catch (Error err) {
198 unpackError = err;
199 }
200 }
201
202 if (unpackError == null) {
203 boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
204 validateCommands();
205 if (atomic && anyRejects())
206 failPendingCommands();
207
208 preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
209 if (atomic && anyRejects())
210 failPendingCommands();
211 executeCommands();
212 }
213 unlockPack();
214
215 if (reportStatus) {
216 if (echoCommandFailures && msgOut != null) {
217 sendStatusReport(false, unpackError, new Reporter() {
218 void sendString(final String s) throws IOException {
219 msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
220 }
221 });
222 msgOut.flush();
223 try {
224 Thread.sleep(500);
225 } catch (InterruptedException wakeUp) {
226 // Ignore an early wake up.
227 }
228 }
229 sendStatusReport(true, unpackError, new Reporter() {
230 void sendString(final String s) throws IOException {
231 pckOut.writeString(s + "\n"); //$NON-NLS-1$
232 }
233 });
234 pckOut.end();
235 } else if (msgOut != null) {
236 sendStatusReport(false, unpackError, new Reporter() {
237 void sendString(final String s) throws IOException {
238 msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$
239 }
240 });
241 }
242
243 if (unpackError != null) {
244 // we already know which exception to throw. Ignore
245 // potential additional exceptions raised in postReceiveHooks
246 try {
247 postReceive.onPostReceive(this, filterCommands(Result.OK));
248 } catch (Throwable e) {
249 }
250 throw new UnpackException(unpackError);
251 }
252 postReceive.onPostReceive(this, filterCommands(Result.OK));
253 }
254 }
255
256 @Override
257 protected String getLockMessageProcessName() {
258 return "jgit receive-pack"; //$NON-NLS-1$
259 }
260 }