2 * Copyright (C) 2008-2010, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
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
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
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.
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
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.
44 package org
.eclipse
.jgit
.transport
;
46 import static org
.eclipse
.jgit
.transport
.BasePackPushConnection
.CAPABILITY_DELETE_REFS
;
47 import static org
.eclipse
.jgit
.transport
.BasePackPushConnection
.CAPABILITY_OFS_DELTA
;
48 import static org
.eclipse
.jgit
.transport
.BasePackPushConnection
.CAPABILITY_REPORT_STATUS
;
49 import static org
.eclipse
.jgit
.transport
.BasePackPushConnection
.CAPABILITY_SIDE_BAND_64K
;
50 import static org
.eclipse
.jgit
.transport
.SideBandOutputStream
.CH_DATA
;
51 import static org
.eclipse
.jgit
.transport
.SideBandOutputStream
.CH_PROGRESS
;
52 import static org
.eclipse
.jgit
.transport
.SideBandOutputStream
.MAX_BUF
;
54 import java
.io
.EOFException
;
55 import java
.io
.IOException
;
56 import java
.io
.InputStream
;
57 import java
.io
.OutputStream
;
58 import java
.io
.OutputStreamWriter
;
59 import java
.io
.Writer
;
60 import java
.util
.ArrayList
;
61 import java
.util
.Collections
;
62 import java
.util
.HashSet
;
63 import java
.util
.List
;
67 import org
.eclipse
.jgit
.errors
.MissingObjectException
;
68 import org
.eclipse
.jgit
.errors
.PackProtocolException
;
69 import org
.eclipse
.jgit
.lib
.Config
;
70 import org
.eclipse
.jgit
.lib
.Constants
;
71 import org
.eclipse
.jgit
.lib
.NullProgressMonitor
;
72 import org
.eclipse
.jgit
.lib
.ObjectId
;
73 import org
.eclipse
.jgit
.lib
.ObjectIdSubclassMap
;
74 import org
.eclipse
.jgit
.lib
.PackLock
;
75 import org
.eclipse
.jgit
.lib
.PersonIdent
;
76 import org
.eclipse
.jgit
.lib
.Ref
;
77 import org
.eclipse
.jgit
.lib
.RefUpdate
;
78 import org
.eclipse
.jgit
.lib
.Repository
;
79 import org
.eclipse
.jgit
.lib
.Config
.SectionParser
;
80 import org
.eclipse
.jgit
.revwalk
.ObjectWalk
;
81 import org
.eclipse
.jgit
.revwalk
.RevBlob
;
82 import org
.eclipse
.jgit
.revwalk
.RevCommit
;
83 import org
.eclipse
.jgit
.revwalk
.RevFlag
;
84 import org
.eclipse
.jgit
.revwalk
.RevObject
;
85 import org
.eclipse
.jgit
.revwalk
.RevTag
;
86 import org
.eclipse
.jgit
.revwalk
.RevTree
;
87 import org
.eclipse
.jgit
.revwalk
.RevWalk
;
88 import org
.eclipse
.jgit
.transport
.ReceiveCommand
.Result
;
89 import org
.eclipse
.jgit
.transport
.RefAdvertiser
.PacketLineOutRefAdvertiser
;
90 import org
.eclipse
.jgit
.util
.io
.InterruptTimer
;
91 import org
.eclipse
.jgit
.util
.io
.TimeoutInputStream
;
92 import org
.eclipse
.jgit
.util
.io
.TimeoutOutputStream
;
95 * Implements the server side of a push connection, receiving objects.
97 public class ReceivePack
{
98 /** Database we write the stored objects into. */
99 private final Repository db
;
101 /** Revision traversal support over {@link #db}. */
102 private final RevWalk walk
;
105 * Is the client connection a bi-directional socket or pipe?
107 * If true, this class assumes it can perform multiple read and write cycles
108 * with the client over the input and output streams. This matches the
109 * functionality available with a standard TCP/IP connection, or a local
110 * operating system or in-memory pipe.
112 * If false, this class runs in a read everything then output results mode,
113 * making it suitable for single round-trip systems RPCs such as HTTP.
115 private boolean biDirectionalPipe
= true;
117 /** Should an incoming transfer validate objects? */
118 private boolean checkReceivedObjects
;
120 /** Should an incoming transfer permit create requests? */
121 private boolean allowCreates
;
123 /** Should an incoming transfer permit delete requests? */
124 private boolean allowDeletes
;
126 /** Should an incoming transfer permit non-fast-forward requests? */
127 private boolean allowNonFastForwards
;
129 private boolean allowOfsDelta
;
131 /** Identity to record action as within the reflog. */
132 private PersonIdent refLogIdent
;
134 /** Filter used while advertising the refs to the client. */
135 private RefFilter refFilter
;
137 /** Hook to validate the update commands before execution. */
138 private PreReceiveHook preReceive
;
140 /** Hook to report on the commands after execution. */
141 private PostReceiveHook postReceive
;
143 /** Timeout in seconds to wait for client interaction. */
146 /** Timer to manage {@link #timeout}. */
147 private InterruptTimer timer
;
149 private TimeoutInputStream timeoutIn
;
151 private InputStream rawIn
;
153 private OutputStream rawOut
;
155 private PacketLineIn pckIn
;
157 private PacketLineOut pckOut
;
161 private IndexPack ip
;
163 /** The refs we advertised as existing at the start of the connection. */
164 private Map
<String
, Ref
> refs
;
166 /** Capabilities requested by the client. */
167 private Set
<String
> enabledCapablities
;
169 /** Commands to execute, as received by the client. */
170 private List
<ReceiveCommand
> commands
;
172 /** Error to display instead of advertising the references. */
173 private StringBuilder advertiseError
;
175 /** An exception caught while unpacking and fsck'ing the objects. */
176 private Throwable unpackError
;
178 /** If {@link BasePackPushConnection#CAPABILITY_REPORT_STATUS} is enabled. */
179 private boolean reportStatus
;
181 /** If {@link BasePackPushConnection#CAPABILITY_SIDE_BAND_64K} is enabled. */
182 private boolean sideBand
;
184 /** Lock around the received pack file, while updating refs. */
185 private PackLock packLock
;
187 private boolean checkReferencedIsReachable
;
190 * Create a new pack receive for an open repository.
193 * the destination repository.
195 public ReceivePack(final Repository into
) {
197 walk
= new RevWalk(db
);
199 final ReceiveConfig cfg
= db
.getConfig().get(ReceiveConfig
.KEY
);
200 checkReceivedObjects
= cfg
.checkReceivedObjects
;
201 allowCreates
= cfg
.allowCreates
;
202 allowDeletes
= cfg
.allowDeletes
;
203 allowNonFastForwards
= cfg
.allowNonFastForwards
;
204 allowOfsDelta
= cfg
.allowOfsDelta
;
205 refFilter
= RefFilter
.DEFAULT
;
206 preReceive
= PreReceiveHook
.NULL
;
207 postReceive
= PostReceiveHook
.NULL
;
210 private static class ReceiveConfig
{
211 static final SectionParser
<ReceiveConfig
> KEY
= new SectionParser
<ReceiveConfig
>() {
212 public ReceiveConfig
parse(final Config cfg
) {
213 return new ReceiveConfig(cfg
);
217 final boolean checkReceivedObjects
;
219 final boolean allowCreates
;
221 final boolean allowDeletes
;
223 final boolean allowNonFastForwards
;
225 final boolean allowOfsDelta
;
227 ReceiveConfig(final Config config
) {
228 checkReceivedObjects
= config
.getBoolean("receive", "fsckobjects",
231 allowDeletes
= !config
.getBoolean("receive", "denydeletes", false);
232 allowNonFastForwards
= !config
.getBoolean("receive",
233 "denynonfastforwards", false);
234 allowOfsDelta
= config
.getBoolean("repack", "usedeltabaseoffset",
239 /** @return the repository this receive completes into. */
240 public final Repository
getRepository() {
244 /** @return the RevWalk instance used by this connection. */
245 public final RevWalk
getRevWalk() {
249 /** @return all refs which were advertised to the client. */
250 public final Map
<String
, Ref
> getAdvertisedRefs() {
255 * @return true if this instance will validate all referenced, but not
256 * supplied by the client, objects are reachable from another
259 public boolean isCheckReferencedObjectsAreReachable() {
260 return checkReferencedIsReachable
;
264 * Validate all referenced but not supplied objects are reachable.
266 * If enabled, this instance will verify that references to objects not
267 * contained within the received pack are already reachable through at least
268 * one other reference selected by the {@link #getRefFilter()} and displayed
269 * as part of {@link #getAdvertisedRefs()}.
271 * This feature is useful when the application doesn't trust the client to
272 * not provide a forged SHA-1 reference to an object, in an attempt to
273 * access parts of the DAG that they aren't allowed to see and which have
274 * been hidden from them via the configured {@link RefFilter}.
276 * Enabling this feature may imply at least some, if not all, of the same
277 * functionality performed by {@link #setCheckReceivedObjects(boolean)}.
278 * Applications are encouraged to enable both features, if desired.
281 * {@code true} to enable the additional check.
283 public void setCheckReferencedObjectsAreReachable(boolean b
) {
284 this.checkReferencedIsReachable
= b
;
288 * @return true if this class expects a bi-directional pipe opened between
289 * the client and itself. The default is true.
291 public boolean isBiDirectionalPipe() {
292 return biDirectionalPipe
;
297 * if true, this class will assume the socket is a fully
298 * bidirectional pipe between the two peers and takes advantage
299 * of that by first transmitting the known refs, then waiting to
300 * read commands. If false, this class assumes it must read the
301 * commands before writing output and does not perform the
302 * initial advertising.
304 public void setBiDirectionalPipe(final boolean twoWay
) {
305 biDirectionalPipe
= twoWay
;
309 * @return true if this instance will verify received objects are formatted
310 * correctly. Validating objects requires more CPU time on this side
313 public boolean isCheckReceivedObjects() {
314 return checkReceivedObjects
;
319 * true to enable checking received objects; false to assume all
320 * received objects are valid.
322 public void setCheckReceivedObjects(final boolean check
) {
323 checkReceivedObjects
= check
;
326 /** @return true if the client can request refs to be created. */
327 public boolean isAllowCreates() {
333 * true to permit create ref commands to be processed.
335 public void setAllowCreates(final boolean canCreate
) {
336 allowCreates
= canCreate
;
339 /** @return true if the client can request refs to be deleted. */
340 public boolean isAllowDeletes() {
346 * true to permit delete ref commands to be processed.
348 public void setAllowDeletes(final boolean canDelete
) {
349 allowDeletes
= canDelete
;
353 * @return true if the client can request non-fast-forward updates of a ref,
354 * possibly making objects unreachable.
356 public boolean isAllowNonFastForwards() {
357 return allowNonFastForwards
;
362 * true to permit the client to ask for non-fast-forward updates
363 * of an existing ref.
365 public void setAllowNonFastForwards(final boolean canRewind
) {
366 allowNonFastForwards
= canRewind
;
369 /** @return identity of the user making the changes in the reflog. */
370 public PersonIdent
getRefLogIdent() {
375 * Set the identity of the user appearing in the affected reflogs.
377 * The timestamp portion of the identity is ignored. A new identity with the
378 * current timestamp will be created automatically when the updates occur
379 * and the log records are written.
382 * identity of the user. If null the identity will be
383 * automatically determined based on the repository
386 public void setRefLogIdent(final PersonIdent pi
) {
390 /** @return the filter used while advertising the refs to the client */
391 public RefFilter
getRefFilter() {
396 * Set the filter used while advertising the refs to the client.
398 * Only refs allowed by this filter will be shown to the client.
399 * Clients may still attempt to create or update a reference hidden
400 * by the configured {@link RefFilter}. These attempts should be
401 * rejected by a matching {@link PreReceiveHook}.
404 * the filter; may be null to show all refs.
406 public void setRefFilter(final RefFilter refFilter
) {
407 this.refFilter
= refFilter
!= null ? refFilter
: RefFilter
.DEFAULT
;
410 /** @return get the hook invoked before updates occur. */
411 public PreReceiveHook
getPreReceiveHook() {
416 * Set the hook which is invoked prior to commands being executed.
418 * Only valid commands (those which have no obvious errors according to the
419 * received input and this instance's configuration) are passed into the
420 * hook. The hook may mark a command with a result of any value other than
421 * {@link Result#NOT_ATTEMPTED} to block its execution.
423 * The hook may be called with an empty command collection if the current
424 * set is completely invalid.
427 * the hook instance; may be null to disable the hook.
429 public void setPreReceiveHook(final PreReceiveHook h
) {
430 preReceive
= h
!= null ? h
: PreReceiveHook
.NULL
;
433 /** @return get the hook invoked after updates occur. */
434 public PostReceiveHook
getPostReceiveHook() {
439 * Set the hook which is invoked after commands are executed.
441 * Only successful commands (type is {@link Result#OK}) are passed into the
442 * hook. The hook may be called with an empty command collection if the
443 * current set all resulted in an error.
446 * the hook instance; may be null to disable the hook.
448 public void setPostReceiveHook(final PostReceiveHook h
) {
449 postReceive
= h
!= null ? h
: PostReceiveHook
.NULL
;
452 /** @return timeout (in seconds) before aborting an IO operation. */
453 public int getTimeout() {
458 * Set the timeout before willing to abort an IO call.
461 * number of seconds to wait (with no data transfer occurring)
462 * before aborting an IO read or write operation with the
465 public void setTimeout(final int seconds
) {
469 /** @return all of the command received by the current request. */
470 public List
<ReceiveCommand
> getAllCommands() {
471 return Collections
.unmodifiableList(commands
);
475 * Send an error message to the client.
477 * If any error messages are sent before the references are advertised to
478 * the client, the errors will be sent instead of the advertisement and the
479 * receive operation will be aborted. All clients should receive and display
480 * such early stage errors.
482 * If the reference advertisements have already been sent, messages are sent
483 * in a side channel. If the client doesn't support receiving messages, the
484 * message will be discarded, with no other indication to the caller or to
487 * {@link PreReceiveHook}s should always try to use
488 * {@link ReceiveCommand#setResult(Result, String)} with a result status of
489 * {@link Result#REJECTED_OTHER_REASON} to indicate any reasons for
490 * rejecting an update. Messages attached to a command are much more likely
491 * to be returned to the client.
494 * string describing the problem identified by the hook. The
495 * string must not end with an LF, and must not contain an LF.
497 public void sendError(final String what
) {
499 if (advertiseError
== null)
500 advertiseError
= new StringBuilder();
501 advertiseError
.append(what
).append('\n');
505 msgs
.write("error: " + what
+ "\n");
506 } catch (IOException e
) {
507 // Ignore write failures.
513 * Send a message to the client, if it supports receiving them.
515 * If the client doesn't support receiving messages, the message will be
516 * discarded, with no other indication to the caller or to the client.
519 * string describing the problem identified by the hook. The
520 * string must not end with an LF, and must not contain an LF.
522 public void sendMessage(final String what
) {
525 msgs
.write(what
+ "\n");
526 } catch (IOException e
) {
527 // Ignore write failures.
532 * Execute the receive task on the socket.
535 * raw input to read client commands and pack data from. Caller
536 * must ensure the input is buffered, otherwise read performance
539 * response back to the Git network client. Caller must ensure
540 * the output is buffered, otherwise write performance may
543 * secondary "notice" channel to send additional messages out
544 * through. When run over SSH this should be tied back to the
545 * standard error channel of the command execution. For most
546 * other network connections this should be null.
547 * @throws IOException
549 public void receive(final InputStream input
, final OutputStream output
,
550 final OutputStream messages
) throws IOException
{
556 final Thread caller
= Thread
.currentThread();
557 timer
= new InterruptTimer(caller
.getName() + "-Timer");
558 timeoutIn
= new TimeoutInputStream(rawIn
, timer
);
559 TimeoutOutputStream o
= new TimeoutOutputStream(rawOut
, timer
);
560 timeoutIn
.setTimeout(timeout
* 1000);
561 o
.setTimeout(timeout
* 1000);
566 pckIn
= new PacketLineIn(rawIn
);
567 pckOut
= new PacketLineOut(rawOut
);
568 if (messages
!= null)
569 msgs
= new OutputStreamWriter(messages
, Constants
.CHARSET
);
571 enabledCapablities
= new HashSet
<String
>();
572 commands
= new ArrayList
<ReceiveCommand
>();
583 // If we are using side band, we need to send a final
584 // flush-pkt to tell the remote peer the side band is
585 // complete and it should stop decoding. We need to
586 // use the original output stream as rawOut is now the
587 // side band data channel.
589 new PacketLineOut(output
).end();
600 enabledCapablities
= null;
613 private void service() throws IOException
{
614 if (biDirectionalPipe
)
615 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut
));
617 refs
= refFilter
.filter(db
.getAllRefs());
618 if (advertiseError
!= null)
621 if (!commands
.isEmpty()) {
622 enableCapabilities();
627 if (needCheckConnectivity())
631 } catch (IOException err
) {
633 } catch (RuntimeException err
) {
635 } catch (Error err
) {
640 if (unpackError
== null) {
647 sendStatusReport(true, new Reporter() {
648 void sendString(final String s
) throws IOException
{
649 pckOut
.writeString(s
+ "\n");
653 } else if (msgs
!= null) {
654 sendStatusReport(false, new Reporter() {
655 void sendString(final String s
) throws IOException
{
656 msgs
.write(s
+ "\n");
661 postReceive
.onPostReceive(this, filterCommands(Result
.OK
));
665 private void unlockPack() {
666 if (packLock
!= null) {
673 * Generate an advertisement of available refs and capabilities.
676 * the advertisement formatter.
677 * @throws IOException
678 * the formatter failed to write an advertisement.
680 public void sendAdvertisedRefs(final RefAdvertiser adv
) throws IOException
{
681 if (advertiseError
!= null) {
682 adv
.writeOne("ERR " + advertiseError
);
686 final RevFlag advertised
= walk
.newFlag("ADVERTISED");
687 adv
.init(walk
, advertised
);
688 adv
.advertiseCapability(CAPABILITY_SIDE_BAND_64K
);
689 adv
.advertiseCapability(CAPABILITY_DELETE_REFS
);
690 adv
.advertiseCapability(CAPABILITY_REPORT_STATUS
);
692 adv
.advertiseCapability(CAPABILITY_OFS_DELTA
);
693 refs
= refFilter
.filter(db
.getAllRefs());
694 final Ref head
= refs
.remove(Constants
.HEAD
);
696 if (head
!= null && !head
.isSymbolic())
697 adv
.advertiseHave(head
.getObjectId());
698 adv
.includeAdditionalHaves();
700 adv
.advertiseId(ObjectId
.zeroId(), "capabilities^{}");
704 private void recvCommands() throws IOException
{
708 line
= pckIn
.readStringRaw();
709 } catch (EOFException eof
) {
710 if (commands
.isEmpty())
714 if (line
== PacketLineIn
.END
)
717 if (commands
.isEmpty()) {
718 final int nul
= line
.indexOf('\0');
720 for (String c
: line
.substring(nul
+ 1).split(" "))
721 enabledCapablities
.add(c
);
722 line
= line
.substring(0, nul
);
726 if (line
.length() < 83) {
727 final String m
= "error: invalid protocol: wanted 'old new ref'";
729 throw new PackProtocolException(m
);
732 final ObjectId oldId
= ObjectId
.fromString(line
.substring(0, 40));
733 final ObjectId newId
= ObjectId
.fromString(line
.substring(41, 81));
734 final String name
= line
.substring(82);
735 final ReceiveCommand cmd
= new ReceiveCommand(oldId
, newId
, name
);
736 if (name
.equals(Constants
.HEAD
)) {
737 cmd
.setResult(Result
.REJECTED_CURRENT_BRANCH
);
739 cmd
.setRef(refs
.get(cmd
.getRefName()));
745 private void enableCapabilities() {
746 reportStatus
= enabledCapablities
.contains(CAPABILITY_REPORT_STATUS
);
748 sideBand
= enabledCapablities
.contains(CAPABILITY_SIDE_BAND_64K
);
750 OutputStream out
= rawOut
;
752 rawOut
= new SideBandOutputStream(CH_DATA
, MAX_BUF
, out
);
753 pckOut
= new PacketLineOut(rawOut
);
754 msgs
= new OutputStreamWriter(new SideBandOutputStream(CH_PROGRESS
,
755 MAX_BUF
, out
), Constants
.CHARSET
);
759 private boolean needPack() {
760 for (final ReceiveCommand cmd
: commands
) {
761 if (cmd
.getType() != ReceiveCommand
.Type
.DELETE
)
767 private void receivePack() throws IOException
{
768 // It might take the client a while to pack the objects it needs
769 // to send to us. We should increase our timeout so we don't
770 // abort while the client is computing.
772 if (timeoutIn
!= null)
773 timeoutIn
.setTimeout(10 * timeout
* 1000);
775 ip
= IndexPack
.create(db
, rawIn
);
777 ip
.setNeedNewObjectIds(checkReferencedIsReachable
);
778 ip
.setNeedBaseObjectIds(checkReferencedIsReachable
);
779 ip
.setObjectChecking(isCheckReceivedObjects());
780 ip
.index(NullProgressMonitor
.INSTANCE
);
782 String lockMsg
= "jgit receive-pack";
783 if (getRefLogIdent() != null)
784 lockMsg
+= " from " + getRefLogIdent().toExternalString();
785 packLock
= ip
.renameAndOpenPack(lockMsg
);
787 if (timeoutIn
!= null)
788 timeoutIn
.setTimeout(timeout
* 1000);
791 private boolean needCheckConnectivity() {
792 return isCheckReceivedObjects()
793 || isCheckReferencedObjectsAreReachable();
796 private void checkConnectivity() throws IOException
{
797 ObjectIdSubclassMap
<ObjectId
> baseObjects
= null;
798 ObjectIdSubclassMap
<ObjectId
> providedObjects
= null;
800 if (checkReferencedIsReachable
) {
801 baseObjects
= ip
.getBaseObjectIds();
802 providedObjects
= ip
.getNewObjectIds();
806 final ObjectWalk ow
= new ObjectWalk(db
);
807 for (final ReceiveCommand cmd
: commands
) {
808 if (cmd
.getResult() != Result
.NOT_ATTEMPTED
)
810 if (cmd
.getType() == ReceiveCommand
.Type
.DELETE
)
812 ow
.markStart(ow
.parseAny(cmd
.getNewId()));
814 for (final Ref ref
: refs
.values()) {
815 RevObject o
= ow
.parseAny(ref
.getObjectId());
816 ow
.markUninteresting(o
);
818 if (checkReferencedIsReachable
&& !baseObjects
.isEmpty()) {
819 while (o
instanceof RevTag
)
820 o
= ((RevTag
) o
).getObject();
821 if (o
instanceof RevCommit
)
822 o
= ((RevCommit
) o
).getTree();
823 if (o
instanceof RevTree
)
824 ow
.markUninteresting(o
);
828 if (checkReferencedIsReachable
) {
829 for (ObjectId id
: baseObjects
) {
830 RevObject b
= ow
.lookupAny(id
, Constants
.OBJ_BLOB
);
831 if (!b
.has(RevFlag
.UNINTERESTING
))
832 throw new MissingObjectException(b
, b
.getType());
837 while ((c
= ow
.next()) != null) {
838 if (checkReferencedIsReachable
&& !providedObjects
.contains(c
))
839 throw new MissingObjectException(c
, Constants
.TYPE_COMMIT
);
843 while ((o
= ow
.nextObject()) != null) {
844 if (checkReferencedIsReachable
) {
845 if (providedObjects
.contains(o
))
848 throw new MissingObjectException(o
, o
.getType());
851 if (o
instanceof RevBlob
&& !db
.hasObject(o
))
852 throw new MissingObjectException(o
, Constants
.TYPE_BLOB
);
856 private void validateCommands() {
857 for (final ReceiveCommand cmd
: commands
) {
858 final Ref ref
= cmd
.getRef();
859 if (cmd
.getResult() != Result
.NOT_ATTEMPTED
)
862 if (cmd
.getType() == ReceiveCommand
.Type
.DELETE
863 && !isAllowDeletes()) {
864 // Deletes are not supported on this repository.
866 cmd
.setResult(Result
.REJECTED_NODELETE
);
870 if (cmd
.getType() == ReceiveCommand
.Type
.CREATE
) {
871 if (!isAllowCreates()) {
872 cmd
.setResult(Result
.REJECTED_NOCREATE
);
876 if (ref
!= null && !isAllowNonFastForwards()) {
877 // Creation over an existing ref is certainly not going
878 // to be a fast-forward update. We can reject it early.
880 cmd
.setResult(Result
.REJECTED_NONFASTFORWARD
);
885 // A well behaved client shouldn't have sent us a
886 // create command for a ref we advertised to it.
888 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "ref exists");
893 if (cmd
.getType() == ReceiveCommand
.Type
.DELETE
&& ref
!= null
894 && !ObjectId
.zeroId().equals(cmd
.getOldId())
895 && !ref
.getObjectId().equals(cmd
.getOldId())) {
896 // Delete commands can be sent with the old id matching our
897 // advertised value, *OR* with the old id being 0{40}. Any
898 // other requested old id is invalid.
900 cmd
.setResult(Result
.REJECTED_OTHER_REASON
,
901 "invalid old id sent");
905 if (cmd
.getType() == ReceiveCommand
.Type
.UPDATE
) {
907 // The ref must have been advertised in order to be updated.
909 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "no such ref");
913 if (!ref
.getObjectId().equals(cmd
.getOldId())) {
914 // A properly functioning client will send the same
915 // object id we advertised.
917 cmd
.setResult(Result
.REJECTED_OTHER_REASON
,
918 "invalid old id sent");
922 // Is this possibly a non-fast-forward style update?
924 RevObject oldObj
, newObj
;
926 oldObj
= walk
.parseAny(cmd
.getOldId());
927 } catch (IOException e
) {
928 cmd
.setResult(Result
.REJECTED_MISSING_OBJECT
, cmd
934 newObj
= walk
.parseAny(cmd
.getNewId());
935 } catch (IOException e
) {
936 cmd
.setResult(Result
.REJECTED_MISSING_OBJECT
, cmd
941 if (oldObj
instanceof RevCommit
&& newObj
instanceof RevCommit
) {
943 if (!walk
.isMergedInto((RevCommit
) oldObj
,
944 (RevCommit
) newObj
)) {
946 .setType(ReceiveCommand
.Type
.UPDATE_NONFASTFORWARD
);
948 } catch (MissingObjectException e
) {
949 cmd
.setResult(Result
.REJECTED_MISSING_OBJECT
, e
951 } catch (IOException e
) {
952 cmd
.setResult(Result
.REJECTED_OTHER_REASON
);
955 cmd
.setType(ReceiveCommand
.Type
.UPDATE_NONFASTFORWARD
);
959 if (!cmd
.getRefName().startsWith(Constants
.R_REFS
)
960 || !Repository
.isValidRefName(cmd
.getRefName())) {
961 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "funny refname");
966 private void executeCommands() {
967 preReceive
.onPreReceive(this, filterCommands(Result
.NOT_ATTEMPTED
));
968 for (final ReceiveCommand cmd
: filterCommands(Result
.NOT_ATTEMPTED
))
972 private void execute(final ReceiveCommand cmd
) {
974 final RefUpdate ru
= db
.updateRef(cmd
.getRefName());
975 ru
.setRefLogIdent(getRefLogIdent());
976 switch (cmd
.getType()) {
978 if (!ObjectId
.zeroId().equals(cmd
.getOldId())) {
979 // We can only do a CAS style delete if the client
980 // didn't bork its delete request by sending the
981 // wrong zero id rather than the advertised one.
983 ru
.setExpectedOldObjectId(cmd
.getOldId());
985 ru
.setForceUpdate(true);
986 status(cmd
, ru
.delete(walk
));
991 case UPDATE_NONFASTFORWARD
:
992 ru
.setForceUpdate(isAllowNonFastForwards());
993 ru
.setExpectedOldObjectId(cmd
.getOldId());
994 ru
.setNewObjectId(cmd
.getNewId());
995 ru
.setRefLogMessage("push", true);
996 status(cmd
, ru
.update(walk
));
999 } catch (IOException err
) {
1000 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, "lock error: "
1001 + err
.getMessage());
1005 private void status(final ReceiveCommand cmd
, final RefUpdate
.Result result
) {
1008 cmd
.setResult(Result
.NOT_ATTEMPTED
);
1013 cmd
.setResult(Result
.LOCK_FAILURE
);
1020 cmd
.setResult(Result
.OK
);
1024 cmd
.setResult(Result
.REJECTED_NONFASTFORWARD
);
1027 case REJECTED_CURRENT_BRANCH
:
1028 cmd
.setResult(Result
.REJECTED_CURRENT_BRANCH
);
1032 cmd
.setResult(Result
.REJECTED_OTHER_REASON
, result
.name());
1037 private List
<ReceiveCommand
> filterCommands(final Result want
) {
1038 final List
<ReceiveCommand
> r
= new ArrayList
<ReceiveCommand
>(commands
1040 for (final ReceiveCommand cmd
: commands
) {
1041 if (cmd
.getResult() == want
)
1047 private void sendStatusReport(final boolean forClient
, final Reporter out
)
1048 throws IOException
{
1049 if (unpackError
!= null) {
1050 out
.sendString("unpack error " + unpackError
.getMessage());
1052 for (final ReceiveCommand cmd
: commands
) {
1053 out
.sendString("ng " + cmd
.getRefName()
1054 + " n/a (unpacker error)");
1061 out
.sendString("unpack ok");
1062 for (final ReceiveCommand cmd
: commands
) {
1063 if (cmd
.getResult() == Result
.OK
) {
1065 out
.sendString("ok " + cmd
.getRefName());
1069 final StringBuilder r
= new StringBuilder();
1071 r
.append(cmd
.getRefName());
1074 switch (cmd
.getResult()) {
1076 r
.append("server bug; ref not processed");
1079 case REJECTED_NOCREATE
:
1080 r
.append("creation prohibited");
1083 case REJECTED_NODELETE
:
1084 r
.append("deletion prohibited");
1087 case REJECTED_NONFASTFORWARD
:
1088 r
.append("non-fast forward");
1091 case REJECTED_CURRENT_BRANCH
:
1092 r
.append("branch is currently checked out");
1095 case REJECTED_MISSING_OBJECT
:
1096 if (cmd
.getMessage() == null)
1097 r
.append("missing object(s)");
1098 else if (cmd
.getMessage().length() == Constants
.OBJECT_ID_STRING_LENGTH
)
1099 r
.append("object " + cmd
.getMessage() + " missing");
1101 r
.append(cmd
.getMessage());
1104 case REJECTED_OTHER_REASON
:
1105 if (cmd
.getMessage() == null)
1106 r
.append("unspecified reason");
1108 r
.append(cmd
.getMessage());
1112 r
.append("failed to lock");
1116 // We shouldn't have reached this case (see 'ok' case above).
1119 out
.sendString(r
.toString());
1123 static abstract class Reporter
{
1124 abstract void sendString(String s
) throws IOException
;