aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn O. Pearce <spearce@spearce.org>2012-06-05 19:31:57 -0400
committerGerrit Code Review @ Eclipse.org <gerrit@eclipse.org>2012-06-05 19:31:57 -0400
commit73cdb08af6e402392bd9bc1af270b220ccc64cb8 (patch)
tree4d880670052dbca06c4703df0a0b1ce12c2c6753
parente83d096ec6fea97b9bee1155654238073f6d1571 (diff)
parent3da13473f7a8baaac6ef1cf965062249c1de141f (diff)
downloadjgit-73cdb08af6e402392bd9bc1af270b220ccc64cb8.tar.gz
jgit-73cdb08af6e402392bd9bc1af270b220ccc64cb8.zip
Merge changes Iee9af8d5,I8e1674f0,If5a6fcc5,I3bb28e4d
* changes: Use BatchRefUpdate for tracking refs in FetchProcess Batch reference updates together for storage Expose ReceiveCommand.updateType to check for UPDATE_NONFASTFORWARD Reject non-fast-forwards earlier in BaseReceivePack
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java317
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java41
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java149
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java25
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java115
8 files changed, 606 insertions, 112 deletions
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 07ef39dc79..9899d14d8d 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 0000000000..e198e07e4d
--- /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 <spearce@spearce.org>
+ * 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.
+ * <p>
+ * 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<ReceiveCommand> 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<ReceiveCommand>();
+ }
+
+ /**
+ * @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.
+ * <p>
+ * 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<ReceiveCommand> 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<ReceiveCommand> cmd) {
+ commands.addAll(cmd);
+ return this;
+ }
+
+ /**
+ * Execute this batch update.
+ * <p>
+ * 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 33c3623058..ed2af4a00c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -147,6 +147,17 @@ public abstract class RefDatabase {
throws IOException;
/**
+ * Create a new batch update to attempt on this database.
+ * <p>
+ * 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.
* <p>
* Aside from taking advantage of {@link #SEARCH_PATH}, this method may be
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 c0d44b1db9..e375221eeb 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<ReceiveCommand> toApply = ReceiveCommand.filter(commands,
- Result.NOT_ATTEMPTED);
+ List<ReceiveCommand> 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 27abf789dd..66d0c62dd9 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<String, Ref> localRefs;
+
FetchProcess(final Transport t, final Collection<RefSpec> 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<Ref> expandAutoFollowTags() throws TransportException {
final Collection<Ref> additionalTags = new ArrayList<Ref>();
- final Map<String, Ref> haveRefs = transport.local.getAllRefs();
+ final Map<String, Ref> 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<String, Ref> haveRefs = transport.local.getAllRefs();
+ final Map<String, Ref> 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<String, Ref> 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 96814c85a8..4c7ffeccee 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}.
*
@@ -265,6 +271,36 @@ public class ReceiveCommand {
}
/**
+ * Update the type of this command by checking for fast-forward.
+ * <p>
+ * 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.
* <p>
* Sets the status of the command as a side effect.
@@ -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 3aaf7bb4ef..421830202e 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 03ecd81e0c..3344c3f6a5 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;
+ }
+ }
}
}