Browse Source

Send a detailed event on working tree modifications

Currently there is no way to determine the precise changes done
to the working tree by a JGit command. Only the CheckoutCommand
actually provides access to the lists of modified, deleted, and
to-be-deleted files, but those lists may be inaccurate (since they
are determined up-front before the working tree is modified) if
the actual checkout then fails halfway through. Moreover, other
JGit commands that modify the working tree do not offer any way to
figure out which files were changed.

This poses problems for EGit, which may need to refresh parts of the
Eclipse workspace when JGit has done java.io file operations.

Provide the foundations for better file change tracking: the working
tree is modified exclusively in DirCacheCheckout. Make it emit a new
type of RepositoryEvent that lists all files that were modified or
deleted, even if the checkout failed halfway through. We update the
'updated' and 'removed' lists determined up-front in case of file
system problems to reflect the actual state of changes made.

EGit thus can register a listener for these events and then knows
exactly which parts of the Eclipse workspace may need to be refreshed.

Two commands manage checking out individual DirCacheEntries themselves:
checkout specific paths, and applying a stash with untracked files.
Make those two also emit such a new WorkingTreeModifiedEvent.

Furthermore, merges may modify files, and clean, rm, and stash create
may delete files.

CQ: 13969
Bug: 500106
Change-Id: I7a100aee315791fa1201f43bbad61fbae60b35cb
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
stable-4.9
Thomas Wolf 7 years ago committed by Matthias Sohn
parent
commit
b13a285098
  1. 1
      org.eclipse.jgit.test/pom.xml
  2. 108
      org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java
  3. 87
      org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java
  4. 328
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java
  5. 27
      org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
  6. 5
      org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java
  7. 5
      org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java
  8. 29
      org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java
  9. 21
      org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
  10. 11
      org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java
  11. 84
      org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
  12. 13
      org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java
  13. 129
      org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java
  14. 61
      org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java

1
org.eclipse.jgit.test/pom.xml

@ -66,7 +66,6 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope>
</dependency> </dependency>
<!-- Optional security provider for encryption tests. --> <!-- Optional security provider for encryption tests. -->

108
org.eclipse.jgit.test/src/org/eclipse/jgit/events/ChangeRecorder.java

@ -0,0 +1,108 @@
/*
* Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
* 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.events;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* A {@link WorkingTreeModifiedListener} that can be used in tests to check
* expected events.
*/
public class ChangeRecorder implements WorkingTreeModifiedListener {
public static final String[] EMPTY = new String[0];
private Set<String> modified = new HashSet<>();
private Set<String> deleted = new HashSet<>();
private int eventCount;
@Override
public void onWorkingTreeModified(WorkingTreeModifiedEvent event) {
eventCount++;
modified.removeAll(event.getDeleted());
deleted.removeAll(event.getModified());
modified.addAll(event.getModified());
deleted.addAll(event.getDeleted());
}
private String[] getModified() {
return modified.toArray(new String[modified.size()]);
}
private String[] getDeleted() {
return deleted.toArray(new String[deleted.size()]);
}
private void reset() {
eventCount = 0;
modified.clear();
deleted.clear();
}
public void assertNoEvent() {
assertEquals("Unexpected WorkingTreeModifiedEvent ", 0, eventCount);
}
public void assertEvent(String[] expectedModified,
String[] expectedDeleted) {
String[] actuallyModified = getModified();
String[] actuallyDeleted = getDeleted();
Arrays.sort(actuallyModified);
Arrays.sort(expectedModified);
Arrays.sort(actuallyDeleted);
Arrays.sort(expectedDeleted);
assertArrayEquals("Unexpected modifications reported", expectedModified,
actuallyModified);
assertArrayEquals("Unexpected deletions reported", expectedDeleted,
actuallyDeleted);
reset();
}
}

87
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java

