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]"
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
;
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
;
55 import java
.util
.logging
.Level
;
56 import java
.util
.regex
.Matcher
;
57 import java
.util
.regex
.Pattern
;
58 import javax
.swing
.JOptionPane
;
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
;
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
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
)
114 List
<String
> out
= new ArrayList
<String
>(list
.size());
115 for (String s
: list
) {
116 out
.add(replaceHttpPassword(s
));
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
132 pathEnv
= System
.getenv().get("Path");// NOI18N
134 pathEnv
= System
.getenv().get("path");// NOI18N
135 String pathSeparator
= System
.getProperty("path.separator");// NOI18N
136 if (pathEnv
== null || pathSeparator
== null)
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())
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
)
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("\\\\", "\\");
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
)
205 Git git
= Git
.getInstance();
207 if ((git
.getStatusCache().getStatus(file
).getStatus() & StatusInfo
.STATUS_VERSIONED_ADDEDLOCALLY
) != 0)
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
>());
229 File par
= file
.getParentFile();
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
;
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
281 public static VCSContext
getCurrentContext(Node
[] nodes
)
284 nodes
= TopComponent
.getRegistry().getActivatedNodes();
285 return VCSContext
.forNodes(nodes
);
289 * Returns path to repository root or null if not managed
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
304 * @return String of repository root path
306 public static File
getRootFile(VCSContext context
)
310 Git git
= Git
.getInstance();
311 File
[] files
= context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]);
312 if (files
== null || files
.length
== 0)
315 return git
.getTopmostManagedParent(files
[0]);
319 * Returns File object for Project Directory
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
)
333 File
[] files
= context
.getRootFiles().toArray(new File
[context
.getRootFiles().size()]);
335 for (File file
: files
) {
336 /* We may be committing a LocallyDeleted file */
338 file
= file
.getParentFile();
340 Project p
= FileOwnerQuery
.getOwner(FileUtil
.toFileObject(file
));
344 Git
.LOG
.log(Level
.FINE
, "GitUtils.getProjectFile(): No project for {0}", // NOI18N
350 public static File
getProjectFile(Project project
)
355 FileObject fo
= project
.getProjectDirectory();
356 return FileUtil
.toFile(fo
);
359 public static File
[] getProjectRootFiles(Project project
)
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
);
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
393 public static void forceStatusRefresh(File file
)
395 if (Git
.getInstance().isAdministrative(file
))
398 StatusCache cache
= Git
.getInstance().getStatusCache();
400 cache
.refreshCached(file
);
401 File repository
= Git
.getInstance().getTopmostManagedParent(file
);
402 if (repository
== null)
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.
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.
437 public static void forceStatusRefreshProject(VCSContext context
)
439 Project project
= getProject(context
);
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
))
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
)
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);
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:
498 * <li>revert package folder properties AND
499 * <li>revert all modified (including deleted) files in the folder
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
++) {
511 StatusInfo info
= cache
.getStatus(dir
);
512 if ((status
& info
.getStatus()) != 0)
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)
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
++) {
540 String path
= file
.getAbsolutePath();
541 if (GitModuleConfig
.getDefault().isExcludedFromCommit(path
) == false)
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
))
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
);
566 DataObject dao
= DataObject
.find(fo
);
567 return dao
.getCookie(EditorCookie
.class) == null;
568 } catch (DataObjectNotFoundException e
) {
569 // not found, continue
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
++) {
583 if (ch
< 32 && ch
!= '\t' && ch
!= '\n' && ch
!= '\r')
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"))
599 if (GitRepository
.REVISION_CURRENT
.equals(revision
))
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)
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
))
636 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_MERGE
))
638 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_DELETEDLOCALLY
))
640 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_REMOVEDLOCALLY
))
642 else if (0 != (status
& StatusInfo
.STATUS_NOTVERSIONED_NEWLOCALLY
))
644 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_COPIEDLOCALLY
))
646 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_ADDEDLOCALLY
))
648 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_MODIFIEDLOCALLY
))
650 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_REMOVEDINREPOSITORY
))
652 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_NEWINREPOSITORY
))
654 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_MODIFIEDINREPOSITORY
))
656 else if (0 != (status
& StatusInfo
.STATUS_VERSIONED_UPTODATE
))
658 else if (0 != (status
& StatusInfo
.STATUS_NOTVERSIONED_EXCLUDED
))
660 else if (0 != (status
& StatusInfo
.STATUS_NOTVERSIONED_NOTMANAGED
))
662 else if (status
== StatusInfo
.STATUS_UNKNOWN
)
665 throw new IllegalArgumentException("Uncomparable status: " + status
);
668 protected static int getFileEnabledStatus()
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('@');
690 return host
.substring(idx
+ 1);
694 * This utility class should not be instantiated anywhere.