[hg] Fixed NPE if hg exe is not configured.
[idea/community.git] / plugins / hg4idea / src / org / zmlx / hg4idea / command / HgRevisionsCommand.java
1 // Copyright 2008-2010 Victor Iacoban
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software distributed under
10 // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 // either express or implied. See the License for the specific language governing permissions and
12 // limitations under the License.
13 package org.zmlx.hg4idea.command;
14
15 import com.intellij.openapi.diagnostic.Logger;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.vcs.FilePath;
18 import com.intellij.openapi.vcs.changes.ChangeListManager;
19 import com.intellij.openapi.vfs.VirtualFile;
20 import org.apache.commons.lang.StringUtils;
21 import org.jetbrains.annotations.Nullable;
22 import org.zmlx.hg4idea.HgFile;
23 import org.zmlx.hg4idea.HgFileRevision;
24 import org.zmlx.hg4idea.HgRevisionNumber;
25 import org.zmlx.hg4idea.util.HgUtil;
26 import org.zmlx.hg4idea.util.HgChangesetUtil;
27 import org.zmlx.hg4idea.execution.HgCommandResult;
28 import org.zmlx.hg4idea.execution.HgCommandExecutor;
29
30 import java.text.ParseException;
31 import java.text.SimpleDateFormat;
32 import java.util.*;
33
34 abstract class HgRevisionsCommand {
35
36   private static final Logger LOG = Logger.getInstance(HgRevisionsCommand.class.getName());
37
38   private static final String[] SHORT_TEMPLATE_ITEMS = { "{rev}","{node|short}", "{parents}", "{date|isodatesec}", "{author}", "{branches}", "{desc}" };
39   private static final String[] LONG_TEMPLATE_ITEMS =
40     { "{rev}", "{node|short}", "{parents}", "{date|isodatesec}", "{author}", "{branches}", "{desc}", "{file_adds}", "{file_mods}", "{file_dels}", "{file_copies}" };
41
42   private static final int REVISION_INDEX = 0;
43   private static final int CHANGESET_INDEX = 1;
44   private static final int PARENTS_INDEX = 2;
45
46   private static final int DATE_INDEX = 3;
47   private static final int AUTHOR_INDEX = 4;
48   private static final int BRANCH_INDEX = 5;
49   private static final int MESSAGE_INDEX = 6;
50   private static final int FILES_ADDED_INDEX = 7;
51   private static final int FILES_MODIFIED_INDEX = 8;
52   private static final int FILES_DELETED_INDEX = 9;
53   private static final int FILES_COPIED_INDEX = 10;
54
55   private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
56
57   private final Project project;
58
59   public HgRevisionsCommand(Project project) {
60     this.project = project;
61   }
62
63   @Nullable
64   protected abstract HgCommandResult execute(
65     HgCommandExecutor executor, VirtualFile repo, String template, int limit, HgFile hgFile
66   );
67
68   public final List<HgFileRevision> execute(HgFile hgFile, int limit, boolean includeFiles) {
69     if ((limit <= 0 && limit != -1) || hgFile == null || hgFile.getRepo() == null) {
70       return Collections.emptyList();
71     }
72
73     HgCommandExecutor hgCommandExecutor = new HgCommandExecutor(project);
74
75     String template = HgChangesetUtil.makeTemplate(includeFiles ? LONG_TEMPLATE_ITEMS : SHORT_TEMPLATE_ITEMS);
76     int itemCount = includeFiles ? LONG_TEMPLATE_ITEMS.length : SHORT_TEMPLATE_ITEMS.length;
77
78     FilePath originalFileName = HgUtil.getOriginalFileName(hgFile.toFilePath(), ChangeListManager.getInstance(project));
79     HgFile originalHgFile = new HgFile(hgFile.getRepo(), originalFileName);
80     HgCommandResult result = execute(
81       hgCommandExecutor, hgFile.getRepo(), template, limit, originalHgFile
82     );
83
84     final List<HgFileRevision> revisions = new LinkedList<HgFileRevision>();
85     if (result == null) {
86       return revisions;
87     }
88
89     String output = result.getRawOutput();
90     String[] changeSets = output.split(HgChangesetUtil.CHANGESET_SEPARATOR);
91     for (String line : changeSets) {
92       try {
93         String[] attributes = line.split(HgChangesetUtil.ITEM_SEPARATOR);
94         if (attributes.length != itemCount) {
95           LOG.debug("Wrong format. Skipping line " + line);
96           continue;
97         }
98
99         String revisionString = attributes[REVISION_INDEX];
100         String changeset = attributes[CHANGESET_INDEX];
101         String parentsString = attributes[PARENTS_INDEX];
102
103         List<HgRevisionNumber> parents = new ArrayList<HgRevisionNumber>(2);
104         if (StringUtils.isEmpty(parentsString)) {
105           Long revision = Long.valueOf(revisionString);
106           HgRevisionNumber parentRevision = HgRevisionNumber.getLocalInstance(String.valueOf(revision - 1));
107           parents.add(parentRevision);
108         } else {
109           //hg returns parents in the format 'rev:node rev:node ' (note the trailing space)
110           String[] parentStrings = parentsString.trim().split(" ");
111           for (String parentString : parentStrings) {
112             String[] parentParts = parentString.split(":");
113             parents.add(HgRevisionNumber.getInstance(parentParts[0], parentParts[1]));
114           }
115         }
116         HgRevisionNumber vcsRevisionNumber = HgRevisionNumber.getInstance(
117           revisionString,
118           changeset,
119           parents
120         );
121
122         Date revisionDate = DATE_FORMAT.parse(attributes[DATE_INDEX]);
123         String author = attributes[AUTHOR_INDEX];
124         String branchName = attributes[BRANCH_INDEX];
125         String commitMessage = attributes[MESSAGE_INDEX];
126
127         Set<String> filesAdded;
128         Set<String> filesModified;
129         Set<String> filesDeleted;
130         Map<String, String> copies;
131         if (FILES_ADDED_INDEX < itemCount) {
132           filesAdded = parseFileList(attributes[FILES_ADDED_INDEX]);
133           filesModified = parseFileList(attributes[FILES_MODIFIED_INDEX]);
134           filesDeleted = parseFileList(attributes[FILES_DELETED_INDEX]);
135
136           copies = parseCopiesFileList(attributes[FILES_COPIED_INDEX]);
137           // Only keep renames, i.e. copies where the source file is also deleted.
138           Iterator<String> keys = copies.keySet().iterator();
139           while (keys.hasNext()) {
140             String s = keys.next();
141             if (!filesDeleted.contains(s)) {
142               keys.remove();
143             }
144           }
145         } else {
146           filesAdded = Collections.emptySet();
147           filesModified = Collections.emptySet();
148           filesDeleted = Collections.emptySet();
149           copies = Collections.emptyMap();
150         }
151
152         revisions.add(new HgFileRevision(project, hgFile, vcsRevisionNumber,
153           branchName, revisionDate, author, commitMessage, filesModified, filesAdded, filesDeleted, copies));
154       } catch (NumberFormatException e) {
155         LOG.warn("Error parsing rev in line " + line);
156       } catch (ParseException e) {
157         LOG.warn("Error parsing date in line " + line);
158       }
159     }
160     return revisions;
161   }
162
163   private Set<String> parseFileList(String fileListString) {
164     if (StringUtils.isEmpty(fileListString)) {
165       return Collections.emptySet();
166     } else {
167       return new HashSet<String>(Arrays.asList(fileListString.split(" ")));
168     }
169   }
170
171   private Map<String, String> parseCopiesFileList(String fileListString) {
172     if (StringUtils.isEmpty(fileListString)) {
173       return Collections.emptyMap();
174     } else {
175       Map<String, String> copies = new HashMap<String, String>();
176
177       String[] filesList = fileListString.split("\\)");
178
179       for (String files : filesList) {
180         String[] file = files.split(" \\(");
181         String target = file[0];
182         String source = file[1];
183
184         copies.put(source, target);
185       }
186
187       return copies;
188     }
189   }
190 }