~ubuntu-branches/ubuntu/trusty/netbeans/trusty

« back to all changes in this revision

Viewing changes to versioncontrol/mercurial/src/org/netbeans/modules/mercurial/util/HgUtils.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
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]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
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
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.modules.mercurial.util;
 
43
 
 
44
import java.io.BufferedWriter;
 
45
import java.io.BufferedReader;
 
46
import java.io.FileReader;
 
47
import java.io.File;
 
48
import java.io.FileOutputStream;
 
49
import java.io.IOException;
 
50
import java.io.OutputStreamWriter;
 
51
import java.io.PrintWriter;
 
52
import java.net.URL;
 
53
import java.util.List;
 
54
import java.util.Arrays;
 
55
import java.util.ArrayList;
 
56
import java.util.LinkedList;
 
57
import java.util.Collection;
 
58
import java.util.Map;
 
59
import java.util.Set;
 
60
import java.util.HashSet;
 
61
import java.util.Comparator;
 
62
import java.util.Iterator;
 
63
import java.util.regex.Pattern;
 
64
import org.netbeans.modules.mercurial.FileInformation;
 
65
import org.netbeans.modules.mercurial.FileStatusCache;
 
66
import org.netbeans.modules.mercurial.Mercurial;
 
67
import org.netbeans.modules.mercurial.HgModuleConfig;
 
68
import org.netbeans.modules.mercurial.HgException;
 
69
import org.netbeans.modules.mercurial.ui.status.SyncFileNode;
 
70
import org.openide.util.NbBundle;
 
71
 
 
72
import org.openide.loaders.DataObject;
 
73
import org.openide.filesystems.FileObject;
 
74
import org.openide.nodes.Node;
 
75
import org.openide.windows.OutputEvent;
 
76
import org.openide.windows.TopComponent;
 
77
import org.netbeans.modules.versioning.spi.VCSContext;
 
78
import org.openide.windows.IOProvider;
 
79
import org.openide.windows.InputOutput;
 
80
import org.openide.windows.OutputWriter;
 
81
import java.util.Calendar;
 
82
import java.util.HashMap;
 
83
import java.util.logging.Level;
 
84
import javax.swing.JOptionPane;
 
85
import org.openide.cookies.EditorCookie;
 
86
import org.openide.filesystems.FileUtil;
 
87
import org.openide.filesystems.FileLock;
 
88
import org.openide.loaders.DataObjectNotFoundException;
 
89
import org.netbeans.api.project.Project;
 
90
import org.netbeans.api.project.ProjectUtils;
 
91
import org.netbeans.api.project.Sources;
 
92
import org.netbeans.api.project.SourceGroup;
 
93
import org.netbeans.api.queries.SharabilityQuery;
 
94
import org.openide.awt.HtmlBrowser;
 
95
import org.openide.util.Utilities;
 
96
import org.openide.windows.OutputListener;
 
97
 
 
98
/**
 
99
 *
 
100
 * @author jrice
 
101
 */
 
102
public class HgUtils {    
 
103
    private static final Pattern metadataPattern = Pattern.compile(".*\\" + File.separatorChar + "(\\.)hg(\\" + File.separatorChar + ".*|$)"); // NOI18N
 
104
    
 
105
    // IGNORE SUPPORT HG: following file patterns are added to {Hg repos}/.hgignore and Hg will ignore any files
 
106
    // that match these patterns, reporting "I"status for them // NOI18N
 
107
    private static final String [] HG_IGNORE_FILES = { "\\.orig$", "\\.rej$"}; // NOI18N
 
108
    
 
109
    private static final String FILENAME_HGIGNORE = ".hgignore"; // NOI18N
 
110
 
 
111
    private static final int MAX_LINES_TO_PRINT = 500;
 
112
 
 
113
    private static final String MSG_TOO_MANY_LINES = "The number of output lines is greater than 500; see message log for complete output";
 
114
 
 
115
 
 
116
    /**
 
117
     * isSolaris - check you are running onthe Solaris OS
 
118
     *
 
119
     * @return boolean true - on Solaris, false - not on Solaris
 
120
     */
 
121
    public static boolean isSolaris(){
 
122
        return System.getProperty("os.name").equals("SunOS"); // NOI18N
 
123
    }
 
124
 
 
125
    /**
 
126
     * isInUserPath - check if passed in name is on the Users PATH environment setting
 
127
     *
 
128
     * @param name to check
 
129
     * @return boolean true - on PATH, false - not on PATH
 
130
     */
 
131
    public static boolean isInUserPath(String name) {
 
132
        String pathEnv = System.getenv().get("PATH");// NOI18N
 
133
        // Work around issues on Windows fetching PATH
 
134
        if(pathEnv == null) pathEnv = System.getenv().get("Path");// NOI18N
 
135
        if(pathEnv == null) pathEnv = System.getenv().get("path");// NOI18N
 
136
        String pathSeparator = System.getProperty("path.separator");// NOI18N
 
137
        if (pathEnv == null || pathSeparator == null) return false;
 
138
 
 
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 hgk.cmd use !isDirectory
 
143
            if (f.exists() && !f.isDirectory()) {
 
144
                return true;
 
145
            }
 
146
        }
 
147
        return false;
 
148
    }
 
