Parcourir la source

Fix fetching with duplicate ref updates

If packed refs are used, duplicate updates result in an exception
because JGit tries to lock the same lock file twice. With non-atomic
ref updates, this used to work, since the same ref would simply be
locked and updated twice in succession.

Let's be more lenient in this case and remove duplicates before
trying to do the ref updates. Silently skip duplicate updates
for the same ref, if they both would update the ref to the same
object ID. (If they don't, behavior is undefined anyway, and we
still throw an exception.)

Add a test that results in a duplicate ref update for a tag.

Bug: 529400
Change-Id: Ide97f20b219646ac24c22e28de0c194a29cb62a5
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
tags/v5.1.0.201808281540-m3
Thomas Wolf il y a 5 ans
Parent
révision
ffd1ac5dde

+ 29
- 0
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java Voir le fichier

import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;


import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;


import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
.setRemoveDeletedRefs(true).call(); .setRemoveDeletedRefs(true).call();
assertNull(db.resolve(remoteBranchName)); assertNull(db.resolve(remoteBranchName));
} }

@Test
public void fetchUpdateRefsWithDuplicateRefspec() throws Exception {
final String tagName = "foo";
remoteGit.commit().setMessage("commit").call();
Ref tagRef1 = remoteGit.tag().setName(tagName).call();
List<RefSpec> refSpecs = new ArrayList<>();
refSpecs.add(new RefSpec("+refs/heads/*:refs/remotes/origin/*"));
refSpecs.add(new RefSpec("+refs/tags/*:refs/tags/*"));
// Updating tags via the RefSpecs and setting TagOpt.FETCH_TAGS (or
// AUTO_FOLLOW) will result internally in *two* updates for the same
// ref.
git.fetch().setRemote("test").setRefSpecs(refSpecs)
.setTagOpt(TagOpt.AUTO_FOLLOW).call();
assertEquals(tagRef1.getObjectId(), db.resolve(tagName));

remoteGit.commit().setMessage("commit 2").call();
Ref tagRef2 = remoteGit.tag().setName(tagName).setForceUpdate(true)
.call();
FetchResult result = git.fetch().setRemote("test").setRefSpecs(refSpecs)
.setTagOpt(TagOpt.FETCH_TAGS).call();
assertEquals(2, result.getTrackingRefUpdates().size());
TrackingRefUpdate update = result
.getTrackingRefUpdate(Constants.R_TAGS + tagName);
assertEquals(RefUpdate.Result.FORCED, update.getResult());
assertEquals(tagRef2.getObjectId(), db.resolve(tagName));
}
} }

+ 23
- 5
org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java Voir le fichier

((BatchingProgressMonitor) monitor).setDelayStart( ((BatchingProgressMonitor) monitor).setDelayStart(
250, TimeUnit.MILLISECONDS); 250, TimeUnit.MILLISECONDS);
} }
if (transport.isRemoveDeletedRefs())
if (transport.isRemoveDeletedRefs()) {
deleteStaleTrackingRefs(result, batch); deleteStaleTrackingRefs(result, batch);
for (TrackingRefUpdate u : localUpdates) {
result.add(u);
batch.addCommand(u.asReceiveCommand());
} }
addUpdateBatchCommands(result, batch);
for (ReceiveCommand cmd : batch.getCommands()) { for (ReceiveCommand cmd : batch.getCommands()) {
cmd.updateType(walk); cmd.updateType(walk);
if (cmd.getType() == UPDATE_NONFASTFORWARD if (cmd.getType() == UPDATE_NONFASTFORWARD
if (cmd.getResult() == NOT_ATTEMPTED) if (cmd.getResult() == NOT_ATTEMPTED)
cmd.setResult(OK); cmd.setResult(OK);
} }
} else
} else {
batch.execute(walk, monitor); batch.execute(walk, monitor);
}
} catch (TransportException e) {
throw e;
} catch (IOException err) { } catch (IOException err) {
throw new TransportException(MessageFormat.format( throw new TransportException(MessageFormat.format(
JGitText.get().failureUpdatingTrackingRef, JGitText.get().failureUpdatingTrackingRef,
} }
} }


private void addUpdateBatchCommands(FetchResult result,
BatchRefUpdate batch) throws TransportException {
Map<String, ObjectId> refs = new HashMap<>();
for (TrackingRefUpdate u : localUpdates) {
// Try to skip duplicates if they'd update to the same object ID
ObjectId existing = refs.get(u.getLocalName());
if (existing == null) {
refs.put(u.getLocalName(), u.getNewObjectId());
result.add(u);
batch.addCommand(u.asReceiveCommand());
} else if (!existing.equals(u.getNewObjectId())) {
throw new TransportException(MessageFormat
.format(JGitText.get().duplicateRef, u.getLocalName()));
}
}
}

private void fetchObjects(final ProgressMonitor monitor) private void fetchObjects(final ProgressMonitor monitor)
throws TransportException { throws TransportException {
try { try {

Chargement…
Annuler
Enregistrer