diff options
89 files changed, 2740 insertions, 1040 deletions
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java index c6fe4e4d91..1079d98439 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java @@ -46,8 +46,10 @@ package org.eclipse.jgit.junit; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Method; @@ -124,13 +126,44 @@ public abstract class JGitTestUtil { // loaded previously return new File("tst", fileName); } + if ("jar".equals(url.getProtocol())) { + try { + File tmp = File.createTempFile("tmp_", "_" + fileName); + copyTestResource(fileName, tmp); + return tmp; + } catch (IOException err) { + throw new RuntimeException("Cannot create temporary file", err); + } + } try { return new File(url.toURI()); - } catch(URISyntaxException e) { + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(e.getMessage() + " " + url); + } catch (URISyntaxException e) { return new File(url.getPath()); } } + public static void copyTestResource(String name, File dest) + throws IOException { + URL url = cl().getResource(CLASSPATH_TO_RESOURCES + name); + if (url == null) + throw new FileNotFoundException(name); + InputStream in = url.openStream(); + try { + FileOutputStream out = new FileOutputStream(dest); + try { + byte[] buf = new byte[4096]; + for (int n; (n = in.read(buf)) > 0;) + out.write(buf, 0, n); + } finally { + out.close(); + } + } finally { + in.close(); + } + } + private static ClassLoader cl() { return JGitTestUtil.class.getClassLoader(); } diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target index 269b90be66..bb8a34c8bc 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.3.target @@ -11,23 +11,23 @@ <target name="jgit.target" sequenceNumber="55"> <locations> <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit"> - <repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.11.v20130520/"/> - <unit id="org.eclipse.jetty.client" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.client.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.continuation" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.continuation.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.http" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.http.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.io" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.io.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.security" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.security.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.server" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.server.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.servlet" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.servlet.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.util" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.util.source" version="7.6.11.v20130520"/> + <repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.14.v20131031/"/> + <unit id="org.eclipse.jetty.client" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.client.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.continuation" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.continuation.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.http" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.http.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.io" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.io.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.security" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.security.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.server" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.server.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.servlet" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.servlet.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.util" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.util.source" version="7.6.14.v20131031"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit"> <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/R20130827064939/repository/"/> diff --git a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target index a8d5d05eb7..f361e42307 100644 --- a/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target +++ b/org.eclipse.jgit.packaging/org.eclipse.jgit.target/jgit-4.4.target @@ -11,23 +11,23 @@ <target name="jgit.target" sequenceNumber="55"> <locations> <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit"> - <repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.11.v20130520/"/> - <unit id="org.eclipse.jetty.client" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.client.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.continuation" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.continuation.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.http" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.http.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.io" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.io.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.security" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.security.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.server" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.server.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.servlet" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.servlet.source" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.util" version="7.6.11.v20130520"/> - <unit id="org.eclipse.jetty.util.source" version="7.6.11.v20130520"/> + <repository location="http://download.eclipse.org/jetty/updates/jetty-bundles-7.x/7.6.14.v20131031/"/> + <unit id="org.eclipse.jetty.client" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.client.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.continuation" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.continuation.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.http" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.http.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.io" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.io.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.security" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.security.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.server" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.server.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.servlet" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.servlet.source" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.util" version="7.6.14.v20131031"/> + <unit id="org.eclipse.jetty.util.source" version="7.6.14.v20131031"/> </location> <location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="slicer" includeSource="true" type="InstallableUnit"> <repository location="http://download.eclipse.org/tools/orbit/downloads/drops/S20131024145017/repository/"/> diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java index 48cb5c54ca..a6ea48c0ea 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java @@ -42,8 +42,11 @@ */ package org.eclipse.jgit.pgm; +import java.io.File; + import org.eclipse.jgit.api.Git; import org.eclipse.jgit.lib.CLIRepositoryTestCase; +import org.eclipse.jgit.util.FileUtils; import org.junit.Assert; import org.junit.Test; @@ -107,6 +110,29 @@ public class CheckoutTest extends CLIRepositoryTestCase { assertEquals("", execute("git checkout HEAD")); } + @Test + public void testCheckoutExistingBranchWithConflict() throws Exception { + Git git = new Git(db); + writeTrashFile("a", "Hello world a"); + git.add().addFilepattern(".").call(); + git.commit().setMessage("commit file a").call(); + git.branchCreate().setName("branch_1").call(); + git.rm().addFilepattern("a").call(); + FileUtils.mkdirs(new File(db.getWorkTree(), "a")); + writeTrashFile("a/b", "Hello world b"); + git.add().addFilepattern("a/b").call(); + git.commit().setMessage("commit folder a").call(); + git.rm().addFilepattern("a").call(); + writeTrashFile("a", "New Hello world a"); + git.add().addFilepattern(".").call(); + + String[] execute = execute("git checkout branch_1"); + Assert.assertEquals( + "error: Your local changes to the following files would be overwritten by checkout:", + execute[0]); + Assert.assertEquals("\ta", execute[1]); + } + static private void assertEquals(String expected, String[] actual) { // if there is more than one line, ignore last one if empty Assert.assertEquals( diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java index 73ae598a8c..acc2be6b43 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java @@ -213,4 +213,122 @@ public class StatusTest extends CLIRepositoryTestCase { "" // }, execute("git status")); // } + + @Test + public void testStatusPorcelain() throws Exception { + Git git = new Git(db); + // Write all files + writeTrashFile("tracked", "tracked"); + writeTrashFile("stagedNew", "stagedNew"); + writeTrashFile("stagedModified", "stagedModified"); + writeTrashFile("stagedDeleted", "stagedDeleted"); + writeTrashFile("trackedModified", "trackedModified"); + writeTrashFile("trackedDeleted", "trackedDeleted"); + writeTrashFile("untracked", "untracked"); + // Test untracked + assertArrayOfLinesEquals(new String[] { // git status output + "?? stagedDeleted", // + "?? stagedModified", // + "?? stagedNew", // + "?? tracked", // + "?? trackedDeleted", // + "?? trackedModified", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Add to index + git.add().addFilepattern("tracked").call(); + git.add().addFilepattern("stagedModified").call(); + git.add().addFilepattern("stagedDeleted").call(); + git.add().addFilepattern("trackedModified").call(); + git.add().addFilepattern("trackedDeleted").call(); + // Test staged count + assertArrayOfLinesEquals(new String[] { // git status output + "A stagedDeleted", // + "A stagedModified", // + "A tracked", // + "A trackedDeleted", // + "A trackedModified", // + "?? stagedNew", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Commit + git.commit().setMessage("initial commit").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? stagedNew", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Make some changes and stage them + writeTrashFile("stagedModified", "stagedModified modified"); + deleteTrashFile("stagedDeleted"); + writeTrashFile("trackedModified", "trackedModified modified"); + deleteTrashFile("trackedDeleted"); + git.add().addFilepattern("stagedModified").call(); + git.rm().addFilepattern("stagedDeleted").call(); + git.add().addFilepattern("stagedNew").call(); + // Test staged/not-staged status + assertArrayOfLinesEquals(new String[] { // git status output + "D stagedDeleted", // + "M stagedModified", // + "A stagedNew", // + " D trackedDeleted", // + " M trackedModified", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Create unmerged file + writeTrashFile("unmerged", "unmerged"); + git.add().addFilepattern("unmerged").call(); + // Commit pending changes + git.add().addFilepattern("trackedModified").call(); + git.rm().addFilepattern("trackedDeleted").call(); + git.commit().setMessage("commit before branching").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Checkout new branch + git.checkout().setCreateBranch(true).setName("test").call(); + // Test branch status + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Commit change and checkout master again + writeTrashFile("unmerged", "changed in test branch"); + git.add().addFilepattern("unmerged").call(); + RevCommit testBranch = git.commit() + .setMessage("changed unmerged in test branch").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + git.checkout().setName("master").call(); + // Change the same file and commit + writeTrashFile("unmerged", "changed in master branch"); + git.add().addFilepattern("unmerged").call(); + git.commit().setMessage("changed unmerged in master branch").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Merge test branch into master + git.merge().include(testBranch.getId()).call(); + // Test unmerged status + assertArrayOfLinesEquals(new String[] { // git status output + "UU unmerged", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Test detached head + String commitId = db.getRef(Constants.MASTER).getObjectId().name(); + git.checkout().setName(commitId).call(); + assertArrayOfLinesEquals(new String[] { // git status output + "UU unmerged", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + } } diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index 1212d93652..d23f378993 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -36,6 +36,8 @@ cantFindGitDirectory=error: can't find git directory cantWrite=Can't write {0} changesNotStagedForCommit=Changes not staged for commit: changesToBeCommitted=Changes to be committed: +checkoutConflict=error: Your local changes to the following files would be overwritten by checkout: +checkoutConflictPathLine=\t{0} commitLabel=commit configFileNotFound=configuration file {0} not found conflictingUsageOf_git_dir_andArguments=conflicting usage of --git-dir and arguments @@ -280,6 +282,7 @@ usage_inputOutputFile=Input/output file usage_listBothRemoteTrackingAndLocalBranches=list both remote-tracking and local branches usage_listCreateOrDeleteBranches=List, create, or delete branches usage_logAllPretty=format:%H %ct %P' output=log --all '--pretty=format:%H %ct %P' output +usage_machineReadableOutput=machine-readable output usage_manageReflogInformation=Manage reflog information usage_mergeFf=When the merge resolves as a fast-forward, only update the branch pointer, without creating a merge commit. usage_mergeNoFf=Create a merge commit even when the merge resolves as a fast-forward. diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java index 8115039729..2f35ecbabd 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Checkout.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2010, 2012 Chris Aniszczyk <caniszczyk@gmail.com> + * Copyright (C) 2013, Obeo * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -47,6 +48,7 @@ import java.text.MessageFormat; import org.eclipse.jgit.api.CheckoutCommand; import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.CheckoutConflictException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.lib.Constants; @@ -107,6 +109,11 @@ class Checkout extends TextBuiltin { } catch (RefAlreadyExistsException e) { throw die(MessageFormat.format(CLIText.get().branchAlreadyExists, name)); + } catch (CheckoutConflictException e) { + outw.println(CLIText.get().checkoutConflict); + for (String path : e.getConflictingPaths()) + outw.println(MessageFormat.format( + CLIText.get().checkoutConflictPathLine, path)); } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java index 0214ed00e0..2ae950bdc5 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java @@ -50,6 +50,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.TreeSet; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.StatusCommand; @@ -71,26 +72,134 @@ class Status extends TextBuiltin { protected final String statusFileListFormatUnmerged = CLIText.get().statusFileListFormatUnmerged; + @Option(name = "--porcelain", usage = "usage_machineReadableOutput") + protected boolean porcelain; + @Option(name = "--", metaVar = "metaVar_path", multiValued = true) protected List<String> filterPaths; @Override protected void run() throws Exception { + StatusCommand statusCommand = new Git(db).status(); + if (filterPaths != null && filterPaths.size() > 0) + for (String path : filterPaths) + statusCommand.addPath(path); + org.eclipse.jgit.api.Status status = statusCommand.call(); + printStatus(status); + } + + private void printStatus(org.eclipse.jgit.api.Status status) + throws IOException { + if (porcelain) + printPorcelainStatus(status); + else + printLongStatus(status); + } + + private void printPorcelainStatus(org.eclipse.jgit.api.Status status) + throws IOException { + + Collection<String> added = status.getAdded(); + Collection<String> changed = status.getChanged(); + Collection<String> removed = status.getRemoved(); + Collection<String> modified = status.getModified(); + Collection<String> missing = status.getMissing(); + Map<String, StageState> conflicting = status.getConflictingStageState(); + + // build a sorted list of all paths except untracked and ignored + TreeSet<String> sorted = new TreeSet<String>(); + sorted.addAll(added); + sorted.addAll(changed); + sorted.addAll(removed); + sorted.addAll(modified); + sorted.addAll(missing); + sorted.addAll(conflicting.keySet()); + + // list each path + for (String path : sorted) { + char x = ' '; + char y = ' '; + + if (added.contains(path)) + x = 'A'; + else if (changed.contains(path)) + x = 'M'; + else if (removed.contains(path)) + x = 'D'; + + if (modified.contains(path)) + y = 'M'; + else if (missing.contains(path)) + y = 'D'; + + if (conflicting.containsKey(path)) { + StageState stageState = conflicting.get(path); + + switch (stageState) { + case BOTH_DELETED: + x = 'D'; + y = 'D'; + break; + case ADDED_BY_US: + x = 'A'; + y = 'U'; + break; + case DELETED_BY_THEM: + x = 'U'; + y = 'D'; + break; + case ADDED_BY_THEM: + x = 'U'; + y = 'A'; + break; + case DELETED_BY_US: + x = 'D'; + y = 'U'; + break; + case BOTH_ADDED: + x = 'A'; + y = 'A'; + break; + case BOTH_MODIFIED: + x = 'U'; + y = 'U'; + break; + default: + throw new IllegalArgumentException("Unknown StageState: " //$NON-NLS-1$ + + stageState); + } + } + + printPorcelainLine(x, y, path); + } + + // untracked are always at the end of the list + TreeSet<String> untracked = new TreeSet<String>(status.getUntracked()); + for (String path : untracked) + printPorcelainLine('?', '?', path); + } + + private void printPorcelainLine(char x, char y, String path) + throws IOException { + StringBuilder lineBuilder = new StringBuilder(); + lineBuilder.append(x).append(y).append(' ').append(path); + outw.println(lineBuilder.toString()); + } + + private void printLongStatus(org.eclipse.jgit.api.Status status) + throws IOException { // Print current branch name final Ref head = db.getRef(Constants.HEAD); - boolean firstHeader = true; if (head != null && head.isSymbolic()) { String branch = Repository.shortenRefName(head.getLeaf().getName()); - outw.println(CLIText.formatLine( - MessageFormat.format(CLIText.get().onBranch, branch))); + outw.println(CLIText.formatLine(MessageFormat.format( + CLIText.get().onBranch, branch))); } else outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch)); + // List changes - StatusCommand statusCommand = new Git(db).status(); - if (filterPaths != null && filterPaths.size() > 0) - for (String path : filterPaths) - statusCommand.addPath(path); - org.eclipse.jgit.api.Status status = statusCommand.call(); + boolean firstHeader = true; + Collection<String> added = status.getAdded(); Collection<String> changed = status.getChanged(); Collection<String> removed = status.getRemoved(); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java index de8675e0fb..a51313ae1e 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2010, 2013 Sasa Zivkov <sasa.zivkov@sap.com> + * Copyright (C) 2013, Obeo * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -106,6 +107,8 @@ public class CLIText extends TranslationBundle { /***/ public String cantWrite; /***/ public String changesNotStagedForCommit; /***/ public String changesToBeCommitted; + /***/ public String checkoutConflict; + /***/ public String checkoutConflictPathLine; /***/ public String commitLabel; /***/ public String conflictingUsageOf_git_dir_andArguments; /***/ public String couldNotCreateBranch; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java index 47ebb6b2a3..098f2b3b5d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java @@ -49,6 +49,7 @@ import java.util.Properties; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.util.GitDateParser; +import org.eclipse.jgit.util.SystemReader; import org.junit.Before; import org.junit.Test; @@ -68,7 +69,8 @@ public class GarbageCollectCommandTest extends RepositoryTestCase { @Test public void testGConeCommit() throws Exception { - Date expire = GitDateParser.parse("now", null); + Date expire = GitDateParser.parse("now", null, SystemReader + .getInstance().getLocale()); Properties res = git.gc().setExpire(expire).call(); assertTrue(res.size() == 7); } @@ -83,8 +85,11 @@ public class GarbageCollectCommandTest extends RepositoryTestCase { writeTrashFile("b.txt", "a couple of words for gc to pack more 2"); writeTrashFile("c.txt", "a couple of words for gc to pack more 3"); git.commit().setAll(true).setMessage("commit3").call(); - Properties res = git.gc().setExpire(GitDateParser.parse("now", null)) - .call(); + Properties res = git + .gc() + .setExpire( + GitDateParser.parse("now", null, SystemReader + .getInstance().getLocale())).call(); assertTrue(res.size() == 7); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java index 8d7758cb7a..64bb8bfa4d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GitConstructionTest.java @@ -45,6 +45,7 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; +import java.io.File; import java.io.IOException; import org.eclipse.jgit.api.ListBranchCommand.ListMode; @@ -53,6 +54,7 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FileUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -123,4 +125,30 @@ public class GitConstructionTest extends RepositoryTestCase { // should not get here } } + + @Test + /** + * Tests that a repository with packfiles can be deleted after calling + * Git.close(). On Windows the first try to delete the worktree will fail + * (because file handles on packfiles are still open) but the second + * attempt after a close will succeed. + * + * @throws IOException + * @throws JGitInternalException + * @throws GitAPIException + */ + public void testClose() throws IOException, JGitInternalException, + GitAPIException { + File workTree = db.getWorkTree(); + Git git = Git.wrap(db); + git.gc().setExpire(null).call(); + git.checkout().setName(git.getRepository().resolve("HEAD^").getName()) + .call(); + try { + FileUtils.delete(workTree, FileUtils.RECURSIVE); + } catch (IOException e) { + git.close(); + FileUtils.delete(workTree, FileUtils.RECURSIVE); + } + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java index 29146dc585..1eeb9f7c0d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java @@ -56,8 +56,11 @@ import org.eclipse.jgit.api.MergeCommand.FastForwardMode; import org.eclipse.jgit.api.MergeResult.MergeStatus; import org.eclipse.jgit.api.errors.InvalidMergeHeadsException; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.merge.MergeStrategy; import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; @@ -1532,6 +1535,37 @@ public class MergeCommandTest extends RepositoryTestCase { assertEquals(MergeStatus.ABORTED, result.getMergeStatus()); } + + @Test + public void testRecursiveMergeWithConflict() throws Exception { + TestRepository<Repository> db_t = new TestRepository<Repository>(db); + BranchBuilder master = db_t.branch("master"); + RevCommit m0 = master.commit().add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9\n") + .message("m0").create(); + RevCommit m1 = master.commit() + .add("f", "1-master\n2\n3\n4\n5\n6\n7\n8\n9\n").message("m1") + .create(); + db_t.getRevWalk().parseCommit(m1); + + BranchBuilder side = db_t.branch("side"); + RevCommit s1 = side.commit().parent(m0) + .add("f", "1\n2\n3\n4\n5\n6\n7\n8\n9-side\n").message("s1") + .create(); + RevCommit s2 = side.commit().parent(m1) + .add("f", "1-master\n2\n3\n4\n5\n6\n7-res(side)\n8\n9-side\n") + .message("s2(merge)").create(); + master.commit().parent(s1) + .add("f", "1-master\n2\n3\n4\n5\n6\n7-conflict\n8\n9-side\n") + .message("m2(merge)").create(); + + Git git = Git.wrap(db); + git.checkout().setName("master").call(); + + MergeResult result = git.merge().setStrategy(MergeStrategy.RECURSIVE) + .include("side", s2).call(); + assertEquals(MergeStatus.CONFLICTING, result.getMergeStatus()); + } + private static void setExecutable(Git git, String path, boolean executable) { FS.DETECTED.setExecute( new File(git.getRepository().getWorkTree(), path), executable); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java index 241d099d19..a61b44eda8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java @@ -68,9 +68,14 @@ import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; +import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.dircache.DirCacheCheckout; +import org.eclipse.jgit.errors.AmbiguousObjectException; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; @@ -80,9 +85,10 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.RepositoryState; import org.eclipse.jgit.merge.MergeStrategy; -import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; @@ -1200,9 +1206,9 @@ public class RebaseCommandTest extends RepositoryTestCase { // rebase RebaseResult result = git.rebase().setUpstream("refs/heads/master") .call(); - assertEquals(Status.CONFLICTS, result.getStatus()); - assertEquals(1, result.getConflicts().size()); - assertEquals("file2", result.getConflicts().get(0)); + assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus()); + assertEquals(1, result.getUncommittedChanges().size()); + assertEquals("file2", result.getUncommittedChanges().get(0)); } @Test @@ -1233,9 +1239,9 @@ public class RebaseCommandTest extends RepositoryTestCase { RebaseResult result = git.rebase().setUpstream("refs/heads/master") .call(); - assertEquals(Status.CONFLICTS, result.getStatus()); - assertEquals(1, result.getConflicts().size()); - assertEquals("file2", result.getConflicts().get(0)); + assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus()); + assertEquals(1, result.getUncommittedChanges().size()); + assertEquals("file2", result.getUncommittedChanges().get(0)); checkFile(uncommittedFile, "uncommitted file2"); assertEquals(RepositoryState.SAFE, git.getRepository().getRepositoryState()); @@ -1268,9 +1274,9 @@ public class RebaseCommandTest extends RepositoryTestCase { // rebase RebaseResult result = git.rebase().setUpstream("refs/heads/master") .call(); - assertEquals(Status.CONFLICTS, result.getStatus()); - assertEquals(1, result.getConflicts().size()); - assertEquals(FILE1, result.getConflicts().get(0)); + assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus()); + assertEquals(1, result.getUncommittedChanges().size()); + assertEquals(FILE1, result.getUncommittedChanges().get(0)); } @Test @@ -1302,9 +1308,9 @@ public class RebaseCommandTest extends RepositoryTestCase { // rebase RebaseResult result = git.rebase().setUpstream("refs/heads/master") .call(); - assertEquals(Status.CONFLICTS, result.getStatus()); - assertEquals(1, result.getConflicts().size()); - assertEquals(FILE1, result.getConflicts().get(0)); + assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus()); + assertEquals(1, result.getUncommittedChanges().size()); + assertEquals(FILE1, result.getUncommittedChanges().get(0)); } @Test @@ -1333,7 +1339,8 @@ public class RebaseCommandTest extends RepositoryTestCase { writeTrashFile("file0", "unstaged modified file0"); // rebase - assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master") + assertEquals(Status.UNCOMMITTED_CHANGES, + git.rebase().setUpstream("refs/heads/master") .call().getStatus()); } @@ -1371,12 +1378,8 @@ public class RebaseCommandTest extends RepositoryTestCase { // rebase RebaseResult result = git.rebase().setUpstream("refs/heads/master") .call(); - assertEquals(Status.FAILED, result.getStatus()); - // staged file0 causes DIRTY_INDEX - assertEquals(1, result.getFailingPaths().size()); - assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths() - .get("file0")); - assertEquals("unstaged modified file0", read(file0)); + assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus()); + assertEquals(1, result.getUncommittedChanges().size()); // index shall be unchanged assertEquals(indexState, indexState(CONTENT)); assertEquals(RepositoryState.SAFE, db.getRepositoryState()); @@ -1412,7 +1415,8 @@ public class RebaseCommandTest extends RepositoryTestCase { writeTrashFile("file0", "unstaged modified file0"); // rebase - assertEquals(Status.OK, git.rebase().setUpstream("refs/heads/master") + assertEquals(Status.UNCOMMITTED_CHANGES, + git.rebase().setUpstream("refs/heads/master") .call().getStatus()); } @@ -1453,11 +1457,9 @@ public class RebaseCommandTest extends RepositoryTestCase { // rebase RebaseResult result = git.rebase().setUpstream("refs/heads/master") .call(); - assertEquals(Status.FAILED, result.getStatus()); + assertEquals(Status.UNCOMMITTED_CHANGES, result.getStatus()); // staged file0 causes DIRTY_INDEX - assertEquals(1, result.getFailingPaths().size()); - assertEquals(MergeFailureReason.DIRTY_INDEX, result.getFailingPaths() - .get("file0")); + assertEquals(1, result.getUncommittedChanges().size()); assertEquals("unstaged modified file0", read(file0)); // index shall be unchanged assertEquals(indexState, indexState(CONTENT)); @@ -1465,6 +1467,82 @@ public class RebaseCommandTest extends RepositoryTestCase { } @Test + public void testFastForwardRebaseWithModification() throws Exception { + // create file0 + file1, add and commit + writeTrashFile("file0", "file0"); + writeTrashFile(FILE1, "file1"); + git.add().addFilepattern("file0").addFilepattern(FILE1).call(); + RevCommit commit = git.commit().setMessage("commit1").call(); + + // create topic branch + createBranch(commit, "refs/heads/topic"); + + // still on master / modify file1, add and commit + writeTrashFile(FILE1, "modified file1"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit2").call(); + + // checkout topic branch / modify file0 and add to index + checkoutBranch("refs/heads/topic"); + writeTrashFile("file0", "modified file0 in index"); + git.add().addFilepattern("file0").addFilepattern(FILE1).call(); + // modify once more + writeTrashFile("file0", "modified file0"); + + // rebase + RebaseResult result = git.rebase().setUpstream("refs/heads/master") + .call(); + assertEquals(Status.FAST_FORWARD, result.getStatus()); + checkFile(new File(db.getWorkTree(), "file0"), "modified file0"); + checkFile(new File(db.getWorkTree(), FILE1), "modified file1"); + assertEquals("[file0, mode:100644, content:modified file0 in index]" + + "[file1, mode:100644, content:modified file1]", + indexState(CONTENT)); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } + + @Test + public void testRebaseWithModificationShouldNotDeleteData() + throws Exception { + // create file0 + file1, add and commit + writeTrashFile("file0", "file0"); + writeTrashFile(FILE1, "file1"); + git.add().addFilepattern("file0").addFilepattern(FILE1).call(); + RevCommit commit = git.commit().setMessage("commit1").call(); + + // create topic branch + createBranch(commit, "refs/heads/topic"); + + // still on master / modify file1, add and commit + writeTrashFile(FILE1, "modified file1"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit2").call(); + + // checkout topic branch / modify file1, add and commit + checkoutBranch("refs/heads/topic"); + writeTrashFile(FILE1, "modified file1 on topic"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit3").call(); + + writeTrashFile("file0", "modified file0"); + + RebaseResult result = git.rebase().setUpstream("refs/heads/master") + .call(); + // the following condition was true before commit 83b6ab233: + // jgit started the rebase and deleted the change on abort + // This test should verify that content was deleted + if (result.getStatus() == Status.STOPPED) + git.rebase().setOperation(Operation.ABORT).call(); + + checkFile(new File(db.getWorkTree(), "file0"), "modified file0"); + checkFile(new File(db.getWorkTree(), FILE1), + "modified file1 on topic"); + assertEquals("[file0, mode:100644, content:file0]" + + "[file1, mode:100644, content:modified file1 on topic]", + indexState(CONTENT)); + } + + @Test public void testRebaseWithUncommittedDelete() throws Exception { // create file0 + file1, add and commit File file0 = writeTrashFile("file0", "file0"); @@ -1496,6 +1574,136 @@ public class RebaseCommandTest extends RepositoryTestCase { assertEquals(RepositoryState.SAFE, db.getRepositoryState()); } + @Test + public void testRebaseWithAutoStash() + throws Exception { + // create file0, add and commit + db.getConfig().setBoolean(ConfigConstants.CONFIG_REBASE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOSTASH, true); + writeTrashFile("file0", "file0"); + git.add().addFilepattern("file0").call(); + git.commit().setMessage("commit0").call(); + // create file1, add and commit + writeTrashFile(FILE1, "file1"); + git.add().addFilepattern(FILE1).call(); + RevCommit commit = git.commit().setMessage("commit1").call(); + + // create topic branch and checkout / create file2, add and commit + createBranch(commit, "refs/heads/topic"); + checkoutBranch("refs/heads/topic"); + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + git.commit().setMessage("commit2").call(); + + // checkout master branch / modify file1, add and commit + checkoutBranch("refs/heads/master"); + writeTrashFile(FILE1, "modified file1"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit3").call(); + + // checkout topic branch / modify file0 + checkoutBranch("refs/heads/topic"); + writeTrashFile("file0", "unstaged modified file0"); + + // rebase + assertEquals(Status.OK, + git.rebase().setUpstream("refs/heads/master").call() + .getStatus()); + checkFile(new File(db.getWorkTree(), "file0"), + "unstaged modified file0"); + checkFile(new File(db.getWorkTree(), FILE1), "modified file1"); + checkFile(new File(db.getWorkTree(), "file2"), "file2"); + assertEquals("[file0, mode:100644, content:file0]" + + "[file1, mode:100644, content:modified file1]" + + "[file2, mode:100644, content:file2]", + indexState(CONTENT)); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + } + + @Test + public void testRebaseWithAutoStashConflictOnApply() throws Exception { + // create file0, add and commit + db.getConfig().setBoolean(ConfigConstants.CONFIG_REBASE_SECTION, null, + ConfigConstants.CONFIG_KEY_AUTOSTASH, true); + writeTrashFile("file0", "file0"); + git.add().addFilepattern("file0").call(); + git.commit().setMessage("commit0").call(); + // create file1, add and commit + writeTrashFile(FILE1, "file1"); + git.add().addFilepattern(FILE1).call(); + RevCommit commit = git.commit().setMessage("commit1").call(); + + // create topic branch and checkout / create file2, add and commit + createBranch(commit, "refs/heads/topic"); + checkoutBranch("refs/heads/topic"); + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + git.commit().setMessage("commit2").call(); + + // checkout master branch / modify file1, add and commit + checkoutBranch("refs/heads/master"); + writeTrashFile(FILE1, "modified file1"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit3").call(); + + // checkout topic branch / modify file0 + checkoutBranch("refs/heads/topic"); + writeTrashFile("file1", "unstaged modified file1"); + + // rebase + assertEquals(Status.STASH_APPLY_CONFLICTS, + git.rebase().setUpstream("refs/heads/master").call() + .getStatus()); + checkFile(new File(db.getWorkTree(), "file0"), "file0"); + checkFile( + new File(db.getWorkTree(), FILE1), + "<<<<<<< HEAD\nmodified file1\n=======\nunstaged modified file1\n>>>>>>> stash\n"); + checkFile(new File(db.getWorkTree(), "file2"), "file2"); + assertEquals( + "[file0, mode:100644, content:file0]" + + "[file1, mode:100644, stage:1, content:file1]" + + "[file1, mode:100644, stage:2, content:modified file1]" + + "[file1, mode:100644, stage:3, content:unstaged modified file1]" + + "[file2, mode:100644, content:file2]", + indexState(CONTENT)); + assertEquals(RepositoryState.SAFE, db.getRepositoryState()); + + List<DiffEntry> diffs = getStashedDiff(); + assertEquals(1, diffs.size()); + assertEquals(DiffEntry.ChangeType.MODIFY, diffs.get(0).getChangeType()); + assertEquals("file1", diffs.get(0).getOldPath()); + } + + private List<DiffEntry> getStashedDiff() throws AmbiguousObjectException, + IncorrectObjectTypeException, IOException, MissingObjectException { + ObjectId stashId = db.resolve("stash@{0}"); + RevWalk revWalk = new RevWalk(db); + RevCommit stashCommit = revWalk.parseCommit(stashId); + List<DiffEntry> diffs = diffWorkingAgainstHead(stashCommit, revWalk); + return diffs; + } + + private TreeWalk createTreeWalk() { + TreeWalk walk = new TreeWalk(db); + walk.setRecursive(true); + walk.setFilter(TreeFilter.ANY_DIFF); + return walk; + } + + private List<DiffEntry> diffWorkingAgainstHead(final RevCommit commit, + RevWalk revWalk) + throws IOException { + TreeWalk walk = createTreeWalk(); + RevCommit parentCommit = revWalk.parseCommit(commit.getParent(0)); + try { + walk.addTree(parentCommit.getTree()); + walk.addTree(commit.getTree()); + return DiffEntry.scan(walk); + } finally { + walk.release(); + } + } + private int countPicks() throws IOException { int count = 0; File todoFile = getTodoFile(); @@ -1595,9 +1803,9 @@ public class RebaseCommandTest extends RepositoryTestCase { // and attempt to rebase RebaseResult rebaseResult = git.rebase() .setUpstream("refs/heads/master").call(); - assertEquals(Status.CONFLICTS, rebaseResult.getStatus()); - assertEquals(1, rebaseResult.getConflicts().size()); - assertEquals(FILE1, rebaseResult.getConflicts().get(0)); + assertEquals(Status.UNCOMMITTED_CHANGES, rebaseResult.getStatus()); + assertEquals(1, rebaseResult.getUncommittedChanges().size()); + assertEquals(FILE1, rebaseResult.getUncommittedChanges().get(0)); checkFile(theFile, "dirty the file"); @@ -1642,6 +1850,19 @@ public class RebaseCommandTest extends RepositoryTestCase { } @Test + public void testRebaseShouldBeAbleToHandleLinesWithoutCommitMessageInRebaseTodoFile() + throws IOException { + String todo = "pick 1111111 \n" + "pick 2222222 Commit 2\n" + + "# Comment line at end\n"; + write(getTodoFile(), todo); + + List<RebaseTodoLine> steps = db.readRebaseTodo(GIT_REBASE_TODO, false); + assertEquals(2, steps.size()); + assertEquals("1111111", steps.get(0).getCommit().name()); + assertEquals("2222222", steps.get(1).getCommit().name()); + } + + @Test public void testRebaseShouldNotFailIfUserAddCommentLinesInPrepareSteps() throws Exception { commitFile(FILE1, FILE1, "master"); @@ -2237,6 +2458,44 @@ public class RebaseCommandTest extends RepositoryTestCase { head1Commit.getFullMessage()); } + @Test + public void testRebaseInteractiveFixupWithBlankLines() throws Exception { + // create file1 on master + writeTrashFile(FILE1, FILE1); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("Add file1\nnew line").call(); + assertTrue(new File(db.getWorkTree(), FILE1).exists()); + + // create file2 on master + writeTrashFile("file2", "file2"); + git.add().addFilepattern("file2").call(); + git.commit().setMessage("Add file2").call(); + assertTrue(new File(db.getWorkTree(), "file2").exists()); + + // update FILE1 on master + writeTrashFile(FILE1, "blah"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("updated file1 on master\n\nsome text").call(); + + git.rebase().setUpstream("HEAD~2") + .runInteractively(new InteractiveHandler() { + + public void prepareSteps(List<RebaseTodoLine> steps) { + steps.get(1).setAction(Action.FIXUP); + } + + public String modifyCommitMessage(String commit) { + fail("No callback to modify commit message expected for single fixup"); + return commit; + } + }).call(); + + RevWalk walk = new RevWalk(db); + ObjectId headId = db.resolve(Constants.HEAD); + RevCommit headCommit = walk.parseCommit(headId); + assertEquals("Add file2", + headCommit.getFullMessage()); + } @Test(expected = InvalidRebaseStepException.class) public void testRebaseInteractiveFixupFirstCommitShouldFail() @@ -2534,6 +2793,59 @@ public class RebaseCommandTest extends RepositoryTestCase { } + @Test + public void testInteractiveRebaseWithModificationShouldNotDeleteDataOnAbort() + throws Exception { + // create file0 + file1, add and commit + writeTrashFile("file0", "file0"); + writeTrashFile(FILE1, "file1"); + git.add().addFilepattern("file0").addFilepattern(FILE1).call(); + git.commit().setMessage("commit1").call(); + + // modify file1, add and commit + writeTrashFile(FILE1, "modified file1"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit2").call(); + + // modify file1, add and commit + writeTrashFile(FILE1, "modified file1 a second time"); + git.add().addFilepattern(FILE1).call(); + git.commit().setMessage("commit3").call(); + + // modify file0, but do not commit + writeTrashFile("file0", "modified file0 in index"); + git.add().addFilepattern("file0").addFilepattern(FILE1).call(); + // do not commit + writeTrashFile("file0", "modified file0"); + + // start rebase + RebaseResult result = git.rebase().setUpstream("HEAD~2") + .runInteractively(new InteractiveHandler() { + + public void prepareSteps(List<RebaseTodoLine> steps) { + steps.get(0).setAction(Action.EDIT); + steps.get(1).setAction(Action.PICK); + } + + public String modifyCommitMessage(String commit) { + return commit; + } + }).call(); + // the following condition was true before commit 83b6ab233: + // jgit started the rebase and deleted the change on abort + // This test should verify that content was deleted + if (result.getStatus() == Status.EDIT) + git.rebase().setOperation(Operation.ABORT).call(); + + checkFile(new File(db.getWorkTree(), "file0"), "modified file0"); + checkFile(new File(db.getWorkTree(), "file1"), + "modified file1 a second time"); + assertEquals("[file0, mode:100644, content:modified file0 in index]" + + "[file1, mode:100644, content:modified file1 a second time]", + indexState(CONTENT)); + + } + private File getTodoFile() { File todoFile = new File(db.getDirectory(), GIT_REBASE_TODO); return todoFile; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java index 16e80f1bfa..2834100389 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashApplyCommandTest.java @@ -409,6 +409,93 @@ public class StashApplyCommandTest extends RepositoryTestCase { } @Test + public void stashedApplyOnOtherBranch() throws Exception { + writeTrashFile(PATH, "content\nmore content\n"); + git.add().addFilepattern(PATH).call(); + git.commit().setMessage("more content").call(); + String path2 = "file2.txt"; + File file2 = writeTrashFile(path2, "content\nmore content\n"); + git.add().addFilepattern(PATH).call(); + git.add().addFilepattern(path2).call(); + git.commit().setMessage("even content").call(); + + String otherBranch = "otherBranch"; + git.branchCreate().setName(otherBranch).call(); + + writeTrashFile(PATH, "master content"); + git.add().addFilepattern(PATH).call(); + git.commit().setMessage("even content").call(); + + git.checkout().setName(otherBranch).call(); + + writeTrashFile(PATH, "otherBranch content"); + git.add().addFilepattern(PATH).call(); + git.commit().setMessage("even more content").call(); + + writeTrashFile(path2, "content\nstashed change\nmore content\n"); + + RevCommit stashed = git.stashCreate().call(); + + assertNotNull(stashed); + assertEquals("content\nmore content\n", read(file2)); + assertEquals("otherBranch content", + read(committedFile)); + assertTrue(git.status().call().isClean()); + + git.checkout().setName("master").call(); + git.stashApply().call(); + assertEquals("content\nstashed change\nmore content\n", read(file2)); + assertEquals("master content", + read(committedFile)); + } + + @Test + public void stashedApplyOnOtherBranchWithStagedChange() throws Exception { + writeTrashFile(PATH, "content\nmore content\n"); + git.add().addFilepattern(PATH).call(); + git.commit().setMessage("more content").call(); + String path2 = "file2.txt"; + File file2 = writeTrashFile(path2, "content\nmore content\n"); + git.add().addFilepattern(PATH).call(); + git.add().addFilepattern(path2).call(); + git.commit().setMessage("even content").call(); + + String otherBranch = "otherBranch"; + git.branchCreate().setName(otherBranch).call(); + + writeTrashFile(PATH, "master content"); + git.add().addFilepattern(PATH).call(); + git.commit().setMessage("even content").call(); + + git.checkout().setName(otherBranch).call(); + + writeTrashFile(PATH, "otherBranch content"); + git.add().addFilepattern(PATH).call(); + git.commit().setMessage("even more content").call(); + + writeTrashFile(path2, + "content\nstashed change in index\nmore content\n"); + git.add().addFilepattern(path2).call(); + writeTrashFile(path2, "content\nstashed change\nmore content\n"); + + RevCommit stashed = git.stashCreate().call(); + + assertNotNull(stashed); + assertEquals("content\nmore content\n", read(file2)); + assertEquals("otherBranch content", read(committedFile)); + assertTrue(git.status().call().isClean()); + + git.checkout().setName("master").call(); + git.stashApply().call(); + assertEquals("content\nstashed change\nmore content\n", read(file2)); + assertEquals( + "[file.txt, mode:100644, content:master content]" + + "[file2.txt, mode:100644, content:content\nstashed change in index\nmore content\n]", + indexState(CONTENT)); + assertEquals("master content", read(committedFile)); + } + + @Test public void workingDirectoryContentMerge() throws Exception { writeTrashFile(PATH, "content\nmore content\n"); git.add().addFilepattern(PATH).call(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java index d911efc1d1..aa98696b24 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/IgnoreMatcherTest.java @@ -340,6 +340,18 @@ public class IgnoreMatcherTest { assertEquals(r.getPattern(), "/patter?"); } + @Test + public void testResetState() { + String pattern = "/build/*"; + String target = "/build"; + // Don't use the assert methods of this class, as we want to test + // whether the state in IgnoreRule is reset properly + IgnoreRule r = new IgnoreRule(pattern); + // Result should be the same for the same inputs + assertFalse(r.isMatch(target, true)); + assertFalse(r.isMatch(target, true)); + } + /** * Check for a match. If target ends with "/", match will assume that the * target is meant to be a directory. diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java deleted file mode 100644 index 9d2a03b097..0000000000 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java +++ /dev/null @@ -1,741 +0,0 @@ -/* - * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> - * and other copyright owners as documented in the project's IP log. - * - * This program and the accompanying materials are made available - * under the terms of the Eclipse Distribution License v1.0 which - * accompanies this distribution, is reproduced below, and is - * available at http://www.eclipse.org/org/documents/edl-v10.php - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * - * - Neither the name of the Eclipse Foundation, Inc. nor the - * names of its contributors may be used to endorse or promote - * products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND - * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package org.eclipse.jgit.internal.storage.file; - -import static java.lang.Integer.valueOf; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.Iterator; -import java.util.concurrent.BrokenBarrierException; -import java.util.concurrent.Callable; -import java.util.concurrent.CyclicBarrier; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics; -import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; -import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; -import org.eclipse.jgit.junit.RepositoryTestCase; -import org.eclipse.jgit.junit.TestRepository; -import org.eclipse.jgit.junit.TestRepository.BranchBuilder; -import org.eclipse.jgit.junit.TestRepository.CommitBuilder; -import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Constants; -import org.eclipse.jgit.lib.EmptyProgressMonitor; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.Ref.Storage; -import org.eclipse.jgit.lib.RefUpdate; -import org.eclipse.jgit.lib.RefUpdate.Result; -import org.eclipse.jgit.merge.MergeStrategy; -import org.eclipse.jgit.merge.Merger; -import org.eclipse.jgit.revwalk.RevBlob; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevTag; -import org.eclipse.jgit.revwalk.RevTree; -import org.eclipse.jgit.util.FileUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -public class GCTest extends LocalDiskRepositoryTestCase { - private TestRepository<FileRepository> tr; - - private FileRepository repo; - - private GC gc; - - private RepoStatistics stats; - - @Before - public void setUp() throws Exception { - super.setUp(); - repo = createWorkRepository(); - tr = new TestRepository<FileRepository>((repo)); - gc = new GC(repo); - } - - @After - public void tearDown() throws Exception { - super.tearDown(); - } - - // GC.packRefs tests - - @Test - public void packRefs_looseRefPacked() throws Exception { - RevBlob a = tr.blob("a"); - tr.lightweightTag("t", a); - - gc.packRefs(); - assertSame(repo.getRef("t").getStorage(), Storage.PACKED); - } - - @Test - public void concurrentPackRefs_onlyOneWritesPackedRefs() throws Exception { - RevBlob a = tr.blob("a"); - tr.lightweightTag("t", a); - - final CyclicBarrier syncPoint = new CyclicBarrier(2); - - Callable<Integer> packRefs = new Callable<Integer>() { - - /** @return 0 for success, 1 in case of error when writing pack */ - public Integer call() throws Exception { - syncPoint.await(); - try { - gc.packRefs(); - return valueOf(0); - } catch (IOException e) { - return valueOf(1); - } - } - }; - ExecutorService pool = Executors.newFixedThreadPool(2); - try { - Future<Integer> p1 = pool.submit(packRefs); - Future<Integer> p2 = pool.submit(packRefs); - assertEquals(1, p1.get().intValue() + p2.get().intValue()); - } finally { - pool.shutdown(); - pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); - } - } - - @Test - public void packRefsWhileRefLocked_refNotPackedNoError() - throws Exception { - RevBlob a = tr.blob("a"); - tr.lightweightTag("t1", a); - tr.lightweightTag("t2", a); - LockFile refLock = new LockFile(new File(repo.getDirectory(), - "refs/tags/t1"), repo.getFS()); - try { - refLock.lock(); - gc.packRefs(); - } finally { - refLock.unlock(); - } - - assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE); - assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED); - } - - @Test - public void packRefsWhileRefUpdated_refUpdateSucceeds() - throws Exception { - RevBlob a = tr.blob("a"); - tr.lightweightTag("t", a); - final RevBlob b = tr.blob("b"); - - final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2); - final CyclicBarrier packRefsDone = new CyclicBarrier(2); - ExecutorService pool = Executors.newFixedThreadPool(2); - try { - Future<Result> result = pool.submit(new Callable<Result>() { - - public Result call() throws Exception { - RefUpdate update = new RefDirectoryUpdate( - (RefDirectory) repo.getRefDatabase(), - repo.getRef("refs/tags/t")) { - @Override - public boolean isForceUpdate() { - try { - refUpdateLockedRef.await(); - packRefsDone.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (BrokenBarrierException e) { - Thread.currentThread().interrupt(); - } - return super.isForceUpdate(); - } - }; - update.setForceUpdate(true); - update.setNewObjectId(b); - return update.update(); - } - }); - - pool.submit(new Callable<Void>() { - public Void call() throws Exception { - refUpdateLockedRef.await(); - gc.packRefs(); - packRefsDone.await(); - return null; - } - }); - - assertSame(result.get(), Result.FORCED); - - } finally { - pool.shutdownNow(); - pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); - } - - assertEquals(repo.getRef("refs/tags/t").getObjectId(), b); - } - - // GC.repack tests - - @Test - public void repackEmptyRepo_noPackCreated() throws IOException { - gc.repack(); - assertEquals(0, repo.getObjectDatabase().getPacks().size()); - } - - @Test - public void concurrentRepack() throws Exception { - final CyclicBarrier syncPoint = new CyclicBarrier(2); - - class DoRepack extends EmptyProgressMonitor implements - Callable<Integer> { - - public void beginTask(String title, int totalWork) { - if (title.equals(JGitText.get().writingObjects)) { - try { - syncPoint.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (BrokenBarrierException ignored) { - // - } - } - } - - /** @return 0 for success, 1 in case of error when writing pack */ - public Integer call() throws Exception { - try { - gc.setProgressMonitor(this); - gc.repack(); - return valueOf(0); - } catch (IOException e) { - // leave the syncPoint in broken state so any awaiting - // threads and any threads that call await in the future get - // the BrokenBarrierException - Thread.currentThread().interrupt(); - try { - syncPoint.await(); - } catch (InterruptedException ignored) { - // - } - return valueOf(1); - } - } - } - - RevBlob a = tr.blob("a"); - tr.lightweightTag("t", a); - - ExecutorService pool = Executors.newFixedThreadPool(2); - try { - DoRepack repack1 = new DoRepack(); - DoRepack repack2 = new DoRepack(); - Future<Integer> result1 = pool.submit(repack1); - Future<Integer> result2 = pool.submit(repack2); - assertEquals(0, result1.get().intValue() + result2.get().intValue()); - } finally { - pool.shutdown(); - pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); - } - } - - // GC.prune tests - - @Test - public void nonReferencedNonExpiredObject_notPruned() throws Exception { - RevBlob a = tr.blob("a"); - gc.setExpire(new Date(lastModified(a))); - gc.prune(Collections.<ObjectId> emptySet()); - assertTrue(repo.hasObject(a)); - } - - @Test - public void nonReferencedExpiredObject_pruned() throws Exception { - RevBlob a = tr.blob("a"); - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - assertFalse(repo.hasObject(a)); - } - - @Test - public void nonReferencedExpiredObjectTree_pruned() throws Exception { - RevBlob a = tr.blob("a"); - RevTree t = tr.tree(tr.file("a", a)); - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - assertFalse(repo.hasObject(t)); - assertFalse(repo.hasObject(a)); - } - - @Test - public void nonReferencedObjects_onlyExpiredPruned() throws Exception { - RevBlob a = tr.blob("a"); - gc.setExpire(new Date(lastModified(a) + 1)); - - fsTick(); - RevBlob b = tr.blob("b"); - - gc.prune(Collections.<ObjectId> emptySet()); - assertFalse(repo.hasObject(a)); - assertTrue(repo.hasObject(b)); - } - - @Test - public void lightweightTag_objectNotPruned() throws Exception { - RevBlob a = tr.blob("a"); - tr.lightweightTag("t", a); - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - assertTrue(repo.hasObject(a)); - } - - @Test - public void annotatedTag_objectNotPruned() throws Exception { - RevBlob a = tr.blob("a"); - RevTag t = tr.tag("t", a); // this doesn't create the refs/tags/t ref - tr.lightweightTag("t", t); - - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - assertTrue(repo.hasObject(t)); - assertTrue(repo.hasObject(a)); - } - - @Test - public void branch_historyNotPruned() throws Exception { - RevCommit tip = commitChain(10); - tr.branch("b").update(tip); - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - do { - assertTrue(repo.hasObject(tip)); - tr.parseBody(tip); - RevTree t = tip.getTree(); - assertTrue(repo.hasObject(t)); - assertTrue(repo.hasObject(tr.get(t, "a"))); - tip = tip.getParentCount() > 0 ? tip.getParent(0) : null; - } while (tip != null); - } - - @Test - public void deleteBranch_historyPruned() throws Exception { - RevCommit tip = commitChain(10); - tr.branch("b").update(tip); - RefUpdate update = repo.updateRef("refs/heads/b"); - update.setForceUpdate(true); - update.delete(); - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - assertTrue(gc.getStatistics().numberOfLooseObjects == 0); - } - - @Test - public void deleteMergedBranch_historyNotPruned() throws Exception { - RevCommit parent = tr.commit().create(); - RevCommit b1Tip = tr.branch("b1").commit().parent(parent).add("x", "x") - .create(); - RevCommit b2Tip = tr.branch("b2").commit().parent(parent).add("y", "y") - .create(); - - // merge b1Tip and b2Tip and update refs/heads/b1 to the merge commit - Merger merger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo); - merger.merge(b1Tip, b2Tip); - CommitBuilder cb = tr.commit(); - cb.parent(b1Tip).parent(b2Tip); - cb.setTopLevelTree(merger.getResultTreeId()); - RevCommit mergeCommit = cb.create(); - RefUpdate u = repo.updateRef("refs/heads/b1"); - u.setNewObjectId(mergeCommit); - u.update(); - - RefUpdate update = repo.updateRef("refs/heads/b2"); - update.setForceUpdate(true); - update.delete(); - - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - assertTrue(repo.hasObject(b2Tip)); - } - - @Test - public void testPackAllObjectsInOnePack() throws Exception { - tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") - .create(); - stats = gc.getStatistics(); - assertEquals(4, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(4, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - - // Do the gc again and check that it hasn't changed anything - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(4, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - } - - @Test - public void testPackRepoWithCorruptReflog() throws Exception { - // create a reflog entry "0000... 0000... foobar" by doing an initial - // refupdate for HEAD which points to a non-existing ref. The - // All-Projects repo of gerrit instances had such entries - RefUpdate ru = repo.updateRef(Constants.HEAD); - ru.link("refs/to/garbage"); - tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") - .create(); - // make sure HEAD exists - Git.wrap(repo).checkout().setName("refs/heads/master").call(); - gc.gc(); - } - - @Test - public void testKeepFiles() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - bb.commit().add("A", "A").add("B", "B").create(); - stats = gc.getStatistics(); - assertEquals(4, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - assertEquals(0, stats.numberOfPackFiles); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(4, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - - Iterator<PackFile> packIt = repo.getObjectDatabase().getPacks() - .iterator(); - PackFile singlePack = packIt.next(); - assertFalse(packIt.hasNext()); - File keepFile = new File(singlePack.getPackFile().getPath() + ".keep"); - assertFalse(keepFile.exists()); - assertTrue(keepFile.createNewFile()); - bb.commit().add("A", "A2").add("B", "B2").create(); - stats = gc.getStatistics(); - assertEquals(4, stats.numberOfLooseObjects); - assertEquals(4, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(8, stats.numberOfPackedObjects); - assertEquals(2, stats.numberOfPackFiles); - - // check that no object is packed twice - Iterator<PackFile> packs = repo.getObjectDatabase().getPacks() - .iterator(); - PackIndex ind1 = packs.next().getIndex(); - assertEquals(4, ind1.getObjectCount()); - PackIndex ind2 = packs.next().getIndex(); - assertEquals(4, ind2.getObjectCount()); - for (MutableEntry e: ind1) - if (ind2.hasObject(e.toObjectId())) - assertFalse( - "the following object is in both packfiles: " - + e.toObjectId(), - ind2.hasObject(e.toObjectId())); - } - - @Test - public void testPackRepoWithNoRefs() throws Exception { - tr.commit().add("A", "A").add("B", "B").create(); - stats = gc.getStatistics(); - assertEquals(4, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(4, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - assertEquals(0, stats.numberOfPackFiles); - } - - @Test - public void testPack2Commits() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(8, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - } - - @Test - public void testPackCommitsAndLooseOne() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - tr.update("refs/heads/master", first); - - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(8, stats.numberOfPackedObjects); - assertEquals(2, stats.numberOfPackFiles); - } - - @Test - public void testNotPackTwice() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - RevCommit first = bb.commit().message("M").add("M", "M").create(); - bb.commit().message("B").add("B", "Q").create(); - bb.commit().message("A").add("A", "A").create(); - RevCommit second = tr.commit().parent(first).message("R").add("R", "Q") - .create(); - tr.update("refs/tags/t1", second); - - Collection<PackFile> oldPacks = tr.getRepository().getObjectDatabase() - .getPacks(); - assertEquals(0, oldPacks.size()); - stats = gc.getStatistics(); - assertEquals(11, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - - gc.setExpireAgeMillis(0); - fsTick(); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - - Iterator<PackFile> pIt = repo.getObjectDatabase().getPacks().iterator(); - long c = pIt.next().getObjectCount(); - if (c == 9) - assertEquals(2, pIt.next().getObjectCount()); - else { - assertEquals(2, c); - assertEquals(9, pIt.next().getObjectCount()); - } - } - - @Test - public void testPackCommitsAndLooseOneNoReflog() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - tr.update("refs/heads/master", first); - - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - - FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"), - FileUtils.RETRY | FileUtils.SKIP_MISSING); - FileUtils.delete( - new File(repo.getDirectory(), "logs/refs/heads/master"), - FileUtils.RETRY | FileUtils.SKIP_MISSING); - gc.gc(); - - stats = gc.getStatistics(); - assertEquals(4, stats.numberOfLooseObjects); - assertEquals(4, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - } - - @Test - public void testPackCommitsAndLooseOneWithPruneNow() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - tr.update("refs/heads/master", first); - - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - gc.setExpireAgeMillis(0); - fsTick(); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(8, stats.numberOfPackedObjects); - assertEquals(2, stats.numberOfPackFiles); - } - - @Test - public void testPackCommitsAndLooseOneWithPruneNowNoReflog() - throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - tr.update("refs/heads/master", first); - - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - - FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"), - FileUtils.RETRY | FileUtils.SKIP_MISSING); - FileUtils.delete( - new File(repo.getDirectory(), "logs/refs/heads/master"), - FileUtils.RETRY | FileUtils.SKIP_MISSING); - gc.setExpireAgeMillis(0); - gc.gc(); - - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(4, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - } - - @Test - public void testIndexSavesObjects() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - bb.commit().add("A", "A3"); // this new content in index should survive - stats = gc.getStatistics(); - assertEquals(9, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(1, stats.numberOfLooseObjects); - assertEquals(8, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - } - - @Test - public void testIndexSavesObjectsWithPruneNow() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - bb.commit().add("A", "A3"); // this new content in index should survive - stats = gc.getStatistics(); - assertEquals(9, stats.numberOfLooseObjects); - assertEquals(0, stats.numberOfPackedObjects); - gc.setExpireAgeMillis(0); - fsTick(); - gc.gc(); - stats = gc.getStatistics(); - assertEquals(0, stats.numberOfLooseObjects); - assertEquals(8, stats.numberOfPackedObjects); - assertEquals(1, stats.numberOfPackFiles); - } - - @Test - public void testPruneNone() throws Exception { - BranchBuilder bb = tr.branch("refs/heads/master"); - bb.commit().add("A", "A").add("B", "B").create(); - bb.commit().add("A", "A2").add("B", "B2").create(); - new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master") - .delete(); - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - gc.setExpireAgeMillis(0); - fsTick(); - gc.prune(Collections.<ObjectId> emptySet()); - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - tr.blob("x"); - stats = gc.getStatistics(); - assertEquals(9, stats.numberOfLooseObjects); - gc.prune(Collections.<ObjectId> emptySet()); - stats = gc.getStatistics(); - assertEquals(8, stats.numberOfLooseObjects); - } - - /** - * Create a chain of commits of given depth. - * <p> - * Each commit contains one file named "a" containing the index of the - * commit in the chain as its content. The created commit chain is - * referenced from any ref. - * <p> - * A chain of depth = N will create 3*N objects in Gits object database. For - * each depth level three objects are created: the commit object, the - * top-level tree object and a blob for the content of the file "a". - * - * @param depth - * the depth of the commit chain. - * @return the commit that is the tip of the commit chain - * @throws Exception - */ - private RevCommit commitChain(int depth) throws Exception { - if (depth <= 0) - throw new IllegalArgumentException("Chain depth must be > 0"); - CommitBuilder cb = tr.commit(); - RevCommit tip; - do { - --depth; - tip = cb.add("a", "" + depth).message("" + depth).create(); - cb = cb.child(); - } while (depth > 0); - return tip; - } - - private long lastModified(AnyObjectId objectId) { - return repo.getObjectDatabase().fileFor(objectId).lastModified(); - } - - private static void fsTick() throws InterruptedException, IOException { - RepositoryTestCase.fsTick(null); - } -} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java new file mode 100644 index 0000000000..0f27099c09 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; + +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; + +public class GcBasicPackingTest extends GcTestCase { + @Test + public void repackEmptyRepo_noPackCreated() throws IOException { + gc.repack(); + assertEquals(0, repo.getObjectDatabase().getPacks().size()); + } + + @Test + public void testPackRepoWithNoRefs() throws Exception { + tr.commit().add("A", "A").add("B", "B").create(); + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + assertEquals(0, stats.numberOfPackFiles); + } + + @Test + public void testPack2Commits() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(8, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } + + @Test + public void testPackAllObjectsInOnePack() throws Exception { + tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") + .create(); + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + + // Do the gc again and check that it hasn't changed anything + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } + + @Test + public void testPackCommitsAndLooseOne() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + tr.update("refs/heads/master", first); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(8, stats.numberOfPackedObjects); + assertEquals(2, stats.numberOfPackFiles); + } + + @Test + public void testNotPackTwice() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + RevCommit first = bb.commit().message("M").add("M", "M").create(); + bb.commit().message("B").add("B", "Q").create(); + bb.commit().message("A").add("A", "A").create(); + RevCommit second = tr.commit().parent(first).message("R").add("R", "Q") + .create(); + tr.update("refs/tags/t1", second); + + Collection<PackFile> oldPacks = tr.getRepository().getObjectDatabase() + .getPacks(); + assertEquals(0, oldPacks.size()); + stats = gc.getStatistics(); + assertEquals(11, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + + gc.setExpireAgeMillis(0); + fsTick(); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + + Iterator<PackFile> pIt = repo.getObjectDatabase().getPacks().iterator(); + long c = pIt.next().getObjectCount(); + if (c == 9) + assertEquals(2, pIt.next().getObjectCount()); + else { + assertEquals(2, c); + assertEquals(9, pIt.next().getObjectCount()); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java new file mode 100644 index 0000000000..c7ee9256d7 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertTrue; + +import java.util.Collections; + +import org.eclipse.jgit.junit.TestRepository.CommitBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.merge.Merger; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.junit.Test; + +public class GcBranchPrunedTest extends GcTestCase { + + @Test + public void branch_historyNotPruned() throws Exception { + RevCommit tip = commitChain(10); + tr.branch("b").update(tip); + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + do { + assertTrue(repo.hasObject(tip)); + tr.parseBody(tip); + RevTree t = tip.getTree(); + assertTrue(repo.hasObject(t)); + assertTrue(repo.hasObject(tr.get(t, "a"))); + tip = tip.getParentCount() > 0 ? tip.getParent(0) : null; + } while (tip != null); + } + + @Test + public void deleteBranch_historyPruned() throws Exception { + RevCommit tip = commitChain(10); + tr.branch("b").update(tip); + RefUpdate update = repo.updateRef("refs/heads/b"); + update.setForceUpdate(true); + update.delete(); + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + assertTrue(gc.getStatistics().numberOfLooseObjects == 0); + } + + @Test + public void deleteMergedBranch_historyNotPruned() throws Exception { + RevCommit parent = tr.commit().create(); + RevCommit b1Tip = tr.branch("b1").commit().parent(parent).add("x", "x") + .create(); + RevCommit b2Tip = tr.branch("b2").commit().parent(parent).add("y", "y") + .create(); + + // merge b1Tip and b2Tip and update refs/heads/b1 to the merge commit + Merger merger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo); + merger.merge(b1Tip, b2Tip); + CommitBuilder cb = tr.commit(); + cb.parent(b1Tip).parent(b2Tip); + cb.setTopLevelTree(merger.getResultTreeId()); + RevCommit mergeCommit = cb.create(); + RefUpdate u = repo.updateRef("refs/heads/b1"); + u.setNewObjectId(mergeCommit); + u.update(); + + RefUpdate update = repo.updateRef("refs/heads/b2"); + update.setForceUpdate(true); + update.delete(); + + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + assertTrue(repo.hasObject(b2Tip)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java new file mode 100644 index 0000000000..07a7be7467 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static java.lang.Integer.valueOf; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.EmptyProgressMonitor; +import org.eclipse.jgit.revwalk.RevBlob; +import org.junit.Test; + +public class GcConcurrentTest extends GcTestCase { + @Test + public void concurrentRepack() throws Exception { + final CyclicBarrier syncPoint = new CyclicBarrier(2); + + class DoRepack extends EmptyProgressMonitor implements + Callable<Integer> { + + public void beginTask(String title, int totalWork) { + if (title.equals(JGitText.get().writingObjects)) { + try { + syncPoint.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (BrokenBarrierException ignored) { + // + } + } + } + + /** @return 0 for success, 1 in case of error when writing pack */ + public Integer call() throws Exception { + try { + gc.setProgressMonitor(this); + gc.repack(); + return valueOf(0); + } catch (IOException e) { + // leave the syncPoint in broken state so any awaiting + // threads and any threads that call await in the future get + // the BrokenBarrierException + Thread.currentThread().interrupt(); + try { + syncPoint.await(); + } catch (InterruptedException ignored) { + // + } + return valueOf(1); + } + } + } + + RevBlob a = tr.blob("a"); + tr.lightweightTag("t", a); + + ExecutorService pool = Executors.newFixedThreadPool(2); + try { + DoRepack repack1 = new DoRepack(); + DoRepack repack2 = new DoRepack(); + Future<Integer> result1 = pool.submit(repack1); + Future<Integer> result2 = pool.submit(repack2); + assertEquals(0, result1.get().intValue() + result2.get().intValue()); + } finally { + pool.shutdown(); + pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + } + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java new file mode 100644 index 0000000000..50e497e67a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertEquals; + +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.junit.Test; + +public class GcDirCacheSavesObjectsTest extends GcTestCase { + @Test + public void testDirCacheSavesObjects() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + bb.commit().add("A", "A3"); // this new content in index should survive + stats = gc.getStatistics(); + assertEquals(9, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(1, stats.numberOfLooseObjects); + assertEquals(8, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } + + @Test + public void testDirCacheSavesObjectsWithPruneNow() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + bb.commit().add("A", "A3"); // this new content in index should survive + stats = gc.getStatistics(); + assertEquals(9, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + gc.setExpireAgeMillis(0); + fsTick(); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(8, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java new file mode 100644 index 0000000000..9e28298823 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Iterator; + +import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.junit.Test; + +public class GcKeepFilesTest extends GcTestCase { + @Test + public void testKeepFiles() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + bb.commit().add("A", "A").add("B", "B").create(); + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + assertEquals(0, stats.numberOfPackFiles); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + + Iterator<PackFile> packIt = repo.getObjectDatabase().getPacks() + .iterator(); + PackFile singlePack = packIt.next(); + assertFalse(packIt.hasNext()); + File keepFile = new File(singlePack.getPackFile().getPath() + ".keep"); + assertFalse(keepFile.exists()); + assertTrue(keepFile.createNewFile()); + bb.commit().add("A", "A2").add("B", "B2").create(); + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(8, stats.numberOfPackedObjects); + assertEquals(2, stats.numberOfPackFiles); + + // check that no object is packed twice + Iterator<PackFile> packs = repo.getObjectDatabase().getPacks() + .iterator(); + PackIndex ind1 = packs.next().getIndex(); + assertEquals(4, ind1.getObjectCount()); + PackIndex ind2 = packs.next().getIndex(); + assertEquals(4, ind2.getObjectCount()); + for (MutableEntry e: ind1) + if (ind2.hasObject(e.toObjectId())) + assertFalse( + "the following object is in both packfiles: " + + e.toObjectId(), + ind2.hasObject(e.toObjectId())); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java new file mode 100644 index 0000000000..0ade902601 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static java.lang.Integer.valueOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jgit.lib.Ref.Storage; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.revwalk.RevBlob; +import org.junit.Test; + +public class GcPackRefsTest extends GcTestCase { + @Test + public void looseRefPacked() throws Exception { + RevBlob a = tr.blob("a"); + tr.lightweightTag("t", a); + + gc.packRefs(); + assertSame(repo.getRef("t").getStorage(), Storage.PACKED); + } + + @Test + public void concurrentOnlyOneWritesPackedRefs() throws Exception { + RevBlob a = tr.blob("a"); + tr.lightweightTag("t", a); + + final CyclicBarrier syncPoint = new CyclicBarrier(2); + + Callable<Integer> packRefs = new Callable<Integer>() { + + /** @return 0 for success, 1 in case of error when writing pack */ + public Integer call() throws Exception { + syncPoint.await(); + try { + gc.packRefs(); + return valueOf(0); + } catch (IOException e) { + return valueOf(1); + } + } + }; + ExecutorService pool = Executors.newFixedThreadPool(2); + try { + Future<Integer> p1 = pool.submit(packRefs); + Future<Integer> p2 = pool.submit(packRefs); + assertEquals(1, p1.get().intValue() + p2.get().intValue()); + } finally { + pool.shutdown(); + pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + } + } + + @Test + public void whileRefLockedRefNotPackedNoError() + throws Exception { + RevBlob a = tr.blob("a"); + tr.lightweightTag("t1", a); + tr.lightweightTag("t2", a); + LockFile refLock = new LockFile(new File(repo.getDirectory(), + "refs/tags/t1"), repo.getFS()); + try { + refLock.lock(); + gc.packRefs(); + } finally { + refLock.unlock(); + } + + assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE); + assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED); + } + + @Test + public void whileRefUpdatedRefUpdateSucceeds() + throws Exception { + RevBlob a = tr.blob("a"); + tr.lightweightTag("t", a); + final RevBlob b = tr.blob("b"); + + final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2); + final CyclicBarrier packRefsDone = new CyclicBarrier(2); + ExecutorService pool = Executors.newFixedThreadPool(2); + try { + Future<Result> result = pool.submit(new Callable<Result>() { + + public Result call() throws Exception { + RefUpdate update = new RefDirectoryUpdate( + (RefDirectory) repo.getRefDatabase(), + repo.getRef("refs/tags/t")) { + @Override + public boolean isForceUpdate() { + try { + refUpdateLockedRef.await(); + packRefsDone.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (BrokenBarrierException e) { + Thread.currentThread().interrupt(); + } + return super.isForceUpdate(); + } + }; + update.setForceUpdate(true); + update.setNewObjectId(b); + return update.update(); + } + }); + + pool.submit(new Callable<Void>() { + public Void call() throws Exception { + refUpdateLockedRef.await(); + gc.packRefs(); + packRefsDone.await(); + return null; + } + }); + + assertSame(result.get(), Result.FORCED); + + } finally { + pool.shutdownNow(); + pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); + } + + assertEquals(repo.getRef("refs/tags/t").getObjectId(), b); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java new file mode 100644 index 0000000000..5b1a4178a6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collections; +import java.util.Date; + +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevTree; +import org.junit.Test; + +public class GcPruneNonReferencedTest extends GcTestCase { + @Test + public void nonReferencedNonExpiredObject_notPruned() throws Exception { + RevBlob a = tr.blob("a"); + gc.setExpire(new Date(lastModified(a))); + gc.prune(Collections.<ObjectId> emptySet()); + assertTrue(repo.hasObject(a)); + } + + @Test + public void nonReferencedExpiredObject_pruned() throws Exception { + RevBlob a = tr.blob("a"); + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + assertFalse(repo.hasObject(a)); + } + + @Test + public void nonReferencedExpiredObjectTree_pruned() throws Exception { + RevBlob a = tr.blob("a"); + RevTree t = tr.tree(tr.file("a", a)); + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + assertFalse(repo.hasObject(t)); + assertFalse(repo.hasObject(a)); + } + + @Test + public void nonReferencedObjects_onlyExpiredPruned() throws Exception { + RevBlob a = tr.blob("a"); + gc.setExpire(new Date(lastModified(a) + 1)); + + fsTick(); + RevBlob b = tr.blob("b"); + + gc.prune(Collections.<ObjectId> emptySet()); + assertFalse(repo.hasObject(a)); + assertTrue(repo.hasObject(b)); + } + + @Test + public void testPackCommitsAndLooseOneWithPruneNow() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + tr.update("refs/heads/master", first); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + gc.setExpireAgeMillis(0); + fsTick(); + gc.gc(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(8, stats.numberOfPackedObjects); + assertEquals(2, stats.numberOfPackFiles); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java new file mode 100644 index 0000000000..2a096fd1c4 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.util.Collections; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.junit.TestRepository.BranchBuilder; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.util.FileUtils; +import org.junit.Test; + +public class GcReflogTest extends GcTestCase { + @Test + public void testPruneNone() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master") + .delete(); + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + tr.blob("x"); + stats = gc.getStatistics(); + assertEquals(9, stats.numberOfLooseObjects); + gc.prune(Collections.<ObjectId> emptySet()); + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + } + + @Test + public void testPackRepoWithCorruptReflog() throws Exception { + // create a reflog entry "0000... 0000... foobar" by doing an initial + // refupdate for HEAD which points to a non-existing ref. The + // All-Projects repo of gerrit instances had such entries + RefUpdate ru = repo.updateRef(Constants.HEAD); + ru.link("refs/to/garbage"); + tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") + .create(); + // make sure HEAD exists + Git.wrap(repo).checkout().setName("refs/heads/master").call(); + gc.gc(); + } + + @Test + public void testPackCommitsAndLooseOneNoReflog() throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + tr.update("refs/heads/master", first); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + + FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"), + FileUtils.RETRY | FileUtils.SKIP_MISSING); + FileUtils.delete( + new File(repo.getDirectory(), "logs/refs/heads/master"), + FileUtils.RETRY | FileUtils.SKIP_MISSING); + gc.gc(); + + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } + + @Test + public void testPackCommitsAndLooseOneWithPruneNowNoReflog() + throws Exception { + BranchBuilder bb = tr.branch("refs/heads/master"); + RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + tr.update("refs/heads/master", first); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + + FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"), + FileUtils.RETRY | FileUtils.SKIP_MISSING); + FileUtils.delete( + new File(repo.getDirectory(), "logs/refs/heads/master"), + FileUtils.RETRY | FileUtils.SKIP_MISSING); + gc.setExpireAgeMillis(0); + gc.gc(); + + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java new file mode 100644 index 0000000000..4afbeff3ec --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import static org.junit.Assert.assertTrue; + +import java.util.Collections; + +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevTag; +import org.junit.Test; + +public class GcTagTest extends GcTestCase { + @Test + public void lightweightTag_objectNotPruned() throws Exception { + RevBlob a = tr.blob("a"); + tr.lightweightTag("t", a); + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + assertTrue(repo.hasObject(a)); + } + + @Test + public void annotatedTag_objectNotPruned() throws Exception { + RevBlob a = tr.blob("a"); + RevTag t = tr.tag("t", a); // this doesn't create the refs/tags/t ref + tr.lightweightTag("t", t); + + gc.setExpireAgeMillis(0); + fsTick(); + gc.prune(Collections.<ObjectId> emptySet()); + assertTrue(repo.hasObject(t)); + assertTrue(repo.hasObject(a)); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java new file mode 100644 index 0000000000..a764f0fddd --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.internal.storage.file; + +import java.io.IOException; + +import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics; +import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.junit.TestRepository.CommitBuilder; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.After; +import org.junit.Before; + +public abstract class GcTestCase extends LocalDiskRepositoryTestCase { + protected TestRepository<FileRepository> tr; + protected FileRepository repo; + protected GC gc; + protected RepoStatistics stats; + + @Before + public void setUp() throws Exception { + super.setUp(); + repo = createWorkRepository(); + tr = new TestRepository<FileRepository>((repo)); + gc = new GC(repo); + } + + @After + public void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Create a chain of commits of given depth. + * <p> + * Each commit contains one file named "a" containing the index of the + * commit in the chain as its content. The created commit chain is + * referenced from any ref. + * <p> + * A chain of depth = N will create 3*N objects in Gits object database. For + * each depth level three objects are created: the commit object, the + * top-level tree object and a blob for the content of the file "a". + * + * @param depth + * the depth of the commit chain. + * @return the commit that is the tip of the commit chain + * @throws Exception + */ + protected RevCommit commitChain(int depth) throws Exception { + if (depth <= 0) + throw new IllegalArgumentException("Chain depth must be > 0"); + CommitBuilder cb = tr.commit(); + RevCommit tip; + do { + --depth; + tip = cb.add("a", "" + depth).message("" + depth).create(); + cb = cb.child(); + } while (depth > 0); + return tip; + } + + protected long lastModified(AnyObjectId objectId) { + return repo.getObjectDatabase().fileFor(objectId).lastModified(); + } + + protected static void fsTick() throws InterruptedException, IOException { + RepositoryTestCase.fsTick(null); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java index 75ccb6b576..aba48bc35f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java @@ -67,7 +67,6 @@ import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.internal.storage.pack.PackWriter.ObjectIdSet; import org.eclipse.jgit.junit.JGitTestUtil; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.lib.AnyObjectId; @@ -79,6 +78,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.pack.PackConfig; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.transport.PackParser; import org.junit.After; import org.junit.Before; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java index 21df179ad8..1731a33622 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java @@ -61,7 +61,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -75,6 +74,7 @@ import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; public class RefUpdateTest extends SampleDataRepositoryTestCase { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java index a0dff7ee86..63bd7f4684 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/ReflogReaderTest.java @@ -55,13 +55,13 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.List; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.CheckoutEntry; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.ReflogEntry; import org.eclipse.jgit.lib.ReflogReader; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; public class ReflogReaderTest extends SampleDataRepositoryTestCase { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java index 921ec29d53..0ab013baff 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0003_BasicTest.java @@ -63,7 +63,6 @@ import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.internal.JGitText; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; @@ -83,6 +82,7 @@ import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.util.FileUtils; import org.junit.Test; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java index dc78909d4a..e8e3d1db68 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/T0004_PackReaderTest.java @@ -46,35 +46,35 @@ package org.eclipse.jgit.internal.storage.file; -import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX; -import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import java.io.File; import java.io.IOException; -import org.eclipse.jgit.internal.storage.file.PackFile; -import org.eclipse.jgit.internal.storage.file.WindowCursor; -import org.eclipse.jgit.junit.JGitTestUtil; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; public class T0004_PackReaderTest extends SampleDataRepositoryTestCase { - private static final String PACK_NAME = "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f"; - private static final File TEST_PACK = JGitTestUtil.getTestResourceFile(PACK_NAME + ".pack"); + private static final String PACK_NAME = "34be9032ac282b11fa9babdc2b2a93ca996c9c2f"; @Test public void test003_lookupCompressedObject() throws IOException { - final PackFile pr; final ObjectId id; final ObjectLoader or; + PackFile pr = null; + for (PackFile p : db.getObjectDatabase().getPacks()) { + if (PACK_NAME.equals(p.getPackName())) { + pr = p; + break; + } + } + assertNotNull("have pack-" + PACK_NAME, pr); + id = ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"); - pr = new PackFile(TEST_PACK, PACK.getBit() | INDEX.getBit()); or = pr.get(new WindowCursor(null), id); assertNotNull(or); assertEquals(Constants.OBJ_TREE, or.getType()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java index f0942203b5..aa061bafc0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/WindowCacheGetTest.java @@ -56,11 +56,11 @@ import java.util.List; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.junit.JGitTestUtil; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.storage.file.WindowCacheConfig; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.util.MutableInteger; import org.junit.Before; import org.junit.Test; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java index b3219cddb5..d29a75ef71 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutMaliciousPathTest.java @@ -405,7 +405,7 @@ public class DirCacheCheckoutMaliciousPathTest extends RepositoryTestCase { } catch (InvalidPathException e) { if (good) throw e; - assertTrue(e.getMessage().startsWith("Invalid path: ")); + assertTrue(e.getMessage().startsWith("Invalid path")); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index cb2ae64db2..98ec706bec 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -467,7 +467,9 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { *3 D F D Y N N Keep *4 D F D N N N Conflict *5 D F F Y N N Y Keep + *5b D F F Y N N N Conflict *6 D F F N N N Y Keep + *6b D F F N N N N Conflict *7 F D F Y Y N N Update *8 F D F N Y N N Conflict *9 F D F Y N N N Update @@ -540,7 +542,17 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { // 5 doit(mk("DF/DF"), mk("DF"), mk("DF")); assertRemoved("DF/DF"); + assertEquals(0, dco.getConflicts().size()); + assertEquals(0, dco.getUpdated().size()); + } + @Test + public void testDirectoryFileConflicts_5b() throws Exception { + // 5 + doit(mk("DF/DF"), mkmap("DF", "different"), mk("DF")); + assertRemoved("DF/DF"); + assertConflict("DF"); + assertEquals(0, dco.getUpdated().size()); } @Test @@ -550,6 +562,19 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { writeTrashFile("DF", "different"); go(); assertRemoved("DF/DF"); + assertEquals(0, dco.getConflicts().size()); + assertEquals(0, dco.getUpdated().size()); + } + + @Test + public void testDirectoryFileConflicts_6b() throws Exception { + // 6 + setupCase(mk("DF/DF"), mk("DF"), mkmap("DF", "different")); + writeTrashFile("DF", "again different"); + go(); + assertRemoved("DF/DF"); + assertConflict("DF"); + assertEquals(0, dco.getUpdated().size()); } @Test diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java index 7c06e86915..26c4d12cc0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java @@ -59,9 +59,9 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.Map; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate.Result; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; /** diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java index 381e1317b1..c9ea286407 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RepositoryResolveTest.java @@ -59,8 +59,8 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.RevisionSyntaxException; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; public class RepositoryResolveTest extends SampleDataRepositoryTestCase { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java index 2682cad0a0..651e62c9ca 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/T0002_TreeTest.java @@ -55,7 +55,7 @@ import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; @SuppressWarnings("deprecation") diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java index 434f53d4b7..21ef7479a2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java @@ -47,13 +47,13 @@ import static org.junit.Assert.assertEquals; import java.io.IOException; import java.util.Arrays; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.SymbolicRef; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Before; import org.junit.Test; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java index a06bcfff78..db9d87dd17 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SimpleMergeTest.java @@ -52,12 +52,12 @@ import java.io.IOException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.treewalk.TreeWalk; import org.junit.Test; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java index 1fddcdc167..b7b2291429 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java @@ -47,9 +47,9 @@ import static org.junit.Assert.assertEquals; import java.util.Arrays; import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.util.GitDateFormatter; import org.eclipse.jgit.util.GitDateFormatter.Format; import org.junit.Before; diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SampleDataRepositoryTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java index fb4b9e9f80..3a3b3d8d8f 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SampleDataRepositoryTestCase.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/test/resources/SampleDataRepositoryTestCase.java @@ -44,10 +44,13 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package org.eclipse.jgit.junit; +package org.eclipse.jgit.test.resources; import java.io.File; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; + /** Test case which includes C Git generated pack files for testing. */ public abstract class SampleDataRepositoryTestCase extends RepositoryTestCase { @@ -66,11 +69,11 @@ public abstract class SampleDataRepositoryTestCase extends RepositoryTestCase { }; final File packDir = new File(db.getObjectDatabase().getDirectory(), "pack"); for (String n : packs) { - copyFile(JGitTestUtil.getTestResourceFile(n + ".pack"), new File(packDir, n + ".pack")); - copyFile(JGitTestUtil.getTestResourceFile(n + ".idx"), new File(packDir, n + ".idx")); + JGitTestUtil.copyTestResource(n + ".pack", new File(packDir, n + ".pack")); + JGitTestUtil.copyTestResource(n + ".idx", new File(packDir, n + ".idx")); } - copyFile(JGitTestUtil.getTestResourceFile("packed-refs"), new File(db - .getDirectory(), "packed-refs")); + JGitTestUtil.copyTestResource("packed-refs", + new File(db.getDirectory(), "packed-refs")); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java index 02ce186669..efc3834f45 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java @@ -61,13 +61,13 @@ import java.util.Set; import org.eclipse.jgit.errors.MissingBundlePrerequisiteException; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.Test; public class BundleWriterTest extends SampleDataRepositoryTestCase { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java index e523db981a..28473c79fa 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushProcessTest.java @@ -55,7 +55,6 @@ import java.util.Map; import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; import org.eclipse.jgit.lib.ProgressMonitor; @@ -63,6 +62,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate.Result; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TextProgressMonitor; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.eclipse.jgit.transport.RemoteRefUpdate.Status; import org.junit.Before; import org.junit.Test; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java index 28a3f4428e..55e1e44206 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TransportTest.java @@ -55,12 +55,12 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import org.eclipse.jgit.junit.SampleDataRepositoryTestCase; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; import org.junit.After; import org.junit.Before; import org.junit.Test; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserBadlyFormattedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserBadlyFormattedTest.java index e0e99a14c2..a6af3a5143 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserBadlyFormattedTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserBadlyFormattedTest.java @@ -92,7 +92,8 @@ public class GitDateParserBadlyFormattedTest { Calendar ref = new GregorianCalendar(SystemReader.getInstance() .getTimeZone(), SystemReader.getInstance().getLocale()); try { - GitDateParser.parse(dateStr, ref); + GitDateParser.parse(dateStr, ref, SystemReader.getInstance() + .getLocale()); fail("The expected ParseException while parsing '" + dateStr + "' did not occur."); } catch (ParseException e) { @@ -103,7 +104,8 @@ public class GitDateParserBadlyFormattedTest { @Theory public void badlyFormattedWithoutRef() { try { - GitDateParser.parse(dateStr, null); + GitDateParser.parse(dateStr, null, SystemReader.getInstance() + .getLocale()); fail("The expected ParseException while parsing '" + dateStr + "' did not occur."); } catch (ParseException e) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserTest.java index 570f4999dd..518ed53fde 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/GitDateParserTest.java @@ -72,7 +72,8 @@ public class GitDateParserTest { GregorianCalendar cal = new GregorianCalendar(SystemReader .getInstance().getTimeZone(), SystemReader.getInstance() .getLocale()); - Date parse = GitDateParser.parse("yesterday", cal); + Date parse = GitDateParser.parse("yesterday", cal, SystemReader + .getInstance().getLocale()); cal.add(Calendar.DATE, -1); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); @@ -87,7 +88,8 @@ public class GitDateParserTest { GregorianCalendar cal = new GregorianCalendar(SystemReader .getInstance().getTimeZone(), SystemReader.getInstance() .getLocale()); - Date parse = GitDateParser.parse("never", cal); + Date parse = GitDateParser.parse("never", cal, SystemReader + .getInstance().getLocale()); Assert.assertEquals(GitDateParser.NEVER, parse); parse = GitDateParser.parse("never", null); Assert.assertEquals(GitDateParser.NEVER, parse); @@ -104,7 +106,8 @@ public class GitDateParserTest { .getLocale()); cal.setTime(refDate); - Date parse = GitDateParser.parse("now", cal); + Date parse = GitDateParser.parse("now", cal, SystemReader.getInstance() + .getLocale()); Assert.assertEquals(refDate, parse); long t1 = SystemReader.getInstance().getCurrentTime(); parse = GitDateParser.parse("now", null); @@ -123,7 +126,8 @@ public class GitDateParserTest { .getLocale()); cal.setTime(refDate); - Date parse = GitDateParser.parse("2 weeks ago", cal); + Date parse = GitDateParser.parse("2 weeks ago", cal, SystemReader + .getInstance().getLocale()); Assert.assertEquals(df.parse("2007-02-07 15:35:00 +0100"), parse); } @@ -138,7 +142,8 @@ public class GitDateParserTest { .getLocale()); cal.setTime(refDate); - Date parse = GitDateParser.parse("2 weeks ago", cal); + Date parse = GitDateParser.parse("2 weeks ago", cal, SystemReader.getInstance() + .getLocale()); Assert.assertEquals(df.parse("2007-02-07 15:35:00 +0100"), parse); parse = GitDateParser.parse("3 days 2 weeks ago", cal); Assert.assertEquals(df.parse("2007-02-04 15:35:00 +0100"), parse); @@ -151,7 +156,8 @@ public class GitDateParserTest { String dateStr = "2007-02-21 15:35:00 +0100"; Date exp = SystemReader.getInstance() .getSimpleDateFormat("yyyy-MM-dd HH:mm:ss Z").parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } @@ -161,7 +167,8 @@ public class GitDateParserTest { Date exp = SystemReader.getInstance() .getSimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z") .parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } @@ -170,7 +177,8 @@ public class GitDateParserTest { String dateStr = "2007-02-21"; Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy-MM-dd") .parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } @@ -179,7 +187,8 @@ public class GitDateParserTest { String dateStr = "2007.02.21"; Date exp = SystemReader.getInstance().getSimpleDateFormat("yyyy.MM.dd") .parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } @@ -188,7 +197,8 @@ public class GitDateParserTest { String dateStr = "02/21/2007"; Date exp = SystemReader.getInstance().getSimpleDateFormat("MM/dd/yyyy") .parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } @@ -197,7 +207,8 @@ public class GitDateParserTest { String dateStr = "21.02.2007"; Date exp = SystemReader.getInstance().getSimpleDateFormat("dd.MM.yyyy") .parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } @@ -207,7 +218,8 @@ public class GitDateParserTest { Date exp = SystemReader.getInstance() .getSimpleDateFormat("EEE MMM dd HH:mm:ss yyyy Z") .parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } @@ -216,7 +228,8 @@ public class GitDateParserTest { String dateStr = "Wed Feb 21 15:35:00 2007"; Date exp = SystemReader.getInstance() .getSimpleDateFormat("EEE MMM dd HH:mm:ss yyyy").parse(dateStr); - Date parse = GitDateParser.parse(dateStr, null); + Date parse = GitDateParser.parse(dateStr, null, SystemReader + .getInstance().getLocale()); Assert.assertEquals(exp, parse); } } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 474b8f349c..bb67c127a7 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -270,6 +270,10 @@ invalidObject=Invalid {0} {1}:{2} invalidOldIdSent=invalid old id sent invalidPacketLineHeader=Invalid packet line header: {0} invalidPath=Invalid path: {0} +invalidPathContainsSeparator=Invalid path (contains separator ''{0}''): {1} +invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0} +invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0} +invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} invalidReflogRevision=Invalid reflog revision: {0} invalidRefName=Invalid ref name: {0} invalidRemote=Invalid remote: {0} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index ef2e987b4e..c23256c74b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -93,11 +93,15 @@ public class AddCommand extends GitCommand<DirCache> { } /** + * Add a path to a file/directory whose content should be added. + * <p> + * A directory name (e.g. <code>dir</code> to add <code>dir/file1</code> and + * <code>dir/file2</code>) can also be given to add all files in the + * directory, recursively. Fileglobs (e.g. *.c) are not yet supported. + * * @param filepattern - * File to add content from. Also a leading directory name (e.g. - * dir to add dir/file1 and dir/file2) can be given to add all - * files in the directory, recursively. Fileglobs (e.g. *.c) are - * not yet supported. + * repository-relative path of file/directory to add (with + * <code>/</code> as separator) * @return {@code this} */ public AddCommand addFilepattern(String filepattern) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java index 12be64bb02..11dfd1c585 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java @@ -86,9 +86,10 @@ public class BlameCommand extends GitCommand<BlameResult> { } /** - * Set file path + * Set file path. * * @param filePath + * file path (with <code>/</code> as separator) * @return this command */ public BlameCommand setFilePath(String filePath) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java index eb447f3142..8dfd211a08 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -305,15 +305,16 @@ public class CheckoutCommand extends GitCommand<Ref> { } /** - * Add a single path to the list of paths to check out. To check out all - * paths, use {@link #setAllPaths(boolean)}. + * Add a single slash-separated path to the list of paths to check out. To + * check out all paths, use {@link #setAllPaths(boolean)}. * <p> * If this option is set, neither the {@link #setCreateBranch(boolean)} nor * {@link #setName(String)} option is considered. In other words, these * options are exclusive. * * @param path - * path to update in the working tree and index + * path to update in the working tree and index (with + * <code>/</code> as separator) * @return {@code this} */ public CheckoutCommand addPath(String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java index b42c80f67b..f273eafe1f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CleanCommand.java @@ -174,7 +174,7 @@ public class CleanCommand extends GitCommand<Set<String>> { * If paths are set, only these paths are affected by the cleaning. * * @param paths - * the paths to set + * the paths to set (with <code>/</code> as separator) * @return {@code this} */ public CleanCommand setPaths(Set<String> paths) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 399d78e095..21d7138c9f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -667,14 +667,14 @@ public class CommitCommand extends GitCommand<RevCommit> { } /** - * Commit dedicated path only - * + * Commit dedicated path only. + * <p> * This method can be called several times to add multiple paths. Full file * paths are supported as well as directory paths; in the latter case this - * commits all files/ directories below the specified path. + * commits all files/directories below the specified path. * * @param only - * path to commit + * path to commit (with <code>/</code> as separator) * @return {@code this} */ public CommitCommand setOnly(String only) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index 08ab88005d..983b6b552e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -125,6 +125,26 @@ public class Git { } /** + * Frees resources held by the underlying {@link Repository} instance. It is + * recommended to call this method as soon as you don't need a reference to + * this {@link Git} instance and the underlying {@link Repository} instance + * anymore. This method closes the underlying object and ref databases. This + * will free memory and file handles. E.g. on Windows the repository will + * keep file handles on pack files unless you call this method. Such open + * file handles may for example prevent that the repository folder in the + * filesystem can be deleted. + * <p> + * After calling close() you should not use this {@link Git} instance and + * the underlying {@link Repository} instance anymore. + * + * @since 3.2 + */ + public void close() { + if (repo != null) + repo.close(); + } + + /** * Returns a command object to execute a {@code clone} command * * @see <a diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java index 51a233458f..9690f799c1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java @@ -282,11 +282,11 @@ public class LogCommand extends GitCommand<Iterable<RevCommit>> { /** * Show only commits that affect any of the specified paths. The path must - * either name a file or a directory exactly. Note that regex expressions or - * wildcards are not supported. + * either name a file or a directory exactly and use <code>/</code> (slash) + * as separator. Note that regex expressions or wildcards are not supported. * * @param path - * a path is relative to the top level of the repository + * a repository-relative path (with <code>/</code> as separator) * @return {@code this} */ public LogCommand addPath(String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java index 509203e528..78b260df71 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/MergeCommand.java @@ -379,8 +379,7 @@ public class MergeCommand extends GitCommand<MergeResult> { if (failingPaths != null) { repo.writeMergeCommitMsg(null); repo.writeMergeHeads(null); - return new MergeResult(null, - merger.getBaseCommit(0, 1), + return new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcCommit.getId() }, MergeStatus.FAILED, mergeStrategy, @@ -390,8 +389,7 @@ public class MergeCommand extends GitCommand<MergeResult> { .formatWithConflicts(mergeMessage, unmergedPaths); repo.writeMergeCommitMsg(mergeMessageWithConflicts); - return new MergeResult(null, - merger.getBaseCommit(0, 1), + return new MergeResult(null, merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcCommit.getId() }, MergeStatus.CONFLICTING, mergeStrategy, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java index 8ae51d74ce..10b273a744 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -70,6 +70,7 @@ import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.NoMessageException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; +import org.eclipse.jgit.api.errors.StashApplyFailureException; import org.eclipse.jgit.api.errors.UnmergedPathsException; import org.eclipse.jgit.api.errors.WrongRepositoryStateException; import org.eclipse.jgit.diff.DiffFormatter; @@ -79,6 +80,7 @@ import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AbbreviatedObjectId; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; @@ -158,6 +160,10 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private static final String MESSAGE_SQUASH = "message-squash"; //$NON-NLS-1$ + private static final String AUTOSTASH = "autostash"; //$NON-NLS-1$ + + private static final String AUTOSTASH_MSG = "On {0}: autostash"; + /** * The available operations */ @@ -257,11 +263,26 @@ public class RebaseCommand extends GitCommand<RebaseResult> { .resolve(upstreamCommitId)); break; case BEGIN: + autoStash(); + if (stopAfterInitialization + || !walk.isMergedInto( + walk.parseCommit(repo.resolve(Constants.HEAD)), + upstreamCommit)) { + org.eclipse.jgit.api.Status status = Git.wrap(repo) + .status().call(); + if (status.hasUncommittedChanges()) { + List<String> list = new ArrayList<String>(); + list.addAll(status.getUncommittedChanges()); + return RebaseResult.uncommittedChanges(list); + } + } RebaseResult res = initFilesAndRewind(); if (stopAfterInitialization) return RebaseResult.INTERACTIVE_PREPARED_RESULT; - if (res != null) + if (res != null) { + autoStashApply(); return res; + } } if (monitor.isCancelled()) @@ -321,12 +342,63 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } return finishRebase(newHead, lastStepWasForward); } catch (CheckoutConflictException cce) { - return new RebaseResult(cce.getConflictingPaths()); + return RebaseResult.conflicts(cce.getConflictingPaths()); } catch (IOException ioe) { throw new JGitInternalException(ioe.getMessage(), ioe); } } + private void autoStash() throws GitAPIException, IOException { + if (repo.getConfig().getBoolean(ConfigConstants.CONFIG_REBASE_SECTION, + ConfigConstants.CONFIG_KEY_AUTOSTASH, false)) { + String message = MessageFormat.format( + AUTOSTASH_MSG, + Repository + .shortenRefName(getHeadName(getHead()))); + RevCommit stashCommit = Git.wrap(repo).stashCreate().setRef(null) + .setWorkingDirectoryMessage( + message) + .call(); + if (stashCommit != null) { + FileUtils.mkdir(rebaseState.getDir()); + rebaseState.createFile(AUTOSTASH, stashCommit.getName()); + } + } + } + + private boolean autoStashApply() throws IOException, GitAPIException { + boolean conflicts = false; + if (rebaseState.getFile(AUTOSTASH).exists()) { + String stash = rebaseState.readFile(AUTOSTASH); + try { + Git.wrap(repo).stashApply().setStashRef(stash) + .ignoreRepositoryState(true).call(); + } catch (StashApplyFailureException e) { + conflicts = true; + RevWalk rw = new RevWalk(repo); + ObjectId stashId = repo.resolve(stash); + RevCommit commit = rw.parseCommit(stashId); + updateStashRef(commit, commit.getAuthorIdent(), + commit.getShortMessage()); + } + } + return conflicts; + } + + private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, + String refLogMessage) throws IOException { + Ref currentRef = repo.getRef(Constants.R_STASH); + RefUpdate refUpdate = repo.updateRef(Constants.R_STASH); + refUpdate.setNewObjectId(commitId); + refUpdate.setRefLogIdent(refLogIdent); + refUpdate.setRefLogMessage(refLogMessage, false); + if (currentRef != null) + refUpdate.setExpectedOldObjectId(currentRef.getObjectId()); + else + refUpdate.setExpectedOldObjectId(ObjectId.zeroId()); + refUpdate.forceUpdate(); + } + private RebaseResult processStep(RebaseTodoLine step, boolean shouldPick) throws IOException, GitAPIException { if (Action.COMMENT.equals(step.getAction())) @@ -340,7 +412,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { RevCommit commitToPick = walk.parseCommit(ids.iterator().next()); if (shouldPick) { if (monitor.isCancelled()) - return new RebaseResult(commitToPick, Status.STOPPED); + return RebaseResult.result(Status.STOPPED, commitToPick); RebaseResult result = cherryPickCommit(commitToPick); if (result != null) return result; @@ -403,8 +475,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { switch (cherryPickResult.getStatus()) { case FAILED: if (operation == Operation.BEGIN) - return abort(new RebaseResult( - cherryPickResult.getFailingPaths())); + return abort(RebaseResult.failed(cherryPickResult + .getFailingPaths())); else return stop(commitToPick, Status.STOPPED); case CONFLICTING: @@ -420,10 +492,13 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } private RebaseResult finishRebase(RevCommit newHead, - boolean lastStepWasForward) throws IOException { + boolean lastStepWasForward) throws IOException, GitAPIException { String headName = rebaseState.readFile(HEAD_NAME); updateHead(headName, newHead, upstreamCommit); + boolean stashConflicts = autoStashApply(); FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); + if (stashConflicts) + return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; if (lastStepWasForward || newHead == null) return RebaseResult.FAST_FORWARD_RESULT; return RebaseResult.OK_RESULT; @@ -550,7 +625,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } else { sb.append("# The ").append(count).append(ordinal) .append(" commit message will be skipped:\n# "); - sb.append(commitToPick.getFullMessage().replaceAll("([\n\r]+)", + sb.append(commitToPick.getFullMessage().replaceAll("([\n\r])", "$1# ")); } // Add the previous message without header (i.e first line) @@ -735,7 +810,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { // Remove cherry pick state file created by CherryPickCommand, it's not // needed for rebase repo.writeCherryPickHead(null); - return new RebaseResult(commitToPick, status); + return RebaseResult.result(status, commitToPick); } String toAuthorScript(PersonIdent author) { @@ -797,16 +872,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> { // we need to store everything into files so that we can implement // --skip, --continue, and --abort - Ref head = repo.getRef(Constants.HEAD); - if (head == null || head.getObjectId() == null) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().refNotResolved, Constants.HEAD)); + Ref head = getHead(); - String headName; - if (head.isSymbolic()) - headName = head.getTarget().getName(); - else - headName = head.getObjectId().getName(); + String headName = getHeadName(head); ObjectId headId = head.getObjectId(); if (headId == null) throw new RefNotFoundException(MessageFormat.format( @@ -845,7 +913,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { Collections.reverse(cherryPickList); // create the folder for the meta information - FileUtils.mkdir(rebaseState.getDir()); + FileUtils.mkdir(rebaseState.getDir(), true); repo.writeOrigHead(headId); rebaseState.createFile(REBASE_HEAD, headId.name()); @@ -881,6 +949,23 @@ public class RebaseCommand extends GitCommand<RebaseResult> { return null; } + private static String getHeadName(Ref head) { + String headName; + if (head.isSymbolic()) + headName = head.getTarget().getName(); + else + headName = head.getObjectId().getName(); + return headName; + } + + private Ref getHead() throws IOException, RefNotFoundException { + Ref head = repo.getRef(Constants.HEAD); + if (head == null || head.getObjectId() == null) + throw new RefNotFoundException(MessageFormat.format( + JGitText.get().refNotResolved, Constants.HEAD)); + return head; + } + private boolean isInteractive() { return interactiveHandler != null; } @@ -895,10 +980,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { */ public RevCommit tryFastForward(RevCommit newCommit) throws IOException, GitAPIException { - Ref head = repo.getRef(Constants.HEAD); - if (head == null || head.getObjectId() == null) - throw new RefNotFoundException(MessageFormat.format( - JGitText.get().refNotResolved, Constants.HEAD)); + Ref head = getHead(); ObjectId headId = head.getObjectId(); if (headId == null) @@ -908,11 +990,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { if (walk.isMergedInto(newCommit, headCommit)) return newCommit; - String headName; - if (head.isSymbolic()) - headName = head.getTarget().getName(); - else - headName = head.getObjectId().getName(); + String headName = getHeadName(head); return tryFastForward(headName, headCommit, newCommit); } @@ -992,7 +1070,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } } - private RebaseResult abort(RebaseResult result) throws IOException { + private RebaseResult abort(RebaseResult result) throws IOException, + GitAPIException { try { ObjectId origHead = repo.readOrigHead(); String commitId = origHead != null ? origHead.name() : null; @@ -1041,9 +1120,12 @@ public class RebaseCommand extends GitCommand<RebaseResult> { JGitText.get().abortingRebaseFailed); } } + boolean stashConflicts = autoStashApply(); // cleanup the files FileUtils.delete(rebaseState.getDir(), FileUtils.RECURSIVE); repo.writeCherryPickHead(null); + if (stashConflicts) + return RebaseResult.STASH_APPLY_CONFLICTS_RESULT; return result; } finally { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java index aaa75d9b88..92c1347ab2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseResult.java @@ -105,6 +105,18 @@ public class RebaseResult { } }, /** + * The repository contains uncommitted changes and the rebase is not a + * fast-forward + * + * @since 3.2 + */ + UNCOMMITTED_CHANGES { + @Override + public boolean isSuccessful() { + return false; + } + }, + /** * Conflicts: checkout of target HEAD failed */ CONFLICTS { @@ -153,6 +165,18 @@ public class RebaseResult { public boolean isSuccessful() { return false; } + }, + + /** + * Applying stash resulted in conflicts + * + * @since 3.2 + */ + STASH_APPLY_CONFLICTS { + @Override + public boolean isSuccessful() { + return true; + } }; /** @@ -177,6 +201,9 @@ public class RebaseResult { static final RebaseResult INTERACTIVE_PREPARED_RESULT = new RebaseResult( Status.INTERACTIVE_PREPARED); + static final RebaseResult STASH_APPLY_CONFLICTS_RESULT = new RebaseResult( + Status.STASH_APPLY_CONFLICTS); + private final Status status; private final RevCommit currentCommit; @@ -185,21 +212,29 @@ public class RebaseResult { private List<String> conflicts; + private List<String> uncommittedChanges; + private RebaseResult(Status status) { this.status = status; currentCommit = null; } + private RebaseResult(Status status, RevCommit commit) { + this.status = status; + currentCommit = commit; + } + /** - * Create <code>RebaseResult</code> with status {@link Status#STOPPED} + * Create <code>RebaseResult</code> * + * @param status * @param commit * current commit - * @param status + * @return the RebaseResult */ - RebaseResult(RevCommit commit, RebaseResult.Status status) { - this.status = status; - currentCommit = commit; + static RebaseResult result(RebaseResult.Status status, + RevCommit commit) { + return new RebaseResult(status, commit); } /** @@ -207,11 +242,13 @@ public class RebaseResult { * * @param failingPaths * list of paths causing this rebase to fail + * @return the RebaseResult */ - RebaseResult(Map<String, MergeFailureReason> failingPaths) { - status = Status.FAILED; - currentCommit = null; - this.failingPaths = failingPaths; + static RebaseResult failed( + Map<String, MergeFailureReason> failingPaths) { + RebaseResult result = new RebaseResult(Status.FAILED); + result.failingPaths = failingPaths; + return result; } /** @@ -219,11 +256,26 @@ public class RebaseResult { * * @param conflicts * the list of conflicting paths + * @return the RebaseResult */ - RebaseResult(List<String> conflicts) { - status = Status.CONFLICTS; - currentCommit = null; - this.conflicts = conflicts; + static RebaseResult conflicts(List<String> conflicts) { + RebaseResult result = new RebaseResult(Status.CONFLICTS); + result.conflicts = conflicts; + return result; + } + + /** + * Create <code>RebaseResult</code> with status + * {@link Status#UNCOMMITTED_CHANGES} + * + * @param uncommittedChanges + * the list of paths + * @return the RebaseResult + */ + static RebaseResult uncommittedChanges(List<String> uncommittedChanges) { + RebaseResult result = new RebaseResult(Status.UNCOMMITTED_CHANGES); + result.uncommittedChanges = uncommittedChanges; + return result; } /** @@ -256,4 +308,15 @@ public class RebaseResult { public List<String> getConflicts() { return conflicts; } + + /** + * @return the list of uncommitted changes if status is + * {@link Status#UNCOMMITTED_CHANGES} + * + * @since 3.2 + */ + public List<String> getUncommittedChanges() { + return uncommittedChanges; + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java index 3a1c209a2d..7c2192dd9f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java @@ -279,16 +279,17 @@ public class ResetCommand extends GitCommand<Ref> { } /** - * @param file - * the file to add + * @param path + * repository-relative path of file/directory to reset (with + * <code>/</code> as separator) * @return this instance */ - public ResetCommand addPath(String file) { + public ResetCommand addPath(String path) { if (mode != null) throw new JGitInternalException(MessageFormat.format( JGitText.get().illegalCombinationOfArguments, "<paths>...", "[--mixed | --soft | --hard]")); //$NON-NLS-1$ - filepaths.add(file); + filepaths.add(path); return this; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java index 2bc9d22449..bc1b29c2f6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RevertCommand.java @@ -191,14 +191,14 @@ public class RevertCommand extends GitCommand<RevCommit> { .getFailingPaths(); if (failingPaths != null) failingResult = new MergeResult(null, - merger.getBaseCommit(0, 1), + merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcParent.getId() }, MergeStatus.FAILED, MergeStrategy.RECURSIVE, merger.getMergeResults(), failingPaths, null); else failingResult = new MergeResult(null, - merger.getBaseCommit(0, 1), + merger.getBaseCommitId(), new ObjectId[] { headCommit.getId(), srcParent.getId() }, MergeStatus.CONFLICTING, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java index ac30ffcb7d..c70b4aeaf2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RmCommand.java @@ -104,7 +104,8 @@ public class RmCommand extends GitCommand<DirCache> { /** * @param filepattern - * File to remove. + * repository-relative path of file to remove (with + * <code>/</code> as separator) * @return {@code this} */ public RmCommand addFilepattern(String filepattern) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java index 020a6dc5f2..8440d8b950 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java @@ -90,6 +90,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> { private boolean applyIndex = true; + private boolean ignoreRepositoryState; + /** * Create command to apply the changes of a stashed commit * @@ -113,6 +115,16 @@ public class StashApplyCommand extends GitCommand<ObjectId> { return this; } + /** + * @param ignoreRepositoryState + * @return {@code this} + * @since 3.2 + */ + public StashApplyCommand ignoreRepositoryState(boolean ignoreRepositoryState) { + this.ignoreRepositoryState = ignoreRepositoryState; + return this; + } + private ObjectId getStashId() throws GitAPIException { final String revision = stashRef != null ? stashRef : DEFAULT_REF; final ObjectId stashId; @@ -143,7 +155,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> { StashApplyFailureException { checkCallable(); - if (repo.getRepositoryState() != RepositoryState.SAFE) + if (!ignoreRepositoryState + && repo.getRepositoryState() != RepositoryState.SAFE) throw new WrongRepositoryStateException(MessageFormat.format( JGitText.get().stashApplyOnUnsafeRepository, repo.getRepositoryState())); @@ -185,6 +198,7 @@ public class StashApplyCommand extends GitCommand<ObjectId> { .newMerger(repo, true); ixMerger.setCommitNames(new String[] { "stashed HEAD", "HEAD", "stashed index" }); + ixMerger.setBase(stashHeadCommit); boolean ok = ixMerger.merge(headCommit, stashIndexCommit); if (ok) { resetIndex(revWalk diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java index fc21b919b6..cf0b6d1d9c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashCreateCommand.java @@ -154,6 +154,7 @@ public class StashCreateCommand extends GitCommand<RevCommit> { /** * Set the reference to update with the stashed commit id + * If null, no reference is updated * <p> * This value defaults to {@link Constants#R_STASH} * @@ -185,6 +186,8 @@ public class StashCreateCommand extends GitCommand<RevCommit> { private void updateStashRef(ObjectId commitId, PersonIdent refLogIdent, String refLogMessage) throws IOException { + if (ref == null) + return; Ref currentRef = repo.getRef(ref); RefUpdate refUpdate = repo.updateRef(ref); refUpdate.setNewObjectId(commitId); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java index e840c2f608..c3fcd8bfe8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Status.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.api; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -66,19 +67,22 @@ public class Status { private final boolean clean; + private final boolean hasUncommittedChanges;; + /** * @param diff */ public Status(IndexDiff diff) { super(); this.diff = diff; - clean = diff.getAdded().isEmpty() // - && diff.getChanged().isEmpty() // - && diff.getRemoved().isEmpty() // - && diff.getMissing().isEmpty() // - && diff.getModified().isEmpty() // - && diff.getUntracked().isEmpty() // - && diff.getConflicting().isEmpty(); + hasUncommittedChanges = !diff.getAdded().isEmpty() // + || !diff.getChanged().isEmpty() // + || !diff.getRemoved().isEmpty() // + || !diff.getMissing().isEmpty() // + || !diff.getModified().isEmpty() // + || !diff.getConflicting().isEmpty(); + clean = !hasUncommittedChanges // + && diff.getUntracked().isEmpty(); } /** @@ -90,6 +94,15 @@ public class Status { } /** + * @return true if any tracked file is changed + * + * @since 3.2 + */ + public boolean hasUncommittedChanges() { + return hasUncommittedChanges; + } + + /** * @return list of files added to the index, not in HEAD (e.g. what you get * if you call 'git add ...' on a newly created file) */ @@ -168,4 +181,21 @@ public class Status { public Set<String> getIgnoredNotInIndex() { return Collections.unmodifiableSet(diff.getIgnoredNotInIndex()); } + + /** + * @return set of files and folders that are known to the repo and changed + * either in the index or in the working tree. + * + * @since 3.2 + */ + public Set<String> getUncommittedChanges() { + Set<String> uncommittedChanges = new HashSet<String>(); + uncommittedChanges.addAll(diff.getAdded()); + uncommittedChanges.addAll(diff.getChanged()); + uncommittedChanges.addAll(diff.getRemoved()); + uncommittedChanges.addAll(diff.getMissing()); + uncommittedChanges.addAll(diff.getModified()); + uncommittedChanges.addAll(diff.getConflicting()); + return uncommittedChanges; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java index eaf5483671..dee0a31b91 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java @@ -89,7 +89,8 @@ public class StatusCommand extends GitCommand<Status> { * supported. * * @param path - * a path is relative to the top level of the repository + * repository-relative path of file/directory to show status for + * (with <code>/</code> as separator) * @return {@code this} * @since 3.1 */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java index bfef053d85..09e4cf0a1b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleAddCommand.java @@ -93,6 +93,7 @@ public class SubmoduleAddCommand extends * Set repository-relative path of submodule * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleAddCommand setPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java index e799bfb8bd..0ed02acc82 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleInitCommand.java @@ -83,6 +83,7 @@ public class SubmoduleInitCommand extends GitCommand<Collection<String>> { * Add repository-relative submodule path to initialize * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleInitCommand addPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java index bbc01adbae..6e89f9873e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleStatusCommand.java @@ -83,6 +83,7 @@ public class SubmoduleStatusCommand extends * Add repository-relative submodule path to limit status reporting to * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleStatusCommand addPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java index 11d3c5acca..e7fe71a890 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleSyncCommand.java @@ -85,6 +85,7 @@ public class SubmoduleSyncCommand extends GitCommand<Map<String, String>> { * Add repository-relative submodule path to synchronize * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleSyncCommand addPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java index 40f6a9f9a4..e2f356a08e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java @@ -110,6 +110,7 @@ public class SubmoduleUpdateCommand extends * Add repository-relative submodule path to initialize * * @param path + * (with <code>/</code> as separator) * @return this command */ public SubmoduleUpdateCommand addPath(final String path) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java index bd896864d7..f8c8442ff8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -76,6 +76,7 @@ import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.io.AutoCRLFOutputStream; @@ -306,8 +307,7 @@ public class DirCacheCheckout { void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i, WorkingTreeIterator f) throws IOException { if (m != null) { - if (!isValidPath(m)) - throw new InvalidPathException(m.getEntryPathString()); + checkValidPath(m); // There is an entry in the merge commit. Means: we want to update // what's currently in the index and working-tree to that one if (i == null) { @@ -522,8 +522,8 @@ public class DirCacheCheckout { String name = walk.getPathString(); - if (m != null && !isValidPath(m)) - throw new InvalidPathException(m.getEntryPathString()); + if (m != null) + checkValidPath(m); if (i == null && m == null && h == null) { // File/Directory conflict case #20 @@ -555,7 +555,9 @@ public class DirCacheCheckout { * 3 D F D Y N N Keep * 4 D F D N N N Conflict * 5 D F F Y N N Y Keep + * 5b D F F Y N N N Conflict * 6 D F F N N N Y Keep + * 6b D F F N N N N Conflict * 7 F D F Y Y N N Update * 8 F D F N Y N N Conflict * 9 F D F Y N N N Update @@ -620,7 +622,11 @@ public class DirCacheCheckout { case 0xF0D: // 18 remove(name); break; - case 0xDFF: // 5 6 + case 0xDFF: // 5 5b 6 6b + if (equalIdAndMode(iId, iMode, mId, mMode)) + keep(dce); // 5 6 + else + conflict(name, dce, h, m); // 5b 6b case 0xFDD: // 10 11 // TODO: make use of tree extension as soon as available in jgit // we would like to do something like @@ -1151,14 +1157,14 @@ public class DirCacheCheckout { forbidden[i] = Constants.encodeASCII(list[i]); } - private static boolean isValidPath(CanonicalTreeParser t) { + private static void checkValidPath(CanonicalTreeParser t) + throws InvalidPathException { for (CanonicalTreeParser i = t; i != null; i = i.getParent()) - if (!isValidPathSegment(i)) - return false; - return true; + checkValidPathSegment(i); } - private static boolean isValidPathSegment(CanonicalTreeParser t) { + private static void checkValidPathSegment(CanonicalTreeParser t) + throws InvalidPathException { boolean isWindows = SystemReader.getInstance().isWindows(); boolean isOSX = SystemReader.getInstance().isMacOS(); boolean ignCase = isOSX || isWindows; @@ -1171,23 +1177,29 @@ public class DirCacheCheckout { int start = ptr; while (ptr < end) { if (raw[ptr] == '/') - return false; + throw new InvalidPathException( + JGitText.get().invalidPathContainsSeparator, + "/", t.getEntryPathString()); //$NON-NLS-1$ if (isWindows) { if (raw[ptr] == '\\') - return false; + throw new InvalidPathException( + JGitText.get().invalidPathContainsSeparator, + "\\", t.getEntryPathString()); //$NON-NLS-1$ if (raw[ptr] == ':') - return false; + throw new InvalidPathException( + JGitText.get().invalidPathContainsSeparator, + ":", t.getEntryPathString()); //$NON-NLS-1$ } ptr++; } - // '.' and '.'' are invalid here + // '.' and '..' are invalid here if (ptr - start == 1) { if (raw[start] == '.') - return false; + throw new InvalidPathException(t.getEntryPathString()); } else if (ptr - start == 2) { if (raw[start] == '.') if (raw[start + 1] == '.') - return false; + throw new InvalidPathException(t.getEntryPathString()); } else if (ptr - start == 4) { // .git (possibly case insensitive) is disallowed if (raw[start] == '.') @@ -1196,15 +1208,24 @@ public class DirCacheCheckout { || (ignCase && raw[start + 2] == 'I')) if (raw[start + 3] == 't' || (ignCase && raw[start + 3] == 'T')) - return false; + throw new InvalidPathException( + t.getEntryPathString()); } if (isWindows) { // Space or period at end of file name is ignored by Windows. // Treat this as a bad path for now. We may want to handle // this as case insensitivity in the future. - if (ptr > 0) - if (raw[ptr - 1] == '.' || raw[ptr - 1] == ' ') - return false; + if (ptr > 0) { + if (raw[ptr - 1] == '.') + throw new InvalidPathException( + JGitText.get().invalidPathPeriodAtEndWindows, + t.getEntryPathString()); + if (raw[ptr - 1] == ' ') + throw new InvalidPathException( + JGitText.get().invalidPathSpaceAtEndWindows, + t.getEntryPathString()); + } + int i; // Bad names, eliminate suffix first for (i = start; i < ptr; ++i) @@ -1222,13 +1243,14 @@ public class DirCacheCheckout { break; } if (k == len) - return false; + throw new InvalidPathException( + JGitText.get().invalidPathReservedOnWindows, + RawParseUtils.decode(forbidden[j]), t + .getEntryPathString()); } } } } - - return true; } private static byte toUpper(byte b) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java index 698636c8cf..50d1c4ca38 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/InvalidPathException.java @@ -60,6 +60,10 @@ public class InvalidPathException extends IllegalArgumentException { * @param path */ public InvalidPathException(String path) { - super(MessageFormat.format(JGitText.get().invalidPath, path)); + this(JGitText.get().invalidPath, path); + } + + InvalidPathException(String messagePattern, Object... arguments) { + super(MessageFormat.format(messagePattern, arguments)); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java index e0c780feec..980f2094bd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java @@ -198,6 +198,7 @@ public class IgnoreRule { } } else { + matcher.reset(); matcher.append(target); if (matcher.isMatch()) return true; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index b2c27a483c..f9700a1ff4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -332,6 +332,10 @@ public class JGitText extends TranslationBundle { /***/ public String invalidOldIdSent; /***/ public String invalidPacketLineHeader; /***/ public String invalidPath; + /***/ public String invalidPathContainsSeparator; + /***/ public String invalidPathPeriodAtEndWindows; + /***/ public String invalidPathSpaceAtEndWindows; + /***/ public String invalidPathReservedOnWindows; /***/ public String invalidReflogRevision; /***/ public String invalidRefName; /***/ public String invalidRemote; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java index a8d797dff2..748a4a38e0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java @@ -556,5 +556,9 @@ public final class DfsBlockCache { hot = true; return v; } + + boolean has() { + return value != null; + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java index 1c588d2c4e..7c4776ea06 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java @@ -191,7 +191,7 @@ public final class DfsPackFile { */ public boolean isIndexLoaded() { DfsBlockCache.Ref<PackIndex> idxref = index; - return idxref != null && idxref.get() != null; + return idxref != null && idxref.has(); } /** @return bytes cached in memory for this pack, excluding the index. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 3e26bc3e62..e06ff65ee4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java @@ -97,6 +97,7 @@ import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.GitDateParser; +import org.eclipse.jgit.util.SystemReader; /** * A garbage collector for git {@link FileRepository}. Instances of this class @@ -175,21 +176,9 @@ public class GC { * * @param oldPacks * @param newPacks - * @param ignoreErrors - * <code>true</code> if we should ignore the fact that a certain - * pack files or index files couldn't be deleted. - * <code>false</code> if an exception should be thrown in such - * cases - * @throws IOException - * if a pack file couldn't be deleted and - * <code>ignoreErrors</code> is set to <code>false</code> */ private void deleteOldPacks(Collection<PackFile> oldPacks, - Collection<PackFile> newPacks, boolean ignoreErrors) - throws IOException { - int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING; - if (ignoreErrors) - deleteOptions |= FileUtils.IGNORE_ERRORS; + Collection<PackFile> newPacks) { oldPackLoop: for (PackFile oldPack : oldPacks) { String oldName = oldPack.getPackName(); // check whether an old pack file is also among the list of new @@ -200,10 +189,7 @@ public class GC { if (!oldPack.shouldBeKept()) { oldPack.close(); - for (PackExt ext : PackExt.values()) { - File f = nameFor(oldName, "." + ext.getExtension()); //$NON-NLS-1$ - FileUtils.delete(f, deleteOptions); - } + prunePack(oldName); } } // close the complete object database. Thats my only chance to force @@ -212,6 +198,42 @@ public class GC { } /** + * Delete files associated with a single pack file. First try to delete the + * ".pack" file because on some platforms the ".pack" file may be locked and + * can't be deleted. In such a case it is better to detect this early and + * give up on deleting files for this packfile. Otherwise we may delete the + * ".index" file and when failing to delete the ".pack" file we are left + * with a ".pack" file without a ".index" file. + * + * @param packName + */ + private void prunePack(String packName) { + PackExt[] extensions = PackExt.values(); + try { + // Delete the .pack file first and if this fails give up on deleting + // the other files + int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING; + for (PackExt ext : extensions) + if (PackExt.PACK.equals(ext)) { + File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$ + FileUtils.delete(f, deleteOptions); + break; + } + // The .pack file has been deleted. Delete as many as the other + // files as you can. + deleteOptions |= FileUtils.IGNORE_ERRORS; + for (PackExt ext : extensions) { + if (!PackExt.PACK.equals(ext)) { + File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$ + FileUtils.delete(f, deleteOptions); + } + } + } catch (IOException e) { + // Deletion of the .pack file failed. Silently return. + } + } + + /** * Like "git prune-packed" this method tries to prune all loose objects * which can be found in packs. If certain objects can't be pruned (e.g. * because the filesystem delete operation fails) this is silently ignored. @@ -286,7 +308,8 @@ public class GC { ConfigConstants.CONFIG_KEY_PRUNEEXPIRE); if (pruneExpireStr == null) pruneExpireStr = PRUNE_EXPIRE_DEFAULT; - expire = GitDateParser.parse(pruneExpireStr, null); + expire = GitDateParser.parse(pruneExpireStr, null, SystemReader + .getInstance().getLocale()); expireAgeMillis = -1; } if (expire != null) @@ -533,7 +556,7 @@ public class GC { if (rest != null) ret.add(rest); } - deleteOldPacks(toBeDeleted, ret, true); + deleteOldPacks(toBeDeleted, ret); prunePacked(); lastPackedRefs = refsBefore; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java index 28d90590a3..db622f319d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java @@ -100,7 +100,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re && ref[7] == ' '; } - private static File getSymRef(File workTree, File dotGit) + private static File getSymRef(File workTree, File dotGit, FS fs) throws IOException { byte[] content = IO.readFully(dotGit); if (!isSymRef(content)) @@ -116,7 +116,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re JGitText.get().invalidGitdirRef, dotGit.getAbsolutePath())); String gitdirPath = RawParseUtils.decode(content, pathStart, lineEnd); - File gitdirFile = new File(gitdirPath); + File gitdirFile = fs.resolve(workTree, gitdirPath); if (gitdirFile.isAbsolute()) return gitdirFile; else @@ -516,7 +516,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re break; } else if (dir.isFile()) try { - setGitDir(getSymRef(current, dir)); + setGitDir(getSymRef(current, dir, tryFS)); break; } catch (IOException ignored) { // Continue searching if gitdir ref isn't found @@ -597,7 +597,7 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re if (!dotGit.isFile()) setGitDir(dotGit); else - setGitDir(getSymRef(getWorkTree(), dotGit)); + setGitDir(getSymRef(getWorkTree(), dotGit, safeFS())); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index 8240ac8f76..81977d74ac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -435,7 +435,7 @@ public class Config { } /** - * Get string value + * Get string value or null if not found. * * @param section * the section @@ -443,7 +443,7 @@ public class Config { * the subsection for the value * @param name * the key name - * @return a String value from git config. + * @return a String value from the config, <code>null</code> if not found */ public String getString(final String section, String subsection, final String name) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 3ff4eefb1c..fd22764b6a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -77,6 +77,13 @@ public class ConfigConstants { /** The "submodule" section */ public static final String CONFIG_SUBMODULE_SECTION = "submodule"; + /** + * The "rebase" section + * + * @since 3.2 + */ + public static final String CONFIG_REBASE_SECTION = "rebase"; + /** The "gc" section */ public static final String CONFIG_GC_SECTION = "gc"; @@ -136,6 +143,14 @@ public class ConfigConstants { /** The "autosetuprebase" key */ public static final String CONFIG_KEY_AUTOSETUPREBASE = "autosetuprebase"; + + /** + * The "autostash" key + * + * @since 3.2 + */ + public static final String CONFIG_KEY_AUTOSTASH = "autostash"; + /** The "name" key */ public static final String CONFIG_KEY_NAME = "name"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java index 15d118c0e9..0cc51d1a52 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -156,9 +156,10 @@ public abstract class ObjectDatabase { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link ObjectReader#OBJ_ANY} if the object type is not known, - * or does not matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if + * the object type is not known, or does not matter to the + * caller. * @return a {@link ObjectLoader} for accessing the object. * @throws MissingObjectException * the object does not exist. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java index 3bd02500cc..58c141870b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectReader.java @@ -194,9 +194,9 @@ public abstract class ObjectReader { * @param objectId * identity of the object to test for existence of. * @param typeHint - * hint about the type of object being requested; - * {@link #OBJ_ANY} if the object type is not known, or does not - * matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object + * type is not known, or does not matter to the caller. * @return true if the specified object is stored in this database. * @throws IncorrectObjectTypeException * typeHint was not OBJ_ANY, and the object's actual type does @@ -235,9 +235,9 @@ public abstract class ObjectReader { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link #OBJ_ANY} if the object type is not known, or does not - * matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object + * type is not known, or does not matter to the caller. * @return a {@link ObjectLoader} for accessing the object. * @throws MissingObjectException * the object does not exist. @@ -323,9 +323,9 @@ public abstract class ObjectReader { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link #OBJ_ANY} if the object type is not known, or does not - * matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link #OBJ_ANY} if the object + * type is not known, or does not matter to the caller. * @return size of object in bytes. * @throws MissingObjectException * the object does not exist. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java index cac67e11e9..ef61e22032 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RebaseTodoFile.java @@ -197,6 +197,8 @@ public class RebaseTodoFile { } tokenCount++; } + if (tokenCount == 2) + return new RebaseTodoLine(action, commit, ""); //$NON-NLS-1$ return null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java index 4a352e8b69..77734bf6b1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -257,9 +257,10 @@ public abstract class Repository { * @param objectId * identity of the object to open. * @param typeHint - * hint about the type of object being requested; - * {@link ObjectReader#OBJ_ANY} if the object type is not known, - * or does not matter to the caller. + * hint about the type of object being requested, e.g. + * {@link Constants#OBJ_BLOB}; {@link ObjectReader#OBJ_ANY} if + * the object type is not known, or does not matter to the + * caller. * @return a {@link ObjectLoader} for accessing the object. * @throws MissingObjectException * the object does not exist. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java index 1346328761..6dba412987 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2008-2013, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -54,8 +54,8 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; -import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTree; @@ -63,7 +63,6 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; -import org.eclipse.jgit.treewalk.EmptyTreeIterator; /** * Instance of a specific {@link MergeStrategy} for a single {@link Repository}. @@ -186,24 +185,11 @@ public abstract class Merger { } /** - * Create an iterator to walk the merge base of two commits. - * - * @param a - * the first commit in {@link #sourceObjects}. - * @param b - * the second commit in {@link #sourceObjects}. - * @return the new iterator - * @throws IncorrectObjectTypeException - * one of the input objects is not a commit. - * @throws IOException - * objects are missing or multiple merge bases were found. - * @since 3.0 + * @return the ID of the commit that was used as merge base for merging, or + * null if no merge base was used or it was set manually + * @since 3.2 */ - protected AbstractTreeIterator mergeBase(RevCommit a, RevCommit b) - throws IOException { - RevCommit base = getBaseCommit(a, b); - return (base == null) ? new EmptyTreeIterator() : openTree(base.getTree()); - } + public abstract ObjectId getBaseCommitId(); /** * Return the merge base of two commits. @@ -217,7 +203,10 @@ public abstract class Merger { * one of the input objects is not a commit. * @throws IOException * objects are missing or multiple merge bases were found. + * @deprecated use {@link #getBaseCommitId()} instead, as that does not + * require walking the commits again */ + @Deprecated public RevCommit getBaseCommit(final int aIdx, final int bIdx) throws IncorrectObjectTypeException, IOException { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java index 34bc9f5e4d..12d6c6b413 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/StrategyOneSided.java @@ -106,5 +106,10 @@ public class StrategyOneSided extends MergeStrategy { public ObjectId getResultTreeId() { return sourceTrees[treeIndex]; } + + @Override + public ObjectId getBaseCommitId() { + return null; + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java index 1ad791bb79..fbedaef865 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ThreeWayMerger.java @@ -49,14 +49,19 @@ import java.io.IOException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.EmptyTreeIterator; /** A merge of 2 trees, using a common base ancestor tree. */ public abstract class ThreeWayMerger extends Merger { private RevTree baseTree; + private ObjectId baseCommitId; + /** * Create a new merge instance for a repository. * @@ -109,6 +114,11 @@ public abstract class ThreeWayMerger extends Merger { return super.merge(tips); } + @Override + public ObjectId getBaseCommitId() { + return baseCommitId; + } + /** * Create an iterator to walk the merge base. * @@ -119,6 +129,15 @@ public abstract class ThreeWayMerger extends Merger { protected AbstractTreeIterator mergeBase() throws IOException { if (baseTree != null) return openTree(baseTree); - return mergeBase(sourceCommits[0], sourceCommits[1]); + RevCommit baseCommit = (baseCommitId != null) ? walk + .parseCommit(baseCommitId) : getBaseCommit(sourceCommits[0], + sourceCommits[1]); + if (baseCommit == null) { + baseCommitId = null; + return new EmptyTreeIterator(); + } else { + baseCommitId = baseCommit.toObjectId(); + return openTree(baseCommit.getTree()); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java index 8f4e491de6..32859c9c58 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/GitDateParser.java @@ -49,6 +49,7 @@ import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import org.eclipse.jgit.internal.JGitText; @@ -71,23 +72,40 @@ public class GitDateParser { // Since SimpleDateFormat instances are expensive to instantiate they should // be cached. Since they are also not threadsafe they are cached using // ThreadLocal. - private static ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>> formatCache = new ThreadLocal<Map<ParseableSimpleDateFormat, SimpleDateFormat>>() { - protected Map<ParseableSimpleDateFormat, SimpleDateFormat> initialValue() { - return new HashMap<ParseableSimpleDateFormat, SimpleDateFormat>(); + private static ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>> formatCache = + new ThreadLocal<Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>>() { + + protected Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> initialValue() { + return new HashMap<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>>(); } }; - // Gets an instance of a SimpleDateFormat. If there is not already an - // appropriate instance in the (ThreadLocal) cache the create one and put in - // into the cache - private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f) { - Map<ParseableSimpleDateFormat, SimpleDateFormat> map = formatCache + // Gets an instance of a SimpleDateFormat for the specified locale. If there + // is not already an appropriate instance in the (ThreadLocal) cache then + // create one and put it into the cache. + private static SimpleDateFormat getDateFormat(ParseableSimpleDateFormat f, + Locale locale) { + Map<Locale, Map<ParseableSimpleDateFormat, SimpleDateFormat>> cache = formatCache .get(); + Map<ParseableSimpleDateFormat, SimpleDateFormat> map = cache + .get(locale); + if (map == null) { + map = new HashMap<ParseableSimpleDateFormat, SimpleDateFormat>(); + cache.put(locale, map); + return getNewSimpleDateFormat(f, locale, map); + } SimpleDateFormat dateFormat = map.get(f); if (dateFormat != null) return dateFormat; + SimpleDateFormat df = getNewSimpleDateFormat(f, locale, map); + return df; + } + + private static SimpleDateFormat getNewSimpleDateFormat( + ParseableSimpleDateFormat f, Locale locale, + Map<ParseableSimpleDateFormat, SimpleDateFormat> map) { SimpleDateFormat df = SystemReader.getInstance().getSimpleDateFormat( - f.formatStr); + f.formatStr, locale); map.put(f, df); return df; } @@ -115,9 +133,9 @@ public class GitDateParser { } /** - * Parses a string into a {@link Date}. Since this parser also supports - * relative formats (e.g. "yesterday") the caller can specify the reference - * date. These types of strings can be parsed: + * Parses a string into a {@link Date} using the default locale. Since this + * parser also supports relative formats (e.g. "yesterday") the caller can + * specify the reference date. These types of strings can be parsed: * <ul> * <li>"never"</li> * <li>"now"</li> @@ -151,6 +169,49 @@ public class GitDateParser { */ public static Date parse(String dateStr, Calendar now) throws ParseException { + return parse(dateStr, now, Locale.getDefault()); + } + + /** + * Parses a string into a {@link Date} using the given locale. Since this + * parser also supports relative formats (e.g. "yesterday") the caller can + * specify the reference date. These types of strings can be parsed: + * <ul> + * <li>"never"</li> + * <li>"now"</li> + * <li>"yesterday"</li> + * <li>"(x) years|months|weeks|days|hours|minutes|seconds ago"<br> + * Multiple specs can be combined like in "2 weeks 3 days ago". Instead of + * ' ' one can use '.' to seperate the words</li> + * <li>"yyyy-MM-dd HH:mm:ss Z" (ISO)</li> + * <li>"EEE, dd MMM yyyy HH:mm:ss Z" (RFC)</li> + * <li>"yyyy-MM-dd"</li> + * <li>"yyyy.MM.dd"</li> + * <li>"MM/dd/yyyy",</li> + * <li>"dd.MM.yyyy"</li> + * <li>"EEE MMM dd HH:mm:ss yyyy Z" (DEFAULT)</li> + * <li>"EEE MMM dd HH:mm:ss yyyy" (LOCAL)</li> + * </ul> + * + * @param dateStr + * the string to be parsed + * @param now + * the base date which is used for the calculation of relative + * formats. E.g. if baseDate is "25.8.2012" then parsing of the + * string "1 week ago" would result in a date corresponding to + * "18.8.2012". This is used when a JGit command calls this + * parser often but wants a consistent starting point for calls.<br> + * If set to <code>null</code> then the current time will be used + * instead. + * @param locale + * locale to be used to parse the date string + * @return the parsed {@link Date} + * @throws ParseException + * if the given dateStr was not recognized + * @since 3.2 + */ + public static Date parse(String dateStr, Calendar now, Locale locale) + throws ParseException { dateStr = dateStr.trim(); Date ret; @@ -161,7 +222,7 @@ public class GitDateParser { return ret; for (ParseableSimpleDateFormat f : ParseableSimpleDateFormat.values()) { try { - return parse_simple(dateStr, f); + return parse_simple(dateStr, f, locale); } catch (ParseException e) { // simply proceed with the next parser } @@ -177,9 +238,10 @@ public class GitDateParser { } // tries to parse a string with the formats supported by SimpleDateFormat - private static Date parse_simple(String dateStr, ParseableSimpleDateFormat f) + private static Date parse_simple(String dateStr, + ParseableSimpleDateFormat f, Locale locale) throws ParseException { - SimpleDateFormat dateFormat = getDateFormat(f); + SimpleDateFormat dateFormat = getDateFormat(f, locale); dateFormat.setLenient(false); return dateFormat.parse(dateStr); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java index b6028610bf..e73f100f96 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -230,6 +230,21 @@ public abstract class SystemReader { } /** + * Returns a simple date format instance as specified by the given pattern. + * + * @param pattern + * the pattern as defined in + * {@link SimpleDateFormat#SimpleDateFormat(String)} + * @param locale + * locale to be used for the {@code SimpleDateFormat} + * @return the simple date format + * @since 3.2 + */ + public SimpleDateFormat getSimpleDateFormat(String pattern, Locale locale) { + return new SimpleDateFormat(pattern, locale); + } + + /** * Returns a date/time format instance for the given styles. * * @param dateStyle @@ -184,7 +184,7 @@ <commons-compress-version>1.4.1</commons-compress-version> <osgi-core-version>4.3.1</osgi-core-version> <servlet-api-version>2.5</servlet-api-version> - <jetty-version>7.6.11.v20130520</jetty-version> + <jetty-version>7.6.14.v20131031</jetty-version> <clirr-version>2.4</clirr-version> </properties> |