Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / hg4idea / src / org / zmlx / hg4idea / HgUtil.java
1 // Copyright 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;
14
15 import com.intellij.openapi.application.ApplicationManager;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.util.ShutDownTracker;
18 import com.intellij.openapi.vcs.FilePath;
19 import com.intellij.openapi.vcs.VcsException;
20 import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
21 import com.intellij.openapi.vfs.VirtualFile;
22 import com.intellij.ui.GuiUtils;
23 import com.intellij.util.containers.HashMap;
24 import com.intellij.vcsUtil.VcsUtil;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.annotations.Nullable;
27 import org.zmlx.hg4idea.command.HgRemoveCommand;
28 import org.zmlx.hg4idea.command.HgWorkingCopyRevisionsCommand;
29
30 import java.io.*;
31 import java.lang.reflect.InvocationTargetException;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.List;
35 import java.util.Map;
36
37 /**
38  * HgUtil is a collection of static utility methods for Mercurial.
39  */
40 public abstract class HgUtil {
41   
42   public static File copyResourceToTempFile(String basename, String extension) throws IOException {
43     final InputStream in = HgUtil.class.getClassLoader().getResourceAsStream("python/" + basename + extension);
44
45     final File tempFile = File.createTempFile(basename, extension);
46     final byte[] buffer = new byte[4096];
47
48     OutputStream out = null;
49     try {
50       out = new FileOutputStream(tempFile, false);
51       int bytesRead;
52       while ((bytesRead = in.read(buffer)) != -1)
53         out.write(buffer, 0, bytesRead);
54     } finally {
55       try {
56         out.close();
57       }
58       catch (IOException e) {
59         // ignore
60       }
61     }
62     try {
63       in.close();
64     }
65     catch (IOException e) {
66       // ignore
67     }
68     tempFile.deleteOnExit();
69     return tempFile;
70   }
71
72   public static void markDirectoryDirty(final Project project, final VirtualFile file) throws InvocationTargetException, InterruptedException {
73     ApplicationManager.getApplication().runReadAction(new Runnable() {
74       public void run() {
75         VcsDirtyScopeManager.getInstance(project).dirDirtyRecursively(file);
76       }
77     });
78     runWriteActionAndWait(new Runnable() {
79       public void run() {
80         file.refresh(true, true);
81       }
82     });
83   }
84
85   public static void markFileDirty( final Project project, final VirtualFile file ) throws InvocationTargetException, InterruptedException {
86     ApplicationManager.getApplication().runReadAction(new Runnable() {
87       public void run() {
88         VcsDirtyScopeManager.getInstance(project).fileDirty(file);
89       }
90     } );
91     runWriteActionAndWait(new Runnable() {
92       public void run() {
93         file.refresh(true, false);
94       }
95     });
96   }
97
98   /**
99    * Runs the given task as a write action in the event dispatching thread and waits for its completion.
100    */
101   public static void runWriteActionAndWait(@NotNull final Runnable runnable) throws InvocationTargetException, InterruptedException {
102     GuiUtils.runOrInvokeAndWait(new Runnable() {
103       public void run() {
104         ApplicationManager.getApplication().runWriteAction(runnable);
105       }
106     });
107   }
108
109   /**
110    * Schedules the given task to be run as a write action in the event dispatching thread.
111    */
112   public static void runWriteActionLater(@NotNull final Runnable runnable) {
113     ApplicationManager.getApplication().invokeLater(new Runnable() {
114       public void run() {
115         ApplicationManager.getApplication().runWriteAction(runnable);
116       }
117     });
118   }
119
120   /**
121    * Returns a temporary python file that will be deleted on exit.
122    * 
123    * Also all compiled version of the python file will be deleted.
124    * 
125    * @param base The basename of the file to copy
126    * @return The temporary copy the specified python file, with all the necessary hooks installed
127    * to make sure it is completely removed at shutdown
128    */
129   @Nullable
130   public static File getTemporaryPythonFile(String base) {
131     try {
132       final File file = copyResourceToTempFile(base, ".py");
133       final String fileName = file.getName();
134       ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
135         public void run() {
136           File[] files = file.getParentFile().listFiles(new FilenameFilter() {
137             public boolean accept(File dir, String name) {
138               return name.startsWith(fileName);
139             }
140           });
141           if (files != null) {
142             for (File file1 : files) {
143               file1.delete();
144             }
145           }
146         }
147       });
148       return file;
149     } catch (IOException e) {
150       return null;
151     }
152   }
153
154   /**
155    * Calls 'hg remove' to remove given files from the VCS.
156    * @param project
157    * @param files files to be removed from the VCS.
158    */
159   public static void removeFilesFromVcs(Project project, List<FilePath> files) {
160     final HgRemoveCommand command = new HgRemoveCommand(project);
161     for (FilePath filePath : files) {
162       final VirtualFile vcsRoot = VcsUtil.getVcsRootFor(project, filePath);
163       if (vcsRoot == null) {
164         continue;
165       }
166       command.execute(new HgFile(vcsRoot, filePath));
167     }
168   }
169
170
171   /**
172    * Finds the nearest parent directory which is an hg root.
173    * @param dir Directory which parent will be checked.
174    * @return Directory which is the nearest hg root being a parent of this directory,
175    * or <code>null</code> if this directory is not under hg.
176    * @see com.intellij.openapi.vcs.AbstractVcs#isVersionedDirectory(com.intellij.openapi.vfs.VirtualFile)
177    */
178   @Nullable
179   public static VirtualFile getNearestHgRoot(VirtualFile dir) {
180     VirtualFile currentDir = dir;
181     while (currentDir != null) {
182       if (isHgRoot(currentDir)) {
183         return currentDir;
184       }
185       currentDir = currentDir.getParent();
186     }
187     return null;
188   }
189
190   /**
191    * Checks if the given directory is an hg root.
192    */
193   public static boolean isHgRoot(VirtualFile dir) {
194     return dir.findChild(".hg") != null;
195   }
196
197   /**
198    * Gets the Mercurial root for the given file path or null if non exists:
199    * the root should not only be in directory mappings, but also the .hg repository folder should exist.
200    * @see #getHgRootOrThrow(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath)
201    */
202   @Nullable
203   public static VirtualFile getHgRootOrNull(Project project, FilePath filePath) {
204     return getNearestHgRoot(VcsUtil.getVcsRootFor(project, filePath));
205   }
206
207   /**
208    * Gets the Mercurial root for the given file path or null if non exists:
209    * the root should not only be in directory mappings, but also the .hg repository folder should exist.
210    * @see #getHgRootOrThrow(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath)
211    * @see #getHgRootOrNull(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath) 
212    */
213   @Nullable
214   public static VirtualFile getHgRootOrNull(Project project, VirtualFile file) {
215     return getHgRootOrNull(project, VcsUtil.getFilePath(file.getPath()));
216   }
217
218   /**
219    * Gets the Mercurial root for the given file path or throws a VcsException if non exists:
220    * the root should not only be in directory mappings, but also the .hg repository folder should exist.
221    * @see #getHgRootOrNull(com.intellij.openapi.project.Project, com.intellij.openapi.vcs.FilePath)
222    */
223   @NotNull
224   public static VirtualFile getHgRootOrThrow(Project project, FilePath filePath) throws VcsException {
225     final VirtualFile vf = getHgRootOrNull(project, filePath);
226     if (vf == null) {
227       throw new VcsException(HgVcsMessages.message("hg4idea.exception.file.not.under.hg", filePath.getPresentableUrl()));
228     }
229     return vf;
230   }
231
232   @NotNull
233   public static VirtualFile getHgRootOrThrow(Project project, VirtualFile file) throws VcsException {
234     return getHgRootOrThrow(project, VcsUtil.getFilePath(file.getPath()));
235   }
236
237   /**
238    * Checks is a merge operation is in progress on the given repository.
239    * Actually gets the number of parents of the current revision. If there are 2 parents, then a merge is going on. Otherwise there is
240    * only one parent. 
241    * @param project    project to work on.
242    * @param repository repository which is checked on merge.
243    * @return True if merge operation is in progress, false if there is no merge operation.
244    */
245   public static boolean isMergeInProgress(@NotNull Project project, VirtualFile repository) {
246     return new HgWorkingCopyRevisionsCommand(project).parents(repository).size() > 1;
247   }
248   /**
249    * Groups the given files by their Mercurial repositories and returns the map of relative paths to files for each repository.
250    * @param hgFiles files to be grouped.
251    * @return key is repository, values is the non-empty list of relative paths to files, which belong to this repository. 
252    */
253   @NotNull
254   public static Map<VirtualFile, List<String>> getRelativePathsByRepository(Collection<HgFile> hgFiles) {
255     final Map<VirtualFile, List<String>> map = new HashMap<VirtualFile, List<String>>();
256     if (hgFiles == null) {
257       return map;
258     }
259     for(HgFile file : hgFiles) {
260       final VirtualFile repo = file.getRepo();
261       List<String> files = map.get(repo);
262       if (files == null) {
263         files = new ArrayList<String>();
264         map.put(repo, files);
265       }
266       files.add(file.getRelativePath());
267     }
268     return map;
269   }
270 }