diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java index d95d7814e..7066f9d42 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/CommitConfigTest.java @@ -11,7 +11,10 @@ package org.eclipse.jgit.lib; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.CommitConfig.CleanupMode; @@ -169,6 +172,82 @@ public void testCleanScissorsAtEnd() throws Exception { CommitConfig.cleanText(message, CleanupMode.SCISSORS, '#')); } + @Test + public void testCommentCharDefault() throws Exception { + CommitConfig cfg = parse(""); + assertEquals('#', cfg.getCommentChar()); + assertFalse(cfg.isAutoCommentChar()); + } + + @Test + public void testCommentCharAuto() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = auto\n"); + assertEquals('#', cfg.getCommentChar()); + assertTrue(cfg.isAutoCommentChar()); + } + + @Test + public void testCommentCharEmpty() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar =\n"); + assertEquals('#', cfg.getCommentChar()); + } + + @Test + public void testCommentCharInvalid() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = \" \"\n"); + assertEquals('#', cfg.getCommentChar()); + } + + @Test + public void testCommentCharNonAscii() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = รถ\n"); + assertEquals('#', cfg.getCommentChar()); + } + + @Test + public void testCommentChar() throws Exception { + CommitConfig cfg = parse("[core]\n\tcommentChar = _\n"); + assertEquals('_', cfg.getCommentChar()); + } + + @Test + public void testDetermineCommentChar() throws Exception { + String text = "A commit message\n\nBody\n"; + assertEquals('#', CommitConfig.determineCommentChar(text)); + } + + @Test + public void testDetermineCommentChar2() throws Exception { + String text = "A commit message\n\nBody\n\n# Conflicts:\n#\tfoo.txt\n"; + char ch = CommitConfig.determineCommentChar(text); + assertNotEquals('#', ch); + assertTrue(ch > ' ' && ch < 127); + } + + @Test + public void testDetermineCommentChar3() throws Exception { + String text = "A commit message\n\n;Body\n\n# Conflicts:\n#\tfoo.txt\n"; + char ch = CommitConfig.determineCommentChar(text); + assertNotEquals('#', ch); + assertNotEquals(';', ch); + assertTrue(ch > ' ' && ch < 127); + } + + @Test + public void testDetermineCommentChar4() throws Exception { + String text = "A commit message\n\nBody\n\n # Conflicts:\n\t #\tfoo.txt\n"; + char ch = CommitConfig.determineCommentChar(text); + assertNotEquals('#', ch); + assertTrue(ch > ' ' && ch < 127); + } + + @Test + public void testDetermineCommentChar5() throws Exception { + String text = "A commit message\n\nBody\n\n#a\n;b\n@c\n!d\n$\n%\n^\n&\n|\n:"; + char ch = CommitConfig.determineCommentChar(text); + assertEquals(0, ch); + } + private static CommitConfig parse(String content) throws ConfigInvalidException { Config c = new Config(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java index 55cc02683..6a9b45b06 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Julian Ruppel + * Copyright (c) 2020, 2022 Julian Ruppel 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 @@ -29,6 +29,7 @@ import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; +import org.eclipse.jgit.util.StringUtils; /** * The standard "commit" configuration parameters. @@ -44,6 +45,9 @@ public class CommitConfig { private static final String CUT = " ------------------------ >8 ------------------------\n"; //$NON-NLS-1$ + private static final char[] COMMENT_CHARS = { '#', ';', '@', '!', '$', '%', + '^', '&', '|', ':' }; + /** * How to clean up commit messages when committing. * @@ -99,6 +103,10 @@ public boolean matchConfigValue(String in) { private CleanupMode cleanupMode; + private char commentCharacter = '#'; + + private boolean autoCommentChar = false; + private CommitConfig(Config rc) { commitTemplatePath = rc.getString(ConfigConstants.CONFIG_COMMIT_SECTION, null, ConfigConstants.CONFIG_KEY_COMMIT_TEMPLATE); @@ -106,6 +114,18 @@ private CommitConfig(Config rc) { null, ConfigConstants.CONFIG_KEY_COMMIT_ENCODING); cleanupMode = rc.getEnum(ConfigConstants.CONFIG_COMMIT_SECTION, null, ConfigConstants.CONFIG_KEY_CLEANUP, CleanupMode.DEFAULT); + String comment = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, null, + ConfigConstants.CONFIG_KEY_COMMENT_CHAR); + if (!StringUtils.isEmptyOrNull(comment)) { + if ("auto".equalsIgnoreCase(comment)) { //$NON-NLS-1$ + autoCommentChar = true; + } else { + char first = comment.charAt(0); + if (first > ' ' && first < 127) { + commentCharacter = first; + } + } + } } /** @@ -130,6 +150,51 @@ public String getCommitEncoding() { return i18nCommitEncoding; } + /** + * Retrieves the comment character set by git config + * {@code core.commentChar}. + * + * @return the character to use for comments in commit messages + * @since 6.2 + */ + public char getCommentChar() { + return commentCharacter; + } + + /** + * Determines the comment character to use for a particular text. If + * {@code core.commentChar} is "auto", tries to determine an unused + * character; if none is found, falls back to '#'. Otherwise returns the + * character given by {@code core.commentChar}. + * + * @param text + * existing text + * + * @return the character to use + * @since 6.2 + */ + public char getCommentChar(String text) { + if (isAutoCommentChar()) { + char toUse = determineCommentChar(text); + if (toUse > 0) { + return toUse; + } + return '#'; + } + return getCommentChar(); + } + + /** + * Tells whether the comment character should be determined by choosing a + * character not occurring in a commit message. + * + * @return {@code true} if git config {@code core.commentChar} is "auto" + * @since 6.2 + */ + public boolean isAutoCommentChar() { + return autoCommentChar; + } + /** * Retrieves the {@link CleanupMode} as given by git config * {@code commit.cleanup}. @@ -315,4 +380,41 @@ private static boolean isComment(String text, char commentChar) { } return false; } + + /** + * Determines a comment character by choosing one from a limited set of + * 7-bit ASCII characters that do not occur in the given text at the + * beginning of any line. If none can be determined, {@code (char) 0} is + * returned. + * + * @param text + * to get a comment character for + * @return the comment character, or {@code (char) 0} if none could be + * determined + * @since 6.2 + */ + public static char determineCommentChar(String text) { + if (StringUtils.isEmptyOrNull(text)) { + return '#'; + } + final boolean[] inUse = new boolean[127]; + for (String line : text.split("\n")) { //$NON-NLS-1$ + int len = line.length(); + for (int i = 0; i < len; i++) { + char ch = line.charAt(i); + if (!Character.isWhitespace(ch)) { + if (ch >= 0 && ch < inUse.length) { + inUse[ch] = true; + } + break; + } + } + } + for (char candidate : COMMENT_CHARS) { + if (!inUse[candidate]) { + return candidate; + } + } + return (char) 0; + } } \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 80d720ae4..8ad32d41c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -2,7 +2,7 @@ * Copyright (C) 2010, Mathias Kinzler * Copyright (C) 2010, Chris Aniszczyk * Copyright (C) 2012-2013, Robin Rosenberg - * Copyright (C) 2018-2021, Andre Bossert and others + * Copyright (C) 2018-2022, Andre Bossert 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 @@ -202,6 +202,13 @@ public final class ConfigConstants { */ public static final String CONFIG_KEY_FORCE_SIGN_ANNOTATED = "forceSignAnnotated"; + /** + * The "commentChar" key. + * + * @since 6.2 + */ + public static final String CONFIG_KEY_COMMENT_CHAR = "commentChar"; + /** * The "hooksPath" key. *