WorkingTreeModifiedEvent: must be fired explicitly after merge

A merge may write files to the working tree. After a successful
merge one must fire a WorkingTreeModifiedEvent explicitly if
getModifiedFiles() is not empty.

Also, any touched files must be reported by the
WorkingTreeModifiedEvent fired by DirCacheCheckout.checkout().

Bug: 552636
Change-Id: I5fab8279ed8be8a4ae34cddfa726836b9277aea6
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2019-11-02 19:26:42 +01:00
parent b29e9bd1cb
commit 64f2407f19
6 changed files with 73 additions and 25 deletions

View File

@ -43,7 +43,6 @@
package org.eclipse.jgit.events; package org.eclipse.jgit.events;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.util.Arrays; import java.util.Arrays;
@ -99,10 +98,12 @@ public void assertEvent(String[] expectedModified,
Arrays.sort(expectedModified); Arrays.sort(expectedModified);
Arrays.sort(actuallyDeleted); Arrays.sort(actuallyDeleted);
Arrays.sort(expectedDeleted); Arrays.sort(expectedDeleted);
assertArrayEquals("Unexpected modifications reported", expectedModified, assertEquals("Unexpected modifications reported",
actuallyModified); Arrays.toString(expectedModified),
assertArrayEquals("Unexpected deletions reported", expectedDeleted, Arrays.toString(actuallyModified));
actuallyDeleted); assertEquals("Unexpected deletions reported",
Arrays.toString(expectedDeleted),
Arrays.toString(actuallyDeleted));
reset(); reset();
} }
} }

View File