@ -55,12 +55,15 @@ import org.eclipse.jgit.api.errors.InvalidRefNameException;
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.StashApplyFailureException; import org.eclipse.jgit.api.errors.StashApplyFailureException;
import org.eclipse.jgit.events.ChangeRecorder;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -77,15 +80,31 @@ public class StashApplyCommandTest extends RepositoryTestCase {
private File committedFile; private File committedFile;
private ChangeRecorder recorder;
private ListenerHandle handle;
@Override @Override
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
super.setUp(); super.setUp();
git = Git.wrap(db); git = Git.wrap(db);
recorder = new ChangeRecorder();
handle = db.getListenerList().addWorkingTreeModifiedListener(recorder);
committedFile = writeTrashFile(PATH, "content"); committedFile = writeTrashFile(PATH, "content");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
head = git.commit().setMessage("add file").call(); head = git.commit().setMessage("add file").call();
assertNotNull(head); assertNotNull(head);
recorder.assertNoEvent();
}
@Override
@After
public void tearDown() throws Exception {
if (handle != null) {
handle.remove();
}
super.tearDown();
} }
@Test @Test
@ -95,10 +114,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertFalse(committedFile.exists()); assertFalse(committedFile.exists());
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -121,11 +142,13 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertFalse(addedFile.exists()); assertFalse(addedFile.exists());
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { addedPath });
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertTrue(addedFile.exists()); assertTrue(addedFile.exists());
assertEquals("content2", read(addedFile)); assertEquals("content2", read(addedFile));
recorder.assertEvent(new String[] { addedPath }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getChanged().isEmpty()); assertTrue(status.getChanged().isEmpty());
@ -142,14 +165,17 @@ public class StashApplyCommandTest extends RepositoryTestCase {
@Test @Test
public void indexDelete() throws Exception { public void indexDelete() throws Exception {
git.rm().addFilepattern("file.txt").call(); git.rm().addFilepattern("file.txt").call();
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertFalse(committedFile.exists()); assertFalse(committedFile.exists());
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file.txt" });
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -170,10 +196,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertEquals("content2", read(committedFile)); assertEquals("content2", read(committedFile));
recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -193,16 +221,21 @@ public class StashApplyCommandTest extends RepositoryTestCase {
File subfolderFile = writeTrashFile(path, "content"); File subfolderFile = writeTrashFile(path, "content");
git.add().addFilepattern(path).call(); git.add().addFilepattern(path).call();
head = git.commit().setMessage("add file").call(); head = git.commit().setMessage("add file").call();
recorder.assertNoEvent();
writeTrashFile(path, "content2"); writeTrashFile(path, "content2");
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(subfolderFile)); assertEquals("content", read(subfolderFile));
recorder.assertEvent(new String[] { "d1/d2/f.txt" },
ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertEquals("content2", read(subfolderFile)); assertEquals("content2", read(subfolderFile));
recorder.assertEvent(new String[] { "d1/d2/f.txt", "d1/d2", "d1" },
ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -225,10 +258,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertEquals("content3", read(committedFile)); assertEquals("content3", read(committedFile));
recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -252,10 +287,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertEquals("content2", read(committedFile)); assertEquals("content2", read(committedFile));
recorder.assertEvent(new String[] { "file.txt" }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -281,10 +318,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertFalse(added.exists()); assertFalse(added.exists());
recorder.assertNoEvent();
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertEquals("content2", read(added)); assertEquals("content2", read(added));
recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getChanged().isEmpty()); assertTrue(status.getChanged().isEmpty());
@ -308,10 +347,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
assertFalse(committedFile.exists()); assertFalse(committedFile.exists());
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { PATH });
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -337,9 +378,13 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed); assertNotNull(stashed);
assertTrue(committedFile.exists()); assertTrue(committedFile.exists());
assertFalse(addedFile.exists()); assertFalse(addedFile.exists());
recorder.assertEvent(new String[] { PATH },
new String[] { "file2.txt" });
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
recorder.assertEvent(new String[] { "file2.txt" },
new String[] { PATH });
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getChanged().isEmpty()); assertTrue(status.getChanged().isEmpty());
@ -362,6 +407,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content3"); writeTrashFile(PATH, "content3");
@ -372,6 +418,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
// expected // expected
} }
assertEquals("content3", read(PATH)); assertEquals("content3", read(PATH));
recorder.assertNoEvent();
} }
@Test @Test
@ -391,10 +438,12 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals("content\nhead change\nmore content\n", assertEquals("content\nhead change\nmore content\n",
read(committedFile)); read(committedFile));
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content\nmore content\ncommitted change\n"); writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.commit().setMessage("committed change").call(); git.commit().setMessage("committed change").call();
recorder.assertNoEvent();
try { try {
git.stashApply().call(); git.stashApply().call();
@ -402,6 +451,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
} catch (StashApplyFailureException e) { } catch (StashApplyFailureException e) {
// expected // expected
} }
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = new StatusCommand(db).call(); Status status = new StatusCommand(db).call();
assertEquals(1, status.getConflicting().size()); assertEquals(1, status.getConflicting().size());
assertEquals( assertEquals(
@ -426,12 +476,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
writeTrashFile(PATH, "master content"); writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call(); git.commit().setMessage("even content").call();
recorder.assertNoEvent();
git.checkout().setName(otherBranch).call(); git.checkout().setName(otherBranch).call();
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "otherBranch content"); writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call(); git.commit().setMessage("even more content").call();
recorder.assertNoEvent();
writeTrashFile(path2, "content\nstashed change\nmore content\n"); writeTrashFile(path2, "content\nstashed change\nmore content\n");
@ -442,12 +495,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals("otherBranch content", assertEquals("otherBranch content",
read(committedFile)); read(committedFile));
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
git.checkout().setName("master").call(); git.checkout().setName("master").call();
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().call(); git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2)); assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals("master content", assertEquals("master content",
read(committedFile)); read(committedFile));
recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
} }
@Test @Test
@ -467,12 +523,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
writeTrashFile(PATH, "master content"); writeTrashFile(PATH, "master content");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.commit().setMessage("even content").call(); git.commit().setMessage("even content").call();
recorder.assertNoEvent();
git.checkout().setName(otherBranch).call(); git.checkout().setName(otherBranch).call();
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "otherBranch content"); writeTrashFile(PATH, "otherBranch content");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.commit().setMessage("even more content").call(); git.commit().setMessage("even more content").call();
recorder.assertNoEvent();
writeTrashFile(path2, writeTrashFile(path2,
"content\nstashed change in index\nmore content\n"); "content\nstashed change in index\nmore content\n");
@ -485,8 +544,10 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals("content\nmore content\n", read(file2)); assertEquals("content\nmore content\n", read(file2));
assertEquals("otherBranch content", read(committedFile)); assertEquals("otherBranch content", read(committedFile));
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
git.checkout().setName("master").call(); git.checkout().setName("master").call();
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().call(); git.stashApply().call();
assertEquals("content\nstashed change\nmore content\n", read(file2)); assertEquals("content\nstashed change\nmore content\n", read(file2));
assertEquals( assertEquals(
@ -494,6 +555,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
+ "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]", + "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]",
indexState(CONTENT)); indexState(CONTENT));
assertEquals("master content", read(committedFile)); assertEquals("master content", read(committedFile));
recorder.assertEvent(new String[] { path2 }, ChangeRecorder.EMPTY);
} }
@Test @Test
@ -501,6 +563,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
writeTrashFile(PATH, "content\nmore content\n"); writeTrashFile(PATH, "content\nmore content\n");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.commit().setMessage("more content").call(); git.commit().setMessage("more content").call();
recorder.assertNoEvent();
writeTrashFile(PATH, "content\nstashed change\nmore content\n"); writeTrashFile(PATH, "content\nstashed change\nmore content\n");
@ -508,15 +571,18 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content\nmore content\n", read(committedFile)); assertEquals("content\nmore content\n", read(committedFile));
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content\nmore content\ncommitted change\n"); writeTrashFile(PATH, "content\nmore content\ncommitted change\n");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.commit().setMessage("committed change").call(); git.commit().setMessage("committed change").call();
recorder.assertNoEvent();
git.stashApply().call(); git.stashApply().call();
assertEquals( assertEquals(
"content\nstashed change\nmore content\ncommitted change\n", "content\nstashed change\nmore content\ncommitted change\n",
read(committedFile)); read(committedFile));
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
} }
@Test @Test
@ -527,6 +593,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
writeTrashFile(PATH, "content3"); writeTrashFile(PATH, "content3");
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
@ -538,6 +605,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
} catch (StashApplyFailureException e) { } catch (StashApplyFailureException e) {
// expected // expected
} }
recorder.assertNoEvent();
assertEquals("content2", read(PATH)); assertEquals("content2", read(PATH));
} }
@ -549,6 +617,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertNotNull(stashed); assertNotNull(stashed);
assertEquals("content", read(committedFile)); assertEquals("content", read(committedFile));
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
String path2 = "file2.txt"; String path2 = "file2.txt";
writeTrashFile(path2, "content3"); writeTrashFile(path2, "content3");
@ -557,6 +626,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getAdded().isEmpty()); assertTrue(status.getAdded().isEmpty());
@ -583,12 +653,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
RevCommit stashed = git.stashCreate().call(); RevCommit stashed = git.stashCreate().call();
assertNotNull(stashed); assertNotNull(stashed);
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
recorder.assertEvent(ChangeRecorder.EMPTY,
new String[] { subdir, path });
git.branchCreate().setName(otherBranch).call(); git.branchCreate().setName(otherBranch).call();
git.checkout().setName(otherBranch).call(); git.checkout().setName(otherBranch).call();
ObjectId unstashed = git.stashApply().call(); ObjectId unstashed = git.stashApply().call();
assertEquals(stashed, unstashed); assertEquals(stashed, unstashed);
recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertTrue(status.getChanged().isEmpty()); assertTrue(status.getChanged().isEmpty());
@ -643,12 +716,15 @@ public class StashApplyCommandTest extends RepositoryTestCase {
git.commit().setMessage("x").call(); git.commit().setMessage("x").call();
file.delete(); file.delete();
git.rm().addFilepattern("file").call(); git.rm().addFilepattern("file").call();
recorder.assertNoEvent();
git.stashCreate().call(); git.stashCreate().call();
recorder.assertEvent(new String[] { "file" }, ChangeRecorder.EMPTY);
file.delete(); file.delete();
git.stashApply().setStashRef("stash@{0}").call(); git.stashApply().setStashRef("stash@{0}").call();
assertFalse(file.exists()); assertFalse(file.exists());
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "file" });
} }
@Test @Test
@ -660,9 +736,11 @@ public class StashApplyCommandTest extends RepositoryTestCase {
git.add().addFilepattern(PATH).call(); git.add().addFilepattern(PATH).call();
git.stashCreate().call(); git.stashCreate().call();
assertTrue(untrackedFile.exists()); assertTrue(untrackedFile.exists());
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
git.stashApply().setStashRef("stash@{0}").call(); git.stashApply().setStashRef("stash@{0}").call();
assertTrue(untrackedFile.exists()); assertTrue(untrackedFile.exists());
recorder.assertEvent(new String[] { PATH }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertEquals(1, status.getUntracked().size()); assertEquals(1, status.getUntracked().size());
@ -684,11 +762,14 @@ public class StashApplyCommandTest extends RepositoryTestCase {
.call(); .call();
assertNotNull(stashedCommit); assertNotNull(stashedCommit);
assertFalse(untrackedFile.exists()); assertFalse(untrackedFile.exists());
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
deleteTrashFile("a/b"); // checkout should create parent dirs deleteTrashFile("a/b"); // checkout should create parent dirs
git.stashApply().setStashRef("stash@{0}").call(); git.stashApply().setStashRef("stash@{0}").call();
assertTrue(untrackedFile.exists()); assertTrue(untrackedFile.exists());
assertEquals("content", read(path)); assertEquals("content", read(path));
recorder.assertEvent(new String[] { path }, ChangeRecorder.EMPTY);
Status status = git.status().call(); Status status = git.status().call();
assertEquals(1, status.getUntracked().size()); assertEquals(1, status.getUntracked().size());
@ -706,6 +787,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
String path = "untracked.txt"; String path = "untracked.txt";
writeTrashFile(path, "untracked"); writeTrashFile(path, "untracked");
git.stashCreate().setIncludeUntracked(true).call(); git.stashCreate().setIncludeUntracked(true).call();
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
writeTrashFile(path, "committed"); writeTrashFile(path, "committed");
head = git.commit().setMessage("add file").call(); head = git.commit().setMessage("add file").call();
@ -719,6 +801,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals(e.getMessage(), JGitText.get().stashApplyConflict); assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
} }
assertEquals("committed", read(path)); assertEquals("committed", read(path));
recorder.assertNoEvent();
} }
@Test @Test
@ -727,6 +810,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
String path = "untracked.txt"; String path = "untracked.txt";
writeTrashFile(path, "untracked"); writeTrashFile(path, "untracked");
git.stashCreate().setIncludeUntracked(true).call(); git.stashCreate().setIncludeUntracked(true).call();
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { path });
writeTrashFile(path, "working-directory"); writeTrashFile(path, "working-directory");
try { try {
@ -736,6 +820,7 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertEquals(e.getMessage(), JGitText.get().stashApplyConflict); assertEquals(e.getMessage(), JGitText.get().stashApplyConflict);
} }
assertEquals("working-directory", read(path)); assertEquals("working-directory", read(path));
recorder.assertNoEvent();
} }
@Test @Test
@ -747,11 +832,13 @@ public class StashApplyCommandTest extends RepositoryTestCase {
assertTrue(PATH + " should exist", check(PATH)); assertTrue(PATH + " should exist", check(PATH));
assertEquals(PATH + " should have been reset", "content", read(PATH)); assertEquals(PATH + " should have been reset", "content", read(PATH));
assertFalse(path + " should not exist", check(path)); assertFalse(path + " should not exist", check(path));
recorder.assertEvent(new String[] { PATH }, new String[] { path });
git.stashApply().setStashRef("stash@{0}").call(); git.stashApply().setStashRef("stash@{0}").call();
assertTrue(PATH + " should exist", check(PATH)); assertTrue(PATH + " should exist", check(PATH));
assertEquals(PATH + " should have new content", "changed", read(PATH)); assertEquals(PATH + " should have new content", "changed", read(PATH));
assertTrue(path + " should exist", check(path)); assertTrue(path + " should exist", check(path));
assertEquals(path + " should have new content", "untracked", assertEquals(path + " should have new content", "untracked",
read(path)); read(path));
recorder.assertEvent(new String[] { PATH, path }, ChangeRecorder.EMPTY);
} }
} }

