summaryrefslogtreecommitdiff
path: root/plugins/svn4idea/src/org/jetbrains/idea/svn/commandLine/UpdateOutputLineConverter.java
blob: 602a6e8c460eb565e3b16fd999b4e2fdfdb6070d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
 * Copyright 2000-2012 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jetbrains.idea.svn.commandLine;

import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnUtil;
import org.jetbrains.idea.svn.api.EventAction;
import org.jetbrains.idea.svn.api.ProgressEvent;
import org.jetbrains.idea.svn.status.StatusType;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;

import java.io.File;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created with IntelliJ IDEA.
 * User: Irina.Chernushina
 * Date: 2/1/12
 * Time: 5:13 PM
 */
public class UpdateOutputLineConverter {

  private final static String MERGING = "--- Merging";
  private final static String RECORDING_MERGE_INFO = "--- Recording mergeinfo";

  private final static String UPDATING = "Updating";
  private final static String AT_REVISION = "At revision (\\d+)\\.";
  private final static String UPDATED_TO_REVISION = "Updated to revision (\\d+)\\.";
  private final static String SKIPPED = "Skipped";
  private final static String RESTORED = "Restored";

  private final static String FETCHING_EXTERNAL = "Fetching external";
  private final static String EXTERNAL = "External at (\\d+)\\.";
  private final static String UPDATED_EXTERNAL = "Updated external to revision (\\d+)\\.";
  
  private final static Pattern ourAtRevision = Pattern.compile(AT_REVISION);
  private final static Pattern ourUpdatedToRevision = Pattern.compile(UPDATED_TO_REVISION);
  private final static Pattern ourCheckedOutRevision = Pattern.compile("Checked out revision (\\d+)\\.");

  // export from repository
  private final static Pattern ourExportedRevision = Pattern.compile("Exported revision (\\d+)\\.");
  // export from working copy
  private final static Pattern ourExportComplete = Pattern.compile("Export complete\\.");

  private final static Pattern ourExternal = Pattern.compile(EXTERNAL);
  private final static Pattern ourUpdatedExternal = Pattern.compile(UPDATED_EXTERNAL);
  private final static Pattern ourCheckedOutExternal = Pattern.compile("Checked out external at revision (\\d+)\\.");

  private final static Pattern[] ourCompletePatterns =
    new Pattern[]{ourAtRevision, ourUpdatedToRevision, ourCheckedOutRevision, ourExportedRevision, ourExternal, ourUpdatedExternal,
      ourCheckedOutExternal, ourExportComplete};

  private final File myBase;
  private File myCurrentFile;

  public UpdateOutputLineConverter(File base) {
    myBase = base;
    // checkout output does not have special line like "Updating '.'" on start - so set current file directly
    // to correctly detect complete event
    myCurrentFile = base;
  }

  public ProgressEvent convert(final String line) {
    // TODO: Add direct processing of "Summary of conflicts" lines at the end of "svn update" output (if there are conflicts).
    // TODO: Now it works ok because parseNormalLine could not determine necessary statuses from that and further lines
    if (StringUtil.isEmptyOrSpaces(line)) return null;

    if (line.startsWith(MERGING) || line.startsWith(RECORDING_MERGE_INFO)) {
      return null;
    } else if (line.startsWith(UPDATING)) {
      myCurrentFile = parseForPath(line);
      return new ProgressEvent(myCurrentFile, -1, null, null, EventAction.UPDATE_NONE, null, null);
    } else if (line.startsWith(RESTORED)) {
      myCurrentFile = parseForPath(line);
      return new ProgressEvent(myCurrentFile, -1, null, null, EventAction.RESTORE, null, null);
    } else if (line.startsWith(SKIPPED)) {
      // called, for instance, when folder is not working copy
      myCurrentFile = parseForPath(line);
      final String comment = parseComment(line);
      return new ProgressEvent(myCurrentFile, -1, null, null, EventAction.SKIP,
                               comment == null ? null : SVNErrorMessage.create(SVNErrorCode.WC_OBSTRUCTED_UPDATE, comment), null);
    } else if (line.startsWith(FETCHING_EXTERNAL)) {
      myCurrentFile = parseForPath(line);
      return new ProgressEvent(myCurrentFile, -1, null, null, EventAction.UPDATE_EXTERNAL, null, null);
    }

    for (int i = 0; i < ourCompletePatterns.length; i++) {
      final Pattern pattern = ourCompletePatterns[i];
      final long revision = matchAndGetRevision(pattern, line);
      if (revision != -1) {
        // TODO: seems that myCurrentFile will not always be correct - complete update message could be right after complete externals update
        // TODO: check this and use Stack instead
        return new ProgressEvent(myCurrentFile, revision, null, null, EventAction.UPDATE_COMPLETED, null, null);
      }
    }

    return parseNormalString(line);
  }