@ -345,6 +345,40 @@ public void testCherryPickConflictFiresModifiedEvent() throws Exception {
} }
} }
@Test
public void testCherryPickNewFileFiresModifiedEvent() throws Exception {
ListenerHandle listener = null;
try (Git git = new Git(db)) {
writeTrashFile("test.txt", "a");
git.add().addFilepattern("test.txt").call();
git.commit().setMessage("commit1").call();
git.checkout().setCreateBranch(true).setName("a").call();
writeTrashFile("side.txt", "side");
git.add().addFilepattern("side.txt").call();
RevCommit side = git.commit().setMessage("side").call();
assertNotNull(side);
assertNotNull(git.checkout().setName(Constants.MASTER).call());
writeTrashFile("test.txt", "b");
assertNotNull(git.add().addFilepattern("test.txt").call());
assertNotNull(git.commit().setMessage("commit2").call());
ChangeRecorder recorder = new ChangeRecorder();
listener = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
CherryPickResult result = git.cherryPick()
.include(side.getId()).call();
assertEquals(CherryPickStatus.OK, result.getStatus());
recorder.assertEvent(new String[] { "side.txt" },
ChangeRecorder.EMPTY);
} finally {
if (listener != null) {
listener.remove();
}
}
}
@Test @Test
public void testCherryPickOurCommitName() throws Exception { public void testCherryPickOurCommitName() throws Exception {
try (Git git = new Git(db)) { try (Git git = new Git(db)) {

View File

@ -57,12 +57,9 @@
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set;
import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.MergeResult.MergeStatus;
import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler; import org.eclipse.jgit.api.RebaseCommand.InteractiveHandler;
@ -78,6 +75,7 @@
import org.eclipse.jgit.errors.IllegalTodoFileModification; import org.eclipse.jgit.errors.IllegalTodoFileModification;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.events.ChangeRecorder;
import org.eclipse.jgit.events.ListenerHandle; import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AbbreviatedObjectId;
@ -2014,10 +2012,9 @@ public void testRebaseWithAutoStashAndSubdirs() throws Exception {
checkoutBranch("refs/heads/topic"); checkoutBranch("refs/heads/topic");
writeTrashFile("sub/file0", "unstaged modified file0"); writeTrashFile("sub/file0", "unstaged modified file0");
Set<String> modifiedFiles = new HashSet<>(); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = db.getListenerList() ListenerHandle handle = db.getListenerList()
.addWorkingTreeModifiedListener( .addWorkingTreeModifiedListener(recorder);
event -> modifiedFiles.addAll(event.getModified()));
try { try {
// rebase // rebase
assertEquals(Status.OK, git.rebase() assertEquals(Status.OK, git.rebase()
@ -2035,9 +2032,8 @@ public void testRebaseWithAutoStashAndSubdirs() throws Exception {
+ "[sub/file0, mode:100644, content:file0]", + "[sub/file0, mode:100644, content:file0]",
indexState(CONTENT)); indexState(CONTENT));
assertEquals(RepositoryState.SAFE, db.getRepositoryState()); assertEquals(RepositoryState.SAFE, db.getRepositoryState());
List<String> modified = new ArrayList<>(modifiedFiles); recorder.assertEvent(new String[] { "file1", "file2", "sub/file0" },
Collections.sort(modified); new String[0]);
assertEquals("[file1, sub/file0]", modified.toString());
} }
@Test @Test

View File

@ -160,6 +160,10 @@ public CherryPickResult call() throws GitAPIException, NoMessageException,
merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$ merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$
cherryPickName }); cherryPickName });
if (merger.merge(newHead, srcCommit)) { if (merger.merge(newHead, srcCommit)) {
if (!merger.getModifiedFiles().isEmpty()) {
repo.fireEvent(new WorkingTreeModifiedEvent(
merger.getModifiedFiles(), null));
}
if (AnyObjectId.isEqual(newHead.getTree().getId(), if (AnyObjectId.isEqual(newHead.getTree().getId(),
merger.getResultTreeId())) { merger.getResultTreeId())) {
continue; continue;

View File

@ -58,6 +58,7 @@
import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.UnmergedPathsException;
import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -175,6 +176,10 @@ public RevCommit call() throws NoMessageException, UnmergedPathsException,
+ "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$ + "This reverts commit " + srcCommit.getId().getName() //$NON-NLS-1$
+ ".\n"; //$NON-NLS-1$ + ".\n"; //$NON-NLS-1$
if (merger.merge(headCommit, srcParent)) { if (merger.merge(headCommit, srcParent)) {
if (!merger.getModifiedFiles().isEmpty()) {
repo.fireEvent(new WorkingTreeModifiedEvent(
merger.getModifiedFiles(), null));
}
if (AnyObjectId.isEqual(headCommit.getTree().getId(), if (AnyObjectId.isEqual(headCommit.getTree().getId(),
merger.getResultTreeId())) merger.getResultTreeId()))
continue; continue;

View File

@ -53,9 +53,11 @@
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.api.errors.CanceledException; import org.eclipse.jgit.api.errors.CanceledException;
import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.FilterFailedException;
@ -142,6 +144,8 @@ public CheckoutMetadata(EolStreamType eolStreamType,
private ArrayList<String> removed = new ArrayList<>(); private ArrayList<String> removed = new ArrayList<>();
private ArrayList<String> kept = new ArrayList<>();
private ObjectId mergeCommitTree; private ObjectId mergeCommitTree;
private DirCache dc; private DirCache dc;
@ -432,11 +436,11 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
if (mtime == null || mtime.equals(Instant.EPOCH)) { if (mtime == null || mtime.equals(Instant.EPOCH)) {
entry.setLastModified(f.getEntryLastModifiedInstant()); entry.setLastModified(f.getEntryLastModifiedInstant());
} }
keep(entry, f); keep(i.getEntryPathString(), entry, f);
} }
} else } else
// The index contains a folder // The index contains a folder
keep(i.getDirCacheEntry(), f); keep(i.getEntryPathString(), i.getDirCacheEntry(), f);
} else { } else {
// There is no entry in the merge commit. Means: we want to delete // There is no entry in the merge commit. Means: we want to delete
// what's currently in the index and working tree // what's currently in the index and working tree
@ -496,8 +500,11 @@ public boolean checkout() throws IOException {
dc.unlock(); dc.unlock();
} finally { } finally {
if (performingCheckout) { if (performingCheckout) {
Set<String> touched = new HashSet<>(conflicts);
touched.addAll(getUpdated().keySet());
touched.addAll(kept);
WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent( WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
getUpdated().keySet(), getRemoved()); touched, getRemoved());
if (!event.isEmpty()) { if (!event.isEmpty()) {
repo.fireEvent(event); repo.fireEvent(event);
} }
@ -826,14 +833,14 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
break; break;
case 0xDFD: // 3 4 case 0xDFD: // 3 4
keep(dce, f); keep(name, dce, f);
break; break;
case 0xF0D: // 18 case 0xF0D: // 18
remove(name); remove(name);
break; break;
case 0xDFF: // 5 5b 6 6b case 0xDFF: // 5 5b 6 6b
if (equalIdAndMode(iId, iMode, mId, mMode)) if (equalIdAndMode(iId, iMode, mId, mMode))
keep(dce, f); // 5 6 keep(name, dce, f); // 5 6
else else
conflict(name, dce, h, m); // 5b 6b conflict(name, dce, h, m); // 5b 6b
break; break;
@ -863,7 +870,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
conflict(name, dce, h, m); // 9 conflict(name, dce, h, m); // 9
break; break;
case 0xFD0: // keep without a rule case 0xFD0: // keep without a rule
keep(dce, f); keep(name, dce, f);
break; break;
case 0xFFD: // 12 13 14 case 0xFFD: // 12 13 14
if (equalIdAndMode(hId, hMode, iId, iMode)) if (equalIdAndMode(hId, hMode, iId, iMode))
@ -883,7 +890,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
conflict(name, dce, h, m); conflict(name, dce, h, m);
break; break;
default: default:
keep(dce, f); keep(name, dce, f);
} }
return; return;
} }
@ -968,7 +975,7 @@ else if (m == null)
if (initialCheckout) if (initialCheckout)
update(name, mId, mMode); update(name, mId, mMode);
else else
keep(dce, f); keep(name, dce, f);
} else } else
conflict(name, dce, h, m); conflict(name, dce, h, m);
} }
@ -1031,7 +1038,7 @@ else if (m == null)
// Nothing in Head // Nothing in Head
// Something in Index // Something in Index
// -> Merge contains nothing new. Keep the index. // -> Merge contains nothing new. Keep the index.
keep(dce, f); keep(name, dce, f);
} else } else
// Merge contains something and it is not the same as Index // Merge contains something and it is not the same as Index
// Nothing in Head // Nothing in Head
@ -1182,7 +1189,7 @@ && isModified_IndexTree(name, iId, iMode, mId, mMode,
// to the other one. // to the other one.
// -> In all three cases we don't touch index and file. // -> In all three cases we don't touch index and file.
keep(dce, f); keep(name, dce, f);
} }
} }
} }
@ -1231,12 +1238,13 @@ private void conflict(String path, DirCacheEntry e, AbstractTreeIterator h, Abst
} }
} }
private void keep(DirCacheEntry e, WorkingTreeIterator f) private void keep(String path, DirCacheEntry e, WorkingTreeIterator f)
throws IOException { throws IOException {
if (e != null && !FileMode.TREE.equals(e.getFileMode())) if (e != null && !FileMode.TREE.equals(e.getFileMode()))
builder.add(e); builder.add(e);
if (force) { if (force) {
if (f.isModified(e, true, this.walk.getObjectReader())) { if (f.isModified(e, true, this.walk.getObjectReader())) {
kept.add(path);
checkoutEntry(repo, e, this.walk.getObjectReader()); checkoutEntry(repo, e, this.walk.getObjectReader());
} }
} }