From 4b3c5194acd8a9b18bf269888cab2ea29c376508 Mon Sep 17 00:00:00 2001
From: Kaushik Lingarkar <quic_kaushikl@quicinc.com>
Date: Tue, 19 Nov 2024 09:37:33 -0800
Subject: [PATCH] Support built-in diff drivers for hunk header function names

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
---
 .../eclipse/jgit/test/resources/greeting.c    | Bin 0 -> 1203 bytes
 .../jgit/test/resources/greeting.javasource   | Bin 0 -> 1081 bytes
 .../eclipse/jgit/test/resources/greeting.py   | Bin 0 -> 961 bytes
 .../eclipse/jgit/test/resources/greeting.rs   | Bin 0 -> 845 bytes
 .../eclipse/jgit/test/resources/sample.dtsi   | Bin 0 -> 384 bytes
 .../diff/DiffFormatterBuiltInDriverTest.java  | 173 ++++++++++++++++++
 org.eclipse.jgit/.settings/.api_filters       |  28 +++
 .../src/org/eclipse/jgit/diff/DiffDriver.java | 116 ++++++++++++
 .../org/eclipse/jgit/diff/DiffFormatter.java  | 140 ++++++++++++--
 .../jgit/diff/PatchIdDiffFormatter.java       |   4 +-
 10 files changed, 448 insertions(+), 13 deletions(-)
 create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.c
 create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.javasource
 create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.py
 create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/greeting.rs
 create mode 100644 org.eclipse.jgit.test/tst-rsrc/org/eclipse/jgit/test/resources/sample.dtsi
 create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java
 create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.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
