SVN 1.7: status through command line
authorirengrig <Irina.Chernushina@jetbrains.com>
Tue, 31 Jan 2012 09:11:49 +0000 (13:11 +0400)
committerirengrig <Irina.Chernushina@jetbrains.com>
Tue, 31 Jan 2012 09:11:49 +0000 (13:11 +0400)
24 files changed:
platform/vcs-api/src/com/intellij/openapi/vcs/LineHandlerHelper.java [new file with mode: 0644]
platform/vcs-api/src/com/intellij/openapi/vcs/LineProcessEventListener.java [new file with mode: 0644]
platform/vcs-api/src/com/intellij/openapi/vcs/ProcessEventListener.java [new file with mode: 0644]
platform/vcs-api/src/com/intellij/util/ProducerConsumer.java [moved from platform/util/src/com/intellij/util/ProducerConsumer.java with 54% similarity]
plugins/git4idea/src/git4idea/commands/GitHandler.java
plugins/git4idea/src/git4idea/commands/GitHandlerListener.java
plugins/git4idea/src/git4idea/commands/GitLineHandler.java
plugins/git4idea/src/git4idea/commands/GitLineHandlerListener.java
plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnApplicationSettings17.java
plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.form
plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnConfigurable.java
plugins/svn4idea/src/org/jetbrains/idea/svn17/SvnRecursiveStatusWalker.java
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommand.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandLineInfoClient.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandLineStatusClient.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandName.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnInfoHandler.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnInfoStructure.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnLineCommand.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnSimpleCommand.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnStatusHandler.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnTextCommand.java [new file with mode: 0644]
plugins/svn4idea/src/org/jetbrains/idea/svn17/portable/ConflictActionConvertor.java
plugins/svn4idea/src/org/jetbrains/idea/svn17/portable/PortableStatus.java

diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/LineHandlerHelper.java b/platform/vcs-api/src/com/intellij/openapi/vcs/LineHandlerHelper.java
new file mode 100644 (file)
index 0000000..9e54e80
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * 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 com.intellij.openapi.vcs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 5:03 PM
+ */
+public class LineHandlerHelper {
+  /**
+   * Split text into lines. New line characters are treated as separators. So if the text starts
+   * with newline, empty string will be the first element, if the text ends with new line, the
+   * empty string will be the last element. The returned lines will be substrings of
+   * the text argument. The new line characters are included into the line text.
+   *
+   * @param text a text to split
+   * @return a list of elements (note that there are always at least one element)
+   */
+  public static List<String> splitText(String text) {
+    int startLine = 0;
+    int i = 0;
+    int n = text.length();
+    ArrayList<String> rc = new ArrayList<String>();
+    while (i < n) {
+      switch (text.charAt(i)) {
+        case '\n':
+          i++;
+          if (i < n && text.charAt(i) == '\r') {
+            i++;
+          }
+          rc.add(text.substring(startLine, i));
+          startLine = i;
+          break;
+        case '\r':
+          i++;
+          if (i < n && text.charAt(i) == '\n') {
+            i++;
+          }
+          rc.add(text.substring(startLine, i));
+          startLine = i;
+          break;
+        default:
+          i++;
+      }
+    }
+    if (startLine == text.length()) {
+      // still add empty line or previous line wouldn't be treated as completed
+      rc.add("");
+    } else {
+      rc.add(text.substring(startLine, i));
+    }
+    return rc;
+  }
+
+  /**
+   * Trim line separator from new line if it presents
+   *
+   * @param line a line to process
+   * @return a trimmed line
+   */
+  public static String trimLineSeparator(String line) {
+    int n = line.length();
+    if (n == 0) {
+      return line;
+    }
+    char ch = line.charAt(n - 1);
+    if (ch == '\n' || ch == '\r') {
+      n--;
+    }
+    else {
+      return line;
+    }
+    if (n > 0) {
+      char ch2 = line.charAt(n - 1);
+      if ((ch2 == '\n' || ch2 == '\r') && ch2 != ch) {
+        n--;
+      }
+    }
+    return line.substring(0, n);
+
+  }
+}
diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/LineProcessEventListener.java b/platform/vcs-api/src/com/intellij/openapi/vcs/LineProcessEventListener.java
new file mode 100644 (file)
index 0000000..dbeac2d
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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 com.intellij.openapi.vcs;
+
+import com.intellij.openapi.util.Key;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 5:08 PM
+ */
+public interface LineProcessEventListener extends ProcessEventListener {
+  /**
+   * This method is invoked when line (as separated by \n or \r) becomes available.
+   *
+   * @param line       a line of the text
+   * @param outputType a type of output (one of constants from {@link com.intellij.execution.process.ProcessOutputTypes})
+   */
+  @SuppressWarnings({"UnusedParameters", "UnnecessaryFullyQualifiedName"})
+  void onLineAvailable(String line, Key outputType);
+}
diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ProcessEventListener.java b/platform/vcs-api/src/com/intellij/openapi/vcs/ProcessEventListener.java
new file mode 100644 (file)
index 0000000..fc85ebc
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * 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 com.intellij.openapi.vcs;
+
+import java.util.EventListener;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 3:47 PM
+ */
+public interface ProcessEventListener extends EventListener {
+  /**
+   * This method is invoked when git process is terminated
+   *
+   * @param exitCode a exit code
+   */
+  void processTerminated(int exitCode);
+
+  /**
+   * This method is invoked if starting git process failed with exception
+   *
+   * @param exception an exception
+   */
+  void startFailed(Throwable exception);
+}
similarity index 54%
rename from platform/util/src/com/intellij/util/ProducerConsumer.java
rename to platform/vcs-api/src/com/intellij/util/ProducerConsumer.java
index 7395bdb729434470fa91bd1d769342121288f9f6..01a89140185361f42535a62456ad1f83aad7812a 100644 (file)
@@ -15,6 +15,9 @@
  */
 package com.intellij.util;
 
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.util.concurrency.Semaphore;
+
 import java.util.ArrayDeque;
 
 /**
@@ -29,7 +32,7 @@ public class ProducerConsumer<T> {
   private final Consumer<T> myConsumer;
   private final int myMaxSize;
   private final Object myLock;
-  private final Thread myConsumerThread;
+  private final ConsumerRunnable myConsumerThread;
   private boolean myIsAlive;
 
   public ProducerConsumer(final Consumer<T> consumer) {
@@ -49,29 +52,73 @@ public class ProducerConsumer<T> {
   }
 
   public ProducerConsumer(final Consumer<T> consumer, final int maxSize) {
+    this(consumer, maxSize, false);
+  }
+
+  public ProducerConsumer(final Consumer<T> consumer, final int maxSize, final boolean onPooledThread) {
     myConsumer = consumer;
     myQueue = new ArrayDeque<T>();
     myMaxSize = maxSize;
     myLock = new Object();
-    myConsumerThread = new Thread(new Runnable() {
-      @Override
-      public void run() {
-        synchronized (myLock) {
-          while (myIsAlive) {
-            if (! myQueue.isEmpty()) {
-              myConsumer.consume(myQueue.removeFirst());
-            } else {
-              try {
-                myLock.wait(10);
-              }
-              catch (InterruptedException e) {
-                //
-              }
+
+    if (onPooledThread) {
+      myConsumerThread = new PooledConsumerRunnable();
+      ApplicationManager.getApplication().executeOnPooledThread(myConsumerThread);
+    } else {
+      myConsumerThread = new ConsumerRunnable();
+    }
+  }
+
+  private class PooledConsumerRunnable extends ConsumerRunnable {
+    private final Semaphore mySemaphore;
+
+    private PooledConsumerRunnable() {
+      mySemaphore = new Semaphore();
+      mySemaphore.down();
+    }
+
+    public void start() {
+      mySemaphore.up();
+    }
+
+    @Override
+    protected void waitForStart() {
+      mySemaphore.waitFor();
+    }
+  }
+
+  private class ConsumerRunnable implements Runnable {
+    private Thread myThread;
+    
+    public void start() {
+      myThread.start();
+    }
+
+    public void setThread(Thread thread) {
+      myThread = thread;
+    }
+
+    @Override
+    public void run() {
+      waitForStart();
+      synchronized (myLock) {
+        while (myIsAlive) {
+          if (! myQueue.isEmpty()) {
+            myConsumer.consume(myQueue.removeFirst());
+          } else {
+            try {
+              myLock.wait(10);
+            }
+            catch (InterruptedException e) {
+              //
             }
           }
         }
       }
-    });
+    }
+
+    protected void waitForStart() {
+    }
   }
 
   public void produce(final T t) {
index 79e0779f14d86859f9399602bf8b7447e746f40d..f535b9744ea0bc74f6f7706337b679a258908885 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.FilePath;
+import com.intellij.openapi.vcs.ProcessEventListener;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.LocalFileSystem;
 import com.intellij.openapi.vfs.VfsUtil;
@@ -83,7 +84,7 @@ public abstract class GitHandler {
   @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
   private boolean myNoSSHFlag = false;
 
-  private final EventDispatcher<GitHandlerListener> myListeners = EventDispatcher.create(GitHandlerListener.class);
+  private final EventDispatcher<ProcessEventListener> myListeners = EventDispatcher.create(ProcessEventListener.class);
   @SuppressWarnings({"FieldAccessedSynchronizedAndUnsynchronized"})
   private boolean mySilent; // if true, the command execution is not logged in version control view
 
@@ -135,7 +136,7 @@ public abstract class GitHandler {
   /**
    * @return multicaster for listeners
    */