149
 
 
150
    /**
 
151
     * confirmDialog - display a confirmation dialog
 
152
     *
 
153
     * @param bundleLocation location of string resources to display
 
154
     * @param title of dialog to display    
 
155
     * @param query ask user
 
156
     * @return boolean true - answered Yes, false - answered No
 
157
     */
 
158
    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);
 
160
 
 
161
        if (response == JOptionPane.YES_OPTION) {
 
162
            return true;
 
163
        } else {
 
164
            return false;
 
165
        }
 
166
    }
 
167
    
 
168
    /**
 
169
     * warningDialog - display a warning dialog
 
170
     *
 
171
     * @param bundleLocation location of string resources to display
 
172
     * @param title of dialog to display    
 
173
     * @param warning to display to the user
 
174
     */
 
175
     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);
 
180
    }
 
181
    
 
182
 
 
183
    /**
 
184
     * stripDoubleSlash - converts '\\' to '\' in path on Windows
 
185
     *
 
186
     * @param String path to convert
 
187
     * @return String converted path
 
188
     */
 
189
    public static String stripDoubleSlash(String path){
 
190
        if(Utilities.isWindows()){                       
 
191
            return path.replace("\\\\", "\\");
 
192
        }
 
193
        return path;
 
194
    }
 
195
    /**
 
196
     * isLocallyAdded - checks to see if this file has been Locally Added to Hg
 
197
     *
 
198
     * @param file to check
 
199
     * @return boolean true - ignore, false - not ignored
 
200
     */
 
201
    public static boolean isLocallyAdded(File file){
 
202
        if (file == null) return false;
 
203
        Mercurial hg = Mercurial.getInstance();        
 
204
 
 
205
        if ((hg.getFileStatusCache().getStatus(file).getStatus() & FileInformation.STATUS_VERSIONED_ADDEDLOCALLY) !=0)
 
206
            return true;
 
207
        else
 
208
            return false;
 
209
    }
 
210
    
 
211
    /**
 
212
     * isIgnored - checks to see if this is a file Hg should ignore
 
213
     *
 
214
     * @param File file to check
 
215
     * @return boolean true - ignore, false - not ignored
 
216
     */
 
217
    public static boolean isIgnored(File file){
 
218
        if (file == null) return true;
 
219
        String name = file.getName();
 
220
        
 
221
        Set<Pattern> patterns = new HashSet<Pattern>(5);
 
222
        addIgnorePatterns(patterns, Mercurial.getInstance().getTopmostManagedParent(file));
 
223
 
 
224
        for (Iterator i = patterns.iterator(); i.hasNext();) {
 
225
            Pattern pattern = (Pattern) i.next();
 
226
            if (pattern.matcher(name).matches()) {
 
227
                return true;
 
228
            }
 
229
        }
 
230
 
 
231
        if (FILENAME_HGIGNORE.equals(name)) return false;
 
232
        int sharability = SharabilityQuery.getSharability(file);
 
233
        if (sharability == SharabilityQuery.NOT_SHARABLE) return true;
 
234
        return false;
 
235
    }
 
236
 
 
237
    /**
 
238
     * createIgnored - creates .hgignore file in the repository in which 
 
239
     * the given file belongs. This .hgignore file ensures Hg will ignore 
 
240
     * the files specified in HG_IGNORE_FILES list
 
241
     *
 
242
     * @param path to repository to place .hgignore file
 
243
     */
 
244
    public static void createIgnored(File path){
 
245
        if( path == null) return;
 
246
        BufferedWriter fileWriter = null;
 
247
        Mercurial hg = Mercurial.getInstance();
 
248
        File root = hg.getTopmostManagedParent(path);
 
249
        if( root == null) return;
 
250
        File ignore = new File(root, FILENAME_HGIGNORE);
 
251
        if (ignore.exists()) return;
 
252
           
 
253
        try     {
 
254
            fileWriter = new BufferedWriter(
 
255
                    new OutputStreamWriter(new FileOutputStream(ignore)));
 
256
            for (String name : HG_IGNORE_FILES) {
 
257
                fileWriter.write(name + "\n"); // NOI18N
 
258
            }
 
259
        } catch (IOException ex) {
 
260
            Mercurial.LOG.log(Level.FINE, "createIgnored(): File {0} - {1}",  // NOI18N
 
261
                    new Object[] {ignore.getAbsolutePath(), ex.toString()});
 
262
        }finally {
 
263
            try {
 
264
                fileWriter.close();
 
265
                hg.getFileStatusCache().refresh(ignore, FileStatusCache.REPOSITORY_STATUS_UNKNOWN);
 
266
            } catch (IOException ex) {
 
267
                Mercurial.LOG.log(Level.FINE, "createIgnored(): File {0} - {1}",  // NOI18N
 
268
                        new Object[] {ignore.getAbsolutePath(), ex.toString()});
 
269
            }
 
270
        }
 
271
    }
 