328
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java

@ -72,6 +72,8 @@ import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.events.ChangeRecorder;
import org.eclipse.jgit.events.ListenerHandle;
import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
@ -141,14 +143,19 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
@Test @Test
public void testResetHard() throws IOException, NoFilepatternException, public void testResetHard() throws IOException, NoFilepatternException,
GitAPIException { GitAPIException {
ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
writeTrashFile("f", "f()"); writeTrashFile("f", "f()");
writeTrashFile("D/g", "g()"); writeTrashFile("D/g", "g()");
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
git.commit().setMessage("inital").call(); git.commit().setMessage("inital").call();
assertIndex(mkmap("f", "f()", "D/g", "g()")); assertIndex(mkmap("f", "f()", "D/g", "g()"));
recorder.assertNoEvent();
git.branchCreate().setName("topic").call(); git.branchCreate().setName("topic").call();
recorder.assertNoEvent();
writeTrashFile("f", "f()\nmaster"); writeTrashFile("f", "f()\nmaster");
writeTrashFile("D/g", "g()\ng2()"); writeTrashFile("D/g", "g()\ng2()");
@ -156,9 +163,12 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
git.add().addFilepattern(".").call(); git.add().addFilepattern(".").call();
RevCommit master = git.commit().setMessage("master-1").call(); RevCommit master = git.commit().setMessage("master-1").call();
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
recorder.assertNoEvent();
checkoutBranch("refs/heads/topic"); checkoutBranch("refs/heads/topic");
assertIndex(mkmap("f", "f()", "D/g", "g()")); assertIndex(mkmap("f", "f()", "D/g", "g()"));
recorder.assertEvent(new String[] { "f", "D/g" },
new String[] { "E/h" });
writeTrashFile("f", "f()\nside"); writeTrashFile("f", "f()\nside");
assertTrue(new File(db.getWorkTree(), "D/g").delete()); assertTrue(new File(db.getWorkTree(), "D/g").delete());
@ -167,26 +177,41 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
git.add().addFilepattern(".").setUpdate(true).call(); git.add().addFilepattern(".").setUpdate(true).call();
RevCommit topic = git.commit().setMessage("topic-1").call(); RevCommit topic = git.commit().setMessage("topic-1").call();
assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
recorder.assertNoEvent();
writeTrashFile("untracked", "untracked"); writeTrashFile("untracked", "untracked");
resetHard(master); resetHard(master);
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
new String[] { "G", "G/i" });
resetHard(topic); resetHard(topic);
assertIndex(mkmap("f", "f()\nside", "G/i", "i()")); assertIndex(mkmap("f", "f()\nside", "G/i", "i()"));
assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked", assertWorkDir(mkmap("f", "f()\nside", "G/i", "i()", "untracked",
"untracked")); "untracked"));
recorder.assertEvent(new String[] { "f", "G/i" },
new String[] { "D", "D/g", "E", "E/h" });
assertEquals(MergeStatus.CONFLICTING, git.merge().include(master) assertEquals(MergeStatus.CONFLICTING, git.merge().include(master)
.call().getMergeStatus()); .call().getMergeStatus());
assertEquals( assertEquals(
"[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]", "[D/g, mode:100644, stage:1][D/g, mode:100644, stage:3][E/h, mode:100644][G/i, mode:100644][f, mode:100644, stage:1][f, mode:100644, stage:2][f, mode:100644, stage:3]",
indexState(0)); indexState(0));
recorder.assertEvent(new String[] { "f", "D/g", "E/h" },
ChangeRecorder.EMPTY);
resetHard(master); resetHard(master);
assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()")); assertIndex(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", "h()"));
assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h", assertWorkDir(mkmap("f", "f()\nmaster", "D/g", "g()\ng2()", "E/h",
"h()", "untracked", "untracked")); "h()", "untracked", "untracked"));
recorder.assertEvent(new String[] { "f", "D/g" },
new String[] { "G", "G/i" });
} finally {
if (handle != null) {
handle.remove();
}
} }
} }
@ -202,13 +227,18 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
@Test @Test
public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile() public void testResetHardFromIndexEntryWithoutFileToTreeWithoutFile()
throws Exception { throws Exception {
ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
writeTrashFile("x", "x"); writeTrashFile("x", "x");
git.add().addFilepattern("x").call(); git.add().addFilepattern("x").call();
RevCommit id1 = git.commit().setMessage("c1").call(); RevCommit id1 = git.commit().setMessage("c1").call();
writeTrashFile("f/g", "f/g"); writeTrashFile("f/g", "f/g");
git.rm().addFilepattern("x").call(); git.rm().addFilepattern("x").call();
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { "x" });
git.add().addFilepattern("f/g").call(); git.add().addFilepattern("f/g").call();
git.commit().setMessage("c2").call(); git.commit().setMessage("c2").call();
deleteTrashFile("f/g"); deleteTrashFile("f/g");
@ -217,6 +247,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// The actual test // The actual test
git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call(); git.reset().setMode(ResetType.HARD).setRef(id1.getName()).call();
assertIndex(mkmap("x", "x")); assertIndex(mkmap("x", "x"));
recorder.assertEvent(new String[] { "x" }, ChangeRecorder.EMPTY);
} finally {
if (handle != null) {
handle.remove();
}
} }
} }
@ -227,13 +262,22 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
*/ */
@Test @Test
public void testInitialCheckout() throws Exception { public void testInitialCheckout() throws Exception {
ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) { try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
TestRepository<Repository> db_t = new TestRepository<>(db); TestRepository<Repository> db_t = new TestRepository<>(db);
BranchBuilder master = db_t.branch("master"); BranchBuilder master = db_t.branch("master");
master.commit().add("f", "1").message("m0").create(); master.commit().add("f", "1").message("m0").create();
assertFalse(new File(db.getWorkTree(), "f").exists()); assertFalse(new File(db.getWorkTree(), "f").exists());
git.checkout().setName("master").call(); git.checkout().setName("master").call();
assertTrue(new File(db.getWorkTree(), "f").exists()); assertTrue(new File(db.getWorkTree(), "f").exists());
recorder.assertEvent(new String[] { "f" }, ChangeRecorder.EMPTY);
} finally {
if (handle != null) {
handle.remove();
}
} }
} }
@ -930,8 +974,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
public void testCheckoutChangeLinkToEmptyDir() throws Exception { public void testCheckoutChangeLinkToEmptyDir() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "was_file"; String fname = "was_file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
writeTrashFile(fname, "a"); writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -952,23 +999,34 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// modify file // modify file
writeTrashFile(fname, "b"); writeTrashFile(fname, "b");
assertWorkDir(mkmap(fname, "b", linkName, "/")); assertWorkDir(mkmap(fname, "b", linkName, "/"));
recorder.assertNoEvent();
// revert both paths to HEAD state // revert both paths to HEAD state
git.checkout().setStartPoint(Constants.HEAD) git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
.addPath(fname).addPath(linkName).call(); .addPath(linkName).call();
assertWorkDir(mkmap(fname, "a", linkName, "a")); assertWorkDir(mkmap(fname, "a", linkName, "a"));
recorder.assertEvent(new String[] { fname, linkName },
ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
public void testCheckoutChangeLinkToEmptyDirs() throws Exception { public void testCheckoutChangeLinkToEmptyDirs() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "was_file"; String fname = "was_file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
writeTrashFile(fname, "a"); writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -991,23 +1049,34 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// modify file // modify file
writeTrashFile(fname, "b"); writeTrashFile(fname, "b");
assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/")); assertWorkDir(mkmap(fname, "b", linkName + "/dummyDir", "/"));
recorder.assertNoEvent();
// revert both paths to HEAD state // revert both paths to HEAD state
git.checkout().setStartPoint(Constants.HEAD) git.checkout().setStartPoint(Constants.HEAD).addPath(fname)
.addPath(fname).addPath(linkName).call(); .addPath(linkName).call();
assertWorkDir(mkmap(fname, "a", linkName, "a")); assertWorkDir(mkmap(fname, "a", linkName, "a"));
recorder.assertEvent(new String[] { fname, linkName },
ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception { public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file"; String fname = "file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
writeTrashFile(fname, "a"); writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1035,15 +1104,24 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// 2 extra files are created // 2 extra files are created
assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
linkName + "/dir2/file2", "d")); linkName + "/dir2/file2", "d"));
recorder.assertNoEvent();
// revert path to HEAD state // revert path to HEAD state
git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call(); git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
.call();
// expect only the one added to the index // expect only the one added to the index
assertWorkDir(mkmap(linkName, "a", fname, "a")); assertWorkDir(mkmap(linkName, "a", fname, "a"));
recorder.assertEvent(new String[] { linkName },
ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1051,8 +1129,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
throws Exception { throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file"; String fname = "file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
writeTrashFile(fname, "a"); writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1081,22 +1162,34 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// 2 extra files are created // 2 extra files are created
assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
linkName + "/dir2/file2", "d")); linkName + "/dir2/file2", "d"));
recorder.assertNoEvent();
// revert path to HEAD state // revert path to HEAD state
git.checkout().setStartPoint(Constants.HEAD).addPath(linkName).call(); git.checkout().setStartPoint(Constants.HEAD).addPath(linkName)
.call();
// original file and link // original file and link
assertWorkDir(mkmap(linkName, "a", fname, "a")); assertWorkDir(mkmap(linkName, "a", fname, "a"));
recorder.assertEvent(new String[] { linkName },
ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
public void testCheckoutChangeFileToEmptyDir() throws Exception { public void testCheckoutChangeFileToEmptyDir() throws Exception {
String fname = "was_file"; String fname = "was_file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
File file = writeTrashFile(fname, "a"); File file = writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1106,23 +1199,31 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
FileUtils.delete(file); FileUtils.delete(file);
FileUtils.mkdir(file); FileUtils.mkdir(file);
assertTrue("File must be a directory now", file.isDirectory()); assertTrue("File must be a directory now", file.isDirectory());
assertWorkDir(mkmap(fname, "/")); assertWorkDir(mkmap(fname, "/"));
recorder.assertNoEvent();
// revert path to HEAD state // revert path to HEAD state
git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
assertWorkDir(mkmap(fname, "a")); assertWorkDir(mkmap(fname, "a"));
recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
public void testCheckoutChangeFileToEmptyDirs() throws Exception { public void testCheckoutChangeFileToEmptyDirs() throws Exception {
String fname = "was_file"; String fname = "was_file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
File file = writeTrashFile(fname, "a"); File file = writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1135,21 +1236,30 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertFalse("Must not delete non empty directory", file.delete()); assertFalse("Must not delete non empty directory", file.delete());
assertWorkDir(mkmap(fname + "/dummyDir", "/")); assertWorkDir(mkmap(fname + "/dummyDir", "/"));
recorder.assertNoEvent();
// revert path to HEAD state // revert path to HEAD state
git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
assertWorkDir(mkmap(fname, "a")); assertWorkDir(mkmap(fname, "a"));
recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
public void testCheckoutChangeFileToNonEmptyDirs() throws Exception { public void testCheckoutChangeFileToNonEmptyDirs() throws Exception {
String fname = "was_file"; String fname = "was_file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
File file = writeTrashFile(fname, "a"); File file = writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1170,25 +1280,35 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertFalse("Must not delete non empty directory", file.delete()); assertFalse("Must not delete non empty directory", file.delete());
// 2 extra files are created // 2 extra files are created
assertWorkDir( assertWorkDir(mkmap(fname + "/dir1/file1", "c",
mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d")); fname + "/dir2/file2", "d"));
recorder.assertNoEvent();
// revert path to HEAD state // revert path to HEAD state
git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
// expect only the one added to the index // expect only the one added to the index
assertWorkDir(mkmap(fname, "a")); assertWorkDir(mkmap(fname, "a"));
recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry() public void testCheckoutChangeFileToNonEmptyDirsAndNewIndexEntry()
throws Exception { throws Exception {
String fname = "was_file"; String fname = "was_file";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
File file = writeTrashFile(fname, "a"); File file = writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1210,15 +1330,21 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertFalse("Must not delete non empty directory", file.delete()); assertFalse("Must not delete non empty directory", file.delete());
// 2 extra files are created // 2 extra files are created
assertWorkDir( assertWorkDir(mkmap(fname + "/dir/file1", "c", fname + "/dir/file2",
mkmap(fname + "/dir/file1", "c", fname + "/dir/file2", "d")); "d"));
recorder.assertNoEvent();
// revert path to HEAD state // revert path to HEAD state
git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call(); git.checkout().setStartPoint(Constants.HEAD).addPath(fname).call();
assertWorkDir(mkmap(fname, "a")); assertWorkDir(mkmap(fname, "a"));
recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
Status st = git.status().call(); Status st = git.status().call();
assertTrue(st.isClean()); assertTrue(st.isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1293,8 +1419,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
public void testOverwriteUntrackedIgnoredFile() throws IOException, public void testOverwriteUntrackedIgnoredFile() throws IOException,
GitAPIException { GitAPIException {
String fname="file.txt"; String fname="file.txt";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
writeTrashFile(fname, "a"); writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1307,26 +1436,40 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
writeTrashFile(fname, "b"); writeTrashFile(fname, "b");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
git.commit().setMessage("modify file").call(); git.commit().setMessage("modify file").call();
recorder.assertNoEvent();
// Switch branches // Switch branches
git.checkout().setName("side").call(); git.checkout().setName("side").call();
recorder.assertEvent(new String[] { fname }, ChangeRecorder.EMPTY);
git.rm().addFilepattern(fname).call(); git.rm().addFilepattern(fname).call();
recorder.assertEvent(ChangeRecorder.EMPTY, new String[] { fname });
writeTrashFile(".gitignore", fname); writeTrashFile(".gitignore", fname);
git.add().addFilepattern(".gitignore").call(); git.add().addFilepattern(".gitignore").call();
git.commit().setMessage("delete and ignore file").call(); git.commit().setMessage("delete and ignore file").call();
writeTrashFile(fname, "Something different"); writeTrashFile(fname, "Something different");
recorder.assertNoEvent();
git.checkout().setName("master").call(); git.checkout().setName("master").call();
assertWorkDir(mkmap(fname, "b")); assertWorkDir(mkmap(fname, "b"));
recorder.assertEvent(new String[] { fname },
new String[] { ".gitignore" });
assertTrue(git.status().call().isClean()); assertTrue(git.status().call().isClean());
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
public void testOverwriteUntrackedFileModeChange() public void testOverwriteUntrackedFileModeChange()
throws IOException, GitAPIException { throws IOException, GitAPIException {
String fname = "file.txt"; String fname = "file.txt";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
File file = writeTrashFile(fname, "a"); File file = writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1338,6 +1481,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// Switch branches // Switch branches
git.checkout().setName("side").call(); git.checkout().setName("side").call();
recorder.assertNoEvent();
// replace file with directory containing files // replace file with directory containing files
FileUtils.delete(file); FileUtils.delete(file);
@ -1353,8 +1497,8 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertFalse("Must not delete non empty directory", file.delete()); assertFalse("Must not delete non empty directory", file.delete());
// 2 extra files are created // 2 extra files are created
assertWorkDir( assertWorkDir(mkmap(fname + "/dir1/file1", "c",
mkmap(fname + "/dir1/file1", "c", fname + "/dir2/file2", "d")); fname + "/dir2/file2", "d"));
try { try {
git.checkout().setName("master").call(); git.checkout().setName("master").call();
@ -1364,6 +1508,12 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertWorkDir(mkmap(fname + "/dir1/file1", "c", assertWorkDir(mkmap(fname + "/dir1/file1", "c",
fname + "/dir2/file2", "d")); fname + "/dir2/file2", "d"));
} }
recorder.assertNoEvent();
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1371,8 +1521,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
throws Exception { throws Exception {
Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); Assume.assumeTrue(FS.DETECTED.supportsSymlinks());
String fname = "file.txt"; String fname = "file.txt";
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add a file // Add a file
writeTrashFile(fname, "a"); writeTrashFile(fname, "a");
git.add().addFilepattern(fname).call(); git.add().addFilepattern(fname).call();
@ -1390,6 +1543,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// Switch branches // Switch branches
git.checkout().setName("side").call(); git.checkout().setName("side").call();
recorder.assertNoEvent();
// replace link with directory containing files // replace link with directory containing files
FileUtils.delete(link); FileUtils.delete(link);
@ -1416,6 +1570,12 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c", assertWorkDir(mkmap(fname, "a", linkName + "/dir1/file1", "c",
linkName + "/dir2/file2", "d")); linkName + "/dir2/file2", "d"));
} }
recorder.assertNoEvent();
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1423,8 +1583,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute()) if (!FS.DETECTED.supportsExecute())
return; return;
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add non-executable file // Add non-executable file
File file = writeTrashFile("file.txt", "a"); File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call(); git.add().addFilepattern("file.txt").call();
@ -1438,6 +1601,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
db.getFS().setExecute(file, true); db.getFS().setExecute(file, true);
git.add().addFilepattern("file.txt").call(); git.add().addFilepattern("file.txt").call();
git.commit().setMessage("commit2").call(); git.commit().setMessage("commit2").call();
recorder.assertNoEvent();
// Verify executable and working directory is clean // Verify executable and working directory is clean
Status status = git.status().call(); Status status = git.status().call();
@ -1453,6 +1617,13 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertTrue(status.getModified().isEmpty()); assertTrue(status.getModified().isEmpty());
assertTrue(status.getChanged().isEmpty()); assertTrue(status.getChanged().isEmpty());
assertFalse(db.getFS().canExecute(file)); assertFalse(db.getFS().canExecute(file));
recorder.assertEvent(new String[] { "file.txt" },
ChangeRecorder.EMPTY);
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1460,8 +1631,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute()) if (!FS.DETECTED.supportsExecute())
return; return;
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add non-executable file // Add non-executable file
File file = writeTrashFile("file.txt", "a"); File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call(); git.add().addFilepattern("file.txt").call();
@ -1496,6 +1670,12 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
assertEquals(1, result.getConflictList().size()); assertEquals(1, result.getConflictList().size());
assertTrue(result.getConflictList().contains("file.txt")); assertTrue(result.getConflictList().contains("file.txt"));
} }
recorder.assertNoEvent();
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1504,8 +1684,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute()) if (!FS.DETECTED.supportsExecute())
return; return;
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add non-executable file // Add non-executable file
File file = writeTrashFile("file.txt", "a"); File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call(); git.add().addFilepattern("file.txt").call();
@ -1532,12 +1715,21 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
"[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]", "[file.txt, mode:100755, content:a][file2.txt, mode:100644, content:]",
indexState(CONTENT)); indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "b", "file2.txt", "")); assertWorkDir(mkmap("file.txt", "b", "file2.txt", ""));
recorder.assertNoEvent();
// Switch branches and check that the dirty file survived in worktree // Switch branches and check that the dirty file survived in
// and index // worktree and index
git.checkout().setName("b1").call(); git.checkout().setName("b1").call();
assertEquals("[file.txt, mode:100755, content:a]", indexState(CONTENT)); assertEquals("[file.txt, mode:100755, content:a]",
indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "b")); assertWorkDir(mkmap("file.txt", "b"));
recorder.assertEvent(ChangeRecorder.EMPTY,
new String[] { "file2.txt" });
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1546,8 +1738,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute()) if (!FS.DETECTED.supportsExecute())
return; return;
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add non-executable file // Add non-executable file
File file = writeTrashFile("file.txt", "a"); File file = writeTrashFile("file.txt", "a");
git.add().addFilepattern("file.txt").call(); git.add().addFilepattern("file.txt").call();
@ -1572,14 +1767,24 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
writeTrashFile("file.txt", "c"); writeTrashFile("file.txt", "c");
db.getFS().setExecute(file, true); db.getFS().setExecute(file, true);
assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT)); assertEquals("[file.txt, mode:100644, content:a]",
indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "c")); assertWorkDir(mkmap("file.txt", "c"));
recorder.assertNoEvent();
// Switch branches and check that the dirty file survived in worktree // Switch branches and check that the dirty file survived in
// worktree
// and index // and index
git.checkout().setName("b1").call(); git.checkout().setName("b1").call();
assertEquals("[file.txt, mode:100644, content:a]", indexState(CONTENT)); assertEquals("[file.txt, mode:100644, content:a]",
indexState(CONTENT));
assertWorkDir(mkmap("file.txt", "c")); assertWorkDir(mkmap("file.txt", "c"));
recorder.assertNoEvent();
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test @Test
@ -1587,8 +1792,11 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
if (!FS.DETECTED.supportsExecute()) if (!FS.DETECTED.supportsExecute())
return; return;
Git git = Git.wrap(db); ChangeRecorder recorder = new ChangeRecorder();
ListenerHandle handle = null;
try (Git git = new Git(db)) {
handle = db.getListenerList()
.addWorkingTreeModifiedListener(recorder);
// Add first file // Add first file
File file1 = writeTrashFile("file1.txt", "a"); File file1 = writeTrashFile("file1.txt", "a");
git.add().addFilepattern("file1.txt").call(); git.add().addFilepattern("file1.txt").call();
@ -1600,10 +1808,13 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
git.add().addFilepattern("file2.txt").call(); git.add().addFilepattern("file2.txt").call();
git.commit().setMessage("commit2").call(); git.commit().setMessage("commit2").call();
assertFalse(db.getFS().canExecute(file2)); assertFalse(db.getFS().canExecute(file2));
recorder.assertNoEvent();
// Create branch from first commit // Create branch from first commit
assertNotNull(git.checkout().setCreateBranch(true).setName("b1") assertNotNull(git.checkout().setCreateBranch(true).setName("b1")
.setStartPoint(Constants.HEAD + "~1").call()); .setStartPoint(Constants.HEAD + "~1").call());
recorder.assertEvent(ChangeRecorder.EMPTY,
new String[] { "file2.txt" });
// Change content and file mode in working directory and index // Change content and file mode in working directory and index
file1 = writeTrashFile("file1.txt", "c"); file1 = writeTrashFile("file1.txt", "c");
@ -1612,6 +1823,13 @@ public class DirCacheCheckoutTest extends RepositoryTestCase {
// Switch back to 'master' // Switch back to 'master'
assertNotNull(git.checkout().setName(Constants.MASTER).call()); assertNotNull(git.checkout().setName(Constants.MASTER).call());
recorder.assertEvent(new String[] { "file2.txt" },
ChangeRecorder.EMPTY);
} finally {
if (handle != null) {
handle.remove();
}
}
} }
@Test(expected = CheckoutConflictException.class) @Test(expected = CheckoutConflictException.class)

