]> source.dussan.org Git - jgit.git/commitdiff
Support built-in diff drivers for hunk header function names 34/1203734/10
authorKaushik Lingarkar <quic_kaushikl@quicinc.com>
Tue, 19 Nov 2024 17:37:33 +0000 (09:37 -0800)
committerMatthias Sohn <matthias.sohn@sap.com>
Wed, 20 Nov 2024 09:53:18 +0000 (10:53 +0100)
The regexes defined for each built-in driver will be used to determine
the function name for a hunk header. Each driver can specify a list of
regexes to negate and match. They can also define pattern compilation
flags if needed. These drivers only apply to text files with unified
patch type.

Following built-in drivers have been added:
- cpp
- dts
- java
- python
- rust

Support for more languages can be added as needed to match the cgit
implementation.

Change-Id: Ice5430bfed7e4aaf9f00e52e44402479984953c5

org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.c [new file with mode: 0644]
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.javasource [new file with mode: 0644]
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.py [new file with mode: 0644]
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.rs [new file with mode: 0644]
org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/sample.dtsi [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java [new file with mode: 0644]
org.eclipse.jgit/.settings/.api_filters
org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java

diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.c b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.c
new file mode 100644 (file)
index 0000000..3661160
--- /dev/null
@@ -0,0 +1,43 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+void getGreeting(char *result, const char *name) {
+    sprintf(result, "Hello, %s!", name);
+}
+
+void getFarewell(char *result, const char *name) {
+    sprintf(result, "Goodbye, %s. Have a great day!", name);
+}
+
+void toLower(char *str) {
+    for (int i = 0; str[i]; i++) {
+        str[i] = tolower(str[i]);
+    }
+}
+
+void getPersonalizedGreeting(char *result, const char *name, const char *timeOfDay) {
+    char timeOfDayLower[50];
+    strcpy(timeOfDayLower, timeOfDay);
+    toLower(timeOfDayLower);
+    if (strcmp(timeOfDayLower, "morning") == 0) {
+        sprintf(result, "Good morning, %s", name);
+    } else if (strcmp(timeOfDayLower, "afternoon") == 0) {
+        sprintf(result, "Good afternoon, %s", name);
+    } else if (strcmp(timeOfDayLower, "evening") == 0) {
+        sprintf(result, "Good evening, %s", name);
+    } else {
+        sprintf(result, "Good day, %s", name);
+    }
+}
+
+int main() {
+    char result[100];
+    getGreeting(result, "foo");
+    printf("%s\\n", result);
+    getFarewell(result, "bar");
+    printf("%s\\n", result);
+    getPersonalizedGreeting(result, "baz", "morning");
+    printf("%s\\n", result);
+    return 0;
+}
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.javasource b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.javasource
new file mode 100644 (file)
index 0000000..9659685
--- /dev/null
@@ -0,0 +1,37 @@
+public class Greeting {
+    public String getGreeting(String name) {
+        String msg = "Hello, " + name + "!";
+        return msg;
+    }
+
+    public String getFarewell(String name) {
+        String msg = "Goodbye, " + name + ". Have a great day!";
+        return msg;
+    }
+
+    public String getPersonalizedGreeting(String name, String timeOfDay) {
+        String msg;
+        switch (timeOfDay.toLowerCase()) {
+        case "morning":
+            msg = "Good morning, " + name;
+            break;
+        case "afternoon":
+            msg = "Good afternoon, " + name;
+            break;
+        case "evening":
+            msg = "Good evening, " + name;
+            break;
+        default:
+            msg = "Good day, " + name;
+            break;
+        }
+        return msg;
+    }
+
+    public static void main(String[] args) {
+        Greeting greeting = new Greeting();
+        System.out.println(greeting.getGreeting("foo"));
+        System.out.println(greeting.getFarewell("bar"));
+        System.out.println(greeting.getPersonalizedGreeting("baz", "morning"));
+    }
+}
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.py b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.py
new file mode 100644 (file)
index 0000000..9eda6cd
--- /dev/null
@@ -0,0 +1,26 @@
+class Greeting:
+    def get_greeting(self, name):
+        greeting_message = f"Hello, {name}!"
+        return greeting_message
+
+    def get_farewell(self, name):
+        farewell_message = f"Goodbye, {name}. Have a great day!"
+        return farewell_message
+
+    def get_personalized_greeting(self, name, time_of_day):
+        time_of_day = time_of_day.lower()
+        if time_of_day == "morning":
+            personalized_message = f"Good morning, {name}"
+        elif time_of_day == "afternoon":
+            personalized_message = f"Good afternoon, {name}"
+        elif time_of_day == "evening":
+            personalized_message = f"Good evening, {name}"
+        else:
+            personalized_message = f"Good day, {name}"
+        return personalized_message
+
+if __name__ == "__main__":
+    greeting = Greeting()
+    print(greeting.get_greeting("foo"))
+    print(greeting.get_farewell("bar"))
+    print(greeting.get_personalized_greeting("baz", "morning"))
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.rs b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.rs
new file mode 100644 (file)
index 0000000..a3aa5cb
--- /dev/null
@@ -0,0 +1,27 @@
+struct Greeting;
+
+impl Greeting {
+    fn get_greeting(&self, name: &str) -> String {
+        format!("Hello, {}!", name)
+    }
+
+    fn get_farewell(&self, name: &str) -> String {
+        format!("Goodbye, {}. Have a great day!", name)
+    }
+
+    fn get_personalized_greeting(&self, name: &str, time_of_day: &str) -> String {
+        match time_of_day.to_lowercase().as_str() {
+            "morning" => format!("Good morning, {}", name),
+            "afternoon" => format!("Good afternoon, {}", name),
+            "evening" => format!("Good evening, {}", name),
+            _ => format!("Good day, {}", name),
+        }
+    }
+}
+
+fn main() {
+    let greeting = Greeting;
+    println!("{}", greeting.get_greeting("foo"));
+    println!("{}", greeting.get_farewell("bar"));
+    println!("{}", greeting.get_personalized_greeting("baz", "morning"));
+}
diff --git a/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/sample.dtsi b/org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/sample.dtsi
new file mode 100644 (file)
index 0000000..6aa4ecd
--- /dev/null
@@ -0,0 +1,25 @@
+/dts-v1/;
+
+/ {
+       model = "Example Board";
+       compatible = "example,board";
+       cpus {
+               cpu@0 {
+                       device_type = "cpu";
+                       compatible = "arm,cortex-a9";
+                       reg = <0>;
+               };
+       };
+
+       memory {
+               device_type = "memory";
+               reg = <0x80000000 0x20000000>;
+       };
+
+       uart0: uart@101f1000 {
+               compatible = "ns16550a";
+               reg = <0x101f1000 0x1000>;
+               interrupts = <5>;
+               clock-frequency = <24000000>;
+       };
+};
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java
new file mode 100644 (file)
index 0000000..1352871
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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.diff;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.junit.Test;
+
+public class DiffFormatterBuiltInDriverTest extends RepositoryTestCase {
+       @Test
+       public void testCppDriver() throws Exception {
+               String fileName = "greeting.c";
+               String body = Files.readString(
+                               Path.of(JGitTestUtil.getTestResourceFile(fileName)
+                                               .getAbsolutePath()));
+               RevCommit c1;
+               RevCommit c2;
+               try (Git git = new Git(db)) {
+                       createCommit(git, ".gitattributes", "*.c   diff=cpp");
+                       c1 = createCommit(git, fileName, body);
+                       c2 = createCommit(git, fileName,
+                                       body.replace("Good day", "Greetings")
+                                                       .replace("baz", "qux"));
+               }
+               try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+                               DiffFormatter diffFormatter = new DiffFormatter(os)) {
+                       String actual = getHunkHeaders(c1, c2, os, diffFormatter);
+                       String expected =
+                                       "@@ -27,7 +27,7 @@ void getPersonalizedGreeting(char *result, const char *name, const char *timeOfD\n"
+                                                       + "@@ -37,7 +37,7 @@ int main() {";
+                       assertEquals(expected, actual);
+               }
+       }
+
+       @Test
+       public void testDtsDriver() throws Exception {
+               String fileName = "sample.dtsi";
+               String body = Files.readString(
+                               Path.of(JGitTestUtil.getTestResourceFile(fileName)
+                                               .getAbsolutePath()));
+               RevCommit c1;
+               RevCommit c2;
+               try (Git git = new Git(db)) {
+                       createCommit(git, ".gitattributes", "*.dtsi   diff=dts");
+                       c1 = createCommit(git, fileName, body);
+                       c2 = createCommit(git, fileName,
+                                       body.replace("clock-frequency = <24000000>",
+                                                       "clock-frequency = <48000000>"));
+               }
+               try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+                               DiffFormatter diffFormatter = new DiffFormatter(os)) {
+                       String actual = getHunkHeaders(c1, c2, os, diffFormatter);
+                       String expected = "@@ -20,6 +20,6 @@ uart0: uart@101f1000 {";
+                       assertEquals(expected, actual);
+               }
+       }
+
+       @Test
+       public void testJavaDriver() throws Exception {
+               String resourceName = "greeting.javasource";
+               String body = Files.readString(
+                               Path.of(JGitTestUtil.getTestResourceFile(resourceName)
+                                               .getAbsolutePath()));
+               RevCommit c1;
+               RevCommit c2;
+               try (Git git = new Git(db)) {
+                       createCommit(git, ".gitattributes", "*.java   diff=java");
+                       String fileName = "Greeting.java";
+                       c1 = createCommit(git, fileName, body);
+                       c2 = createCommit(git, fileName,
+                                       body.replace("Good day", "Greetings")
+                                                       .replace("baz", "qux"));
+               }
+               try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+                               DiffFormatter diffFormatter = new DiffFormatter(os)) {
+                       String actual = getHunkHeaders(c1, c2, os, diffFormatter);
+                       String expected =
+                                       "@@ -22,7 +22,7 @@ public String getPersonalizedGreeting(String name, String timeOfDay) {\n"
+                                                       + "@@ -32,6 +32,6 @@ public static void main(String[] args) {";
+                       assertEquals(expected, actual);
+               }
+       }
+
+       @Test
+       public void testPythonDriver() throws Exception {
+               String fileName = "greeting.py";
+               String body = Files.readString(
+                               Path.of(JGitTestUtil.getTestResourceFile(fileName)
+                                               .getAbsolutePath()));
+               RevCommit c1;
+               RevCommit c2;
+               try (Git git = new Git(db)) {
+                       createCommit(git, ".gitattributes", "*.py   diff=python");
+                       c1 = createCommit(git, fileName, body);
+                       c2 = createCommit(git, fileName,
+                                       body.replace("Good day", "Greetings"));
+               }
+               try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+                               DiffFormatter diffFormatter = new DiffFormatter(os)) {
+                       String actual = getHunkHeaders(c1, c2, os, diffFormatter);
+                       String expected = "@@ -16,7 +16,7 @@ def get_personalized_greeting(self, name, time_of_day):";
+                       assertEquals(expected, actual);
+               }
+       }
+
+       @Test
+       public void testRustDriver() throws Exception {
+               String fileName = "greeting.rs";
+               String body = Files.readString(
+                               Path.of(JGitTestUtil.getTestResourceFile(fileName)
+                                               .getAbsolutePath()));
+               RevCommit c1;
+               RevCommit c2;
+               try (Git git = new Git(db)) {
+                       createCommit(git, ".gitattributes", "*.rs   diff=rust");
+                       c1 = createCommit(git, fileName, body);
+                       c2 = createCommit(git, fileName,
+                                       body.replace("Good day", "Greetings")
+                                                       .replace("baz", "qux"));
+               }
+               try (ByteArrayOutputStream os = new ByteArrayOutputStream();
+                               DiffFormatter diffFormatter = new DiffFormatter(os)) {
+                       String actual = getHunkHeaders(c1, c2, os, diffFormatter);
+                       String expected =
+                                       "@@ -14,7 +14,7 @@ fn get_personalized_greeting(&self, name: &str, time_of_day: &str) -> String {\n"
+                                                       + "@@ -23,5 +23,5 @@ fn main() {";
+                       assertEquals(expected, actual);
+               }
+       }
+
+       private String getHunkHeaders(RevCommit c1, RevCommit c2,
+                       ByteArrayOutputStream os, DiffFormatter diffFormatter)
+                       throws IOException {
+               diffFormatter.setRepository(db);
+               diffFormatter.format(new CanonicalTreeParser(null, db.newObjectReader(),
+                                               c1.getTree()),
+                               new CanonicalTreeParser(null, db.newObjectReader(),
+                                               c2.getTree()));
+               diffFormatter.flush();
+               return Arrays.stream(os.toString(StandardCharsets.UTF_8).split("\n"))
+                               .filter(line -> line.startsWith("@@"))
+                               .collect(Collectors.joining("\n"));
+       }
+
+       private RevCommit createCommit(Git git, String fileName, String body)
+                       throws IOException, GitAPIException {
+               writeTrashFile(fileName, body);
+               git.add().addFilepattern(".").call();
+               return git.commit().setMessage("message").call();
+       }
+}
index 2c22f0231c419efdeb8c7d9185cfd3fc7b7d9747..d3ae4cf376679b198668b530abbb73e3f8bdbfab 100644 (file)
@@ -1,5 +1,33 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <component id="org.eclipse.jgit" version="2">
+    <resource path="src/org/eclipse/jgit/diff/DiffDriver.java" type="org.eclipse.jgit.diff.DiffDriver">
+        <filter id="1109393411">
+            <message_arguments>
+                <message_argument value="6.10.1"/>
+                <message_argument value="org.eclipse.jgit.diff.DiffDriver"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/diff/DiffFormatter.java" type="org.eclipse.jgit.diff.DiffFormatter">
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="6.10.1"/>
+                <message_argument value="format(EditList, RawText, RawText, DiffDriver)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="6.10.1"/>
+                <message_argument value="format(FileHeader, RawText, RawText, DiffDriver)"/>
+            </message_arguments>
+        </filter>
+        <filter id="1142947843">
+            <message_arguments>
+                <message_argument value="6.10.1"/>
+                <message_argument value="writeHunkHeader(int, int, int, int, String)"/>
+            </message_arguments>
+        </filter>
+    </resource>
     <resource path="src/org/eclipse/jgit/gitrepo/RepoProject.java" type="org.eclipse.jgit.gitrepo.RepoProject">
         <filter id="1141899266">
             <message_arguments>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java
