Browse Source

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
stable-2.1
Shawn O. Pearce 12 years ago committed by Gerrit Code Review @ Eclipse.org
parent
commit
73cdb08af6
  1. 3
      org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java
  2. 317
      org.eclipse.jgit/src/org/eclipse/jgit/lib/BatchRefUpdate.java
  3. 11
      org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
  4. 39
      org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
  5. 135
      org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
  6. 57
      org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java
  7. 25
      org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java
  8. 115
      org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java

3
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java

@ -56,6 +56,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.SampleDataRepositoryTestCase;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -209,7 +210,7 @@ public class TransportTest extends SampleDataRepositoryTestCase {
assertEquals("refs/remotes/test/a", tru.getLocalName()); assertEquals("refs/remotes/test/a", tru.getLocalName());
assertEquals("refs/heads/a", tru.getRemoteName()); assertEquals("refs/heads/a", tru.getRemoteName());
assertEquals(db.resolve("refs/heads/a"), tru.getNewObjectId()); assertEquals(db.resolve("refs/heads/a"), tru.getNewObjectId());
assertNull(tru.getOldObjectId()); assertEquals(ObjectId.zeroId(), tru.getOldObjectId());
} }
@Test @Test

317
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;
}
}
}

11
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) public abstract RefRename newRename(String fromName, String toName)
throws IOException; 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. * Read a single reference.
* <p> * <p>