272
 
 
273
    private static void addIgnorePatterns(Set<Pattern> patterns, File file) {
 
274
        Set<String> shPatterns;
 
275
        try {
 
276
            shPatterns = readIgnoreEntries(file);
 
277
        } catch (IOException e) {
 
278
            // ignore invalid entries
 
279
            return;
 
280
        }
 
281
        for (Iterator i = shPatterns.iterator(); i.hasNext();) {
 
282
            String shPattern = (String) i.next();
 
283
            if ("!".equals(shPattern)) { // NOI18N
 
284
                patterns.clear();
 
285
            } else {
 
286
                try {
 
287
                    patterns.add(Pattern.compile(shPattern));
 
288
                } catch (Exception e) {
 
289
                    // unsupported pattern
 
290
                }
 
291
            }
 
292
        }
 
293
    }
 
294
 
 
295
    private static Set<String> readIgnoreEntries(File directory) throws IOException {
 
296
        File hgIgnore = new File(directory, FILENAME_HGIGNORE);
 
297
 
 
298
        Set<String> entries = new HashSet<String>(5);
 
299
        if (!hgIgnore.canRead()) return entries;
 
300
 
 
301
        String s;
 
302
        BufferedReader r = null;
 
303
        try {
 
304
            r = new BufferedReader(new FileReader(hgIgnore));
 
305
            while ((s = r.readLine()) != null) {
 
306
                entries.addAll(Arrays.asList(s.trim().split(" ")));
 
307
            }
 
308
        } finally {
 
309
            if (r != null) try { r.close(); } catch (IOException e) {}
 
310
        }
 
311
        return entries;
 
312
    }
 
313
 
 
314
    private static String computePatternToIgnore(File directory, File file) {
 
315
        String name = file.getAbsolutePath().substring(directory.getAbsolutePath().length()+1);
 
316
        return name.replace(' ', '?').replace(File.separatorChar, '/');
 
317
    }
 
318
 
 
319
    private static void writeIgnoreEntries(File directory, Set entries) throws IOException {
 
320
        File hgIgnore = new File(directory, FILENAME_HGIGNORE);
 
321
        FileObject fo = FileUtil.toFileObject(hgIgnore);
 
322
 
 
323
        if (entries.size() == 0) {
 
324
            if (fo != null) fo.delete();
 
325
            return;
 
326
        }
 
327
 
 
328
        if (fo == null || !fo.isValid()) {
 
329
            fo = FileUtil.toFileObject(directory);
 
330
            fo = fo.createData(FILENAME_HGIGNORE);
 
331
        }
 
332
        FileLock lock = fo.lock();
 
333
        PrintWriter w = null;
 
334
        try {
 
335
            w = new PrintWriter(fo.getOutputStream(lock));
 
336
            for (Iterator i = entries.iterator(); i.hasNext();) {
 
337
                w.println(i.next());
 
338
            }
 
339
        } finally {
 
340
            lock.releaseLock();
 
341
            if (w != null) w.close();
 
342
        }
 
343
    }
 
344
 
 
345
    /**
 
346
     * addIgnored - Add the specified files to the .hgignore file in the 
 
347
     * specified repository.
 
348
     *
 
349
     * @param directory for repository for .hgignore file
 
350
     * @param files an array of Files to be added
 
351
     */
 
352
    public static void addIgnored(File directory, File[] files) throws IOException {
 
353
        Set<String> entries = readIgnoreEntries(directory);
 
354
        for (File file: files) {
 
355
            String patterntoIgnore = computePatternToIgnore(directory, file);
 
356
            entries.add(patterntoIgnore);
 
357
        }
 
358
        writeIgnoreEntries(directory, entries);
 
359
    }
 
360
 
 
361
    /**
 
362
     * removeIgnored - Remove the specified files from the .hgignore file in 
 
363
     * the specified repository.
 
364
     *
 
365
     * @param directory for repository for .hgignore file
 
366
     * @param files an array of Files to be removed
 
367
     */
 
368
    public static void removeIgnored(File directory, File[] files) throws IOException {
 
369
        Set entries = readIgnoreEntries(directory);
 
370
        for (File file: files) {
 
371
            String patterntoIgnore = computePatternToIgnore(directory, file);
 
372
            entries.remove(patterntoIgnore);
 
373
        }
 
374
        writeIgnoreEntries(directory, entries);
 
375
    }
 