new file mode 100644 (file)
index 0000000..9ae1aaa
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2024 Qualcomm Innovation Center, Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * 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.diff;
+
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * Built-in drivers for various languages, sorted by name. These drivers will be
+ * used to determine function names for a hunk.
+ * <p>
+ * When writing or updating patterns, assume the contents are syntactically
+ * correct. Patterns can be simple and need not cover all syntactical corner
+ * cases, as long as they are sufficiently permissive.
+ *
+ * @since 6.10.1
+ */
+@SuppressWarnings({"ImmutableEnumChecker", "nls"})
+public enum DiffDriver {
+       /**
+        * Built-in diff driver for <a
+        * href="https://learn.microsoft.com/en-us/cpp/cpp/cpp-language-reference">c++</a>
+        */
+       cpp(List.of(
+                       /* Jump targets or access declarations */
+                       "^[ \\t]*[A-Za-z_][A-Za-z_0-9]*:\\s*($|/[/*])"), List.of(
+                       /* functions/methods, variables, and compounds at top level */
+                       "^((::\\s*)?[A-Za-z_].*)$")),
+       /**
+        * Built-in diff driver for <a
+        * href="https://devicetree-specification.readthedocs.io/en/stable/source-language.html">device
+        * tree files</a>
+        */
+       dts(List.of(";", "="), List.of(
+                       /* lines beginning with a word optionally preceded by '&' or the root */
+                       "^[ \\t]*((/[ \\t]*\\{|&?[a-zA-Z_]).*)")),
+       /**
+        * Built-in diff driver for <a
+        * href="https://docs.oracle.com/javase/specs/jls/se21/html/index.html">java</a>
+        */
+       java(List.of(
+                       "^[ \\t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)"),
+                       List.of(
+                                       /* Class, enum, interface, and record declarations */
+                                       "^[ \\t]*(([a-z-]+[ \\t]+)*(class|enum|interface|record)[ \\t]+.*)$",
+                                       /* Method definitions; note that constructor signatures are not */
+                                       /* matched because they are indistinguishable from method calls. */
+                                       "^[ \\t]*(([A-Za-z_<>&\\]\\[][?&<>.,A-Za-z_0-9]*[ \\t]+)+[A-Za-z_]"
+                                                       + "[A-Za-z_0-9]*[ \\t]*\\([^;]*)$")),
+       /**
+        * Built-in diff driver for <a
+        * href="https://docs.python.org/3/reference/index.html">python</a>
+        */
+       python(List.of("^[ \\t]*((class|(async[ \\t]+)?def)[ \\t].*)$")),
+       /**
+        * Built-in diff driver for <a *
+        * href="https://doc.rust-lang.org/reference/introduction.html">java</a>
+        */
+       rust(List.of("^[\\t ]*((pub(\\([^\\)]+\\))?[\\t ]+)?"
+                       + "((async|const|unsafe|extern([\\t ]+\"[^\"]+\"))[\\t ]+)?"
+                       + "(struct|enum|union|mod|trait|fn|impl|macro_rules!)[< \\t]+[^;]*)$"));
+
+       private final List<Pattern> negatePatterns;
+
+       private final List<Pattern> matchPatterns;
+
+       DiffDriver(List<String> negate, List<String> match, int flags) {
+               if (negate != null) {
+                       this.negatePatterns = negate.stream()
+                                       .map(r -> Pattern.compile(r, flags))
+                                       .collect(Collectors.toList());
+               } else {
+                       this.negatePatterns = null;
+               }
+               this.matchPatterns = match.stream().map(r -> Pattern.compile(r, flags))
+                               .collect(Collectors.toList());
+       }
+
+       DiffDriver(List<String> match) {
+               this(null, match, 0);
+       }
+
+       DiffDriver(List<String> negate, List<String> match) {
+               this(negate, match, 0);
+       }
+
+       /**
+        * Returns the list of patterns used to exclude certain lines from being
+        * considered as function names.
+        *
+        * @return the list of negate patterns
+        */
+       public List<Pattern> getNegatePatterns() {
+               return negatePatterns;
+       }
+
+       /**
+        * Returns the list of patterns used to match lines for potential function
+        * names.
+        *
+        * @return the list of match patterns
+        */
+       public List<Pattern> getMatchPatterns() {
+               return matchPatterns;
+       }
+}
index 2f472b5c0aee2c91e9cff8ab83102cfc15eb6aa5..fa446e18cd8eb752d8c5db38982f6d3f3ee10e32 100644 (file)
@@ -30,7 +30,9 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
+import java.util.regex.Pattern;
 import org.eclipse.jgit.api.errors.CanceledException;
