blob: 8100d73da220abbbcd9b328e29fee2be2681ad06 [file] [log] [blame]
/*
* Copyright 2000-2012 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 org.jetbrains.idea.svn.status;
import com.intellij.openapi.util.Getter;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.util.containers.Convertor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnUtil;
import org.jetbrains.idea.svn.api.BaseSvnClient;
import org.jetbrains.idea.svn.api.Depth;
import org.jetbrains.idea.svn.commandLine.*;
import org.jetbrains.idea.svn.info.Info;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.wc.*;
import org.tmatesoft.svn.core.wc2.SvnTarget;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Created with IntelliJ IDEA.
* User: Irina.Chernushina
* Date: 1/25/12
* Time: 5:21 PM
*/
public class CmdStatusClient extends BaseSvnClient implements StatusClient {
@Override
public long doStatus(final File path,
final SVNRevision revision,
final Depth depth,
boolean remote,
boolean reportAll,
boolean includeIgnored,
boolean collectParentExternals,
final StatusConsumer handler,
final Collection changeLists) throws SvnBindException {
File base = path.isDirectory() ? path : path.getParentFile();
base = CommandUtil.correctUpToExistingParent(base);
final Info infoBase = myFactory.createInfoClient().doInfo(base, revision);
List<String> parameters = new ArrayList<String>();
putParameters(parameters, path, depth, remote, reportAll, includeIgnored, changeLists);
CommandExecutor command = execute(myVcs, SvnTarget.fromFile(path), SvnCommandName.st, parameters, null);
parseResult(path, revision, handler, base, infoBase, command);
return 0;
}
private void parseResult(final File path,
SVNRevision revision,
StatusConsumer handler,
File base,
Info infoBase,
CommandExecutor command) throws SvnBindException {
String result = command.getOutput();
if (StringUtil.isEmptyOrSpaces(result)) {
throw new SvnBindException("Status request returned nothing for command: " + command.getCommandText());
}
try {
final SvnStatusHandler[] svnHandl = new SvnStatusHandler[1];
svnHandl[0] = createStatusHandler(revision, handler, base, infoBase, svnHandl);
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
parser.parse(new ByteArrayInputStream(result.trim().getBytes(CharsetToolkit.UTF8_CHARSET)), svnHandl[0]);
if (!svnHandl[0].isAnythingReported()) {
if (!SvnUtil.isSvnVersioned(myVcs, path)) {
throw new SvnBindException(SVNErrorCode.WC_NOT_DIRECTORY, "Command - " + command.getCommandText() + ". Result - " + result);
} else {
// return status indicating "NORMAL" state
// typical output would be like
// <status>
// <target path="1.txt"></target>
// </status>
// so it does not contain any <entry> element and current parsing logic returns null
PortableStatus status = new PortableStatus();
status.setFile(path);
status.setPath(path.getAbsolutePath());
status.setContentsStatus(StatusType.STATUS_NORMAL);
status.setInfoGetter(new Getter<Info>() {
@Override
public Info get() {
return createInfoGetter(null).convert(path);
}
});
try {
handler.consume(status);
}
catch (SVNException e) {
throw new SvnBindException(e);
}
}
}
}
catch (SvnExceptionWrapper e) {
throw new SvnBindException(e.getCause());
} catch (IOException e) {
throw new SvnBindException(e);
}
catch (ParserConfigurationException e) {
throw new SvnBindException(e);
}
catch (SAXException e) {
// status parsing errors are logged separately as sometimes there are parsing errors connected to terminal output handling.
// these errors primarily occur when status output is rather large.
// and status output could be large, for instance, when working copy is locked (seems that each file is listed in status output).
command.logCommand();
throw new SvnBindException(e);
}
}
private static void putParameters(@NotNull List<String> parameters,
@NotNull File path,
@Nullable Depth depth,
boolean remote,
boolean reportAll,
boolean includeIgnored,
@Nullable Collection changeLists) {
CommandUtil.put(parameters, path);
CommandUtil.put(parameters, depth);
CommandUtil.put(parameters, remote, "-u");
CommandUtil.put(parameters, reportAll, "--verbose");
CommandUtil.put(parameters, includeIgnored, "--no-ignore");
// TODO: Fix this check - update corresponding parameters in StatusClient
CommandUtil.putChangeLists(parameters, changeLists);
parameters.add("--xml");
}
public SvnStatusHandler createStatusHandler(final SVNRevision revision,
final StatusConsumer handler,
final File base,
final Info infoBase, final SvnStatusHandler[] svnHandl) {
final SvnStatusHandler.ExternalDataCallback callback = createStatusCallback(handler, base, infoBase, svnHandl);
return new SvnStatusHandler(callback, base, createInfoGetter(revision));
}
private Convertor<File, Info> createInfoGetter(final SVNRevision revision) {
return new Convertor<File, Info>() {
@Override
public Info convert(File o) {
try {
return myFactory.createInfoClient().doInfo(o, revision);
}
catch (SvnBindException e) {
throw new SvnExceptionWrapper(e);
}
}
};
}
public static SvnStatusHandler.ExternalDataCallback createStatusCallback(final StatusConsumer handler,
final File base,
final Info infoBase,
final SvnStatusHandler[] svnHandl) {
final Map<File, Info> externalsMap = new HashMap<File, Info>();
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) {
Info 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 (StatusType.STATUS_EXTERNAL.equals(pending.getNodeStatus())) {
externalsMap.put(pending.getFile(), pending.getInfo());
}
handler.consume(pending);
}
catch (SVNException e) {
throw new SvnExceptionWrapper(e);
}
}
@Override
public void switchChangeList(String newList) {
changelistName[0] = newList;
}
};
}
@Override
public Status doStatus(File path, boolean remote) throws SvnBindException {
final Status[] svnStatus = new Status[1];
doStatus(path, SVNRevision.UNDEFINED, Depth.EMPTY, remote, false, false, false, new StatusConsumer() {
@Override
public void consume(Status status) throws SVNException {
svnStatus[0] = status;
}
}, null);
return svnStatus[0];
}
}