  private final static Set<Character> ourActions = new HashSet<Character>(Arrays.asList(new Character[] {'A', 'D', 'U', 'C', 'G', 'E', 'R'}));

  @Nullable
  private ProgressEvent parseNormalString(final String line) {
    if (line.length() < 5) return null;
    final char first = line.charAt(0);
    if (' ' != first && ! ourActions.contains(first)) return null;
    final StatusType contentsStatus = CommandUtil.getStatusType(first);
    final char second = line.charAt(1);
    final StatusType propertiesStatus = CommandUtil.getStatusType(second);
    final char lock = line.charAt(2); // dont know what to do with stolen lock info
    if (' ' != lock && 'B' != lock) return null;
    final char treeConflict = line.charAt(3);
    if (' ' != treeConflict && 'C' != treeConflict) return null;
    final boolean haveTreeConflict = 'C' == treeConflict;

    final String path = line.substring(4).trim();
    if (StringUtil.isEmptyOrSpaces(path)) return null;
    final File file = createFile(path);
    if (StatusType.STATUS_OBSTRUCTED.equals(contentsStatus)) {
      // obstructed
      return new ProgressEvent(file, -1, contentsStatus, propertiesStatus, EventAction.UPDATE_SKIP_OBSTRUCTION, null, null);
    }
    
    EventAction action;
    EventAction expectedAction;
    if (StatusType.STATUS_ADDED.equals(contentsStatus)) {
      expectedAction = EventAction.UPDATE_ADD;
    } else if (StatusType.STATUS_DELETED.equals(contentsStatus)) {
      expectedAction = EventAction.UPDATE_DELETE;
    } else {
      expectedAction = EventAction.UPDATE_UPDATE;
    }
    action = expectedAction;
    if (haveTreeConflict) {
      action = EventAction.TREE_CONFLICT;
    }

    return new ProgressEvent(file, -1, contentsStatus, propertiesStatus, action, null, null);
  }

  private File createFile(String path) {
    return SvnUtil.resolvePath(myBase, path);
  }

  @Nullable
  private long matchAndGetRevision(final Pattern pattern, final String line) {
    final Matcher matcher = pattern.matcher(line);
    if (matcher.matches()) {
      if (pattern == ourExportComplete) {
        return 0;
      }

      final String group = matcher.group(1);
      if (group == null) return -1;
      try {
        return Long.parseLong(group);
      } catch (NumberFormatException e) {
        //                                                                                    
      }
    }
    return -1;
  }

  @Nullable
  private String parseComment(final String line) {
    final int idx = line.lastIndexOf("--");
    if (idx != -1 && idx < (line.length() - 2)) {
      return line.substring(idx + 2).trim();
    }
    return null;
  }

  @Nullable
  private File parseForPath(final String line) {
    final int idx1 = line.indexOf('\'');
    if (idx1 == -1) return null;
    final int idx2 = line.indexOf('\'', idx1 + 1);
    if (idx2 == -1) return null;
    final String substring = line.substring(idx1 + 1, idx2);
    if (".".equals(substring)) return myBase;
    return createFile(substring);
  }
}