mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
SVN: one-click merge: correct source url (actual for the case when branch point is above the wc root) + fork-join to find copy point
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.openapi.vcs;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.concurrency.Semaphore;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
// competes to calculate The Result
|
||||
public class ConcurrentTasks<T> {
|
||||
private volatile boolean myResultKnown;
|
||||
private final Semaphore mySemaphore;
|
||||
private volatile T myResult;
|
||||
private volatile int myCntAlive;
|
||||
private final ProgressIndicator myParentIndicator;
|
||||
private final Consumer<Consumer<T>>[] myTasks;
|
||||
|
||||
public void compute() {
|
||||
final EmptyProgressIndicator pi = new EmptyProgressIndicator() {
|
||||
@Override
|
||||
public void checkCanceled() {
|
||||
if (myResultKnown || (myParentIndicator != null) && myParentIndicator.isCanceled()) {
|
||||
super.cancel();
|
||||
}
|
||||
super.checkCanceled();
|
||||
}
|
||||
};
|
||||
myCntAlive = myTasks.length;
|
||||
mySemaphore.down();
|
||||
|
||||
final List<Future<?>> futures = new LinkedList<Future<?>>();
|
||||
for (final Consumer<Consumer<T>> task : myTasks) {
|
||||
if (myResultKnown) break;
|
||||
final Runnable computableProxy = new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
task.consume(new Consumer<T>() {
|
||||
public void consume(T t) {
|
||||
myResult = t;
|
||||
myResultKnown = true;
|
||||
-- myCntAlive;
|
||||
mySemaphore.up();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
-- myCntAlive;
|
||||
if (myCntAlive == 0) {
|
||||
mySemaphore.up();
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
final Future<?> future = ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
|
||||
public void run() {
|
||||
ProgressManager.getInstance().runProcess(computableProxy, pi);
|
||||
}
|
||||
});
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
mySemaphore.waitFor(1000);
|
||||
if (myResultKnown) break;
|
||||
pi.checkCanceled();
|
||||
}
|
||||
// in it possible to even interrupt() threads involved, but at the moment it's better for tasks themselves to check cancel status
|
||||
for (Future<?> future : futures) {
|
||||
if ((! future.isCancelled() && (! future.isDone()))) {
|
||||
future.cancel(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isResultKnown() {
|
||||
return myResultKnown;
|
||||
}
|
||||
|
||||
public T getResult() {
|
||||
return myResult;
|
||||
}
|
||||
|
||||
public ConcurrentTasks(final ProgressIndicator parentIndicator, final Consumer<Consumer<T>>... tasks) {
|
||||
myParentIndicator = parentIndicator;
|
||||
myTasks = tasks;
|
||||
mySemaphore = new Semaphore();
|
||||
}
|
||||
}
|
||||
@@ -262,7 +262,7 @@ public class CopiesPanel {
|
||||
private void mergeFrom(final WCInfo wcInfo, final VirtualFile root, final LinkLabel mergeLabel) {
|
||||
SelectBranchPopup.showForBranchRoot(myProject, root, new SelectBranchPopup.BranchSelectedCallback() {
|
||||
public void branchSelected(Project project, SvnBranchConfigurationNew configuration, String url, long revision) {
|
||||
new QuickMerge(project, url, wcInfo, configuration.getBaseName(url)).execute();
|
||||
new QuickMerge(project, url, wcInfo, configuration.getBaseName(url), configuration).execute();
|
||||
}
|
||||
}, "Select branch", mergeLabel);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.idea.svn.SvnVcs;
|
||||
import org.jetbrains.idea.svn.actions.ChangeListsMergerFactory;
|
||||
import org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew;
|
||||
import org.jetbrains.idea.svn.history.CopyData;
|
||||
import org.jetbrains.idea.svn.history.FirstInBranch;
|
||||
import org.jetbrains.idea.svn.history.SvnChangeList;
|
||||
@@ -47,6 +48,7 @@ import org.jetbrains.idea.svn.mergeinfo.SvnMergeInfoCache;
|
||||
import org.jetbrains.idea.svn.update.UpdateEventHandler;
|
||||
import org.tmatesoft.svn.core.SVNException;
|
||||
import org.tmatesoft.svn.core.SVNURL;
|
||||
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
@@ -56,31 +58,58 @@ import java.util.List;
|
||||
public class QuickMerge {
|
||||
private final Project myProject;
|
||||
private final String myBranchName;
|
||||
private final SvnBranchConfigurationNew myConfiguration;
|
||||
private final WCInfo myWcInfo;
|
||||
private final String mySourceUrl;
|
||||
private String mySourceUrl;
|
||||
private SvnVcs myVcs;
|
||||
private final String myTitle;
|
||||
|
||||
public QuickMerge(Project project, String sourceUrl, WCInfo wcInfo, final String branchName) {
|
||||
public QuickMerge(Project project, String sourceUrl, WCInfo wcInfo, final String branchName, final SvnBranchConfigurationNew configuration) {
|
||||
myProject = project;
|
||||
myBranchName = branchName;
|
||||
myConfiguration = configuration;
|
||||
myVcs = SvnVcs.getInstance(project);
|
||||
mySourceUrl = sourceUrl;
|
||||
myWcInfo = wcInfo;
|
||||
myTitle = "Merge from " + myBranchName;
|
||||
}
|
||||
|
||||
private void correctSourceUrl() throws SVNException {
|
||||
final SVNURL branch = myConfiguration.getWorkingBranch(myWcInfo.getUrl());
|
||||
if (branch != null && (! myWcInfo.getUrl().equals(branch))) {
|
||||
final String branchString = branch.toString();
|
||||
if (SVNPathUtil.isAncestor(branchString, myWcInfo.getRootUrl())) {
|
||||
final String subPath = SVNPathUtil.getRelativePath(branchString, myWcInfo.getRootUrl());
|
||||
mySourceUrl = SVNPathUtil.append(mySourceUrl, subPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean prompt(final String question) {
|
||||
return Messages.showOkCancelDialog(myProject, question, myTitle, Messages.getQuestionIcon()) == 0;
|
||||
}
|
||||
|
||||
@CalledInAwt
|
||||
public void execute() {
|
||||
if (mySourceUrl.equals(myWcInfo.getRootUrl())) {
|
||||
if (SVNPathUtil.isAncestor(mySourceUrl, myWcInfo.getRootUrl()) || SVNPathUtil.isAncestor(myWcInfo.getRootUrl(), mySourceUrl)) {
|
||||
showErrorBalloon("Cannot merge from self");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
correctSourceUrl();
|
||||
}
|
||||
catch (SVNException e) {
|
||||
showErrorBalloon(e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
if (! myWcInfo.getFormat().supportsMergeInfo()) {
|
||||
mergeAll();
|
||||
return;
|
||||
}
|
||||
|
||||
final int result = Messages.showDialog(myProject, "Merge all?", "Merge from " + myBranchName,
|
||||
final int result = Messages.showDialog(myProject, "Merge all?", myTitle,
|
||||
new String[]{"Merge &all", "&Select revisions to merge", "Cancel"}, 0, Messages.getQuestionIcon());
|
||||
if (result == 2) return;
|
||||
if (result == 0) {
|
||||
|
||||
@@ -169,6 +169,10 @@ public class SvnBranchPointsCalculator {
|
||||
if (! excRef.isNull()) {
|
||||
throw excRef.get();
|
||||
}
|
||||
|
||||
for (String key : myForSearchMap.keySet()) {
|
||||
Collections.sort((List<String>) myForSearchMap.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
public void put(final String uid, final String target, final BranchCopyData data) throws IOException {
|
||||
@@ -180,7 +184,16 @@ public class SvnBranchPointsCalculator {
|
||||
}
|
||||
map.put(target, data);
|
||||
myPersistentMap.put(uid, map);
|
||||
myForSearchMap.putValue(uid, target);
|
||||
if (myForSearchMap.containsKey(uid)) {
|
||||
final List<String> list = (List<String>)myForSearchMap.get(uid);
|
||||
final int idx = Collections.binarySearch(list, target);
|
||||
if (idx < 0) {
|
||||
final int insertionIdx = - idx - 1;
|
||||
list.add(insertionIdx, target);
|
||||
}
|
||||
} else {
|
||||
myForSearchMap.putValue(uid, target);
|
||||
}
|
||||
}
|
||||
myPersistentMap.force();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
package org.jetbrains.idea.svn.history;
|
||||
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.vcs.ConcurrentTasks;
|
||||
import com.intellij.util.Consumer;
|
||||
import org.jetbrains.idea.svn.SvnVcs;
|
||||
import org.tmatesoft.svn.core.*;
|
||||
@@ -27,27 +29,16 @@ import java.util.Map;
|
||||
|
||||
public class FirstInBranch implements Runnable {
|
||||
private final SvnVcs myVcs;
|
||||
private final String myRepositoryRoot;
|
||||
private final String myFullBranchUrl;
|
||||
private final String myFullTrunkUrl;
|
||||
private final String myBranchUrl;
|
||||
private final String myTrunkUrl;
|
||||
private final Consumer<CopyData> myConsumer;
|
||||
private CopyData myResult;
|
||||
private final boolean myPrimary;
|
||||
|
||||
public FirstInBranch(final SvnVcs vcs, final String repositoryRoot, final String branchUrl, final String trunkUrl, final Consumer<CopyData> consumer) {
|
||||
this(vcs, repositoryRoot, branchUrl, trunkUrl, consumer, true);
|
||||
}
|
||||
|
||||
public FirstInBranch(final SvnVcs vcs, final String repositoryRoot, final String branchUrl, final String trunkUrl, final Consumer<CopyData> consumer, final boolean primary) {
|
||||
myPrimary = primary;
|
||||
myVcs = vcs;
|
||||
myRepositoryRoot = repositoryRoot;
|
||||
myConsumer = consumer;
|
||||
|
||||
myResult = null;
|
||||
|
||||
myFullBranchUrl = branchUrl;
|
||||
myFullTrunkUrl = trunkUrl;
|
||||
myBranchUrl = relativePath(repositoryRoot, branchUrl);
|
||||
@@ -60,44 +51,84 @@ public class FirstInBranch implements Runnable {
|
||||
}
|
||||
|
||||
public void run() {
|
||||
final SVNLogClient logClient = myVcs.createLogClient();
|
||||
|
||||
final SVNURL branchURL;
|
||||
final SVNURL trunkURL;
|
||||
try {
|
||||
logClient.doLog(SVNURL.parseURIEncoded(myFullBranchUrl), null, SVNRevision.UNDEFINED, SVNRevision.HEAD, SVNRevision.create(0), true, true, 0,
|
||||
new ISVNLogEntryHandler() {
|
||||
public void handleLogEntry(final SVNLogEntry logEntry) throws SVNException {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
final Map map = logEntry.getChangedPaths();
|
||||
for (Object o : map.values()) {
|
||||
final SVNLogEntryPath path = (SVNLogEntryPath) o;
|
||||
final String localPath = path.getPath();
|
||||
final String copyPath = path.getCopyPath();
|
||||
|
||||
if ('A' == path.getType() &&
|
||||
(myBranchUrl.equals(localPath) || SVNPathUtil.isAncestor(localPath, myBranchUrl)) &&
|
||||
(myTrunkUrl.equals(copyPath)) || SVNPathUtil.isAncestor(copyPath, myTrunkUrl)) {
|
||||
myResult = new CopyData(path.getCopyRevision(), logEntry.getRevision(), myPrimary);
|
||||
throw new MockException();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (MockException e) {
|
||||
myConsumer.consume(myResult);
|
||||
return;
|
||||
branchURL = SVNURL.parseURIEncoded(myFullBranchUrl);
|
||||
trunkURL = SVNURL.parseURIEncoded(myFullTrunkUrl);
|
||||
}
|
||||
catch (SVNException e) {
|
||||
myConsumer.consume(myResult);
|
||||
myConsumer.consume(null);
|
||||
return;
|
||||
}
|
||||
if (myPrimary) {
|
||||
new FirstInBranch(myVcs, myRepositoryRoot, myFullTrunkUrl, myFullBranchUrl, myConsumer, false).run();
|
||||
|
||||
final ConcurrentTasks<CopyData> tasks =
|
||||
new ConcurrentTasks<CopyData>(ProgressManager.getInstance().getProgressIndicator(), createTask(branchURL), createTask(trunkURL));
|
||||
tasks.compute();
|
||||
if (tasks.isResultKnown()) {
|
||||
myConsumer.consume(tasks.getResult());
|
||||
} else {
|
||||
myConsumer.consume(myResult);
|
||||
myConsumer.consume(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class MockException extends RuntimeException {}
|
||||
private Consumer<Consumer<CopyData>> createTask(final SVNURL branchURL) {
|
||||
return new Consumer<Consumer<CopyData>>() {
|
||||
public void consume(final Consumer<CopyData> copyDataConsumer) {
|
||||
final SVNLogClient logClient = myVcs.createLogClient();
|
||||
final long start1 = getStart(logClient, branchURL);
|
||||
if (start1 > 0) {
|
||||
final SVNRevision start1Rev = SVNRevision.create(start1);
|
||||
try {
|
||||
logClient.doLog(branchURL, null, SVNRevision.UNDEFINED, start1Rev, start1Rev, true, true, 1, new ISVNLogEntryHandler() {
|
||||
public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
|
||||
checkForCopy(logEntry, copyDataConsumer);
|
||||
}
|
||||
});
|
||||
} catch (SVNException e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static long getStart(final SVNLogClient logClient, final SVNURL url) {
|
||||
final Ref<Long> myRevisionCandidate = new Ref<Long>(0L);
|
||||
try {
|
||||
logClient.doLog(url, null, SVNRevision.UNDEFINED, SVNRevision.HEAD, SVNRevision.create(0),
|
||||
true, false, 0, new ISVNLogEntryHandler() {
|
||||
public void handleLogEntry(SVNLogEntry logEntry) throws SVNException {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
myRevisionCandidate.set(logEntry.getRevision());
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (SVNException e) {
|
||||
//
|
||||
}
|
||||
return myRevisionCandidate.get();
|
||||
}
|
||||
|
||||
private void checkForCopy(final SVNLogEntry logEntry, final Consumer<CopyData> result) {
|
||||
final Map map = logEntry.getChangedPaths();
|
||||
for (Object o : map.values()) {
|
||||
final SVNLogEntryPath path = (SVNLogEntryPath) o;
|
||||
final String localPath = path.getPath();
|
||||
final String copyPath = path.getCopyPath();
|
||||
|
||||
if ('A' == path.getType()) {
|
||||
if ((myBranchUrl.equals(localPath) || SVNPathUtil.isAncestor(localPath, myBranchUrl)) &&
|
||||
(myTrunkUrl.equals(copyPath)) || SVNPathUtil.isAncestor(copyPath, myTrunkUrl)) {
|
||||
result.consume(new CopyData(path.getCopyRevision(), logEntry.getRevision(), true));
|
||||
} else {
|
||||
if ((myBranchUrl.equals(copyPath) || SVNPathUtil.isAncestor(copyPath, myBranchUrl)) &&
|
||||
(myTrunkUrl.equals(localPath)) || SVNPathUtil.isAncestor(localPath, myTrunkUrl)) {
|
||||
result.consume(new CopyData(path.getCopyRevision(), logEntry.getRevision(), false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,16 +56,20 @@ public class SvnIntegrateChangesTask extends Task.Backgroundable {
|
||||
|
||||
private final List<VcsException> myExceptions;
|
||||
|
||||
private final UpdateEventHandler myHandler;
|
||||
private final IMerger myMerger;
|
||||
private final ResolveWorker myResolveWorker;
|
||||
private UpdateEventHandler myHandler;
|
||||
private IMerger myMerger;
|
||||
private ResolveWorker myResolveWorker;
|
||||
private FilePathImpl myMergeTarget;
|
||||
private final String myTitle;
|
||||
private final MergerFactory myMergerFactory;
|
||||
private final SVNURL myCurrentBranchUrl;
|
||||
private boolean myDryRun;
|
||||
|
||||
public SvnIntegrateChangesTask(final SvnVcs vcs, final WorkingCopyInfo info, final MergerFactory mergerFactory,
|
||||
final SVNURL currentBranchUrl, final String title, final boolean dryRun) {
|
||||
super(vcs.getProject(), title, true, VcsConfiguration.getInstance(vcs.getProject()).getUpdateOption());
|
||||
myMergerFactory = mergerFactory;
|
||||
myCurrentBranchUrl = currentBranchUrl;
|
||||
myDryRun = dryRun;
|
||||
myTitle = title;
|
||||
|
||||
@@ -76,10 +80,6 @@ public class SvnIntegrateChangesTask extends Task.Backgroundable {
|
||||
|
||||
myAccomulatedFiles = new UpdatedFilesReverseSide(UpdatedFiles.create());
|
||||
myExceptions = new ArrayList<VcsException>();
|
||||
|
||||
myHandler = new IntegrateEventHandler(myVcs, ProgressManager.getInstance().getProgressIndicator());
|
||||
myMerger = mergerFactory.createMerger(myVcs, new File(info.getLocalPath()), myHandler, currentBranchUrl);
|
||||
myResolveWorker = new ResolveWorker(myInfo.isUnderProjectRoot(), myProject);
|
||||
}
|
||||
|
||||
private void indicatorOnStart() {
|
||||
@@ -93,6 +93,10 @@ public class SvnIntegrateChangesTask extends Task.Backgroundable {
|
||||
}
|
||||
|
||||
public void run(@NotNull final ProgressIndicator indicator) {
|
||||
myHandler = new IntegrateEventHandler(myVcs, ProgressManager.getInstance().getProgressIndicator());
|
||||
myMerger = myMergerFactory.createMerger(myVcs, new File(myInfo.getLocalPath()), myHandler, myCurrentBranchUrl);
|
||||
myResolveWorker = new ResolveWorker(myInfo.isUnderProjectRoot(), myProject);
|
||||
|
||||
BlockReloadingUtil.block();
|
||||
myProjectLevelVcsManager.startBackgroundVcsOperation();
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ public class BranchInfo {
|
||||
final String absoluteInTrunkPath = SVNPathUtil.append(myRepositoryRoot, path);
|
||||
if (! absoluteInTrunkPath.startsWith(myTrunkPathCorrespondingToLocalBranchPath)) {
|
||||
result.putValue(SvnMergeInfoCache.MergeCheckResult.NOT_EXISTS, path);
|
||||
continue;
|
||||
}
|
||||
final String relativeToTrunkPath = absoluteInTrunkPath.substring(myTrunkPathCorrespondingToLocalBranchPath.length());
|
||||
final String localPathInBranch = new File(branchPath, relativeToTrunkPath).getAbsolutePath();
|
||||
|
||||
Reference in New Issue
Block a user