From 7026658ac8915bf1e0534132f7c5d4d02473490b Mon Sep 17 00:00:00 2001 From: Kaloyan Raev Date: Thu, 10 Oct 2013 23:08:14 +0300 Subject: [PATCH] CLI status should support --porcelain Add support for the machine-readable output format along with the existing default long format. Bug: 419968 Change-Id: I37fe5121b4c9dbae1106b1d18e9fdc134070a9dd Signed-off-by: Kaloyan Raev --- .../tst/org/eclipse/jgit/pgm/StatusTest.java | 118 ++++++++++++++++ .../jgit/pgm/internal/CLIText.properties | 1 + .../src/org/eclipse/jgit/pgm/Status.java | 129 ++++++++++++++++-- 3 files changed, 238 insertions(+), 10 deletions(-) diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java index 73ae598a8..acc2be6b4 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java @@ -213,4 +213,122 @@ public void testStatus() throws Exception { "" // }, execute("git status")); // } + + @Test + public void testStatusPorcelain() throws Exception { + Git git = new Git(db); + // Write all files + writeTrashFile("tracked", "tracked"); + writeTrashFile("stagedNew", "stagedNew"); + writeTrashFile("stagedModified", "stagedModified"); + writeTrashFile("stagedDeleted", "stagedDeleted"); + writeTrashFile("trackedModified", "trackedModified"); + writeTrashFile("trackedDeleted", "trackedDeleted"); + writeTrashFile("untracked", "untracked"); + // Test untracked + assertArrayOfLinesEquals(new String[] { // git status output + "?? stagedDeleted", // + "?? stagedModified", // + "?? stagedNew", // + "?? tracked", // + "?? trackedDeleted", // + "?? trackedModified", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Add to index + git.add().addFilepattern("tracked").call(); + git.add().addFilepattern("stagedModified").call(); + git.add().addFilepattern("stagedDeleted").call(); + git.add().addFilepattern("trackedModified").call(); + git.add().addFilepattern("trackedDeleted").call(); + // Test staged count + assertArrayOfLinesEquals(new String[] { // git status output + "A stagedDeleted", // + "A stagedModified", // + "A tracked", // + "A trackedDeleted", // + "A trackedModified", // + "?? stagedNew", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Commit + git.commit().setMessage("initial commit").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? stagedNew", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Make some changes and stage them + writeTrashFile("stagedModified", "stagedModified modified"); + deleteTrashFile("stagedDeleted"); + writeTrashFile("trackedModified", "trackedModified modified"); + deleteTrashFile("trackedDeleted"); + git.add().addFilepattern("stagedModified").call(); + git.rm().addFilepattern("stagedDeleted").call(); + git.add().addFilepattern("stagedNew").call(); + // Test staged/not-staged status + assertArrayOfLinesEquals(new String[] { // git status output + "D stagedDeleted", // + "M stagedModified", // + "A stagedNew", // + " D trackedDeleted", // + " M trackedModified", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Create unmerged file + writeTrashFile("unmerged", "unmerged"); + git.add().addFilepattern("unmerged").call(); + // Commit pending changes + git.add().addFilepattern("trackedModified").call(); + git.rm().addFilepattern("trackedDeleted").call(); + git.commit().setMessage("commit before branching").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Checkout new branch + git.checkout().setCreateBranch(true).setName("test").call(); + // Test branch status + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Commit change and checkout master again + writeTrashFile("unmerged", "changed in test branch"); + git.add().addFilepattern("unmerged").call(); + RevCommit testBranch = git.commit() + .setMessage("changed unmerged in test branch").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + git.checkout().setName("master").call(); + // Change the same file and commit + writeTrashFile("unmerged", "changed in master branch"); + git.add().addFilepattern("unmerged").call(); + git.commit().setMessage("changed unmerged in master branch").call(); + assertArrayOfLinesEquals(new String[] { // git status output + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Merge test branch into master + git.merge().include(testBranch.getId()).call(); + // Test unmerged status + assertArrayOfLinesEquals(new String[] { // git status output + "UU unmerged", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + // Test detached head + String commitId = db.getRef(Constants.MASTER).getObjectId().name(); + git.checkout().setName(commitId).call(); + assertArrayOfLinesEquals(new String[] { // git status output + "UU unmerged", // + "?? untracked", // + "" // + }, execute("git status --porcelain")); // + } } diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index fc83b956b..d23f37899 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -282,6 +282,7 @@ usage_inputOutputFile=Input/output file usage_listBothRemoteTrackingAndLocalBranches=list both remote-tracking and local branches usage_listCreateOrDeleteBranches=List, create, or delete branches usage_logAllPretty=format:%H %ct %P' output=log --all '--pretty=format:%H %ct %P' output +usage_machineReadableOutput=machine-readable output usage_manageReflogInformation=Manage reflog information usage_mergeFf=When the merge resolves as a fast-forward, only update the branch pointer, without creating a merge commit. usage_mergeNoFf=Create a merge commit even when the merge resolves as a fast-forward. diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java index 0214ed00e..2ae950bdc 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Status.java @@ -50,6 +50,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.TreeSet; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.StatusCommand; @@ -71,26 +72,134 @@ class Status extends TextBuiltin { protected final String statusFileListFormatUnmerged = CLIText.get().statusFileListFormatUnmerged; + @Option(name = "--porcelain", usage = "usage_machineReadableOutput") + protected boolean porcelain; + @Option(name = "--", metaVar = "metaVar_path", multiValued = true) protected List filterPaths; @Override protected void run() throws Exception { - // Print current branch name - final Ref head = db.getRef(Constants.HEAD); - boolean firstHeader = true; - if (head != null && head.isSymbolic()) { - String branch = Repository.shortenRefName(head.getLeaf().getName()); - outw.println(CLIText.formatLine( - MessageFormat.format(CLIText.get().onBranch, branch))); - } else - outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch)); - // List changes StatusCommand statusCommand = new Git(db).status(); if (filterPaths != null && filterPaths.size() > 0) for (String path : filterPaths) statusCommand.addPath(path); org.eclipse.jgit.api.Status status = statusCommand.call(); + printStatus(status); + } + + private void printStatus(org.eclipse.jgit.api.Status status) + throws IOException { + if (porcelain) + printPorcelainStatus(status); + else + printLongStatus(status); + } + + private void printPorcelainStatus(org.eclipse.jgit.api.Status status) + throws IOException { + + Collection added = status.getAdded(); + Collection changed = status.getChanged(); + Collection removed = status.getRemoved(); + Collection modified = status.getModified(); + Collection missing = status.getMissing(); + Map conflicting = status.getConflictingStageState(); + + // build a sorted list of all paths except untracked and ignored + TreeSet sorted = new TreeSet(); + sorted.addAll(added); + sorted.addAll(changed); + sorted.addAll(removed); + sorted.addAll(modified); + sorted.addAll(missing); + sorted.addAll(conflicting.keySet()); + + // list each path + for (String path : sorted) { + char x = ' '; + char y = ' '; + + if (added.contains(path)) + x = 'A'; + else if (changed.contains(path)) + x = 'M'; + else if (removed.contains(path)) + x = 'D'; + + if (modified.contains(path)) + y = 'M'; + else if (missing.contains(path)) + y = 'D'; + + if (conflicting.containsKey(path)) { + StageState stageState = conflicting.get(path); + + switch (stageState) { + case BOTH_DELETED: + x = 'D'; + y = 'D'; + break; + case ADDED_BY_US: + x = 'A'; + y = 'U'; + break; + case DELETED_BY_THEM: + x = 'U'; + y = 'D'; + break; + case ADDED_BY_THEM: + x = 'U'; + y = 'A'; + break; + case DELETED_BY_US: + x = 'D'; + y = 'U'; + break; + case BOTH_ADDED: + x = 'A'; + y = 'A'; + break; + case BOTH_MODIFIED: + x = 'U'; + y = 'U'; + break; + default: + throw new IllegalArgumentException("Unknown StageState: " //$NON-NLS-1$ + + stageState); + } + } + + printPorcelainLine(x, y, path); + } + + // untracked are always at the end of the list + TreeSet untracked = new TreeSet(status.getUntracked()); + for (String path : untracked) + printPorcelainLine('?', '?', path); + } + + private void printPorcelainLine(char x, char y, String path) + throws IOException { + StringBuilder lineBuilder = new StringBuilder(); + lineBuilder.append(x).append(y).append(' ').append(path); + outw.println(lineBuilder.toString()); + } + + private void printLongStatus(org.eclipse.jgit.api.Status status) + throws IOException { + // Print current branch name + final Ref head = db.getRef(Constants.HEAD); + if (head != null && head.isSymbolic()) { + String branch = Repository.shortenRefName(head.getLeaf().getName()); + outw.println(CLIText.formatLine(MessageFormat.format( + CLIText.get().onBranch, branch))); + } else + outw.println(CLIText.formatLine(CLIText.get().notOnAnyBranch)); + + // List changes + boolean firstHeader = true; + Collection added = status.getAdded(); Collection changed = status.getChanged(); Collection removed = status.getRemoved();