2 * Copyright (C) 2008, Google Inc.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org
.spearce
.jgit
.transport
;
40 import java
.io
.BufferedWriter
;
41 import java
.io
.EOFException
;
42 import java
.io
.IOException
;
43 import java
.io
.InputStream
;
44 import java
.io
.OutputStream
;
45 import java
.io
.OutputStreamWriter
;
46 import java
.io
.PrintWriter
;
47 import java
.util
.ArrayList
;
48 import java
.util
.Collections
;
49 import java
.util
.HashSet
;
50 import java
.util
.Iterator
;
51 import java
.util
.List
;
55 import org
.spearce
.jgit
.errors
.MissingObjectException
;
56 import org
.spearce
.jgit
.errors
.PackProtocolException
;
57 import org
.spearce
.jgit
.lib
.Constants
;
58 import org
.spearce
.jgit
.lib
.NullProgressMonitor
;
59 import org
.spearce
.jgit
.lib
.ObjectId
;
60 import org
.spearce
.jgit
.lib
.PackLock
;
61 import org
.spearce
.jgit
.lib
.PersonIdent
;
62 import org
.spearce
.jgit
.lib
.Ref
;
63 import org
.spearce
.jgit
.lib
.RefComparator
;
64 import org
.spearce
.jgit
.lib
.RefUpdate
;
65 import org
.spearce
.jgit
.lib
.Repository
;
66 import org
.spearce
.jgit
.lib
.RepositoryConfig
;
67 import org
.spearce
.jgit
.revwalk
.ObjectWalk
;
68 import org
.spearce
.jgit
.revwalk
.RevCommit
;
69 import org
.spearce
.jgit
.revwalk
.RevObject
;
70 import org
.spearce
.jgit
.revwalk
.RevWalk
;
71 import org
.spearce
.jgit
.transport
.ReceiveCommand
.Result
;
74 * Implements the server side of a push connection, receiving objects.
76 public class ReceivePack
{
77 static final String CAPABILITY_REPORT_STATUS
= BasePackPushConnection
.CAPABILITY_REPORT_STATUS
;
79 static final String CAPABILITY_DELETE_REFS
= BasePackPushConnection
.CAPABILITY_DELETE_REFS
;
81 static final String CAPABILITY_OFS_DELTA
= BasePackPushConnection
.CAPABILITY_OFS_DELTA
;
83 /** Database we write the stored objects into. */
84 private final Repository db
;
86 /** Revision traversal support over {@link #db}. */
87 private final RevWalk walk
;
89 /** Should an incoming transfer validate objects? */
90 private boolean checkReceivedObjects
;
92 /** Should an incoming transfer permit create requests? */
93 private boolean allowCreates
;
95 /** Should an incoming transfer permit delete requests? */
96 private boolean allowDeletes
;
98 /** Should an incoming transfer permit non-fast-forward requests? */
99 private boolean allowNonFastForwards
;
101 private boolean allowOfsDelta
;
103 /** Identity to record action as within the reflog. */
104 private PersonIdent refLogIdent
;
106 /** Hook to validate the update commands before execution. */
107 private PreReceiveHook preReceive
;
109 /** Hook to report on the commands after execution. */
110 private PostReceiveHook postReceive
;
112 private InputStream rawIn
;
114 private OutputStream rawOut
;
116 private PacketLineIn pckIn
;
118 private PacketLineOut pckOut
;
120 private PrintWriter msgs
;
122 /** The refs we advertised as existing at the start of the connection. */
123 private Map
<String
, Ref
> refs
;
125 /** Capabilities requested by the client. */
126 private Set
<String
> enabledCapablities
;
128 /** Commands to execute, as received by the client. */
129 private List
<ReceiveCommand
> commands
;
131 /** An exception caught while unpacking and fsck'ing the objects. */
132 private Throwable unpackError
;
134 /** if {@link #enabledCapablities} has {@link #CAPABILITY_REPORT_STATUS} */
135 private boolean reportStatus
;
137 /** Lock around the received pack file, while updating refs. */
138 private PackLock packLock
;
141 * Create a new pack receive for an open repository.
144 * the destination repository.
146 public ReceivePack(final Repository into
) {
148 walk
= new RevWalk(db
);
150 final RepositoryConfig cfg
= db
.getConfig();
151 checkReceivedObjects
= cfg
.getBoolean("receive", "fsckobjects", false);
153 allowDeletes
= !cfg
.getBoolean("receive", "denydeletes", false);
154 allowNonFastForwards
= !cfg
.getBoolean("receive",
155 "denynonfastforwards", false);
156 allowOfsDelta
= cfg
.getBoolean("repack", "usedeltabaseoffset", true);
157 preReceive
= PreReceiveHook
.NULL
;
158 postReceive
= PostReceiveHook
.NULL
;
161 /** @return the repository this receive completes into. */
162 public final Repository
getRepository() {
166 /** @return the RevWalk instance used by this connection. */
167 public final RevWalk
getRevWalk() {
171 /** @return all refs which were advertised to the client. */
172 public final Map
<String
, Ref
> getAdvertisedRefs() {
177 * @return true if this instance will verify received objects are formatted
178 * correctly. Validating objects requires more CPU time on this side
181 public boolean isCheckReceivedObjects() {
182 return checkReceivedObjects
;
187 * true to enable checking received objects; false to assume all
188 * received objects are valid.
190 public void setCheckReceivedObjects(final boolean check
) {
191 checkReceivedObjects
= check
;
194 /** @return true if the client can request refs to be created. */
195 public boolean isAllowCreates() {
201 * true to permit create ref commands to be processed.
203 public void setAllowCreates(final boolean canCreate
) {
204 allowCreates
= canCreate
;
207 /** @return true if the client can request refs to be deleted. */
208 public boolean isAllowDeletes() {
214 * true to permit delete ref commands to be processed.
216 public void setAllowDeletes(final boolean canDelete
) {
217 allowDeletes
= canDelete
;
221 * @return true if the client can request non-fast-forward updates of a ref,
222 * possibly making objects unreachable.
224 public boolean isAllowNonFastForwards() {
225 return allowNonFastForwards
;
230 * true to permit the client to ask for non-fast-forward updates
231 * of an existing ref.
233 public void setAllowNonFastForwards(final boolean canRewind
) {
234 allowNonFastForwards
= canRewind
;
237 /** @return identity of the user making the changes in the reflog. */
238 public PersonIdent
getRefLogIdent() {
243 * Set the identity of the user appearing in the affected reflogs.
245 * The timestamp portion of the identity is ignored. A new identity with the
246 * current timestamp will be created automatically when the updates occur
247 * and the log records are written.
250 * identity of the user. If null the identity will be
251 * automatically determined based on the repository
254 public void setRefLogIdent(final PersonIdent pi
) {
258 /** @return get the hook invoked before updates occur. */
259 public PreReceiveHook
getPreReceiveHook() {
264 * Set the hook which is invoked prior to commands being executed.
266 * Only valid commands (those which have no obvious errors according to the
267 * received input and this instance's configuration) are passed into the
268 * hook. The hook may mark a command with a result of any value other than
269 * {@link Result#NOT_ATTEMPTED} to block its execution.
271 * The hook may be called with an empty command collection if the current
272 * set is completely invalid.
275 * the hook instance; may be null to disable the hook.
277 public void setPreReceiveHook(final PreReceiveHook h
) {
278 preReceive
= h
!= null ? h
: PreReceiveHook
.NULL
;
281 /** @return get the hook invoked after updates occur. */
282 public PostReceiveHook
getPostReceiveHook() {
287 * Set the hook which is invoked after commands are executed.
289 * Only successful commands (type is {@link Result#OK}) are passed into the
290 * hook. The hook may be called with an empty command collection if the
291 * current set all resulted in an error.
294 * the hook instance; may be null to disable the hook.
296 public void setPostReceiveHook(final PostReceiveHook h
) {
297 postReceive
= h
!= null ? h
: PostReceiveHook
.NULL
;
300 /** @return all of the command received by the current request. */
301 public List
<ReceiveCommand
> getAllCommands() {
302 return Collections
.unmodifiableList(commands
);
306 * Send an error message to the client, if it supports receiving them.
308 * If the client doesn't support receiving messages, the message will be
309 * discarded, with no other indication to the caller or to the client.
311 * {@link PreReceiveHook}s should always try to use
312 * {@link ReceiveCommand#setResult(Result, String)} with a result status of
313 * {@link Result#REJECTED_OTHER_REASON} to indicate any reasons for
314 * rejecting an update. Messages attached to a command are much more likely
315 * to be returned to the client.
318 * string describing the problem identified by the hook. The
319 * string must not end with an LF, and must not contain an LF.
321 public void sendError(final String what
) {
322 sendMessage("error", what
);
326 * Send a message to the client, if it supports receiving them.
328 * If the client doesn't support receiving messages, the message will be
329 * discarded, with no other indication to the caller or to the client.
332 * string describing the problem identified by the hook. The
333 * string must not end with an LF, and must not contain an LF.
335 public void sendMessage(final String what
) {
336 sendMessage("remote", what
);
339 private void sendMessage(final String type
, final String what
) {
341 msgs
.println(type
+ ": " + what
);
345 * Execute the receive task on the socket.
348 * raw input to read client commands and pack data from. Caller
349 * must ensure the input is buffered, otherwise read performance
352 * response back to the Git network client. Caller must ensure
353 * the output is buffered, otherwise write performance may
356 * secondary "notice" channel to send additional messages out
357 * through. When run over SSH this should be tied back to the
358 * standard error channel of the command execution. For most
359 * other network connections this should be null.
360 * @throws IOException
362 public void receive(final InputStream input
, final OutputStream output
,
363 final OutputStream messages
) throws IOException
{
368 pckIn
= new PacketLineIn(rawIn
);
369 pckOut
= new PacketLineOut(rawOut
);
370 if (messages
!= null) {
371 msgs
= new PrintWriter(new BufferedWriter(
372 new OutputStreamWriter(messages
, Constants
.CHARSET
),
375 public void println() {
381 enabledCapablities
= new HashSet
<String
>();
382 commands
= new ArrayList
<ReceiveCommand
>();
398 enabledCapablities
= null;
404 private void service() throws IOException
{
405 sendAdvertisedRefs();
407 if (!commands
.isEmpty()) {
408 enableCapabilities();
413 if (isCheckReceivedObjects())
416 } catch (IOException err
) {
418 } catch (RuntimeException err
) {
420 } catch (Error err
) {
425 if (unpackError
== null) {
432 sendStatusReport(true, new Reporter() {
433 void sendString(final String s
) throws IOException
{
434 pckOut
.writeString(s
+ "\n");
438 } else if (msgs
!= null) {
439 sendStatusReport(false, new Reporter() {
440 void sendString(final String s
) throws IOException
{
447 postReceive
.onPostReceive(this, filterCommands(Result
.OK
));
451 private void unlockPack() {
452 if (packLock
!= null) {
458 private void sendAdvertisedRefs() throws IOException
{
459 refs
= db
.getAllRefs();
461 final StringBuilder m
= new StringBuilder(100);
462 final char[] idtmp
= new char[2 * Constants
.OBJECT_ID_LENGTH
];
463 final Iterator
<Ref
> i
= RefComparator
.sort(refs
.values()).iterator();
466 final Ref r
= i
.next();
467 format(m
, idtmp
, r
.getObjectId(), r
.getOrigName());
469 format(m
, idtmp
, ObjectId
.zeroId(), "capabilities^{}");
473 m
.append(CAPABILITY_DELETE_REFS
);
475 m
.append(CAPABILITY_REPORT_STATUS
);
478 m
.append(CAPABILITY_OFS_DELTA
);
481 writeAdvertisedRef(m
);
484 while (i
.hasNext()) {
485 final Ref r
= i
.next();
486 format(m
, idtmp
, r
.getObjectId(), r
.getOrigName());
487 writeAdvertisedRef(m
);
492 private void format(final StringBuilder m
, final char[] idtmp
,
493 final ObjectId id
, final String name
) {
500 private void writeAdvertisedRef(final StringBuilder m
) throws IOException
{
502 pckOut
.writeString(m
.toString());
505 private void recvCommands() throws IOException
{
509 line
= pckIn
.readStringRaw();
510 } catch (EOFException eof
) {
511 if (commands
.isEmpty())
515 if (line
== PacketLineIn
.END
)
518 if (commands
.isEmpty()) {
519 final int nul
= line
.indexOf('\0');
521 for (String c
: line
.substring(nul
+ 1).split(" "))
522 enabledCapablities
.add(c
);
523 line
= line
.substring(0, nul
);
527 if (line
.length() < 83) {
528 final String m
= "error: invalid protocol: wanted 'old new ref'";
530 throw new PackProtocolException(m
);
533 final ObjectId oldId
= ObjectId
.fromString(line
.substring(0, 40));
534 final ObjectId newId
= ObjectId
.fromString(line
.substring(41, 81));
535 final String name
= line
.substring(82);
536 final ReceiveCommand cmd
= new ReceiveCommand(oldId
, newId
, name
);
537 cmd
.setRef(refs
.get(cmd
.getRefName()));
542 private void enableCapabilities() {
543 reportStatus
= enabledCapablities
.contains(CAPABILITY_REPORT_STATUS
);
546 private boolean needPack() {
547 for (final ReceiveCommand cmd
: commands
) {
548 if (cmd
.getType() != ReceiveCommand
.Type
.DELETE
)
554 private void receivePack() throws IOException
{
555 final IndexPack ip
= IndexPack
.create(db
, rawIn
);
557 ip
.setObjectChecking(isCheckReceivedObjects());
558 ip
.index(NullProgressMonitor
.INSTANCE
);
560 String lockMsg
= "jgit receive-pack";
561 if (getRefLogIdent() != null)
562 lockMsg
+= " from " + getRefLogIdent().toExternalString();
563 packLock
= ip
.renameAndOpenPack(lockMsg
);
566 private void checkConnectivity() throws IOException
{
567 final ObjectWalk ow
= new ObjectWalk(db
);
568 for (final ReceiveCommand cmd
: commands
) {
569 if (cmd
.getResult() != Result
.NOT_ATTEMPTED
)
571 if (cmd
.getType() == ReceiveCommand
.Type
.DELETE
)
573 ow
.markStart(ow
.parseAny(cmd
.getNewId()));
575 for (final Ref ref
: refs
.values())
576 ow
.markUninteresting(ow
.parseAny(ref
.getObjectId()));
577 ow
.checkConnectivity();
580 private void validateCommands() {
581 for (final ReceiveCommand cmd
: commands
) {
582 final Ref ref
= cmd
.getRef();
583 if (cmd
.getResult() != Result
.NOT_ATTEMPTED
)
586 if (cmd
.getType() == ReceiveCommand
.Type
.DELETE
587 && !isAllowDeletes()) {
588 // Deletes are not supported on this repository.
590 cmd
.setResult(Result
.REJECTED_NODELETE
);
594 if (cmd
.getType() == ReceiveCommand
.Type
.CREATE
) {
595 if (!isAllowCreates()) {
596 cmd
.setResult(Result
.REJECTED_NOCREATE
);
600 if (ref
!= null && !isAllowNonFastForwards()) {
601 // Creation over an existing ref is certainly not going
602 // to be a fast-forward update. We can reject it early.
604 cmd
.setResult(Result
.REJECTED_NONFASTFORWARD
);
609 // A well behaved client shouldn't have sent us an
610 // update command for a ref we advertised to it.
612 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "ref exists");
617 if (cmd
.getType() == ReceiveCommand
.Type
.DELETE
&& ref
!= null
618 && !ObjectId
.zeroId().equals(cmd
.getOldId())
619 && !ref
.getObjectId().equals(cmd
.getOldId())) {
620 // Delete commands can be sent with the old id matching our
621 // advertised value, *OR* with the old id being 0{40}. Any
622 // other requested old id is invalid.
624 cmd
.setResult(Result
.REJECTED_OTHER_REASON
,
625 "invalid old id sent");
629 if (cmd
.getType() == ReceiveCommand
.Type
.UPDATE
) {
631 // The ref must have been advertised in order to be updated.
633 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "no such ref");
637 if (!ref
.getObjectId().equals(cmd
.getOldId())) {
638 // A properly functioning client will send the same
639 // object id we advertised.
641 cmd
.setResult(Result
.REJECTED_OTHER_REASON
,
642 "invalid old id sent");
646 // Is this possibly a non-fast-forward style update?
648 RevObject oldObj
, newObj
;
650 oldObj
= walk
.parseAny(cmd
.getOldId());
651 } catch (IOException e
) {
652 cmd
.setResult(Result
.REJECTED_MISSING_OBJECT
, cmd
658 newObj
= walk
.parseAny(cmd
.getNewId());
659 } catch (IOException e
) {
660 cmd
.setResult(Result
.REJECTED_MISSING_OBJECT
, cmd
665 if (oldObj
instanceof RevCommit
&& newObj
instanceof RevCommit
) {
667 if (!walk
.isMergedInto((RevCommit
) oldObj
,
668 (RevCommit
) newObj
)) {
670 .setType(ReceiveCommand
.Type
.UPDATE_NONFASTFORWARD
);
672 } catch (MissingObjectException e
) {
673 cmd
.setResult(Result
.REJECTED_MISSING_OBJECT
, e
675 } catch (IOException e
) {
676 cmd
.setResult(Result
.REJECTED_OTHER_REASON
);
679 cmd
.setType(ReceiveCommand
.Type
.UPDATE_NONFASTFORWARD
);
683 if (!cmd
.getRefName().startsWith(Constants
.R_REFS
)
684 || !Repository
.isValidRefName(cmd
.getRefName())) {
685 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "funny refname");
690 private void executeCommands() {
691 preReceive
.onPreReceive(this, filterCommands(Result
.NOT_ATTEMPTED
));
692 for (final ReceiveCommand cmd
: filterCommands(Result
.NOT_ATTEMPTED
))
696 private void execute(final ReceiveCommand cmd
) {
698 final RefUpdate ru
= db
.updateRef(cmd
.getRefName());
699 ru
.setRefLogIdent(getRefLogIdent());
700 switch (cmd
.getType()) {
702 if (!ObjectId
.zeroId().equals(cmd
.getOldId())) {
703 // We can only do a CAS style delete if the client
704 // didn't bork its delete request by sending the
705 // wrong zero id rather than the advertised one.
707 ru
.setExpectedOldObjectId(cmd
.getOldId());
709 ru
.setForceUpdate(true);
710 status(cmd
, ru
.delete(walk
));
715 case UPDATE_NONFASTFORWARD
:
716 ru
.setForceUpdate(isAllowNonFastForwards());
717 ru
.setExpectedOldObjectId(cmd
.getOldId());
718 ru
.setNewObjectId(cmd
.getNewId());
719 ru
.setRefLogMessage("push", true);
720 status(cmd
, ru
.update(walk
));
723 } catch (IOException err
) {
724 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "lock error: "
729 private void status(final ReceiveCommand cmd
, final RefUpdate
.Result result
) {
732 cmd
.setResult(Result
.NOT_ATTEMPTED
);
737 cmd
.setResult(Result
.LOCK_FAILURE
);
744 cmd
.setResult(Result
.OK
);
748 cmd
.setResult(Result
.REJECTED_NONFASTFORWARD
);
751 case REJECTED_CURRENT_BRANCH
:
752 cmd
.setResult(Result
.REJECTED_CURRENT_BRANCH
);
756 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, result
.name());
761 private List
<ReceiveCommand
> filterCommands(final Result want
) {
762 final List
<ReceiveCommand
> r
= new ArrayList
<ReceiveCommand
>(commands
764 for (final ReceiveCommand cmd
: commands
) {
765 if (cmd
.getResult() == want
)
771 private void sendStatusReport(final boolean forClient
, final Reporter out
)
773 if (unpackError
!= null) {
774 out
.sendString("unpack error " + unpackError
.getMessage());
776 for (final ReceiveCommand cmd
: commands
) {
777 out
.sendString("ng " + cmd
.getRefName()
778 + " n/a (unpacker error)");
785 out
.sendString("unpack ok");
786 for (final ReceiveCommand cmd
: commands
) {
787 if (cmd
.getResult() == Result
.OK
) {
789 out
.sendString("ok " + cmd
.getRefName());
793 final StringBuilder r
= new StringBuilder();
795 r
.append(cmd
.getRefName());
798 switch (cmd
.getResult()) {
800 r
.append("server bug; ref not processed");
803 case REJECTED_NOCREATE
:
804 r
.append("creation prohibited");
807 case REJECTED_NODELETE
:
808 r
.append("deletion prohibited");
811 case REJECTED_NONFASTFORWARD
:
812 r
.append("non-fast forward");
815 case REJECTED_CURRENT_BRANCH
:
816 r
.append("branch is currently checked out");
819 case REJECTED_MISSING_OBJECT
:
820 if (cmd
.getMessage() == null)
821 r
.append("missing object(s)");
822 else if (cmd
.getMessage().length() == 2 * Constants
.OBJECT_ID_LENGTH
)
823 r
.append("object " + cmd
.getMessage() + " missing");
825 r
.append(cmd
.getMessage());
828 case REJECTED_OTHER_REASON
:
829 if (cmd
.getMessage() == null)
830 r
.append("unspecified reason");
832 r
.append(cmd
.getMessage());
836 r
.append("failed to lock");
840 // We shouldn't have reached this case (see 'ok' case above).
843 out
.sendString(r
.toString());
847 static abstract class Reporter
{
848 abstract void sendString(String s
) throws IOException
;