Disambiguate pkt-line "0000" from "0004"
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / ReceivePack.java
blobabaf8768f91f0a54857d79fecb75cdd19e120f94
1 /*
2 * Copyright (C) 2008, Google Inc.
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
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
21 * written permission.
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;
52 import java.util.Map;
53 import java.util.Set;
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;
73 /**
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.
143 * @param into
144 * the destination repository.
146 public ReceivePack(final Repository into) {
147 db = into;
148 walk = new RevWalk(db);
150 final RepositoryConfig cfg = db.getConfig();
151 checkReceivedObjects = cfg.getBoolean("receive", "fsckobjects", false);
152 allowCreates = true;
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() {
163 return db;
166 /** @return the RevWalk instance used by this connection. */
167 public final RevWalk getRevWalk() {
168 return walk;
171 /** @return all refs which were advertised to the client. */
172 public final Map<String, Ref> getAdvertisedRefs() {
173 return refs;
177 * @return true if this instance will verify received objects are formatted
178 * correctly. Validating objects requires more CPU time on this side
179 * of the connection.
181 public boolean isCheckReceivedObjects() {
182 return checkReceivedObjects;
186 * @param check
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() {
196 return allowCreates;
200 * @param canCreate
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() {
209 return allowDeletes;
213 * @param canDelete
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;
229 * @param canRewind
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() {
239 return refLogIdent;
243 * Set the identity of the user appearing in the affected reflogs.
244 * <p>
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.
249 * @param pi
250 * identity of the user. If null the identity will be
251 * automatically determined based on the repository
252 * configuration.
254 public void setRefLogIdent(final PersonIdent pi) {
255 refLogIdent = pi;
258 /** @return get the hook invoked before updates occur. */
259 public PreReceiveHook getPreReceiveHook() {
260 return preReceive;
264 * Set the hook which is invoked prior to commands being executed.
265 * <p>
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.
270 * <p>
271 * The hook may be called with an empty command collection if the current
272 * set is completely invalid.
274 * @param h
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() {
283 return postReceive;
287 * Set the hook which is invoked after commands are executed.
288 * <p>
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.
293 * @param h
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.
307 * <p>
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.
310 * <p>
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.
317 * @param what
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.
327 * <p>
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.
331 * @param what
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) {
340 if (msgs != null)
341 msgs.println(type + ": " + what);
345 * Execute the receive task on the socket.
347 * @param input
348 * raw input to read client commands and pack data from. Caller
349 * must ensure the input is buffered, otherwise read performance
350 * may suffer.
351 * @param output
352 * response back to the Git network client. Caller must ensure
353 * the output is buffered, otherwise write performance may
354 * suffer.
355 * @param messages
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 {
364 try {
365 rawIn = input;
366 rawOut = output;
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),
373 8192)) {
374 @Override
375 public void println() {
376 print('\n');
381 enabledCapablities = new HashSet<String>();
382 commands = new ArrayList<ReceiveCommand>();
384 service();
385 } finally {
386 try {
387 if (msgs != null) {
388 msgs.flush();
390 } finally {
391 unlockPack();
392 rawIn = null;
393 rawOut = null;
394 pckIn = null;
395 pckOut = null;
396 msgs = null;
397 refs = null;
398 enabledCapablities = null;
399 commands = null;
404 private void service() throws IOException {
405 sendAdvertisedRefs();
406 recvCommands();
407 if (!commands.isEmpty()) {
408 enableCapabilities();
410 if (needPack()) {
411 try {
412 receivePack();
413 if (isCheckReceivedObjects())
414 checkConnectivity();
415 unpackError = null;
416 } catch (IOException err) {
417 unpackError = err;
418 } catch (RuntimeException err) {
419 unpackError = err;
420 } catch (Error err) {
421 unpackError = err;
425 if (unpackError == null) {
426 validateCommands();
427 executeCommands();
429 unlockPack();
431 if (reportStatus) {
432 sendStatusReport(true, new Reporter() {
433 void sendString(final String s) throws IOException {
434 pckOut.writeString(s + "\n");
437 pckOut.end();
438 } else if (msgs != null) {
439 sendStatusReport(false, new Reporter() {
440 void sendString(final String s) throws IOException {
441 msgs.println(s);
444 msgs.flush();
447 postReceive.onPostReceive(this, filterCommands(Result.OK));
451 private void unlockPack() {
452 if (packLock != null) {
453 packLock.unlock();
454 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();
465 if (i.hasNext()) {
466 final Ref r = i.next();
467 format(m, idtmp, r.getObjectId(), r.getOrigName());
468 } else {
469 format(m, idtmp, ObjectId.zeroId(), "capabilities^{}");
471 m.append('\0');
472 m.append(' ');
473 m.append(CAPABILITY_DELETE_REFS);
474 m.append(' ');
475 m.append(CAPABILITY_REPORT_STATUS);
476 if (allowOfsDelta) {
477 m.append(' ');
478 m.append(CAPABILITY_OFS_DELTA);
480 m.append(' ');
481 writeAdvertisedRef(m);
484 while (i.hasNext()) {
485 final Ref r = i.next();
486 format(m, idtmp, r.getObjectId(), r.getOrigName());
487 writeAdvertisedRef(m);
489 pckOut.end();
492 private void format(final StringBuilder m, final char[] idtmp,
493 final ObjectId id, final String name) {
494 m.setLength(0);
495 id.copyTo(idtmp, m);
496 m.append(' ');
497 m.append(name);
500 private void writeAdvertisedRef(final StringBuilder m) throws IOException {
501 m.append('\n');
502 pckOut.writeString(m.toString());
505 private void recvCommands() throws IOException {
506 for (;;) {
507 String line;
508 try {
509 line = pckIn.readStringRaw();
510 } catch (EOFException eof) {
511 if (commands.isEmpty())
512 return;
513 throw eof;
515 if (line == PacketLineIn.END)
516 break;
518 if (commands.isEmpty()) {
519 final int nul = line.indexOf('\0');
520 if (nul >= 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'";
529 sendError(m);
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()));
538 commands.add(cmd);
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)
549 return true;
551 return false;
554 private void receivePack() throws IOException {
555 final IndexPack ip = IndexPack.create(db, rawIn);
556 ip.setFixThin(true);
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)
570 continue;
571 if (cmd.getType() == ReceiveCommand.Type.DELETE)
572 continue;
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)
584 continue;
586 if (cmd.getType() == ReceiveCommand.Type.DELETE
587 && !isAllowDeletes()) {
588 // Deletes are not supported on this repository.
590 cmd.setResult(Result.REJECTED_NODELETE);
591 continue;
594 if (cmd.getType() == ReceiveCommand.Type.CREATE) {
595 if (!isAllowCreates()) {
596 cmd.setResult(Result.REJECTED_NOCREATE);
597 continue;
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);
605 continue;
608 if (ref != null) {
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");
613 continue;
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");
626 continue;
629 if (cmd.getType() == ReceiveCommand.Type.UPDATE) {
630 if (ref == null) {
631 // The ref must have been advertised in order to be updated.
633 cmd.setResult(Result.REJECTED_OTHER_REASON, "no such ref");
634 continue;
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");
643 continue;
646 // Is this possibly a non-fast-forward style update?
648 RevObject oldObj, newObj;
649 try {
650 oldObj = walk.parseAny(cmd.getOldId());
651 } catch (IOException e) {
652 cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
653 .getOldId().name());
654 continue;
657 try {
658 newObj = walk.parseAny(cmd.getNewId());
659 } catch (IOException e) {
660 cmd.setResult(Result.REJECTED_MISSING_OBJECT, cmd
661 .getNewId().name());
662 continue;
665 if (oldObj instanceof RevCommit && newObj instanceof RevCommit) {
666 try {
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
674 .getMessage());
675 } catch (IOException e) {
676 cmd.setResult(Result.REJECTED_OTHER_REASON);
678 } else {
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))
693 execute(cmd);
696 private void execute(final ReceiveCommand cmd) {
697 try {
698 final RefUpdate ru = db.updateRef(cmd.getRefName());
699 ru.setRefLogIdent(getRefLogIdent());
700 switch (cmd.getType()) {
701 case DELETE:
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));
711 break;
713 case CREATE:
714 case UPDATE:
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));
721 break;
723 } catch (IOException err) {
724 cmd.setResult(Result.REJECTED_OTHER_REASON, "lock error: "
725 + err.getMessage());
729 private void status(final ReceiveCommand cmd, final RefUpdate.Result result) {
730 switch (result) {
731 case NOT_ATTEMPTED:
732 cmd.setResult(Result.NOT_ATTEMPTED);
733 break;
735 case LOCK_FAILURE:
736 case IO_FAILURE:
737 cmd.setResult(Result.LOCK_FAILURE);
738 break;
740 case NO_CHANGE:
741 case NEW:
742 case FORCED:
743 case FAST_FORWARD:
744 cmd.setResult(Result.OK);
745 break;
747 case REJECTED:
748 cmd.setResult(Result.REJECTED_NONFASTFORWARD);
749 break;
751 case REJECTED_CURRENT_BRANCH:
752 cmd.setResult(Result.REJECTED_CURRENT_BRANCH);
753 break;
755 default:
756 cmd.setResult(Result.REJECTED_OTHER_REASON, result.name());
757 break;
761 private List<ReceiveCommand> filterCommands(final Result want) {
762 final List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands
763 .size());
764 for (final ReceiveCommand cmd : commands) {
765 if (cmd.getResult() == want)
766 r.add(cmd);
768 return r;
771 private void sendStatusReport(final boolean forClient, final Reporter out)
772 throws IOException {
773 if (unpackError != null) {
774 out.sendString("unpack error " + unpackError.getMessage());
775 if (forClient) {
776 for (final ReceiveCommand cmd : commands) {
777 out.sendString("ng " + cmd.getRefName()
778 + " n/a (unpacker error)");
781 return;
784 if (forClient)
785 out.sendString("unpack ok");
786 for (final ReceiveCommand cmd : commands) {
787 if (cmd.getResult() == Result.OK) {
788 if (forClient)
789 out.sendString("ok " + cmd.getRefName());
790 continue;
793 final StringBuilder r = new StringBuilder();
794 r.append("ng ");
795 r.append(cmd.getRefName());
796 r.append(" ");
798 switch (cmd.getResult()) {
799 case NOT_ATTEMPTED:
800 r.append("server bug; ref not processed");
801 break;
803 case REJECTED_NOCREATE:
804 r.append("creation prohibited");
805 break;
807 case REJECTED_NODELETE:
808 r.append("deletion prohibited");
809 break;
811 case REJECTED_NONFASTFORWARD:
812 r.append("non-fast forward");
813 break;
815 case REJECTED_CURRENT_BRANCH:
816 r.append("branch is currently checked out");
817 break;
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");
824 else
825 r.append(cmd.getMessage());
826 break;
828 case REJECTED_OTHER_REASON:
829 if (cmd.getMessage() == null)
830 r.append("unspecified reason");
831 else
832 r.append(cmd.getMessage());
833 break;
835 case LOCK_FAILURE:
836 r.append("failed to lock");
837 break;
839 case OK:
840 // We shouldn't have reached this case (see 'ok' case above).
841 continue;
843 out.sendString(r.toString());
847 static abstract class Reporter {
848 abstract void sendString(String s) throws IOException;