diff options
16 files changed, 419 insertions, 78 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java index de75fd5b61..48ede966f9 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandTest.java @@ -48,7 +48,9 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode; import org.eclipse.jgit.api.MergeResult.MergeStatus; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RepositoryTestCase; import org.eclipse.jgit.lib.StoredConfig; @@ -125,6 +127,46 @@ public class PullCommandTest extends RepositoryTestCase { assertFileContentsEqual(targetFile, result); } + public void testPullLocalConflict() throws Exception { + target.branchCreate().setName("basedOnMaster").setStartPoint( + "refs/heads/master").setUpstreamMode(SetupUpstreamMode.TRACK) + .call(); + target.getRepository().updateRef(Constants.HEAD).link( + "refs/heads/basedOnMaster"); + PullResult res = target.pull().call(); + // nothing to update since we don't have different data yet + assertNull(res.getFetchResult()); + assertTrue(res.getMergeResult().getMergeStatus().equals( + MergeStatus.ALREADY_UP_TO_DATE)); + + assertFileContentsEqual(targetFile, "Hello world"); + + // change the file in master + target.getRepository().updateRef(Constants.HEAD).link( + "refs/heads/master"); + writeToFile(targetFile, "Master change"); + target.add().addFilepattern("SomeFile.txt").call(); + target.commit().setMessage("Source change in master").call(); + + // change the file in slave + target.getRepository().updateRef(Constants.HEAD).link( + "refs/heads/basedOnMaster"); + writeToFile(targetFile, "Slave change"); + target.add().addFilepattern("SomeFile.txt").call(); + target.commit().setMessage("Source change in based on master").call(); + + res = target.pull().call(); + + String sourceChangeString = "Master change\n>>>>>>> branch 'refs/heads/master' of local repository"; + + assertNull(res.getFetchResult()); + assertEquals(res.getMergeResult().getMergeStatus(), + MergeStatus.CONFLICTING); + String result = "<<<<<<< HEAD\nSlave change\n=======\n" + + sourceChangeString + "\n"; + assertFileContentsEqual(targetFile, result); + } + @Override protected void setUp() throws Exception { super.setUp(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java new file mode 100644 index 0000000000..6839f8d3c1 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ThreadSafeProgressMonitorTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2010, Google Inc. + * 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.lib; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + +public class ThreadSafeProgressMonitorTest extends TestCase { + public void testFailsMethodsOnBackgroundThread() + throws InterruptedException { + final MockProgressMonitor mock = new MockProgressMonitor(); + final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(mock); + + runOnThread(new Runnable() { + public void run() { + try { + pm.start(1); + fail("start did not fail on background thread"); + } catch (IllegalStateException notMainThread) { + // Expected result + } + + try { + pm.beginTask("title", 1); + fail("beginTask did not fail on background thread"); + } catch (IllegalStateException notMainThread) { + // Expected result + } + + try { + pm.endTask(); + fail("endTask did not fail on background thread"); + } catch (IllegalStateException notMainThread) { + // Expected result + } + } + }); + + // Ensure we didn't alter the mock above when checking threads. + assertNull(mock.taskTitle); + assertEquals(0, mock.value); + } + + public void testMethodsOkOnMainThread() { + final MockProgressMonitor mock = new MockProgressMonitor(); + final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(mock); + + pm.start(1); + assertEquals(1, mock.value); + + pm.beginTask("title", 42); + assertEquals("title", mock.taskTitle); + assertEquals(42, mock.value); + + pm.update(1); + assertEquals(43, mock.value); + + pm.update(2); + assertEquals(45, mock.value); + + pm.endTask(); + assertNull(mock.taskTitle); + assertEquals(0, mock.value); + } + + public void testUpdateOnBackgroundThreads() throws InterruptedException { + final MockProgressMonitor mock = new MockProgressMonitor(); + final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(mock); + + pm.startWorker(); + + final CountDownLatch doUpdate = new CountDownLatch(1); + final CountDownLatch didUpdate = new CountDownLatch(1); + final CountDownLatch doEndWorker = new CountDownLatch(1); + + final Thread bg = new Thread() { + public void run() { + assertFalse(pm.isCancelled()); + + await(doUpdate); + pm.update(2); + didUpdate.countDown(); + + await(doEndWorker); + pm.update(1); + pm.endWorker(); + } + }; + bg.start(); + + pm.pollForUpdates(); + assertEquals(0, mock.value); + doUpdate.countDown(); + + await(didUpdate); + pm.pollForUpdates(); + assertEquals(2, mock.value); + + doEndWorker.countDown(); + pm.waitForCompletion(); + assertEquals(3, mock.value); + } + + private static void await(CountDownLatch cdl) { + try { + assertTrue("latch released", cdl.await(1000, TimeUnit.MILLISECONDS)); + } catch (InterruptedException ie) { + fail("Did not expect to be interrupted"); + } + } + + private static void runOnThread(Runnable task) throws InterruptedException { + Thread t = new Thread(task); + t.start(); + t.join(1000); + assertFalse("thread has stopped", t.isAlive()); + } + + private static class MockProgressMonitor implements ProgressMonitor { + String taskTitle; + + int value; + + public void update(int completed) { + value += completed; + } + + public void start(int totalTasks) { + value = totalTasks; + } + + public void beginTask(String title, int totalWork) { + taskTitle = title; + value = totalWork; + } + + public void endTask() { + taskTitle = null; + value = 0; + } + + public boolean isCancelled() { + return false; + } + } +} diff --git a/org.eclipse.jgit.ui/build.properties b/org.eclipse.jgit.ui/build.properties index aa1a008269..84f1c95cfa 100644 --- a/org.eclipse.jgit.ui/build.properties +++ b/org.eclipse.jgit.ui/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + resources/ output.. = bin/ bin.includes = META-INF/,\ .,\ diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java index 4a5d4603ca..e699a72cfa 100644 --- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java +++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AWTPlotRenderer.java @@ -49,6 +49,7 @@ import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Polygon; +import java.io.Serializable; import org.eclipse.jgit.awtui.CommitGraphPane.GraphCellRender; import org.eclipse.jgit.awtui.SwingCommitList.SwingLane; @@ -57,11 +58,13 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revplot.AbstractPlotRenderer; import org.eclipse.jgit.revplot.PlotCommit; -final class AWTPlotRenderer extends AbstractPlotRenderer<SwingLane, Color> { +final class AWTPlotRenderer extends AbstractPlotRenderer<SwingLane, Color> + implements Serializable { + private static final long serialVersionUID = 1L; final GraphCellRender cell; - Graphics2D g; + transient Graphics2D g; AWTPlotRenderer(final GraphCellRender c) { cell = c; diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java index 13d408f19f..9c9d1f4a5c 100644 --- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java +++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/CommitGraphPane.java @@ -222,6 +222,7 @@ public class CommitGraphPane extends JTable { PlotCommit<SwingLane> commit; + @SuppressWarnings("unchecked") public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java index b58547a9e1..d8660e5ed6 100644 --- a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java +++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/SwingCommitList.java @@ -87,5 +87,10 @@ class SwingCommitList extends PlotCommitList<SwingCommitList.SwingLane> { public boolean equals(Object o) { return super.equals(o) && color.equals(((SwingLane)o).color); } + + @Override + public int hashCode() { + return super.hashCode() ^ color.hashCode(); + } } } diff --git a/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml b/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml index ba9d1d0996..0383ad9b87 100644 --- a/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml +++ b/org.eclipse.jgit/findBugs/FindBugsExcludeFilter.xml @@ -4,7 +4,7 @@ memory mapped segments if the JVM heap is out of address space. --> <Match> - <Class name="org.eclipse.jgit.lib.PackFile" /> + <Class name="org.eclipse.jgit.storage.file.PackFile" /> <Method name="mmap" /> <Bug pattern="DM_GC" /> </Match> @@ -21,7 +21,7 @@ here with == assuming .equals() style equality. --> <Match> - <Class name="org.eclipse.jgit.lib.util.StringUtils" /> + <Class name="org.eclipse.jgit.util.StringUtils" /> <Bug pattern="ES_COMPARING_PARAMETER_STRING_WITH_EQ" /> </Match> </FindBugsFilter> diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index 409b43492a..8af6e315fc 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -113,6 +113,7 @@ corruptionDetectedReReadingAt=Corruption detected re-reading at {0} couldNotCheckOutBecauseOfConflicts=Could not check out because of conflicts couldNotDeleteLockFileShouldNotHappen=Could not delete lock file. Should not happen couldNotDeleteTemporaryIndexFileShouldNotHappen=Could not delete temporary index file. Should not happen +couldNotGetAdvertisedRef=Could not get advertised Ref for branch {0} couldNotLockHEAD=Could not lock HEAD couldNotReadIndexInOneGo=Could not read index in one go, only {0} out of {1} read couldNotRenameDeleteOldIndex=Could not rename delete old index diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index d6c6018006..6a75a9eb86 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -173,6 +173,7 @@ public class JGitText extends TranslationBundle { /***/ public String couldNotCheckOutBecauseOfConflicts; /***/ public String couldNotDeleteLockFileShouldNotHappen; /***/ public String couldNotDeleteTemporaryIndexFileShouldNotHappen; + /***/ public String couldNotGetAdvertisedRef; /***/ public String couldNotLockHEAD; /***/ public String couldNotReadIndexInOneGo; /***/ public String couldNotRenameDeleteOldIndex; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java index b7f09d9a79..acbf3f10aa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PullCommand.java @@ -157,14 +157,6 @@ public class PullCommand extends GitCommand<PullResult> { throw new InvalidConfigurationException(MessageFormat.format( JGitText.get().missingConfigurationForKey, missingKey)); } - final String remoteUri = repo.getConfig().getString("remote", remote, - ConfigConstants.CONFIG_KEY_URL); - if (remoteUri == null) { - String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT - + remote + DOT + ConfigConstants.CONFIG_KEY_URL; - throw new InvalidConfigurationException(MessageFormat.format( - JGitText.get().missingConfigurationForKey, missingKey)); - } // get the name of the branch in the remote repository // stored in configuration key branch.<branch name>.merge @@ -175,13 +167,14 @@ public class PullCommand extends GitCommand<PullResult> { // check if the branch is configured for pull-rebase remoteBranchName = repoConfig.getString( ConfigConstants.CONFIG_BRANCH_SECTION, branchName, - ConfigConstants.CONFIG_KEY_MERGE); + ConfigConstants.CONFIG_KEY_REBASE); if (remoteBranchName != null) { // TODO implement pull-rebase throw new JGitInternalException( "Pull with rebase is not yet supported"); } } + if (remoteBranchName == null) { String missingKey = ConfigConstants.CONFIG_BRANCH_SECTION + DOT + branchName + DOT + ConfigConstants.CONFIG_KEY_MERGE; @@ -189,45 +182,66 @@ public class PullCommand extends GitCommand<PullResult> { JGitText.get().missingConfigurationForKey, missingKey)); } - if (monitor.isCancelled()) - throw new CanceledException(MessageFormat.format( - JGitText.get().operationCanceled, - JGitText.get().pullTaskName)); + final boolean isRemote = !remote.equals("."); + String remoteUri; + FetchResult fetchRes; + if (isRemote) { + remoteUri = repo.getConfig().getString("remote", remote, + ConfigConstants.CONFIG_KEY_URL); + if (remoteUri == null) { + String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT + + remote + DOT + ConfigConstants.CONFIG_KEY_URL; + throw new InvalidConfigurationException(MessageFormat.format( + JGitText.get().missingConfigurationForKey, missingKey)); + } - FetchCommand fetch = new FetchCommand(repo); - fetch.setRemote(remote); - if (monitor != null) - fetch.setProgressMonitor(monitor); - fetch.setTimeout(this.timeout); + if (monitor.isCancelled()) + throw new CanceledException(MessageFormat.format( + JGitText.get().operationCanceled, + JGitText.get().pullTaskName)); - FetchResult fetchRes = fetch.call(); + FetchCommand fetch = new FetchCommand(repo); + fetch.setRemote(remote); + if (monitor != null) + fetch.setProgressMonitor(monitor); + fetch.setTimeout(this.timeout); + + fetchRes = fetch.call(); + } else { + // we can skip the fetch altogether + remoteUri = "local repository"; + fetchRes = null; + } monitor.update(1); // we check the updates to see which of the updated branches corresponds // to the remote branch name - AnyObjectId commitToMerge = null; + AnyObjectId commitToMerge; - Ref r = fetchRes.getAdvertisedRef(remoteBranchName); - if (r == null) - r = fetchRes.getAdvertisedRef(Constants.R_HEADS + remoteBranchName); - if (r == null) { - // TODO: we should be able to get the mapping also if nothing was - // updated by the fetch; for the time being, use the naming - // convention as fall back - String remoteTrackingBranch = Constants.R_REMOTES + remote + '/' - + branchName; + if (isRemote) { + Ref r = null; + if (fetchRes != null) { + r = fetchRes.getAdvertisedRef(remoteBranchName); + if (r == null) + r = fetchRes.getAdvertisedRef(Constants.R_HEADS + + remoteBranchName); + } + if (r == null) + throw new JGitInternalException(MessageFormat.format(JGitText + .get().couldNotGetAdvertisedRef, remoteBranchName)); + else + commitToMerge = r.getObjectId(); + } else { try { - commitToMerge = repo.resolve(remoteTrackingBranch); + commitToMerge = repo.resolve(remoteBranchName); } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfPullCommand, e); } - - } else - commitToMerge = r.getObjectId(); + } if (monitor.isCancelled()) throw new CanceledException(MessageFormat.format( diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java index 3459109b6e..9d309d5076 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/MyersDiff.java @@ -300,7 +300,7 @@ public class MyersDiff<S extends Sequence> { final int getIndex(int d, int k) { // TODO: remove -if (((d + k - middleK) % 2) == 1) +if (((d + k - middleK) % 2) != 0) throw new RuntimeException(MessageFormat.format(JGitText.get().unexpectedOddResult, d, k, middleK)); return (d + k - middleK) / 2; } @@ -472,7 +472,7 @@ if (k < beginK || k > endK) if (k < backward.beginK || k > backward.endK) return false; // TODO: move out of loop - if (((d - 1 + k - backward.middleK) % 2) == 1) + if (((d - 1 + k - backward.middleK) % 2) != 0) return false; if (x < backward.getX(d - 1, k)) return false; @@ -514,7 +514,7 @@ if (k < beginK || k > endK) if (k < forward.beginK || k > forward.endK) return false; // TODO: move out of loop - if (((d + k - forward.middleK) % 2) == 1) + if (((d + k - forward.middleK) % 2) != 0) return false; if (x > forward.getX(d, k)) return false; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java index 38937410d0..57bea42db4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/AbbreviatedObjectId.java @@ -43,6 +43,7 @@ package org.eclipse.jgit.lib; +import java.io.Serializable; import java.text.MessageFormat; import org.eclipse.jgit.JGitText; @@ -61,7 +62,9 @@ import org.eclipse.jgit.util.RawParseUtils; * This class converts the hex string into a binary form, to make it more * efficient for matching against an object. */ -public final class AbbreviatedObjectId { +public final class AbbreviatedObjectId implements Serializable { + private static final long serialVersionUID = 1L; + /** * Test a string of characters to verify it is a hex format. * <p> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java index 9708bb2f92..9e8e256b01 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ThreadSafeProgressMonitor.java @@ -43,16 +43,35 @@ package org.eclipse.jgit.lib; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; /** * Wrapper around the general {@link ProgressMonitor} to make it thread safe. + * + * Updates to the underlying ProgressMonitor are made only from the thread that + * allocated this wrapper. Callers are responsible for ensuring the allocating + * thread uses {@link #pollForUpdates()} or {@link #waitForCompletion()} to + * update the underlying ProgressMonitor. + * + * Only {@link #update(int)}, {@link #isCancelled()}, and {@link #endWorker()} + * may be invoked from a worker thread. All other methods of the ProgressMonitor + * interface can only be called from the thread that allocates this wrapper. */ public class ThreadSafeProgressMonitor implements ProgressMonitor { private final ProgressMonitor pm; private final ReentrantLock lock; + private final Thread mainThread; + + private final AtomicInteger workers; + + private final AtomicInteger pendingUpdates; + + private final Semaphore process; + /** * Wrap a ProgressMonitor to be thread safe. * @@ -62,33 +81,87 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor { public ThreadSafeProgressMonitor(ProgressMonitor pm) { this.pm = pm; this.lock = new ReentrantLock(); + this.mainThread = Thread.currentThread(); + this.workers = new AtomicInteger(0); + this.pendingUpdates = new AtomicInteger(0); + this.process = new Semaphore(0); } public void start(int totalTasks) { - lock.lock(); - try { - pm.start(totalTasks); - } finally { - lock.unlock(); - } + if (!isMainThread()) + throw new IllegalStateException(); + pm.start(totalTasks); } public void beginTask(String title, int totalWork) { - lock.lock(); - try { - pm.beginTask(title, totalWork); - } finally { - lock.unlock(); + if (!isMainThread()) + throw new IllegalStateException(); + pm.beginTask(title, totalWork); + } + + /** Notify the monitor a worker is starting. */ + public void startWorker() { + startWorkers(1); + } + + /** + * Notify the monitor of workers starting. + * + * @param count + * the number of worker threads that are starting. + */ + public void startWorkers(int count) { + workers.addAndGet(count); + } + + /** Notify the monitor a worker is finished. */ + public void endWorker() { + if (workers.decrementAndGet() == 0) + process.release(); + } + + /** + * Non-blocking poll for pending updates. + * + * This method can only be invoked by the same thread that allocated this + * ThreadSafeProgressMonior. + */ + public void pollForUpdates() { + assert isMainThread(); + doUpdates(); + } + + /** + * Process pending updates and wait for workers to finish. + * + * This method can only be invoked by the same thread that allocated this + * ThreadSafeProgressMonior. + * + * @throws InterruptedException + * if the main thread is interrupted while waiting for + * completion of workers. + */ + public void waitForCompletion() throws InterruptedException { + assert isMainThread(); + while (0 < workers.get()) { + doUpdates(); + process.acquire(); } + doUpdates(); + } + + private void doUpdates() { + int cnt = pendingUpdates.getAndSet(0); + if (0 < cnt) + pm.update(cnt); } public void update(int completed) { - lock.lock(); - try { - pm.update(completed); - } finally { - lock.unlock(); - } + int old = pendingUpdates.getAndAdd(completed); + if (isMainThread()) + doUpdates(); + else if (old == 0) + process.release(); } public boolean isCancelled() { @@ -101,11 +174,12 @@ public class ThreadSafeProgressMonitor implements ProgressMonitor { } public void endTask() { - lock.lock(); - try { - pm.endTask(); - } finally { - lock.unlock(); - } + if (!isMainThread()) + throw new IllegalStateException(); + pm.endTask(); + } + + private boolean isMainThread() { + return Thread.currentThread() == mainThread; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java index 2e4489cda4..96c8361adb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/RefDirectory.java @@ -800,6 +800,13 @@ public class RefDirectory extends RefDatabase { final LooseRef n = scanRef(null, name); if (n == null) return packed.get(name); + + // check whether the found new ref is the an additional ref. These refs + // should not go into looseRefs + for (int i = 0; i < additionalRefsNames.length; i++) + if (name.equals(additionalRefsNames[i])) + return n; + if (looseRefs.compareAndSet(curList, curList.add(idx, n))) modCnt.incrementAndGet(); return n; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java index 5e551e9d49..aa0374618a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/DeltaTask.java @@ -46,7 +46,7 @@ package org.eclipse.jgit.storage.pack; import java.util.concurrent.Callable; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.lib.ThreadSafeProgressMonitor; final class DeltaTask implements Callable<Object> { private final PackConfig config; @@ -55,7 +55,7 @@ final class DeltaTask implements Callable<Object> { private final DeltaCache dc; - private final ProgressMonitor pm; + private final ThreadSafeProgressMonitor pm; private final int batchSize; @@ -64,7 +64,8 @@ final class DeltaTask implements Callable<Object> { private final ObjectToPack[] list; DeltaTask(PackConfig config, ObjectReader reader, DeltaCache dc, - ProgressMonitor pm, int batchSize, int start, ObjectToPack[] list) { + ThreadSafeProgressMonitor pm, int batchSize, int start, + ObjectToPack[] list) { this.config = config; this.templateReader = reader; this.dc = dc; @@ -82,6 +83,7 @@ final class DeltaTask implements Callable<Object> { dw.search(pm, list, start, batchSize); } finally { or.release(); + pm.endWorker(); } return null; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java index 20c4bb0f97..5986aca4e3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackWriter.java @@ -59,7 +59,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -675,7 +674,7 @@ public class PackWriter { } final DeltaCache dc = new ThreadSafeDeltaCache(config); - final ProgressMonitor pm = new ThreadSafeProgressMonitor(monitor); + final ThreadSafeProgressMonitor pm = new ThreadSafeProgressMonitor(monitor); // Guess at the size of batch we want. Because we don't really // have a way for a thread to steal work from another thread if @@ -713,6 +712,7 @@ public class PackWriter { i += batchSize; myTasks.add(new DeltaTask(config, reader, dc, pm, batchSize, start, list)); } + pm.startWorkers(myTasks.size()); final Executor executor = config.getExecutor(); final List<Throwable> errors = Collections @@ -720,7 +720,7 @@ public class PackWriter { if (executor instanceof ExecutorService) { // Caller supplied us a service, use it directly. // - runTasks((ExecutorService) executor, myTasks, errors); + runTasks((ExecutorService) executor, pm, myTasks, errors); } else if (executor == null) { // Caller didn't give us a way to run the tasks, spawn up a @@ -728,7 +728,7 @@ public class PackWriter { // ExecutorService pool = Executors.newFixedThreadPool(threads); try { - runTasks(pool, myTasks, errors); + runTasks(pool, pm, myTasks, errors); } finally { pool.shutdown(); for (;;) { @@ -746,7 +746,6 @@ public class PackWriter { // asynchronous execution. Wrap everything and hope it // can schedule these for us. // - final CountDownLatch done = new CountDownLatch(myTasks.size()); for (final DeltaTask task : myTasks) { executor.execute(new Runnable() { public void run() { @@ -754,14 +753,12 @@ public class PackWriter { task.call(); } catch (Throwable failure) { errors.add(failure); - } finally { - done.countDown(); } } }); } try { - done.await(); + pm.waitForCompletion(); } catch (InterruptedException ie) { // We can't abort the other tasks as we have no handle. // Cross our fingers and just break out anyway. @@ -789,13 +786,14 @@ public class PackWriter { } } - private void runTasks(ExecutorService pool, List<DeltaTask> tasks, - List<Throwable> errors) throws IOException { + private void runTasks(ExecutorService pool, ThreadSafeProgressMonitor pm, + List<DeltaTask> tasks, List<Throwable> errors) throws IOException { List<Future<?>> futures = new ArrayList<Future<?>>(tasks.size()); for (DeltaTask task : tasks) futures.add(pool.submit(task)); try { + pm.waitForCompletion(); for (Future<?> f : futures) { try { f.get(); |