376
 
 
377
    /**
 
378
     * Returns a Map keyed by Directory, containing a single File/FileInformation Map for each Directories file contents.
 
379
     *
 
380
     * @param Map of <File, FileInformation> interestingFiles to be processed and divided up into Files in Directory
 
381
     * @param Collection of <File> files to be processed against the interestingFiles
 
382
     * @return Map of Dirs containing Map of files and status for all files in each directory
 
383
     * @throws org.netbeans.modules.mercurial.HgException
 
384
     */
 
385
    public static Map<File, Map<File, FileInformation>> getInterestingDirs(Map<File, FileInformation> interestingFiles, Collection<File> files) {
 
386
        Map<File, Map<File, FileInformation>> interestingDirs = new HashMap<File, Map<File, FileInformation>>();
 
387
 
 
388
        Calendar start = Calendar.getInstance();
 
389
        for (File file : files) {
 
390
            if (file.isDirectory()) {
 
391
                if (interestingDirs.get(file) == null) {
 
392
                    interestingDirs.put(file, new HashMap<File, FileInformation>());
 
393
                }
 
394
            } else {
 
395
                File par = file.getParentFile();
 
396
                if (par != null) {
 
397
                    if (interestingDirs.get(par) == null) {
 
398
                        interestingDirs.put(par, new HashMap<File, FileInformation>());
 
399
                    }
 
400
                    FileInformation fi = interestingFiles.get(file);
 
401
                    interestingDirs.get(par).put(file, fi);
 
402
                }
 
403
            }
 
404
        }
 
405
        Calendar end = Calendar.getInstance();
 
406
        Mercurial.LOG.log(Level.FINE, "getInterestingDirs: process interesting Dirs took {0} millisecs",  // NOI18N
 
407
                end.getTimeInMillis() - start.getTimeInMillis());
 
408
        return interestingDirs;
 
409
    }
 
410
 
 
411
    /**
 
412
     * Semantics is similar to {@link org.openide.windows.TopComponent#getActivatedNodes()} except that this
 
413
     * method returns File objects instead of Nodes. Every node is examined for Files it represents. File and Folder
 
414
     * nodes represent their underlying files or folders. Project nodes are represented by their source groups. Other
 
415
     * logical nodes must provide FileObjects in their Lookup.
 
416
     *
 
417
     * @param nodes null (then taken from windowsystem, it may be wrong on editor tabs #66700).
 
418
     * @param includingFileStatus if any activated file does not have this CVS status, an empty array is returned
 
419
     * @param includingFolderStatus if any activated folder does not have this CVS status, an empty array is returned
 
420
     * @return File [] array of activated files, or an empty array if any of examined files/folders does not have given status
 
421
     */
 
422
    public static VCSContext getCurrentContext(Node[] nodes, int includingFileStatus, int includingFolderStatus) {
 
423
        VCSContext context = getCurrentContext(nodes);
 
424
        FileStatusCache cache = Mercurial.getInstance().getFileStatusCache();
 
425
        for (File file : context.getRootFiles()) {
 
426
            FileInformation fi = cache.getStatus(file);
 
427
            if (file.isDirectory()) {
 
428
                if ((fi.getStatus() & includingFolderStatus) == 0) return VCSContext.EMPTY;
 
429
            } else {
 
430
                if ((fi.getStatus() & includingFileStatus) == 0) return VCSContext.EMPTY;
 
431
            }
 
432
        }
 
433
        return context;
 
434
    }
 
435
 
 
436
    /**
 
437
     * Semantics is similar to {@link org.openide.windows.TopComponent#getActiva
 
438
tedNodes()} except that this
 
439
     * method returns File objects instead of Nodes. Every node is examined for
 
440
Files it represents. File and Folder
 
441
     * nodes represent their underlying files or folders. Project nodes are repr
 
442
esented by their source groups. Other
 
443
     * logical nodes must provide FileObjects in their Lookup.
 
444
     *
 
445
     * @return File [] array of activated files
 
446
     * @param nodes or null (then taken from windowsystem, it may be wrong on ed
 
447
itor tabs #66700).
 
448
     */
 
449
 
 
450
    public static VCSContext getCurrentContext(Node[] nodes) {
 
451
        if (nodes == null) {
 
452
            nodes = TopComponent.getRegistry().getActivatedNodes();
 
453
        }
 
454
        return VCSContext.forNodes(nodes);
 
455
    }
 
456
 
 
457
   /**
 
458
     * Returns path to repository root or null if not managed
 
459
     *
 
460
     * @param VCSContext
 
461
     * @return String of repository root path
 
462
     */
 
463
    public static String getRootPath(VCSContext context){
 
464
        File root = getRootFile(context);
 
465
        return (root == null) ? null: root.getAbsolutePath();
 
466
    }
 
