Provide git config commit.cleanup

Add an enumeration for the possible values, and a method to resolve the
"default" value. Give CommitConfig a static method to process a text
according to a given clean-up mode and comment character.

(The core.commentChar is not yet handled by JGit; it's hard-coded as #.)

Bug: 553065
Change-Id: If6e384522275f73b713fbc29ffcaa1753c239dea
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
This commit is contained in:
Thomas Wolf 2022-01-15 22:51:06 +01:00
parent 2b01ac3389
commit 318a25f0e6
3 changed files with 366 additions and 0 deletions

View File

@ -0,0 +1,179 @@
/*
* Copyright (C) 2022, Thomas Wolf <thomas.wolf@paranor.ch> 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.lib;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
import org.junit.Test;
public class CommitConfigTest {
@Test
public void testDefaults() throws Exception {
CommitConfig cfg = parse("");
assertEquals("Unexpected clean-up mode", CleanupMode.DEFAULT,
cfg.getCleanupMode());
}
@Test
public void testCommitCleanup() throws Exception {
String[] values = { "strip", "whitespace", "verbatim", "scissors",
"default" };
CleanupMode[] expected = { CleanupMode.STRIP, CleanupMode.WHITESPACE,
CleanupMode.VERBATIM, CleanupMode.SCISSORS,
CleanupMode.DEFAULT };
for (int i = 0; i < values.length; i++) {
CommitConfig cfg = parse("[commit]\n\tcleanup = " + values[i]);
assertEquals("Unexpected clean-up mode", expected[i],
cfg.getCleanupMode());
}
}
@Test
public void testResolve() throws Exception {
String[] values = { "strip", "whitespace", "verbatim", "scissors",
"default" };
CleanupMode[] expected = { CleanupMode.STRIP, CleanupMode.WHITESPACE,
CleanupMode.VERBATIM, CleanupMode.SCISSORS,
CleanupMode.DEFAULT };
for (int i = 0; i < values.length; i++) {
CommitConfig cfg = parse("[commit]\n\tcleanup = " + values[i]);
for (CleanupMode mode : CleanupMode.values()) {
for (int j = 0; j < 2; j++) {
CleanupMode resolved = cfg.resolve(mode, j == 0);
if (mode != CleanupMode.DEFAULT) {
assertEquals("Clean-up mode should be unchanged", mode,
resolved);
} else if (i + 1 < values.length) {
assertEquals("Unexpected clean-up mode", expected[i],
resolved);
} else {
assertEquals("Unexpected clean-up mode",
j == 0 ? CleanupMode.STRIP
: CleanupMode.WHITESPACE,
resolved);
}
}
}
}
}
@Test
public void testCleanDefaultThrows() throws Exception {
assertThrows(IllegalArgumentException.class, () -> CommitConfig
.cleanText("Whatever", CleanupMode.DEFAULT, '#'));
}
@Test
public void testCleanVerbatim() throws Exception {
String message = "\n \nWhatever \n\n\n# A comment\n\nMore\t \n\n\n";
assertEquals("Unexpected message change", message,
CommitConfig.cleanText(message, CleanupMode.VERBATIM, '#'));
}
@Test
public void testCleanWhitespace() throws Exception {
String message = "\n \nWhatever \n\n\n# A comment\n\nMore\t \n\n\n";
assertEquals("Unexpected message change",
"Whatever\n\n# A comment\n\nMore\n",
CommitConfig.cleanText(message, CleanupMode.WHITESPACE, '#'));
}
@Test
public void testCleanStrip() throws Exception {
String message = "\n \nWhatever \n\n\n# A comment\n\nMore\t \n\n\n";
assertEquals("Unexpected message change", "Whatever\n\nMore\n",
CommitConfig.cleanText(message, CleanupMode.STRIP, '#'));
}
@Test
public void testCleanStripCustomChar() throws Exception {
String message = "\n \nWhatever \n\n\n# Not a comment\n\n <A comment\nMore\t \n\n\n";
assertEquals("Unexpected message change",
"Whatever\n\n# Not a comment\n\nMore\n",
CommitConfig.cleanText(message, CleanupMode.STRIP, '<'));
}
@Test
public void testCleanScissors() throws Exception {
String message = "\n \nWhatever \n\n\n# Not a comment\n\n <A comment\nMore\t \n\n\n"
+ "# ------------------------ >8 ------------------------\n"
+ "More\nMore\n";
assertEquals("Unexpected message change",
"Whatever\n\n# Not a comment\n\n <A comment\nMore\n",
CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
}
@Test
public void testCleanScissorsCustomChar() throws Exception {
String message = "\n \nWhatever \n\n\n# Not a comment\n\n <A comment\nMore\t \n\n\n"
+ "< ------------------------ >8 ------------------------\n"
+ "More\nMore\n";
assertEquals("Unexpected message change",
"Whatever\n\n# Not a comment\n\n <A comment\nMore\n",
CommitConfig.cleanText(message, CleanupMode.SCISSORS, '<'));
}
@Test
public void testCleanScissorsAtTop() throws Exception {
String message = "# ------------------------ >8 ------------------------\n"
+ "\n \nWhatever \n\n\n# Not a comment\n\n <A comment\nMore\t \n\n\n"
+ "More\nMore\n";
assertEquals("Unexpected message change", "",
CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
}
@Test
public void testCleanScissorsNoScissor() throws Exception {
String message = "\n \nWhatever \n\n\n# A comment\n\nMore\t \n\n\n";
assertEquals("Unexpected message change",
"Whatever\n\n# A comment\n\nMore\n",
CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
}
@Test
public void testCleanScissorsNoScissor2() throws Exception {
String message = "Text\n"
+ "## ------------------------ >8 ------------------------\n"
+ "More\nMore\n";
assertEquals("Unexpected message change", message,
CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
}
@Test
public void testCleanScissorsNoScissor3() throws Exception {
String message = "Text\n"
// Wrong number of dashes
+ "# ----------------------- >8 ------------------------\n"
+ "More\nMore\n";
assertEquals("Unexpected message change", message,
CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
}
@Test
public void testCleanScissorsAtEnd() throws Exception {
String message = "Text\n"
+ "# ------------------------ >8 ------------------------\n";
assertEquals("Unexpected message change", "Text\n",
CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#'));
}
private static CommitConfig parse(String content)
throws ConfigInvalidException {
Config c = new Config();
c.fromText(content);
return c.get(CommitConfig.KEY);
}
}

View File

@ -18,11 +18,13 @@
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.text.MessageFormat;
import java.util.Locale;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Config.ConfigEnum;
import org.eclipse.jgit.lib.Config.SectionParser;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.IO;
@ -34,22 +36,76 @@
* @since 5.13
*/
public class CommitConfig {
/**
* Key for {@link Config#get(SectionParser)}.
*/
public static final Config.SectionParser<CommitConfig> KEY = CommitConfig::new;
private static final String CUT = " ------------------------ >8 ------------------------\n"; //$NON-NLS-1$
/**
* How to clean up commit messages when committing.
*
* @since 6.1
*/
public enum CleanupMode implements ConfigEnum {
/**
* {@link #WHITESPACE}, additionally remove comment lines.
*/
STRIP,
/**
* Remove trailing whitespace and leading and trailing empty lines;
* collapse multiple empty lines to a single one.
*/
WHITESPACE,
/**
* Make no changes.
*/
VERBATIM,
/**
* Omit everything from the first "scissor" line on, then apply
* {@link #WHITESPACE}.
*/
SCISSORS,
/**
* Use {@link #STRIP} for user-edited messages, otherwise
* {@link #WHITESPACE}, unless overridden by a git config setting other
* than DEFAULT.
*/
DEFAULT;
@Override
public String toConfigValue() {
return name().toLowerCase(Locale.ROOT);
}
@Override
public boolean matchConfigValue(String in) {
return toConfigValue().equals(in);
}
}
private final static Charset DEFAULT_COMMIT_MESSAGE_ENCODING = StandardCharsets.UTF_8;
private String i18nCommitEncoding;
private String commitTemplatePath;
private CleanupMode cleanupMode;
private CommitConfig(Config rc) {
commitTemplatePath = rc.getString(ConfigConstants.CONFIG_COMMIT_SECTION,
null, ConfigConstants.CONFIG_KEY_COMMIT_TEMPLATE);
i18nCommitEncoding = rc.getString(ConfigConstants.CONFIG_SECTION_I18N,
null, ConfigConstants.CONFIG_KEY_COMMIT_ENCODING);
cleanupMode = rc.getEnum(ConfigConstants.CONFIG_COMMIT_SECTION, null,
ConfigConstants.CONFIG_KEY_CLEANUP, CleanupMode.DEFAULT);
}
/**
@ -74,6 +130,48 @@ public String getCommitEncoding() {
return i18nCommitEncoding;
}
/**
* Retrieves the {@link CleanupMode} as given by git config
* {@code commit.cleanup}.
*
* @return the {@link CleanupMode}; {@link CleanupMode#DEFAULT} if the git
* config is not set
* @since 6.1
*/
@NonNull
public CleanupMode getCleanupMode() {
return cleanupMode;
}
/**
* Computes a non-default {@link CleanupMode} from the given mode and the
* git config.
*
* @param mode
* {@link CleanupMode} to resolve
* @param defaultStrip
* if {@code true} return {@link CleanupMode#STRIP} if the git
* config is also "default", otherwise return
* {@link CleanupMode#WHITESPACE}
* @return the {@code mode}, if it is not {@link CleanupMode#DEFAULT},
* otherwise the resolved mode, which is never
* {@link CleanupMode#DEFAULT}
* @since 6.1
*/
@NonNull
public CleanupMode resolve(@NonNull CleanupMode mode,
boolean defaultStrip) {
if (CleanupMode.DEFAULT == mode) {
CleanupMode defaultMode = getCleanupMode();
if (CleanupMode.DEFAULT == defaultMode) {
return defaultStrip ? CleanupMode.STRIP
: CleanupMode.WHITESPACE;
}
return defaultMode;
}
return mode;
}
/**
* Get the content to the commit template as defined in
* {@code commit.template}. If no {@code i18n.commitEncoding} is specified,
@ -135,4 +233,86 @@ private Charset getEncoding() throws ConfigInvalidException {
return commitMessageEncoding;
}
/**
* Processes a text according to the given {@link CleanupMode}.
*
* @param text
* text to process
* @param mode
* {@link CleanupMode} to use
* @param commentChar
* comment character (normally {@code #}) to use if {@code mode}
* is {@link CleanupMode#STRIP} or {@link CleanupMode#SCISSORS}
* @return the processed text
* @throws IllegalArgumentException
* if {@code mode} is {@link CleanupMode#DEFAULT} (use
* {@link #resolve(CleanupMode, boolean)} first)
* @since 6.1
*/
public static String cleanText(@NonNull String text,
@NonNull CleanupMode mode, char commentChar) {
String toProcess = text;
boolean strip = false;
switch (mode) {
case VERBATIM:
return text;
case SCISSORS:
String cut = commentChar + CUT;
if (text.startsWith(cut)) {
return ""; //$NON-NLS-1$
}
int cutPos = text.indexOf('\n' + cut);
if (cutPos >= 0) {
toProcess = text.substring(0, cutPos + 1);
}
break;
case STRIP:
strip = true;
break;
case WHITESPACE:
break;
case DEFAULT:
default:
// Internal error; no translation
throw new IllegalArgumentException("Invalid clean-up mode " + mode); //$NON-NLS-1$
}
// WHITESPACE
StringBuilder result = new StringBuilder();
boolean lastWasEmpty = true;
for (String line : toProcess.split("\n")) { //$NON-NLS-1$
line = line.stripTrailing();
if (line.isEmpty()) {
if (!lastWasEmpty) {
result.append('\n');
lastWasEmpty = true;
}
} else if (!strip || !isComment(line, commentChar)) {
lastWasEmpty = false;
result.append(line).append('\n');
}
}
int bufferSize = result.length();
if (lastWasEmpty && bufferSize > 0) {
bufferSize--;
result.setLength(bufferSize);
}
if (bufferSize > 0 && !toProcess.endsWith("\n")) { //$NON-NLS-1$
if (result.charAt(bufferSize - 1) == '\n') {
result.setLength(bufferSize - 1);
}
}
return result.toString();
}
private static boolean isComment(String text, char commentChar) {
int len = text.length();
for (int i = 0; i < len; i++) {
char ch = text.charAt(i);
if (!Character.isWhitespace(ch)) {
return ch == commentChar;
}
}
return false;
}
}

View File

@ -181,6 +181,13 @@ public final class ConfigConstants {
*/
public static final String CONFIG_TAG_SECTION = "tag";
/**
* The "cleanup" key
*
* @since 6.1
*/
public static final String CONFIG_KEY_CLEANUP = "cleanup";
/**
* The "gpgSign" key
*