27
org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java

@ -47,8 +47,10 @@ import java.io.IOException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set;
import org.eclipse.jgit.api.CheckoutResult.Status; import org.eclipse.jgit.api.CheckoutResult.Status;
import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.CheckoutConflictException;
@ -66,6 +68,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.errors.UnmergedPathException;
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 +178,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
private boolean checkoutAllPaths; private boolean checkoutAllPaths;
private Set<String> actuallyModifiedPaths;
/** /**
* @param repo * @param repo
*/ */
@ -410,7 +415,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
} }
/** /**
* Checkout paths into index and working directory * Checkout paths into index and working directory, firing a
* {@link WorkingTreeModifiedEvent} if the working tree was modified.
* *
* @return this instance * @return this instance
* @throws IOException * @throws IOException
@ -418,6 +424,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
*/ */
protected CheckoutCommand checkoutPaths() throws IOException, protected CheckoutCommand checkoutPaths() throws IOException,
RefNotFoundException { RefNotFoundException {
actuallyModifiedPaths = new HashSet<>();
DirCache dc = repo.lockDirCache(); DirCache dc = repo.lockDirCache();
try (RevWalk revWalk = new RevWalk(repo); try (RevWalk revWalk = new RevWalk(repo);
TreeWalk treeWalk = new TreeWalk(repo, TreeWalk treeWalk = new TreeWalk(repo,
@ -432,7 +439,16 @@ public class CheckoutCommand extends GitCommand<Ref> {
checkoutPathsFromCommit(treeWalk, dc, commit); checkoutPathsFromCommit(treeWalk, dc, commit);
} }
} finally { } finally {
try {
dc.unlock(); dc.unlock();
} finally {
WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
actuallyModifiedPaths, null);
actuallyModifiedPaths = null;
if (!event.isEmpty()) {
repo.fireEvent(event);
}
}
} }
return this; return this;
} }
@ -461,9 +477,11 @@ public class CheckoutCommand extends GitCommand<Ref> {
int stage = ent.getStage(); int stage = ent.getStage();
if (stage > DirCacheEntry.STAGE_0) { if (stage > DirCacheEntry.STAGE_0) {
if (checkoutStage != null) { if (checkoutStage != null) {
if (stage == checkoutStage.number) if (stage == checkoutStage.number) {
checkoutPath(ent, r, new CheckoutMetadata( checkoutPath(ent, r, new CheckoutMetadata(
eolStreamType, filterCommand)); eolStreamType, filterCommand));
actuallyModifiedPaths.add(path);
}
} else { } else {
UnmergedPathException e = new UnmergedPathException( UnmergedPathException e = new UnmergedPathException(
ent); ent);
@ -472,6 +490,7 @@ public class CheckoutCommand extends GitCommand<Ref> {
} else { } else {
checkoutPath(ent, r, new CheckoutMetadata(eolStreamType, checkoutPath(ent, r, new CheckoutMetadata(eolStreamType,
filterCommand)); filterCommand));
actuallyModifiedPaths.add(path);
} }
} }
}); });
@ -492,13 +511,15 @@ public class CheckoutCommand extends GitCommand<Ref> {
final EolStreamType eolStreamType = treeWalk.getEolStreamType(); final EolStreamType eolStreamType = treeWalk.getEolStreamType();
final String filterCommand = treeWalk final String filterCommand = treeWalk
.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE); .getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE);
editor.add(new PathEdit(treeWalk.getPathString()) { final String path = treeWalk.getPathString();
editor.add(new PathEdit(path) {
@Override @Override
public void apply(DirCacheEntry ent) { public void apply(DirCacheEntry ent) {
ent.setObjectId(blobId); ent.setObjectId(blobId);
ent.setFileMode(mode); ent.setFileMode(mode);
checkoutPath(ent, r, checkoutPath(ent, r,
new CheckoutMetadata(eolStreamType, filterCommand)); new CheckoutMetadata(eolStreamType, filterCommand));
actuallyModifiedPaths.add(path);
} }
}); });
} }