467
    
 
468
   /**
 
469
     * Returns path to repository root or null if not managed
 
470
     *
 
471
     * @param VCSContext
 
472
     * @return String of repository root path
 
473
     */
 
474
    public static File getRootFile(VCSContext context){
 
475
        if (context == null) return null;
 
476
        Mercurial hg = Mercurial.getInstance();
 
477
        File [] files = context.getRootFiles().toArray(new File[context.getRootFiles().size()]);
 
478
        if (files == null || files.length == 0) return null;
 
479
        
 
480
        File root = hg.getTopmostManagedParent(files[0]);
 
481
        return root;
 
482
    }
 
483
    
 
484
   /**
 
485
     * Returns File object for Project Directory
 
486
     *
 
487
     * @param VCSContext
 
488
     * @return File object of Project Directory
 
489
     */
 
490
    public static File getProjectFile(VCSContext context){
 
491
        return getProjectFile(getProject(context));
 
492
    }
 
493
 
 
494
    public static Project getProject(VCSContext context){
 
495
        if (context == null) return null;
 
496
        Node [] nodes = context.getElements().lookupAll(Node.class).toArray(new Node[0]);
 
497
        for (Node node : nodes) {
 
498
            Node tmpNode = node;
 
499
 
 
500
            Project project = (Project) tmpNode.getLookup().lookup(Project.class);
 
501
            while (project == null) {
 
502
                tmpNode = tmpNode.getParentNode();
 
503
                if (tmpNode ==  null) {
 
504
                    Mercurial.LOG.log(Level.FINE, "HgUtils.getProjectFile(): No project for {0}",  // NOI18N
 
505
                        node.toString());
 
506
                    break;
 
507
                }
 
508
                project = (Project) tmpNode.getLookup().lookup(Project.class);
 
509
            } 
 
510
            if (project != null) {
 
511
                return project;
 
512
            }
 
513
        }
 
514
        return null;
 
515
    }
 
516
    
 
517
    public static File getProjectFile(Project project){
 
518
        if (project == null) return null;
 
519
 
 
520
        FileObject fo = project.getProjectDirectory();
 
521
        return  FileUtil.toFile(fo);
 
522
    }
 
523
 
 
524
    public static File[] getProjectRootFiles(Project project){
 
525
        if (project == null) return null;
 
526
        Set<File> set = new HashSet<File>();
 
527
 
 
528
        Sources sources = ProjectUtils.getSources(project);
 
529
        SourceGroup [] sourceGroups = sources.getSourceGroups(Sources.TYPE_GENERIC);
 
530
        for (int j = 0; j < sourceGroups.length; j++) {
 
531
            SourceGroup sourceGroup = sourceGroups[j];
 
532
            FileObject srcRootFo = sourceGroup.getRootFolder();
 
533
            File rootFile = FileUtil.toFile(srcRootFo);
 
534
            set.add(rootFile);
 
535
        }
 
536
        return set.toArray(new File[set.size()]);
 
537
    }
 
538
 
 
539
    /**
 
540
     * Checks file location to see if it is part of mercurial metdata
 
541
     *
 
542
     * @param file file to check
 
543
     * @return true if the file or folder is a part of mercurial metadata, false otherwise
 
544
     */
 
545
    public static boolean isPartOfMercurialMetadata(File file) {
 
546
        return metadataPattern.matcher(file.getAbsolutePath()).matches();
 
547
    }
 
548
    
 
549
 
 
550
    /**
 
551
     * Forces refresh of Status for the given directory 
 
552
     *
 
553
     * @param start file or dir to begin refresh from
 
554
     * @return void
 
555
     */
 
556
    public static void forceStatusRefresh(File file) {
 
557
        if (Mercurial.getInstance().isAdministrative(file)) return;
 
558
        
 
559
        try {
 
560
            FileStatusCache cache = Mercurial.getInstance().getFileStatusCache();
 
561
 
 
562
            cache.refreshCached(file);
 
563
            File repository = Mercurial.getInstance().getTopmostManagedParent(file);
 
564
            if (repository == null) {
 
565
                return;
 
566
            }
 
567
        
 
568
            if (file.isDirectory()) {
 
569
                Map<File, FileInformation> interestingFiles;
 
570
                interestingFiles = HgCommand.getInterestingStatus(repository, file);
 
571
                if (!interestingFiles.isEmpty()){
 
572
                    Collection<File> files = interestingFiles.keySet();
 
573
                    for (File aFile : files) {
 
574
                        FileInformation fi = interestingFiles.get(aFile);
 
575
                        cache.refreshFileStatus(aFile, fi, null);
 
576
                    }
 
577
                }
 
578
            }
 
579
 
 
580
        } catch (HgException ex) {
 
581
        }
 
582
    }
 
583
 
 
584
    /**
 
585
     * Forces refresh of Status for the specfied context.
 
586
     *
 
587
     * @param VCSContext context to be updated.
 
588
     * @return void
 
589
     */
 