39
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.MissingObjectException;
import org.eclipse.jgit.errors.PackProtocolException; import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.SectionParser; import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -1086,11 +1087,11 @@ public abstract class BaseReceivePack {
if (oldObj instanceof RevCommit && newObj instanceof RevCommit) { if (oldObj instanceof RevCommit && newObj instanceof RevCommit) {
try { try {
if (!walk.isMergedInto((RevCommit) oldObj, if (walk.isMergedInto((RevCommit) oldObj,
(RevCommit) newObj)) { (RevCommit) newObj))
cmd cmd.setTypeFastForwardUpdate();
.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); else
} cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD);
} catch (MissingObjectException e) { } catch (MissingObjectException e) {
cmd.setResult(Result.REJECTED_MISSING_OBJECT, e cmd.setResult(Result.REJECTED_MISSING_OBJECT, e
.getMessage()); .getMessage());
@ -1100,6 +1101,12 @@ public abstract class BaseReceivePack {
} else { } else {
cmd.setType(ReceiveCommand.Type.UPDATE_NONFASTFORWARD); 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) if (!cmd.getRefName().startsWith(Constants.R_REFS)
@ -1123,20 +1130,30 @@ public abstract class BaseReceivePack {
/** Execute commands to update references. */ /** Execute commands to update references. */
protected void executeCommands() { protected void executeCommands() {
List<ReceiveCommand> toApply = ReceiveCommand.filter(commands, List<ReceiveCommand> toApply = filterCommands(Result.NOT_ATTEMPTED);
Result.NOT_ATTEMPTED); if (toApply.isEmpty())
return;
ProgressMonitor updating = NullProgressMonitor.INSTANCE; ProgressMonitor updating = NullProgressMonitor.INSTANCE;
if (sideBand) { if (sideBand) {
SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut); SideBandProgressMonitor pm = new SideBandProgressMonitor(msgOut);
pm.setDelayStart(250, TimeUnit.MILLISECONDS); pm.setDelayStart(250, TimeUnit.MILLISECONDS);
updating = pm; updating = pm;
} }
updating.beginTask(JGitText.get().updatingReferences, toApply.size());
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) { for (ReceiveCommand cmd : toApply) {
updating.update(1); if (cmd.getResult() == Result.NOT_ATTEMPTED)
cmd.execute(this); cmd.reject(err);
}
} }
updating.endTask();
} }
/** /**

135
org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java

@ -44,6 +44,11 @@
package org.eclipse.jgit.transport; 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.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter; 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.NotSupportedException;
import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.BatchingProgressMonitor; import org.eclipse.jgit.lib.BatchingProgressMonitor;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref; 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.ObjectWalk;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.LockFile; import org.eclipse.jgit.storage.file.LockFile;
@ -97,6 +103,8 @@ class FetchProcess {
private FetchConnection conn; private FetchConnection conn;
private Map<String, Ref> localRefs;
FetchProcess(final Transport t, final Collection<RefSpec> f) { FetchProcess(final Transport t, final Collection<RefSpec> f) {
transport = t; transport = t;
toFetch = f; toFetch = f;
@ -108,6 +116,7 @@ class FetchProcess {
localUpdates.clear(); localUpdates.clear();
fetchHeadUpdates.clear(); fetchHeadUpdates.clear();
packLocks.clear(); packLocks.clear();
localRefs = null;
try { try {
executeImp(monitor, result); executeImp(monitor, result);
@ -183,27 +192,40 @@ class FetchProcess {
closeConnection(result); closeConnection(result);
} }
BatchRefUpdate batch = transport.local.getRefDatabase()
.newBatchUpdate()
.setAllowNonFastForwards(true)
.setRefLogMessage("fetch", true);
final RevWalk walk = new RevWalk(transport.local); final RevWalk walk = new RevWalk(transport.local);
try { try {
if (monitor instanceof BatchingProgressMonitor) { if (monitor instanceof BatchingProgressMonitor) {
((BatchingProgressMonitor) monitor).setDelayStart( ((BatchingProgressMonitor) monitor).setDelayStart(
250, TimeUnit.MILLISECONDS); 250, TimeUnit.MILLISECONDS);
} }
monitor.beginTask(JGitText.get().updatingReferences, localUpdates.size());
if (transport.isRemoveDeletedRefs()) if (transport.isRemoveDeletedRefs())
deleteStaleTrackingRefs(result, walk); deleteStaleTrackingRefs(result, batch);
for (TrackingRefUpdate u : localUpdates) { for (TrackingRefUpdate u : localUpdates) {
try {
monitor.update(1);
u.update(walk);
result.add(u); result.add(u);
} catch (IOException err) { batch.addCommand(u.asReceiveCommand());
throw new TransportException(MessageFormat.format(JGitText
.get().failureUpdatingTrackingRef,
u.getLocalName(), err.getMessage()), err);
} }
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);
} }
monitor.endTask(); 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 { } finally {
walk.release(); walk.release();
} }
@ -320,7 +342,7 @@ class FetchProcess {
try { try {
for (final ObjectId want : askFor.keySet()) for (final ObjectId want : askFor.keySet())
ow.markStart(ow.parseAny(want)); 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.markUninteresting(ow.parseAny(ref.getObjectId()));
ow.checkConnectivity(); ow.checkConnectivity();
} finally { } finally {
@ -354,7 +376,7 @@ class FetchProcess {
private Collection<Ref> expandAutoFollowTags() throws TransportException { private Collection<Ref> expandAutoFollowTags() throws TransportException {
final Collection<Ref> additionalTags = new ArrayList<Ref>(); 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()) { for (final Ref r : conn.getRefs()) {
if (!isTag(r)) if (!isTag(r))
continue; continue;
@ -385,7 +407,7 @@ class FetchProcess {
} }
private void expandFetchTags() throws TransportException { 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()) { for (final Ref r : conn.getRefs()) {
if (!isTag(r)) if (!isTag(r))
continue; continue;
@ -404,17 +426,10 @@ class FetchProcess {
throws TransportException { throws TransportException {
final ObjectId newId = src.getObjectId(); final ObjectId newId = src.getObjectId();
if (spec.getDestination() != null) { if (spec.getDestination() != null) {
try {
final TrackingRefUpdate tru = createUpdate(spec, newId); final TrackingRefUpdate tru = createUpdate(spec, newId);
if (newId.equals(tru.getOldObjectId())) if (newId.equals(tru.getOldObjectId()))
return; return;
localUpdates.add(tru); 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);
}
} }
askFor.put(newId, src); askFor.put(newId, src);
@ -427,21 +442,41 @@ class FetchProcess {
fetchHeadUpdates.add(fhr); fetchHeadUpdates.add(fhr);
} }
private TrackingRefUpdate createUpdate(final RefSpec spec, private TrackingRefUpdate createUpdate(RefSpec spec, ObjectId newId)
final ObjectId newId) throws IOException { throws TransportException {
return new TrackingRefUpdate(transport.local, spec, newId, "fetch"); 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, private void deleteStaleTrackingRefs(FetchResult result,
final RevWalk walk) throws TransportException { BatchRefUpdate batch) throws IOException {
final Repository db = transport.local; for (final Ref ref : localRefs().values()) {
for (final Ref ref : db.getAllRefs().values()) {
final String refname = ref.getName(); final String refname = ref.getName();
for (final RefSpec spec : toFetch) { for (final RefSpec spec : toFetch) {
if (spec.matchDestination(refname)) { if (spec.matchDestination(refname)) {
final RefSpec s = spec.expandFromDestination(refname); final RefSpec s = spec.expandFromDestination(refname);
if (result.getAdvertisedRef(s.getSource()) == null) { 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, private void deleteTrackingRef(final FetchResult result,
final Repository db, final RevWalk walk, final RefSpec spec, final BatchRefUpdate batch, final RefSpec spec, final Ref localRef) {
final Ref localRef) throws TransportException { if (localRef.getObjectId() == null)
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; return;
} TrackingRefUpdate update = new TrackingRefUpdate(
u.delete(walk); true,
switch (u.getResult()) { spec.getSource(),
case NEW: localRef.getName(),
case NO_CHANGE: localRef.getObjectId(),
case FAST_FORWARD: ObjectId.zeroId());
case FORCED: result.add(update);
break; batch.addCommand(update.asReceiveCommand());
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);
}
} }
private static boolean isTag(final Ref r) { private static boolean isTag(final Ref r) {
@ -483,4 +504,12 @@ class FetchProcess {
private static boolean isTag(final String name) { private static boolean isTag(final String name) {
return name.startsWith(Constants.R_TAGS); 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 "";
}
} }

57
org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java

@ -49,9 +49,13 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate; 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}. * A command being processed by {@link BaseReceivePack}.
@ -157,6 +161,8 @@ public class ReceiveCommand {
private String message; private String message;
private boolean typeIsCorrect;
/** /**
* Create a new command for {@link BaseReceivePack}. * Create a new command for {@link BaseReceivePack}.
* *
@ -264,6 +270,36 @@ public class ReceiveCommand {
message = m; message = m;
} }
/**
* 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. * Execute this command during a receive-pack session.
* <p> * <p>
@ -301,8 +337,7 @@ public class ReceiveCommand {
break; break;
} }
} catch (IOException err) { } catch (IOException err) {
setResult(Result.REJECTED_OTHER_REASON, MessageFormat.format( reject(err);
JGitText.get().lockError, err.getMessage()));
} }
} }
@ -314,7 +349,18 @@ public class ReceiveCommand {
type = t; 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) { switch (r) {
case NOT_ATTEMPTED: case NOT_ATTEMPTED:
setResult(Result.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 @Override
public String toString() { public String toString() {
return getType().name() + ": " + getOldId().name() + " " return getType().name() + ": " + getOldId().name() + " "

25
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.internal.JGitText;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
@ -144,6 +145,8 @@ public class RemoteRefUpdate {
private final Repository localDb; private final Repository localDb;
private RefUpdate localUpdate;
/** /**
* Construct remote ref update request by providing an update specification. * Construct remote ref update request by providing an update specification.
* Object is created with default {@link Status#NOT_ATTEMPTED} status and no * Object is created with default {@link Status#NOT_ATTEMPTED} status and no
@ -299,10 +302,20 @@ public class RemoteRefUpdate {
this.remoteName = remoteName; this.remoteName = remoteName;
this.forceUpdate = forceUpdate; this.forceUpdate = forceUpdate;
if (localName != null && localDb != null) if (localName != null && localDb != null) {
trackingRefUpdate = new TrackingRefUpdate(localDb, localName, localUpdate = localDb.updateRef(localName);
remoteName, true, newObjectId, "push"); localUpdate.setForceUpdate(true);
else localUpdate.setRefLogMessage("push", true);
localUpdate.setNewObjectId(newObjectId);
trackingRefUpdate = new TrackingRefUpdate(
true,
remoteName,
localName,
localUpdate.getOldObjectId() != null
? localUpdate.getOldObjectId()
: ObjectId.zeroId(),
newObjectId);
} else
trackingRefUpdate = null; trackingRefUpdate = null;
this.localDb = localDb; this.localDb = localDb;
this.expectedOldObjectId = expectedOldObjectId; this.expectedOldObjectId = expectedOldObjectId;
@ -449,9 +462,9 @@ public class RemoteRefUpdate {
*/ */
protected void updateTrackingRef(final RevWalk walk) throws IOException { protected void updateTrackingRef(final RevWalk walk) throws IOException {
if (isDelete()) if (isDelete())
trackingRefUpdate.delete(walk); trackingRefUpdate.setResult(localUpdate.delete(walk));
else else
trackingRefUpdate.update(walk); trackingRefUpdate.setResult(localUpdate.update(walk));
} }
@Override @Override

115
org.eclipse.jgit/src/org/eclipse/jgit/transport/TrackingRefUpdate.java

@ -45,35 +45,31 @@
package org.eclipse.jgit.transport; package org.eclipse.jgit.transport;
import java.io.IOException;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate; 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. */ /** Update of a locally stored tracking branch. */
public class TrackingRefUpdate { public class TrackingRefUpdate {
private final String remoteName; private final String remoteName;
private final String localName;
private boolean forceUpdate;
private ObjectId oldObjectId;
private ObjectId newObjectId;
private final RefUpdate update; private RefUpdate.Result result;
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);
}
TrackingRefUpdate(final Repository db, final String localName, TrackingRefUpdate(
final String remoteName, final boolean forceUpdate, boolean canForceUpdate,
final AnyObjectId nv, final String msg) throws IOException { String remoteName,
String localName,
AnyObjectId oldValue,
AnyObjectId newValue) {
this.remoteName = remoteName; this.remoteName = remoteName;
update = db.updateRef(localName); this.localName = localName;
update.setForceUpdate(forceUpdate); this.forceUpdate = canForceUpdate;
update.setNewObjectId(nv); this.oldObjectId = oldValue.copy();
update.setRefLogMessage(msg, true); this.newObjectId = newValue.copy();
} }
/** /**
@ -95,7 +91,7 @@ public class TrackingRefUpdate {
* @return the name used within this local repository. * @return the name used within this local repository.
*/ */
public String getLocalName() { 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. * @return new value. Null if the caller has not configured it.
*/ */
public ObjectId getNewObjectId() { 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 * value may change if someone else modified the ref between the time we
* last read it and when the ref was locked for update. * 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 * @return the value of the ref prior to the update being attempted.
* the updated has not been attempted yet.
*/ */
public ObjectId getOldObjectId() { public ObjectId getOldObjectId() {
return update.getOldObjectId(); return oldObjectId;
} }
/** /**
@ -127,15 +122,75 @@ public class TrackingRefUpdate {
* *
* @return the status of the update. * @return the status of the update.
*/ */
public Result getResult() { public RefUpdate.Result getResult() {
return update.getResult(); return result;
}
void setResult(RefUpdate.Result result) {
this.result = result;
} }
void update(final RevWalk walk) throws IOException { ReceiveCommand asReceiveCommand() {
update.update(walk); return new Command();
} }
void delete(final RevWalk walk) throws IOException { final class Command extends ReceiveCommand {
update.delete(walk); 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;
}
}
} }
} }

Loading…
Cancel
Save