5
org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java

@ -54,6 +54,7 @@ import java.util.TreeSet;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
@ -135,6 +136,10 @@ public class CleanCommand extends GitCommand<Set<String>> {
} }
} catch (IOException e) { } catch (IOException e) {
throw new JGitInternalException(e.getMessage(), e); throw new JGitInternalException(e.getMessage(), e);
} finally {
if (!files.isEmpty()) {
repo.fireEvent(new WorkingTreeModifiedEvent(null, files));
}
} }
return files; return files;
} }

5
org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java

@ -64,6 +64,7 @@ import org.eclipse.jgit.api.errors.NoHeadException;
import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.NoMessageException;
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.Config.ConfigEnum; import org.eclipse.jgit.lib.Config.ConfigEnum;
@ -355,6 +356,10 @@ public class MergeCommand extends GitCommand<MergeResult> {
.getMergeResults(); .getMergeResults();
failingPaths = resolveMerger.getFailingPaths(); failingPaths = resolveMerger.getFailingPaths();
unmergedPaths = resolveMerger.getUnmergedPaths(); unmergedPaths = resolveMerger.getUnmergedPaths();
if (!resolveMerger.getModifiedFiles().isEmpty()) {
repo.fireEvent(new WorkingTreeModifiedEvent(
resolveMerger.getModifiedFiles(), null));
}
} else } else
noProblems = merger.merge(headCommit, srcCommit); noProblems = merger.merge(headCommit, srcCommit);
refLogMessage.append(": Merge made by "); //$NON-NLS-1$ refLogMessage.append(": Merge made by "); //$NON-NLS-1$