590
    public static void forceStatusRefresh(VCSContext context) {
 
591
        for (File root :  context.getRootFiles()) {
 
592
            forceStatusRefresh(root);
 
593
        }
 
594
    }
 
595
 
 
596
    /**
 
597
     * Forces refresh of Status for the project of the specified context
 
598
     *
 
599
     * @param VCSContext ctx whose project is be updated.
 
600
     * @return void
 
601
     */
 
602
    public static void forceStatusRefreshProject(VCSContext context) {
 
603
        Project project = getProject(context);
 
604
        if (project == null) return;
 
605
        File[] files = getProjectRootFiles(project);
 
606
        for (int j = 0; j < files.length; j++) {
 
607
            forceStatusRefresh(files[j]);
 
608
        }
 
609
    }
 
610
 
 
611
    /**
 
612
     * Tests parent/child relationship of files.
 
613
     *
 
614
     * @param parent file to be parent of the second parameter
 
615
     * @param file file to be a child of the first parameter
 
616
     * @return true if the second parameter represents the same file as the first parameter OR is its descendant (child)
 
617
     */
 
618
    public static boolean isParentOrEqual(File parent, File file) {
 
619
        for (; file != null; file = file.getParentFile()) {
 
620
            if (file.equals(parent)) return true;
 
621
        }
 
622
        return false;
 
623
    }
 
624
    
 
625
    /**
 
626
     * Returns path of file relative to root repository or a warning message
 
627
     * if the file is not under the repository root.
 
628
     *
 
629
     * @param File to get relative path from the repository root
 
630
     * @return String of relative path of the file from teh repository root
 
631
     */
 
632
    public static String getRelativePath(File file) {
 
633
            if (file == null){
 
634
                return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository"); // NOI18N
 
635
            }
 
636
            String shortPath = file.getAbsolutePath();
 
637
            if (shortPath == null){
 
638
                return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository"); // NOI18N
 
639
            }
 
640
            
 
641
            Mercurial mercurial = Mercurial.getInstance();
 
642
            File rootManagedFolder = mercurial.getTopmostManagedParent(file);
 
643
            if ( rootManagedFolder == null){
 
644
                return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository"); // NOI18N
 
645
            }
 
646
            
 
647
            String root = rootManagedFolder.getAbsolutePath();
 
648
            if(shortPath.startsWith(root)) {
 
649
                return shortPath.substring(root.length()+1);
 
650
            }else{
 
651
                return NbBundle.getMessage(SyncFileNode.class, "LBL_Location_NotInRepository"); // NOI18N
 
652
            }
 
653
     }
 
654
 
 
655
    /**
 
656
     * Normalize flat files, Mercurial treats folder as normal file
 
657
     * so it's necessary explicitly list direct descendants to
 
658
     * get classical flat behaviour.
 
659
     *
 
660
     * <p> E.g. revert on package node means:
 
661
     * <ul>
 
662
     *   <li>revert package folder properties AND
 
663
     *   <li>revert all modified (including deleted) files in the folder
 
664
     * </ul>
 
665
     *
 
666
     * @return files with given status and direct descendants with given status.
 
667
     */
 
668
 
 
669
    public static File[] flatten(File[] files, int status) {
 
670
        LinkedList<File> ret = new LinkedList<File>();
 
671
 
 
672
        FileStatusCache cache = Mercurial.getInstance().getFileStatusCache();
 
673
        for (int i = 0; i<files.length; i++) {
 
674
            File dir = files[i];
 
675
            FileInformation info = cache.getStatus(dir);
 
676
            if ((status & info.getStatus()) != 0) {
 
677
                ret.add(dir);
 
678
            }
 
679
            File[] entries = cache.listFiles(dir);  // comparing to dir.listFiles() lists already deleted too
 
680
            for (int e = 0; e<entries.length; e++) {
 
681
                File entry = entries[e];
 
682
                info = cache.getStatus(entry);
 
683
                if ((status & info.getStatus()) != 0) {
 
684
                    ret.add(entry);
 
685
                }
 
686
            }
 
687
        }
 
688
 
 
689
        return ret.toArray(new File[ret.size()]);
 
690
    }
 
691
 
 
692
    /**
 
693
     * Utility method that returns all non-excluded modified files that are
 
694
     * under given roots (folders) and have one of specified statuses.
 
695
     *
 
696
     * @param context context to search
 
697
     * @param includeStatus bit mask of file statuses to include in result
 
698
     * @return File [] array of Files having specified status
 
699
     */
 
