|
|
|
@ -42,12 +42,18 @@
|
|
|
|
|
*/ |
|
|
|
|
package org.eclipse.jgit.merge; |
|
|
|
|
|
|
|
|
|
import static org.junit.Assert.assertFalse; |
|
|
|
|
import static org.junit.Assert.*; |
|
|
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
|
import java.io.FileInputStream; |
|
|
|
|
import java.io.IOException; |
|
|
|
|
|
|
|
|
|
import org.eclipse.jgit.api.Git; |
|
|
|
|
import org.eclipse.jgit.api.MergeResult; |
|
|
|
|
import org.eclipse.jgit.api.MergeResult.MergeStatus; |
|
|
|
|
import org.eclipse.jgit.dircache.DirCache; |
|
|
|
|
import org.eclipse.jgit.lib.RepositoryTestCase; |
|
|
|
|
import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; |
|
|
|
|
import org.eclipse.jgit.revwalk.RevCommit; |
|
|
|
|
import org.eclipse.jgit.treewalk.FileTreeIterator; |
|
|
|
|
import org.eclipse.jgit.util.FileUtils; |
|
|
|
@ -95,4 +101,174 @@ public class ResolveMergerTest extends RepositoryTestCase {
|
|
|
|
|
assertFalse(ok); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void checkLockedFilesToBeDeleted() throws Exception { |
|
|
|
|
Git git = Git.wrap(db); |
|
|
|
|
|
|
|
|
|
writeTrashFile("a.txt", "orig"); |
|
|
|
|
writeTrashFile("b.txt", "orig"); |
|
|
|
|
git.add().addFilepattern("a.txt").addFilepattern("b.txt").call(); |
|
|
|
|
RevCommit first = git.commit().setMessage("added a.txt, b.txt").call(); |
|
|
|
|
|
|
|
|
|
// modify and delete files on the master branch
|
|
|
|
|
writeTrashFile("a.txt", "master"); |
|
|
|
|
git.rm().addFilepattern("b.txt").call(); |
|
|
|
|
RevCommit masterCommit = git.commit() |
|
|
|
|
.setMessage("modified a.txt, deleted b.txt").setAll(true) |
|
|
|
|
.call(); |
|
|
|
|
|
|
|
|
|
// switch back to a side branch
|
|
|
|
|
git.checkout().setCreateBranch(true).setStartPoint(first) |
|
|
|
|
.setName("side").call(); |
|
|
|
|
writeTrashFile("c.txt", "side"); |
|
|
|
|
git.add().addFilepattern("c.txt").call(); |
|
|
|
|
git.commit().setMessage("added c.txt").call(); |
|
|
|
|
|
|
|
|
|
// Get a handle to the the file so on windows it can't be deleted.
|
|
|
|
|
FileInputStream fis = new FileInputStream(new File(db.getWorkTree(), |
|
|
|
|
"b.txt")); |
|
|
|
|
MergeResult mergeRes = git.merge().include(masterCommit).call(); |
|
|
|
|
if (mergeRes.getMergeStatus().equals(MergeStatus.FAILED)) { |
|
|
|
|
// probably windows
|
|
|
|
|
assertEquals(1, mergeRes.getFailingPaths().size()); |
|
|
|
|
assertEquals(MergeFailureReason.COULD_NOT_DELETE, mergeRes |
|
|
|
|
.getFailingPaths().get("b.txt")); |
|
|
|
|
} |
|
|
|
|
assertEquals("[a.txt, mode:100644, content:master]" |
|
|
|
|
+ "[c.txt, mode:100644, content:side]", indexState(CONTENT)); |
|
|
|
|
fis.close(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
public void checkForCorrectIndex() throws Exception { |
|
|
|
|
File f; |
|
|
|
|
long lastTs4, lastTsIndex; |
|
|
|
|
Git git = Git.wrap(db); |
|
|
|
|
File indexFile = db.getIndexFile(); |
|
|
|
|
|
|
|
|
|
// Create initial content and remember when the last file was written.
|
|
|
|
|
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); |
|
|
|
|
lastTs4 = f.lastModified(); |
|
|
|
|
|
|
|
|
|
// add all files, commit and check this doesn't update any working tree
|
|
|
|
|
// files and that the index is in a new file system timer tick. Make
|
|
|
|
|
// sure to wait long enough before adding so the index doesn't contain
|
|
|
|
|
// racily clean entries
|
|
|
|
|
fsTick(f); |
|
|
|
|
git.add().addFilepattern(".").call(); |
|
|
|
|
RevCommit firstCommit = git.commit().setMessage("initial commit") |
|
|
|
|
.call(); |
|
|
|
|
checkConsistentLastModified("0", "1", "2", "3", "4"); |
|
|
|
|
checkModificationTimeStampOrder("1", "2", "3", "4", "<.git/index"); |
|
|
|
|
assertEquals("Commit should not touch working tree file 4", lastTs4, |
|
|
|
|
new File(db.getWorkTree(), "4").lastModified()); |
|
|
|
|
lastTsIndex = indexFile.lastModified(); |
|
|
|
|
|
|
|
|
|
// Do modifications on the master branch. Then add and commit. This
|
|
|
|
|
// should touch only "0", "2 and "3"
|
|
|
|
|
fsTick(indexFile); |
|
|
|
|
f = writeTrashFiles(false, "master", null, "1master\n2\n3", "master", |
|
|
|
|
null); |
|
|
|
|
fsTick(f); |
|
|
|
|
git.add().addFilepattern(".").call(); |
|
|
|
|
RevCommit masterCommit = git.commit().setMessage("master commit") |
|
|
|
|
.call(); |
|
|
|
|
checkConsistentLastModified("0", "1", "2", "3", "4"); |
|
|
|
|
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" |
|
|
|
|
+ lastTsIndex, "<0", |
|
|
|
|
"2", "3", "<.git/index"); |
|
|
|
|
lastTsIndex = indexFile.lastModified(); |
|
|
|
|
|
|
|
|
|
// Checkout a side branch. This should touch only "0", "2 and "3"
|
|
|
|
|
fsTick(indexFile); |
|
|
|
|
git.checkout().setCreateBranch(true).setStartPoint(firstCommit) |
|
|
|
|
.setName("side").call(); |
|
|
|
|
checkConsistentLastModified("0", "1", "2", "3", "4"); |
|
|
|
|
checkModificationTimeStampOrder("1", "4", "*" + lastTs4, "<*" |
|
|
|
|
+ lastTsIndex, "<0", |
|
|
|
|
"2", "3", ".git/index"); |
|
|
|
|
lastTsIndex = indexFile.lastModified(); |
|
|
|
|
|
|
|
|
|
// This checkout may have populated worktree and index so fast that we
|
|
|
|
|
// may have smudged entries now. Check that we have the right content
|
|
|
|
|
// and then rewrite the index to get rid of smudged state
|
|
|
|
|
assertEquals("[0, mode:100644, content:orig]" //
|
|
|
|
|
+ "[1, mode:100644, content:orig]" //
|
|
|
|
|
+ "[2, mode:100644, content:1\n2\n3]" //
|
|
|
|
|
+ "[3, mode:100644, content:orig]" //
|
|
|
|
|
+ "[4, mode:100644, content:orig]", //
|
|
|
|
|
indexState(CONTENT)); |
|
|
|
|
fsTick(indexFile); |
|
|
|
|
f = writeTrashFiles(false, "orig", "orig", "1\n2\n3", "orig", "orig"); |
|
|
|
|
lastTs4 = f.lastModified(); |
|
|
|
|
fsTick(f); |
|
|
|
|
git.add().addFilepattern(".").call(); |
|
|
|
|
checkConsistentLastModified("0", "1", "2", "3", "4"); |
|
|
|
|
checkModificationTimeStampOrder("*" + lastTsIndex, "<0", "1", "2", "3", |
|
|
|
|
"4", "<.git/index"); |
|
|
|
|
lastTsIndex = indexFile.lastModified(); |
|
|
|
|
|
|
|
|
|
// Do modifications on the side branch. Touch only "1", "2 and "3"
|
|
|
|
|
fsTick(indexFile); |
|
|
|
|
f = writeTrashFiles(false, null, "side", "1\n2\n3side", "side", null); |
|
|
|
|
fsTick(f); |
|
|
|
|
git.add().addFilepattern(".").call(); |
|
|
|
|
git.commit().setMessage("side commit").call(); |
|
|
|
|
checkConsistentLastModified("0", "1", "2", "3", "4"); |
|
|
|
|
checkModificationTimeStampOrder("0", "4", "*" + lastTs4, "<*" |
|
|
|
|
+ lastTsIndex, "<1", "2", "3", "<.git/index"); |
|
|
|
|
lastTsIndex = indexFile.lastModified(); |
|
|
|
|
|
|
|
|
|
// merge master and side. Should only touch "0," "2" and "3"
|
|
|
|
|
fsTick(indexFile); |
|
|
|
|
git.merge().include(masterCommit).call(); |
|
|
|
|
checkConsistentLastModified("0", "1", "2", "4"); |
|
|
|
|
checkModificationTimeStampOrder("4", "*" + lastTs4, "<1", "<*" |
|
|
|
|
+ lastTsIndex, "<0", "2", "3", ".git/index"); |
|
|
|
|
assertEquals( |
|
|
|
|
"[0, mode:100644, content:master]" //
|
|
|
|
|
+ "[1, mode:100644, content:side]" //
|
|
|
|
|
+ "[2, mode:100644, content:1master\n2\n3side\n]" //
|
|
|
|
|
+ "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" //
|
|
|
|
|
+ "[4, mode:100644, content:orig]", //
|
|
|
|
|
indexState(CONTENT)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Assert that every specified index entry has the same last modification
|
|
|
|
|
// timestamp as the associated file
|
|
|
|
|
private void checkConsistentLastModified(String... pathes) |
|
|
|
|
throws IOException { |
|
|
|
|
DirCache dc = db.readDirCache(); |
|
|
|
|
File workTree = db.getWorkTree(); |
|
|
|
|
for (String path : pathes) |
|
|
|
|
assertEquals( |
|
|
|
|
"IndexEntry with path " |
|
|
|
|
+ path |
|
|
|
|
+ " has lastmodified with is different from the worktree file", |
|
|
|
|
new File(workTree, path).lastModified(), dc.getEntry(path) |
|
|
|
|
.getLastModified()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Assert that modification timestamps of working tree files are as
|
|
|
|
|
// expected. You may specify n files. It is asserted that every file
|
|
|
|
|
// i+1 is not older than file i. If a path of file i+1 is prefixed with "<"
|
|
|
|
|
// then this file must be younger then file i. A path "*<modtime>"
|
|
|
|
|
// represents a file with a modification time of <modtime>
|
|
|
|
|
// E.g. ("a", "b", "<c", "f/a.txt") means: a<=b<c<=f/a.txt
|
|
|
|
|
private void checkModificationTimeStampOrder(String... pathes) { |
|
|
|
|
long lastMod = Long.MIN_VALUE; |
|
|
|
|
for (String p : pathes) { |
|
|
|
|
boolean strong = p.startsWith("<"); |
|
|
|
|
boolean fixed = p.charAt(strong ? 1 : 0) == '*'; |
|
|
|
|
p = p.substring((strong ? 1 : 0) + (fixed ? 1 : 0)); |
|
|
|
|
long curMod = fixed ? Long.valueOf(p).longValue() : new File( |
|
|
|
|
db.getWorkTree(), p).lastModified(); |
|
|
|
|
if (strong) |
|
|
|
|
assertTrue("path " + p + " is not younger than predecesssor", |
|
|
|
|
curMod > lastMod); |
|
|
|
|
else |
|
|
|
|
assertTrue("path " + p + " is older than predecesssor", |
|
|
|
|
curMod >= lastMod); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|