29
org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java

@ -44,8 +44,10 @@ package org.eclipse.jgit.api;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
@ -53,6 +55,7 @@ import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.FileMode;
@ -145,6 +148,7 @@ public class RmCommand extends GitCommand<DirCache> {
checkCallable(); checkCallable();
DirCache dc = null; DirCache dc = null;
List<String> actuallyDeletedFiles = new ArrayList<>();
try (final TreeWalk tw = new TreeWalk(repo)) { try (final TreeWalk tw = new TreeWalk(repo)) {
dc = repo.lockDirCache(); dc = repo.lockDirCache();
DirCacheBuilder builder = dc.builder(); DirCacheBuilder builder = dc.builder();
@ -157,11 +161,14 @@ public class RmCommand extends GitCommand<DirCache> {
if (!cached) { if (!cached) {
final FileMode mode = tw.getFileMode(0); final FileMode mode = tw.getFileMode(0);
if (mode.getObjectType() == Constants.OBJ_BLOB) { if (mode.getObjectType() == Constants.OBJ_BLOB) {
String relativePath = tw.getPathString();
final File path = new File(repo.getWorkTree(), final File path = new File(repo.getWorkTree(),
tw.getPathString()); relativePath);
// Deleting a blob is simply a matter of removing // Deleting a blob is simply a matter of removing
// the file or symlink named by the tree entry. // the file or symlink named by the tree entry.
delete(path); if (delete(path)) {
actuallyDeletedFiles.add(relativePath);
}
} }
} }
} }
@ -171,16 +178,28 @@ public class RmCommand extends GitCommand<DirCache> {
throw new JGitInternalException( throw new JGitInternalException(
JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e); JGitText.get().exceptionCaughtDuringExecutionOfRmCommand, e);
} finally { } finally {
if (dc != null) try {
if (dc != null) {
dc.unlock(); dc.unlock();
} }
} finally {
if (!actuallyDeletedFiles.isEmpty()) {
repo.fireEvent(new WorkingTreeModifiedEvent(null,
actuallyDeletedFiles));
}
}
}
return dc; return dc;
} }
private void delete(File p) { private boolean delete(File p) {
while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) boolean deleted = false;
while (p != null && !p.equals(repo.getWorkTree()) && p.delete()) {
deleted = true;
p = p.getParentFile(); p = p.getParentFile();
} }
return deleted;
}
} }