700
    public static File [] getModifiedFiles(VCSContext context, int includeStatus) {
 
701
        File[] all = Mercurial.getInstance().getFileStatusCache().listFiles(context, includeStatus);
 
702
        List<File> files = new ArrayList<File>();
 
703
        for (int i = 0; i < all.length; i++) {
 
704
            File file = all[i];
 
705
            String path = file.getAbsolutePath();
 
706
            if (HgModuleConfig.getDefault().isExcludedFromCommit(path) == false) {
 
707
                files.add(file);
 
708
            }
 
709
        }
 
710
 
 
711
        // ensure that command roots (files that were explicitly selected by user) are included in Diff
 
712
        FileStatusCache cache = Mercurial.getInstance().getFileStatusCache();
 
713
        for (File file : context.getRootFiles()) {
 
714
            if (file.isFile() && (cache.getStatus(file).getStatus() & includeStatus) != 0 && !files.contains(file)) {
 
715
                files.add(file);
 
716
            }
 
717
        }
 
718
        return files.toArray(new File[files.size()]);
 
719
    }
 
720
 
 
721
    /**
 
722
     * Checks if the file is binary.
 
723
     *
 
724
     * @param file file to check
 
725
     * @return true if the file cannot be edited in NetBeans text editor, false otherwise
 
726
     */
 
727
    public static boolean isFileContentBinary(File file) {
 
728
        FileObject fo = FileUtil.toFileObject(file);
 
729
        if (fo == null) return false;
 
730
        try {
 
731
            DataObject dao = DataObject.find(fo);
 
732
            return dao.getCookie(EditorCookie.class) == null;
 
733
        } catch (DataObjectNotFoundException e) {
 
734
            // not found, continue
 
735
        }
 
736
        return false;
 
737
    }
 
738
 
 
739
    /**
 
740
     * @return true if the buffer is almost certainly binary.
 
741
     * Note: Non-ASCII based encoding encoded text is binary,
 
742
     * newlines cannot be reliably detected.
 
743
     */
 
744
    public static boolean isBinary(byte[] buffer) {
 
745
        for (int i = 0; i<buffer.length; i++) {
 
746
            int ch = buffer[i];
 
747
            if (ch < 32 && ch != '\t' && ch != '\n' && ch != '\r') {
 
748
                return true;
 
749
            }
 
750
        }
 
751
        return false;
 
752
    }
 
753
 
 
754
    /**
 
755
     * Compares two {@link FileInformation} objects by importance of statuses they represent.
 
756
     */
 
757
    public static class ByImportanceComparator<T> implements Comparator<FileInformation> {
 
758
        public int compare(FileInformation i1, FileInformation i2) {
 
759
            return getComparableStatus(i1.getStatus()) - getComparableStatus(i2.getStatus());
 
760
        }
 
761
    }
 
762
 
 
763
    /**
 
764
     * Gets integer status that can be used in comparators. The more important the status is for the user,
 
765
     * the lower value it has. Conflict is 0, unknown status is 100.
 
766
     *
 
767
     * @return status constant suitable for 'by importance' comparators
 
768
     */
 
769
    public static int getComparableStatus(int status) {
 
770
        if (0 != (status & FileInformation.STATUS_VERSIONED_CONFLICT)) {
 
771
            return 0;
 
772
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_MERGE)) {
 
773
            return 1;
 
774
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_DELETEDLOCALLY)) {
 
775
            return 10;
 
776
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY)) {
 
777
            return 11;
 
778
       } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NEWLOCALLY)) {
 
779
            return 12;
 
780
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_ADDEDLOCALLY)) {
 
781
            return 13;
 
782
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDLOCALLY)) {
 
783
            return 14;
 
784
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_REMOVEDINREPOSITORY)) {
 
785
            return 30;
 
786
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_NEWINREPOSITORY)) {
 
787
            return 31;
 
788
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_MODIFIEDINREPOSITORY)) {
 
789
            return 32;
 
790
        } else if (0 != (status & FileInformation.STATUS_VERSIONED_UPTODATE)) {
 
791
            return 50;
 
792
        } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_EXCLUDED)){
 
793
            return 100;
 
794
        } else if (0 != (status & FileInformation.STATUS_NOTVERSIONED_NOTMANAGED)) {
 
795
            return 101;
 
796
        } else if (status == FileInformation.STATUS_UNKNOWN) {
 
797
            return 102;
 
798
        } else {
 
799
            throw new IllegalArgumentException("Uncomparable status: " + status); // NOI18N
 
800
        }
 
801
    }
 
802
 
 
803
    protected static int getFileEnabledStatus() {
 
804
        return ~0;
 
805
    }
 
806
 
 
807
    protected static int getDirectoryEnabledStatus() {
 
808
        return FileInformation.STATUS_MANAGED & ~FileInformation.STATUS_NOTVERSIONED_EXCLUDED;
 
809
    }
 
810
 
 
811
    /**
 
812
     * Rips an eventual username off - e.g. user@svn.host.org
 
813
     *
 
814
     * @param host - hostname with a userneame
 
815
     * @return host - hostname without the username
 
816
     */
 
