Improve performance and remove race conditions during commit
[nbjgit.git] / src / org / gitscm / nbjgit / util / GitCommand.java
blob3eef7c451df9fcd3b42165401ec5ae1ad5222dae
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.
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.gitscm.nbjgit.util;
43 import org.gitscm.nbjgit.OutputLogger;
44 import org.gitscm.nbjgit.StatusInfo;
45 import java.io.File;
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;
52 import java.util.Map;
53 import java.util.Set;
54 import org.gitscm.nbjgit.Git;
55 import org.gitscm.nbjgit.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;
79 /**
82 public class GitCommand {
84 public static void deleteConflictFile(String absolutePath)
86 // boolean success = (new File(path + GIT_STR_CONFLICT_EXT)).delete();
87 throw new UnsupportedOperationException("Not yet implemented");
90 static public void doAdd(File root, List<File> addCandidates, OutputLogger logger)
92 try {
93 Repository repo = Git.getInstance().getRepository(root);
94 GitIndex index = repo.getIndex();
96 for (File dstFile : addCandidates) {
97 Entry entry = index.add(root, dstFile);
98 entry.setAssumeValid(false);
101 index.write();
103 } catch (Exception ex) {
104 logger.output(ex.getMessage());
108 public static void doAdd(File root, File dstFile, OutputLogger logger)
110 try {
111 Repository repo = Git.getInstance().getRepository(root);
112 GitIndex index = repo.getIndex();
113 Entry entry = index.add(root, dstFile);
115 entry.setAssumeValid(false);
116 index.write();
118 } catch (Exception ex) {
119 logger.output(ex.getMessage());
123 public static void doCat(File root, File base, File tempFile, String revision)
125 OutputLogger logger = Git.getInstance().getLogger(root.getAbsolutePath());
126 try {
127 Repository repo = Git.getInstance().getRepository(root);
128 Commit commit = repo.mapCommit(revision);
130 if (commit == null) {
131 logger.output("Revision " + revision + " not found");
132 return;
135 String relative = toGitPath(getRelative(root, base));
137 ObjectId[] ids = { commit.getTree().getId() };
138 TreeWalk walk = TreeWalk.forPath(repo, relative, ids);
140 ObjectId blobId = walk.getObjectId(0);
142 ObjectLoader blob = repo.openBlob(blobId);
143 if (blob == null) {
144 logger.output("Failed to open blob " + blobId);
145 return;
148 FileOutputStream out = new FileOutputStream(tempFile);
149 out.write(blob.getCachedBytes());
151 out.close();
153 } catch (Exception ex) {
154 // FIXME: Do unload, delete, ... here
155 logger.output(ex.getMessage());
159 public static void doRevert(File root, List<File> files, String revStr, boolean doBackup, OutputLogger logger)
161 Repository repo = Git.getInstance().getRepository(root);
163 try {
164 GitIndex index = new GitIndex(repo);
165 Tree tree = repo.mapTree(revStr);
166 index.readTree(tree);
168 for (File file : files) {
169 String path = getRelative(root, file);
170 Entry entry = index.getEntry(path);
172 if (entry == null)
173 continue;
174 if (doBackup)
175 file.renameTo(new File(file.getAbsolutePath() + ".orig"));
176 index.checkoutEntry(root, entry);
179 } catch (IOException ex) {
183 public static List<String> doUpdateAll(File root, boolean doForcedUpdate, String revStr)
185 throw new UnsupportedOperationException("Not yet implemented");
188 public static RepositoryRevision.Walk getLogMessages(String rootPath, Set<File> files, String fromRevision, String toRevision, boolean showMerges, OutputLogger logger)
190 File root = new File(rootPath);
191 Repository repo = Git.getInstance().getRepository(root);
192 RepositoryRevision.Walk walk = new RepositoryRevision.Walk(repo);
194 try {
195 if (fromRevision == null)
196 fromRevision = Constants.HEAD;
197 ObjectId from = repo.resolve(fromRevision);
198 if (from == null)
199 return null;
200 walk.markStart(walk.parseCommit(from));
201 ObjectId to = toRevision != null ? repo.resolve(toRevision) : null;
202 if (to != null)
203 walk.markUninteresting(walk.parseCommit(to));
205 List<PathFilter> paths = new ArrayList<PathFilter>();
206 for (File file : files) {
207 String path = getRelative(root, file);
209 if (!path.isEmpty())
210 paths.add(PathFilter.create(path));
213 if (!paths.isEmpty())
214 walk.setTreeFilter(PathFilterGroup.create(paths));
215 if (!showMerges)
216 walk.setRevFilter(RevFilter.NO_MERGES);
218 } catch (IOException ioe) {
219 return null;
222 return walk;
225 public static boolean isNoUpdates(String get)
227 throw new UnsupportedOperationException("Not yet implemented");
230 private static String buildReflogMessage(String commitMessage)
232 String firstLine = commitMessage;
233 int newlineIndex = commitMessage.indexOf("\n");
235 if (newlineIndex > 0)
236 firstLine = commitMessage.substring(0, newlineIndex);
237 return "\tcommit: " + firstLine;
240 private static void prepareTrees(File root, List<File> selectedItems,
241 HashMap<Repository, Tree> treeMap)
242 throws IOException, UnsupportedEncodingException
244 Repository repo = Git.getInstance().getRepository(root);
246 for (File file : selectedItems) {
247 Tree projTree = treeMap.get(repo);
248 if (projTree == null) {
249 projTree = repo.mapTree("HEAD");
250 if (projTree == null)
251 projTree = new Tree(repo);
252 treeMap.put(repo, projTree);
253 System.out.println("Orig tree id: " + projTree.getId());
256 GitIndex index = repo.getIndex();
257 String repoRelativePath = getRelative(root, file);
258 String string = repoRelativePath;
260 TreeEntry treeMember = projTree.findBlobMember(repoRelativePath);
261 // we always want to delete it from the current tree, since if it's
262 // updated, we'll add it again
263 if (treeMember != null)
264 treeMember.delete();
265 Entry idxEntry = index.getEntry(string);
266 if (true /* if modified on disk or missing */) {
267 if (!file.isFile()) {
268 index.remove(root, file);
269 index.write();
270 System.out.println("Phantom file, so removing from index");
271 continue;
274 if (idxEntry.update(file))
275 index.write();
278 if (idxEntry != null) {
279 projTree.addFile(repoRelativePath);
280 TreeEntry newMember = projTree.findBlobMember(repoRelativePath);
282 newMember.setId(idxEntry.getObjectId());
283 System.out.println("New member id for " + repoRelativePath + ": " + newMember.getId() + " idx id: " + idxEntry.getObjectId());
288 private static void writeTreeWithSubTrees(Tree tree) throws IOException
290 if (tree.getId() == null) {
291 System.out.println("writing tree for: " + tree.getFullName());
293 for (TreeEntry entry : tree.members()) {
294 if (entry.isModified())
295 if (entry instanceof Tree)
296 writeTreeWithSubTrees((Tree) entry);
297 else
298 System.out.println("BAD JUJU: " + entry.getFullName());
300 ObjectWriter writer = new ObjectWriter(tree.getRepository());
301 tree.setId(writer.writeTree(tree));
305 public static void doCommit(File root, List<File> commitCandidates, String message, OutputLogger logger) throws IOException
307 HashMap<Repository, Tree> treeMap = new HashMap<Repository, Tree>();
309 prepareTrees(root, commitCandidates, treeMap);
311 for (java.util.Map.Entry<Repository, Tree> entry : treeMap.entrySet()) {
313 Tree tree = entry.getValue();
314 Repository repo = tree.getRepository();
315 PersonIdent personIdent = new PersonIdent(repo);
317 writeTreeWithSubTrees(tree);
319 final RefUpdate ru = repo.updateRef(Constants.HEAD);
320 ObjectId[] parentIds;
321 if (ru.getOldObjectId() != null)
322 parentIds = new ObjectId[] { ru.getOldObjectId() };
323 else
324 parentIds = new ObjectId[0];
325 Commit commit = new Commit(repo, parentIds);
326 commit.setTree(tree);
327 message = message.replaceAll("\r", "\n");
329 commit.setMessage(message);
330 commit.setAuthor(personIdent);
331 commit.setCommitter(personIdent);
333 ObjectWriter writer = new ObjectWriter(repo);
334 commit.setCommitId(writer.writeCommit(commit));
336 ru.setNewObjectId(commit.getCommitId());
337 ru.setRefLogMessage(buildReflogMessage(message), false);
338 ru.update();
339 boolean ok;
340 if (ru.getOldObjectId() != null)
341 ok = ru.getResult() == RefUpdate.Result.FAST_FORWARD;
342 else
343 ok = ru.getResult() == RefUpdate.Result.NEW;
345 if (!ok)
346 logger.output("Failed to update " + ru.getName() + " to commit " + commit.getCommitId() + ".");
350 public static void doRemove(File root, List<File> deleteCandidates, OutputLogger logger)
352 try {
353 Repository repo = Git.getInstance().getRepository(root);
354 GitIndex index = repo.getIndex();
355 boolean dirty = false;
357 for (File srcFile : deleteCandidates) {
358 if (index.remove(root, srcFile))
359 dirty = true;
362 if (dirty)
363 index.write();
364 } catch (Exception ex) {
365 logger.output(ex.getMessage());
370 public static void doRemove(File root, File srcFile, OutputLogger logger)
372 try {
373 Repository repo = Git.getInstance().getRepository(root);
374 GitIndex index = repo.getIndex();
376 if (index.remove(root, srcFile))
377 index.write();
378 } catch (Exception ex) {
379 logger.output(ex.getMessage());
384 public static void doRenameAfter(File root, File srcFile, File dstFile, OutputLogger logger)
386 throw new UnsupportedOperationException("Not yet implemented");
389 public static List<String[]> getRevisions(File root, int limit)
391 return getRevisionsForFile(root, null, limit);
394 public static List<String[]> getRevisionsForFile(File root, File[] files, int limit)
396 Repository repo = Git.getInstance().getRepository(root);
397 RevWalk walk = new RevWalk(repo);
398 List<String[]> revs = new ArrayList<String[]>();
400 try {
401 ObjectId from = repo.resolve(Constants.HEAD);
402 if (from == null)
403 return null;
404 walk.markStart(walk.parseCommit(from));
406 if (files != null) {
407 List<PathFilter> paths = new ArrayList<PathFilter>();
408 for (File file : files) {
409 String path = getRelative(root, file);
411 if (!path.isEmpty())
412 paths.add(PathFilter.create(path));
415 if (!paths.isEmpty())
416 walk.setTreeFilter(PathFilterGroup.create(paths));
419 for (RevCommit rev : walk) {
420 revs.add(new String[] { rev.getShortMessage(), rev.getId().toString() });
421 if (--limit <= 0)
422 break;
425 } catch (IOException ioe) {
428 return revs;
431 private static String getRelative(File root, File dir)
433 return getRelative(root.getAbsolutePath(), dir.getAbsolutePath());
436 private static String getRelative(String root, String dir)
438 if (dir.equals(root))
439 return "";
440 return dir.replace(root + File.separator, "");
443 private static String toGitPath(String path)
445 return File.separatorChar == '/'
446 ? path : path.replace(File.separator, "/");
449 private static void put(Set<String> set, String relPath,
450 Map<File, StatusInfo> files, File root, int status)
452 for (String path : set) {
453 if (relPath.length() > 0 && !path.startsWith(relPath))
454 continue;
455 File file = new File(root, path);
456 files.put(file, new StatusInfo(status, null, false));
462 * m odified
463 * a dded
464 * r emoved
465 * d eleted
466 * u nknown
467 * C opies
469 * i gnored
470 * c lean
472 public static Map<File, StatusInfo> getAllStatus(File root, File dir) throws IOException
474 String relPath = getRelative(root, dir);
475 OutputLogger logger = Git.getInstance().getLogger(root.getAbsolutePath());
477 Repository repo = Git.getInstance().getRepository(root);
478 Map<File, StatusInfo> files = new HashMap<File, StatusInfo>();
480 try {
481 repo.refreshFromDisk();
482 IndexDiff index = new IndexDiff(repo);
483 index.diff();
485 put(index.getAdded(), relPath, files, root,
486 StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY);
487 put(index.getRemoved(), relPath, files, root,
488 StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY);
489 put(index.getMissing(), relPath, files, root,
490 StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY);
491 put(index.getChanged(), relPath, files, root,
492 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
493 put(index.getModified(), relPath, files, root,
494 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
496 final FileTreeIterator workTree = new FileTreeIterator(repo.getWorkDir());
497 final TreeWalk walk = new TreeWalk(repo);
499 walk.reset(); // drop the first empty tree
500 walk.setRecursive(true);
501 walk.addTree(workTree);
503 int share = SharabilityQuery.getSharability(dir);
504 if (share == SharabilityQuery.NOT_SHARABLE)
505 return files;
507 while (walk.next()) {
508 String path = walk.getPathString();
510 if (relPath.length() > 0 && !path.startsWith(relPath))
511 continue;
512 if (index.getAdded().contains(path) ||
513 index.getRemoved().contains(path) ||
514 index.getMissing().contains(path) ||
515 index.getChanged().contains(path) ||
516 index.getModified().contains(path))
517 continue;
518 Entry entry = repo.getIndex().getEntry(path);
519 File file = new File(root, path);
521 int status;
522 if (entry != null)
523 status = StatusInfo.STATUS_VERSIONED_UPTODATE;
524 else {
525 if (share == SharabilityQuery.MIXED &&
526 SharabilityQuery.getSharability(file) == SharabilityQuery.NOT_SHARABLE)
527 continue;
529 status = StatusInfo.STATUS_NOTVERSIONED_NEWLOCALLY;
532 files.put(file, new StatusInfo(status, null, false));
535 } catch (IOException ex) {
536 Exceptions.printStackTrace(ex);
539 return files;
543 * m odified
544 * a dded
545 * r emoved
546 * d eleted
547 * u nknown
548 * C opies
550 public static Map<File, StatusInfo> getInterestingStatus(File root, File dir)
552 String relPath = getRelative(root, dir);
554 Repository repo = Git.getInstance().getRepository(root);
555 IndexDiff index;
557 Map<File, StatusInfo> files = new HashMap<File, StatusInfo>();
559 try {
560 repo.refreshFromDisk();
561 index = new IndexDiff(repo);
562 index.diff();
564 put(index.getAdded(), relPath, files, root,
565 StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY);
566 put(index.getRemoved(), relPath, files, root,
567 StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY);
568 put(index.getMissing(), relPath, files, root,
569 StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY);
570 put(index.getChanged(), relPath, files, root,
571 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
572 put(index.getModified(), relPath, files, root,
573 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
575 final FileTreeIterator workTree = new FileTreeIterator(repo.getWorkDir());
576 final TreeWalk walk = new TreeWalk(repo);
578 walk.reset(); // drop the first empty tree
579 walk.setRecursive(true);
580 walk.addTree(workTree);
582 while (walk.next()) {
583 String path = walk.getPathString();
585 if (relPath.length() > 0 && !path.startsWith(relPath))
586 continue;
587 if (index.getAdded().contains(path) ||
588 index.getRemoved().contains(path) ||
589 index.getMissing().contains(path) ||
590 index.getChanged().contains(path) ||
591 index.getModified().contains(path))
592 continue;
593 Entry entry = repo.getIndex().getEntry(path);
594 if (entry != null)
595 continue;
596 int status = StatusInfo.STATUS_NOTVERSIONED_NEWLOCALLY;
597 File file = new File(root, path);
598 files.put(file, new StatusInfo(status, null, false));
601 } catch (IOException ex) {
602 Exceptions.printStackTrace(ex);
605 return files;
608 public static StatusInfo getSingleStatus(File root, File file)
610 OutputLogger logger = Git.getInstance().getLogger(root.getAbsolutePath());
611 Repository repo = Git.getInstance().getRepository(root);
612 IndexDiff index;
614 int share = SharabilityQuery.getSharability(file.getParentFile());
615 if (share == SharabilityQuery.NOT_SHARABLE ||
616 (share == SharabilityQuery.MIXED &&
617 SharabilityQuery.getSharability(file) == SharabilityQuery.NOT_SHARABLE))
618 return new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_EXCLUDED, null, false);
620 int status = StatusInfo.STATUS_UNKNOWN;
621 String name = getRelative(root, file);
623 try {
624 repo.refreshFromDisk();
625 index = new IndexDiff(repo);
626 index.diff();
627 } catch (IOException ex) {
628 Exceptions.printStackTrace(ex);
629 return new StatusInfo(status, null, false);
632 if (index.getAdded().contains(name))
633 status = StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY;
634 else if (index.getRemoved().contains(name))
635 status = StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY;
636 else if (index.getMissing().contains(name))
637 status = StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY;
638 else if (index.getChanged().contains(name))
639 status = StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY;
640 else if (index.getModified().contains(name))
641 status = StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY;
642 else
643 status = StatusInfo.STATUS_VERSIONED_UPTODATE;
644 StatusInfo info = new StatusInfo(status, null, false);
646 return info;