Issue 20: Introduce CommitBuilder utility for creating commits
[nbgit.git] / src / org / nbgit / util / GitCommand.java
blobd2620b7914fb13c64f994f753544d4b8e4b0d602
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.nbgit.util;
43 import org.nbgit.OutputLogger;
44 import org.nbgit.StatusInfo;
45 import java.io.File;
46 import java.io.IOException;
47 import java.io.UnsupportedEncodingException;
48 import java.util.ArrayList;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.Map;
52 import java.util.Set;
53 import org.nbgit.Git;
54 import org.nbgit.ui.log.RepositoryRevision;
55 import org.netbeans.api.queries.SharabilityQuery;
56 import org.openide.util.Exceptions;
57 import org.spearce.jgit.lib.Commit;
58 import org.spearce.jgit.lib.Constants;
59 import org.spearce.jgit.lib.GitIndex;
60 import org.spearce.jgit.lib.GitIndex.Entry;
61 import org.spearce.jgit.lib.IndexDiff;
62 import org.spearce.jgit.lib.ObjectId;
63 import org.spearce.jgit.lib.ObjectWriter;
64 import org.spearce.jgit.lib.PersonIdent;
65 import org.spearce.jgit.lib.RefUpdate;
66 import org.spearce.jgit.lib.Repository;
67 import org.spearce.jgit.lib.Tree;
68 import org.spearce.jgit.lib.TreeEntry;
69 import org.spearce.jgit.revwalk.RevCommit;
70 import org.spearce.jgit.revwalk.RevWalk;
71 import org.spearce.jgit.revwalk.filter.RevFilter;
72 import org.spearce.jgit.treewalk.FileTreeIterator;
73 import org.spearce.jgit.treewalk.TreeWalk;
74 import org.spearce.jgit.treewalk.filter.PathFilter;
75 import org.spearce.jgit.treewalk.filter.PathFilterGroup;
77 /**
80 public class GitCommand {
82 public static RepositoryRevision.Walk getLogMessages(String rootPath, Set<File> files, String fromRevision, String toRevision, boolean showMerges, OutputLogger logger) {
83 File root = new File(rootPath);
84 Repository repo = Git.getInstance().getRepository(root);
85 RepositoryRevision.Walk walk = new RepositoryRevision.Walk(repo);
87 try {
88 if (fromRevision == null) {
89 fromRevision = Constants.HEAD;
91 ObjectId from = repo.resolve(fromRevision);
92 if (from == null) {
93 return null;
95 walk.markStart(walk.parseCommit(from));
96 ObjectId to = toRevision != null ? repo.resolve(toRevision) : null;
97 if (to != null) {
98 walk.markUninteresting(walk.parseCommit(to));
100 List<PathFilter> paths = new ArrayList<PathFilter>();
101 for (File file : files) {
102 String path = getRelative(root, file);
104 if (!(path.length() == 0)) {
105 paths.add(PathFilter.create(path));
109 if (!paths.isEmpty()) {
110 walk.setTreeFilter(PathFilterGroup.create(paths));
112 if (!showMerges) {
113 walk.setRevFilter(RevFilter.NO_MERGES);
115 } catch (IOException ioe) {
116 return null;
119 return walk;
122 private static String buildReflogMessage(String commitMessage) {
123 String firstLine = commitMessage;
124 int newlineIndex = commitMessage.indexOf("\n");
126 if (newlineIndex > 0) {
127 firstLine = commitMessage.substring(0, newlineIndex);
129 return "\tcommit: " + firstLine;
132 private static Tree prepareTree(File root, List<File> selectedItems)
133 throws IOException, UnsupportedEncodingException {
134 Repository repo = Git.getInstance().getRepository(root);
135 Tree projTree = repo.mapTree("HEAD");
136 if (projTree == null)
137 projTree = new Tree(repo);
139 for (File file : selectedItems) {
140 GitIndex index = repo.getIndex();
141 String repoRelativePath = getRelative(root, file);
142 String string = repoRelativePath;
144 TreeEntry treeMember = projTree.findBlobMember(repoRelativePath);
145 // we always want to delete it from the current tree, since if it's
146 // updated, we'll add it again
147 if (treeMember != null) {
148 treeMember.delete();
150 Entry idxEntry = index.getEntry(string);
151 if (true /* if modified on disk or missing */) {
152 if (!file.isFile()) {
153 index.remove(root, file);
154 index.write();
155 System.out.println("Phantom file, so removing from index");
156 continue;
159 if (idxEntry.update(file)) {
160 index.write();
164 if (idxEntry != null) {
165 projTree.addFile(repoRelativePath);
166 TreeEntry newMember = projTree.findBlobMember(repoRelativePath);
168 newMember.setId(idxEntry.getObjectId());
169 System.out.println("New member id for " + repoRelativePath + ": " + newMember.getId() + " idx id: " + idxEntry.getObjectId());
173 return projTree;
176 private static void writeTreeWithSubTrees(Tree tree) throws IOException {
177 if (tree.getId() == null) {
178 System.out.println("writing tree for: " + tree.getFullName());
180 for (TreeEntry entry : tree.members()) {
181 if (entry.isModified()) {
182 if (entry instanceof Tree) {
183 writeTreeWithSubTrees((Tree) entry);
184 } else {
185 System.out.println("BAD JUJU: " + entry.getFullName());
189 ObjectWriter writer = new ObjectWriter(tree.getRepository());
190 tree.setId(writer.writeTree(tree));
194 public static void doCommit(File root, List<File> commitCandidates, String message, OutputLogger logger) throws IOException {
195 Tree tree = prepareTree(root, commitCandidates);
197 Repository repo = tree.getRepository();
198 PersonIdent personIdent = new PersonIdent(repo);
200 writeTreeWithSubTrees(tree);
201 doCommit(repo, tree.getTreeId(), personIdent, message, logger);
204 public static void doCommit(Repository repo, ObjectId treeId, PersonIdent personIdent, String message, OutputLogger logger) throws IOException {
205 final RefUpdate ru = repo.updateRef(Constants.HEAD);
206 ObjectId[] parentIds;
207 if (ru.getOldObjectId() != null) {
208 parentIds = new ObjectId[]{ru.getOldObjectId()};
209 } else {
210 parentIds = new ObjectId[0];
212 Commit commit = new Commit(repo, parentIds);
213 commit.setTreeId(treeId);
214 message = message.replaceAll("\r", "\n");
216 commit.setMessage(message);
217 commit.setAuthor(personIdent);
218 commit.setCommitter(personIdent);
220 ObjectWriter writer = new ObjectWriter(repo);
221 commit.setCommitId(writer.writeCommit(commit));
223 ru.setNewObjectId(commit.getCommitId());
224 ru.setRefLogMessage(buildReflogMessage(message), false);
225 ru.update();
226 boolean ok;
227 if (ru.getOldObjectId() != null) {
228 ok = ru.getResult() == RefUpdate.Result.FAST_FORWARD;
229 } else {
230 ok = ru.getResult() == RefUpdate.Result.NEW;
232 if (!ok) {
233 logger.output("Failed to update " + ru.getName() + " to commit " + commit.getCommitId() + ".");
237 public static List<String[]> getRevisions(File root, int limit) {
238 return getRevisionsForFile(root, null, limit);
241 public static List<String[]> getRevisionsForFile(File root, File[] files, int limit) {
242 Repository repo = Git.getInstance().getRepository(root);
243 RevWalk walk = new RevWalk(repo);
244 List<String[]> revs = new ArrayList<String[]>();
246 try {
247 ObjectId from = repo.resolve(Constants.HEAD);
248 if (from == null) {
249 return null;
251 walk.markStart(walk.parseCommit(from));
253 if (files != null) {
254 List<PathFilter> paths = new ArrayList<PathFilter>();
255 for (File file : files) {
256 String path = getRelative(root, file);
258 if (!(path.length() == 0)) {
259 paths.add(PathFilter.create(path));
263 if (!paths.isEmpty()) {
264 walk.setTreeFilter(PathFilterGroup.create(paths));
268 for (RevCommit rev : walk) {
269 revs.add(new String[]{rev.getShortMessage(), rev.getId().name()});
270 if (--limit <= 0) {
271 break;
275 } catch (IOException ioe) {
278 return revs;
281 private static String getRelative(File root, File dir) {
282 return getRelative(root.getAbsolutePath(), dir.getAbsolutePath());
285 private static String getRelative(String root, String dir) {
286 if (dir.equals(root)) {
287 return "";
289 return dir.replace(root + File.separator, "");
292 private static void put(Set<String> set, String relPath,
293 Map<File, StatusInfo> files, File root, int status) {
294 for (String path : set) {
295 if (relPath.length() > 0 && !path.startsWith(relPath)) {
296 continue;
298 File file = new File(root, path);
299 files.put(file, new StatusInfo(status, null, false));
305 * m odified
306 * a dded
307 * r emoved
308 * d eleted
309 * u nknown
310 * C opies
312 * i gnored
313 * c lean
315 public static Map<File, StatusInfo> getAllStatus(File root, File dir) throws IOException {
316 String relPath = getRelative(root, dir);
318 Repository repo = Git.getInstance().getRepository(root);
319 Map<File, StatusInfo> files = new HashMap<File, StatusInfo>();
321 try {
322 repo.refreshFromDisk();
323 IndexDiff index = new IndexDiff(repo);
324 index.diff();
326 put(index.getAdded(), relPath, files, root,
327 StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY);
328 put(index.getRemoved(), relPath, files, root,
329 StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY);
330 put(index.getMissing(), relPath, files, root,
331 StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY);
332 put(index.getChanged(), relPath, files, root,
333 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
334 put(index.getModified(), relPath, files, root,
335 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
337 final FileTreeIterator workTree = new FileTreeIterator(repo.getWorkDir());
338 final TreeWalk walk = new TreeWalk(repo);
340 walk.reset(); // drop the first empty tree
341 walk.setRecursive(true);
342 walk.addTree(workTree);
344 int share = SharabilityQuery.getSharability(dir);
345 if (share == SharabilityQuery.NOT_SHARABLE) {
346 return files;
348 while (walk.next()) {
349 String path = walk.getPathString();
351 if (relPath.length() > 0 && !path.startsWith(relPath)) {
352 continue;
354 if (index.getAdded().contains(path) ||
355 index.getRemoved().contains(path) ||
356 index.getMissing().contains(path) ||
357 index.getChanged().contains(path) ||
358 index.getModified().contains(path)) {
359 continue;
361 Entry entry = repo.getIndex().getEntry(path);
362 File file = new File(root, path);
364 int status;
365 if (entry != null) {
366 status = StatusInfo.STATUS_VERSIONED_UPTODATE;
367 } else {
368 if (share == SharabilityQuery.MIXED &&
369 SharabilityQuery.getSharability(file) == SharabilityQuery.NOT_SHARABLE) {
370 continue;
372 status = StatusInfo.STATUS_NOTVERSIONED_NEWLOCALLY;
375 files.put(file, new StatusInfo(status, null, false));
378 } catch (IOException ex) {
379 Exceptions.printStackTrace(ex);
382 return files;
386 * m odified
387 * a dded
388 * r emoved
389 * d eleted
390 * u nknown
391 * C opies
393 public static Map<File, StatusInfo> getInterestingStatus(File root, File dir) {
394 String relPath = getRelative(root, dir);
396 Repository repo = Git.getInstance().getRepository(root);
397 IndexDiff index;
399 Map<File, StatusInfo> files = new HashMap<File, StatusInfo>();
401 try {
402 repo.refreshFromDisk();
403 index = new IndexDiff(repo);
404 index.diff();
406 put(index.getAdded(), relPath, files, root,
407 StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY);
408 put(index.getRemoved(), relPath, files, root,
409 StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY);
410 put(index.getMissing(), relPath, files, root,
411 StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY);
412 put(index.getChanged(), relPath, files, root,
413 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
414 put(index.getModified(), relPath, files, root,
415 StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY);
417 final FileTreeIterator workTree = new FileTreeIterator(repo.getWorkDir());
418 final TreeWalk walk = new TreeWalk(repo);
420 walk.reset(); // drop the first empty tree
421 walk.setRecursive(true);
422 walk.addTree(workTree);
424 while (walk.next()) {
425 String path = walk.getPathString();
427 if (relPath.length() > 0 && !path.startsWith(relPath)) {
428 continue;
430 if (index.getAdded().contains(path) ||
431 index.getRemoved().contains(path) ||
432 index.getMissing().contains(path) ||
433 index.getChanged().contains(path) ||
434 index.getModified().contains(path)) {
435 continue;
437 Entry entry = repo.getIndex().getEntry(path);
438 if (entry != null) {
439 continue;
441 int status = StatusInfo.STATUS_NOTVERSIONED_NEWLOCALLY;
442 File file = new File(root, path);
443 files.put(file, new StatusInfo(status, null, false));
446 } catch (IOException ex) {
447 Exceptions.printStackTrace(ex);
450 return files;
453 public static StatusInfo getSingleStatus(File root, File file) {
454 Repository repo = Git.getInstance().getRepository(root);
455 IndexDiff index;
457 int share = SharabilityQuery.getSharability(file.getParentFile());
458 if (share == SharabilityQuery.NOT_SHARABLE ||
459 (share == SharabilityQuery.MIXED &&
460 SharabilityQuery.getSharability(file) == SharabilityQuery.NOT_SHARABLE)) {
461 return new StatusInfo(StatusInfo.STATUS_NOTVERSIONED_EXCLUDED, null, false);
463 int status = StatusInfo.STATUS_UNKNOWN;
464 String name = getRelative(root, file);
466 try {
467 repo.refreshFromDisk();
468 index = new IndexDiff(repo);
469 index.diff();
470 } catch (IOException ex) {
471 Exceptions.printStackTrace(ex);
472 return new StatusInfo(status, null, false);
475 if (index.getAdded().contains(name)) {
476 status = StatusInfo.STATUS_VERSIONED_ADDEDLOCALLY;
477 } else if (index.getRemoved().contains(name)) {
478 status = StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY;
479 } else if (index.getMissing().contains(name)) {
480 status = StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY;
481 } else if (index.getChanged().contains(name)) {
482 status = StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY;
483 } else if (index.getModified().contains(name)) {
484 status = StatusInfo.STATUS_VERSIONED_MODIFIEDLOCALLY;
485 } else {
486 status = StatusInfo.STATUS_VERSIONED_UPTODATE;
488 StatusInfo info = new StatusInfo(status, null, false);
490 return info;