IDEA-94873 Subversion: renamed/moved file located in root added as svn:externals gets incorrect status when command line client is used

when parsing CL status, take into account that there could be externals in the way, and URLs should be calculated with respect to them
+ test
This commit is contained in:
irengrig
2012-11-15 15:11:04 +04:00
parent b1ace7dbfe
commit 27ea3a3073
3 changed files with 204 additions and 61 deletions
@@ -26,10 +26,7 @@ import org.jetbrains.idea.svn.portable.SvnExceptionWrapper;
import org.jetbrains.idea.svn.portable.SvnStatusClientI;
import org.tmatesoft.svn.core.*;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNInfo;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
@@ -39,6 +36,8 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
@@ -101,62 +100,10 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI {
// todo can not understand why revision can be used here
final SvnSimpleCommand command = new SvnSimpleCommand(myProject, base, SvnCommandName.st);
putParameters(depth, remote, reportAll, includeIgnored, changeLists, command);
if (depth != null) {
command.addParameters("--depth", depth.getName());
}
if (remote) {
command.addParameters("-u");
}
if (reportAll) {
command.addParameters("-v");
}
if (includeIgnored) {
command.addParameters("--no-ignore");
}
// no way in interface to ignore externals
/*if (! collectParentExternals) {
command.addParameters("--ignore-externals");
}*/
//--changelist (--cl) ARG
changelistsToCommand(changeLists, command);
command.addParameters("--xml");
final String[] changelistName = new String[1];
final SvnStatusHandler[] svnHandl = new SvnStatusHandler[1];
svnHandl[0] = new SvnStatusHandler(new SvnStatusHandler.ExternalDataCallback() {
@Override
public void switchPath() {
final PortableStatus pending = svnHandl[0].getPending();
pending.setChangelistName(changelistName[0]);
try {
if (infoBase != null) {
final String append = SVNPathUtil.append(infoBase.getURL().toString(), FileUtil.toSystemIndependentName(pending.getPath()));
pending.setURL(SVNURL.parseURIEncoded(append));
}
handler.handleStatus(pending);
}
catch (SVNException e) {
throw new SvnExceptionWrapper(e);
}
}
@Override
public void switchChangeList(String newList) {
changelistName[0] = newList;
}
}, base, new Convertor<File, SVNInfo>() {
@Override
public SVNInfo convert(File o) {
try {
return myInfoClient.doInfo(o, revision);
}
catch (SVNException e) {
throw new SvnExceptionWrapper(e);
}
}
});
svnHandl[0] = createStatusHandler(revision, handler, base, infoBase, svnHandl);
try {
final String result = command.run();
@@ -185,6 +132,109 @@ public class SvnCommandLineStatusClient implements SvnStatusClientI {
return 0;
}
private void putParameters(SVNDepth depth,
boolean remote,
boolean reportAll,
boolean includeIgnored,
Collection changeLists,
SvnSimpleCommand command) {
if (depth != null) {
command.addParameters("--depth", depth.getName());
}
if (remote) {
command.addParameters("-u");
}
if (reportAll) {
command.addParameters("-v");
}
if (includeIgnored) {
command.addParameters("--no-ignore");
}
// no way in interface to ignore externals
/*if (! collectParentExternals) {
command.addParameters("--ignore-externals");
}*/
//--changelist (--cl) ARG
changelistsToCommand(changeLists, command);
command.addParameters("--xml");
}
public SvnStatusHandler createStatusHandler(final SVNRevision revision,
final ISVNStatusHandler handler,
final File base,
final SVNInfo infoBase, final SvnStatusHandler[] svnHandl) {
final SvnStatusHandler.ExternalDataCallback callback = createStatusCallback(handler, base, infoBase, svnHandl);
return new SvnStatusHandler(callback, base, new Convertor<File, SVNInfo>() {
@Override
public SVNInfo convert(File o) {
try {
return myInfoClient.doInfo(o, revision);
}
catch (SVNException e) {
throw new SvnExceptionWrapper(e);
}
}
});
}
public static SvnStatusHandler.ExternalDataCallback createStatusCallback(final ISVNStatusHandler handler,
final File base,
final SVNInfo infoBase,
final SvnStatusHandler[] svnHandl) {
final Map<File, SVNInfo> externalsMap = new HashMap<File, SVNInfo>();
final String[] changelistName = new String[1];
return new SvnStatusHandler.ExternalDataCallback() {
@Override
public void switchPath() {
final PortableStatus pending = svnHandl[0].getPending();
pending.setChangelistName(changelistName[0]);
try {
//if (infoBase != null) {
SVNInfo baseInfo = infoBase;
File baseFile = base;
final File pendingFile = new File(pending.getPath());
if (! externalsMap.isEmpty()) {
for (File file : externalsMap.keySet()) {
if (FileUtil.isAncestor(file, pendingFile, false)) {
baseInfo = externalsMap.get(file);
baseFile = file;
break;
}
}
}
if (baseInfo != null) {
final String append;
final String systemIndependentPath = FileUtil.toSystemIndependentName(pending.getPath());
if (pendingFile.isAbsolute()) {
final String relativePath =
FileUtil.getRelativePath(FileUtil.toSystemIndependentName(baseFile.getPath()), systemIndependentPath, '/');
append = SVNPathUtil.append(baseInfo.getURL().toString(), FileUtil.toSystemIndependentName(relativePath));
}
else {
append = SVNPathUtil.append(baseInfo.getURL().toString(), systemIndependentPath);
}
pending.setURL(SVNURL.parseURIEncoded(append));
}
if (SVNStatusType.STATUS_EXTERNAL.equals(pending.getNodeStatus())) {
externalsMap.put(pending.getFile(), pending.getInfo());
}
handler.handleStatus(pending);
}
catch (SVNException e) {
throw new SvnExceptionWrapper(e);
}
}
@Override
public void switchChangeList(String newList) {
changelistName[0] = newList;
}
};
}
public static void changelistsToCommand(Collection changeLists, SvnSimpleCommand command) {
if (changeLists != null) {
for (Object o : changeLists) {
@@ -150,6 +150,10 @@ public class PortableStatus extends SVNStatus {
return myInfo;
}
public SVNInfo getInfo() {
return initInfo();
}
@Override
public SVNNodeKind getKind() {
if (myFileExists) return super.getKind();
@@ -21,6 +21,7 @@ import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.MultiMap;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.jetbrains.idea.svn.commandLine.SvnCommandLineStatusClient;
import org.jetbrains.idea.svn.commandLine.SvnInfoHandler;
import org.jetbrains.idea.svn.commandLine.SvnStatusHandler;
import org.jetbrains.idea.svn.portable.IdeaSVNInfo;
@@ -29,8 +30,10 @@ import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
import org.tmatesoft.svn.core.wc.SVNInfo;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
@@ -551,7 +554,8 @@ public class SvnParseCommandLineParseTest extends TestCase {
"</status>\n";
final SvnStatusHandler[] handlerArr = new SvnStatusHandler[1];
final SvnStatusHandler handler = new SvnStatusHandler(new SvnStatusHandler.ExternalDataCallback() {
final SvnStatusHandler handler = new
SvnStatusHandler(new SvnStatusHandler.ExternalDataCallback() {
@Override
public void switchPath() {
handlerArr[0].getPending().getKind();
@@ -574,8 +578,7 @@ public class SvnParseCommandLineParseTest extends TestCase {
final int secondIdx = o.getPath().indexOf(":", idx + 1);
Assert.assertTrue(o.getPath(), secondIdx == -1);
try {
return new IdeaSVNInfo("C:/base/1", SVNURL.parseURIEncoded("http://a.b.c"), SVNRevision.HEAD, SVNNodeKind.FILE, "",
SVNURL.parseURIEncoded("http://a.b.c"), 1, new Date(), "me", null, SVNDepth.EMPTY, 1);
return createStubInfo("C:/base/1", "http://a.b.c");
}
catch (SVNException e) {
//
@@ -589,4 +592,90 @@ public class SvnParseCommandLineParseTest extends TestCase {
parser.parse(new ByteArrayInputStream(s.getBytes(CharsetToolkit.UTF8_CHARSET)), handler);
final MultiMap<String,PortableStatus> changes = handler.getCurrentListChanges();
}
private IdeaSVNInfo createStubInfo(final String basePath, final String baseUrl) throws SVNException {
return new IdeaSVNInfo(basePath, SVNURL.parseURIEncoded(baseUrl), SVNRevision.HEAD, SVNNodeKind.FILE, "",
SVNURL.parseURIEncoded("http://a.b.c"), 1, new Date(), "me", null, SVNDepth.EMPTY, 1);
}
public void testStatusInExternalMove() throws Exception {
final String status = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<status>\n" +
"<target\n" +
" path=\".\">\n" +
"<entry\n" +
" path=\"slave\">\n" +
"<wc-status\n" +
" item=\"external\"\n" +
" props=\"none\">\n" +
"</wc-status>\n" +
"</entry>\n" +
"<entry\n" +
" path=\"src\\com\\test\\just\">\n" +
"<wc-status\n" +
" props=\"none\"\n" +
" item=\"unversioned\">\n" +
"</wc-status>\n" +
"</entry>\n" +
"<entry\n" +
" path=\"C:\\TestProjects\\sortedProjects\\Subversion\\local2\\sep12main\\main\\slave\\src\\com\\slave\\MacMessagesParser.java\">\n" +
"<wc-status\n" +
" item=\"added\"\n" +
" props=\"none\"\n" +
" copied=\"true\">\n" +
"<commit\n" +
" revision=\"7\">\n" +
"<author>admin</author>\n" +
"<date>2012-09-12T12:16:51.621000Z</date>\n" +
"</commit>\n" +
"</wc-status>\n" +
"</entry>\n" +
"<entry\n" +
" path=\"C:\\TestProjects\\sortedProjects\\Subversion\\local2\\sep12main\\main\\slave\\src\\com\\slave\\SomeOtherClass.java\">\n" +
"<wc-status\n" +
" props=\"none\"\n" +
" item=\"deleted\"\n" +
" revision=\"7\">\n" +
"<commit\n" +
" revision=\"7\">\n" +
"<author>admin</author>\n" +
"<date>2012-09-12T12:16:51.621000Z</date>\n" +
"</commit>\n" +
"</wc-status>\n" +
"</entry>\n" +
"</target>\n" +
"</status>";
final String basePath = "C:\\TestProjects\\sortedProjects\\Subversion\\local2\\sep12main\\main";
final SvnStatusHandler[] handler = new SvnStatusHandler[1];
final File baseFile = new File(basePath);
final SvnStatusHandler.ExternalDataCallback callback = SvnCommandLineStatusClient.createStatusCallback(new ISVNStatusHandler() {
@Override
public void handleStatus(SVNStatus status) throws SVNException {
System.out.println(status.getURL());
if (new File("C:\\TestProjects\\sortedProjects\\Subversion\\local2\\sep12main\\main\\slave\\src\\com\\slave\\MacMessagesParser.java").equals(status.getFile())) {
Assert.assertEquals("http://external/src/com/slave/MacMessagesParser.java", status.getURL().toString());
}
if (new File("C:\\TestProjects\\sortedProjects\\Subversion\\local2\\sep12main\\main\\slave\\src\\com\\slave\\SomeOtherClass.java").equals(status.getFile())) {
Assert.assertEquals("http://external/src/com/slave/SomeOtherClass.java", status.getURL().toString());
}
}
}, baseFile, createStubInfo(basePath, "http://mainurl/"), handler);
handler[0] = new SvnStatusHandler(callback, baseFile, new Convertor<File, SVNInfo>() {
@Override
public SVNInfo convert(File o) {
try {
if (new File("C:\\TestProjects\\sortedProjects\\Subversion\\local2\\sep12main\\main\\slave").equals(o)) {
return createStubInfo(o.getPath(), "http://external");
}
return createStubInfo(o.getPath(), "http://12345");
}
catch (SVNException e) {
throw new RuntimeException(e);
}
}
});
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
parser.parse(new ByteArrayInputStream(status.getBytes(CharsetToolkit.UTF8_CHARSET)), handler[0]);
final MultiMap<String,PortableStatus> changes = handler[0].getCurrentListChanges();
}
}