diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2013-12-05 00:14:08 +0100 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2013-12-05 00:14:25 +0100 |
commit | 162a5c4c89b289af3755a2f26843cdf908e93c50 (patch) | |
tree | 377792666836715cee1ec2f4d5582641d854dce3 | |
parent | fceae20181b2b20632fe44731407bfb940093817 (diff) | |
parent | f86a488e32906593903acb31a93a82bed8d87915 (diff) | |
download | jgit-162a5c4c89b289af3755a2f26843cdf908e93c50.tar.gz jgit-162a5c4c89b289af3755a2f26843cdf908e93c50.zip |
Merge branch 'master' into stable-3.2
* master:
Implement rebase.autostash
CLI status should support --porcelain
More helpful InvalidPathException messages (include reason)
Fix IgnoreRule#isMatch returning wrong result due to missing reset
Fix exception on conflicts with recursive merge
Add pgm test for checkout of existing branch with checkout conflict
Fix broken symbolic links on Cygwin.
Do not allow non-ff-rebase if there are uncommitted changes
Manage CheckoutConflictException in pgm
Fix handling of file/folder conflicts during a checkout
Mention null return in Javadoc of Config#getString
Fix applying stash on other commit
Use static factory methods instead of overloaded constructors
Break up GCTest to run in parallel
Modify T0004_PackReaderTest to use existing pack
Move SampleDataRepositoryTestCase to org.eclipse.jgit.test
Support running from JARs in JGitTestUtil
Cache SimpleDateFormat in GitDateParser per locale
Fix FIXUP error for blank lines in interactive rebase
Fix parsing Rebase todo lines when commit message is missing
Add close() method to API
Update Jetty to 7.6.14.v20131031
Document that path parameters should use '/' as separator
Improve Javadoc for typeHint parameter
Do not update the ref hot bit when checking isIndexLoaded
Don't delete .idx file if .pack file can't be deleted
Change-Id: I02abfc09000d0fe9bdf4331c65bec7046f586179
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
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> |