ReceivePack: Clarify the check reachable option
[jgit.git] / org.eclipse.jgit / src / org / eclipse / jgit / transport / ReceivePack.java
blob4e62d7427f4cc036684863eec6b738fb8e9c2d54
1 /*
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
14 * conditions are met:
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
27 * written permission.
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;
64 import java.util.Map;
65 import java.util.Set;
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;
94 /**
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?
106 * <p>
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.
111 * <p>
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. */
144 private int timeout;
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;
159 private Writer msgs;
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.
192 * @param into
193 * the destination repository.
195 public ReceivePack(final Repository into) {
196 db = 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",
229 false);
230 allowCreates = true;
231 allowDeletes = !config.getBoolean("receive", "denydeletes", false);
232 allowNonFastForwards = !config.getBoolean("receive",
233 "denynonfastforwards", false);
234 allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset",
235 true);
239 /** @return the repository this receive completes into. */
240 public final Repository getRepository() {
241 return db;
244 /** @return the RevWalk instance used by this connection. */
245 public final RevWalk getRevWalk() {
246 return walk;
249 /** @return all refs which were advertised to the client. */
250 public final Map<String, Ref> getAdvertisedRefs() {
251 return refs;
255 * @return true if this instance will validate all referenced, but not
256 * supplied by the client, objects are reachable from another
257 * reference.
259 public boolean isCheckReferencedObjectsAreReachable() {
260 return checkReferencedIsReachable;
264 * Validate all referenced but not supplied objects are reachable.
265 * <p>
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()}.
270 * <p>
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}.
275 * <p>
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.
280 * @param b
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;
296 * @param twoWay
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
311 * of the connection.
313 public boolean isCheckReceivedObjects() {
314 return checkReceivedObjects;
318 * @param check
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() {
328 return allowCreates;
332 * @param canCreate
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() {
341 return allowDeletes;
345 * @param canDelete
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;
361 * @param canRewind
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() {
371 return refLogIdent;
375 * Set the identity of the user appearing in the affected reflogs.
376 * <p>
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.
381 * @param pi
382 * identity of the user. If null the identity will be
383 * automatically determined based on the repository
384 * configuration.
386 public void setRefLogIdent(final PersonIdent pi) {
387 refLogIdent = pi;
390 /** @return the filter used while advertising the refs to the client */
391 public RefFilter getRefFilter() {
392 return refFilter;
396 * Set the filter used while advertising the refs to the client.
397 * <p>
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}.
403 * @param refFilter
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() {
412 return preReceive;
416 * Set the hook which is invoked prior to commands being executed.
417 * <p>
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.
422 * <p>
423 * The hook may be called with an empty command collection if the current
424 * set is completely invalid.
426 * @param h
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() {
435 return postReceive;
439 * Set the hook which is invoked after commands are executed.
440 * <p>
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.
445 * @param h
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() {
454 return timeout;
458 * Set the timeout before willing to abort an IO call.
460 * @param seconds
461 * number of seconds to wait (with no data transfer occurring)
462 * before aborting an IO read or write operation with the
463 * connected client.
465 public void setTimeout(final int seconds) {
466 timeout = 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.
476 * <p>
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.
481 * <p>
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
485 * the client.
486 * <p>
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.
493 * @param what
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) {
498 if (refs == null) {
499 if (advertiseError == null)
500 advertiseError = new StringBuilder();
501 advertiseError.append(what).append('\n');
502 } else {
503 try {
504 if (msgs != null)
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.
514 * <p>
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.
518 * @param what
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) {
523 try {
524 if (msgs != null)
525 msgs.write(what + "\n");
526 } catch (IOException e) {
527 // Ignore write failures.
532 * Execute the receive task on the socket.
534 * @param input
535 * raw input to read client commands and pack data from. Caller
536 * must ensure the input is buffered, otherwise read performance
537 * may suffer.
538 * @param output
539 * response back to the Git network client. Caller must ensure
540 * the output is buffered, otherwise write performance may
541 * suffer.
542 * @param messages
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 {
551 try {
552 rawIn = input;
553 rawOut = output;
555 if (timeout > 0) {
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);
562 rawIn = timeoutIn;
563 rawOut = o;
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>();
574 service();
575 } finally {
576 try {
577 if (pckOut != null)
578 pckOut.flush();
579 if (msgs != null)
580 msgs.flush();
582 if (sideBand) {
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();
591 } finally {
592 unlockPack();
593 timeoutIn = null;
594 rawIn = null;
595 rawOut = null;
596 pckIn = null;
597 pckOut = null;
598 msgs = null;
599 refs = null;
600 enabledCapablities = null;
601 commands = null;
602 if (timer != null) {
603 try {
604 timer.terminate();
605 } finally {
606 timer = null;
613 private void service() throws IOException {
614 if (biDirectionalPipe)
615 sendAdvertisedRefs(new PacketLineOutRefAdvertiser(pckOut));
616 else
617 refs = refFilter.filter(db.getAllRefs());
618 if (advertiseError != null)
619 return;
620 recvCommands();
621 if (!commands.isEmpty()) {
622 enableCapabilities();
624 if (needPack()) {
625 try {
626 receivePack();
627 if (needCheckConnectivity())
628 checkConnectivity();
629 ip = null;
630 unpackError = null;
631 } catch (IOException err) {
632 unpackError = err;
633 } catch (RuntimeException err) {
634 unpackError = err;
635 } catch (Error err) {
636 unpackError = err;
640 if (unpackError == null) {
641 validateCommands();
642 executeCommands();
644 unlockPack();
646 if (reportStatus) {
647 sendStatusReport(true, new Reporter() {
648 void sendString(final String s) throws IOException {
649 pckOut.writeString(s + "\n");
652 pckOut.end();
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) {
667 packLock.unlock();
668 packLock = null;
673 * Generate an advertisement of available refs and capabilities.
675 * @param adv
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);
683 return;
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);
691 if (allowOfsDelta)
692 adv.advertiseCapability(CAPABILITY_OFS_DELTA);
693 refs = refFilter.filter(db.getAllRefs());
694 final Ref head = refs.remove(Constants.HEAD);
695 adv.send(refs);
696 if (head != null && !head.isSymbolic())
697 adv.advertiseHave(head.getObjectId());
698 adv.includeAdditionalHaves();
699 if (adv.isEmpty())
700 adv.advertiseId(ObjectId.zeroId(), "capabilities^{}");
701 adv.end();
704 private void recvCommands() throws IOException {
705 for (;;) {
706 String line;
707 try {
708 line = pckIn.readStringRaw();
709 } catch (EOFException eof) {
710 if (commands.isEmpty())
711 return;
712 throw eof;
714 if (line == PacketLineIn.END)
715 break;
717 if (commands.isEmpty()) {
718 final int nul = line.indexOf('\0');
719 if (nul >= 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'";
728 sendError(m);
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);
738 } else {
739 cmd.setRef(refs.get(cmd.getRefName()));
741 commands.add(cmd);
745 private void enableCapabilities() {
746 reportStatus = enabledCapablities.contains(CAPABILITY_REPORT_STATUS);
748 sideBand = enabledCapablities.contains(CAPABILITY_SIDE_BAND_64K);
749 if (sideBand) {
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)
762 return true;
764 return false;
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);
776 ip.setFixThin(true);
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();
804 ip = null;
806 final ObjectWalk ow = new ObjectWalk(db);
807 for (final ReceiveCommand cmd : commands) {
808 if (cmd.getResult() != Result.NOT_ATTEMPTED)
809 continue;
810 if (cmd.getType() == ReceiveCommand.Type.DELETE)
811 continue;
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());
836 RevCommit c;
837 while ((c = ow.next()) != null) {
838 if (checkReferencedIsReachable && !providedObjects.contains(c))
839 throw new MissingObjectException(c, Constants.TYPE_COMMIT);
842 RevObject o;
843 while ((o = ow.nextObject()) != null) {
844 if (checkReferencedIsReachable) {
845 if (providedObjects.contains(o))
846 continue;
847 else
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)
860 continue;
862 if (cmd.getType() == ReceiveCommand.Type.DELETE
863 && !isAllowDeletes()) {
864 // Deletes are not supported on this repository.
866 cmd.setResult(Result.REJECTED_NODELETE);
867 continue;
870 if (cmd.getType() == ReceiveCommand.Type.CREATE) {
871 if (!isAllowCreates()) {
872 cmd.setResult(Result.REJECTED_NOCREATE);
873 continue;
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);
881 continue;
884 if (ref != null) {
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");
889 continue;
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");
902 continue;
905 if (cmd.getType() == ReceiveCommand.Type.UPDATE) {
906 if (ref == null) {
907 // The ref must have been advertised in order to be updated.
909 cmd.setResult(Result.REJECTED_OTHER_REASON, "no such ref");
910 continue;
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");
919 continue;
922 // Is this possibly a non-fast-forward style update?
924 RevObject oldObj, newObj;
925 try {
926 oldObj = walk.parseAny(cmd.getOldId());
927 } catch (IOException e) {
928 cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
929 .getOldId().name());
930 continue;
933 try {
934 newObj = walk.parseAny(cmd.getNewId());
935 } catch (IOException e) {
936 cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
937 .getNewId().name());
938 continue;
941 if (oldObj instanceof RevCommit && newObj instanceof RevCommit) {
942 try {
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
950 .getMessage());
951 } catch (IOException e) {
952 cmd.setResult(Result.REJECTED_OTHER_REASON);
954 } else {
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))
969 execute(cmd);
972 private void execute(final ReceiveCommand cmd) {
973 try {
974 final RefUpdate ru = db.updateRef(cmd.getRefName());
975 ru.setRefLogIdent(getRefLogIdent());
976 switch (cmd.getType()) {
977 case DELETE:
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));
987 break;
989 case CREATE:
990 case UPDATE:
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));
997 break;
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) {
1006 switch (result) {
1007 case NOT_ATTEMPTED:
1008 cmd.setResult(Result.NOT_ATTEMPTED);
1009 break;
1011 case LOCK_FAILURE:
1012 case IO_FAILURE:
1013 cmd.setResult(Result.LOCK_FAILURE);
1014 break;
1016 case NO_CHANGE:
1017 case NEW:
1018 case FORCED:
1019 case FAST_FORWARD:
1020 cmd.setResult(Result.OK);
1021 break;
1023 case REJECTED:
1024 cmd.setResult(Result.REJECTED_NONFASTFORWARD);
1025 break;
1027 case REJECTED_CURRENT_BRANCH:
1028 cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
1029 break;
1031 default:
1032 cmd.setResult(Result.REJECTED_OTHER_REASON, result.name());
1033 break;
1037 private List<ReceiveCommand> filterCommands(final Result want) {
1038 final List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands
1039 .size());
1040 for (final ReceiveCommand cmd : commands) {
1041 if (cmd.getResult() == want)
1042 r.add(cmd);
1044 return r;
1047 private void sendStatusReport(final boolean forClient, final Reporter out)
1048 throws IOException {
1049 if (unpackError != null) {
1050 out.sendString("unpack error " + unpackError.getMessage());
1051 if (forClient) {
1052 for (final ReceiveCommand cmd : commands) {
1053 out.sendString("ng " + cmd.getRefName()
1054 + " n/a (unpacker error)");
1057 return;
1060 if (forClient)
1061 out.sendString("unpack ok");
1062 for (final ReceiveCommand cmd : commands) {
1063 if (cmd.getResult() == Result.OK) {
1064 if (forClient)
1065 out.sendString("ok " + cmd.getRefName());
1066 continue;
1069 final StringBuilder r = new StringBuilder();
1070 r.append("ng ");
1071 r.append(cmd.getRefName());
1072 r.append(" ");
1074 switch (cmd.getResult()) {
1075 case NOT_ATTEMPTED:
1076 r.append("server bug; ref not processed");
1077 break;
1079 case REJECTED_NOCREATE:
1080 r.append("creation prohibited");
1081 break;
1083 case REJECTED_NODELETE:
1084 r.append("deletion prohibited");
1085 break;
1087 case REJECTED_NONFASTFORWARD:
1088 r.append("non-fast forward");
1089 break;
1091 case REJECTED_CURRENT_BRANCH:
1092 r.append("branch is currently checked out");
1093 break;
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");
1100 else
1101 r.append(cmd.getMessage());
1102 break;
1104 case REJECTED_OTHER_REASON:
1105 if (cmd.getMessage() == null)
1106 r.append("unspecified reason");
1107 else
1108 r.append(cmd.getMessage());
1109 break;
1111 case LOCK_FAILURE:
1112 r.append("failed to lock");
1113 break;
1115 case OK:
1116 // We shouldn't have reached this case (see 'ok' case above).
1117 continue;
1119 out.sendString(r.toString());
1123 static abstract class Reporter {
1124 abstract void sendString(String s) throws IOException;