diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java index 07ef39dc7..9899d14d8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java @@ -56,6 +56,7 @@ import java.util.Collections; import java.util.List; import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; import org.junit.After; import org.junit.Before; @@ -209,7 +210,7 @@ public class TransportTest extends SampleDataRepositoryTestCase { assertEquals("refs/remotes/test/a", tru.getLocalName()); assertEquals("refs/heads/a", tru.getRemoteName()); assertEquals(db.resolve("refs/heads/a"), tru.getNewObjectId()); - assertNull(tru.getOldObjectId()); + assertEquals(ObjectId.zeroId(), tru.getOldObjectId()); } @Test diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java new file mode 100644 index 000000000..e198e07e4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2008-2012, Google Inc. + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.lib; + +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; + +/** + * Batch of reference updates to be applied to a repository. + *

+ * The batch update is primarily useful in the transport code, where a client or + * server is making changes to more than one reference at a time. + */ +public class BatchRefUpdate { + private final RefDatabase refdb; + + /** Commands to apply during this batch. */ + private final List commands; + + /** Does the caller permit a forced update on a reference? */ + private boolean allowNonFastForwards; + + /** Identity to record action as within the reflog. */ + private PersonIdent refLogIdent; + + /** Message the caller wants included in the reflog. */ + private String refLogMessage; + + /** Should the result value be appended to {@link #refLogMessage}. */ + private boolean refLogIncludeResult; + + /** + * Initialize a new batch update. + * + * @param refdb + * the reference database of the repository to be updated. + */ + protected BatchRefUpdate(RefDatabase refdb) { + this.refdb = refdb; + this.commands = new ArrayList(); + } + + /** + * @return true if the batch update will permit a non-fast-forward update to + * an existing reference. + */ + public boolean isAllowNonFastForwards() { + return allowNonFastForwards; + } + + /** + * Set if this update wants to permit a forced update. + * + * @param allow + * true if this update batch should ignore merge tests. + * @return {@code this}. + */ + public BatchRefUpdate setAllowNonFastForwards(boolean allow) { + allowNonFastForwards = allow; + return this; + } + + /** @return identity of the user making the change in the reflog. */ + public PersonIdent getRefLogIdent() { + return refLogIdent; + } + + /** + * Set the identity of the user appearing in the reflog. + *

+ * The timestamp portion of the identity is ignored. A new identity with the + * current timestamp will be created automatically when the update occurs + * and the log record is written. + * + * @param pi + * identity of the user. If null the identity will be + * automatically determined based on the repository + * configuration. + * @return {@code this}. + */ + public BatchRefUpdate setRefLogIdent(final PersonIdent pi) { + refLogIdent = pi; + return this; + } + + /** + * Get the message to include in the reflog. + * + * @return message the caller wants to include in the reflog; null if the + * update should not be logged. + */ + public String getRefLogMessage() { + return refLogMessage; + } + + /** @return {@code true} if the ref log message should show the result. */ + public boolean isRefLogIncludingResult() { + return refLogIncludeResult; + } + + /** + * Set the message to include in the reflog. + * + * @param msg + * the message to describe this change. It may be null if + * appendStatus is null in order not to append to the reflog + * @param appendStatus + * true if the status of the ref change (fast-forward or + * forced-update) should be appended to the user supplied + * message. + * @return {@code this}. + */ + public BatchRefUpdate setRefLogMessage(String msg, boolean appendStatus) { + if (msg == null && !appendStatus) + disableRefLog(); + else if (msg == null && appendStatus) { + refLogMessage = ""; + refLogIncludeResult = true; + } else { + refLogMessage = msg; + refLogIncludeResult = appendStatus; + } + return this; + } + + /** + * Don't record this update in the ref's associated reflog. + * + * @return {@code this}. + */ + public BatchRefUpdate disableRefLog() { + refLogMessage = null; + refLogIncludeResult = false; + return this; + } + + /** @return true if log has been disabled by {@link #disableRefLog()}. */ + public boolean isRefLogDisabled() { + return refLogMessage == null; + } + + /** @return commands this update will process. */ + public List getCommands() { + return Collections.unmodifiableList(commands); + } + + /** + * Add a single command to this batch update. + * + * @param cmd + * the command to add, must not be null. + * @return {@code this}. + */ + public BatchRefUpdate addCommand(ReceiveCommand cmd) { + commands.add(cmd); + return this; + } + + /** + * Add commands to this batch update. + * + * @param cmd + * the commands to add, must not be null. + * @return {@code this}. + */ + public BatchRefUpdate addCommand(ReceiveCommand... cmd) { + return addCommand(Arrays.asList(cmd)); + } + + /** + * Add commands to this batch update. + * + * @param cmd + * the commands to add, must not be null. + * @return {@code this}. + */ + public BatchRefUpdate addCommand(Collection cmd) { + commands.addAll(cmd); + return this; + } + + /** + * Execute this batch update. + *

+ * The default implementation of this method performs a sequential reference + * update over each reference. + * + * @param walk + * a RevWalk to parse tags in case the storage system wants to + * store them pre-peeled, a common performance optimization. + * @param update + * progress monitor to receive update status on. + * @throws IOException + * the database is unable to accept the update. Individual + * command status must be tested to determine if there is a + * partial failure, or a total failure. + */ + public void execute(RevWalk walk, ProgressMonitor update) + throws IOException { + update.beginTask(JGitText.get().updatingReferences, commands.size()); + for (ReceiveCommand cmd : commands) { + try { + update.update(1); + + if (cmd.getResult() == NOT_ATTEMPTED) { + cmd.updateType(walk); + RefUpdate ru = newUpdate(cmd); + switch (cmd.getType()) { + case DELETE: + cmd.setResult(ru.delete(walk)); + continue; + + case CREATE: + case UPDATE: + case UPDATE_NONFASTFORWARD: + cmd.setResult(ru.update(walk)); + continue; + } + } + } catch (IOException err) { + cmd.setResult(REJECTED_OTHER_REASON, MessageFormat.format( + JGitText.get().lockError, err.getMessage())); + } + } + update.endTask(); + } + + /** + * Create a new RefUpdate copying the batch settings. + * + * @param cmd + * specific command the update should be created to copy. + * @return a single reference update command. + * @throws IOException + * the reference database cannot make a new update object for + * the given reference. + */ + protected RefUpdate newUpdate(ReceiveCommand cmd) throws IOException { + RefUpdate ru = refdb.newUpdate(cmd.getRefName(), false); + if (isRefLogDisabled()) + ru.disableRefLog(); + else { + ru.setRefLogIdent(refLogIdent); + ru.setRefLogMessage(refLogMessage, refLogIncludeResult); + } + switch (cmd.getType()) { + case DELETE: + if (!ObjectId.zeroId().equals(cmd.getOldId())) + ru.setExpectedOldObjectId(cmd.getOldId()); + ru.setForceUpdate(true); + return ru; + + case CREATE: + case UPDATE: + case UPDATE_NONFASTFORWARD: + default: + ru.setForceUpdate(isAllowNonFastForwards()); + ru.setExpectedOldObjectId(cmd.getOldId()); + ru.setNewObjectId(cmd.getNewId()); + return ru; + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java index 33c362305..ed2af4a00 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -146,6 +146,17 @@ public abstract class RefDatabase { public abstract RefRename newRename(String fromName, String toName) throws IOException; + /** + * Create a new batch update to attempt on this database. + *

+ * The default implementation performs a sequential update of each command. + * + * @return a new batch update object. + */ + public BatchRefUpdate newBatchUpdate() { + return new BatchRefUpdate(this); + } + /** * Read a single reference. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index c0d44b1db..e375221ee 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -67,6 +67,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Constants; @@ -1086,11 +1087,11 @@ public abstract class BaseReceivePack { if (oldObj instanceof RevCommit && newObj instanceof RevCommit) { try { - if (!walk.isMergedInto((RevCommit) oldObj, - (RevCommit) newObj)) { - cmd - .setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); - } + if (walk.isMergedInto((RevCommit) oldObj, + (RevCommit) newObj)) + cmd.setTypeFastForwardUpdate(); + else + cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); } catch (MissingObjectException e) { cmd.setResult(Result.REJECTED_MISSING_OBJECT, e .getMessage()); @@ -1100,6 +1101,12 @@ public abstract class BaseReceivePack { } else { cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); } + + if (cmd.getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD + && !isAllowNonFastForwards()) { + cmd.setResult(Result.REJECTED_NONFASTFORWARD); + continue; + } } if (!cmd.getRefName().startsWith(Constants.R_REFS) @@ -1123,20 +1130,30 @@ public abstract class BaseReceivePack { /** Execute commands to update references. */ protected void executeCommands() { - List toApply = ReceiveCommand.filter(commands, - Result.NOT_ATTEMPTED); + List toApply = filterCommands(Result.NOT_ATTEMPTED); + if (toApply.isEmpty()) + return; + ProgressMonitor updating = NullProgressMonitor.INSTANCE; if (sideBand) { SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut); pm.setDelayStart(250, TimeUnit.MILLISECONDS); updating = pm; } - updating.beginTask(JGitText.get().updatingReferences, toApply.size()); - for (ReceiveCommand cmd : toApply) { - updating.update(1); - cmd.execute(this); + + BatchRefUpdate batch = db.getRefDatabase().newBatchUpdate(); + batch.setAllowNonFastForwards(isAllowNonFastForwards()); + batch.setRefLogIdent(getRefLogIdent()); + batch.setRefLogMessage("push", true); + batch.addCommand(toApply); + try { + batch.execute(walk, updating); + } catch (IOException err) { + for (ReceiveCommand cmd : toApply) { + if (cmd.getResult() == Result.NOT_ATTEMPTED) + cmd.reject(err); + } } - updating.endTask(); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java index 27abf789d..66d0c62dd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -44,6 +44,11 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.OK; +import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_NONFASTFORWARD; +import static org.eclipse.jgit.transport.ReceiveCommand.Type.UPDATE_NONFASTFORWARD; + import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; @@ -63,12 +68,13 @@ import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.LockFile; @@ -97,6 +103,8 @@ class FetchProcess { private FetchConnection conn; + private Map localRefs; + FetchProcess(final Transport t, final Collection f) { transport = t; toFetch = f; @@ -108,6 +116,7 @@ class FetchProcess { localUpdates.clear(); fetchHeadUpdates.clear(); packLocks.clear(); + localRefs = null; try { executeImp(monitor, result); @@ -183,27 +192,40 @@ class FetchProcess { closeConnection(result); } + BatchRefUpdate batch = transport.local.getRefDatabase() + .newBatchUpdate() + .setAllowNonFastForwards(true) + .setRefLogMessage("fetch", true); final RevWalk walk = new RevWalk(transport.local); try { if (monitor instanceof BatchingProgressMonitor) { ((BatchingProgressMonitor) monitor).setDelayStart( 250, TimeUnit.MILLISECONDS); } - monitor.beginTask(JGitText.get().updatingReferences, localUpdates.size()); if (transport.isRemoveDeletedRefs()) - deleteStaleTrackingRefs(result, walk); + deleteStaleTrackingRefs(result, batch); for (TrackingRefUpdate u : localUpdates) { - try { - monitor.update(1); - u.update(walk); - result.add(u); - } catch (IOException err) { - throw new TransportException(MessageFormat.format(JGitText - .get().failureUpdatingTrackingRef, - u.getLocalName(), err.getMessage()), err); - } + result.add(u); + batch.addCommand(u.asReceiveCommand()); } - monitor.endTask(); + for (ReceiveCommand cmd : batch.getCommands()) { + cmd.updateType(walk); + if (cmd.getType() == UPDATE_NONFASTFORWARD + && cmd instanceof TrackingRefUpdate.Command + && !((TrackingRefUpdate.Command) cmd).canForceUpdate()) + cmd.setResult(REJECTED_NONFASTFORWARD); + } + if (transport.isDryRun()) { + for (ReceiveCommand cmd : batch.getCommands()) { + if (cmd.getResult() == NOT_ATTEMPTED) + cmd.setResult(OK); + } + } else + batch.execute(walk, monitor); + } catch (IOException err) { + throw new TransportException(MessageFormat.format( + JGitText.get().failureUpdatingTrackingRef, + getFirstFailedRefName(batch), err.getMessage()), err); } finally { walk.release(); } @@ -320,7 +342,7 @@ class FetchProcess { try { for (final ObjectId want : askFor.keySet()) ow.markStart(ow.parseAny(want)); - for (final Ref ref : transport.local.getAllRefs().values()) + for (final Ref ref : localRefs().values()) ow.markUninteresting(ow.parseAny(ref.getObjectId())); ow.checkConnectivity(); } finally { @@ -354,7 +376,7 @@ class FetchProcess { private Collection expandAutoFollowTags() throws TransportException { final Collection additionalTags = new ArrayList(); - final Map haveRefs = transport.local.getAllRefs(); + final Map haveRefs = localRefs(); for (final Ref r : conn.getRefs()) { if (!isTag(r)) continue; @@ -385,7 +407,7 @@ class FetchProcess { } private void expandFetchTags() throws TransportException { - final Map haveRefs = transport.local.getAllRefs(); + final Map haveRefs = localRefs(); for (final Ref r : conn.getRefs()) { if (!isTag(r)) continue; @@ -404,17 +426,10 @@ class FetchProcess { throws TransportException { final ObjectId newId = src.getObjectId(); if (spec.getDestination() != null) { - try { - final TrackingRefUpdate tru = createUpdate(spec, newId); - if (newId.equals(tru.getOldObjectId())) - return; - localUpdates.add(tru); - } catch (IOException err) { - // Bad symbolic ref? That is the most likely cause. - // - throw new TransportException( MessageFormat.format( - JGitText.get().cannotResolveLocalTrackingRefForUpdating, spec.getDestination()), err); - } + final TrackingRefUpdate tru = createUpdate(spec, newId); + if (newId.equals(tru.getOldObjectId())) + return; + localUpdates.add(tru); } askFor.put(newId, src); @@ -427,21 +442,41 @@ class FetchProcess { fetchHeadUpdates.add(fhr); } - private TrackingRefUpdate createUpdate(final RefSpec spec, - final ObjectId newId) throws IOException { - return new TrackingRefUpdate(transport.local, spec, newId, "fetch"); + private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId) + throws TransportException { + Ref ref = localRefs().get(spec.getDestination()); + ObjectId oldId = ref != null && ref.getObjectId() != null + ? ref.getObjectId() + : ObjectId.zeroId(); + return new TrackingRefUpdate( + spec.isForceUpdate(), + spec.getSource(), + spec.getDestination(), + oldId, + newId); + } + + private Map localRefs() throws TransportException { + if (localRefs == null) { + try { + localRefs = transport.local.getRefDatabase() + .getRefs(RefDatabase.ALL); + } catch (IOException err) { + throw new TransportException(JGitText.get().cannotListRefs, err); + } + } + return localRefs; } - private void deleteStaleTrackingRefs(final FetchResult result, - final RevWalk walk) throws TransportException { - final Repository db = transport.local; - for (final Ref ref : db.getAllRefs().values()) { + private void deleteStaleTrackingRefs(FetchResult result, + BatchRefUpdate batch) throws IOException { + for (final Ref ref : localRefs().values()) { final String refname = ref.getName(); for (final RefSpec spec : toFetch) { if (spec.matchDestination(refname)) { final RefSpec s = spec.expandFromDestination(refname); if (result.getAdvertisedRef(s.getSource()) == null) { - deleteTrackingRef(result, db, walk, s, ref); + deleteTrackingRef(result, batch, s, ref); } } } @@ -449,31 +484,17 @@ class FetchProcess { } private void deleteTrackingRef(final FetchResult result, - final Repository db, final RevWalk walk, final RefSpec spec, - final Ref localRef) throws TransportException { - final String name = localRef.getName(); - try { - final TrackingRefUpdate u = new TrackingRefUpdate(db, name, spec - .getSource(), true, ObjectId.zeroId(), "deleted"); - result.add(u); - if (transport.isDryRun()){ - return; - } - u.delete(walk); - switch (u.getResult()) { - case NEW: - case NO_CHANGE: - case FAST_FORWARD: - case FORCED: - break; - default: - throw new TransportException(transport.getURI(), MessageFormat.format( - JGitText.get().cannotDeleteStaleTrackingRef2, name, u.getResult().name())); - } - } catch (IOException e) { - throw new TransportException(transport.getURI(), MessageFormat.format( - JGitText.get().cannotDeleteStaleTrackingRef, name), e); - } + final BatchRefUpdate batch, final RefSpec spec, final Ref localRef) { + if (localRef.getObjectId() == null) + return; + TrackingRefUpdate update = new TrackingRefUpdate( + true, + spec.getSource(), + localRef.getName(), + localRef.getObjectId(), + ObjectId.zeroId()); + result.add(update); + batch.addCommand(update.asReceiveCommand()); } private static boolean isTag(final Ref r) { @@ -483,4 +504,12 @@ class FetchProcess { private static boolean isTag(final String name) { return name.startsWith(Constants.R_TAGS); } + + private static String getFirstFailedRefName(BatchRefUpdate batch) { + for (ReceiveCommand cmd : batch.getCommands()) { + if (cmd.getResult() != ReceiveCommand.Result.OK) + return cmd.getRefName(); + } + return ""; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java index 96814c85a..4c7ffecce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java @@ -49,9 +49,13 @@ import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevWalk; /** * A command being processed by {@link BaseReceivePack}. @@ -157,6 +161,8 @@ public class ReceiveCommand { private String message; + private boolean typeIsCorrect; + /** * Create a new command for {@link BaseReceivePack}. * @@ -264,6 +270,36 @@ public class ReceiveCommand { message = m; } + /** + * Update the type of this command by checking for fast-forward. + *

+ * If the command's current type is UPDATE, a merge test will be performed + * using the supplied RevWalk to determine if {@link #getOldId()} is fully + * merged into {@link #getNewId()}. If some commits are not merged the + * update type is changed to {@link Type#UPDATE_NONFASTFORWARD}. + * + * @param walk + * an instance to perform the merge test with. The caller must + * allocate and release this object. + * @throws IOException + * either oldId or newId is not accessible in the repository + * used by the RevWalk. This usually indicates data corruption, + * and the command cannot be processed. + */ + public void updateType(RevWalk walk) throws IOException { + if (typeIsCorrect) + return; + if (type == Type.UPDATE && !AnyObjectId.equals(oldId, newId)) { + RevObject o = walk.parseAny(oldId); + RevObject n = walk.parseAny(newId); + if (!(o instanceof RevCommit) + || !(n instanceof RevCommit) + || !walk.isMergedInto((RevCommit) o, (RevCommit) n)) + setType(Type.UPDATE_NONFASTFORWARD); + } + typeIsCorrect = true; + } + /** * Execute this command during a receive-pack session. *

@@ -301,8 +337,7 @@ public class ReceiveCommand { break; } } catch (IOException err) { - setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format( - JGitText.get().lockError, err.getMessage())); + reject(err); } } @@ -314,7 +349,18 @@ public class ReceiveCommand { type = t; } - private void setResult(final RefUpdate.Result r) { + void setTypeFastForwardUpdate() { + type = Type.UPDATE; + typeIsCorrect = true; + } + + /** + * Set the result of this command. + * + * @param r + * the new result code for this command. + */ + public void setResult(RefUpdate.Result r) { switch (r) { case NOT_ATTEMPTED: setResult(Result.NOT_ATTEMPTED); @@ -346,6 +392,11 @@ public class ReceiveCommand { } } + void reject(IOException err) { + setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format( + JGitText.get().lockError, err.getMessage())); + } + @Override public String toString() { return getType().name() + ": " + getOldId().name() + " " diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java index 3aaf7bb4e..421830202 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java @@ -49,6 +49,7 @@ import java.text.MessageFormat; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; @@ -144,6 +145,8 @@ public class RemoteRefUpdate { private final Repository localDb; + private RefUpdate localUpdate; + /** * Construct remote ref update request by providing an update specification. * Object is created with default {@link Status#NOT_ATTEMPTED} status and no @@ -299,10 +302,20 @@ public class RemoteRefUpdate { this.remoteName = remoteName; this.forceUpdate = forceUpdate; - if (localName != null && localDb != null) - trackingRefUpdate = new TrackingRefUpdate(localDb, localName, - remoteName, true, newObjectId, "push"); - else + if (localName != null && localDb != null) { + localUpdate = localDb.updateRef(localName); + localUpdate.setForceUpdate(true); + localUpdate.setRefLogMessage("push", true); + localUpdate.setNewObjectId(newObjectId); + trackingRefUpdate = new TrackingRefUpdate( + true, + remoteName, + localName, + localUpdate.getOldObjectId() != null + ? localUpdate.getOldObjectId() + : ObjectId.zeroId(), + newObjectId); + } else trackingRefUpdate = null; this.localDb = localDb; this.expectedOldObjectId = expectedOldObjectId; @@ -449,9 +462,9 @@ public class RemoteRefUpdate { */ protected void updateTrackingRef(final RevWalk walk) throws IOException { if (isDelete()) - trackingRefUpdate.delete(walk); + trackingRefUpdate.setResult(localUpdate.delete(walk)); else - trackingRefUpdate.update(walk); + trackingRefUpdate.setResult(localUpdate.update(walk)); } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java index 03ecd81e0..3344c3f6a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java @@ -45,35 +45,31 @@ package org.eclipse.jgit.transport; -import java.io.IOException; - import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.RefUpdate; -import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.lib.RefUpdate.Result; -import org.eclipse.jgit.revwalk.RevWalk; /** Update of a locally stored tracking branch. */ public class TrackingRefUpdate { private final String remoteName; + private final String localName; + private boolean forceUpdate; + private ObjectId oldObjectId; + private ObjectId newObjectId; - private final RefUpdate update; - - TrackingRefUpdate(final Repository db, final RefSpec spec, - final AnyObjectId nv, final String msg) throws IOException { - this(db, spec.getDestination(), spec.getSource(), spec.isForceUpdate(), - nv, msg); - } + private RefUpdate.Result result; - TrackingRefUpdate(final Repository db, final String localName, - final String remoteName, final boolean forceUpdate, - final AnyObjectId nv, final String msg) throws IOException { + TrackingRefUpdate( + boolean canForceUpdate, + String remoteName, + String localName, + AnyObjectId oldValue, + AnyObjectId newValue) { this.remoteName = remoteName; - update = db.updateRef(localName); - update.setForceUpdate(forceUpdate); - update.setNewObjectId(nv); - update.setRefLogMessage(msg, true); + this.localName = localName; + this.forceUpdate = canForceUpdate; + this.oldObjectId = oldValue.copy(); + this.newObjectId = newValue.copy(); } /** @@ -95,7 +91,7 @@ public class TrackingRefUpdate { * @return the name used within this local repository. */ public String getLocalName() { - return update.getName(); + return localName; } /** @@ -104,7 +100,7 @@ public class TrackingRefUpdate { * @return new value. Null if the caller has not configured it. */ public ObjectId getNewObjectId() { - return update.getNewObjectId(); + return newObjectId; } /** @@ -115,11 +111,10 @@ public class TrackingRefUpdate { * value may change if someone else modified the ref between the time we * last read it and when the ref was locked for update. * - * @return the value of the ref prior to the update being attempted; null if - * the updated has not been attempted yet. + * @return the value of the ref prior to the update being attempted. */ public ObjectId getOldObjectId() { - return update.getOldObjectId(); + return oldObjectId; } /** @@ -127,15 +122,75 @@ public class TrackingRefUpdate { * * @return the status of the update. */ - public Result getResult() { - return update.getResult(); + public RefUpdate.Result getResult() { + return result; } - void update(final RevWalk walk) throws IOException { - update.update(walk); + void setResult(RefUpdate.Result result) { + this.result = result; } - void delete(final RevWalk walk) throws IOException { - update.delete(walk); + ReceiveCommand asReceiveCommand() { + return new Command(); + } + + final class Command extends ReceiveCommand { + private Command() { + super(oldObjectId, newObjectId, localName); + } + + boolean canForceUpdate() { + return forceUpdate; + } + + @Override + public void setResult(RefUpdate.Result status) { + result = status; + super.setResult(status); + } + + @Override + public void setResult(ReceiveCommand.Result status) { + result = decode(status); + super.setResult(status); + } + + @Override + public void setResult(ReceiveCommand.Result status, String msg) { + result = decode(status); + super.setResult(status, msg); + } + + private RefUpdate.Result decode(ReceiveCommand.Result status) { + switch (status) { + case OK: + if (AnyObjectId.equals(oldObjectId, newObjectId)) + return RefUpdate.Result.NO_CHANGE; + switch (getType()) { + case CREATE: + return RefUpdate.Result.NEW; + case UPDATE: + return RefUpdate.Result.FAST_FORWARD; + case DELETE: + case UPDATE_NONFASTFORWARD: + default: + return RefUpdate.Result.FORCED; + } + + case REJECTED_NOCREATE: + case REJECTED_NODELETE: + case REJECTED_NONFASTFORWARD: + return RefUpdate.Result.REJECTED; + case REJECTED_CURRENT_BRANCH: + return RefUpdate.Result.REJECTED_CURRENT_BRANCH; + case REJECTED_MISSING_OBJECT: + return RefUpdate.Result.IO_FAILURE; + case LOCK_FAILURE: + case NOT_ATTEMPTED: + case REJECTED_OTHER_REASON: + default: + return RefUpdate.Result.LOCK_FAILURE; + } + } } }