Merge branch 'stable-4.11' into stable-5.0
* stable-4.11: Prepare 4.11.10-SNAPSHOT builds JGit v4.11.9.201909030838-r Bazel: Update bazlets to the latest master revision Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file BatchRefUpdate: repro racy atomic update, and fix it Delete unused FileTreeIteratorWithTimeControl Fix RacyGitTests#testRacyGitDetection Change RacyGitTests to create a racy git situation in a stable way Silence API warnings Change-Id: Ifb6a4dbea2f48fd2ffa66eb737d61920aefedfbd Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
commit
c31879ac4e
|
@ -2,7 +2,7 @@ workspace(name = "jgit")
|
|||
|
||||
load("//tools:bazlets.bzl", "load_bazlets")
|
||||
|
||||
load_bazlets(commit = "3afbeab55ece585dbfc7a980bf7214b24ddbbe86")
|
||||
load_bazlets(commit = "09a035e98077dce549d5f6a7472d06c4b8f792d2")
|
||||
|
||||
load(
|
||||
"@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl",
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<component id="org.eclipse.jgit.http.apache" version="2">
|
||||
<resource path="META-INF/MANIFEST.MF">
|
||||
<filter id="925892614">
|
||||
<message_arguments>
|
||||
<message_argument value="5.0.4"/>
|
||||
<message_argument value="4.11.0"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
</component>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<component id="org.eclipse.jgit.pgm" version="2">
|
||||
<resource path="META-INF/MANIFEST.MF">
|
||||
<filter id="925892614">
|
||||
<message_arguments>
|
||||
<message_argument value="5.0.4"/>
|
||||
<message_argument value="4.11.0"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
</component>
|
|
@ -19,7 +19,6 @@ HELPERS = glob(["src/**/*.java"]) + [PKG + c for c in [
|
|||
"revwalk/RevQueueTestCase.java",
|
||||
"revwalk/RevWalkTestCase.java",
|
||||
"transport/SpiTransport.java",
|
||||
"treewalk/FileTreeIteratorWithTimeControl.java",
|
||||
"treewalk/filter/AlwaysCloneTreeFilter.java",
|
||||
"test/resources/SampleDataRepositoryTestCase.java",
|
||||
"util/CPUTimeStopWatch.java",
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
package org.eclipse.jgit.internal.storage.file;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.eclipse.jgit.internal.storage.file.BatchRefUpdateTest.Result.LOCK_FAILURE;
|
||||
|
@ -64,6 +65,7 @@
|
|||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -161,6 +163,33 @@ public void removeListener() {
|
|||
refsChangedEvents = 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void packedRefsFileIsSorted() throws IOException {
|
||||
assumeTrue(atomic);
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
BatchRefUpdate bu = diskRepo.getRefDatabase().newBatchUpdate();
|
||||
String b1 = String.format("refs/heads/a%d",i);
|
||||
String b2 = String.format("refs/heads/b%d",i);
|
||||
bu.setAtomic(atomic);
|
||||
ReceiveCommand c1 = new ReceiveCommand(ObjectId.zeroId(), A, b1);
|
||||
ReceiveCommand c2 = new ReceiveCommand(ObjectId.zeroId(), B, b2);
|
||||
bu.addCommand(c1, c2);
|
||||
try (RevWalk rw = new RevWalk(diskRepo)) {
|
||||
bu.execute(rw, NullProgressMonitor.INSTANCE);
|
||||
}
|
||||
assertEquals(c1.getResult(), ReceiveCommand.Result.OK);
|
||||
assertEquals(c2.getResult(), ReceiveCommand.Result.OK);
|
||||
}
|
||||
|
||||
File packed = new File(diskRepo.getDirectory(), "packed-refs");
|
||||
String packedStr = new String(Files.readAllBytes(packed.toPath()), UTF_8);
|
||||
|
||||
int a2 = packedStr.indexOf("refs/heads/a1");
|
||||
int b1 = packedStr.indexOf("refs/heads/b0");
|
||||
assertTrue(a2 < b1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleNoForce() throws IOException {
|
||||
writeLooseRef("refs/heads/master", A);
|
||||
|
|
|
@ -42,93 +42,25 @@
|
|||
*/
|
||||
package org.eclipse.jgit.lib;
|
||||
|
||||
import static java.lang.Long.valueOf;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.dircache.DirCache;
|
||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||
import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl;
|
||||
import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
|
||||
import org.eclipse.jgit.util.FileUtils;
|
||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RacyGitTests extends RepositoryTestCase {
|
||||
@Test
|
||||
public void testIterator() throws IllegalStateException, IOException,
|
||||
InterruptedException {
|
||||
TreeSet<Long> modTimes = new TreeSet<>();
|
||||
File lastFile = null;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
lastFile = new File(db.getWorkTree(), "0." + i);
|
||||
FileUtils.createNewFile(lastFile);
|
||||
if (i == 5)
|
||||
fsTick(lastFile);
|
||||
}
|
||||
modTimes.add(valueOf(fsTick(lastFile)));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
lastFile = new File(db.getWorkTree(), "1." + i);
|
||||
FileUtils.createNewFile(lastFile);
|
||||
}
|
||||
modTimes.add(valueOf(fsTick(lastFile)));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
lastFile = new File(db.getWorkTree(), "2." + i);
|
||||
FileUtils.createNewFile(lastFile);
|
||||
if (i % 4 == 0)
|
||||
fsTick(lastFile);
|
||||
}
|
||||
FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl(
|
||||
db, modTimes);
|
||||
try (NameConflictTreeWalk tw = new NameConflictTreeWalk(db)) {
|
||||
tw.addTree(fileIt);
|
||||
tw.setRecursive(true);
|
||||
FileTreeIterator t;
|
||||
long t0 = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertTrue(tw.next());
|
||||
t = tw.getTree(0, FileTreeIterator.class);
|
||||
if (i == 0) {
|
||||
t0 = t.getEntryLastModified();
|
||||
} else {
|
||||
assertEquals(t0, t.getEntryLastModified());
|
||||
}
|
||||
}
|
||||
long t1 = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertTrue(tw.next());
|
||||
t = tw.getTree(0, FileTreeIterator.class);
|
||||
if (i == 0) {
|
||||
t1 = t.getEntryLastModified();
|
||||
assertTrue(t1 > t0);
|
||||
} else {
|
||||
assertEquals(t1, t.getEntryLastModified());
|
||||
}
|
||||
}
|
||||
long t2 = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertTrue(tw.next());
|
||||
t = tw.getTree(0, FileTreeIterator.class);
|
||||
if (i == 0) {
|
||||
t2 = t.getEntryLastModified();
|
||||
assertTrue(t2 > t1);
|
||||
} else {
|
||||
assertEquals(t2, t.getEntryLastModified());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRacyGitDetection() throws Exception {
|
||||
TreeSet<Long> modTimes = new TreeSet<>();
|
||||
File lastFile;
|
||||
|
||||
// Reset to force creation of index file
|
||||
try (Git git = new Git(db)) {
|
||||
git.reset().call();
|
||||
|
@ -136,45 +68,59 @@ public void testRacyGitDetection() throws Exception {
|
|||
|
||||
// wait to ensure that modtimes of the file doesn't match last index
|
||||
// file modtime
|
||||
modTimes.add(valueOf(fsTick(db.getIndexFile())));
|
||||
fsTick(db.getIndexFile());
|
||||
|
||||
// create two files
|
||||
addToWorkDir("a", "a");
|
||||
lastFile = addToWorkDir("b", "b");
|
||||
File a = writeToWorkDir("a", "a");
|
||||
File b = writeToWorkDir("b", "b");
|
||||
assertTrue(a.setLastModified(b.lastModified()));
|
||||
assertTrue(b.setLastModified(b.lastModified()));
|
||||
|
||||
// wait to ensure that file-modTimes and therefore index entry modTime
|
||||
// doesn't match the modtime of index-file after next persistance
|
||||
modTimes.add(valueOf(fsTick(lastFile)));
|
||||
fsTick(b);
|
||||
|
||||
// now add both files to the index. No racy git expected
|
||||
resetIndex(new FileTreeIteratorWithTimeControl(db, modTimes));
|
||||
resetIndex(new FileTreeIterator(db));
|
||||
|
||||
assertEquals(
|
||||
"[a, mode:100644, time:t0, length:1, content:a]" +
|
||||
"[b, mode:100644, time:t0, length:1, content:b]",
|
||||
"[a, mode:100644, time:t0, length:1, content:a]"
|
||||
+ "[b, mode:100644, time:t0, length:1, content:b]",
|
||||
indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT));
|
||||
|
||||
// Remember the last modTime of index file. All modifications times of
|
||||
// further modification are translated to this value so it looks that
|
||||
// files have been modified in the same time slot as the index file
|
||||
modTimes.add(Long.valueOf(db.getIndexFile().lastModified()));
|
||||
// wait to ensure the file 'a' is updated at t1.
|
||||
fsTick(db.getIndexFile());
|
||||
|
||||
// modify one file
|
||||
addToWorkDir("a", "a2");
|
||||
// now update the index the index. 'a' has to be racily clean -- because
|
||||
// it's modification time is exactly the same as the previous index file
|
||||
// mod time.
|
||||
resetIndex(new FileTreeIteratorWithTimeControl(db, modTimes));
|
||||
// Create a racy git situation. This is a situation that the index is
|
||||
// updated and then a file is modified within the same tick of the
|
||||
// filesystem timestamp resolution. By changing the index file
|
||||
// artificially, we create a fake racy situation.
|
||||
File updatedA = writeToWorkDir("a", "a2");
|
||||
long newLastModified = updatedA.lastModified() + 100;
|
||||
assertTrue(updatedA.setLastModified(newLastModified));
|
||||
resetIndex(new FileTreeIterator(db));
|
||||
assertTrue(db.getIndexFile().setLastModified(newLastModified));
|
||||
|
||||
db.readDirCache();
|
||||
// although racily clean a should not be reported as being dirty
|
||||
DirCache dc = db.readDirCache();
|
||||
// check index state: although racily clean a should not be reported as
|
||||
// being dirty since we forcefully reset the index to match the working
|
||||
// tree
|
||||
assertEquals(
|
||||
"[a, mode:100644, time:t1, smudged, length:0, content:a2]" +
|
||||
"[b, mode:100644, time:t0, length:1, content:b]",
|
||||
indexState(SMUDGE|MOD_TIME|LENGTH|CONTENT));
|
||||
"[a, mode:100644, time:t1, smudged, length:0, content:a2]"
|
||||
+ "[b, mode:100644, time:t0, length:1, content:b]",
|
||||
indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT));
|
||||
|
||||
// compare state of files in working tree with index to check that
|
||||
// FileTreeIterator.isModified() works as expected
|
||||
FileTreeIterator f = new FileTreeIterator(db.getWorkTree(), db.getFS(),
|
||||
db.getConfig().get(WorkingTreeOptions.KEY));
|
||||
assertTrue(f.findFile("a"));
|
||||
try (ObjectReader reader = db.newObjectReader()) {
|
||||
assertFalse(f.isModified(dc.getEntry("a"), false, reader));
|
||||
}
|
||||
}
|
||||
|
||||
private File addToWorkDir(String path, String content) throws IOException {
|
||||
private File writeToWorkDir(String path, String content) throws IOException {
|
||||
File f = new File(db.getWorkTree(), path);
|
||||
try (FileOutputStream fos = new FileOutputStream(f)) {
|
||||
fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
|
||||
|
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
|
||||
* 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.treewalk;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ObjectReader;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
|
||||
/**
|
||||
* A {@link FileTreeIterator} used in tests which allows to specify explicitly
|
||||
* what will be returned by {@link #getEntryLastModified()}. This allows to
|
||||
* write tests where certain files have to have the same modification time.
|
||||
* <p>
|
||||
* This iterator is configured by a list of strictly increasing long values
|
||||
* t(0), t(1), ..., t(n). For each file with a modification between t(x) and
|
||||
* t(x+1) [ t(x) <= time < t(x+1) ] this iterator will report t(x). For
|
||||
* files with a modification time smaller t(0) a modification time of 0 is
|
||||
* returned. For files with a modification time greater or equal t(n) t(n) will
|
||||
* be returned.
|
||||
* <p>
|
||||
* This class was written especially to test racy-git problems
|
||||
*/
|
||||
public class FileTreeIteratorWithTimeControl extends FileTreeIterator {
|
||||
private TreeSet<Long> modTimes;
|
||||
|
||||
public FileTreeIteratorWithTimeControl(FileTreeIterator p, Repository repo,
|
||||
TreeSet<Long> modTimes) {
|
||||
super(p, repo.getWorkTree(), repo.getFS());
|
||||
this.modTimes = modTimes;
|
||||
}
|
||||
|
||||
public FileTreeIteratorWithTimeControl(FileTreeIterator p, File f, FS fs,
|
||||
TreeSet<Long> modTimes) {
|
||||
super(p, f, fs);
|
||||
this.modTimes = modTimes;
|
||||
}
|
||||
|
||||
public FileTreeIteratorWithTimeControl(Repository repo,
|
||||
TreeSet<Long> modTimes) {
|
||||
super(repo);
|
||||
this.modTimes = modTimes;
|
||||
}
|
||||
|
||||
public FileTreeIteratorWithTimeControl(File f, FS fs,
|
||||
TreeSet<Long> modTimes) {
|
||||
super(f, fs, new Config().get(WorkingTreeOptions.KEY));
|
||||
this.modTimes = modTimes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractTreeIterator createSubtreeIterator(ObjectReader reader) {
|
||||
return new FileTreeIteratorWithTimeControl(this,
|
||||
((FileEntry) current()).getFile(), fs, modTimes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntryLastModified() {
|
||||
if (modTimes == null)
|
||||
return 0;
|
||||
Long cutOff = Long.valueOf(super.getEntryLastModified() + 1);
|
||||
SortedSet<Long> head = modTimes.headSet(cutOff);
|
||||
return head.isEmpty() ? 0 : head.last().longValue();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<component id="org.eclipse.jgit.ui" version="2">
|
||||
<resource path="META-INF/MANIFEST.MF">
|
||||
<filter id="925892614">
|
||||
<message_arguments>
|
||||
<message_argument value="5.0.4"/>
|
||||
<message_argument value="4.11.0"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
</component>
|
|
@ -1,13 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<component id="org.eclipse.jgit" version="2">
|
||||
<resource path="META-INF/MANIFEST.MF">
|
||||
<filter id="924844039">
|
||||
<message_arguments>
|
||||
<message_argument value="5.0.4"/>
|
||||
<message_argument value="5.0.0"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/errors/PackInvalidException.java" type="org.eclipse.jgit.errors.PackInvalidException">
|
||||
<filter id="1142947843">
|
||||
<message_arguments>
|
||||
|
@ -22,6 +14,15 @@
|
|||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/lib/Constants.java" type="org.eclipse.jgit.lib.Constants">
|
||||
<filter id="1141899266">
|
||||
<message_arguments>
|
||||
<message_argument value="4.7"/>
|
||||
<message_argument value="5.0"/>
|
||||
<message_argument value="LOCK_SUFFIX"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/lib/GitmoduleEntry.java" type="org.eclipse.jgit.lib.GitmoduleEntry">
|
||||
<filter id="1109393411">
|
||||
<message_arguments>
|
||||
|
@ -38,6 +39,22 @@
|
|||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/lib/ObjectIdSerializer.java" type="org.eclipse.jgit.lib.ObjectIdSerializer">
|
||||
<filter id="1141899266">
|
||||
<message_arguments>
|
||||
<message_argument value="4.11"/>
|
||||
<message_argument value="5.0"/>
|
||||
<message_argument value="readWithoutMarker(InputStream)"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
<filter id="1141899266">
|
||||
<message_arguments>
|
||||
<message_argument value="4.11"/>
|
||||
<message_argument value="5.0"/>
|
||||
<message_argument value="writeWithoutMarker(OutputStream, AnyObjectId)"/>
|
||||
</message_arguments>
|
||||
</filter>
|
||||
</resource>
|
||||
<resource path="src/org/eclipse/jgit/util/FS.java" type="org.eclipse.jgit.util.FS">
|
||||
<filter id="1141899266">
|
||||
<message_arguments>
|
||||
|
|
|
@ -51,10 +51,10 @@
|
|||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -364,65 +364,72 @@ private Map<String, LockFile> lockLooseRefs(List<ReceiveCommand> commands)
|
|||
|
||||
private static RefList<Ref> applyUpdates(RevWalk walk, RefList<Ref> refs,
|
||||
List<ReceiveCommand> commands) throws IOException {
|
||||
int nDeletes = 0;
|
||||
List<ReceiveCommand> adds = new ArrayList<>(commands.size());
|
||||
// Construct a new RefList by merging the old list with the updates.
|
||||
// This assumes that each ref occurs at most once as a ReceiveCommand.
|
||||
Collections.sort(commands, new Comparator<ReceiveCommand>() {
|
||||
@Override
|
||||
public int compare(ReceiveCommand a, ReceiveCommand b) {
|
||||
return a.getRefName().compareTo(b.getRefName());
|
||||
}
|
||||
});
|
||||
|
||||
int delta = 0;
|
||||
for (ReceiveCommand c : commands) {
|
||||
if (c.getType() == ReceiveCommand.Type.CREATE) {
|
||||
adds.add(c);
|
||||
} else if (c.getType() == ReceiveCommand.Type.DELETE) {
|
||||
nDeletes++;
|
||||
switch (c.getType()) {
|
||||
case DELETE:
|
||||
delta--;
|
||||
break;
|
||||
case CREATE:
|
||||
delta++;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
int addIdx = 0;
|
||||
|
||||
// Construct a new RefList by linearly scanning the old list, and merging in
|
||||
// any updates.
|
||||
Map<String, ReceiveCommand> byName = byName(commands);
|
||||
RefList.Builder<Ref> b =
|
||||
new RefList.Builder<>(refs.size() - nDeletes + adds.size());
|
||||
for (Ref ref : refs) {
|
||||
String name = ref.getName();
|
||||
ReceiveCommand cmd = byName.remove(name);
|
||||
if (cmd == null) {
|
||||
RefList.Builder<Ref> b = new RefList.Builder<>(refs.size() + delta);
|
||||
int refIdx = 0;
|
||||
int cmdIdx = 0;
|
||||
while (refIdx < refs.size() || cmdIdx < commands.size()) {
|
||||
Ref ref = (refIdx < refs.size()) ? refs.get(refIdx) : null;
|
||||
ReceiveCommand cmd = (cmdIdx < commands.size())
|
||||
? commands.get(cmdIdx)
|
||||
: null;
|
||||
int cmp = 0;
|
||||
if (ref != null && cmd != null) {
|
||||
cmp = ref.getName().compareTo(cmd.getRefName());
|
||||
} else if (ref == null) {
|
||||
cmp = 1;
|
||||
} else if (cmd == null) {
|
||||
cmp = -1;
|
||||
}
|
||||
|
||||
if (cmp < 0) {
|
||||
b.add(ref);
|
||||
continue;
|
||||
}
|
||||
if (!cmd.getOldId().equals(ref.getObjectId())) {
|
||||
lockFailure(cmd, commands);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Consume any adds between the last and current ref.
|
||||
while (addIdx < adds.size()) {
|
||||
ReceiveCommand currAdd = adds.get(addIdx);
|
||||
if (currAdd.getRefName().compareTo(name) < 0) {
|
||||
b.add(peeledRef(walk, currAdd));
|
||||
byName.remove(currAdd.getRefName());
|
||||
} else {
|
||||
break;
|
||||
refIdx++;
|
||||
} else if (cmp > 0) {
|
||||
assert cmd != null;
|
||||
if (cmd.getType() != ReceiveCommand.Type.CREATE) {
|
||||
lockFailure(cmd, commands);
|
||||
return null;
|
||||
}
|
||||
addIdx++;
|
||||
}
|
||||
|
||||
if (cmd.getType() != ReceiveCommand.Type.DELETE) {
|
||||
b.add(peeledRef(walk, cmd));
|
||||
cmdIdx++;
|
||||
} else {
|
||||
assert cmd != null;
|
||||
assert ref != null;
|
||||
if (!cmd.getOldId().equals(ref.getObjectId())) {
|
||||
lockFailure(cmd, commands);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cmd.getType() != ReceiveCommand.Type.DELETE) {
|
||||
b.add(peeledRef(walk, cmd));
|
||||
}
|
||||
cmdIdx++;
|
||||
refIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
// All remaining adds are valid, since the refs didn't exist.
|
||||
while (addIdx < adds.size()) {
|
||||
ReceiveCommand cmd = adds.get(addIdx++);
|
||||
byName.remove(cmd.getRefName());
|
||||
b.add(peeledRef(walk, cmd));
|
||||
}
|
||||
|
||||
// Any remaining updates/deletes do not correspond to any existing refs, so
|
||||
// they are lock failures.
|
||||
if (!byName.isEmpty()) {
|
||||
lockFailure(byName.values().iterator().next(), commands);
|
||||
return null;
|
||||
}
|
||||
|
||||
return b.toRefList();
|
||||
}
|
||||
|
||||
|
@ -501,15 +508,6 @@ private String toResultString(ReceiveCommand cmd) {
|
|||
}
|
||||
}
|
||||
|
||||
private static Map<String, ReceiveCommand> byName(
|
||||
List<ReceiveCommand> commands) {
|
||||
Map<String, ReceiveCommand> ret = new LinkedHashMap<>();
|
||||
for (ReceiveCommand cmd : commands) {
|
||||
ret.put(cmd.getRefName(), cmd);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Ref peeledRef(RevWalk walk, ReceiveCommand cmd)
|
||||
throws IOException {
|
||||
ObjectId newId = cmd.getNewId().copy();
|
||||
|
|
Loading…
Reference in New Issue