21
org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012, GitHub Inc. * Copyright (C) 2012, 2017 GitHub Inc.
* and other copyright owners as documented in the project's IP log. * and other copyright owners as documented in the project's IP log.
* *
* This program and the accompanying materials are made available * This program and the accompanying materials are made available
@ -44,6 +44,9 @@ package org.eclipse.jgit.api;
import java.io.IOException; import java.io.IOException;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.InvalidRefNameException;
@ -58,6 +61,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout.CheckoutMetadata;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.errors.CheckoutConflictException;
import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.EolStreamType; import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
@ -198,7 +202,13 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
"stash" }); //$NON-NLS-1$ "stash" }); //$NON-NLS-1$
merger.setBase(stashHeadCommit); merger.setBase(stashHeadCommit);
merger.setWorkingTreeIterator(new FileTreeIterator(repo)); merger.setWorkingTreeIterator(new FileTreeIterator(repo));
if (merger.merge(headCommit, stashCommit)) { boolean mergeSucceeded = merger.merge(headCommit, stashCommit);
List<String> modifiedByMerge = merger.getModifiedFiles();
if (!modifiedByMerge.isEmpty()) {
repo.fireEvent(
new WorkingTreeModifiedEvent(modifiedByMerge, null));
}
if (mergeSucceeded) {
DirCache dc = repo.lockDirCache(); DirCache dc = repo.lockDirCache();
DirCacheCheckout dco = new DirCacheCheckout(repo, headTree, DirCacheCheckout dco = new DirCacheCheckout(repo, headTree,
dc, merger.getResultTreeId()); dc, merger.getResultTreeId());
@ -329,6 +339,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
private void resetUntracked(RevTree tree) throws CheckoutConflictException, private void resetUntracked(RevTree tree) throws CheckoutConflictException,
IOException { IOException {
Set<String> actuallyModifiedPaths = new HashSet<>();
// TODO maybe NameConflictTreeWalk ? // TODO maybe NameConflictTreeWalk ?
try (TreeWalk walk = new TreeWalk(repo)) { try (TreeWalk walk = new TreeWalk(repo)) {
walk.addTree(tree); walk.addTree(tree);
@ -361,6 +372,12 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
checkoutPath(entry, reader, checkoutPath(entry, reader,
new CheckoutMetadata(eolStreamType, null)); new CheckoutMetadata(eolStreamType, null));
actuallyModifiedPaths.add(entry.getPathString());
}
} finally {
if (!actuallyModifiedPaths.isEmpty()) {
repo.fireEvent(new WorkingTreeModifiedEvent(
actuallyModifiedPaths, null));
} }
} }
} }

11
org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java

@ -62,6 +62,7 @@ import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.errors.UnmergedPathException;
import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -240,6 +241,7 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
public RevCommit call() throws GitAPIException { public RevCommit call() throws GitAPIException {
checkCallable(); checkCallable();
List<String> deletedFiles = new ArrayList<>();
Ref head = getHead(); Ref head = getHead();
try (ObjectReader reader = repo.newObjectReader()) { try (ObjectReader reader = repo.newObjectReader()) {
RevCommit headCommit = parseCommit(reader, head.getObjectId()); RevCommit headCommit = parseCommit(reader, head.getObjectId());
@ -377,9 +379,11 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
// Remove untracked files // Remove untracked files
if (includeUntracked) { if (includeUntracked) {
for (DirCacheEntry entry : untracked) { for (DirCacheEntry entry : untracked) {
String repoRelativePath = entry.getPathString();
File file = new File(repo.getWorkTree(), File file = new File(repo.getWorkTree(),
entry.getPathString()); repoRelativePath);
FileUtils.delete(file); FileUtils.delete(file);
deletedFiles.add(repoRelativePath);
} }
} }
@ -394,6 +398,11 @@ public class StashCreateCommand extends GitCommand<RevCommit> {
return parseCommit(reader, commitId); return parseCommit(reader, commitId);
} catch (IOException e) { } catch (IOException e) {
throw new JGitInternalException(JGitText.get().stashFailed, e); throw new JGitInternalException(JGitText.get().stashFailed, e);
} finally {
if (!deletedFiles.isEmpty()) {
repo.fireEvent(
new WorkingTreeModifiedEvent(null, deletedFiles));
}
} }
} }
} }

84
org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java vendored