817
    public static String ripUserFromHost(String host) {
 
818
        int idx = host.indexOf('@');
 
819
        if(idx < 0) {
 
820
            return host;
 
821
        } else {
 
822
            return host.substring(idx + 1);
 
823
        }
 
824
    }
 
825
 
 
826
 
 
827
    /**
 
828
     * Print contents of list to Mercurial Output Tab
 
829
     *
 
830
     * @param list to print out
 
831
     * 
 
832
     */
 
833
     public static void outputMercurialTab(List<String> list){
 
834
        if( list.isEmpty()) return;
 
835
 
 
836
        InputOutput io = IOProvider.getDefault().getIO(Mercurial.MERCURIAL_OUTPUT_TAB_TITLE, false);
 
837
        io.select();
 
838
        OutputWriter out = io.getOut();
 
839
        
 
840
        int lines = list.size();
 
841
        if (lines > MAX_LINES_TO_PRINT) {
 
842
            out.println(list.get(1));
 
843
            out.println(list.get(2));
 
844
            out.println(list.get(3));
 
845
            out.println("...");
 
846
            out.println(list.get(list.size() -1));
 
847
            out.println(MSG_TOO_MANY_LINES);
 
848
            for (String s : list){
 
849
                Mercurial.LOG.log(Level.WARNING, s);
 
850
            }
 
851
        } else {
 
852
            for (String s : list){
 
853
                out.println(s);
 
854
 
 
855
            }
 
856
        }
 
857
        out.close();
 
858
    }
 
859
 
 
860
     /**
 
861
     * Print msg to Mercurial Output Tab
 
862
     *
 
863
     * @param String msg to print out
 
864
     * 
 
865
     */
 
866
     public static void outputMercurialTab(String msg){
 
867
        if( msg == null) return;
 
868
 
 
869
        InputOutput io = IOProvider.getDefault().getIO(Mercurial.MERCURIAL_OUTPUT_TAB_TITLE, false);
 
870
        io.select();
 
871
        OutputWriter out = io.getOut();
 
872
        
 
873
        out.println(msg);
 
874
        out.close();
 
875
    }
 
876
 
 
877
    /**
 
878
     * Print msg to Mercurial Output Tab in Red
 
879
     *
 
880
     * @param String msg to print out
 
881
     * 
 
882
     */
 
883
     public static void outputMercurialTabInRed(String msg){
 
884
        if( msg == null) return;
 
885
 
 
886
        InputOutput io = IOProvider.getDefault().getIO(Mercurial.MERCURIAL_OUTPUT_TAB_TITLE, false);
 
887
        io.select();
 
888
        OutputWriter out = io.getErr();
 
889
        
 
890
        out.println(msg);
 
891
        out.close();
 
892
    }
 
893
 
 
894
    /**
 
895
     * Print URL to Mercurial Output Tab as an active Hyperlink
 
896
     *
 
897
     * @param String sURL to print out
 
898
     * 
 
899
     */
 
900
     public static void outputMercurialTabLink(final String sURL){
 
901
         if (sURL == null) return;
 
902
         
 
903
         try {
 
904
             InputOutput io = IOProvider.getDefault().getIO(Mercurial.MERCURIAL_OUTPUT_TAB_TITLE, false);
 
905
             io.select();
 
906
             OutputWriter out = io.getOut();
 
907
 
 
908
             OutputListener listener = new OutputListener() {
 
909
                         public void outputLineAction(OutputEvent ev) {
 
910
                             try {
 
911
                                 HtmlBrowser.URLDisplayer.getDefault().showURL(new URL(sURL));
 
912
                             } catch (IOException ex) {
 
913
                             // Ignore
 
914
                             }
 
915
                         }
 
916
                         public void outputLineSelected(OutputEvent ev) {}
 
917
                         public void outputLineCleared(OutputEvent ev) {}
 
918
                     };
 
919
             out.println(sURL, listener, true);
 
920
             out.close();
 
921
         } catch (IOException ex) {
 
922
         // Ignore
 
923
         }
 
924
     }
 
925
 
 
926
    /**
 
927
     * Select and Clear Mercurial Output Tab
 
928
     *
 
929
     * @param list to print out
 
930
     * 
 
931
     */
 
932
     public static void clearOutputMercurialTab(){
 
933
         InputOutput io = IOProvider.getDefault().getIO(
 
934
                 Mercurial.MERCURIAL_OUTPUT_TAB_TITLE, false);
 
935
         
 
936
         io.select();
 
937
         OutputWriter out = io.getOut();
 
938
         
 
939
         try {
 
940
             out.reset();
 
941
         } catch (IOException ex) {
 
942
             // Ignore Exception
 
943
         }
 
944
         out.close();
 
945
    }
 
946
     
 
947
    /**
 
948
     * This utility class should not be instantiated anywhere.
 
949
     */
 
950
    private HgUtils() {
 
951
    }
 
952
    
 
953
}