index 0000000000000000000000000000000000000000..366116092188f04fca1e85b50f101b093abf92d8
GIT binary patch
literal 1203
zcmY$+%uCKGO-WU-DK1IL%-74X<3biH%FIhg6-zFuEJy_jah2s~rYNMRmbe$Crj}&p
zrE4T-Bo--X6{QxJ=9K6tB<JT9mncBQ@)C1XH5ID46ciK`iVKP|^Geb*VCs}SQgd?h
zbre*K6_s=pz$&e|YPsOXyCoK-mZ#?AkZZhqett?)Wh%%5Jq3@%vQ&jch4iA-#1e&+
z#7ZnymgM{7m!}p%tt&1mf;l`bzeqtNGp|G;Q^8ijz*?cWq$oNw)><J`TN|bX>_4!u
zf~`VHeh%0mh&oMcu<vV;y%vyKRGgoen3GwRnnGkKqK0cpW^Sr~noD9O%mLuwhKqtd
z5^ZV_3$eSnq$s(dQUg_5M*+@<IuRBCaB;9PP|3_R1r3lnxdj;JDCOoC<z?ojD`_g&
z+A0{poCFRN^f(7aiUL$UC}t2b4^m#MkeXARs({a~#I%ysqP+b4Jfdxb>mbX%)Us5P
z{03Ez(=JF}0C^T8KY+s~C9x8VUQprzrNG?8%sdTP0E6QklAEFp4PcRtoG~GiItof@
z`T0uFh=j(7l4@~GOrDaC0z?f|GCj2fp6wtKItofjiABU}#+tB^460JnQ9vXYNb1Dv
QprX{0(xN;C15n8T0LHa<hX4Qo

literal 0
HcmV?d00001

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
index 0000000000000000000000000000000000000000..9659685c634d49ae837a96f7eefb789ca34133e5
GIT binary patch
literal 1081
zcmXRYP0GnkR!GiCEG|}XFG@`<$;?YvsOC~oP*8x%1(y_o#L`nsVDcJJp}fT0R86R2
zkOru5ZgIMTt%8zAYEDkRj)IbcwgO0@g0_N^qLMY(WRT*b)RNMoJcZoibck3j7g!3z
zQn$pS)biAv9O5i>&(BXus!T<-R!_kru`E>~Q6ar3HL*k?C9#qmhXkY+73b$A=44i-
zreF&R9R;ZWOEPm){nK0$E3t<S#Jiv%C@#+|NzPEvfNRhz$@j@GPc3pzEKb$XMAn|1
zSe&Y$l$&3amzkHYWCac?kP#pZ4R4Uw6`(2*{zupbQkqnhnwX6cf|#0^R+3tjm!F@9
z$6UA?l1xr5OC{i5s0t!YO-W5lEX^sw?fsO*N+OM_g$E)2gjigXSdy8nP?n#WqL7=I
znFmdV(Xk4NMd`)JQ3Oj9pi~CQMz#uhsparorJ;!k_~6RolGI$i{L&J=f}+g4lAJt^
ubZF+&L(XVQY5Dm|n#5~_XBMTT#3E8OVolsiNr_cTI*4Qkb4V>$Ef)YH!!-^7

literal 0
HcmV?d00001

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
index 0000000000000000000000000000000000000000..9eda6cd8fe526d57f50ff6f4b3c9b379afdc5025
GIT binary patch
literal 961
zcmYe!Nh~f_a4$+tEy>JFx8hPzP*6xoO;bouEs0NuifR<6=A`K;<R#{&YC=?iRKaB9
zb5o0p6Vp=_Y!%X!JW_LV@^uudL27FimB7Y<6c?qIlosVFpzGlRD}dRTmROWpo|=<`
z-9DHsihb_+`6)@2sW2P$6g(2kQWX+G7A2M_q$E~ix&hrBR5ui)78U2`CFW#SrKVsD
z03C&r%-q!Y{IvL##7d;lKoV83MOLGilV6@%q@fA+DJUp1(~zYVY;6^ka`TJwGV{`v
z5P=I)h~gCFKn8hQ0jdY)b3{<5=3ucQF|8!EC@()hk0d+bx`?zTwJeone?awMv!OVZ
zlmJOdti&_~n(nX#B^Os_nnHX$D5b{7gQFonJ~uHlFFqa`MzF-HU<=P5&}b|u%FHX#
ufXVBj<Q1i~{Cp)%NOWUS2TOe#N=b=Dc+{b|7-kblQ<ahqBEe{CasdEr0WkRh

literal 0
HcmV?d00001

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
index 0000000000000000000000000000000000000000..a3aa5cbe7c617c65bc15ba1f8710e0f6ce634c43
GIT binary patch
literal 845
zcmXReDJo4aQE)FxO)bgHOSk6Y%FHduf%6rrxfB!>6w>k((o;*~)1j&~)QVGc(sUH^
z5_40n6x51KiZm5;?G%DbiZb)kp<2M&@{4j4OB6MfJW_LV@^uudYZa9sx-`MY)^dR<
zsOf2mMXBYfIXNVo?w+5Yl2n-rGG9-@Be5)1AyFZ{C^fM}AtkXApN$2nMaB7fi8+~7
zsVR6or=w7knVTA)pBA5zSc%WSxrrso8OVzDO7i1#^2<|;k`s$lH8k}Si{pz+iZnEl
z!Uz;jO1b$(d6{|XN(#1i3Td!F19?jUDg*KxEa-H=fd<kC!b*v0C8<Su`T2QRbi?KF
z=ua(6#bGy82DjdLO!uZFR$^CQ3wCuaS1lJ;TAo5~VrCvZ7;;ieK=B1mfC{$ov}X<0
zQBahbSCW&bsG(F{tE2;0r-zarmD2L_l{7U8>wu>*rKH3nqI6(SBT7k$RZ2Pv@Q4H1
HUCRXkC`<mL

literal 0
HcmV?d00001

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
index 0000000000000000000000000000000000000000..6aa4ecdd4cb442e52be17a1e93d556acd68af7f8
GIT binary patch
literal 384
zcmdN-DJj-1Gt{@{;?h^B=HkrFPf5*DuvJiUtw_u*$VpXj%1<mxQL^UZOwP|ONG!=r
z%1H%Dr$VH4lHk$>rNtlvIFk!X9Sp!M&Xm-$%;ePglF9<Gp5%g3kU^YiMkN;I>LlkE
zm84eaCR#!i6s4vs*eci<*jaONa@JaNfiM?mZfb6RQ6<=DG?O7RV7)M{6&3~%pkPp8
z1Yv=VtF`9h;w()pDlxE908tKx28L;d1_lr(qk1y0*wD<>)F2VnV7LZ@3Xndq8#D7t
uQj3a83rdPX?luLBCFkTPXX~aFr52W^<|S8xB#cZTE&<8cT65J}a{&MduxNS!

literal 0
HcmV?d00001

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
index 000000000..135287198
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffFormatterBuiltInDriverTest.java
@@ -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();
+	}
+}
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
index 2c22f0231..d3ae4cf37 100644
--- a/org.eclipse.jgit/.settings/.api_filters
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -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
index 000000000..9ae1aaa29
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffDriver.java
@@ -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;
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
index 2f472b5c0..fa446e18c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java
@@ -30,7 +30,9 @@
 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 void format(List<? extends DiffEntry> entries) throws IOException {
 	 */
 	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 @@ private String quotePath(String path) {
 	 *            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 void format(FileHeader head, RawText a, RawText b)
 		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 void format(FileHeader head, RawText a, RawText b)
 	 *            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 void format(EditList edits, RawText a, RawText b)
 			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 @@ protected void writeRemovedLine(RawText text, int line)
 	 * @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 @@ protected void writeHunkHeader(int aStartLine, int aEndLine,
 		out.write(' ');
 		out.write('@');
 		out.write('@');
+		if (funcName != null) {
+			out.write(' ');
+			out.write(funcName.getBytes());
+		}
 		out.write('\n');
 	}
 
@@ -1247,4 +1323,46 @@ private boolean combineB(List<Edit> e, int i) {
 	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;
+	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java
index 4343642f9..b401bbe73 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/PatchIdDiffFormatter.java
@@ -44,8 +44,8 @@ public ObjectId getCalulatedPatchId() {
 	}
 
 	@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
 	}
 
-- 
GitLab