@ -50,6 +50,7 @@ import java.nio.file.StandardCopyOption;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -61,6 +62,7 @@ import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.IndexWriteException; import org.eclipse.jgit.errors.IndexWriteException;
import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
@ -85,6 +87,7 @@ import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FS.ExecutionResult;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IntList;
import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.RawParseUtils;
import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.EolStreamTypeUtil; import org.eclipse.jgit.util.io.EolStreamTypeUtil;
@ -151,6 +154,8 @@ public class DirCacheCheckout {
private boolean emptyDirCache; private boolean emptyDirCache;
private boolean performingCheckout;
/** /**
* @return a list of updated paths and smudgeFilterCommands * @return a list of updated paths and smudgeFilterCommands
*/ */
@ -432,7 +437,8 @@ public class DirCacheCheckout {
} }
/** /**
* Execute this checkout * Execute this checkout. A {@link WorkingTreeModifiedEvent} is fired if the
* working tree was modified; even if the checkout fails.
* *
* @return <code>false</code> if this method could not delete all the files * @return <code>false</code> if this method could not delete all the files
* which should be deleted (e.g. because of of the files was * which should be deleted (e.g. because of of the files was
@ -448,7 +454,17 @@ public class DirCacheCheckout {
try { try {
return doCheckout(); return doCheckout();
} finally { } finally {
try {
dc.unlock(); dc.unlock();
} finally {
if (performingCheckout) {
WorkingTreeModifiedEvent event = new WorkingTreeModifiedEvent(
getUpdated().keySet(), getRemoved());
if (!event.isEmpty()) {
repo.fireEvent(event);
}
}
}
} }
} }
@ -472,11 +488,13 @@ public class DirCacheCheckout {
// update our index // update our index
builder.finish(); builder.finish();
performingCheckout = true;
File file = null; File file = null;
String last = null; String last = null;
// when deleting files process them in the opposite order as they have // when deleting files process them in the opposite order as they have
// been reported. This ensures the files are deleted before we delete // been reported. This ensures the files are deleted before we delete
// their parent folders // their parent folders
IntList nonDeleted = new IntList();
for (int i = removed.size() - 1; i >= 0; i--) { for (int i = removed.size() - 1; i >= 0; i--) {
String r = removed.get(i); String r = removed.get(i);
file = new File(repo.getWorkTree(), r); file = new File(repo.getWorkTree(), r);
@ -486,25 +504,47 @@ public class DirCacheCheckout {
// a submodule, in which case we shall not attempt // a submodule, in which case we shall not attempt
// to delete it. A submodule is not empty, so it // to delete it. A submodule is not empty, so it
// is safe to check this after a failed delete. // is safe to check this after a failed delete.
if (!repo.getFS().isDirectory(file)) if (!repo.getFS().isDirectory(file)) {
nonDeleted.add(i);
toBeDeleted.add(r); toBeDeleted.add(r);
}
} else { } else {
if (last != null && !isSamePrefix(r, last)) if (last != null && !isSamePrefix(r, last))
removeEmptyParents(new File(repo.getWorkTree(), last)); removeEmptyParents(new File(repo.getWorkTree(), last));
last = r; last = r;
} }
} }
if (file != null) if (file != null) {
removeEmptyParents(file); removeEmptyParents(file);
}
for (Map.Entry<String, CheckoutMetadata> e : updated.entrySet()) { removed = filterOut(removed, nonDeleted);
nonDeleted = null;
Iterator<Map.Entry<String, CheckoutMetadata>> toUpdate = updated
.entrySet().iterator();
Map.Entry<String, CheckoutMetadata> e = null;
try {
while (toUpdate.hasNext()) {
e = toUpdate.next();
String path = e.getKey(); String path = e.getKey();
CheckoutMetadata meta = e.getValue(); CheckoutMetadata meta = e.getValue();
DirCacheEntry entry = dc.getEntry(path); DirCacheEntry entry = dc.getEntry(path);
if (!FileMode.GITLINK.equals(entry.getRawMode())) if (!FileMode.GITLINK.equals(entry.getRawMode())) {
checkoutEntry(repo, entry, objectReader, false, meta); checkoutEntry(repo, entry, objectReader, false, meta);
} }
e = null;
}
} catch (Exception ex) {
// We didn't actually modify the current entry nor any that
// might follow.
if (e != null) {
toUpdate.remove();
}
while (toUpdate.hasNext()) {
e = toUpdate.next();
toUpdate.remove();
}
throw ex;
}
// commit the index builder - a new index is persisted // commit the index builder - a new index is persisted
if (!builder.commit()) if (!builder.commit())
throw new IndexWriteException(); throw new IndexWriteException();
@ -512,6 +552,36 @@ public class DirCacheCheckout {
return toBeDeleted.size() == 0; return toBeDeleted.size() == 0;
} }
private static ArrayList<String> filterOut(ArrayList<String> strings,
IntList indicesToRemove) {
int n = indicesToRemove.size();
if (n == strings.size()) {
return new ArrayList<>(0);
}
switch (n) {
case 0:
return strings;
case 1:
strings.remove(indicesToRemove.get(0));
return strings;
default:
int length = strings.size();
ArrayList<String> result = new ArrayList<>(length - n);
// Process indicesToRemove from the back; we know that it
// contains indices in descending order.
int j = n - 1;
int idx = indicesToRemove.get(j);
for (int i = 0; i < length; i++) {
if (i == idx) {
idx = (--j >= 0) ? indicesToRemove.get(j) : -1;
} else {
result.add(strings.get(i));
}
}
return result;
}
}
private static boolean isSamePrefix(String a, String b) { private static boolean isSamePrefix(String a, String b) {
int as = a.lastIndexOf('/'); int as = a.lastIndexOf('/');
int bs = b.lastIndexOf('/'); int bs = b.lastIndexOf('/');

13
org.eclipse.jgit/src/org/eclipse/jgit/events/ListenerList.java

@ -52,6 +52,19 @@ import java.util.concurrent.CopyOnWriteArrayList;
public class ListenerList { public class ListenerList {
private final ConcurrentMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>> lists = new ConcurrentHashMap<>(); private final ConcurrentMap<Class<? extends RepositoryListener>, CopyOnWriteArrayList<ListenerHandle>> lists = new ConcurrentHashMap<>();
/**
* Register a {@link WorkingTreeModifiedListener}.
*
* @param listener
* the listener implementation.
* @return handle to later remove the listener.
* @since 4.9
*/
public ListenerHandle addWorkingTreeModifiedListener(
WorkingTreeModifiedListener listener) {
return addListener(WorkingTreeModifiedListener.class, listener);
}
/** /**
* Register an IndexChangedListener. * Register an IndexChangedListener.
* *

129
org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedEvent.java

@ -0,0 +1,129 @@
/*
* Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
* 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.events;
import java.util.Collections;
import java.util.Collection;
import org.eclipse.jgit.annotations.NonNull;
/**
* A {@link RepositoryEvent} describing changes to the working tree. It is fired
* whenever a {@link org.eclipse.jgit.dircache.DirCacheCheckout} modifies
* (adds/deletes/updates) files in the working tree.
*
* @since 4.9
*/
public class WorkingTreeModifiedEvent
extends RepositoryEvent<WorkingTreeModifiedListener> {
private Collection<String> modified;
private Collection<String> deleted;
/**
* Creates a new {@link WorkingTreeModifiedEvent} with the given
* collections.
*
* @param modified
* repository-relative paths that were added or updated
* @param deleted
* repository-relative paths that were deleted
*/
public WorkingTreeModifiedEvent(Collection<String> modified,
Collection<String> deleted) {
this.modified = modified;
this.deleted = deleted;
}
/**
* Determines whether there are any changes recorded in this event.
*
* @return {@code true} if no files were modified or deleted, {@code false}
* otherwise
*/
public boolean isEmpty() {
return (modified == null || modified.isEmpty())
&& (deleted == null || deleted.isEmpty());
}
/**
* Retrieves the {@link Collection} of repository-relative paths of files
* that were modified (added or updated).
*
* @return the set
*/
public @NonNull Collection<String> getModified() {
Collection<String> result = modified;
if (result == null) {
result = Collections.emptyList();
modified = result;
}
return result;
}
/**
* Retrieves the {@link Collection} of repository-relative paths of files
* that were deleted.
*
* @return the set
*/
public @NonNull Collection<String> getDeleted() {
Collection<String> result = deleted;
if (result == null) {
result = Collections.emptyList();
deleted = result;
}
return result;
}
@Override
public Class<WorkingTreeModifiedListener> getListenerType() {
return WorkingTreeModifiedListener.class;
}
@Override
public void dispatch(WorkingTreeModifiedListener listener) {
listener.onWorkingTreeModified(this);
}
}

61
org.eclipse.jgit/src/org/eclipse/jgit/events/WorkingTreeModifiedListener.java

@ -0,0 +1,61 @@
/*
* Copyright (C) 2017, Thomas Wolf <thomas.wolf@paranor.ch>
* 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.events;
/**
* Receives {@link WorkingTreeModifiedEvent}s, which are fired whenever a
* {@link org.eclipse.jgit.dircache.DirCacheCheckout} modifies
* (adds/deletes/updates) files in the working tree.
*
* @since 4.9
*/
public interface WorkingTreeModifiedListener extends RepositoryListener {
/**
* Respond to working tree modifications.
*
* @param event
*/
void onWorkingTreeModified(WorkingTreeModifiedEvent event);
}
Loading…
Cancel
Save