diff options
5 files changed, 300 insertions, 70 deletions
diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties index d13c47d297..67357be1da 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/CLIText.properties @@ -66,6 +66,7 @@ metaVar_directory=DIRECTORY metaVar_file=FILE metaVar_gitDir=GIT_DIR metaVar_hostName=HOSTNAME +metaVar_linesOfContext=lines metaVar_message=message metaVar_name=name metaVar_object=object @@ -139,6 +140,7 @@ usage_cloneRepositoryIntoNewDir=Clone a repository into a new directory usage_configureTheServiceInDaemonServicename=configure the service in daemon.servicename usage_deleteBranchEvenIfNotMerged=delete branch (even if not merged) usage_deleteFullyMergedBranch=delete fully merged branch +usage_detectRenames=detect renamed files usage_directoriesToExport=directories to export usage_disableTheServiceInAllRepositories=disable the service in all repositories usage_displayAListOfAllRegisteredJgitCommands=Display a list of all registered jgit commands @@ -160,6 +162,7 @@ usage_listBothRemoteTrackingAndLocalBranches=list both remote-tracking and local usage_listCreateOrDeleteBranches=List, create, or delete branches usage_logAllPretty=format:%H %ct %P' output=log --all '--pretty=format:%H %ct %P' output usage_moveRenameABranch=move/rename a branch +usage_nameStatus=show only name and status of files usage_outputFile=Output file usage_path=path usage_performFsckStyleChecksOnReceive=perform fsck style checks on receive @@ -170,6 +173,7 @@ usage_recurseIntoSubtrees=recurse into subtrees usage_recordChangesToRepository=Record changes to the repository usage_setTheGitRepositoryToOperateOn=set the git repository to operate on usage_showRefNamesMatchingCommits=Show ref names matching commits +usage_showPatch=display patch usage_symbolicVersionForTheProject=Symbolic version for the project usage_synchronizeIPZillaData=Synchronize IPZilla data usage_tagMessage=tag message diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java index fc1e400ab0..a3fca638b5 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Diff.java @@ -45,28 +45,28 @@ package org.eclipse.jgit.pgm; +import java.io.BufferedOutputStream; import java.io.IOException; -import java.io.PrintStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.Option; - +import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; -import org.eclipse.jgit.diff.MyersDiff; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.diff.RawTextIgnoreAllWhitespace; import org.eclipse.jgit.diff.RawTextIgnoreLeadingWhitespace; import org.eclipse.jgit.diff.RawTextIgnoreTrailingWhitespace; import org.eclipse.jgit.diff.RawTextIgnoreWhitespaceChange; -import org.eclipse.jgit.lib.FileMode; -import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.diff.RenameDetector; +import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.pgm.opt.PathTreeFilterHandler; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_ShowDiffs") class Diff extends TextBuiltin { @@ -78,9 +78,15 @@ class Diff extends TextBuiltin { @Argument(index = 1, metaVar = "metaVar_treeish", required = true) private final List<AbstractTreeIterator> trees = new ArrayList<AbstractTreeIterator>(); - @Option(name = "--", metaVar = "metaVar_port", multiValued = true, handler = PathTreeFilterHandler.class) + @Option(name = "--", metaVar = "metaVar_paths", multiValued = true, handler = PathTreeFilterHandler.class) private TreeFilter pathFilter = TreeFilter.ALL; + @Option(name = "-M", usage = "usage_detectRenames") + private boolean detectRenames; + + @Option(name = "--name-status", usage = "usage_nameStatus") + private boolean showNameAndStatusOnly; + @Option(name = "--ignore-space-at-eol") private boolean ignoreWsTrailing; @@ -93,10 +99,69 @@ class Diff extends TextBuiltin { @Option(name = "-w", aliases = { "--ignore-all-space" }) private boolean ignoreWsAll; - private DiffFormatter fmt = new DiffFormatter(); + @Option(name = "-U", aliases = { "--unified" }, metaVar = "metaVar_linesOfContext") + void unified(int lines) { + fmt.setContext(lines); + } + + private DiffFormatter fmt = new DiffFormatter() { + @Override + protected RawText newRawText(byte[] raw) { + if (ignoreWsAll) + return new RawTextIgnoreAllWhitespace(raw); + else if (ignoreWsTrailing) + return new RawTextIgnoreTrailingWhitespace(raw); + else if (ignoreWsChange) + return new RawTextIgnoreWhitespaceChange(raw); + else if (ignoreWsLeading) + return new RawTextIgnoreLeadingWhitespace(raw); + else + return new RawText(raw); + } + }; @Override protected void run() throws Exception { + List<DiffEntry> files = scan(); + + if (showNameAndStatusOnly) { + nameStatus(out, files); + out.flush(); + + } else { + BufferedOutputStream o = new BufferedOutputStream(System.out); + fmt.format(o, db, files); + o.flush(); + } + } + + static void nameStatus(PrintWriter out, List<DiffEntry> files) { + for (DiffEntry ent : files) { + switch (ent.getChangeType()) { + case ADD: + out.println("A\t" + ent.getNewName()); + break; + case DELETE: + out.println("D\t" + ent.getOldName()); + break; + case MODIFY: + out.println("M\t" + ent.getNewName()); + break; + case COPY: + out.format("C%1$03d\t%2$s\t%3$s", ent.getScore(), // + ent.getOldName(), ent.getNewName()); + out.println(); + break; + case RENAME: + out.format("R%1$03d\t%2$s\t%3$s", ent.getScore(), // + ent.getOldName(), ent.getNewName()); + out.println(); + break; + } + } + } + + private List<DiffEntry> scan() throws IOException { final TreeWalk walk = new TreeWalk(db); walk.reset(); walk.setRecursive(true); @@ -104,65 +169,12 @@ class Diff extends TextBuiltin { walk.addTree(i); walk.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, pathFilter)); - while (walk.next()) - outputDiff(System.out, walk.getPathString(), - walk.getObjectId(0), walk.getFileMode(0), - walk.getObjectId(1), walk.getFileMode(1)); - } - - protected void outputDiff(PrintStream out, String path, - ObjectId id1, FileMode mode1, ObjectId id2, FileMode mode2) throws IOException { - String name1 = "a/" + path; - String name2 = "b/" + path; - out.println("diff --git " + name1 + " " + name2); - boolean isNew=false; - boolean isDelete=false; - if (id1.equals(ObjectId.zeroId())) { - out.println("new file mode " + mode2); - isNew=true; - } else if (id2.equals(ObjectId.zeroId())) { - out.println("deleted file mode " + mode1); - isDelete=true; - } else if (!mode1.equals(mode2)) { - out.println("old mode " + mode1); - out.println("new mode " + mode2); + List<DiffEntry> files = DiffEntry.scan(walk); + if (detectRenames) { + RenameDetector rd = new RenameDetector(db); + rd.addAll(files); + files = rd.compute(new TextProgressMonitor()); } - out.println("index " + id1.abbreviate(db, 7).name() - + ".." + id2.abbreviate(db, 7).name() - + (mode1.equals(mode2) ? " " + mode1 : "")); - out.println("--- " + (isNew ? "/dev/null" : name1)); - out.println("+++ " + (isDelete ? "/dev/null" : name2)); - - byte[] aRaw = getRawBytes(id1); - byte[] bRaw = getRawBytes(id2); - - if (RawText.isBinary(aRaw) || RawText.isBinary(bRaw)) { - out.println("Binary files differ"); - return; - } - - RawText a = getRawText(aRaw); - RawText b = getRawText(bRaw); - MyersDiff diff = new MyersDiff(a, b); - fmt.formatEdits(out, a, b, diff.getEdits()); - } - - private byte[] getRawBytes(ObjectId id) throws IOException { - if (id.equals(ObjectId.zeroId())) - return new byte[] {}; - return db.openBlob(id).getCachedBytes(); - } - - private RawText getRawText(byte[] raw) { - if (ignoreWsAll) - return new RawTextIgnoreAllWhitespace(raw); - else if (ignoreWsTrailing) - return new RawTextIgnoreTrailingWhitespace(raw); - else if (ignoreWsChange) - return new RawTextIgnoreWhitespaceChange(raw); - else if (ignoreWsLeading) - return new RawTextIgnoreLeadingWhitespace(raw); - else - return new RawText(raw); + return files; } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java index 9aa197e4ab..511da44984 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Log.java @@ -45,22 +45,32 @@ package org.eclipse.jgit.pgm; +import java.io.BufferedOutputStream; +import java.io.IOException; import java.text.DateFormat; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TimeZone; -import org.kohsuke.args4j.Option; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.diff.RenameDetector; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.AndTreeFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.kohsuke.args4j.Option; @Command(common = true, usage = "usage_viewCommitHistory") class Log extends RevWalkTextBuiltin { @@ -73,6 +83,22 @@ class Log extends RevWalkTextBuiltin { @Option(name="--decorate", usage="usage_showRefNamesMatchingCommits") private boolean decorate; + @Option(name = "-M", usage = "usage_detectRenames") + private boolean detectRenames; + + @Option(name = "--name-status", usage = "usage_nameStatus") + private boolean showNameAndStatusOnly; + + @Option(name = "-p", usage = "usage_showPatch") + private boolean showPatch; + + @Option(name = "-U", aliases = { "--unified" }, metaVar = "metaVar_linesOfContext") + void unified(int lines) { + diffFmt.setContext(lines); + } + + private DiffFormatter diffFmt = new DiffFormatter(); + Log() { fmt = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy ZZZZZ", Locale.US); } @@ -120,6 +146,34 @@ class Log extends RevWalkTextBuiltin { } out.println(); + if (c.getParentCount() > 0 && (showNameAndStatusOnly || showPatch)) + showDiff(c); out.flush(); } + + private void showDiff(RevCommit c) throws IOException { + final TreeWalk tw = new TreeWalk(db); + tw.reset(); + tw.setRecursive(true); + tw.addTree(c.getParent(0).getTree()); + tw.addTree(c.getTree()); + tw.setFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, pathFilter)); + + List<DiffEntry> files = DiffEntry.scan(tw); + if (detectRenames) { + RenameDetector rd = new RenameDetector(db); + rd.addAll(files); + files = rd.compute(new TextProgressMonitor()); + } + + if (showNameAndStatusOnly) { + Diff.nameStatus(out, files); + + } else { + out.flush(); + BufferedOutputStream o = new BufferedOutputStream(System.out); + diffFmt.format(o, db, files); + o.flush(); + } + } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java index ea6eeb102c..beb961d99c 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevWalkTextBuiltin.java @@ -114,7 +114,7 @@ abstract class RevWalkTextBuiltin extends TextBuiltin { private final List<RevCommit> commits = new ArrayList<RevCommit>(); @Option(name = "--", metaVar = "metaVar_path", multiValued = true, handler = PathTreeFilterHandler.class) - private TreeFilter pathFilter = TreeFilter.ALL; + protected TreeFilter pathFilter = TreeFilter.ALL; private final List<RevFilter> revLimiter = new ArrayList<RevFilter>(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index c40d3b7000..b08d1cb502 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.diff; +import static org.eclipse.jgit.lib.Constants.encode; import static org.eclipse.jgit.lib.Constants.encodeASCII; import java.io.IOException; @@ -51,7 +52,13 @@ import java.io.OutputStream; import java.util.List; import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.patch.FileHeader; +import org.eclipse.jgit.util.QuotedString; /** * Format an {@link EditList} as a Git style unified patch script. @@ -81,6 +88,159 @@ public class DiffFormatter { } /** + * Format a patch script from a list of difference entries. + * + * @param out + * stream to write the patch script out to. + * @param src + * repository the file contents can be read from. + * @param entries + * entries describing the affected files. + * @throws IOException + * a file's content cannot be read, or the output stream cannot + * be written to. + */ + public void format(final OutputStream out, Repository src, + List<? extends DiffEntry> entries) throws IOException { + for(DiffEntry ent : entries) { + if (ent instanceof FileHeader) { + format( + out, + (FileHeader) ent, // + newRawText(open(src, ent.getOldMode(), ent.getOldId())), + newRawText(open(src, ent.getNewMode(), ent.getNewId()))); + } else { + format(out, src, ent); + } + } + } + + private void format(OutputStream out, Repository src, DiffEntry ent) + throws IOException { + String oldName = quotePath("a/" + ent.getOldName()); + String newName = quotePath("b/" + ent.getNewName()); + out.write(encode("diff --git " + oldName + " " + newName + "\n")); + + switch(ent.getChangeType()) { + case ADD: + out.write(encodeASCII("new file mode ")); + ent.getNewMode().copyTo(out); + out.write('\n'); + break; + + case DELETE: + out.write(encodeASCII("deleted file mode ")); + ent.getOldMode().copyTo(out); + out.write('\n'); + break; + + case RENAME: + out.write(encode("similarity index " + ent.getScore() + "%")); + out.write('\n'); + + out.write(encode("rename from " + quotePath(ent.getOldName()))); + out.write('\n'); + + out.write(encode("rename to " + quotePath(ent.getNewName()))); + out.write('\n'); + break; + + case COPY: + out.write(encode("similarity index " + ent.getScore() + "%")); + out.write('\n'); + + out.write(encode("copy from " + quotePath(ent.getOldName()))); + out.write('\n'); + + out.write(encode("copy to " + quotePath(ent.getNewName()))); + out.write('\n'); + + if (!ent.getOldMode().equals(ent.getNewMode())) { + out.write(encodeASCII("new file mode ")); + ent.getNewMode().copyTo(out); + out.write('\n'); + } + break; + } + + switch (ent.getChangeType()) { + case RENAME: + case MODIFY: + if (!ent.getOldMode().equals(ent.getNewMode())) { + out.write(encodeASCII("old mode ")); + ent.getOldMode().copyTo(out); + out.write('\n'); + + out.write(encodeASCII("new mode ")); + ent.getNewMode().copyTo(out); + out.write('\n'); + } + } + + out.write(encodeASCII("index " // + + format(src, ent.getOldId()) // + + ".." // + + format(src, ent.getNewId()))); + if (ent.getOldMode().equals(ent.getNewMode())) { + out.write(' '); + ent.getNewMode().copyTo(out); + } + out.write('\n'); + out.write(encode("--- " + oldName + '\n')); + out.write(encode("+++ " + newName + '\n')); + + byte[] aRaw = open(src, ent.getOldMode(), ent.getOldId()); + byte[] bRaw = open(src, ent.getNewMode(), ent.getNewId()); + + if (RawText.isBinary(aRaw) || RawText.isBinary(bRaw)) { + out.write(encodeASCII("Binary files differ\n")); + + } else { + RawText a = newRawText(aRaw); + RawText b = newRawText(bRaw); + formatEdits(out, a, b, new MyersDiff(a, b).getEdits()); + } + } + + /** + * Construct a RawText sequence for use with {@link MyersDiff}. + * + * @param content + * text to be compared. + * @return the raw text instance to handle the content. + */ + protected RawText newRawText(byte[] content) { + return new RawText(content); + } + + private String format(Repository db, AbbreviatedObjectId oldId) { + if (oldId.isComplete()) + oldId = oldId.toObjectId().abbreviate(db, 8); + return oldId.name(); + } + + private static String quotePath(String name) { + String q = QuotedString.GIT_PATH.quote(name); + return ('"' + name + '"').equals(q) ? name : q; + } + + private byte[] open(Repository src, FileMode mode, AbbreviatedObjectId id) + throws IOException { + if (mode == FileMode.MISSING) + return new byte[] {}; + + if (mode.getObjectType() != Constants.OBJ_BLOB) + return new byte[] {}; + + if (id.isComplete()) { + ObjectLoader ldr = src.openObject(id.toObjectId()); + return ldr.getCachedBytes(); + } + + return new byte[] {}; + } + + /** * Format a patch script, reusing a previously parsed FileHeader. * <p> * This formatter is primarily useful for editing an existing patch script |