summaryrefslogtreecommitdiff
path: root/plugins/svn4idea/src/org/jetbrains/idea/svn/status/CmdStatusClient.java
blob: 8100d73da220abbbcd9b328e29fee2be2681ad06 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*
 * 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];
  }
}