-  protected GitHandlerListener listeners() {
+  protected ProcessEventListener listeners() {
     return myListeners.getMulticaster();
   }
 
@@ -237,7 +238,7 @@ public abstract class GitHandler {
    *
    * @param listener a listener
    */
-  protected void addListener(GitHandlerListener listener) {
+  protected void addListener(ProcessEventListener listener) {
     myListeners.addListener(listener);
   }
 
index a90d7045915d6d4878c1611489c7c03e5bb3bed9..df32a852af9a316fa16a4ab54490420be8f640ad 100644 (file)
  */
 package git4idea.commands;
 
-import java.util.EventListener;
+import com.intellij.openapi.vcs.ProcessEventListener;
 
 /**
  * Listener for event common for all handlers
  */
-public interface GitHandlerListener extends EventListener {
-  /**
-   * This method is invoked when git process is terminated
-   *
-   * @param exitCode a exit code
-   */
-  void processTerminated(int exitCode);
-
-  /**
-   * This method is invoked if starting git process failed with exception
-   *
-   * @param exception an exception
-   */
-  void startFailed(Throwable exception);
-
+public interface GitHandlerListener extends ProcessEventListener {
 }
index b341036552454dcb0b407ccbc2c3dfe9c9ca1ed6..ecfa17baabec43c3eb46a6571756ebf5dfb52afa 100644 (file)
@@ -18,6 +18,7 @@ package git4idea.commands;
 import com.intellij.execution.process.ProcessOutputTypes;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vcs.LineHandlerHelper;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.EventDispatcher;
 import org.jetbrains.annotations.NotNull;
@@ -95,7 +96,7 @@ public class GitLineHandler extends GitTextHandler {
    * {@inheritDoc}
    */
   protected void onTextAvailable(final String text, final Key outputType) {
-    Iterator<String> lines = splitText(text).iterator();
+    Iterator<String> lines = LineHandlerHelper.splitText(text).iterator();
     if (ProcessOutputTypes.STDOUT == outputType) {
       notifyLines(outputType, lines, myStdoutLine);
     }
@@ -147,7 +148,7 @@ public class GitLineHandler extends GitTextHandler {
    * @param outputType output type
    */
   private void notifyLine(final String line, final Key outputType) {
-    String trimmed = trimLineSeparator(line);
+    String trimmed = LineHandlerHelper.trimLineSeparator(line);
     // if line ends with return, then it is a progress line, ignore it
     if (myVcs != null && !"\r".equals(line.substring(trimmed.length()))) {
       if (outputType == ProcessOutputTypes.STDOUT && !isStdoutSuppressed()) {
@@ -159,77 +160,4 @@ public class GitLineHandler extends GitTextHandler {
     }
     myLineListeners.getMulticaster().onLineAvailable(trimmed, outputType);
   }
-
-  /**
-   * Trim line separator from new line if it presents
-   *
-   * @param line a line to process
-   * @return a trimmed line
-   */
-  private static String trimLineSeparator(String line) {
-    int n = line.length();
-    if (n == 0) {
-      return line;
-    }
-    char ch = line.charAt(n - 1);
-    if (ch == '\n' || ch == '\r') {
-      n--;
-    }
-    else {
-      return line;
-    }
-    if (n > 0) {
-      char ch2 = line.charAt(n - 1);
-      if ((ch2 == '\n' || ch2 == '\r') && ch2 != ch) {
-        n--;
-      }
-    }
-    return line.substring(0, n);
-
-  }
-
-  /**
-   * Split text into lines. New line characters are treated as separators. So if the text starts
-   * with newline, empty string will be the first element, if the text ends with new line, the
-   * empty string will be the last element. The returned lines will be substrings of
-   * the text argument. The new line characters are included into the line text.
-   *
-   * @param text a text to split
-   * @return a list of elements (note that there are always at least one element)
-   */
-  private static List<String> splitText(String text) {
-    int startLine = 0;
-    int i = 0;
-    int n = text.length();
-    ArrayList<String> rc = new ArrayList<String>();
-    while (i < n) {
-      switch (text.charAt(i)) {
-        case '\n':
-          i++;
-          if (i < n && text.charAt(i) == '\r') {
-            i++;
-          }
-          rc.add(text.substring(startLine, i));
-          startLine = i;
-          break;
-        case '\r':
-          i++;
-          if (i < n && text.charAt(i) == '\n') {
-            i++;
-          }
-          rc.add(text.substring(startLine, i));
-          startLine = i;
-          break;
-        default:
-          i++;
-      }
-    }
-    if (startLine == text.length()) {
-      // still add empty line or previous line wouldn't be treated as completed
-      rc.add("");
-    } else {
-      rc.add(text.substring(startLine, i));
-    }
-    return rc;
-  }
 }
index 365d3e59266d056e0156530df6e3d2e4be14638a..8ac8d0b6f7abdb71440947a4590b374fd895bc53 100644 (file)
 package git4idea.commands;
 
 import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vcs.LineProcessEventListener;
 
 /**
  * Listener for line events
  */
-public interface GitLineHandlerListener extends GitHandlerListener {
+public interface GitLineHandlerListener extends LineProcessEventListener {
   /**
    * This method is invoked when line (as separated by \n or \r) becomes available.
    *
index 4457bb168f3a6935eac479641e8602b9b219e9c1..9987f00ec6038422a17e6c0c9407b3fb8a734cd8 100644 (file)
@@ -45,6 +45,7 @@ public class SvnApplicationSettings17 implements PersistentStateComponent<SvnApp
   public static class ConfigurationBean {
     public List<String> myCheckoutURLs = new ArrayList<String>();
     public List<String> myTypedURLs = new ArrayList<String>();
+    public String mySvnCommandLine = "svn";
   }
 
   private ConfigurationBean myConfigurationBean;
@@ -67,6 +68,14 @@ public class SvnApplicationSettings17 implements PersistentStateComponent<SvnApp
     myConfigurationBean = object;
     getTypedList();
   }
+  
+  public void setCommandLinePath(final String path) {
+    myConfigurationBean.mySvnCommandLine = path;
+  }
+  
+  public String getCommandLinePath() {
+    return myConfigurationBean.mySvnCommandLine;
+  }
 
   private LimitedStringsList getTypedList() {
     if (myLimitedStringsList == null) {
index 5ca930041f857bf0f76b94c00d0cca7d4c00902e..bb7a896d1f51a1b3c1ed5799ced35276d1f110f7 100644 (file)
@@ -3,7 +3,7 @@
   <grid id="27dc6" binding="myComponent" layout-manager="GridLayoutManager" row-count="23" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
     <margin top="0" left="0" bottom="0" right="0"/>
     <constraints>
-      <xy x="20" y="20" width="959" height="771"/>
+      <xy x="20" y="20" width="959" height="793"/>
     </constraints>
     <properties/>
     <border type="none"/>
         </constraints>
         <properties/>
       </component>
-      <grid id="dcfcc" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+      <grid id="dcfcc" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
         <margin top="0" left="0" bottom="0" right="0"/>
         <constraints>
           <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
               <text value="You need to have JavaHL 1.7.2"/>
             </properties>
           </component>
+          <component id="9c218" class="javax.swing.JRadioButton" binding="myWithCommandLineClient" default-binding="true">
+            <constraints>
+              <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <text value="with command line client"/>
+            </properties>
+          </component>
         </children>
       </grid>
     </children>
index de7ec0bcbaa1b2d27226899389443312a79b2efc..f7e988ac7e6cd99817ec97011ff29748d9e7c67b 100644 (file)
@@ -72,6 +72,7 @@ public class SvnConfigurable implements Configurable {
   private JRadioButton myJavaHLAcceleration;
   private JRadioButton myNoAcceleration;
   private JLabel myJavaHLInfo;
+  private JRadioButton myWithCommandLineClient;
 
   @NonNls private static final String HELP_ID = "project.propSubversion";
 
@@ -148,6 +149,7 @@ public class SvnConfigurable implements Configurable {
     ButtonGroup bg = new ButtonGroup();
     bg.add(myNoAcceleration);
     bg.add(myJavaHLAcceleration);
+    bg.add(myWithCommandLineClient);
   }
 
   private static FileChooserDescriptor createFileDescriptor() {
@@ -225,6 +227,7 @@ public class SvnConfigurable implements Configurable {
   private SvnConfiguration17.UseAcceleration acceleration() {
     if (myNoAcceleration.isSelected()) return SvnConfiguration17.UseAcceleration.nothing;
     if (myJavaHLAcceleration.isSelected()) return SvnConfiguration17.UseAcceleration.javaHL;
+    if (myWithCommandLineClient.isSelected()) return SvnConfiguration17.UseAcceleration.commandLine;
     return SvnConfiguration17.UseAcceleration.nothing;
   }
 
@@ -246,6 +249,9 @@ public class SvnConfigurable implements Configurable {
     if (SvnConfiguration17.UseAcceleration.javaHL.equals(acceleration)) {
       myJavaHLAcceleration.setSelected(true);
       return;
+    } else if (SvnConfiguration17.UseAcceleration.commandLine.equals(acceleration)) {
+      myWithCommandLineClient.setSelected(true);
+      return;
     }
     myNoAcceleration.setSelected(true);
   }
index c4fb891de102ce351b82b31526bef148060b0b70..6039db56d5cd4de7626f3ff0b162d9e5d16ac066 100644 (file)
@@ -19,6 +19,7 @@ import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.vcsUtil.VcsUtil;
+import org.jetbrains.idea.svn17.commandLine.SvnCommandLineStatusClient;
 import org.jetbrains.idea.svn17.portable.JavaHLSvnStatusClient;
 import org.jetbrains.idea.svn17.portable.SvnStatusClientI;
 import org.jetbrains.idea.svn17.portable.SvnkitSvnStatusClient;
@@ -135,6 +136,8 @@ public class SvnRecursiveStatusWalker {
       if (CheckJavaHL.isPresent() && SvnConfiguration17.UseAcceleration.javaHL.equals(myConfiguration17.myUseAcceleration) &&
           Svn17Detector.is17(myProject, file)) {
         return new JavaHLSvnStatusClient(myProject);
+      } else if (SvnConfiguration17.UseAcceleration.commandLine.equals(myConfiguration17.myUseAcceleration)) {
+        return new SvnCommandLineStatusClient(myProject);
       }
       return mySvnClient;
     }
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommand.java
new file mode 100644 (file)
index 0000000..444946a
--- /dev/null
@@ -0,0 +1,177 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.ProcessEventListener;
+import com.intellij.util.EventDispatcher;
+import com.intellij.util.Processor;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.idea.svn17.SvnApplicationSettings17;
+
+import java.io.File;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 12:58 PM
+ */
+public abstract class SvnCommand {
+  private static final Logger LOG = Logger.getInstance(SvnCommand.class.getName());
+
+  protected final Project myProject;
+  protected final GeneralCommandLine myCommandLine;
+  private final File myWorkingDirectory;
+  protected Process myProcess;
+  private final Object myLock;
+  private Integer myExitCode; // exit code or null if exit code is not yet available
+
+  private final EventDispatcher<ProcessEventListener> myListeners = EventDispatcher.create(ProcessEventListener.class);
+
+  private Processor<OutputStream> myInputProcessor; // The processor for stdin
+
+  // todo check version
+  /*c:\Program Files (x86)\CollabNet\Subversion Client17>svn --version --quiet
+  1.7.2*/
+
+  public SvnCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) {
+    myLock = new Object();
+    myProject = project;
+    myCommandLine = new GeneralCommandLine();
+    myWorkingDirectory = workingDirectory;
+    final SvnApplicationSettings17 applicationSettings17 = SvnApplicationSettings17.getInstance();
+    myCommandLine.setExePath(applicationSettings17.getCommandLinePath());
+    myCommandLine.setWorkDirectory(workingDirectory);
+    myCommandLine.addParameter(commandName.getName());
+  }
+
+  public void start() {
+    synchronized (myLock) {
+      checkNotStarted();
+
+      try {
+        myProcess = startProcess();
+        startHandlingStreams();
+      } catch (Throwable t) {
+        // todo check executable
+        myListeners.getMulticaster().startFailed(t);
+      }
+    }
+  }
+
+  /**
+   * Wait for process termination
+   */
+  public void waitFor() {
+    checkStarted();
+    try {
+      if (myInputProcessor != null && myProcess != null) {
+        myInputProcessor.process(myProcess.getOutputStream());
+      }
+    }
+    finally {
+      waitForProcess();
+    }
+  }
+
+  public void cancel() {
+    synchronized (myLock) {
+      checkStarted();
+      destroyProcess();
+    }
+  }
+  
+  protected void setExitCode(final int code) {
+    synchronized (myLock) {
+      myExitCode = code;
+    }
+  }
+
+  public void addListener(final ProcessEventListener listener) {
+    synchronized (myLock) {
+      myListeners.addListener(listener);
+    }
+  }
+
+  protected ProcessEventListener listeners() {
+    synchronized (myLock) {
+      return myListeners.getMulticaster();
+    }
+  }
+
+  public void addParameters(@NonNls @NotNull String... parameters) {
+    synchronized (myLock) {
+      checkNotStarted();
+      myCommandLine.addParameters(parameters);
+    }
+  }
+
+  public void addParameters(List<String> parameters) {
+    synchronized (myLock) {
+      checkNotStarted();
+      myCommandLine.addParameters(parameters);
+    }
+  }
+
+  public abstract void destroyProcess();
+  protected abstract void waitForProcess();
+
+  protected abstract Process startProcess() throws ExecutionException;
+
+  /**
+   * Start handling process output streams for the handler.
+   */
+  protected abstract void startHandlingStreams();
+
+  /**
+   * check that process is not started yet
+   *
+   * @throws IllegalStateException if process has been already started
+   */
+  private void checkNotStarted() {
+    if (isStarted()) {
+      throw new IllegalStateException("The process has been already started");
+    }
+  }
+
+  /**
+   * check that process is started
+   *
+   * @throws IllegalStateException if process has not been started
+   */
+  protected void checkStarted() {
+    if (! isStarted()) {
+      throw new IllegalStateException("The process is not started yet");
+    }
+  }
+
+  /**
+   * @return true if process is started
+   */
+  public boolean isStarted() {
+    synchronized (myLock) {
+      return myProcess != null;
+    }
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandLineInfoClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandLineInfoClient.java
new file mode 100644 (file)
index 0000000..48ab736
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.util.Consumer;
+import org.jetbrains.idea.svn17.SvnVcs17;
+import org.jetbrains.idea.svn17.portable.SvnExceptionWrapper;
+import org.jetbrains.idea.svn17.portable.SvnkitSvnWcClient;
+import org.tmatesoft.svn.core.*;
+import org.tmatesoft.svn.core.wc.ISVNInfoHandler;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNWCClient;
+import org.xml.sax.SAXException;
+import sun.reflect.generics.reflectiveObjects.NotImplementedException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringBufferInputStream;
+import java.util.Collection;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/27/12
+ * Time: 12:59 PM
+ */
+public class SvnCommandLineInfoClient extends SvnkitSvnWcClient {
+  private final Project myProject;
+
+  public SvnCommandLineInfoClient(final Project project) {
+    super(SvnVcs17.getInstance(project).createWCClient());
+    myProject = project;
+  }
+
+  @Override
+  public void doInfo(File path, SVNRevision revision, boolean recursive, ISVNInfoHandler handler) throws SVNException {
+    doInfo(path, SVNRevision.UNDEFINED, revision, recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, null, handler);
+  }
+
+  @Override
+  public void doInfo(File path, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNInfoHandler handler)
+    throws SVNException {
+    doInfo(path, pegRevision, revision, recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, null, handler);
+  }
+
+  @Override
+  public void doInfo(File path,
+                     SVNRevision pegRevision,
+                     SVNRevision revision,
+                     SVNDepth depth,
+                     Collection changeLists,
+                     final ISVNInfoHandler handler) throws SVNException {
+    final File base = path.isDirectory() ? path : path.getParentFile();
+    final SvnSimpleCommand command = new SvnSimpleCommand(myProject, base, SvnCommandName.info);
+
+    if (depth != null) {
+      command.addParameters("--depth", depth.getName());
+    }
+    if (revision != null && ! SVNRevision.UNDEFINED.equals(revision) && ! SVNRevision.WORKING.equals(revision)) {
+      command.addParameters("-r", revision.toString());
+    }
+    command.addParameters("--xml");
+    SvnCommandLineStatusClient.changelistsToCommand(changeLists, command);
+    if (pegRevision != null && ! SVNRevision.UNDEFINED.equals(pegRevision) && ! SVNRevision.WORKING.equals(pegRevision)) {
+      command.addParameters(path.getPath() + "@" + pegRevision.toString());
+    } else {
+      command.addParameters(path.getPath());
+    }
+
+    final SvnInfoHandler[] infoHandler = new SvnInfoHandler[1];
+    infoHandler[0] = new SvnInfoHandler(base, new Consumer<SVNInfo>() {
+      @Override
+      public void consume(SVNInfo info) {
+        try {
+          handler.handleInfo(info);
+        }
+        catch (SVNException e) {
+          throw new SvnExceptionWrapper(e);
+        }
+      }
+    });
+
+    try {
+      final String result = command.run();
+      // todo not synchronized wrapper stream!
+      SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+      parser.parse(new StringBufferInputStream(result), infoHandler[0]);
+
+    }
+    catch (SvnExceptionWrapper e) {
+      throw (SVNException) e.getCause();
+    } catch (IOException e) {
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+    catch (ParserConfigurationException e) {
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+    catch (SAXException e) {
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+    catch (VcsException e) {
+      final String text = e.getMessage();
+      if (!StringUtil.isEmptyOrSpaces(text) && text.contains("W155010")) {
+        // just null
+        return;
+      }
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+  }
+
+  @Override
+  public void doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision, boolean recursive, ISVNInfoHandler handler)
+    throws SVNException {
+    doInfo(url, pegRevision, revision, recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, handler);
+  }
+
+  @Override
+  public void doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision, SVNDepth depth, ISVNInfoHandler handler)
+    throws SVNException {
+    throw new NotImplementedException();
+  }
+
+  @Override
+  public SVNInfo doInfo(File path, SVNRevision revision) throws SVNException {
+    final SVNInfo[] infoArr = new SVNInfo[1];
+    doInfo(path, SVNRevision.UNDEFINED, revision, SVNDepth.EMPTY, null, new ISVNInfoHandler() {
+      @Override
+      public void handleInfo(SVNInfo info) throws SVNException {
+        infoArr[0] = info;
+      }
+    });
+    return infoArr[0];
+  }
+
+  @Override
+  public SVNInfo doInfo(SVNURL url, SVNRevision pegRevision, SVNRevision revision) throws SVNException {
+    final SVNInfo[] infoArr = new SVNInfo[1];
+    doInfo(url, pegRevision, revision, SVNDepth.EMPTY, new ISVNInfoHandler() {
+      @Override
+      public void handleInfo(SVNInfo info) throws SVNException {
+        infoArr[0] = info;
+      }
+    });
+    return infoArr[0];
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandLineStatusClient.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandLineStatusClient.java
new file mode 100644 (file)
index 0000000..79bfd9a
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Getter;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.util.containers.Convertor;
+import org.jetbrains.idea.svn17.portable.PortableStatus;
+import org.jetbrains.idea.svn17.portable.SvnExceptionWrapper;
+import org.jetbrains.idea.svn17.portable.SvnStatusClientI;
+import org.tmatesoft.svn.core.*;
+import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
+import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
+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.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.*;
+import java.util.Collection;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 5:21 PM
+ */
+public class SvnCommandLineStatusClient implements SvnStatusClientI {
+  private final Project myProject;
+  private final SvnCommandLineInfoClient myInfoClient;
+
+  public SvnCommandLineStatusClient(Project project) {
+    myProject = project;
+    myInfoClient = new SvnCommandLineInfoClient(project);
+  }
+
+  @Override
+  public long doStatus(File path, boolean recursive, boolean remote, boolean reportAll, boolean includeIgnored, ISVNStatusHandler handler)
+    throws SVNException {
+    return doStatus(path, recursive, remote, reportAll, includeIgnored, false, handler);
+  }
+
+  @Override
+  public long doStatus(File path,
+                       boolean recursive,
+                       boolean remote,
+                       boolean reportAll,
+                       boolean includeIgnored,
+                       boolean collectParentExternals,
+                       ISVNStatusHandler handler) throws SVNException {
+    return doStatus(path, SVNRevision.UNDEFINED, recursive, remote, reportAll, includeIgnored, collectParentExternals, handler);
+  }
+
+  @Override
+  public long doStatus(File path,
+                       SVNRevision revision,
+                       boolean recursive,
+                       boolean remote,
+                       boolean reportAll,
+                       boolean includeIgnored,
+                       boolean collectParentExternals,
+                       ISVNStatusHandler handler) throws SVNException {
+    return doStatus(path, revision, recursive ? SVNDepth.INFINITY : SVNDepth.EMPTY, remote, reportAll, includeIgnored,
+                    collectParentExternals, handler, null);
+  }
+
+  @Override
+  public long doStatus(final File path,
+                       final SVNRevision revision,
+                       final SVNDepth depth,
+                       boolean remote,
+                       boolean reportAll,
+                       boolean includeIgnored,
+                       boolean collectParentExternals,
+                       final ISVNStatusHandler handler,
+                       final Collection changeLists) throws SVNException {
+    final File base = path.isDirectory() ? path : path.getParentFile();
+
+    final SVNInfo infoBase = myInfoClient.doInfo(base, revision);
+
+    // TODO check file case
+    // TODO check file case
+    // TODO check file case
+    // TODO check file case
+    // TODO check file case
+
+
+    // todo can not understand why revision can be used here
+    final SvnSimpleCommand command = new SvnSimpleCommand(myProject, base, SvnCommandName.st);
+
+    if (depth != null) {
+      command.addParameters("--depth", depth.getName());
+    }
+    if (remote) {
+      command.addParameters("-u");
+    }
+    if (reportAll) {
+      command.addParameters("-v");
+    }
+    if (includeIgnored) {
+      command.addParameters("--no-ignore");
+    }
+    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.DataCallback() {
+      @Override
+      public void switchPath() {
+        final PortableStatus pending = svnHandl[0].getPending();
+        pending.setChangelistName(changelistName[0]);
+        try {
+          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);
+        }
+      }
+    });
+
+    try {
+      final String result = command.run();
+      // todo not synchronized wrapper stream!
+      SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+      parser.parse(new StringBufferInputStream(result), svnHandl[0]);
+
+    }
+    catch (SvnExceptionWrapper e) {
+      throw (SVNException) e.getCause();
+    } catch (IOException e) {
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+    catch (ParserConfigurationException e) {
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+    catch (SAXException e) {
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+    catch (VcsException e) {
+      throw new SVNException(SVNErrorMessage.create(SVNErrorCode.IO_ERROR), e);
+    }
+    return 0;
+  }
+
+  public static void changelistsToCommand(Collection changeLists, SvnSimpleCommand command) {
+    if (changeLists != null) {
+      for (Object o : changeLists) {
+        final String name = (String) o;
+        command.addParameters("--cl", name);
+      }
+    }
+  }
+
+  @Override
+  public SVNStatus doStatus(File path, boolean remote) throws SVNException {
+    return doStatus(path, remote, false);
+  }
+
+  @Override
+  public SVNStatus doStatus(File path, boolean remote, boolean collectParentExternals) throws SVNException {
+    final SVNStatus[] svnStatus = new SVNStatus[1];
+    doStatus(path, SVNRevision.UNDEFINED, SVNDepth.EMPTY, remote, false, false, collectParentExternals, new ISVNStatusHandler() {
+      @Override
+      public void handleStatus(SVNStatus status) throws SVNException {
+        svnStatus[0] = status;
+      }
+    }, null);
+    return svnStatus[0];
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandName.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnCommandName.java
new file mode 100644 (file)
index 0000000..154c23c
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * 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.svn17.commandLine;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 1:49 PM
+ */
+public enum SvnCommandName {
+  version("--version"),
+  info("info"),
+  st("st"),
+  up("up");
+  
+  private final String myName;
+
+  private SvnCommandName(String name) {
+    myName = name;
+  }
+
+  public String getName() {
+    return myName;
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnInfoHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnInfoHandler.java
new file mode 100644 (file)
index 0000000..dfa25da
--- /dev/null
@@ -0,0 +1,783 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.openapi.util.Getter;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.Consumer;
+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.internal.util.SVNDate;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/27/12
+ * Time: 1:00 PM
+ */
+public class SvnInfoHandler extends DefaultHandler {
+  private final File myBase;
+  private final Consumer<SVNInfo> myInfoConsumer;
+  private Map<File, SVNInfo> myResultsMap;
+  private SvnInfoStructure myPending;
+  private final Map<String, Getter<ElementHandlerBase>> myElementsMap;
+  private final List<ElementHandlerBase> myParseStack;
+  private final StringBuilder mySb;
+
+  public SvnInfoHandler(File base, final Consumer<SVNInfo> infoConsumer) {
+    myBase = base;
+    myInfoConsumer = infoConsumer;
+    myPending = new SvnInfoStructure();
+    myElementsMap = new HashMap<String, Getter<ElementHandlerBase>>();
+    fillElements();
+    myParseStack = new ArrayList<ElementHandlerBase>();
+    myParseStack.add(new Fake());
+    myResultsMap = new HashMap<File, SVNInfo>();
+    mySb = new StringBuilder();
+  }
+
+  private void switchPending() throws SAXException {
+    final SVNInfo info;
+    try {
+      info = myPending.convert();
+    }
+    catch (SVNException e) {
+      throw new SAXException(e);
+    }
+    if (myInfoConsumer != null) {
+      myInfoConsumer.consume(info);
+    }
+    myResultsMap.put(info.getFile(), info);
+    myPending = new SvnInfoStructure();
+  }
+
+  @Override
+  public void endDocument() throws SAXException {
+    assertSAX(! myParseStack.isEmpty());
+    for (int i = myParseStack.size() - 1; i >= 0; -- i) {
+      ElementHandlerBase current = myParseStack.get(i);
+      if (current instanceof Entry) {
+        switchPending();
+        break;
+      }
+    }
+    myParseStack.clear();
+  }
+
+  @Override
+  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+    assertSAX(! myParseStack.isEmpty());
+    ElementHandlerBase current = myParseStack.get(myParseStack.size() - 1);
+    if (mySb.length() > 0) {
+      current.characters(mySb.toString().trim(), myPending);
+      mySb.setLength(0);
+    }
+
+    while (true) {
+      final boolean createNewChild = current.startElement(uri, localName, qName, attributes);
+      if (createNewChild) {
+        assertSAX(myElementsMap.containsKey(qName));
+        final ElementHandlerBase newChild = myElementsMap.get(qName).get();
+        newChild.updateInfo(attributes, myPending);
+        myParseStack.add(newChild);
+        return;
+      } else {
+        // go up
+        if (current instanceof Entry) {
+          switchPending();
+        }
+        myParseStack.remove(myParseStack.size() - 1);
+        assertSAX(! myParseStack.isEmpty());
+        current = myParseStack.get(myParseStack.size() - 1);
+      }
+    }
+  }
+
+  @Override
+  public void characters(char[] ch, int start, int length) throws SAXException {
+    assertSAX(! myParseStack.isEmpty());
+    mySb.append(ch, start, length);
+  }
+
+  public Map<String, Getter<ElementHandlerBase>> getElementsMap() {
+    return myElementsMap;
+  }
+
+  private void fillElements() {
+    myElementsMap.put("copy-from-url", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new CopyFromUrl();
+      }
+    });
+    myElementsMap.put("copy-from-rev", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new CopyFromRev();
+      }
+    });
+    myElementsMap.put("changelist", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new ChangeList();
+      }
+    });
+    myElementsMap.put("author", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Author();
+      }
+    });
+    myElementsMap.put("checksum", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Checksum();
+      }
+    });
+    myElementsMap.put("commit", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Commit();
+      }
+    });
+    myElementsMap.put("conflict", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Conflict();
+      }
+    });
+    myElementsMap.put("cur-base-file", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new CurBase();
+      }
+    });
+    myElementsMap.put("date", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Date();
+      }
+    });
+    myElementsMap.put("depth", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Depth();
+      }
+    });
+    myElementsMap.put("entry", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Entry(myBase);
+      }
+    });
+    myElementsMap.put("info", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Info();
+      }
+    });
+    myElementsMap.put("prev-base-file", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new PrevBase();
+      }
+    });
+    myElementsMap.put("prev-wc-file", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new PrevWc();
+      }
+    });
+    myElementsMap.put("prop-file", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new PropFile();
+      }
+    });
+    myElementsMap.put("repository", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Repository();
+      }
+    });
+    myElementsMap.put("root", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Root();
+      }
+    });
+    myElementsMap.put("schedule", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Schedule();
+      }
+    });
+    myElementsMap.put("text-updated", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new TextUpdated();
+      }
+    });
+    myElementsMap.put("tree-conflict", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new TreeConflict();
+      }
+    });
+    myElementsMap.put("url", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Url();
+      }
+    });
+    myElementsMap.put("uuid", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Uuid();
+      }
+    });
+    myElementsMap.put("version", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Version();
+      }
+    });
+    myElementsMap.put("wc-info", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new WcInfo();
+      }
+    });
+    myElementsMap.put("wcroot-abspath", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new WcRoot();
+      }
+    });
+  }
+
+  public Map<File, SVNInfo> getResultsMap() {
+    return myResultsMap;
+  }
+
+  private static void assertSAX(final boolean shouldBeTrue) throws SAXException {
+    if (! shouldBeTrue) {
+      throw new SAXException("can not parse output");
+    }
+  }
+
+  private static class Version extends ElementHandlerBase {
+    private Version() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+      final String side = attributes.getValue("side");
+      if ("source-left".equals(side)) {
+        final SvnInfoStructure.ConflictVersion conflictVersion = new SvnInfoStructure.ConflictVersion();
+        structure.myTreeConflict.mySourceLeft = conflictVersion;
+        setConflictFields(attributes, conflictVersion);
+      } else if ("source-right".equals(side)) {
+        final SvnInfoStructure.ConflictVersion conflictVersion = new SvnInfoStructure.ConflictVersion();
+        structure.myTreeConflict.mySourceRight = conflictVersion;
+        setConflictFields(attributes, conflictVersion);
+      }
+    }
+
+    private void setConflictFields(Attributes attributes, SvnInfoStructure.ConflictVersion conflictVersion) {
+      conflictVersion.myKind = attributes.getValue("kind");
+      conflictVersion.myPathInRepo = attributes.getValue("path-in-repos");
+      conflictVersion.myRepoUrl = attributes.getValue("repos-url");
+      conflictVersion.myRevision = attributes.getValue("revision");
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+    }
+  }
+
+  private static class TreeConflict extends ElementHandlerBase {
+    private TreeConflict() {
+      super(new String[]{}, new String[]{"version"});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+      final SvnInfoStructure.TreeConflictDescription d = new SvnInfoStructure.TreeConflictDescription();
+      structure.myTreeConflict = d;
+      final String operation = attributes.getValue("operation");
+      if (! StringUtil.isEmptyOrSpaces(operation)) {
+        d.myOperation = operation;
+      }
+      final String kind = attributes.getValue("kind");
+      if (! StringUtil.isEmptyOrSpaces(kind)) {
+        d.myKind = kind;
+      }
+      final String reason = attributes.getValue("reason");
+      if (! StringUtil.isEmptyOrSpaces(reason)) {
+        d.myReason = reason;
+      }
+      final String victim = attributes.getValue("victim");
+      if (! StringUtil.isEmptyOrSpaces(victim)) {
+        d.myVictim = victim;
+      }
+      final String action = attributes.getValue("action");
+      if (! StringUtil.isEmptyOrSpaces(action)) {
+        d.myAction = action;
+      }
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+    }
+  }
+  
+  private static class PropFile extends ElementHandlerBase {
+    private PropFile() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      // todo check whether base should be added
+      structure.myPropRejectFile = s;
+    }
+  }
+  
+  private static class CurBase extends ElementHandlerBase {
+    private CurBase() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.myConflictWorking = s;
+    }
+  }
+
+  private static class PrevWc extends ElementHandlerBase {
+    private PrevWc() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.myConflictNew = s;
+    }
+  }
+
+  private static class PrevBase extends ElementHandlerBase {
+    private PrevBase() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      // todo path? or plus base
+      structure.myConflictOld = s;
+    }
+  }
+
+  private static class Conflict extends ElementHandlerBase {
+    private Conflict() {
+      super(new String[]{"prev-base-file","prev-wc-file","cur-base-file","prop-file"}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+    }
+  }
+
+  private static class Date extends ElementHandlerBase {
+    private Date() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      final SVNDate date = SVNDate.parseDate(s);
+      structure.myCommittedDate = date;
+    }
+  }
+
+  private static class Author extends ElementHandlerBase {
+    private Author() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.myAuthor = s;
+    }
+  }
+
+  private static class Commit extends ElementHandlerBase {
+    private Commit() {
+      super(new String[]{"author","date"}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+      final String revision = attributes.getValue("revision");
+      try {
+        final long number = Long.parseLong(revision);
+        structure.myCommittedRevision = number;
+      } catch (NumberFormatException e) {
+        throw new SAXException(e);
+      }
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+    }
+  }
+
+  private static class Checksum extends ElementHandlerBase {
+    private Checksum() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.myChecksum = s;
+    }
+  }
+
+  private static class TextUpdated extends ElementHandlerBase {
+    private TextUpdated() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      final SVNDate date = SVNDate.parseDate(s);
+      structure.myTextTime = date;
+    }
+  }
+
+  private static class Depth extends ElementHandlerBase {
+    private Depth() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.myDepth = SVNDepth.fromString(s);
+    }
+  }
+
+  private static class Schedule extends ElementHandlerBase {
+    private Schedule() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.mySchedule = s;
+    }
+  }
+
+  private static class WcRoot extends ElementHandlerBase {
+    private WcRoot() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      // there is no such thing???
+    }
+  }
+  
+  private static class ChangeList extends ElementHandlerBase {
+    private ChangeList() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.myChangelistName = s;
+    }
+  }
+
+  private static class CopyFromUrl extends ElementHandlerBase {
+    private CopyFromUrl() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      try {
+        structure.myCopyFromURL = SVNURL.parseURIEncoded(s);
+      }
+      catch (SVNException e) {
+        throw new SAXException(e);
+      }
+    }
+  }
+
+  private static class CopyFromRev extends ElementHandlerBase {
+    private CopyFromRev() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      try {
+        final long number = Long.parseLong(s);
+        structure.myCopyFromRevision = number;
+      } catch (NumberFormatException e) {
+        throw new SAXException(e);
+      }
+    }
+  }
+
+  private static class WcInfo extends ElementHandlerBase {
+    private WcInfo() {
+      super(new String[]{"wcroot-abspath", "schedule", "depth", "text-updated", "checksum", "changelist", "copy-from-url",
+      "copy-from-rev"}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+    }
+  }
+  
+  private static class Uuid extends ElementHandlerBase {
+    private Uuid() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      structure.myUuid = s;
+    }
+  }
+
+  private static class Root extends ElementHandlerBase {
+    private Root() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      try {
+        structure.myRootURL = SVNURL.parseURIEncoded(s);
+      }
+      catch (SVNException e) {
+        throw new SAXException(e);
+      }
+    }
+  }
+
+  private static class Repository extends ElementHandlerBase {
+    private Repository() {
+      super(new String[]{"root", "uuid"}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+    }
+  }
+
+  private static class Url extends ElementHandlerBase {
+    private Url() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) throws SAXException {
+      try {
+        structure.myUrl = SVNURL.parseURIEncoded(s);
+      }
+      catch (SVNException e) {
+        throw new SAXException(e);
+      }
+    }
+  }
+
+  private static class Entry extends ElementHandlerBase {
+    private final File myBase;
+
+    private Entry(final File base) {
+      super(new String[]{"url","repository","wc-info","commit","conflict","tree-conflict"}, new String[]{});
+      myBase = base;
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException {
+      final String kind = attributes.getValue("kind");
+      assertSAX(! StringUtil.isEmptyOrSpaces(kind));
+      structure.myKind = SVNNodeKind.parseKind(kind);
+      
+      final String path = attributes.getValue("path");
+      assertSAX(! StringUtil.isEmptyOrSpaces(path));
+      structure.myFile = new File(myBase, path);
+
+      final String revision = attributes.getValue("revision");
+      assertSAX(! StringUtil.isEmptyOrSpaces(revision));
+      try {
+        final long number = Long.parseLong(revision);
+        structure.myRevision = number;
+      } catch (NumberFormatException e) {
+        structure.myRevision = -1;
+        //throw new SAXException(e);
+      }
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) {
+    }
+  }
+
+  private static class Info extends ElementHandlerBase {
+    private Info() {
+      super(new String[]{}, new String[]{"entry"});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) {
+    }
+  }
+
+  private static class Fake extends ElementHandlerBase {
+    private Fake() {
+      super(new String[]{"info"}, new String[]{});
+    }
+
+    @Override
+    protected void updateInfo(Attributes attributes, SvnInfoStructure structure) {
+    }
+
+    @Override
+    public void characters(String s, SvnInfoStructure structure) {
+    }
+  }
+
+  private abstract static class ElementHandlerBase {
+    private final Set<String> myAwaitedChildren;
+    private final Set<String> myAwaitedChildrenMultiple;
+
+    ElementHandlerBase(String[] awaitedChildren, String[] awaitedChildrenMultiple) {
+      myAwaitedChildren = new HashSet<String>(Arrays.asList(awaitedChildren));
+      myAwaitedChildrenMultiple = new HashSet<String>(Arrays.asList(awaitedChildrenMultiple));
+    }
+
+    protected abstract void updateInfo(Attributes attributes, SvnInfoStructure structure) throws SAXException;
+
+    public boolean startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+      if (myAwaitedChildrenMultiple.contains(qName)) {
+        return true;
+      }
+      return myAwaitedChildren.remove(qName);
+    }
+
+    public abstract void characters(String s, SvnInfoStructure structure) throws SAXException;
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnInfoStructure.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnInfoStructure.java
new file mode 100644 (file)
index 0000000..9300d4b
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * 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.svn17.commandLine;
+
+import org.apache.subversion.javahl.ConflictDescriptor;
+import org.jetbrains.idea.svn17.portable.ConflictActionConvertor;
+import org.jetbrains.idea.svn17.portable.ConflictReasonConvertor;
+import org.jetbrains.idea.svn17.portable.IdeaSVNInfo;
+import org.jetbrains.idea.svn17.portable.OperationConvertor;
+import org.tmatesoft.svn.core.*;
+import org.tmatesoft.svn.core.internal.wc.SVNConflictVersion;
+import org.tmatesoft.svn.core.wc.*;
+import org.xml.sax.SAXException;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/27/12
+ * Time: 2:11 PM
+ */
+public class SvnInfoStructure {
+  public File myFile;
+  public SVNURL myUrl;
+  public SVNURL myRootURL;
+  public long myRevision;
+  public SVNNodeKind myKind;
+  public String myUuid;
+  public long myCommittedRevision;
+  public Date myCommittedDate;
+  public String myAuthor;
+  public String mySchedule;
+  public SVNURL myCopyFromURL;
+  public long myCopyFromRevision;
+  public Date myTextTime;
+  public String myPropTime;
+  public String myChecksum;
+  public String myConflictOld;
+  public String myConflictNew;
+  public String myConflictWorking;
+  public String myPropRejectFile;
+  public SVNLock myLock;
+  public SVNDepth myDepth;
+  public String myChangelistName;
+  public long myWcSize;
+  public Date myCorrectCommittedDate;
+  public Date myCorrectTextDate;
+
+  public TreeConflictDescription myTreeConflict;
+
+  public SVNInfo convert() throws SAXException, SVNException {
+    return new IdeaSVNInfo(myFile, myUrl, myRootURL, myRevision, myKind, myUuid, myCommittedRevision, myCommittedDate, myAuthor, mySchedule,
+                           myCopyFromURL, myCopyFromRevision, myTextTime, myPropTime, myChecksum, myConflictOld, myConflictNew, myConflictWorking,
+                           myPropRejectFile, myLock, myDepth, myChangelistName, myWcSize, createTreeConflict());
+  }
+
+  private SVNTreeConflictDescription createTreeConflict() throws SAXException, SVNException {
+    if (myTreeConflict == null) {
+      return null;
+    }
+    else {
+      final SVNConflictAction action = ConflictActionConvertor.create(ConflictDescriptor.Action.valueOf(myTreeConflict.myAction));
+      final SVNConflictReason reason = parseConflictReason(myTreeConflict.myReason);
+      //final SVNConflictReason reason = ConflictReasonConvertor.convert(ConflictDescriptor.Reason.valueOf(myTreeConflict.myReason));
+      final SVNOperation operation = OperationConvertor.convert(ConflictDescriptor.Operation.valueOf(myTreeConflict.myOperation));
+      return new SVNTreeConflictDescription(myFile, myKind, action, reason, operation,
+                                            createVersion(myTreeConflict.mySourceLeft),
+                                            createVersion(myTreeConflict.mySourceRight));
+    }
+  }
+
+  private SVNConflictReason parseConflictReason(String reason) throws SAXException {
+    if (ConflictDescriptor.Reason.edited.equals(reason)) {
+      return SVNConflictReason.EDITED;
+    } else if (ConflictDescriptor.Reason.obstructed.equals(reason)) {
+      return SVNConflictReason.OBSTRUCTED;
+    } else if (ConflictDescriptor.Reason.deleted.equals(reason)) {
+      return SVNConflictReason.DELETED;
+    } else if (ConflictDescriptor.Reason.missing.equals(reason)) {
+      return SVNConflictReason.MISSING;
+    } else if (ConflictDescriptor.Reason.unversioned.equals(reason)) {
+      return SVNConflictReason.UNVERSIONED;
+    } else if (ConflictDescriptor.Reason.added.equals(reason)) {
+      return SVNConflictReason.ADDED;
+    }
+    if ("edit".equals(reason)) {
+      return SVNConflictReason.EDITED;
+    } else if (reason.contains("obstruct")) {
+      return SVNConflictReason.OBSTRUCTED;
+    } else if ("delete".equals(reason)) {
+      return SVNConflictReason.DELETED;
+    } else if (reason.contains("miss")) {
+      return SVNConflictReason.MISSING;
+    } else if (reason.contains("unversion")) {
+      return SVNConflictReason.UNVERSIONED;
+    } else if (reason.contains("add")) {
+      return SVNConflictReason.ADDED;
+    }
+    throw new SAXException("Can not parse conflict reason: " + reason);
+  }
+
+  private SVNConflictVersion createVersion(final ConflictVersion version) throws SVNException, SAXException {
+    return version == null ? null : new SVNConflictVersion(SVNURL.parseURIEncoded(version.myRepoUrl), version.myPathInRepo,
+                                                           parseRevision(version.myRevision), SVNNodeKind.parseKind(version.myKind));
+  }
+  
+  private long parseRevision(final String revision) throws SAXException {
+    try {
+      return Long.parseLong(revision);
+    } catch (NumberFormatException e) {
+      throw new SAXException(e);
+    }
+  }
+
+  public static class TreeConflictDescription {
+    public String myOperation;
+    public String myKind;
+    public String myReason;
+    public String myVictim;
+    public String myAction;
+
+    public ConflictVersion mySourceLeft;
+    public ConflictVersion mySourceRight;
+  }
+  
+  public static class ConflictVersion {
+    public String myKind;
+    public String myPathInRepo;
+    public String myRepoUrl;
+    public String myRevision;
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnLineCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnLineCommand.java
new file mode 100644 (file)
index 0000000..25fc620
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.execution.process.ProcessOutputTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vcs.LineHandlerHelper;
+import com.intellij.openapi.vcs.LineProcessEventListener;
+import com.intellij.util.EventDispatcher;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.Iterator;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 4:05 PM
+ *
+ * honestly stolen from GitLineHandler
+ */
+public class SvnLineCommand extends SvnTextCommand {
+  /**
+   * the partial line from stdout stream
+   */
+  private final StringBuilder myStdoutLine = new StringBuilder();
+  /**
+   * the partial line from stderr stream
+   */
+  private final StringBuilder myStderrLine = new StringBuilder();
+  private final EventDispatcher<LineProcessEventListener> myLineListeners;
+
+  public SvnLineCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) {
+    super(project, workingDirectory, commandName);
+    myLineListeners = EventDispatcher.create(LineProcessEventListener.class);
+  }
+
+  @Override
+  protected void processTerminated(int exitCode) {
+    // force newline
+    if (myStdoutLine.length() != 0) {
+      onTextAvailable("\n\r", ProcessOutputTypes.STDOUT);
+    }
+    else if (myStderrLine.length() != 0) {
+      onTextAvailable("\n\r", ProcessOutputTypes.STDERR);
+    }
+  }
+
+  @Override
+  protected void onTextAvailable(String text, Key outputType) {
+    Iterator<String> lines = LineHandlerHelper.splitText(text).iterator();
+    if (ProcessOutputTypes.STDOUT == outputType) {
+      notifyLines(outputType, lines, myStdoutLine);
+    }
+    else if (ProcessOutputTypes.STDERR == outputType) {
+      notifyLines(outputType, lines, myStderrLine);
+    }
+  }
+
+  private void notifyLines(final Key outputType, final Iterator<String> lines, final StringBuilder lineBuilder) {
+    if (!lines.hasNext()) return;
+    if (lineBuilder.length() > 0) {
+      lineBuilder.append(lines.next());
+      if (lines.hasNext()) {
+        // line is complete
+        final String line = lineBuilder.toString();
+        notifyLine(line, outputType);
+        lineBuilder.setLength(0);
+      }
+    }
+    while (true) {
+      String line = null;
+      if (lines.hasNext()) {
+        line = lines.next();
+      }
+
+      if (lines.hasNext()) {
+        notifyLine(line, outputType);
+      }
+      else {
+        if (line != null && line.length() > 0) {
+          lineBuilder.append(line);
+        }
+        break;
+      }
+    }
+  }
+
+  private void notifyLine(final String line, final Key outputType) {
+    String trimmed = LineHandlerHelper.trimLineSeparator(line);
+    myLineListeners.getMulticaster().onLineAvailable(trimmed, outputType);
+  }
+
+  public void addListener(LineProcessEventListener listener) {
+    myLineListeners.addListener(listener);
+    super.addListener(listener);
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnSimpleCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnSimpleCommand.java
new file mode 100644 (file)
index 0000000..b9a70bb
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.execution.process.ProcessOutputTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vcs.ProcessEventListener;
+import com.intellij.openapi.vcs.VcsException;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 4:04 PM
+ */
+public class SvnSimpleCommand extends SvnTextCommand {
+  private final StringBuilder myStderr;
+  private final StringBuilder myStdout;
+
+  public SvnSimpleCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) {
+    super(project, workingDirectory, commandName);
+
+    myStderr = new StringBuilder();
+    myStdout = new StringBuilder();
+  }
+
+  @Override
+  protected void processTerminated(int exitCode) {
+    //
+  }
+
+  @Override
+  protected void onTextAvailable(String text, Key outputType) {
+    if (ProcessOutputTypes.STDOUT.equals(outputType)) {
+      myStdout.append(text);
+    } else if (ProcessOutputTypes.STDERR.equals(outputType)) {
+      myStderr.append(text);
+    }
+  }
+
+  public StringBuilder getStderr() {
+    return myStderr;
+  }
+
+  public StringBuilder getStdout() {
+    return myStdout;
+  }
+
+  public String run() throws VcsException {
+    final VcsException[] ex = new VcsException[1];
+    final String[] result = new String[1];
+    addListener(new ProcessEventListener() {
+      @Override
+      public void processTerminated(int exitCode) {
+        try {
+          if (exitCode == 0) {
+            result[0] = getStdout().toString();
+          }
+          else {
+            String msg = getStderr().toString();
+            if (msg.length() == 0) {
+              msg = getStdout().toString();
+            }
+            if (msg.length() == 0) {
+              msg = "Svn process exited with error code: " + exitCode;
+            }
+            ex[0] = new VcsException(msg);
+          }
+        }
+        catch (Throwable t) {
+          ex[0] = new VcsException(t.toString(), t);
+        }
+      }
+
+      @Override
+      public void startFailed(Throwable exception) {
+        ex[0] = new VcsException("Process failed to start (" + myCommandLine.getCommandLineString() + "): " + exception.toString(), exception);
+      }
+    });
+    start();
+    waitFor();
+    if (ex[0] != null) {
+      throw ex[0];
+    }
+    if (result[0] == null) {
+      throw new VcsException("Svn command returned null: " + myCommandLine.getCommandLineString());
+    }
+    return result[0];
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnStatusHandler.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnStatusHandler.java
new file mode 100644 (file)
index 0000000..853f8bf
--- /dev/null
@@ -0,0 +1,521 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.openapi.util.Getter;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.containers.Convertor;
+import com.intellij.util.containers.MultiMap;
+import org.jetbrains.idea.svn17.portable.PortableStatus;
+import org.jetbrains.idea.svn17.portable.StatusCallbackConvertor;
+import org.tmatesoft.svn.core.SVNNodeKind;
+import org.tmatesoft.svn.core.internal.util.SVNDate;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.SVNStatusType;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 7:59 PM
+ */
+public class SvnStatusHandler extends DefaultHandler {
+  private String myChangelistName;
+  private List<PortableStatus> myDefaultListStatuses;
+  private MultiMap<String, PortableStatus> myCurrentListChanges;
+  private PortableStatus myPending;
+
+  private final List<ElementHandlerBase> myParseStack;
+  private final Map<String, Getter<ElementHandlerBase>> myElementsMap;
+  private final DataCallback myDataCallback;
+  private final File myBase;
+  private final StringBuilder mySb;
+
+  public SvnStatusHandler(final DataCallback dataCallback, File base, final Convertor<File, SVNInfo> infoGetter) {
+    myBase = base;
+    myParseStack = new ArrayList<ElementHandlerBase>();
+    myParseStack.add(new Fake());
+
+    myElementsMap = new HashMap<String, Getter<ElementHandlerBase>>();
+    fillElements();
+
+    if (dataCallback != null) {
+      myDataCallback = new DataCallback() {
+        @Override
+        public void switchPath() {
+          dataCallback.switchPath();
+          newPending(infoGetter);
+        }
+
+        @Override
+        public void switchChangeList(String newList) {
+          dataCallback.switchChangeList(newList);
+        }
+      };
+    } else {
+      myDataCallback = new DataCallback() {
+        @Override
+        public void switchPath() {
+          if (myChangelistName == null) {
+            myDefaultListStatuses.add(myPending);
+          } else {
+            myCurrentListChanges.putValue(myChangelistName, myPending);
+          }
+          newPending(infoGetter);
+        }
+
+        @Override
+        public void switchChangeList(String newList) {
+          myChangelistName = newList;
+        }
+      };
+    }
+    newPending(infoGetter);
+    mySb = new StringBuilder();
+  }
+
+  private void newPending(final Convertor<File, SVNInfo> infoGetter) {
+    myPending = new PortableStatus();
+    myPending.setInfoGetter(new Getter<SVNInfo>() {
+      @Override
+      public SVNInfo get() {
+        return infoGetter.convert(myPending.getFile());
+      }
+    });
+  }
+
+  public PortableStatus getPending() {
+    return myPending;
+  }
+
+  public List<PortableStatus> getDefaultListStatuses() {
+    return myDefaultListStatuses;
+  }
+
+  public MultiMap<String, PortableStatus> getCurrentListChanges() {
+    return myCurrentListChanges;
+  }
+
+  private void fillElements() {
+    myElementsMap.put("status", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Status();
+      }
+    });
+    myElementsMap.put("author", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Author();
+      }
+    });
+    myElementsMap.put("changelist", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Changelist();
+      }
+    });
+    myElementsMap.put("commit", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Commit();
+      }
+    });
+    myElementsMap.put("date", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Date();
+      }
+    });
+    myElementsMap.put("entry", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Entry(myBase);
+      }
+    });
+    myElementsMap.put("target", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new Target();
+      }
+    });
+    myElementsMap.put("wc-status", new Getter<ElementHandlerBase>() {
+      @Override
+      public ElementHandlerBase get() {
+        return new WcStatus();
+      }
+    });
+  }
+
+  @Override
+  public void endElement(String uri, String localName, String qName) throws SAXException {
+    //
+  }
+
+  @Override
+  public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+    assertSAX(! myParseStack.isEmpty());
+    ElementHandlerBase current = myParseStack.get(myParseStack.size() - 1);
+    if (mySb.length() > 0) {
+      current.characters(mySb.toString().trim(), myPending);
+      mySb.setLength(0);
+    }
+
+    while (true) {
+      final boolean createNewChild = current.startElement(uri, localName, qName, attributes);
+      if (createNewChild) {
+        assertSAX(myElementsMap.containsKey(qName));
+        final ElementHandlerBase newChild = myElementsMap.get(qName).get();
+        newChild.updateStatus(attributes, myPending);
+        myParseStack.add(newChild);
+        return;
+      } else {
+        // go up
+        current.postEffect(myDataCallback);
+        myParseStack.remove(myParseStack.size() - 1);
+        assertSAX(! myParseStack.isEmpty());
+        current = myParseStack.get(myParseStack.size() - 1);
+      }
+    }
+  }
+
+  @Override
+  public void characters(char[] ch, int start, int length) throws SAXException {
+    assertSAX(! myParseStack.isEmpty());
+    mySb.append(ch, start, length);
+  }
+
+  @Override
+  public void endDocument() throws SAXException {
+    assertSAX(! myParseStack.isEmpty());
+    for (int i = myParseStack.size() - 1; i >= 0; -- i) {
+      ElementHandlerBase current = myParseStack.get(i);
+      current.postEffect(myDataCallback);
+    }
+    myParseStack.clear();
+  }
+
+  private static void assertSAX(final boolean shouldBeTrue) throws SAXException {
+    if (! shouldBeTrue) {
+      throw new SAXException("can not parse output");
+    }
+  }
+
+  private static class Fake extends ElementHandlerBase {
+    private Fake() {
+      super(new String[]{"status"}, new String[]{});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+    }
+  }
+
+  private static class Date extends ElementHandlerBase {
+    private Date() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+      final SVNDate date = SVNDate.parseDate(s);
+      //if (SVNDate.NULL.equals(date)) return;
+      pending.setRemoteDate(date);
+    }
+  }
+
+  private static class Author extends ElementHandlerBase {
+    private Author() {
+      super(new String[]{}, new String[]{});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+      pending.setRemoteAuthor(s);
+    }
+  }
+
+  /*        <commit
+              revision="25">
+            <author>admin</author>
+            <date>2011-11-09T12:21:02.401530Z</date>
+  */
+  private static class Commit extends ElementHandlerBase {
+    private Commit() {
+      super(new String[]{"author", "date"}, new String[]{});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
+      final String revision = attributes.getValue("revision");
+      if (! StringUtil.isEmptyOrSpaces(revision)) {
+        try {
+          final long number = Long.parseLong(revision);
+          status.setRemoteRevision(SVNRevision.create(number));
+        } catch (NumberFormatException e) {
+          throw new SAXException(e);
+        }
+      }
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+    }
+  }
+
+  /*      <wc-status   props="none"   copied="true"   tree-conflicted="true"   item="added">
+      <wc-status       props="none"   item="unversioned">
+      <wc-status       props="none"   item="added"   revision="-1">
+      <wc-status       props="none"   item="modified"   revision="112">
+      <wc-status       props="conflicted"  item="normal"  revision="112">
+  */
+  private static class WcStatus extends ElementHandlerBase {
+    private WcStatus() {
+      super(new String[]{"commit"}, new String[]{});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
+      final String props = attributes.getValue("props");
+      assertSAX(props != null);
+      final SVNStatusType propertiesStatus = StatusCallbackConvertor.convert(org.apache.subversion.javahl.types.Status.Kind.valueOf(props));
+      status.setPropertiesStatus(propertiesStatus);
+      final String item = attributes.getValue("item");
+      assertSAX(item != null);
+      final SVNStatusType contentsStatus = StatusCallbackConvertor.convert(org.apache.subversion.javahl.types.Status.Kind.valueOf(item));
+      status.setContentsStatus(contentsStatus);
+
+      if (SVNStatusType.STATUS_CONFLICTED.equals(propertiesStatus) || SVNStatusType.STATUS_CONFLICTED.equals(contentsStatus)) {
+        status.setIsConflicted(true);
+      }
+
+      // optional
+      final String copied = attributes.getValue("copied");
+      if (copied != null && Boolean.parseBoolean(copied)) {
+        status.setIsCopied(true);
+      }
+      final String treeConflicted = attributes.getValue("tree-conflicted");
+      if (treeConflicted != null && Boolean.parseBoolean(treeConflicted)) {
+        status.setIsConflicted(true);
+      }
+
+      final String revision = attributes.getValue("revision");
+      if (! StringUtil.isEmptyOrSpaces(revision)) {
+        try {
+          final long number = Long.parseLong(revision);
+          status.setRevision(SVNRevision.create(number));
+        } catch (NumberFormatException e) {
+          throw new SAXException(e);
+        }
+      }
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+    }
+  }
+
+  private static class Entry extends ElementHandlerBase {
+    private final File myBase;
+
+    private Entry(final File base) {
+      super(new String[]{"wc-status"}, new String[]{});
+      myBase = base;
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
+      final String path = attributes.getValue("path");
+      assertSAX(path != null);
+      final File file = new File(myBase, path);
+      status.setFile(file);
+      status.setKind(file.isDirectory() ? SVNNodeKind.DIR : SVNNodeKind.FILE);
+      status.setPath(path);
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+      callback.switchPath();
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+    }
+  }
+
+  private static class Changelist extends ElementHandlerBase {
+    private String myName;
+    
+    private Changelist() {
+      super(new String[]{}, new String[]{"entry"});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
+      final String name = attributes.getValue("name");
+      assertSAX(! StringUtil.isEmptyOrSpaces(name));
+      myName = name;
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+      callback.switchChangeList(myName);
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+    }
+  }
+
+  private static class Target extends ElementHandlerBase {
+    private Target() {
+      super(new String[]{}, new String[]{"entry"});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) {
+    }
+
+    @Override
+    public void postEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+    }
+  }
+
+  private static class Status extends ElementHandlerBase {
+    private Status() {
+      super(new String[]{"target"}, new String[]{"changelist"});
+    }
+
+    @Override
+    protected void updateStatus(Attributes attributes, PortableStatus status) {
+    }
+
+    @Override
+    public void postEffect(final DataCallback callback) {
+    }
+
+    @Override
+    public void preEffect(DataCallback callback) {
+    }
+
+    @Override
+    public void characters(String s, PortableStatus pending) {
+    }
+  }
+
+  public abstract static class ElementHandlerBase {
+    private final Set<String> myAwaitedChildren;
+    private final Set<String> myAwaitedChildrenMultiple;
+
+    ElementHandlerBase(String[] awaitedChildren, String[] awaitedChildrenMultiple) {
+      myAwaitedChildren = new HashSet<String>(Arrays.asList(awaitedChildren));
+      myAwaitedChildrenMultiple = new HashSet<String>(Arrays.asList(awaitedChildrenMultiple));
+    }
+
+    protected abstract void updateStatus(Attributes attributes, PortableStatus status) throws SAXException;
+    public abstract void postEffect(final DataCallback callback);
+    public abstract void preEffect(final DataCallback callback);
+
+    public boolean startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+      if (myAwaitedChildrenMultiple.contains(qName)) {
+        return true;
+      }
+      return myAwaitedChildren.remove(qName);
+    }
+
+    public abstract void characters(String s, PortableStatus pending);
+  }
+
+  public interface DataCallback {
+    void switchPath();
+    void switchChangeList(final String newList);
+  }
+}
diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnTextCommand.java b/plugins/svn4idea/src/org/jetbrains/idea/svn17/commandLine/SvnTextCommand.java
new file mode 100644 (file)
index 0000000..7c7dd20
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * 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.svn17.commandLine;
+
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.process.OSProcessHandler;
+import com.intellij.execution.process.ProcessEvent;
+import com.intellij.execution.process.ProcessListener;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+
+/**
+ * Created with IntelliJ IDEA.
+ * User: Irina.Chernushina
+ * Date: 1/25/12
+ * Time: 3:09 PM
+ */
+public abstract class SvnTextCommand extends SvnCommand {
+  private boolean myIsDestroyed;
+  private OSProcessHandler myHandler;
+
+  public SvnTextCommand(Project project, File workingDirectory, @NotNull SvnCommandName commandName) {
+    super(project, workingDirectory, commandName);
+  }
+
+  @Override
+  protected void waitForProcess() {
+    if (myHandler != null) {
+      myHandler.waitFor();
+    }
+  }
+
+  @Override
+  protected Process startProcess() throws ExecutionException {
+    if (myIsDestroyed) return null;
+    final Process process = myCommandLine.createProcess();
+    myHandler = new OSProcessHandler(process, myCommandLine.getCommandLineString());
+    return myHandler.getProcess();
+  }
+
+  @Override
+  protected void startHandlingStreams() {
+    if (myIsDestroyed || myProcess == null) return;
+
+    myHandler.addProcessListener(new ProcessListener() {
+      public void startNotified(final ProcessEvent event) {
+        // do nothing
+      }
+
+      public void processTerminated(final ProcessEvent event) {
+        final int exitCode = event.getExitCode();
+        try {
+          setExitCode(exitCode);
+          //cleanupEnv();   todo
+          SvnTextCommand.this.processTerminated(exitCode);
+        } finally {
+          listeners().processTerminated(exitCode);
+        }
+      }
+
+      public void processWillTerminate(final ProcessEvent event, final boolean willBeDestroyed) {
+        // do nothing
+      }
+
+      public void onTextAvailable(final ProcessEvent event, final Key outputType) {
+        SvnTextCommand.this.onTextAvailable(event.getText(), outputType);
+      }
+    });
+    myHandler.startNotify();
+  }
+
+  protected abstract void processTerminated(int exitCode);
+  protected abstract void onTextAvailable(final String text, final Key outputType);
+
+  @Override
+  public void destroyProcess() {
+    myIsDestroyed = true;
+    if (myHandler != null) {
+      myHandler.destroyProcess();
+    }
+  }
+}
index 353b03e3a85fb7cb5a0513f9975cab7d33bda030..a6a70462a80333fbbe03658063bb3c8efe0c43a5 100644 (file)
@@ -26,7 +26,10 @@ import org.tmatesoft.svn.core.wc.SVNConflictAction;
  */
 public class ConflictActionConvertor {
   public static SVNConflictAction create(final ConflictDescriptor conflict) {
-    ConflictDescriptor.Action action = conflict.getAction();
+    return create(conflict.getAction());
+  }
+
+  public static SVNConflictAction create(ConflictDescriptor.Action action) {
     if (ConflictDescriptor.Action.add.equals(action)) {
       return SVNConflictAction.ADD;
     } else if (ConflictDescriptor.Action.delete.equals(action)) {
index 463e0d792914cb238cab9f13f2d5cd4c9047f496..59d0db10fd25131418f35dd88fb8b023546922cc 100644 (file)
@@ -16,6 +16,7 @@
 package org.jetbrains.idea.svn17.portable;
 
 import com.intellij.openapi.util.Getter;
+import org.jetbrains.idea.svn17.WorkingCopyFormat;
 import org.tmatesoft.svn.core.SVNLock;
 import org.tmatesoft.svn.core.SVNNodeKind;
 import org.tmatesoft.svn.core.SVNURL;
@@ -32,9 +33,10 @@ import java.util.Map;
  * Time: 12:29 PM
  */
 public class PortableStatus extends SVNStatus {
-  private final boolean myConflicted;
-  private final Getter<SVNInfo> myInfoGetter;
+  private boolean myConflicted;
+  private Getter<SVNInfo> myInfoGetter;
   private SVNInfo myInfo;
+  private String myPath;
 
   /**
    * Constructs an <b>SVNStatus</b> object filling it with status information
@@ -99,6 +101,35 @@ public class PortableStatus extends SVNStatus {
           remotePropertiesStatus, isLocked, isCopied, isSwitched, isFileExternal, null, null, null, null, null, null, remoteLock,
           localLock, entryProperties, changelistName, wcFormatVersion, null);
     myConflicted = isConflicted;
+    myInfoGetter = infoGetter == null ? new Getter<SVNInfo>() {
+      @Override
+      public SVNInfo get() {
+        return null;
+      }
+    } : infoGetter;
+  }
+
+  public PortableStatus() {
+    myInfoGetter = new Getter<SVNInfo>() {
+      @Override
+      public SVNInfo get() {
+        return null;
+      }
+    };
+    setWorkingCopyFormat(WorkingCopyFormat.ONE_DOT_SEVEN.getFormat());
+  }
+
+  @Override
+  public void setIsConflicted(boolean isConflicted) {
+    myConflicted = isConflicted;
+    super.setIsConflicted(isConflicted);
+  }
+
+  public void setConflicted(boolean conflicted) {
+    myConflicted = conflicted;
+  }
+
+  public void setInfoGetter(Getter<SVNInfo> infoGetter) {
     myInfoGetter = infoGetter;
   }
 
@@ -109,6 +140,10 @@ public class PortableStatus extends SVNStatus {
 
   private SVNInfo initInfo() {
     if (myInfo == null) {
+      final SVNStatusType contentsStatus = getContentsStatus();
+      if (contentsStatus == null || SVNStatusType.UNKNOWN.equals(contentsStatus)) {
+        return null;
+      }
       myInfo = myInfoGetter.get();
     }
     return myInfo;
@@ -125,7 +160,8 @@ public class PortableStatus extends SVNStatus {
   @Override
   public File getConflictNewFile() {
     if (! isConflicted()) return null;
-    return initInfo().getConflictNewFile();
+    final SVNInfo info = initInfo();
+    return info == null ? null : info.getConflictNewFile();
   }
 
   /**
@@ -140,7 +176,8 @@ public class PortableStatus extends SVNStatus {
   @Override
   public File getConflictOldFile() {
     if (! isConflicted()) return null;
-    return initInfo().getConflictOldFile();
+    final SVNInfo info = initInfo();
+    return info == null ? null : info.getConflictOldFile();
   }
 
   /**
@@ -156,7 +193,8 @@ public class PortableStatus extends SVNStatus {
   @Override
   public File getConflictWrkFile() {
     if (! isConflicted()) return null;
-    return initInfo().getConflictWrkFile();
+    final SVNInfo info = initInfo();
+    return info == null ? null : info.getConflictWrkFile();
   }
 
   /**
@@ -169,7 +207,8 @@ public class PortableStatus extends SVNStatus {
   @Override
   public File getPropRejectFile() {
     if (! isConflicted()) return null;
-    return initInfo().getPropConflictFile();
+    final SVNInfo info = initInfo();
+    return info == null ? null : info.getPropConflictFile();
   }
 
   /**
@@ -181,6 +220,8 @@ public class PortableStatus extends SVNStatus {
   @Override
   public String getCopyFromURL() {
     if (! isCopied()) return null;
+    final SVNInfo info = initInfo();
+    if (info == null) return null;
     SVNURL url = initInfo().getCopyFromURL();
     return url == null ? null : url.toString();
   }
@@ -194,7 +235,8 @@ public class PortableStatus extends SVNStatus {
   @Override
   public SVNRevision getCopyFromRevision() {
     if (! isCopied()) return null;
-    return initInfo().getCopyFromRevision();
+    final SVNInfo info = initInfo();
+    return info == null ? null : info.getCopyFromRevision();
   }
 
   /**
@@ -207,6 +249,15 @@ public class PortableStatus extends SVNStatus {
   @Override
   public SVNTreeConflictDescription getTreeConflict() {
     if (! isConflicted()) return null;
-    return initInfo().getTreeConflict();
+    final SVNInfo info = initInfo();
+    return info == null ? null : info.getTreeConflict();
+  }
+
+  public void setPath(String path) {
+    myPath = path;
+  }
+
+  public String getPath() {
+    return myPath;
   }
 }