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.
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.
41 package org
.nbgit
.util
;
43 import org
.nbgit
.OutputLogger
;
44 import org
.nbgit
.StatusInfo
;
46 import java
.io
.FileOutputStream
;
47 import java
.io
.IOException
;
48 import java
.io
.UnsupportedEncodingException
;
49 import java
.util
.ArrayList
;
50 import java
.util
.HashMap
;
51 import java
.util
.List
;
55 import org
.nbgit
.ui
.log
.RepositoryRevision
;
56 import org
.netbeans
.api
.queries
.SharabilityQuery
;
57 import org
.openide
.util
.Exceptions
;
58 import org
.spearce
.jgit
.lib
.Commit
;
59 import org
.spearce
.jgit
.lib
.Constants
;
60 import org
.spearce
.jgit
.lib
.GitIndex
;
61 import org
.spearce
.jgit
.lib
.GitIndex
.Entry
;
62 import org
.spearce
.jgit
.lib
.IndexDiff
;
63 import org
.spearce
.jgit
.lib
.ObjectId
;
64 import org
.spearce
.jgit
.lib
.ObjectLoader
;
65 import org
.spearce
.jgit
.lib
.ObjectWriter
;
66 import org
.spearce
.jgit
.lib
.PersonIdent
;
67 import org
.spearce
.jgit
.lib
.RefUpdate
;
68 import org
.spearce
.jgit
.lib
.Repository
;
69 import org
.spearce
.jgit
.lib
.Tree
;
70 import org
.spearce
.jgit
.lib
.TreeEntry
;
71 import org
.spearce
.jgit
.revwalk
.RevCommit
;
72 import org
.spearce
.jgit
.revwalk
.RevWalk
;
73 import org
.spearce
.jgit
.revwalk
.filter
.RevFilter
;
74 import org
.spearce
.jgit
.treewalk
.FileTreeIterator
;
75 import org
.spearce
.jgit
.treewalk
.TreeWalk
;
76 import org
.spearce
.jgit
.treewalk
.filter
.PathFilter
;
77 import org
.spearce
.jgit
.treewalk
.filter
.PathFilterGroup
;
82 public class GitCommand
{
84 static public void doAdd(File root
, List
<File
> addCandidates
, OutputLogger logger
) {
86 Repository repo
= Git
.getInstance().getRepository(root
);
87 GitIndex index
= repo
.getIndex();
89 for (File dstFile
: addCandidates
) {
90 Entry entry
= index
.add(root
, dstFile
);
91 entry
.setAssumeValid(false);
96 } catch (Exception ex
) {
97 logger
.output(ex
.getMessage());
101 public static void doMove(File root
, File srcFile
, File dstFile
, OutputLogger logger
) {
103 Repository repo
= Git
.getInstance().getRepository(root
);
104 GitIndex index
= repo
.getIndex();
105 Entry entry
= index
.add(root
, dstFile
);
107 entry
.setAssumeValid(false);
108 index
.remove(root
, srcFile
);
111 } catch (Exception ex
) {
112 logger
.output(ex
.getMessage());
116 public static void doCat(File root
, File base
, File tempFile
, String revision
) {
117 OutputLogger logger
= OutputLogger
.getLogger(root
.getAbsolutePath());
119 Repository repo
= Git
.getInstance().getRepository(root
);
120 Commit commit
= repo
.mapCommit(revision
);
122 if (commit
== null) {
123 logger
.output("Revision " + revision
+ " not found");
127 String relative
= toGitPath(getRelative(root
, base
));
129 ObjectId
[] ids
= {commit
.getTree().getId()};
130 TreeWalk walk
= TreeWalk
.forPath(repo
, relative
, ids
);
132 ObjectId blobId
= walk
.getObjectId(0);
134 ObjectLoader blob
= repo
.openBlob(blobId
);
136 logger
.output("Failed to open blob " + blobId
);
140 FileOutputStream out
= new FileOutputStream(tempFile
);
141 out
.write(blob
.getCachedBytes());
145 } catch (Exception ex
) {
146 // FIXME: Do unload, delete, ... here
147 logger
.output(ex
.getMessage());
151 public static void doRevert(File root
, List
<File
> files
, String revStr
, boolean doBackup
, OutputLogger logger
) {
152 Repository repo
= Git
.getInstance().getRepository(root
);
155 GitIndex index
= new GitIndex(repo
);
156 Tree tree
= repo
.mapTree(revStr
);
157 index
.readTree(tree
);
159 for (File file
: files
) {
160 String path
= getRelative(root
, file
);
161 Entry entry
= index
.getEntry(path
);
167 file
.renameTo(new File(file
.getAbsolutePath() + ".orig"));
169 index
.checkoutEntry(root
, entry
);
172 } catch (IOException ex
) {
176 public static RepositoryRevision
.Walk
getLogMessages(String rootPath
, Set
<File
> files
, String fromRevision
, String toRevision
, boolean showMerges
, OutputLogger logger
) {
177 File root
= new File(rootPath
);
178 Repository repo
= Git
.getInstance().getRepository(root
);
179 RepositoryRevision
.Walk walk
= new RepositoryRevision
.Walk(repo
);
182 if (fromRevision
== null) {
183 fromRevision
= Constants
.HEAD
;
185 ObjectId from
= repo
.resolve(fromRevision
);
189 walk
.markStart(walk
.parseCommit(from
));
190 ObjectId to
= toRevision
!= null ? repo
.resolve(toRevision
) : null;
192 walk
.markUninteresting(walk
.parseCommit(to
));
194 List
<PathFilter
> paths
= new ArrayList
<PathFilter
>();
195 for (File file
: files
) {
196 String path
= getRelative(root
, file
);
198 if (!(path
.length() == 0)) {
199 paths
.add(PathFilter
.create(path
));
203 if (!paths
.isEmpty()) {
204 walk
.setTreeFilter(PathFilterGroup
.create(paths
));
207 walk
.setRevFilter(RevFilter
.NO_MERGES
);
209 } catch (IOException ioe
) {
216 private static String
buildReflogMessage(String commitMessage
) {
217 String firstLine
= commitMessage
;
218 int newlineIndex
= commitMessage
.indexOf("\n");
220 if (newlineIndex
> 0) {
221 firstLine
= commitMessage
.substring(0, newlineIndex
);
223 return "\tcommit: " + firstLine
;
226 private static void prepareTrees(File root
, List
<File
> selectedItems
,
227 HashMap
<Repository
, Tree
> treeMap
)
228 throws IOException
, UnsupportedEncodingException
{
229 Repository repo
= Git
.getInstance().getRepository(root
);
231 for (File file
: selectedItems
) {
232 Tree projTree
= treeMap
.get(repo
);
233 if (projTree
== null) {
234 projTree
= repo
.mapTree("HEAD");
235 if (projTree
== null) {
236 projTree
= new Tree(repo
);
238 treeMap
.put(repo
, projTree
);
239 System
.out
.println("Orig tree id: " + projTree
.getId());
242 GitIndex index
= repo
.getIndex();
243 String repoRelativePath
= getRelative(root
, file
);
244 String string
= repoRelativePath
;
246 TreeEntry treeMember
= projTree
.findBlobMember(repoRelativePath
);
247 // we always want to delete it from the current tree, since if it's
248 // updated, we'll add it again
249 if (treeMember
!= null) {
252 Entry idxEntry
= index
.getEntry(string
);
253 if (true /* if modified on disk or missing */) {
254 if (!file
.isFile()) {
255 index
.remove(root
, file
);
257 System
.out
.println("Phantom file, so removing from index");
261 if (idxEntry
.update(file
)) {
266 if (idxEntry
!= null) {
267 projTree
.addFile(repoRelativePath
);
268 TreeEntry newMember
= projTree
.findBlobMember(repoRelativePath
);
270 newMember
.setId(idxEntry
.getObjectId());
271 System
.out
.println("New member id for " + repoRelativePath
+ ": " + newMember
.getId() + " idx id: " + idxEntry
.getObjectId());
276 private static void writeTreeWithSubTrees(Tree tree
) throws IOException
{
277 if (tree
.getId() == null) {
278 System
.out
.println("writing tree for: " + tree
.getFullName());
280 for (TreeEntry entry
: tree
.members()) {
281 if (entry
.isModified()) {
282 if (entry
instanceof Tree
) {
283 writeTreeWithSubTrees((Tree
) entry
);
285 System
.out
.println("BAD JUJU: " + entry
.getFullName());
289 ObjectWriter writer
= new ObjectWriter(tree
.getRepository());
290 tree
.setId(writer
.writeTree(tree
));
294 public static void doCommit(File root
, List
<File
> commitCandidates
, String message
, OutputLogger logger
) throws IOException
{
295 HashMap
<Repository
, Tree
> treeMap
= new HashMap
<Repository
, Tree
>();
297 prepareTrees(root
, commitCandidates
, treeMap
);
299 for (java
.util
.Map
.Entry
<Repository
, Tree
> entry
: treeMap
.entrySet()) {
301 Tree tree
= entry
.getValue();
302 Repository repo
= tree
.getRepository();
303 PersonIdent personIdent
= new PersonIdent(repo
);
305 writeTreeWithSubTrees(tree
);
307 final RefUpdate ru
= repo
.updateRef(Constants
.HEAD
);
308 ObjectId
[] parentIds
;
309 if (ru
.getOldObjectId() != null) {
310 parentIds
= new ObjectId
[]{ru
.getOldObjectId()};
312 parentIds
= new ObjectId
[0];
314 Commit commit
= new Commit(repo
, parentIds
);
315 commit
.setTree(tree
);
316 message
= message
.replaceAll("\r", "\n");
318 commit
.setMessage(message
);
319 commit
.setAuthor(personIdent
);
320 commit
.setCommitter(personIdent
);
322 ObjectWriter writer
= new ObjectWriter(repo
);
323 commit
.setCommitId(writer
.writeCommit(commit
));
325 ru
.setNewObjectId(commit
.getCommitId());
326 ru
.setRefLogMessage(buildReflogMessage(message
), false);
329 if (ru
.getOldObjectId() != null) {
330 ok
= ru
.getResult() == RefUpdate
.Result
.FAST_FORWARD
;
332 ok
= ru
.getResult() == RefUpdate
.Result
.NEW
;
335 logger
.output("Failed to update " + ru
.getName() + " to commit " + commit
.getCommitId() + ".");
340 public static void doRemove(File root
, List
<File
> deleteCandidates
, OutputLogger logger
) {
342 Repository repo
= Git
.getInstance().getRepository(root
);
343 GitIndex index
= repo
.getIndex();
344 boolean dirty
= false;
346 for (File srcFile
: deleteCandidates
) {
347 if (index
.remove(root
, srcFile
)) {
355 } catch (Exception ex
) {
356 logger
.output(ex
.getMessage());
361 public static void doRemove(File root
, File srcFile
, OutputLogger logger
) {
363 Repository repo
= Git
.getInstance().getRepository(root
);
364 GitIndex index
= repo
.getIndex();
366 if (index
.remove(root
, srcFile
)) {
369 } catch (Exception ex
) {
370 logger
.output(ex
.getMessage());
375 public static List
<String
[]> getRevisions(File root
, int limit
) {
376 return getRevisionsForFile(root
, null, limit
);
379 public static List
<String
[]> getRevisionsForFile(File root
, File
[] files
, int limit
) {
380 Repository repo
= Git
.getInstance().getRepository(root
);
381 RevWalk walk
= new RevWalk(repo
);
382 List
<String
[]> revs
= new ArrayList
<String
[]>();
385 ObjectId from
= repo
.resolve(Constants
.HEAD
);
389 walk
.markStart(walk
.parseCommit(from
));
392 List
<PathFilter
> paths
= new ArrayList
<PathFilter
>();
393 for (File file
: files
) {
394 String path
= getRelative(root
, file
);
396 if (!(path
.length() == 0)) {
397 paths
.add(PathFilter
.create(path
));
401 if (!paths
.isEmpty()) {
402 walk
.setTreeFilter(PathFilterGroup
.create(paths
));
406 for (RevCommit rev
: walk
) {
407 revs
.add(new String
[]{rev
.getShortMessage(), rev
.getId().name()});
413 } catch (IOException ioe
) {
419 private static String
getRelative(File root
, File dir
) {
420 return getRelative(root
.getAbsolutePath(), dir
.getAbsolutePath());
423 private static String
getRelative(String root
, String dir
) {
424 if (dir
.equals(root
)) {
427 return dir
.replace(root
+ File
.separator
, "");
430 private static String
toGitPath(String path
) {
431 return File
.separatorChar
== '/'
432 ? path
: path
.replace(File
.separator
, "/");
435 private static void put(Set
<String
> set
, String relPath
,
436 Map
<File
, StatusInfo
> files
, File root
, int status
) {
437 for (String path
: set
) {
438 if (relPath
.length() > 0 && !path
.startsWith(relPath
)) {
441 File file
= new File(root
, path
);
442 files
.put(file
, new StatusInfo(status
, null, false));
458 public static Map
<File
, StatusInfo
> getAllStatus(File root
, File dir
) throws IOException
{
459 String relPath
= getRelative(root
, dir
);
461 Repository repo
= Git
.getInstance().getRepository(root
);
462 Map
<File
, StatusInfo
> files
= new HashMap
<File
, StatusInfo
>();
465 repo
.refreshFromDisk();
466 IndexDiff index
= new IndexDiff(repo
);
469 put(index
.getAdded(), relPath
, files
, root
,
470 StatusInfo
.STATUS_VERSIONED_ADDEDLOCALLY
);
471 put(index
.getRemoved(), relPath
, files
, root
,
472 StatusInfo
.STATUS_VERSIONED_REMOVEDLOCALLY
);
473 put(index
.getMissing(), relPath
, files
, root
,
474 StatusInfo
.STATUS_VERSIONED_DELETEDLOCALLY
);
475 put(index
.getChanged(), relPath
, files
, root
,
476 StatusInfo
.STATUS_VERSIONED_MODIFIEDLOCALLY
);
477 put(index
.getModified(), relPath
, files
, root
,
478 StatusInfo
.STATUS_VERSIONED_MODIFIEDLOCALLY
);
480 final FileTreeIterator workTree
= new FileTreeIterator(repo
.getWorkDir());
481 final TreeWalk walk
= new TreeWalk(repo
);
483 walk
.reset(); // drop the first empty tree
484 walk
.setRecursive(true);
485 walk
.addTree(workTree
);
487 int share
= SharabilityQuery
.getSharability(dir
);
488 if (share
== SharabilityQuery
.NOT_SHARABLE
) {
491 while (walk
.next()) {
492 String path
= walk
.getPathString();
494 if (relPath
.length() > 0 && !path
.startsWith(relPath
)) {
497 if (index
.getAdded().contains(path
) ||
498 index
.getRemoved().contains(path
) ||
499 index
.getMissing().contains(path
) ||
500 index
.getChanged().contains(path
) ||
501 index
.getModified().contains(path
)) {
504 Entry entry
= repo
.getIndex().getEntry(path
);
505 File file
= new File(root
, path
);
509 status
= StatusInfo
.STATUS_VERSIONED_UPTODATE
;
511 if (share
== SharabilityQuery
.MIXED
&&
512 SharabilityQuery
.getSharability(file
) == SharabilityQuery
.NOT_SHARABLE
) {
515 status
= StatusInfo
.STATUS_NOTVERSIONED_NEWLOCALLY
;
518 files
.put(file
, new StatusInfo(status
, null, false));
521 } catch (IOException ex
) {
522 Exceptions
.printStackTrace(ex
);
536 public static Map
<File
, StatusInfo
> getInterestingStatus(File root
, File dir
) {
537 String relPath
= getRelative(root
, dir
);
539 Repository repo
= Git
.getInstance().getRepository(root
);
542 Map
<File
, StatusInfo
> files
= new HashMap
<File
, StatusInfo
>();
545 repo
.refreshFromDisk();
546 index
= new IndexDiff(repo
);
549 put(index
.getAdded(), relPath
, files
, root
,
550 StatusInfo
.STATUS_VERSIONED_ADDEDLOCALLY
);
551 put(index
.getRemoved(), relPath
, files
, root
,
552 StatusInfo
.STATUS_VERSIONED_REMOVEDLOCALLY
);
553 put(index
.getMissing(), relPath
, files
, root
,
554 StatusInfo
.STATUS_VERSIONED_DELETEDLOCALLY
);
555 put(index
.getChanged(), relPath
, files
, root
,
556 StatusInfo
.STATUS_VERSIONED_MODIFIEDLOCALLY
);
557 put(index
.getModified(), relPath
, files
, root
,
558 StatusInfo
.STATUS_VERSIONED_MODIFIEDLOCALLY
);
560 final FileTreeIterator workTree
= new FileTreeIterator(repo
.getWorkDir());
561 final TreeWalk walk
= new TreeWalk(repo
);
563 walk
.reset(); // drop the first empty tree
564 walk
.setRecursive(true);
565 walk
.addTree(workTree
);
567 while (walk
.next()) {
568 String path
= walk
.getPathString();
570 if (relPath
.length() > 0 && !path
.startsWith(relPath
)) {
573 if (index
.getAdded().contains(path
) ||
574 index
.getRemoved().contains(path
) ||
575 index
.getMissing().contains(path
) ||
576 index
.getChanged().contains(path
) ||
577 index
.getModified().contains(path
)) {
580 Entry entry
= repo
.getIndex().getEntry(path
);
584 int status
= StatusInfo
.STATUS_NOTVERSIONED_NEWLOCALLY
;
585 File file
= new File(root
, path
);
586 files
.put(file
, new StatusInfo(status
, null, false));
589 } catch (IOException ex
) {
590 Exceptions
.printStackTrace(ex
);
596 public static StatusInfo
getSingleStatus(File root
, File file
) {
597 Repository repo
= Git
.getInstance().getRepository(root
);
600 int share
= SharabilityQuery
.getSharability(file
.getParentFile());
601 if (share
== SharabilityQuery
.NOT_SHARABLE
||
602 (share
== SharabilityQuery
.MIXED
&&
603 SharabilityQuery
.getSharability(file
) == SharabilityQuery
.NOT_SHARABLE
)) {
604 return new StatusInfo(StatusInfo
.STATUS_NOTVERSIONED_EXCLUDED
, null, false);
606 int status
= StatusInfo
.STATUS_UNKNOWN
;
607 String name
= getRelative(root
, file
);
610 repo
.refreshFromDisk();
611 index
= new IndexDiff(repo
);
613 } catch (IOException ex
) {
614 Exceptions
.printStackTrace(ex
);
615 return new StatusInfo(status
, null, false);
618 if (index
.getAdded().contains(name
)) {
619 status
= StatusInfo
.STATUS_VERSIONED_ADDEDLOCALLY
;
620 } else if (index
.getRemoved().contains(name
)) {
621 status
= StatusInfo
.STATUS_VERSIONED_REMOVEDLOCALLY
;
622 } else if (index
.getMissing().contains(name
)) {
623 status
= StatusInfo
.STATUS_VERSIONED_DELETEDLOCALLY
;
624 } else if (index
.getChanged().contains(name
)) {
625 status
= StatusInfo
.STATUS_VERSIONED_MODIFIEDLOCALLY
;
626 } else if (index
.getModified().contains(name
)) {
627 status
= StatusInfo
.STATUS_VERSIONED_MODIFIEDLOCALLY
;
629 status
= StatusInfo
.STATUS_VERSIONED_UPTODATE
;
631 StatusInfo info
= new StatusInfo(status
, null, false);