+import org.eclipse.jgit.attributes.Attribute;
 import org.eclipse.jgit.diff.DiffAlgorithm.SupportedAlgorithm;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
 import org.eclipse.jgit.dircache.DirCacheIterator;
@@ -703,7 +705,7 @@ public class DiffFormatter implements AutoCloseable {
         */
        public void format(DiffEntry ent) throws IOException {
                FormatResult res = createFormatResult(ent);
-               format(res.header, res.a, res.b);
+               format(res.header, res.a, res.b, getDiffDriver(ent));
        }
 
        private static byte[] writeGitLinkText(AbbreviatedObjectId id) {
@@ -749,11 +751,14 @@ public class DiffFormatter implements AutoCloseable {
         *            text source for the post-image version of the content. This
         *            must match the content of
         *            {@link org.eclipse.jgit.patch.FileHeader#getNewId()}.
+        * @param diffDriver
+        *            the diff driver used to obtain function names in hunk headers
         * @throws java.io.IOException
-        *             writing to the supplied stream failed.
+        *            writing to the supplied stream failed.
+        * @since 6.10.1
         */
-       public void format(FileHeader head, RawText a, RawText b)
-                       throws IOException {
+       public void format(FileHeader head, RawText a, RawText b,
+                       DiffDriver diffDriver) throws IOException {
                // Reuse the existing FileHeader as-is by blindly copying its
                // header lines, but avoiding its hunks. Instead we recreate
                // the hunks from the text instances we have been supplied.
@@ -763,8 +768,49 @@ public class DiffFormatter implements AutoCloseable {
                if (!head.getHunks().isEmpty())
                        end = head.getHunks().get(0).getStartOffset();
                out.write(head.getBuffer(), start, end - start);
-               if (head.getPatchType() == PatchType.UNIFIED)
-                       format(head.toEditList(), a, b);
+               if (head.getPatchType() == PatchType.UNIFIED) {
+                       format(head.toEditList(), a, b, diffDriver);
+               }
+       }
+
+       /**
+        * Format a patch script, reusing a previously parsed FileHeader.
+        * <p>
+        * This formatter is primarily useful for editing an existing patch script
+        * to increase or reduce the number of lines of context within the script.
+        * All header lines are reused as-is from the supplied FileHeader.
+        *
+        * @param head
+        *              existing file header containing the header lines to copy.
+        * @param a
+        *              text source for the pre-image version of the content. This must match
+        *              the content of {@link org.eclipse.jgit.patch.FileHeader#getOldId()}.
+        * @param b
+        *              text source for the post-image version of the content. This must match
+        *              the content of {@link org.eclipse.jgit.patch.FileHeader#getNewId()}.
+        * @throws java.io.IOException
+        *              writing to the supplied stream failed.
+        */
+       public void format(FileHeader head, RawText a, RawText b)
+                       throws IOException {
+               format(head, a, b, null);
+       }
+
+       /**
+        * Formats a list of edits in unified diff format
+        *
+        * @param edits
+        *              some differences which have been calculated between A and B
+        * @param a
+        *              the text A which was compared
+        * @param b
+        *              the text B which was compared
+        * @throws java.io.IOException
+        *              if an IO error occurred
+        */
+       public void format(EditList edits, RawText a, RawText b)
+                       throws IOException {
+               format(edits, a, b, null);
        }
 
        /**
@@ -776,11 +822,14 @@ public class DiffFormatter implements AutoCloseable {
         *            the text A which was compared
         * @param b
         *            the text B which was compared
+        * @param diffDriver
+        *            the diff driver used to obtain function names in hunk headers
         * @throws java.io.IOException
         *             if an IO error occurred
+        * @since 6.10.1
         */
-       public void format(EditList edits, RawText a, RawText b)
-                       throws IOException {
+       public void format(EditList edits, RawText a, RawText b,
+                       DiffDriver diffDriver) throws IOException {
                for (int curIdx = 0; curIdx < edits.size();) {
                        Edit curEdit = edits.get(curIdx);
                        final int endIdx = findCombinedEnd(edits, curIdx);
@@ -791,7 +840,8 @@ public class DiffFormatter implements AutoCloseable {
                        final int aEnd = (int) Math.min(a.size(), (long) endEdit.getEndA() + context);
                        final int bEnd = (int) Math.min(b.size(), (long) endEdit.getEndB() + context);
 
-                       writeHunkHeader(aCur, aEnd, bCur, bEnd);
+                       writeHunkHeader(aCur, aEnd, bCur, bEnd,
+                                       getFuncName(a, aCur - 1, diffDriver));
 
                        while (aCur < aEnd || bCur < bEnd) {
                                if (aCur < curEdit.getBeginA() || endIdx + 1 < curIdx) {
@@ -881,8 +931,30 @@ public class DiffFormatter implements AutoCloseable {
         * @throws java.io.IOException
         *             if an IO error occurred
         */
-       protected void writeHunkHeader(int aStartLine, int aEndLine,
-                       int bStartLine, int bEndLine) throws IOException {
+       protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine,
+                       int bEndLine) throws IOException {
+               writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine, null);
+       }
+
+       /**
+        * Output a hunk header
+        *
+        * @param aStartLine
+        *            within first source
+        * @param aEndLine
+        *            within first source
+        * @param bStartLine
+        *            within second source
+        * @param bEndLine
+        *            within second source
+        * @param funcName
+        *            function name of this hunk
+        * @throws java.io.IOException
+        *             if an IO error occurred
+        * @since 6.10.1
+        */
+       protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine,
+                       int bEndLine, String funcName) throws IOException {
                out.write('@');
                out.write('@');
                writeRange('-', aStartLine + 1, aEndLine - aStartLine);
@@ -890,6 +962,10 @@ public class DiffFormatter implements AutoCloseable {
                out.write(' ');
                out.write('@');
                out.write('@');
+               if (funcName != null) {
+                       out.write(' ');
+                       out.write(funcName.getBytes());
+               }
                out.write('\n');
        }
 
@@ -1247,4 +1323,46 @@ public class DiffFormatter implements AutoCloseable {
        private static boolean end(Edit edit, int a, int b) {
                return edit.getEndA() <= a && edit.getEndB() <= b;
        }
+
+       private String getFuncName(RawText text, int startAt,
+                       DiffDriver diffDriver) {
+               if (diffDriver != null) {
+                       while (startAt > 0) {
+                               String line = text.getString(startAt);
+                               startAt--;
+                               if (matchesAny(diffDriver.getNegatePatterns(), line)) {
+                                       continue;
+                               }
+                               if (matchesAny(diffDriver.getMatchPatterns(), line)) {
+                                       String funcName = line.replaceAll("^[ \\t]+", "");
+                                       return funcName.substring(0,
+                                                       Math.min(funcName.length(), 80)).trim();
+                               }
+                       }
+               }
+               return null;
+       }
+
+       private boolean matchesAny(List<Pattern> patterns, String text) {
+               if (patterns != null) {
+                       for (Pattern p : patterns) {
+                               if (p.matcher(text).find()) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+
+       private DiffDriver getDiffDriver(DiffEntry entry) {
+               Attribute diffAttr = entry.getDiffAttribute();
+               if (diffAttr != null) {
+                       try {
+                               return DiffDriver.valueOf(diffAttr.getValue());
+                       } catch (IllegalArgumentException e) {
+                               return null;
+                       }
+               }
+               return null;
+       }
 }
index 4343642f9a291282b7a1f559a6ea3d8c35ea7e8a..b401bbe73db80c541335d48c47e9a7fe2c61ba8b 100644 (file)
@@ -44,8 +44,8 @@ public class PatchIdDiffFormatter extends DiffFormatter {
        }
 
        @Override
-       protected void writeHunkHeader(int aStartLine, int aEndLine,
-                       int bStartLine, int bEndLine) throws IOException {
+       protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine,
+                       int bEndLine, String funcName) throws IOException {
                // The hunk header is not taken into account for patch id calculation
        }