[pgm] Add options --name-only, --name-status to diff, log, show

Change-Id: Ib218bd2ccbd7990feca4c35d8c8dc34d4a8291e6
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffTest.java
new file mode 100644
index 0000000..859b54d
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/DiffTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.pgm;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DiffTest extends CLIRepositoryTestCase {
+
+	private static final String NO_NEWLINE = "\\ No newline at end of file";
+
+	@Before
+	public void setup() throws Exception {
+		writeTrashFile("a", "a");
+		execute("git add a");
+		execute("git commit -m added");
+	}
+
+	@Test
+	public void testDiffCommitNewFile() throws Exception {
+		writeTrashFile("a1", "a");
+		String result = toString(execute("git diff"));
+		assertEquals(
+				toString("diff --git a/a1 b/a1", "new file mode 100644",
+						"index 0000000..2e65efe", "--- /dev/null", "+++ b/a1",
+						"@@ -0,0 +1 @@", "+a", NO_NEWLINE),
+				result);
+	}
+
+	@Test
+	public void testDiffCommitModifiedFile() throws Exception {
+		writeTrashFile("a", "a1");
+		String result = toString(execute("git diff"));
+		assertEquals(
+				toString("diff --git a/a b/a", "index 2e65efe..59ef8d1 100644",
+						"--- a/a", "+++ b/a", "@@ -1 +1 @@",
+						"-a", NO_NEWLINE, "+a1", NO_NEWLINE),
+				result);
+	}
+
+	@Test
+	public void testDiffCommitModifiedFileNameOnly() throws Exception {
+		writeTrashFile("a", "a1");
+		writeTrashFile("b", "b");
+		String result = toString(execute("git diff --name-only"));
+		assertEquals(toString("a", "b"), result);
+	}
+
+	@Test
+	public void testDiffCommitModifiedFileNameStatus() throws Exception {
+		writeTrashFile("a", "a1");
+		writeTrashFile("b", "b");
+		String result = toString(execute("git diff --name-status"));
+		assertEquals(toString("M\ta", "A\tb"), result);
+	}
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LogTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LogTest.java
new file mode 100644
index 0000000..1cc52a4
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/LogTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.pgm;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LogTest extends CLIRepositoryTestCase {
+
+	@Before
+	public void setup() throws Exception {
+		writeTrashFile("a", "a");
+		writeTrashFile("b", "a");
+		execute("git add a b");
+		execute("git commit -m added");
+	}
+
+	@Test
+	public void testLogCommitNewFile() throws Exception {
+		String result = toString(execute("git log"));
+		assertEquals(
+				toString("commit b4680f542095a8b41ea4258a5c03b548543a817c",
+						"Author: GIT_COMMITTER_NAME <GIT_COMMITTER_EMAIL>",
+						"Date:   Sat Aug 15 20:12:58 2009 -0330", "added"),
+				result);
+	}
+
+	@Test
+	public void testLogNameOnly() throws Exception {
+		String result = toString(execute("git log --name-only"));
+		assertEquals(
+				toString("commit b4680f542095a8b41ea4258a5c03b548543a817c",
+						"Author: GIT_COMMITTER_NAME <GIT_COMMITTER_EMAIL>",
+						"Date:   Sat Aug 15 20:12:58 2009 -0330", "added", "a",
+						"b"),
+				result);
+	}
+
+	@Test
+	public void testDiffCommitModifiedFileNameStatus() throws Exception {
+		String result = toString(execute("git log --name-status"));
+		assertEquals(toString("commit b4680f542095a8b41ea4258a5c03b548543a817c",
+				"Author: GIT_COMMITTER_NAME <GIT_COMMITTER_EMAIL>",
+				"Date:   Sat Aug 15 20:12:58 2009 -0330", "added", "A\ta",
+				"A\tb"),
+				result);
+	}
+}
diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ShowTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ShowTest.java
new file mode 100644
index 0000000..47d5d34
--- /dev/null
+++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ShowTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022, Matthias Sohn <matthias.sohn@sap.com> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.pgm;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.lib.CLIRepositoryTestCase;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ShowTest extends CLIRepositoryTestCase {
+
+	private static final String NO_NEWLINE = "\\ No newline at end of file";
+
+	@Before
+	public void setup() throws Exception {
+		writeTrashFile("a", "a");
+		writeTrashFile("b", "b");
+		execute("git add a b");
+		execute("git commit -m added");
+		writeTrashFile("a", "a1");
+		execute("git add a");
+		execute("git commit -m modified");
+	}
+
+	@Test
+	public void testShow() throws Exception {
+		String result = toString(execute("git show"));
+		assertEquals(
+				toString("commit ecdf62e777b7413fc463c20e935403d424410ab2",
+						"Author: GIT_COMMITTER_NAME <GIT_COMMITTER_EMAIL>",
+						"Date:   Sat Aug 15 20:12:58 2009 -0330", "",
+						"    modified", "", "diff --git a/a b/a",
+						"index 2e65efe..59ef8d1 100644", "--- a/a", "+++ b/a",
+						"@@ -1 +1 @@", "-a", NO_NEWLINE, "+a1", NO_NEWLINE),
+				result);
+	}
+
+	@Test
+	public void testShowNameOnly() throws Exception {
+		String result = toString(execute("git show --name-only"));
+		assertEquals(toString("commit ecdf62e777b7413fc463c20e935403d424410ab2",
+				"Author: GIT_COMMITTER_NAME <GIT_COMMITTER_EMAIL>",
+				"Date:   Sat Aug 15 20:12:58 2009 -0330", "", "    modified",
+				"a"), result);
+	}
+
+	@Test
+	public void testShowNameStatus() throws Exception {
+		String result = toString(execute("git show --name-status"));
+		assertEquals(toString("commit ecdf62e777b7413fc463c20e935403d424410ab2",
+				"Author: GIT_COMMITTER_NAME <GIT_COMMITTER_EMAIL>",
+				"Date:   Sat Aug 15 20:12:58 2009 -0330", "", "    modified",
+				"M\ta"), result);
+	}
+}
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 b14531a..48f4e85 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
@@ -40,6 +40,7 @@
 cannotResolve=Cannot resolve {0}
 cannotSetupConsole=Cannot setup console
 cannotUseObjectsWithGlog=Cannot use --objects with glog
+cannotUseNameStatusOnlyAndNameOnly=Cannot use --name-only, --name-status are mutually exclusive
 cantFindGitDirectory=error: can't find git directory
 cantWrite=Can''t write {0}
 changesNotStagedForCommit=Changes not staged for commit:
@@ -413,6 +414,7 @@
 usage_message=Set the commit message to be used for the merge commit (in case one is created).
 usage_moveRenameABranch=move/rename a branch
 usage_nameStatus=show only name and status of files
+usage_nameOnly=show only name of files
 usage_noCheckoutAfterClone=no checkout of HEAD is performed after the clone is complete
 usage_noCommit=Don't commit after a successful merge
 usage_noPrefix=do not show any source or destination prefix
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 cdbcbc0..3152c44 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
@@ -48,6 +48,10 @@
 class Diff extends TextBuiltin {
 	private DiffFormatter diffFmt;
 
+	private boolean showNameOnly = false;
+
+	private boolean showNameAndStatusOnly = false;
+
 	@Argument(index = 0, metaVar = "metaVar_treeish")
 	private AbstractTreeIterator oldTree;
 
@@ -81,7 +85,22 @@ void setAlgorithm(SupportedAlgorithm s) {
 	private Integer renameLimit;
 
 	@Option(name = "--name-status", usage = "usage_nameStatus")
-	private boolean showNameAndStatusOnly;
+	void nameAndStatusOnly(boolean on) {
+		if (showNameOnly) {
+			throw new IllegalArgumentException(
+					CLIText.get().cannotUseNameStatusOnlyAndNameOnly);
+		}
+		showNameAndStatusOnly = on;
+	}
+
+	@Option(name = "--name-only", usage = "usage_nameOnly")
+	void nameOnly(boolean on) {
+		if (showNameAndStatusOnly) {
+			throw new IllegalArgumentException(
+					CLIText.get().cannotUseNameStatusOnlyAndNameOnly);
+		}
+		showNameOnly = on;
+	}
 
 	@Option(name = "--ignore-space-at-eol")
 	void ignoreSpaceAtEol(@SuppressWarnings("unused") boolean on) {
@@ -183,6 +202,9 @@ protected void run() {
 			if (showNameAndStatusOnly) {
 				nameStatus(outw, diffFmt.scan(oldTree, newTree));
 				outw.flush();
+			} else if(showNameOnly) {
+				nameOnly(outw, diffFmt.scan(oldTree, newTree));
+				outw.flush();
 			} else {
 				diffFmt.format(oldTree, newTree);
 				diffFmt.flush();
@@ -220,4 +242,27 @@ static void nameStatus(ThrowingPrintWriter out, List<DiffEntry> files)
 			}
 		}
 	}
+
+	static void nameOnly(ThrowingPrintWriter out, List<DiffEntry> files)
+			throws IOException {
+		for (DiffEntry ent : files) {
+			switch (ent.getChangeType()) {
+				case ADD:
+					out.println(ent.getNewPath());
+					break;
+				case DELETE:
+					out.println(ent.getOldPath());
+					break;
+				case MODIFY:
+					out.println(ent.getNewPath());
+					break;
+				case COPY:
+					out.println(ent.getNewPath());
+					break;
+				case RENAME:
+					out.println(ent.getNewPath());
+					break;
+			}
+		}
+	}
 }
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 353b64b..d693051 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
@@ -60,6 +60,10 @@ class Log extends RevWalkTextBuiltin {
 
 	private Map<String, NoteMap> noteMaps;
 
+	private boolean showNameOnly = false;
+
+	private boolean showNameAndStatusOnly = false;
+
 	@Option(name="--decorate", usage="usage_showRefNamesMatchingCommits")
 	private boolean decorate;
 
@@ -99,7 +103,22 @@ void noRenames(@SuppressWarnings("unused") boolean on) {
 	private Integer renameLimit;
 
 	@Option(name = "--name-status", usage = "usage_nameStatus")
-	private boolean showNameAndStatusOnly;
+	void nameAndStatusOnly(boolean on) {
+		if (showNameOnly) {
+			throw new IllegalArgumentException(
+					CLIText.get().cannotUseNameStatusOnlyAndNameOnly);
+		}
+		showNameAndStatusOnly = on;
+	}
+
+	@Option(name = "--name-only", usage = "usage_nameOnly")
+	void nameOnly(boolean on) {
+		if (showNameAndStatusOnly) {
+			throw new IllegalArgumentException(
+					CLIText.get().cannotUseNameStatusOnlyAndNameOnly);
+		}
+		showNameOnly = on;
+	}
 
 	@Option(name = "--ignore-space-at-eol")
 	void ignoreSpaceAtEol(@SuppressWarnings("unused") boolean on) {
@@ -266,8 +285,10 @@ protected void show(RevCommit c) throws Exception {
 		if (showNotes(c))
 			outw.println();
 
-		if (c.getParentCount() <= 1 && (showNameAndStatusOnly || showPatch))
+		if (c.getParentCount() <= 1 && (showNameAndStatusOnly || showPatch
+				|| showNameOnly)) {
 			showDiff(c);
+		}
 		outw.flush();
 	}
 
@@ -364,9 +385,11 @@ private void showDiff(RevCommit c) throws IOException {
 				: null;
 		final RevTree b = c.getTree();
 
-		if (showNameAndStatusOnly)
+		if (showNameAndStatusOnly) {
 			Diff.nameStatus(outw, diffFmt.scan(a, b));
-		else {
+		} else if (showNameOnly) {
+			Diff.nameOnly(outw, diffFmt.scan(a, b));
+		} else {
 			outw.flush();
 			diffFmt.format(a, b);
 			diffFmt.flush();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
index 3beab60..c18d35a 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Show.java
@@ -58,6 +58,10 @@ class Show extends TextBuiltin {
 
 	private DiffFormatter diffFmt;
 
+	private boolean showNameOnly = false;
+
+	private boolean showNameAndStatusOnly = false;
+
 	@Argument(index = 0, metaVar = "metaVar_object")
 	private String objectName;
 
@@ -83,7 +87,22 @@ void noRenames(@SuppressWarnings("unused") boolean on) {
 	private Integer renameLimit;
 
 	@Option(name = "--name-status", usage = "usage_nameStatus")
-	private boolean showNameAndStatusOnly;
+	void nameAndStatusOnly(boolean on) {
+		if (showNameOnly) {
+			throw new IllegalArgumentException(
+					CLIText.get().cannotUseNameStatusOnlyAndNameOnly);
+		}
+		showNameAndStatusOnly = on;
+	}
+
+	@Option(name = "--name-only", usage = "usage_nameOnly")
+	void nameOnly(boolean on) {
+		if (showNameAndStatusOnly) {
+			throw new IllegalArgumentException(
+					CLIText.get().cannotUseNameStatusOnlyAndNameOnly);
+		}
+		showNameOnly = on;
+	}
 
 	@Option(name = "--ignore-space-at-eol")
 	void ignoreSpaceAtEol(@SuppressWarnings("unused") boolean on) {
@@ -302,9 +321,11 @@ private void showDiff(RevCommit c) throws IOException {
 		final RevTree a = c.getParent(0).getTree();
 		final RevTree b = c.getTree();
 
-		if (showNameAndStatusOnly)
+		if (showNameAndStatusOnly) {
 			Diff.nameStatus(outw, diffFmt.scan(a, b));
-		else {
+		} else if (showNameOnly) {
+			Diff.nameOnly(outw, diffFmt.scan(a, b));
+		} else {
 			outw.flush();
 			diffFmt.format(a, b);
 			diffFmt.flush();
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
index e06f150..490f800 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java
@@ -119,6 +119,7 @@ public static String fatalError(String message) {
 	/***/ public String cannotResolve;
 	/***/ public String cannotSetupConsole;
 	/***/ public String cannotUseObjectsWithGlog;
+	/***/ public String cannotUseNameStatusOnlyAndNameOnly;
 	/***/ public String cantFindGitDirectory;
 	/***/ public String cantWrite;
 	/***/ public String changesNotStagedForCommit;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
index a925a08..040b29d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java
@@ -51,6 +51,8 @@ public class DiffCommand extends GitCommand<List<DiffEntry>> {
 
 	private boolean showNameAndStatusOnly;
 
+	private boolean showNameOnly;
+
 	private OutputStream out;
 
 	private int contextLines = -1;
@@ -72,7 +74,7 @@ protected DiffCommand(Repository repo) {
 	}
 
 	private DiffFormatter getDiffFormatter() {
-		return out != null && !showNameAndStatusOnly
+		return out != null && !showNameAndStatusOnly && !showNameOnly
 				? new DiffFormatter(new BufferedOutputStream(out))
 				: new DiffFormatter(NullOutputStream.INSTANCE);
 	}
@@ -114,7 +116,7 @@ public List<DiffEntry> call() throws GitAPIException {
 			diffFmt.setPathFilter(pathFilter);
 
 			List<DiffEntry> result = diffFmt.scan(oldTree, newTree);
-			if (showNameAndStatusOnly) {
+			if (showNameAndStatusOnly || showNameOnly) {
 				return result;
 			}
 			if (contextLines >= 0) {
@@ -195,6 +197,19 @@ public DiffCommand setShowNameAndStatusOnly(boolean showNameAndStatusOnly) {
 	}
 
 	/**
+	 * Set whether to return only names of changed files
+	 *
+	 * @param showNameOnly
+	 *            whether to return only names files
+	 * @return this instance
+	 * @since 6.4
+	 */
+	public DiffCommand setShowNameOnly(boolean showNameOnly) {
+		this.showNameOnly = showNameOnly;
+		return this;
+	}
+
+	/**
 	 * Set output stream
 	 *
 	 * @param out