Cleanup code whitespace
[nbgit.git] / src / org / nbgit / util / GitUtils.java
blobe029e32d4b042f1ed553a4b0bc796743ce7720a2
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common
8 * Development and Distribution License("CDDL") (collectively, the
9 * "License"). You may not use this file except in compliance with the
10 * License. You can obtain a copy of the License at
11 * http://www.netbeans.org/cddl-gplv2.html
12 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13 * specific language governing permissions and limitations under the
14 * License. When distributing the software, include this License Header
15 * Notice in each file and include the License file at
16 * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17 * particular file as subject to the "Classpath" exception as provided
18 * by Sun in the GPL Version 2 section of the License file that
19 * accompanied this code. If applicable, add the following below the
20 * License Header, with the fields enclosed by brackets [] replaced by
21 * your own identifying information:
22 * "Portions Copyrighted [year] [name of copyright owner]"
24 * Contributor(s):
26 * The Original Software is NetBeans. The Initial Developer of the Original
27 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28 * Microsystems, Inc. All Rights Reserved.
29 * Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions).
31 * If you wish your version of this file to be governed by only the CDDL
32 * or only the GPL Version 2, indicate your decision by adding
33 * "[Contributor] elects to include this software in this distribution
34 * under the [CDDL or GPL Version 2] license." If you do not indicate a
35 * single choice of license, a recipient has the option to distribute
36 * your version of this file under either the CDDL, the GPL Version 2 or
37 * to extend the choice of license to its licensees as provided above.
38 * However, if you add GPL Version 2 code and therefore, elected the GPL
39 * Version 2 license, then the option applies only if the new code is
40 * made subject to such option by the copyright holder.
42 package org.nbgit.util;
44 import java.io.File;
45 import java.io.IOException;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.Comparator;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.LinkedList;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Set;
55 import java.util.logging.Level;
56 import java.util.regex.Matcher;
57 import java.util.regex.Pattern;
58 import javax.swing.JOptionPane;
59 import org.nbgit.Git;
60 import org.nbgit.GitRepository;
61 import org.nbgit.StatusCache;
62 import org.nbgit.GitModuleConfig;
63 import org.nbgit.StatusInfo;
64 import org.nbgit.ui.status.SyncFileNode;
65 import org.netbeans.api.project.FileOwnerQuery;
66 import org.netbeans.api.project.Project;
67 import org.netbeans.api.project.ProjectUtils;
68 import org.netbeans.api.project.SourceGroup;
69 import org.netbeans.api.project.Sources;
70 import org.netbeans.modules.versioning.spi.VCSContext;
71 import org.openide.cookies.EditorCookie;
72 import org.openide.filesystems.FileObject;
73 import org.openide.filesystems.FileUtil;
74 import org.openide.loaders.DataObject;
75 import org.openide.loaders.DataObjectNotFoundException;
76 import org.openide.nodes.Node;
77 import org.openide.util.NbBundle;
78 import org.openide.util.Utilities;
79 import org.openide.windows.TopComponent;
80 import org.spearce.jgit.lib.Constants;
82 /**
84 * @author jrice
85 * @author alexbcoles
87 public class GitUtils {
89 private static final Pattern httpPasswordPattern = Pattern.compile("(https*://)(\\w+\\b):(\\b\\S*)@"); //NOI18N
90 private static final String httpPasswordReplacementStr = "$1$2:\\*\\*\\*\\*@"; //NOI18N
91 private static final Pattern metadataPattern = Pattern.compile(".*\\" + File.separatorChar + "(\\.)git(\\" + File.separatorChar + ".*|$)"); // NOI18N
93 /**
94 * replaceHttpPassword - replace any http or https passwords in the string
96 * @return String modified string with **** instead of passwords
98 public static String replaceHttpPassword(String s)
100 Matcher m = httpPasswordPattern.matcher(s);
101 return m.replaceAll(httpPasswordReplacementStr);
105 * replaceHttpPassword - replace any http or https passwords in the List<String>
107 * @return List<String> containing modified strings with **** instead of passwords
109 public static List<String> replaceHttpPassword(List<String> list)
111 if (list == null)
112 return null;
114 List<String> out = new ArrayList<String>(list.size());
115 for (String s : list) {
116 out.add(replaceHttpPassword(s));
118 return out;
122 * isInUserPath - check if passed in name is on the Users PATH environment setting
124 * @param name to check
125 * @return boolean true - on PATH, false - not on PATH
127 public static boolean isInUserPath(String name)
129 String pathEnv = System.getenv().get("PATH");// NOI18N
130 // Work around issues on Windows fetching PATH
131 if (pathEnv == null)
132 pathEnv = System.getenv().get("Path");// NOI18N
133 if (pathEnv == null)
134 pathEnv = System.getenv().get("path");// NOI18N
135 String pathSeparator = System.getProperty("path.separator");// NOI18N
136 if (pathEnv == null || pathSeparator == null)
137 return false;
139 String[] paths = pathEnv.split(pathSeparator);
140 for (String path : paths) {
141 File f = new File(path, name);
142 // On Windows isFile will fail on gitk.cmd use !isDirectory
143 if (f.exists() && !f.isDirectory())
144 return true;
146 return false;
150 * confirmDialog - display a confirmation dialog
152 * @param bundleLocation location of string resources to display
153 * @param title of dialog to display
154 * @param query ask user
155 * @return boolean true - answered Yes, false - answered No
157 public static boolean confirmDialog(Class bundleLocation, String title, String query)
159 int response = JOptionPane.showOptionDialog(null, NbBundle.getMessage(bundleLocation, query), NbBundle.getMessage(bundleLocation, title), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null);
161 if (response == JOptionPane.YES_OPTION)
162 return true;
163 else
164 return false;
168 * warningDialog - display a warning dialog
170 * @param bundleLocation location of string resources to display
171 * @param title of dialog to display
172 * @param warning to display to the user
174 public static void warningDialog(Class bundleLocation, String title, String warning)
176 JOptionPane.showMessageDialog(null,
177 NbBundle.getMessage(bundleLocation, warning),
178 NbBundle.getMessage(bundleLocation, title),
179 JOptionPane.WARNING_MESSAGE);
183 * stripDoubleSlash - converts '\\' to '\' in path on Windows
185 * @param String path to convert
186 * @return String converted path
188 public static String stripDoubleSlash(String path)
190 if (Utilities.isWindows())
191 return path.replace("\\\\", "\\");
192 return path;
196 * isLocallyAdded - checks to see if this file has been Locally Added to Git
198 * @param file to check
199 * @return boolean true - ignore, false - not ignored
201 public static boolean isLocallyAdded(File file)
203 if (file == null)
204 return false;
205 Git git = Git.getInstance();
207 if ((git.getStatusCache().getStatus(file).getStatus() & StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY) != 0)
208 return true;
209 else
210 return false;
214 * Returns a Map keyed by Directory, containing a single File/StatusInfo Map for each Directories file contents.
216 * @param Map of <File, StatusInfo> interestingFiles to be processed and divided up into Files in Directory
217 * @param Collection of <File> files to be processed against the interestingFiles
218 * @return Map of Dirs containing Map of files and status for all files in each directory
220 public static Map<File, Map<File, StatusInfo>> getInterestingDirs(Map<File, StatusInfo> interestingFiles, Collection<File> files)
222 Map<File, Map<File, StatusInfo>> interestingDirs = new HashMap<File, Map<File, StatusInfo>>();
224 for (File file : files) {
225 if (file.isDirectory()) {
226 if (interestingDirs.get(file) == null)
227 interestingDirs.put(file, new HashMap<File, StatusInfo>());
228 } else {
229 File par = file.getParentFile();
230 if (par != null) {
231 if (interestingDirs.get(par) == null)
232 interestingDirs.put(par, new HashMap<File, StatusInfo>());
233 StatusInfo fi = interestingFiles.get(file);
234 interestingDirs.get(par).put(file, fi);
239 return interestingDirs;
243 * Semantics is similar to {@link org.openide.windows.TopComponent#getActivatedNodes()} except that this
244 * method returns File objects instead of Nodes. Every node is examined for Files it represents. File and Folder
245 * nodes represent their underlying files or folders. Project nodes are represented by their source groups. Other
246 * logical nodes must provide FileObjects in their Lookup.
248 * @param nodes null (then taken from windowsystem, it may be wrong on editor tabs #66700).
249 * @param includingFileStatus if any activated file does not have this CVS status, an empty array is returned
250 * @param includingFolderStatus if any activated folder does not have this CVS status, an empty array is returned
251 * @return File [] array of activated files, or an empty array if any of examined files/folders does not have given status
253 public static VCSContext getCurrentContext(Node[] nodes, int includingFileStatus, int includingFolderStatus)
255 VCSContext context = getCurrentContext(nodes);
256 StatusCache cache = Git.getInstance().getStatusCache();
257 for (File file : context.getRootFiles()) {
258 StatusInfo fi = cache.getStatus(file);
259 if (file.isDirectory()) {
260 if ((fi.getStatus() & includingFolderStatus) == 0)
261 return VCSContext.EMPTY;
262 } else if ((fi.getStatus() & includingFileStatus) == 0)
263 return VCSContext.EMPTY;
265 return context;
269 * Semantics is similar to {@link org.openide.windows.TopComponent#getActiva
270 tedNodes()} except that this
271 * method returns File objects instead of Nodes. Every node is examined for
272 Files it represents. File and Folder
273 * nodes represent their underlying files or folders. Project nodes are repr
274 esented by their source groups. Other
275 * logical nodes must provide FileObjects in their Lookup.
277 * @return File [] array of activated files
278 * @param nodes or null (then taken from windowsystem, it may be wrong on ed
279 itor tabs #66700).
281 public static VCSContext getCurrentContext(Node[] nodes)
283 if (nodes == null)
284 nodes = TopComponent.getRegistry().getActivatedNodes();
285 return VCSContext.forNodes(nodes);
289 * Returns path to repository root or null if not managed
291 * @param VCSContext
292 * @return String of repository root path
294 public static String getRootPath(VCSContext context)
296 File root = getRootFile(context);
297 return (root == null) ? null : root.getAbsolutePath();
301 * Returns path to repository root or null if not managed
303 * @param VCSContext
304 * @return String of repository root path
306 public static File getRootFile(VCSContext context)
308 if (context == null)
309 return null;
310 Git git = Git.getInstance();
311 File[] files = context.getRootFiles().toArray(new File[context.getRootFiles().size()]);
312 if (files == null || files.length == 0)
313 return null;
315 return git.getTopmostManagedParent(files[0]);
319 * Returns File object for Project Directory
321 * @param VCSContext
322 * @return File object of Project Directory
324 public static File getProjectFile(VCSContext context)
326 return getProjectFile(getProject(context));
329 public static Project getProject(VCSContext context)
331 if (context == null)
332 return null;
333 File[] files = context.getRootFiles().toArray(new File[context.getRootFiles().size()]);
335 for (File file : files) {
336 /* We may be committing a LocallyDeleted file */
337 if (!file.exists())
338 file = file.getParentFile();
340 Project p = FileOwnerQuery.getOwner(FileUtil.toFileObject(file));
341 if (p != null)
342 return p;
343 else
344 Git.LOG.log(Level.FINE, "GitUtils.getProjectFile(): No project for {0}", // NOI18N
345 file);
347 return null;
350 public static File getProjectFile(Project project)
352 if (project == null)
353 return null;
355 FileObject fo = project.getProjectDirectory();
356 return FileUtil.toFile(fo);
359 public static File[] getProjectRootFiles(Project project)
361 if (project == null)
362 return null;
363 Set<File> set = new HashSet<File>();
365 Sources sources = ProjectUtils.getSources(project);
366 SourceGroup[] sourceGroups = sources.getSourceGroups(Sources.TYPE_GENERIC);
367 for (int j = 0; j < sourceGroups.length; j++) {
368 SourceGroup sourceGroup = sourceGroups[j];
369 FileObject srcRootFo = sourceGroup.getRootFolder();
370 File rootFile = FileUtil.toFile(srcRootFo);
371 set.add(rootFile);
373 return set.toArray(new File[set.size()]);
377 * Checks file location to see if it is part of Git metdata
379 * @param file file to check
380 * @return true if the file or folder is a part of Git metadata, false otherwise
382 public static boolean isPartOfGitMetadata(File file)
384 return metadataPattern.matcher(file.getAbsolutePath()).matches();
388 * Forces refresh of Status for the given directory
390 * @param start file or dir to begin refresh from
391 * @return void
393 public static void forceStatusRefresh(File file)
395 if (Git.getInstance().isAdministrative(file))
396 return;
398 StatusCache cache = Git.getInstance().getStatusCache();
400 cache.refreshCached(file);
401 File repository = Git.getInstance().getTopmostManagedParent(file);
402 if (repository == null)
403 return;
405 if (file.isDirectory()) {
406 Map<File, StatusInfo> interestingFiles;
407 interestingFiles = GitCommand.getInterestingStatus(repository, file);
408 if (!interestingFiles.isEmpty()) {
409 Collection<File> files = interestingFiles.keySet();
410 for (File aFile : files) {
411 StatusInfo fi = interestingFiles.get(aFile);
412 cache.refreshFileStatus(aFile, fi, null);
419 * Forces refresh of Status for the specfied context.
421 * @param VCSContext context to be updated.
422 * @return void
424 public static void forceStatusRefresh(VCSContext context)
426 for (File root : context.getRootFiles()) {
427 forceStatusRefresh(root);
432 * Forces refresh of Status for the project of the specified context
434 * @param VCSContext ctx whose project is be updated.
435 * @return void
437 public static void forceStatusRefreshProject(VCSContext context)
439 Project project = getProject(context);
440 if (project == null)
441 return;
442 File[] files = getProjectRootFiles(project);
443 for (int j = 0; j < files.length; j++) {
444 forceStatusRefresh(files[j]);
449 * Tests parent/child relationship of files.
451 * @param parent file to be parent of the second parameter
452 * @param file file to be a child of the first parameter
453 * @return true if the second parameter represents the same file as the first parameter OR is its descendant (child)
455 public static boolean isParentOrEqual(File parent, File file)
457 for (; file != null; file = file.getParentFile()) {
458 if (file.equals(parent))
459 return true;
461 return false;
465 * Returns path of file relative to root repository or a warning message
466 * if the file is not under the repository root.
468 * @param File to get relative path from the repository root
469 * @return String of relative path of the file from teh repository root
471 public static String getRelativePath(File file)
473 if (file == null)
474 return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository");
475 String shortPath = file.getAbsolutePath();
476 if (shortPath == null)
477 return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository");
479 Git git = Git.getInstance();
480 File rootManagedFolder = git.getTopmostManagedParent(file);
481 if (rootManagedFolder == null)
482 return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository");
484 String root = rootManagedFolder.getAbsolutePath();
485 if (shortPath.startsWith(root))
486 return shortPath.substring(root.length() + 1);
487 else
488 return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository");
492 * Normalize flat files, Git treats folder as normal file
493 * so it's necessary explicitly list direct descendants to
494 * get classical flat behaviour.
496 * <p> E.g. revert on package node means:
497 * <ul>
498 * <li>revert package folder properties AND
499 * <li>revert all modified (including deleted) files in the folder
500 * </ul>
502 * @return files with given status and direct descendants with given status.
504 public static File[] flatten(File[] files, int status)
506 LinkedList<File> ret = new LinkedList<File>();
508 StatusCache cache = Git.getInstance().getStatusCache();
509 for (int i = 0; i < files.length; i++) {
510 File dir = files[i];
511 StatusInfo info = cache.getStatus(dir);
512 if ((status & info.getStatus()) != 0)
513 ret.add(dir);
514 File[] entries = cache.listFiles(dir); // comparing to dir.listFiles() lists already deleted too
515 for (int e = 0; e < entries.length; e++) {
516 File entry = entries[e];
517 info = cache.getStatus(entry);
518 if ((status & info.getStatus()) != 0)
519 ret.add(entry);
523 return ret.toArray(new File[ret.size()]);
527 * Utility method that returns all non-excluded modified files that are
528 * under given roots (folders) and have one of specified statuses.
530 * @param context context to search
531 * @param includeStatus bit mask of file statuses to include in result
532 * @return File [] array of Files having specified status
534 public static File[] getModifiedFiles(VCSContext context, int includeStatus)
536 File[] all = Git.getInstance().getStatusCache().listFiles(context, includeStatus);
537 List<File> files = new ArrayList<File>();
538 for (int i = 0; i < all.length; i++) {
539 File file = all[i];
540 String path = file.getAbsolutePath();
541 if (GitModuleConfig.getDefault().isExcludedFromCommit(path) == false)
542 files.add(file);
545 // ensure that command roots (files that were explicitly selected by user) are included in Diff
546 StatusCache cache = Git.getInstance().getStatusCache();
547 for (File file : context.getRootFiles()) {
548 if (file.isFile() && (cache.getStatus(file).getStatus() & includeStatus) != 0 && !files.contains(file))
549 files.add(file);
551 return files.toArray(new File[files.size()]);
555 * Checks if the file is binary.
557 * @param file file to check
558 * @return true if the file cannot be edited in NetBeans text editor, false otherwise
560 public static boolean isFileContentBinary(File file)
562 FileObject fo = FileUtil.toFileObject(file);
563 if (fo == null)
564 return false;
565 try {
566 DataObject dao = DataObject.find(fo);
567 return dao.getCookie(EditorCookie.class) == null;
568 } catch (DataObjectNotFoundException e) {
569 // not found, continue
571 return false;
575 * @return true if the buffer is almost certainly binary.
576 * Note: Non-ASCII based encoding encoded text is binary,
577 * newlines cannot be reliably detected.
579 public static boolean isBinary(byte[] buffer)
581 for (int i = 0; i < buffer.length; i++) {
582 int ch = buffer[i];
583 if (ch < 32 && ch != '\t' && ch != '\n' && ch != '\r')
584 return true;
586 return false;
590 * Loads the file in specified revision.
592 * @return null if the file does not exist in given revision
594 public static File getFileRevision(File base, String revision) throws IOException
596 if (revision.equals("-1"))
597 return null;
599 if (GitRepository.REVISION_CURRENT.equals(revision))
600 return base;
602 if (GitRepository.REVISION_BASE.equals(revision))
603 revision = Constants.HEAD;
605 File tempFile = File.createTempFile("tmp", "-" + base.getName()); //NOI18N
606 File repository = Git.getInstance().getTopmostManagedParent(base);
608 GitCommand.doCat(repository, base, tempFile, revision);
609 if (tempFile.length() == 0)
610 return null;
611 return tempFile;
615 * Compares two {@link StatusInfo} objects by importance of statuses they represent.
617 public static class ByImportanceComparator<T> implements Comparator<StatusInfo> {
619 public int compare(StatusInfo i1, StatusInfo i2)
621 return getComparableStatus(i1.getStatus()) - getComparableStatus(i2.getStatus());
627 * Gets integer status that can be used in comparators. The more important the status is for the user,
628 * the lower value it has. Conflict is 0, unknown status is 100.
630 * @return status constant suitable for 'by importance' comparators
632 public static int getComparableStatus(int status)
634 if (0 != (status & StatusInfo.STATUS_VERSIONED_CONFLICT))
635 return 0;
636 else if (0 != (status & StatusInfo.STATUS_VERSIONED_MERGE))
637 return 1;
638 else if (0 != (status & StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY))
639 return 10;
640 else if (0 != (status & StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY))
641 return 11;
642 else if (0 != (status & StatusInfo.STATUS_NOTVERSIONED_NEWLOCALLY))
643 return 12;
644 else if (0 != (status & StatusInfo.STATUS_VERSIONED_COPIEDLOCALLY))
645 return 13;
646 else if (0 != (status & StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY))
647 return 14;
648 else if (0 != (status & StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY))
649 return 15;
650 else if (0 != (status & StatusInfo.STATUS_VERSIONED_REMOVEDINREPOSITORY))
651 return 30;
652 else if (0 != (status & StatusInfo.STATUS_VERSIONED_NEWINREPOSITORY))
653 return 31;
654 else if (0 != (status & StatusInfo.STATUS_VERSIONED_MODIFIEDINREPOSITORY))
655 return 32;
656 else if (0 != (status & StatusInfo.STATUS_VERSIONED_UPTODATE))
657 return 50;
658 else if (0 != (status & StatusInfo.STATUS_NOTVERSIONED_EXCLUDED))
659 return 100;
660 else if (0 != (status & StatusInfo.STATUS_NOTVERSIONED_NOTMANAGED))
661 return 101;
662 else if (status == StatusInfo.STATUS_UNKNOWN)
663 return 102;
664 else
665 throw new IllegalArgumentException("Uncomparable status: " + status);
668 protected static int getFileEnabledStatus()
670 return ~0;
673 protected static int getDirectoryEnabledStatus()
675 return StatusInfo.STATUS_MANAGED & ~StatusInfo.STATUS_NOTVERSIONED_EXCLUDED;
679 * Rips an eventual username off - e.g. user@svn.host.org
681 * @param host - hostname with a userneame
682 * @return host - hostname without the username
684 public static String ripUserFromHost(String host)
686 int idx = host.indexOf('@');
687 if (idx < 0)
688 return host;
689 else
690 return host.substring(idx + 1);
694 * This utility class should not be instantiated anywhere.
696 private GitUtils()