{"thread":{"id":"62935","subject":"[PATCH 0/1] [GSOC 2025] [Newbie] userdiff: add built-in pattern for shell scripts","startedAt":"2025-02-11T11:47:26Z","lastAt":"2025-05-18T07:41:25Z","messageCount":37,"participants":["Moumita","Johannes Sixt","Eric Sunshine","Junio C Hamano","MOUMITA DHAR"],"isPatch":true,"patchVersion":1,"patchTotal":1},"messages":[{"id":"512230","messageId":"20250211114611.9334-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":null,"subject":"[PATCH 0/1] [GSOC 2025] [Newbie] userdiff: add built-in pattern for shell scripts","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-02-11T11:46:09Z","receivedAt":"2025-02-11T11:47:26Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"This patch adds a built-in userdiff pattern for shell scripts, allowing Git to recognize function names in shell scripts when displaying diffs.\nThis change defines a regular expression for detecting shell functions.\n\nMoumita (1):\n  Added built in function recognition for shell\n\n userdiff.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\n-- \n2.48.0\n\n"},{"id":"512231","messageId":"20250211114611.9334-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250211114611.9334-1-dhar61595@gmail.com","subject":"[PATCH 1/1] Added built in function recognition for shell","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-02-11T11:46:10Z","receivedAt":"2025-02-11T11:47:40Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"Introduced a built-in userdiff driver for shell scripts, enabling\naccurate function name recognition in `git diff` hunk headers.\n\nEnhancements include:\n- Function name detection for both POSIX and Bash/Ksh-style functions:\n  - `function_name() { ... }`\n  - `function function_name { ... }`\n- Exclusion of shell keywords that can resemble function names,\n  preventing false matches (e.g., `if`, `for`, `while`, `return`, etc.).\n- Improved tokenization support for:\n  - Identifiers (variable and function names)\n  - Numeric constants (integers and decimals)\n  - Shell variables (`$VAR`, `${VAR}`)\n  - Logical (`&&`, `||`, `==`, `!=`, `<=`, `>=`) and arithmetic operators\n  - Assignment and redirection operators\n  - Brackets and grouping symbols\n\nThis update improves Git’s diff readability for shell scripts,\nbringing it in line with existing built-in userdiff drivers.\n\nSigned-off-by: Moumita <dhar61595@gmail.com>\n---\n userdiff.c | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..a8c14807c6 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -334,6 +334,26 @@ PATTERNS(\"scheme\",\n \t \"\\\\|([^\\\\\\\\]*)\\\\|\"\n \t /* All other words should be delimited by spaces or parentheses */\n \t \"|([^][)(}{[ \\t])+\"),\n+PATTERNS(\"shell\",\n+\t /* Negate shell keywords that can look like functions */\n+\t \"!^[ \\t]*(if|elif|else|fi|for|while|until|case|esac|then|do|done|return|break|continue)\\\\b\\n\"\n+\t /* POSIX-style shell functions: function_name() { ... } */\n+\t \"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\(\\\\)[ \\t]*\\\\{\\n\"\n+\t /* Bash/Ksh-style functions: function function_name { ... } */\n+\t \"^[ \\t]*function[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\{\\n\",\n+\t /* -- */\n+\t /* Identifiers: variable and function names */\n+\t \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Numeric constants: integers and decimals */\n+\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?\"\n+\t /* Shell variables: $VAR and ${VAR} */\n+\t \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"\n+\t /* Logical and comparison operators */\n+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n+\t /* Assignment and arithmetic operators */\n+\t \"|[-+*/%&|^!=<>]=?\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"tex\", \"^(\\\\\\\\((sub)*section|chapter|part)\\\\*{0,1}\\\\{.*)$\",\n \t \"\\\\\\\\[a-zA-Z@]+|\\\\\\\\.|([a-zA-Z0-9]|[^\\x01-\\x7f])+\"),\n { .name = \"default\", .binary = -1 },\n-- \n2.48.0\n\n"},{"id":"512461","messageId":"5c2abae6-f06d-4f42-a705-c02a9f0dd4f5@kdbg.org","threadId":"62935","inReplyTo":"20250211114611.9334-2-dhar61595@gmail.com","subject":"Re: [PATCH 1/1] Added built in function recognition for shell","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-02-15T14:37:09Z","receivedAt":"2025-02-15T14:37:18Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 11.02.25 um 12:46 schrieb Moumita:\n> Introduced a built-in userdiff driver for shell scripts, enabling\n> accurate function name recognition in `git diff` hunk headers.\n> \n> Enhancements include:\n> - Function name detection for both POSIX and Bash/Ksh-style functions:\n>   - `function_name() { ... }`\n>   - `function function_name { ... }`\n> - Exclusion of shell keywords that can resemble function names,\n>   preventing false matches (e.g., `if`, `for`, `while`, `return`, etc.).\n> - Improved tokenization support for:\n>   - Identifiers (variable and function names)\n>   - Numeric constants (integers and decimals)\n>   - Shell variables (`$VAR`, `${VAR}`)\n>   - Logical (`&&`, `||`, `==`, `!=`, `<=`, `>=`) and arithmetic operators\n>   - Assignment and redirection operators\n>   - Brackets and grouping symbols\n> \n> This update improves Git’s diff readability for shell scripts,\n> bringing it in line with existing built-in userdiff drivers.\n\nPlease remove the marketing tone from the commit message.\n\n - \"accurate function name recognition\": Is not possible because the\ntools (regular expressions) don't have sufficient context to allow it.\n\n - \"Enhancements include\": Either list *all* enhancements, or focus on\nnoteworthy ones, but don't make a long list that's still incomplete.\n\n - The word \"improve\" is never needed in the context that we see it\nhere, because you certainly don't want to worsen the code base.\n\nPlease study section \"Describe your changes well\" in\nDocumentation/SubmittingPatches on how to write the commit message.\n(Describe the state before the change in present tense, and the changes\nin imperative mood.)\n\nAn important point is *why* we want this change. In the case of a new\nuserdiff driver, however, the answer is usally simply \"because we can\",\nand everybody knows it. Please don't write a long paragraph that dances\naround this fact. Just don't write it; but if there are other reason,\nplease do say so.\n\n> \n> Signed-off-by: Moumita <dhar61595@gmail.com>\n\nThe name given in Signed-off-by has legal meaning. If you have a given\nname and a surname, please use both names here and then by extension\nalso as patch author.\n\n> ---\n>  userdiff.c | 20 ++++++++++++++++++++\n>  1 file changed, 20 insertions(+)\n> \n> diff --git a/userdiff.c b/userdiff.c\n> index 340c4eb4f7..a8c14807c6 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -334,6 +334,26 @@ PATTERNS(\"scheme\",\n>  \t \"\\\\|([^\\\\\\\\]*)\\\\|\"\n>  \t /* All other words should be delimited by spaces or parentheses */\n>  \t \"|([^][)(}{[ \\t])+\"),\n> +PATTERNS(\"shell\",\n\nCorrectly sorted into the list. Good!\n\nHOWEVER! We already have a diffdriver for bash. It would be better to\nextend that one than to introduce a new driver.\n\n> +\t /* Negate shell keywords that can look like functions */\n> +\t \"!^[ \\t]*(if|elif|else|fi|for|while|until|case|esac|then|do|done|return|break|continue)\\\\b\\n\"\n\nThis list looks unnecessary. The userdiff driver can assume that it\noperates on syntactically valid text.\n\n   if () {\n   }\n\nisn't correct, and that's the case for all listed keywords.\n\n> +\t /* POSIX-style shell functions: function_name() { ... } */\n> +\t \"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\(\\\\)[ \\t]*\\\\{\\n\"\n\nTwo nitpicks:\n\n- There can be whitespace between the parentheses.\n- The function body can also be in parentheses.\n\n   foo ( ) (\n      echo\n   )\n\nwould be a correct shell function definition. Its body always runs in a\nsub-shell.\n\n> +\t /* Bash/Ksh-style functions: function function_name { ... } */\n> +\t \"^[ \\t]*function[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\{\\n\",\n\nBoth here and above, just let the regular expression end before the\nopening bracket. Then it would also recognize this oddity:\n\nfunction x \\\n  { echo x; }\n\n> +\t /* -- */\n> +\t /* Identifiers: variable and function names */\n> +\t \"[a-zA-Z_][a-zA-Z0-9_]*\"\n> +\t /* Numeric constants: integers and decimals */\n> +\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?\"\n\nTypically, a - or + isn't part of the number, but a separate operator. I\nsuggest to drop the leading '[-+]?'. But see below.\n\n> +\t /* Shell variables: $VAR and ${VAR} */\n> +\t \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"\n\nThe uses of ${name} are rather exceptional. The cases where ${ is used\nare the complex cases, for example:\n\n    base=${fname##*/}\n    base=${base%.\"$ext\"}\n\nIn such cases, I would prefer that the constituents of the expression\nare their own tokens and not lumped into a single \"variable name\" token.\n\n> +\t /* Logical and comparison operators */\n> +\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n> +\t /* Assignment and arithmetic operators */\n> +\t \"|[-+*/%&|^!=<>]=?\"\n\nWhich makes me think: Text in shell commands has only very little\nrestrictions. A typical case are command options starting with one or\ntwo dashes. Do we want to separate the dash from the option name? This\nregular expression would do so, and I would consider it a deficiency.\n\n> +\t /* Brackets and grouping symbols */\n> +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n>  PATTERNS(\"tex\", \"^(\\\\\\\\((sub)*section|chapter|part)\\\\*{0,1}\\\\{.*)$\",\n>  \t \"\\\\\\\\[a-zA-Z@]+|\\\\\\\\.|([a-zA-Z0-9]|[^\\x01-\\x7f])+\"),\n>  { .name = \"default\", .binary = -1 },\n\nIt would be nice to have some tests. Have a look at t/t4018/bash-* and\nt/t4034/* (no bash there, yet).\n\n-- Hannes\n\n"},{"id":"512606","messageId":"20250218153537.16320-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250211114611.9334-1-dhar61595@gmail.com","subject":"[PATCH v2 0/1] [PATCH v2 0/1] [GSOC 2025] [Newbie] userdiff: add built-in pattern for shell scripts","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-02-18T15:35:26Z","receivedAt":"2025-02-18T15:36:54Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"The modifications that I made were -\n\"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\([ \\t]*\\\\)[ \\t]*\"  - so that is allows foo() and foo ( ).\n\"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\([ \\t]*\\\\)[ \\t]*(\\\\{|\\\\(|\\\\[\\\\[)\" - so that it recognises {, (, or [[ as function bodies\n\"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\([ \\t]*\\\\)[ \\t]*(\\\\{|\\\\(|\\\\[\\\\[)|\\\\\\\\\\n\"-  so that it matches \"function foo \\\n  { echo \"hello\"; }\"\n\"(?:function[ \\t]+(?=[a-zA-Z_]))?[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))?\" - so that it matches function foo() { echo \"hello\"; }\nand also function bar { echo \"no parens\"; }\n\"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"- It seperates ${} expansions\n\"|--?[a-zA-Z0-9_-]+\" - It ensures -opt and --long-opt are treated as whole tokens.\n\"|[0-9]+(\\\\.[0-9]*)?\" - It matches numbers without +/-\n\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n userdiff.c | 34 +++++++++++++++++++++++-----------\n 1 file changed, 23 insertions(+), 11 deletions(-)\n\nRange-diff against v1:\n1:  683ea08819 ! 1:  de2e8f9792 userdiff : Added built in function recognition for shell\n    @@\n      ## Metadata ##\n    -Author: Moumita <dhar61595@gmail.com>\n    +Author: Moumita Dhar <dhar61595@gmail.com>\n     \n      ## Commit message ##\n    -    userdiff : Added built in function recognition for shell\n    +    userdiff: extend Bash pattern to cover more shell function forms\n     \n    -    Introduced a built-in userdiff driver for shell scripts, enabling\n    -    accurate function name recognition in `git diff` hunk headers.\n    +    The existing Bash userdiff pattern misses some shell function forms, such as\n    +    `function foo()`, multi-line definitions, and extra whitespace.\n     \n    -    Enhancements include:\n    -    - Function name detection for both POSIX and Bash/Ksh-style functions:\n    -      - `function_name() { ... }`\n    -      - `function function_name { ... }`\n    -    - Exclusion of shell keywords that can resemble function names,\n    -      preventing false matches (e.g., `if`, `for`, `while`, `return`, etc.).\n    -    - Improved tokenization support for:\n    -      - Identifiers (variable and function names)\n    -      - Numeric constants (integers and decimals)\n    -      - Shell variables (`$VAR`, `${VAR}`)\n    -      - Logical (`&&`, `||`, `==`, `!=`, `<=`, `>=`) and arithmetic operators\n    -      - Assignment and redirection operators\n    -      - Brackets and grouping symbols\n    +    Extend the pattern to:\n    +    - Support `function foo()` syntax.\n    +    - Allow spaces in `foo ( )` definitions.\n    +    - Recognize multi-line definitions with backslashes.\n    +    - Broaden function body detection.\n     \n    -    This update improves Git’s diff readability for shell scripts,\n    -    bringing it in line with existing built-in userdiff drivers.\n    -\n    -    Signed-off-by: Moumita <dhar61595@gmail.com>\n    +    Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n     \n      ## userdiff.c ##\n    -@@ userdiff.c: PATTERNS(\"scheme\",\n    - \t \"\\\\|([^\\\\\\\\]*)\\\\|\"\n    - \t /* All other words should be delimited by spaces or parentheses */\n    - \t \"|([^][)(}{[ \\t])+\"),\n    -+PATTERNS(\"shell\",\n    -+\t /* Negate shell keywords that can look like functions */\n    -+\t  \"!^[ \\t]*(if|elif|else|fi|for|while|until|case|esac|then|do|done|return|break|continue)\\\\b\\n\"\n    -+\t /* POSIX-style shell functions: function_name() { ... } */\n    -+\t \"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\(\\\\)[ \\t]*\\\\{\\n\"\n    -+\t /* Bash/Ksh-style functions: function function_name { ... } */\n    -+\t \"^[ \\t]*function[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\{\\n\",\n    -+\t /* -- */\n    +@@ userdiff.c: IPATTERN(\"ada\",\n    + \t \"|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?\"\n    + \t \"|=>|\\\\.\\\\.|\\\\*\\\\*|:=|/=|>=|<=|<<|>>|<>\"),\n    + PATTERNS(\"bash\",\n    +-\t /* Optional leading indentation */\n    ++     /* Optional leading indentation */\n    + \t \"^[ \\t]*\"\n    +-\t /* Start of captured text */\n    ++\t /* Start of captured function name */\n    + \t \"(\"\n    + \t \"(\"\n    +-\t     /* POSIX identifier with mandatory parentheses */\n    +-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    ++\t\t /* POSIX identifier with mandatory parentheses (allow spaces inside) */\n    ++\t\t \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\)\"\n    + \t \"|\"\n    +-\t     /* Bashism identifier with optional parentheses */\n    +-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    ++\t\t /* Bash-style function definitions, allowing optional `function` keyword */\n    ++\t\t \"(?:function[ \\t]+(?=[a-zA-Z_]))?[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))?\"\n    + \t \")\"\n    + \t /* Optional whitespace */\n    + \t \"[ \\t]*\"\n    +-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n    +-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n    +-\t /* End of captured text */\n    ++\t /* Allow function body to start with `{`, `(` (subshell), `[[` */\n    ++\t \"(\\\\{|\\\\(|\\\\[\\\\[)\"\n    ++\t /* End of captured function name */\n    + \t \")\",\n    + \t /* -- */\n    +-\t /* Characters not in the default $IFS value */\n    +-\t \"[^ \\t]+\"),\n     +\t /* Identifiers: variable and function names */\n     +\t \"[a-zA-Z_][a-zA-Z0-9_]*\"\n     +\t /* Numeric constants: integers and decimals */\n    -+\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?\"\n    -+\t /* Shell variables: $VAR and ${VAR} */\n    ++\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n    ++\t /* Shell variables: `$VAR`, `${VAR}` */\n     +\t \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"\n     +\t /* Logical and comparison operators */\n     +\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n     +\t /* Assignment and arithmetic operators */\n     +\t \"|[-+*/%&|^!=<>]=?\"\n    ++\t /* Command-line options (to avoid splitting `-option`) */\n    ++\t \"|--?[a-zA-Z0-9_-]+\"\n     +\t /* Brackets and grouping symbols */\n     +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n    - PATTERNS(\"tex\", \"^(\\\\\\\\((sub)*section|chapter|part)\\\\*{0,1}\\\\{.*)$\",\n    - \t \"\\\\\\\\[a-zA-Z@]+|\\\\\\\\.|([a-zA-Z0-9]|[^\\x01-\\x7f])+\"),\n    - { .name = \"default\", .binary = -1 },\n    + PATTERNS(\"bibtex\",\n    + \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n    + \t /* -- */\n-- \n2.48.0\n\n"},{"id":"512607","messageId":"20250218153537.16320-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250218153537.16320-1-dhar61595@gmail.com","subject":"[PATCH v2 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-02-18T15:35:27Z","receivedAt":"2025-02-18T15:37:02Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"From: Moumita Dhar <dhar61595@gmail.com>\n\nThe existing Bash userdiff pattern misses some shell function forms, such as\n`function foo()`, multi-line definitions, and extra whitespace.\n\nExtend the pattern to:\n- Support `function foo()` syntax.\n- Allow spaces in `foo ( )` definitions.\n- Recognize multi-line definitions with backslashes.\n- Broaden function body detection.\n\nSigned-off-by: Moumita Dhar <dhar61595@gmail.com>\n---\n userdiff.c | 34 +++++++++++++++++++++++-----------\n 1 file changed, 23 insertions(+), 11 deletions(-)\n\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..194e28883d 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -53,26 +53,38 @@ IPATTERN(\"ada\",\n \t \"|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?\"\n \t \"|=>|\\\\.\\\\.|\\\\*\\\\*|:=|/=|>=|<=|<<|>>|<>\"),\n PATTERNS(\"bash\",\n-\t /* Optional leading indentation */\n+     /* Optional leading indentation */\n \t \"^[ \\t]*\"\n-\t /* Start of captured text */\n+\t /* Start of captured function name */\n \t \"(\"\n \t \"(\"\n-\t     /* POSIX identifier with mandatory parentheses */\n-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n+\t\t /* POSIX identifier with mandatory parentheses (allow spaces inside) */\n+\t\t \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\)\"\n \t \"|\"\n-\t     /* Bashism identifier with optional parentheses */\n-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n+\t\t /* Bash-style function definitions, allowing optional `function` keyword */\n+\t\t \"(?:function[ \\t]+(?=[a-zA-Z_]))?[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))?\"\n \t \")\"\n \t /* Optional whitespace */\n \t \"[ \\t]*\"\n-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n-\t /* End of captured text */\n+\t /* Allow function body to start with `{`, `(` (subshell), `[[` */\n+\t \"(\\\\{|\\\\(|\\\\[\\\\[)\"\n+\t /* End of captured function name */\n \t \")\",\n \t /* -- */\n-\t /* Characters not in the default $IFS value */\n-\t \"[^ \\t]+\"),\n+\t /* Identifiers: variable and function names */\n+\t \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Numeric constants: integers and decimals */\n+\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n+\t /* Shell variables: `$VAR`, `${VAR}` */\n+\t \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"\n+\t /* Logical and comparison operators */\n+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n+\t /* Assignment and arithmetic operators */\n+\t \"|[-+*/%&|^!=<>]=?\"\n+\t /* Command-line options (to avoid splitting `-option`) */\n+\t \"|--?[a-zA-Z0-9_-]+\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"bibtex\",\n \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n \t /* -- */\n-- \n2.48.0\n\n"},{"id":"512625","messageId":"CAPig+cStFE7r2-Kd-js_VHjuF5LGp+0Kniez61i1-Lus=gyVHQ@mail.gmail.com","threadId":"62935","inReplyTo":"20250218153537.16320-1-dhar61595@gmail.com","subject":"Re: [PATCH v2 0/1] [PATCH v2 0/1] [GSOC 2025] [Newbie] userdiff: add built-in pattern for shell scripts","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2025-02-18T17:30:47Z","receivedAt":"2025-02-18T17:30:58Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Tue, Feb 18, 2025 at 10:36 AM Moumita <dhar61595@gmail.com> wrote:\n> The modifications that I made were -\n> \"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\([ \\t]*\\\\)[ \\t]*\"  - so that is allows foo() and foo ( ).\n> \"^[ \\t]*([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*\\\\([ \\t]*\\\\)[ \\t]*(\\\\{|\\\\(|\\\\[\\\\[)\" - so that it recognises {, (, or [[ as function bodies\n\nRegarding this last point, for completeness, I had meant to respond to\nj6t's review of your patch by saying that, according to POSIX, a\nfunction body can be any compound command, which means the body does\nnot need to be encased in braces. For instance, this works:\n\n    $ foo() echo nothing\n    $ foo\n    nothing\n    $\n\nThat said, I doubt that this usage is common, thus is probably not\nworth worrying about.\n"},{"id":"512637","messageId":"xmqqy0y3jbjk.fsf@gitster.g","threadId":"62935","inReplyTo":"20250218153537.16320-2-dhar61595@gmail.com","subject":"Re: [PATCH v2 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-18T19:30:23Z","receivedAt":"2025-02-18T19:30:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Moumita <dhar61595@gmail.com> writes:\n\n>  PATTERNS(\"bash\",\n> -\t /* Optional leading indentation */\n> +     /* Optional leading indentation */\n\nWhat is this change about?\n\n>  \t \"^[ \\t]*\"\n> -\t /* Start of captured text */\n> +\t /* Start of captured function name */\n>  \t \"(\"\n>  \t \"(\"\n> -\t     /* POSIX identifier with mandatory parentheses */\n> -\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n> +\t\t /* POSIX identifier with mandatory parentheses (allow spaces inside) */\n> +\t\t \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\)\"\n\nIs indentation-change intended and required for this patch to work correctly?\n\n>  \t \"|\"\n> -\t     /* Bashism identifier with optional parentheses */\n> -\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n> +\t\t /* Bash-style function definitions, allowing optional `function` keyword */\n> +\t\t \"(?:function[ \\t]+(?=[a-zA-Z_]))?[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))?\"\n\nDitto.\n\nRegular expressions are write-only language; please make sure that\nyou do not add any unnecessary changes to distract eyes of\nreviewers from spotting the _real_ changes that improves the current\ncodebase.\n\n>  \t \")\"\n>  \t /* Optional whitespace */\n>  \t \"[ \\t]*\"\n> -\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n> -\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n> -\t /* End of captured text */\n> +\t /* Allow function body to start with `{`, `(` (subshell), `[[` */\n> +\t \"(\\\\{|\\\\(|\\\\[\\\\[)\"\n> +\t /* End of captured function name */\n>  \t \")\",\n\n\n>  \t /* -- */\n> -\t /* Characters not in the default $IFS value */\n> -\t \"[^ \\t]+\"),\n\nWe used to pretty-much use \"a run of non-whitespace characters is a\ntoken\".  Now we are a bit more picky.\n\nWhich may or may not be good, but it is hard to tell if it is an\nimprovement.\n\n> +\t /* Identifiers: variable and function names */\n> +\t \"[a-zA-Z_][a-zA-Z0-9_]*\"\n> +\t /* Numeric constants: integers and decimals */\n> +\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n> +\t /* Shell variables: `$VAR`, `${VAR}` */\n> +\t \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"\n> +\t /* Logical and comparison operators */\n> +\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n> +\t /* Assignment and arithmetic operators */\n> +\t \"|[-+*/%&|^!=<>]=?\"\n> +\t /* Command-line options (to avoid splitting `-option`) */\n> +\t \"|--?[a-zA-Z0-9_-]+\"\n> +\t /* Brackets and grouping symbols */\n> +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n\nThe fact that this patch does not have any changes to \"t/\" hierarchy\nsuggests me that we do not have existing tests to see how sample\ntext files in the supported languages are tokenized (otherwise the\nabove changes would require adjusting such existing tests), so I\nthink it should be left outside of this topic, but I wonder if\nadding such tests gives us a good way to demonstrate the effect of\nthese changes to userdiff patterns.\n\nThanks.\n"},{"id":"512651","messageId":"xmqq34gaj02j.fsf@gitster.g","threadId":"62935","inReplyTo":"20250218153537.16320-2-dhar61595@gmail.com","subject":"Re: [PATCH v2 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-18T23:38:12Z","receivedAt":"2025-02-18T23:38:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Moumita <dhar61595@gmail.com> writes:\n\n> From: Moumita Dhar <dhar61595@gmail.com>\n>\n> The existing Bash userdiff pattern misses some shell function forms, such as\n> `function foo()`, multi-line definitions, and extra whitespace.\n>\n> Extend the pattern to:\n> - Support `function foo()` syntax.\n> - Allow spaces in `foo ( )` definitions.\n> - Recognize multi-line definitions with backslashes.\n> - Broaden function body detection.\n>\n> Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n\nApplied to any one of the recent tips of 'master', this seemed break\ntests and the reproduction seems to be quite easy.\n\n    $ make \n    $ cd t && sh t4018-*.sh -i -v\n    ...\n    test_expect_code: command exited with 128, we wanted 1 ...\n    not ok 6 - builtin bash pattern compiles\n    #...\n    #\t\t\ttest_grep ! fatal msg &&\n    #\t\t\ttest_grep ! error msg\n    #\t\t\n    $ cat t/trash*.t4018*/msg\n    fatal: Invalid regexp to look for hunk header: ^[ \t]*(([a-z...\n\nPlease make it a habit to run tests after you modified the code\nbefore sending out patches with the modifications.\n\nThanks.\n\n"},{"id":"512873","messageId":"e5e0c54d-1e71-49e9-9581-134880996e4a@kdbg.org","threadId":"62935","inReplyTo":"20250218153537.16320-2-dhar61595@gmail.com","subject":"Re: [PATCH v2 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-02-22T18:14:03Z","receivedAt":"2025-02-22T18:14:19Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Thank you for this follow-up.\n\nAfter reviewing my text below I find that the words may sound harsh.\nPlease don't let you turn down by the lack of friendlyness. In technical\nreviews I (and very likely many other reviewers on this list) prefer to\nget to the point right away and not waste our time by talking in circles.\n\nI also do not repeat what others have said in their review.\n\nAm 18.02.25 um 16:35 schrieb Moumita:\n> From: Moumita Dhar <dhar61595@gmail.com>\n> \n> The existing Bash userdiff pattern misses some shell function forms, such as\n> `function foo()`, multi-line definitions, and extra whitespace.\n> \n> Extend the pattern to:\n> - Support `function foo()` syntax.\n> - Allow spaces in `foo ( )` definitions.\n> - Recognize multi-line definitions with backslashes.\n> - Broaden function body detection.\n\nPlease be accurate what you mention here. The first two are already\npresent and not new, the third one I cannot find, and the last one is\nnot immediately visible if not wrong (you are removing the '((' introducer).\n\n> \n> Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n> ---\n>  userdiff.c | 34 +++++++++++++++++++++++-----------\n>  1 file changed, 23 insertions(+), 11 deletions(-)\n> \n> diff --git a/userdiff.c b/userdiff.c\n> index 340c4eb4f7..194e28883d 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -53,26 +53,38 @@ IPATTERN(\"ada\",\n>  \t \"|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?\"\n>  \t \"|=>|\\\\.\\\\.|\\\\*\\\\*|:=|/=|>=|<=|<<|>>|<>\"),\n>  PATTERNS(\"bash\",\n> -\t /* Optional leading indentation */\n> +     /* Optional leading indentation */\n>  \t \"^[ \\t]*\"\n> -\t /* Start of captured text */\n> +\t /* Start of captured function name */\n>  \t \"(\"\n>  \t \"(\"\n> -\t     /* POSIX identifier with mandatory parentheses */\n> -\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n> +\t\t /* POSIX identifier with mandatory parentheses (allow spaces inside) */\n> +\t\t \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\)\"\n>  \t \"|\"\n> -\t     /* Bashism identifier with optional parentheses */\n> -\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n> +\t\t /* Bash-style function definitions, allowing optional `function` keyword */\n> +\t\t \"(?:function[ \\t]+(?=[a-zA-Z_]))?[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))?\"\n>  \t \")\"\n\nYou inserted (?=[a-zA-Z_]) after the function. What does this do? Then\nyou made 'function identifier' optional. Why?\n\nYou also added a ? at the end. Is that to make the parenthesis optional?\nBut look closely: they are already optional, because we already have\n\n  ( space_opt \"(\" space_opt \")\" | space_opt )\n\nIt would be a minor improvement to turn this into\n\n  space_opt ( \"(\" space_opt \")\" )?\n\nMake a mental bookmark here.\n\n>  \t /* Optional whitespace */\n>  \t \"[ \\t]*\"\n> -\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n> -\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n> -\t /* End of captured text */\n> +\t /* Allow function body to start with `{`, `(` (subshell), `[[` */\n> +\t \"(\\\\{|\\\\(|\\\\[\\\\[)\"\n> +\t /* End of captured function name */\n\nWhat is the justification that you removed \"((\" as introduction of the\nfunction body?\n\n>  \t \")\",\n\nSo, we capture only what we expect to be a function header in typical cases.\n\nLet me make a suggestion. What if we replaced everything from the mental\nbookmark above up to the closing parenthesis by .*$ like we do in all\nother builtin drivers? Then everything on the line that contains the\nfunction header would be used as hunk header, and we do not care what\nthe function body looks like. It would also cover the case mentioned by\nEric elsewhere where the body is just a simple command.\n\nIf that leads to new cases that are detected, it would be really nice to\nadd corresponding test cases in t/t4018/.\n\n>  \t /* -- */\n> -\t /* Characters not in the default $IFS value */\n> -\t \"[^ \\t]+\"),\n> +\t /* Identifiers: variable and function names */\n> +\t \"[a-zA-Z_][a-zA-Z0-9_]*\"\n> +\t /* Numeric constants: integers and decimals */\n> +\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n> +\t /* Shell variables: `$VAR`, `${VAR}` */\n> +\t \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"\n> +\t /* Logical and comparison operators */\n> +\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n> +\t /* Assignment and arithmetic operators */\n> +\t \"|[-+*/%&|^!=<>]=?\"\n> +\t /* Command-line options (to avoid splitting `-option`) */\n> +\t \"|--?[a-zA-Z0-9_-]+\"\n> +\t /* Brackets and grouping symbols */\n> +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n\nAs far as I can see, you introduced a pattern for options, but otherwise\nleft the patterns the same as in the earlier round. Of course, you are\nnot obliged to integrate every suggestion that is made by a reviewer,\nbut it is good tone that you leave a comment explaining why you\ndismissed a suggestion.\n\nTo expand on my suggestion how to treat ${var} substitution:\n\n- Replace \\\\$\\\\{[^}]+\\\\} by just \\\\$\\\\{ to match only the introducer.\n(The closing brace is already matched by a later pattern.)\n- Add operators := :- :+ :? # ## %% that are used frequently in these\ncomplex expansions.\n- Have a look at section \"Shell Parameter Expansion\" in the bash\nmanual[1] about the multitude of operators that can occur after ${.\nMaybe you want to add more of them.\n\n[1]\nhttps://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion\n\n-- Hannes\n\n"},{"id":"512874","messageId":"85d3318f-5e5b-48c5-8ba0-27465a2e733f@kdbg.org","threadId":"62935","inReplyTo":"xmqqy0y3jbjk.fsf@gitster.g","subject":"Re: [PATCH v2 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-02-22T18:15:34Z","receivedAt":"2025-02-22T18:15:41Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 18.02.25 um 20:30 schrieb Junio C Hamano:\n> Moumita <dhar61595@gmail.com> writes:\n>>  \t /* -- */\n>> -\t /* Characters not in the default $IFS value */\n>> -\t \"[^ \\t]+\"),\n> \n> We used to pretty-much use \"a run of non-whitespace characters is a\n> token\".  Now we are a bit more picky.\n> \n> Which may or may not be good, but it is hard to tell if it is an\n> improvement.\n\nIt is only a stand-in, because every built-in userdiff driver must have\na word pattern. See the old thread here:\nhttps://lore.kernel.org/git/373640ea4d95f3b279b9d460d9a8889b4030b4e9.camel@engmark.name/\n\n-- Hannes\n\n"},{"id":"512916","messageId":"xmqqjz9f71e8.fsf@gitster.g","threadId":"62935","inReplyTo":"85d3318f-5e5b-48c5-8ba0-27465a2e733f@kdbg.org","subject":"Re: [PATCH v2 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-02-24T16:28:31Z","receivedAt":"2025-02-24T16:28:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n> Am 18.02.25 um 20:30 schrieb Junio C Hamano:\n>> Moumita <dhar61595@gmail.com> writes:\n>>>  \t /* -- */\n>>> -\t /* Characters not in the default $IFS value */\n>>> -\t \"[^ \\t]+\"),\n>> \n>> We used to pretty-much use \"a run of non-whitespace characters is a\n>> token\".  Now we are a bit more picky.\n>> \n>> Which may or may not be good, but it is hard to tell if it is an\n>> improvement.\n>\n> It is only a stand-in, because every built-in userdiff driver must have\n> a word pattern.\n\nYeah, I know.  I was merely saying that it was not obvious that the\nnew pattern, which is way more elaborate, is improvement over that\nstand-in pattern.  As these patterns are meant to be applied to only\nsyntactically valid text, by going more specific pattern from simple\nand lenient pattern, we stop recognising some word that we used to\ntake as a word (i.e. specific patterns need to worry about false\nnegatives, while simpler patterns only have to avoid egregious false\npositives).\n\n> See the old thread here:\n> https://lore.kernel.org/git/373640ea4d95f3b279b9d460d9a8889b4030b4e9.camel@engmark.name/\n\nYup, 2ff6c346 (userdiff: support Bash, 2020-10-22) is where the\nstand-in pattern came from, which is the v3 iteration of that patch.\n\nThanks.\n"},{"id":"515254","messageId":"20250328200525.4437-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250218153537.16320-1-dhar61595@gmail.com","subject":"[PATCH v3 0/1] userdiff: improve Bash function and word regex patterns","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-03-28T20:05:24Z","receivedAt":"2025-03-28T20:07:29Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"This patch improves function detection in userdiff for Bash scripts.  \nThe old regex tried to match function bodies explicitly, which caused  \nissues with line continuations (`\\`) and simple command bodies. Instead,  \nI have replaced it with `.*$`, making it more consistent with other userdiff  \ndrivers and ensuring we capture the full function definition line.  \n\nI also refined the word regex to better handle Bash syntax, including  \nparameter expansions, arithmetic expressions, and command-line options.  \n\nI have  added test cases to cover these changes, making sure everything  \nworks as expected.\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n t/t4018/bash-bashism-style-multiline-function |  4 +++\n t/t4018/bash-posix-style-multiline-function   |  4 +++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 30 +++++++++++++++++++\n t/t4034/bash/post                             | 25 ++++++++++++++++\n t/t4034/bash/pre                              | 25 ++++++++++++++++\n userdiff.c                                    | 24 +++++++++++----\n 8 files changed, 110 insertions(+), 6 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\nRange-diff against v2:\n1:  de2e8f9792 ! 1:  3d077fadc4 userdiff: extend Bash pattern to cover more shell function forms\n    @@ Metadata\n      ## Commit message ##\n         userdiff: extend Bash pattern to cover more shell function forms\n     \n    -    The existing Bash userdiff pattern misses some shell function forms, such as\n    -    `function foo()`, multi-line definitions, and extra whitespace.\n    +    The previous function regex required explicit matching of function\n    +    bodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n     \n    -    Extend the pattern to:\n    -    - Support `function foo()` syntax.\n    -    - Allow spaces in `foo ( )` definitions.\n    -    - Recognize multi-line definitions with backslashes.\n    -    - Broaden function body detection.\n    +    - It failed to capture valid functions where `{` was on the next line\n    +      due to line continuation (`\\`).\n    +    - It did not recognize functions with single  command body, such as\n    +      `x () echo hello`.\n    +\n    +    Replacing the function body matching logic with `.*$`, ensures\n    +    that everything on the function definition line is captured,\n    +    aligning with other userdiff drivers and improving hunk headers in\n    +    `git diff`.\n    +\n    +    Additionally, the word regex is refined to better recognize shell\n    +    syntax, including additional parameter expansion operators and\n    +    command-line options, improving syntax-aware diffs.\n     \n         Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n     \n    + ## t/t4018/bash-bashism-style-multiline-function (new) ##\n    +@@\n    ++function RIGHT \\\n    ++{    \n    ++    echo 'ChangeMe'\n    ++}\n    + \\ No newline at end of file\n    +\n    + ## t/t4018/bash-posix-style-multiline-function (new) ##\n    +@@\n    ++RIGHT() \\\n    ++{\n    ++    ChangeMe\n    ++}\n    + \\ No newline at end of file\n    +\n    + ## t/t4018/bash-posix-style-single-command-function (new) ##\n    +@@\n    ++RIGHT() echo \"hello\"\n    ++\n    ++    ChangeMe\n    +\n    + ## t/t4034-diff-words.sh ##\n    +@@ t/t4034-diff-words.sh: test_expect_success 'unset default driver' '\n    + \n    + test_language_driver ada\n    + test_language_driver bibtex\n    ++test_language_driver bash\n    + test_language_driver cpp\n    + test_language_driver csharp\n    + test_language_driver css\n    +\n    + ## t/t4034/bash/expect (new) ##\n    +@@\n    ++<BOLD>diff --git a/pre b/post<RESET>\n    ++<BOLD>index 09ac008..60ba6a2 100644<RESET>\n    ++<BOLD>--- a/pre<RESET>\n    ++<BOLD>+++ b/post<RESET>\n    ++<CYAN>@@ -1,25 +1,25 @@<RESET>\n    ++<RED>my_var<RESET><GREEN>new_var<RESET>=10\n    ++x=<RED>123<RESET><GREEN>456<RESET>\n    ++y=<RED>3.14<RESET><GREEN>2.71<RESET>\n    ++z=<RED>.5<RESET><GREEN>.75<RESET>\n    ++echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n    ++${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n    ++if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n    ++((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n    ++((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n    ++$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n    ++$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n    ++${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n    ++${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n    ++${<RED>a<RESET><GREEN>x<RESET>##*/}\n    ++${<RED>a<RESET><GREEN>x<RESET>%.*}\n    ++${<RED>a<RESET><GREEN>x<RESET>%%.*}\n    ++${<RED>a<RESET><GREEN>x<RESET>^^}\n    ++${<RED>a<RESET><GREEN>x<RESET>,}\n    ++${<RED>a<RESET><GREEN>x<RESET>,,}\n    ++${!<RED>a<RESET><GREEN>x<RESET>}\n    ++${<RED>a<RESET><GREEN>x<RESET>[@]}\n    ++${<RED>a<RESET><GREEN>x<RESET>:?error message}\n    ++${<RED>a<RESET><GREEN>x<RESET>:2:3}\n    ++ls <RED>-a<RESET><GREEN>-x<RESET>\n    ++ls <RED>--a<RESET><GREEN>--x<RESET>\n    +\n    + ## t/t4034/bash/post (new) ##\n    +@@\n    ++new_var=10\n    ++x=456\n    ++y=2.71\n    ++z=.75\n    ++echo $USERNAME\n    ++${HOMEDIR}\n    ++if [ \"$x\" == \"$y\" ] || [ \"$x\" != \"$y\" ]; then echo \"OK\"; fi\n    ++((x+=y))\n    ++((x-=y))\n    ++$((x<<y))\n    ++$((x>>y))\n    ++${x:-y}\n    ++${x:=y}\n    ++${x##*/}\n    ++${x%.*}\n    ++${x%%.*}\n    ++${x^^}\n    ++${x,}\n    ++${x,,}\n    ++${!x}\n    ++${x[@]}\n    ++${x:?error message}\n    ++${x:2:3}\n    ++ls -x\n    ++ls --x\n    +\n    + ## t/t4034/bash/pre (new) ##\n    +@@\n    ++my_var=10\n    ++x=123\n    ++y=3.14\n    ++z=.5\n    ++echo $USER\n    ++${HOME}\n    ++if [ \"$a\" == \"$b\" ] || [ \"$c\" != \"$d\" ]; then echo \"OK\"; fi\n    ++((a+=b))\n    ++((a-=b))\n    ++$((a << b))\n    ++$((a >> b))\n    ++${a:-b}\n    ++${a:=b}\n    ++${a##*/}\n    ++${a%.*}\n    ++${a%%.*}\n    ++${a^^}\n    ++${a,}\n    ++${a,,}\n    ++${!a}\n    ++${a[@]}\n    ++${a:?error message}\n    ++${a:2:3}\n    ++ls -a\n    ++ls --a\n    +\n      ## userdiff.c ##\n    -@@ userdiff.c: IPATTERN(\"ada\",\n    - \t \"|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?\"\n    - \t \"|=>|\\\\.\\\\.|\\\\*\\\\*|:=|/=|>=|<=|<<|>>|<>\"),\n    - PATTERNS(\"bash\",\n    --\t /* Optional leading indentation */\n    -+     /* Optional leading indentation */\n    - \t \"^[ \\t]*\"\n    --\t /* Start of captured text */\n    -+\t /* Start of captured function name */\n    - \t \"(\"\n    - \t \"(\"\n    --\t     /* POSIX identifier with mandatory parentheses */\n    --\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    -+\t\t /* POSIX identifier with mandatory parentheses (allow spaces inside) */\n    -+\t\t \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\)\"\n    - \t \"|\"\n    --\t     /* Bashism identifier with optional parentheses */\n    --\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    -+\t\t /* Bash-style function definitions, allowing optional `function` keyword */\n    -+\t\t \"(?:function[ \\t]+(?=[a-zA-Z_]))?[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))?\"\n    +@@ userdiff.c: PATTERNS(\"bash\",\n    + \t     /* Bashism identifier with optional parentheses */\n    + \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n      \t \")\"\n    - \t /* Optional whitespace */\n    - \t \"[ \\t]*\"\n    +-\t /* Optional whitespace */\n    +-\t \"[ \\t]*\"\n     -\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n     -\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n    --\t /* End of captured text */\n    -+\t /* Allow function body to start with `{`, `(` (subshell), `[[` */\n    -+\t \"(\\\\{|\\\\(|\\\\[\\\\[)\"\n    -+\t /* End of captured function name */\n    ++\t /* Everything after the function header is captured  */\n    ++\t \".*$\"\n    + \t /* End of captured text */\n      \t \")\",\n      \t /* -- */\n     -\t /* Characters not in the default $IFS value */\n     -\t \"[^ \\t]+\"),\n     +\t /* Identifiers: variable and function names */\n    -+\t \"[a-zA-Z_][a-zA-Z0-9_]*\"\n    ++\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n     +\t /* Numeric constants: integers and decimals */\n    -+\t \"|[-+]?[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n    -+\t /* Shell variables: `$VAR`, `${VAR}` */\n    -+\t \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{[^}]+\\\\}\"\n    -+\t /* Logical and comparison operators */\n    ++\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n    ++\t /* Shell variables: $VAR, ${VAR} */\n    ++\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n    ++\t  /* Logical and comparison operators */\n     +\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n     +\t /* Assignment and arithmetic operators */\n     +\t \"|[-+*/%&|^!=<>]=?\"\n    -+\t /* Command-line options (to avoid splitting `-option`) */\n    ++\t /* Additional parameter expansion operators */\n    ++\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n    ++\t /* Command-line options (to avoid splitting -option) */\n     +\t \"|--?[a-zA-Z0-9_-]+\"\n     +\t /* Brackets and grouping symbols */\n     +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n-- \n2.48.0\n\n"},{"id":"515255","messageId":"20250328200525.4437-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250328200525.4437-1-dhar61595@gmail.com","subject":"[PATCH v3 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-03-28T20:05:25Z","receivedAt":"2025-03-28T20:07:42Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"From: Moumita Dhar <dhar61595@gmail.com>\n\nThe previous function regex required explicit matching of function\nbodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n\n- It failed to capture valid functions where `{` was on the next line\n  due to line continuation (`\\`).\n- It did not recognize functions with single  command body, such as\n  `x () echo hello`.\n\nReplacing the function body matching logic with `.*$`, ensures\nthat everything on the function definition line is captured,\naligning with other userdiff drivers and improving hunk headers in\n`git diff`.\n\nAdditionally, the word regex is refined to better recognize shell\nsyntax, including additional parameter expansion operators and\ncommand-line options, improving syntax-aware diffs.\n\nSigned-off-by: Moumita Dhar <dhar61595@gmail.com>\n---\n t/t4018/bash-bashism-style-multiline-function |  4 +++\n t/t4018/bash-posix-style-multiline-function   |  4 +++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 30 +++++++++++++++++++\n t/t4034/bash/post                             | 25 ++++++++++++++++\n t/t4034/bash/pre                              | 25 ++++++++++++++++\n userdiff.c                                    | 24 +++++++++++----\n 8 files changed, 110 insertions(+), 6 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\ndiff --git a/t/t4018/bash-bashism-style-multiline-function b/t/t4018/bash-bashism-style-multiline-function\nnew file mode 100644\nindex 0000000000..0800daa156\n--- /dev/null\n+++ b/t/t4018/bash-bashism-style-multiline-function\n@@ -0,0 +1,4 @@\n+function RIGHT \\\n+{    \n+    echo 'ChangeMe'\n+}\n\\ No newline at end of file\ndiff --git a/t/t4018/bash-posix-style-multiline-function b/t/t4018/bash-posix-style-multiline-function\nnew file mode 100644\nindex 0000000000..756f21524b\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-multiline-function\n@@ -0,0 +1,4 @@\n+RIGHT() \\\n+{\n+    ChangeMe\n+}\n\\ No newline at end of file\ndiff --git a/t/t4018/bash-posix-style-single-command-function b/t/t4018/bash-posix-style-single-command-function\nnew file mode 100644\nindex 0000000000..398ae1c5d2\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-single-command-function\n@@ -0,0 +1,3 @@\n+RIGHT() echo \"hello\"\n+\n+    ChangeMe\ndiff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\nindex f51d3557f1..0be647c2fb 100755\n--- a/t/t4034-diff-words.sh\n+++ b/t/t4034-diff-words.sh\n@@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n \n test_language_driver ada\n test_language_driver bibtex\n+test_language_driver bash\n test_language_driver cpp\n test_language_driver csharp\n test_language_driver css\ndiff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\nnew file mode 100644\nindex 0000000000..a0f7cbd5a3\n--- /dev/null\n+++ b/t/t4034/bash/expect\n@@ -0,0 +1,30 @@\n+<BOLD>diff --git a/pre b/post<RESET>\n+<BOLD>index 09ac008..60ba6a2 100644<RESET>\n+<BOLD>--- a/pre<RESET>\n+<BOLD>+++ b/post<RESET>\n+<CYAN>@@ -1,25 +1,25 @@<RESET>\n+<RED>my_var<RESET><GREEN>new_var<RESET>=10\n+x=<RED>123<RESET><GREEN>456<RESET>\n+y=<RED>3.14<RESET><GREEN>2.71<RESET>\n+z=<RED>.5<RESET><GREEN>.75<RESET>\n+echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n+${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n+if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n+((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n+((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n+$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n+$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n+${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n+${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n+${<RED>a<RESET><GREEN>x<RESET>##*/}\n+${<RED>a<RESET><GREEN>x<RESET>%.*}\n+${<RED>a<RESET><GREEN>x<RESET>%%.*}\n+${<RED>a<RESET><GREEN>x<RESET>^^}\n+${<RED>a<RESET><GREEN>x<RESET>,}\n+${<RED>a<RESET><GREEN>x<RESET>,,}\n+${!<RED>a<RESET><GREEN>x<RESET>}\n+${<RED>a<RESET><GREEN>x<RESET>[@]}\n+${<RED>a<RESET><GREEN>x<RESET>:?error message}\n+${<RED>a<RESET><GREEN>x<RESET>:2:3}\n+ls <RED>-a<RESET><GREEN>-x<RESET>\n+ls <RED>--a<RESET><GREEN>--x<RESET>\ndiff --git a/t/t4034/bash/post b/t/t4034/bash/post\nnew file mode 100644\nindex 0000000000..60ba6a2e75\n--- /dev/null\n+++ b/t/t4034/bash/post\n@@ -0,0 +1,25 @@\n+new_var=10\n+x=456\n+y=2.71\n+z=.75\n+echo $USERNAME\n+${HOMEDIR}\n+if [ \"$x\" == \"$y\" ] || [ \"$x\" != \"$y\" ]; then echo \"OK\"; fi\n+((x+=y))\n+((x-=y))\n+$((x<<y))\n+$((x>>y))\n+${x:-y}\n+${x:=y}\n+${x##*/}\n+${x%.*}\n+${x%%.*}\n+${x^^}\n+${x,}\n+${x,,}\n+${!x}\n+${x[@]}\n+${x:?error message}\n+${x:2:3}\n+ls -x\n+ls --x\ndiff --git a/t/t4034/bash/pre b/t/t4034/bash/pre\nnew file mode 100644\nindex 0000000000..09ac008a83\n--- /dev/null\n+++ b/t/t4034/bash/pre\n@@ -0,0 +1,25 @@\n+my_var=10\n+x=123\n+y=3.14\n+z=.5\n+echo $USER\n+${HOME}\n+if [ \"$a\" == \"$b\" ] || [ \"$c\" != \"$d\" ]; then echo \"OK\"; fi\n+((a+=b))\n+((a-=b))\n+$((a << b))\n+$((a >> b))\n+${a:-b}\n+${a:=b}\n+${a##*/}\n+${a%.*}\n+${a%%.*}\n+${a^^}\n+${a,}\n+${a,,}\n+${!a}\n+${a[@]}\n+${a:?error message}\n+${a:2:3}\n+ls -a\n+ls --a\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..4c77c7e0f6 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -64,15 +64,27 @@ PATTERNS(\"bash\",\n \t     /* Bashism identifier with optional parentheses */\n \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n \t \")\"\n-\t /* Optional whitespace */\n-\t \"[ \\t]*\"\n-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n+\t /* Everything after the function header is captured  */\n+\t \".*$\"\n \t /* End of captured text */\n \t \")\",\n \t /* -- */\n-\t /* Characters not in the default $IFS value */\n-\t \"[^ \\t]+\"),\n+\t /* Identifiers: variable and function names */\n+\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Numeric constants: integers and decimals */\n+\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n+\t /* Shell variables: $VAR, ${VAR} */\n+\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n+\t  /* Logical and comparison operators */\n+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n+\t /* Assignment and arithmetic operators */\n+\t \"|[-+*/%&|^!=<>]=?\"\n+\t /* Additional parameter expansion operators */\n+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n+\t /* Command-line options (to avoid splitting -option) */\n+\t \"|--?[a-zA-Z0-9_-]+\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"bibtex\",\n \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n \t /* -- */\n-- \n2.48.0\n\n"},{"id":"515284","messageId":"xmqqfrivd4ei.fsf@gitster.g","threadId":"62935","inReplyTo":"20250328200525.4437-1-dhar61595@gmail.com","subject":"Re: [PATCH v3 0/1] userdiff: improve Bash function and word regex patterns","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-03-29T19:26:45Z","receivedAt":"2025-03-29T19:26:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Moumita <dhar61595@gmail.com> writes:\n\n>     + ## t/t4018/bash-bashism-style-multiline-function (new) ##\n>     +@@\n>     ++function RIGHT \\\n>     ++{    \n>     ++    echo 'ChangeMe'\n>     ++}\n>     + \\ No newline at end of file\n>     +\n>     + ## t/t4018/bash-posix-style-multiline-function (new) ##\n>     +@@\n>     ++RIGHT() \\\n>     ++{\n>     ++    ChangeMe\n>     ++}\n>     + \\ No newline at end of file\n\nFor these new test, is it essential that these sample files end in\nincomplete lines?  In other words, are these tests trying to make\nsure that the function line is correctly found even if the function\nbody is at the end of the file that lack the final terminating LF?\n\nIf that is what they are testing, please add comments near the\nbeginning of the file to tell future developers that it is essential\nthat they keep these files end in incomplete lines and why.\n\nIf that is not what these tests are checking, then make these lines\ncomplete lines instead, as they waste future developers' time making\nthem wonder if there are valid reasons why these files must end in\nincomplete lines.\n\n"},{"id":"515298","messageId":"CAF=ncLYYcPD=17FBUnnWLW_UmwkasSUzKWm-ZeHjAuWeiF981g@mail.gmail.com","threadId":"62935","inReplyTo":"xmqqfrivd4ei.fsf@gitster.g","subject":"Re: [PATCH v3 0/1] userdiff: improve Bash function and word regex patterns","fromName":"MOUMITA DHAR","fromEmail":"dhar61595@gmail.com","sentAt":"2025-03-30T12:28:43Z","receivedAt":"2025-03-30T12:36:16Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"On Sun, 30 Mar 2025 at 00:56, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Moumita <dhar61595@gmail.com> writes:\n>\n> >     + ## t/t4018/bash-bashism-style-multiline-function (new) ##\n> >     +@@\n> >     ++function RIGHT \\\n> >     ++{\n> >     ++    echo 'ChangeMe'\n> >     ++}\n> >     + \\ No newline at end of file\n> >     +\n> >     + ## t/t4018/bash-posix-style-multiline-function (new) ##\n> >     +@@\n> >     ++RIGHT() \\\n> >     ++{\n> >     ++    ChangeMe\n> >     ++}\n> >     + \\ No newline at end of file\n>\n> For these new test, is it essential that these sample files end in\n> incomplete lines?  In other words, are these tests trying to make\n> sure that the function line is correctly found even if the function\n> body is at the end of the file that lack the final terminating LF?\n\n>No , the skipping of newline at the end of the test files is not intended.The tests are only meant to check multiline function detection, ensuring that a function body starting on the next line after a line continuation \\ is correctly recognized.\n\n> If that is what they are testing, please add comments near the\n> beginning of the file to tell future developers that it is essential\n> that they keep these files end in incomplete lines and why.\n>\n> If that is not what these tests are checking, then make these lines\n> complete lines instead, as they waste future developers' time making\n> them wonder if there are valid reasons why these files must end in\n> incomplete lines.\n> Yes I will do that and send the patch again . Thank you for the feedback.\n"},{"id":"515299","messageId":"20250330134018.9662-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250328200525.4437-1-dhar61595@gmail.com","subject":"[PATCH v4 0/1][GSOC] userdiff:Added newlines at the end of the test cases","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-03-30T13:39:56Z","receivedAt":"2025-03-30T13:41:38Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"While making this change, I  considered whether shell scripts can lack a final newline and whether it would be useful to test this case. Technically, a shell script can be missing a final newline, \nand shells like Bash can still process it. However, this is uncommon and generally discouraged due to POSIX and Unix conventions and\nalso although Bash can process such files, missing a final newline can cause unexpected behavior in edge cases—such as when concatenating scripts, where the last \nline of one script might merge with the first line of another.\n\nGiven these considerations, I have added the missing newline to the test files to align with standard conventions and avoid\n unnecessary confusion for future developers.\n\nIf testing function detection in a file without a final newline is considered useful\n, I can add a separate test specifically for that edge case. Let me know if that would be helpful.\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n t/t4018/bash-bashism-style-multiline-function |  4 +++\n t/t4018/bash-posix-style-multiline-function   |  4 +++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 30 +++++++++++++++++++\n t/t4034/bash/post                             | 25 ++++++++++++++++\n t/t4034/bash/pre                              | 25 ++++++++++++++++\n userdiff.c                                    | 24 +++++++++++----\n 8 files changed, 110 insertions(+), 6 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\nRange-diff against v3:\n1:  3d077fadc4 ! 1:  40cffd3b4a userdiff: extend Bash pattern to cover more shell function forms\n    @@ t/t4018/bash-bashism-style-multiline-function (new)\n     +{    \n     +    echo 'ChangeMe'\n     +}\n    - \\ No newline at end of file\n     \n      ## t/t4018/bash-posix-style-multiline-function (new) ##\n     @@\n    @@ t/t4018/bash-posix-style-multiline-function (new)\n     +{\n     +    ChangeMe\n     +}\n    - \\ No newline at end of file\n     \n      ## t/t4018/bash-posix-style-single-command-function (new) ##\n     @@\n-- \n2.48.0\n\n"},{"id":"515300","messageId":"20250330134018.9662-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250330134018.9662-1-dhar61595@gmail.com","subject":"[PATCH v4 1/1][GSOC] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-03-30T13:39:57Z","receivedAt":"2025-03-30T13:41:56Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"From: Moumita Dhar <dhar61595@gmail.com>\n\nThe previous function regex required explicit matching of function\nbodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n\n- It failed to capture valid functions where `{` was on the next line\n  due to line continuation (`\\`).\n- It did not recognize functions with single  command body, such as\n  `x () echo hello`.\n\nReplacing the function body matching logic with `.*$`, ensures\nthat everything on the function definition line is captured,\naligning with other userdiff drivers and improving hunk headers in\n`git diff`.\n\nAdditionally, the word regex is refined to better recognize shell\nsyntax, including additional parameter expansion operators and\ncommand-line options, improving syntax-aware diffs.\n\nSigned-off-by: Moumita Dhar <dhar61595@gmail.com>\n---\n t/t4018/bash-bashism-style-multiline-function |  4 +++\n t/t4018/bash-posix-style-multiline-function   |  4 +++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 30 +++++++++++++++++++\n t/t4034/bash/post                             | 25 ++++++++++++++++\n t/t4034/bash/pre                              | 25 ++++++++++++++++\n userdiff.c                                    | 24 +++++++++++----\n 8 files changed, 110 insertions(+), 6 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\ndiff --git a/t/t4018/bash-bashism-style-multiline-function b/t/t4018/bash-bashism-style-multiline-function\nnew file mode 100644\nindex 0000000000..284d50dd99\n--- /dev/null\n+++ b/t/t4018/bash-bashism-style-multiline-function\n@@ -0,0 +1,4 @@\n+function RIGHT \\\n+{    \n+    echo 'ChangeMe'\n+}\ndiff --git a/t/t4018/bash-posix-style-multiline-function b/t/t4018/bash-posix-style-multiline-function\nnew file mode 100644\nindex 0000000000..cc8727cbcd\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-multiline-function\n@@ -0,0 +1,4 @@\n+RIGHT() \\\n+{\n+    ChangeMe\n+}\ndiff --git a/t/t4018/bash-posix-style-single-command-function b/t/t4018/bash-posix-style-single-command-function\nnew file mode 100644\nindex 0000000000..398ae1c5d2\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-single-command-function\n@@ -0,0 +1,3 @@\n+RIGHT() echo \"hello\"\n+\n+    ChangeMe\ndiff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\nindex f51d3557f1..0be647c2fb 100755\n--- a/t/t4034-diff-words.sh\n+++ b/t/t4034-diff-words.sh\n@@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n \n test_language_driver ada\n test_language_driver bibtex\n+test_language_driver bash\n test_language_driver cpp\n test_language_driver csharp\n test_language_driver css\ndiff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\nnew file mode 100644\nindex 0000000000..a0f7cbd5a3\n--- /dev/null\n+++ b/t/t4034/bash/expect\n@@ -0,0 +1,30 @@\n+<BOLD>diff --git a/pre b/post<RESET>\n+<BOLD>index 09ac008..60ba6a2 100644<RESET>\n+<BOLD>--- a/pre<RESET>\n+<BOLD>+++ b/post<RESET>\n+<CYAN>@@ -1,25 +1,25 @@<RESET>\n+<RED>my_var<RESET><GREEN>new_var<RESET>=10\n+x=<RED>123<RESET><GREEN>456<RESET>\n+y=<RED>3.14<RESET><GREEN>2.71<RESET>\n+z=<RED>.5<RESET><GREEN>.75<RESET>\n+echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n+${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n+if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n+((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n+((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n+$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n+$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n+${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n+${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n+${<RED>a<RESET><GREEN>x<RESET>##*/}\n+${<RED>a<RESET><GREEN>x<RESET>%.*}\n+${<RED>a<RESET><GREEN>x<RESET>%%.*}\n+${<RED>a<RESET><GREEN>x<RESET>^^}\n+${<RED>a<RESET><GREEN>x<RESET>,}\n+${<RED>a<RESET><GREEN>x<RESET>,,}\n+${!<RED>a<RESET><GREEN>x<RESET>}\n+${<RED>a<RESET><GREEN>x<RESET>[@]}\n+${<RED>a<RESET><GREEN>x<RESET>:?error message}\n+${<RED>a<RESET><GREEN>x<RESET>:2:3}\n+ls <RED>-a<RESET><GREEN>-x<RESET>\n+ls <RED>--a<RESET><GREEN>--x<RESET>\ndiff --git a/t/t4034/bash/post b/t/t4034/bash/post\nnew file mode 100644\nindex 0000000000..60ba6a2e75\n--- /dev/null\n+++ b/t/t4034/bash/post\n@@ -0,0 +1,25 @@\n+new_var=10\n+x=456\n+y=2.71\n+z=.75\n+echo $USERNAME\n+${HOMEDIR}\n+if [ \"$x\" == \"$y\" ] || [ \"$x\" != \"$y\" ]; then echo \"OK\"; fi\n+((x+=y))\n+((x-=y))\n+$((x<<y))\n+$((x>>y))\n+${x:-y}\n+${x:=y}\n+${x##*/}\n+${x%.*}\n+${x%%.*}\n+${x^^}\n+${x,}\n+${x,,}\n+${!x}\n+${x[@]}\n+${x:?error message}\n+${x:2:3}\n+ls -x\n+ls --x\ndiff --git a/t/t4034/bash/pre b/t/t4034/bash/pre\nnew file mode 100644\nindex 0000000000..09ac008a83\n--- /dev/null\n+++ b/t/t4034/bash/pre\n@@ -0,0 +1,25 @@\n+my_var=10\n+x=123\n+y=3.14\n+z=.5\n+echo $USER\n+${HOME}\n+if [ \"$a\" == \"$b\" ] || [ \"$c\" != \"$d\" ]; then echo \"OK\"; fi\n+((a+=b))\n+((a-=b))\n+$((a << b))\n+$((a >> b))\n+${a:-b}\n+${a:=b}\n+${a##*/}\n+${a%.*}\n+${a%%.*}\n+${a^^}\n+${a,}\n+${a,,}\n+${!a}\n+${a[@]}\n+${a:?error message}\n+${a:2:3}\n+ls -a\n+ls --a\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..4c77c7e0f6 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -64,15 +64,27 @@ PATTERNS(\"bash\",\n \t     /* Bashism identifier with optional parentheses */\n \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n \t \")\"\n-\t /* Optional whitespace */\n-\t \"[ \\t]*\"\n-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n+\t /* Everything after the function header is captured  */\n+\t \".*$\"\n \t /* End of captured text */\n \t \")\",\n \t /* -- */\n-\t /* Characters not in the default $IFS value */\n-\t \"[^ \\t]+\"),\n+\t /* Identifiers: variable and function names */\n+\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Numeric constants: integers and decimals */\n+\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n+\t /* Shell variables: $VAR, ${VAR} */\n+\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n+\t  /* Logical and comparison operators */\n+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n+\t /* Assignment and arithmetic operators */\n+\t \"|[-+*/%&|^!=<>]=?\"\n+\t /* Additional parameter expansion operators */\n+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n+\t /* Command-line options (to avoid splitting -option) */\n+\t \"|--?[a-zA-Z0-9_-]+\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"bibtex\",\n \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n \t /* -- */\n-- \n2.48.0\n\n"},{"id":"517134","messageId":"xmqq4iy2u2ip.fsf@gitster.g","threadId":"62935","inReplyTo":"20250330134018.9662-2-dhar61595@gmail.com","subject":"Re: [PATCH v4 1/1][GSOC] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-02T21:27:26Z","receivedAt":"2025-05-02T21:27:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Moumita <dhar61595@gmail.com> writes:\n\n> From: Moumita Dhar <dhar61595@gmail.com>\n>\n> The previous function regex required explicit matching of function\n> bodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n>\n> - It failed to capture valid functions where `{` was on the next line\n>   due to line continuation (`\\`).\n> - It did not recognize functions with single  command body, such as\n>   `x () echo hello`.\n>\n> Replacing the function body matching logic with `.*$`, ensures\n> that everything on the function definition line is captured,\n> aligning with other userdiff drivers and improving hunk headers in\n> `git diff`.\n>\n> Additionally, the word regex is refined to better recognize shell\n> syntax, including additional parameter expansion operators and\n> command-line options, improving syntax-aware diffs.\n>\n> Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n> ---\n\nThis iteration hasn't seen any reviews or responses.  Is everybody\nwho participated in reviews in the previous rounds happy with it?\n\nThanks.\n"},{"id":"517356","messageId":"9d548d94-eed8-434a-a9ef-67df694c5c79@kdbg.org","threadId":"62935","inReplyTo":"20250330134018.9662-2-dhar61595@gmail.com","subject":"Re: [PATCH v4 1/1][GSOC] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-05-06T16:30:04Z","receivedAt":"2025-05-06T16:30:19Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 30.03.25 um 15:39 schrieb Moumita:\n> From: Moumita Dhar <dhar61595@gmail.com>\n> \n> The previous function regex required explicit matching of function\n> bodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n> \n> - It failed to capture valid functions where `{` was on the next line\n>   due to line continuation (`\\`).\n> - It did not recognize functions with single  command body, such as\n>   `x () echo hello`.\n\nGood.\n\n> Replacing the function body matching logic with `.*$`, ensures\n> that everything on the function definition line is captured,\n> aligning with other userdiff drivers and improving hunk headers in\n> `git diff`.\n\nPersonally, I am a bit allergic against marketing speak. *Of course* do\nwe intend improve the code. No need to mention it.\n\n> Additionally, the word regex is refined to better recognize shell\n> syntax, including additional parameter expansion operators and\n> command-line options, improving syntax-aware diffs.\n\nGood. (But see above about \"improving\".)\n\n> \n> Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n> ---\n\n> diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\n> index f51d3557f1..0be647c2fb 100755\n> --- a/t/t4034-diff-words.sh\n> +++ b/t/t4034-diff-words.sh\n> @@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n>  \n>  test_language_driver ada\n>  test_language_driver bibtex\n> +test_language_driver bash\n>  test_language_driver cpp\n>  test_language_driver csharp\n>  test_language_driver css\n\nIncluding a test for word-diff is very much appreciated!\n\n> diff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\n> new file mode 100644\n> index 0000000000..a0f7cbd5a3\n> --- /dev/null\n> +++ b/t/t4034/bash/expect\n> @@ -0,0 +1,30 @@\n> +<BOLD>diff --git a/pre b/post<RESET>\n> +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n> +<BOLD>--- a/pre<RESET>\n> +<BOLD>+++ b/post<RESET>\n> +<CYAN>@@ -1,25 +1,25 @@<RESET>\n> +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n> +x=<RED>123<RESET><GREEN>456<RESET>\n> +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n> +z=<RED>.5<RESET><GREEN>.75<RESET>\n\nOK.\n\n> +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n\nThis tests a typical variable expansion. Good.\n\n> +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n\nA more elaborate variable expansion does not include the surrounding ${\n}. Good.\n\n> +if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n\nThis line also only tests variable expansions and is quite redundant. It\ncould test the operators that we see, but it doesn't. See below for\nideas how to do that.\n\nAnd all from here...\n\n> +((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n> +((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n> +$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n> +$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n> +${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n> +${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n> +${<RED>a<RESET><GREEN>x<RESET>##*/}\n> +${<RED>a<RESET><GREEN>x<RESET>%.*}\n> +${<RED>a<RESET><GREEN>x<RESET>%%.*}\n> +${<RED>a<RESET><GREEN>x<RESET>^^}\n> +${<RED>a<RESET><GREEN>x<RESET>,}\n> +${<RED>a<RESET><GREEN>x<RESET>,,}\n\n... to here also only test variable expansions, but not the operators.\nAs written, they are totally redundant and should be dropped or improved.\n\nTo test, for example, that '##' is kept together as an operator, you\nhave to have\n\n   ${x#*/}\n\nin the pre-image and\n\n   ${x##*/}\n\nin the post-image, so that we see\n\n   ${x<RED>#<RESET><GREEN>##<RESET>*/}\n\nin the result. If '##' were two tokens (due to a bug in the pattern), we\nwould see\n\n   ${x#<GREEN>#<RESET>*/}\n\nin the result.\n\nYou should go through all not-single-character operators and have a\npre-image and a post-image where one characters is added or removed. The\nfollowing is not a correct test:\n\npre:    ((x+=b))\npost:   ((x-=b))\nresult: ((x<RED>+=<RESET><GREEN>-=<RESET>))\n\nbecause we would see this result even if the pattern has a bug that\nrecognizes only one of += or -=, but would split the other one. A\ncorrect test would be:\n\npre:    ((x+b))\npost:   ((x+=b))\nresult: ((x<RED>+<RESET><GREEN>+=<RESET>))\n\n> +${!<RED>a<RESET><GREEN>x<RESET>}\n\nHere, you should have no '!' in the pre-image and the '!' in the post\nimage and not change the name. Then the test demonstrates that '!' is\nits own token and not merged with '${' (if that is the intent of the\ntest; otherwise it is a redunant test).\n\n> +${<RED>a<RESET><GREEN>x<RESET>[@]}\n\nIf you want to test that '@' is not merged with the brackets, then you\ncan have, for example '*' in the pre-image and '@' in the post image.\n\n> +${<RED>a<RESET><GREEN>x<RESET>:?error message}\n\nEither redundant or another two-character operator to test.\n\n> +${<RED>a<RESET><GREEN>x<RESET>:2:3}\n\nRedundant.\n\n> +ls <RED>-a<RESET><GREEN>-x<RESET>\n> +ls <RED>--a<RESET><GREEN>--x<RESET>\n\nBoth are good tests.\n\n> diff --git a/userdiff.c b/userdiff.c\n> index 340c4eb4f7..4c77c7e0f6 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -64,15 +64,27 @@ PATTERNS(\"bash\",\n>  \t     /* Bashism identifier with optional parentheses */\n>  \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n>  \t \")\"\n> -\t /* Optional whitespace */\n> -\t \"[ \\t]*\"\n> -\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n> -\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n> +\t /* Everything after the function header is captured  */\n> +\t \".*$\"\n\nI remember suggesting to capture everything after the function header.\nHowever, If I am not mistaken, this does not do what I intended (and as\nwritten it means that a pointless matching operation happens). The hunk\nheader shows everything that is in the outermost parentheses (group).\nThis catch-all, however, is even outside the outermost group and not\ncaptured. It should be above the closing parenthesis that we see in the\ncontext.\n\nTo test for this, you can have one test where \"RIGHT\" is not the\nfunction name, but in the comment on the function header line.\n\n>  \t /* End of captured text */\n>  \t \")\",\n>  \t /* -- */\n> -\t /* Characters not in the default $IFS value */\n> -\t \"[^ \\t]+\"),\n> +\t /* Identifiers: variable and function names */\n> +\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n> +\t /* Numeric constants: integers and decimals */\n> +\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n> +\t /* Shell variables: $VAR, ${VAR} */\n> +\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n> +\t  /* Logical and comparison operators */\n> +\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n> +\t /* Assignment and arithmetic operators */\n> +\t \"|[-+*/%&|^!=<>]=?\"\n> +\t /* Additional parameter expansion operators */\n> +\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n\nWhat is the purpose of this \"/[a-zA-Z0-9_-]+\"? It would mean, for\nexample that changes in alphanumeric path names would include the slash\nin the changed part. I don't think this should be here.\n\n> +\t /* Command-line options (to avoid splitting -option) */\n> +\t \"|--?[a-zA-Z0-9_-]+\"\n> +\t /* Brackets and grouping symbols */\n> +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n>  PATTERNS(\"bibtex\",\n>  \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n>  \t /* -- */\n\n-- Hannes\n\n"},{"id":"517727","messageId":"CAF=ncLbaxYPRx79wYVgpMFuV7UGSObMD+A3zTssWvtVJmXHXVg@mail.gmail.com","threadId":"62935","inReplyTo":"9d548d94-eed8-434a-a9ef-67df694c5c79@kdbg.org","subject":"Re: [PATCH v4 1/1][GSOC] userdiff: extend Bash pattern to cover more shell function forms","fromName":"MOUMITA DHAR","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-10T11:37:30Z","receivedAt":"2025-05-10T11:46:52Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"On Tue, 6 May 2025 at 22:00, Johannes Sixt <j6t@kdbg.org> wrote:\n>\n> Am 30.03.25 um 15:39 schrieb Moumita:\n> > From: Moumita Dhar <dhar61595@gmail.com>\n> >\n> > The previous function regex required explicit matching of function\n> > bodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n> >\n> > - It failed to capture valid functions where `{` was on the next line\n> >   due to line continuation (`\\`).\n> > - It did not recognize functions with single  command body, such as\n> >   `x () echo hello`.\n>\n> Good.\n>\n> > Replacing the function body matching logic with `.*$`, ensures\n> > that everything on the function definition line is captured,\n> > aligning with other userdiff drivers and improving hunk headers in\n> > `git diff`.\n>\n> Personally, I am a bit allergic against marketing speak. *Of course* do\n> we intend improve the code. No need to mention it.\n>\n> > Additionally, the word regex is refined to better recognize shell\n> > syntax, including additional parameter expansion operators and\n> > command-line options, improving syntax-aware diffs.\n>\n> Good. (But see above about \"improving\".)\n>\n> > Yes I will be careful about the commit messages.\n\n> > Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n> > ---\n>\n> > diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\n> > index f51d3557f1..0be647c2fb 100755\n> > --- a/t/t4034-diff-words.sh\n> > +++ b/t/t4034-diff-words.sh\n> > @@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n> >\n> >  test_language_driver ada\n> >  test_language_driver bibtex\n> > +test_language_driver bash\n> >  test_language_driver cpp\n> >  test_language_driver csharp\n> >  test_language_driver css\n>\n> Including a test for word-diff is very much appreciated!\n>\n> > diff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\n> > new file mode 100644\n> > index 0000000000..a0f7cbd5a3\n> > --- /dev/null\n> > +++ b/t/t4034/bash/expect\n> > @@ -0,0 +1,30 @@\n> > +<BOLD>diff --git a/pre b/post<RESET>\n> > +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n> > +<BOLD>--- a/pre<RESET>\n> > +<BOLD>+++ b/post<RESET>\n> > +<CYAN>@@ -1,25 +1,25 @@<RESET>\n> > +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n> > +x=<RED>123<RESET><GREEN>456<RESET>\n> > +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n> > +z=<RED>.5<RESET><GREEN>.75<RESET>\n>\n> OK.\n>\n> > +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n>\n> This tests a typical variable expansion. Good.\n>\n> > +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n>\n> A more elaborate variable expansion does not include the surrounding ${\n> }. Good.\n>\n> > +if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n>\n> This line also only tests variable expansions and is quite redundant. It\n> could test the operators that we see, but it doesn't. See below for\n> ideas how to do that.\n>\n> And all from here...\n>\n> > +((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n> > +((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n> > +$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n> > +$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n> > +${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n> > +${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n> > +${<RED>a<RESET><GREEN>x<RESET>##*/}\n> > +${<RED>a<RESET><GREEN>x<RESET>%.*}\n> > +${<RED>a<RESET><GREEN>x<RESET>%%.*}\n> > +${<RED>a<RESET><GREEN>x<RESET>^^}\n> > +${<RED>a<RESET><GREEN>x<RESET>,}\n> > +${<RED>a<RESET><GREEN>x<RESET>,,}\n>\n> ... to here also only test variable expansions, but not the operators.\n> As written, they are totally redundant and should be dropped or improved.\n>\n> To test, for example, that '##' is kept together as an operator, you\n> have to have\n>\n>    ${x#*/}\n>\n> in the pre-image and\n>\n>    ${x##*/}\n>\n> in the post-image, so that we see\n>\n>    ${x<RED>#<RESET><GREEN>##<RESET>*/}\n>\n> in the result. If '##' were two tokens (due to a bug in the pattern), we\n> would see\n>\n>    ${x#<GREEN>#<RESET>*/}\n>\n> in the result.\n>\n> You should go through all not-single-character operators and have a\n> pre-image and a post-image where one characters is added or removed. The\n> following is not a correct test:\n>\n> pre:    ((x+=b))\n> post:   ((x-=b))\n> result: ((x<RED>+=<RESET><GREEN>-=<RESET>))\n>\n> because we would see this result even if the pattern has a bug that\n> recognizes only one of += or -=, but would split the other one. A\n> correct test would be:\n>\n> pre:    ((x+b))\n> post:   ((x+=b))\n> result: ((x<RED>+<RESET><GREEN>+=<RESET>))\n>\n> > +${!<RED>a<RESET><GREEN>x<RESET>}\n>\n> Here, you should have no '!' in the pre-image and the '!' in the post\n> image and not change the name. Then the test demonstrates that '!' is\n> its own token and not merged with '${' (if that is the intent of the\n> test; otherwise it is a redunant test).\n>\n> > +${<RED>a<RESET><GREEN>x<RESET>[@]}\n>\n> If you want to test that '@' is not merged with the brackets, then you\n> can have, for example '*' in the pre-image and '@' in the post image.\n\n>\n> > +${<RED>a<RESET><GREEN>x<RESET>:?error message}\n>\n> Either redundant or another two-character operator to test.\n\nOk I got it.\n>\n> > +${<RED>a<RESET><GREEN>x<RESET>:2:3}\n>\n> Redundant.\n>\nI understand I will remove it.\n\n> > +ls <RED>-a<RESET><GREEN>-x<RESET>\n> > +ls <RED>--a<RESET><GREEN>--x<RESET>\n>\n> Both are good tests.\n\nThank you. I understand the suggestions about the word diff tests and\nI will remove the redundant ones and write the correct tests .\n>\n> > diff --git a/userdiff.c b/userdiff.c\n> > index 340c4eb4f7..4c77c7e0f6 100644\n> > --- a/userdiff.c\n> > +++ b/userdiff.c\n> > @@ -64,15 +64,27 @@ PATTERNS(\"bash\",\n> >            /* Bashism identifier with optional parentheses */\n> >            \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n> >        \")\"\n> > -      /* Optional whitespace */\n> > -      \"[ \\t]*\"\n> > -      /* Compound command starting with `{`, `(`, `((` or `[[` */\n> > -      \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n> > +      /* Everything after the function header is captured  */\n> > +      \".*$\"\n>\n> I remember suggesting to capture everything after the function header.\n> However, If I am not mistaken, this does not do what I intended (and as\n> written it means that a pointless matching operation happens). The hunk\n> header shows everything that is in the outermost parentheses (group).\n> This catch-all, however, is even outside the outermost group and not\n> captured. It should be above the closing parenthesis that we see in the\n> context.\n\n> I am sorry I want to understand a thing  , we want everything from the function name to the end of the line to be the hunk header right ? So in the pattern \"^[ \\t]*\"\n/* Start of captured text */\n\"(\"\n\"(\"\n     /* POSIX identifier with mandatory parentheses */\n     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n\"|\"\n     /* Bashism identifier with optional parentheses */\n     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n\")\"\n/* Optional whitespace */\n\"[ \\t]*\"\n/* Compound command starting with `{`, `(`, `((` or `[[` */\n\"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n/* End of captured text */\n\")\"\nif I replace\n\"[ \\t]*\"\n\"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"  part with .*$ then will it not capture the\nentire line ? I mean the outermost group ends here-\n\n /* End of captured text */\n\")\"\nright ? What am I getting wrong ?\n\n> To test for this, you can have one test where \"RIGHT\" is not the\n> function name, but in the comment on the function header line.\n>\n> >        /* End of captured text */\n> >        \")\",\n> >        /* -- */\n> > -      /* Characters not in the default $IFS value */\n> > -      \"[^ \\t]+\"),\n> > +      /* Identifiers: variable and function names */\n> > +       \"[a-zA-Z_][a-zA-Z0-9_]*\"\n> > +      /* Numeric constants: integers and decimals */\n> > +       \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n> > +      /* Shell variables: $VAR, ${VAR} */\n> > +       \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n> > +       /* Logical and comparison operators */\n> > +      \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n> > +      /* Assignment and arithmetic operators */\n> > +      \"|[-+*/%&|^!=<>]=?\"\n> > +      /* Additional parameter expansion operators */\n> > +      \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n>\n> What is the purpose of this \"/[a-zA-Z0-9_-]+\"? It would mean, for\n> example that changes in alphanumeric path names would include the slash\n> in the changed part. I don't think this should be here.\n\n> Ok I got it.\n\n> > +      /* Command-line options (to avoid splitting -option) */\n> > +      \"|--?[a-zA-Z0-9_-]+\"\n> > +      /* Brackets and grouping symbols */\n> > +      \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n> >  PATTERNS(\"bibtex\",\n> >        \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n> >        /* -- */\n>\n> -- Hannes\n>\n"},{"id":"517733","messageId":"ada6ff3b-1311-4d47-a098-7abc0ea9c46b@kdbg.org","threadId":"62935","inReplyTo":"CAF=ncLbaxYPRx79wYVgpMFuV7UGSObMD+A3zTssWvtVJmXHXVg@mail.gmail.com","subject":"Re: [PATCH v4 1/1][GSOC] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-05-10T12:40:39Z","receivedAt":"2025-05-10T12:40:50Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"The way how you cited the message makes it very difficult to see what is\ncitation, code, or your message text. Please insert sufficient blank\nlines in your replies. Also, indentation may help to declare what is the\ncode that you talk about.\n\nAm 10.05.25 um 13:37 schrieb MOUMITA DHAR:\n> On Tue, 6 May 2025 at 22:00, Johannes Sixt <j6t@kdbg.org> wrote:\n>>> diff --git a/userdiff.c b/userdiff.c\n>>> index 340c4eb4f7..4c77c7e0f6 100644\n>>> --- a/userdiff.c\n>>> +++ b/userdiff.c\n>>> @@ -64,15 +64,27 @@ PATTERNS(\"bash\",\n>>>            /* Bashism identifier with optional parentheses */\n>>>            \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n>>>        \")\"\n>>> -      /* Optional whitespace */\n>>> -      \"[ \\t]*\"\n>>> -      /* Compound command starting with `{`, `(`, `((` or `[[` */\n>>> -      \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n>>> +      /* Everything after the function header is captured  */\n>>> +      \".*$\"\n>>\n>> I remember suggesting to capture everything after the function header.\n>> However, If I am not mistaken, this does not do what I intended (and as\n>> written it means that a pointless matching operation happens). The hunk\n>> header shows everything that is in the outermost parentheses (group).\n>> This catch-all, however, is even outside the outermost group and not\n>> captured. It should be above the closing parenthesis that we see in the\n>> context.\n> > I am sorry I want to understand a thing  , we want everything from\n> the function name to the end of the line to be the hunk header\n> right ? So in the pattern\n\n> \"^[ \\t]*\"> /* Start of captured text */\n> \"(\"\n> \"(\"\n>      /* POSIX identifier with mandatory parentheses */\n>      \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n> \"|\"\n>      /* Bashism identifier with optional parentheses */\n>      \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n> \")\"\n> /* Optional whitespace */\n> \"[ \\t]*\"\n> /* Compound command starting with `{`, `(`, `((` or `[[` */\n> \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n> /* End of captured text */\n> \")\"\n\n> if I replace\n\n> \"[ \\t]*\"\n> \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n\n> part with .*$ then will it not capture the\n> entire line ? I mean the outermost group ends here-\n> \n>  /* End of captured text */\n> \")\"\n> right ? What am I getting wrong ?\n\nIt is my error, sorry. I see now that your intended change was correct.\n\nHowever, in my tests, a comment after the function name is not captured.\nThe reason is that the parentheses are not balanced. My editor tells me\nthat the parenthesis in \"(function\" is matched with the lone \")\" in the\nnext line, but the latter is intended to match up with the \"(\" above the\n'/* POSIX' comment. There is something fishy going on and needs a fix.\n\nTo test, apply this:\n\ndiff --git a/t/t4018/bash-posix-style-function b/t/t4018/bash-posix-style-function\nindex a4d144856e..673c51b89e 100644\n--- a/t/t4018/bash-posix-style-function\n+++ b/t/t4018/bash-posix-style-function\n@@ -1,4 +1,4 @@\n-RIGHT() {\n+afunc () { # RIGHT\n \n     ChangeMe\n }\n\n-- Hannes\n\n"},{"id":"517775","messageId":"20250511125809.14180-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250330134018.9662-1-dhar61595@gmail.com","subject":"[PATCH v5 0/1] Added the closing \")\" to make sure is not unbalanced and corrected the tests for word diff","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-11T12:58:07Z","receivedAt":"2025-05-11T12:59:35Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"I wrapped the POSIX function pattern in an extra set of parentheses and added \na closing parenthesis in the Bashism function case to balance the opening group ,this makes the grouping explicit and consistent\nwith the outer structure of the regex. \n\nAlso I added the test case so that everything on the function definition line is captured correctly.\nAnd corrected the word diff test so that the operators like '+=' are treated as a single token.\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n t/t4018/bash-bashism-style-multiline-function |  4 ++\n .../bash-hunk-header-complete-line-capture    |  4 ++\n t/t4018/bash-posix-style-multiline-function   |  4 ++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 38 +++++++++++++++++++\n t/t4034/bash/post                             | 33 ++++++++++++++++\n t/t4034/bash/pre                              | 33 ++++++++++++++++\n userdiff.c                                    | 28 ++++++++++----\n 9 files changed, 140 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-hunk-header-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\nRange-diff against v4:\n1:  40cffd3b4a ! 1:  478b77e0c8 userdiff: extend Bash pattern to cover more shell function forms\n    @@ Commit message\n           `x () echo hello`.\n     \n         Replacing the function body matching logic with `.*$`, ensures\n    -    that everything on the function definition line is captured,\n    -    aligning with other userdiff drivers and improving hunk headers in\n    -    `git diff`.\n    +    that everything on the function definition line is captured.\n     \n         Additionally, the word regex is refined to better recognize shell\n         syntax, including additional parameter expansion operators and\n    -    command-line options, improving syntax-aware diffs.\n    +    command-line options.\n     \n         Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n     \n    @@ t/t4018/bash-bashism-style-multiline-function (new)\n     +    echo 'ChangeMe'\n     +}\n     \n    + ## t/t4018/bash-hunk-header-complete-line-capture (new) ##\n    +@@\n    ++func() { # RIGHT\n    ++\n    ++    ChangeMe\n    ++}\n    + \\ No newline at end of file\n    +\n      ## t/t4018/bash-posix-style-multiline-function (new) ##\n     @@\n     +RIGHT() \\\n    @@ t/t4034/bash/expect (new)\n     +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n     +<BOLD>--- a/pre<RESET>\n     +<BOLD>+++ b/post<RESET>\n    -+<CYAN>@@ -1,25 +1,25 @@<RESET>\n    ++<CYAN>@@ -1,33 +1,33 @@<RESET>\n     +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n     +x=<RED>123<RESET><GREEN>456<RESET>\n     +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n     +z=<RED>.5<RESET><GREEN>.75<RESET>\n     +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n     +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n    -+if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n    -+((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n    -+((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n    -+${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>##*/}\n    -+${<RED>a<RESET><GREEN>x<RESET>%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>%%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>^^}\n    -+${<RED>a<RESET><GREEN>x<RESET>,}\n    -+${<RED>a<RESET><GREEN>x<RESET>,,}\n    -+${!<RED>a<RESET><GREEN>x<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>[@]}\n    -+${<RED>a<RESET><GREEN>x<RESET>:?error message}\n    -+${<RED>a<RESET><GREEN>x<RESET>:2:3}\n    ++((a<RED>+<RESET><GREEN>+=<RESET>b))\n    ++((a<RED>*<RESET><GREEN>*=<RESET>b))\n    ++((a<RED>/<RESET><GREEN>/=<RESET>b))\n    ++((a<RED>%<RESET><GREEN>%=<RESET>b))\n    ++((a<RED>|<RESET><GREEN>|=<RESET>b))\n    ++((a<RED>^<RESET><GREEN>^=<RESET>b))\n    ++((a<RED>=<RESET><GREEN>==<RESET>b))\n    ++((a<RED>!<RESET><GREEN>!=<RESET>b))\n    ++((a<RED><<RESET><GREEN><=<RESET>b))\n    ++((a<RED>><RESET><GREEN>>=<RESET>b))\n    ++$((a<RED><<RESET><GREEN><<<RESET>b))\n    ++$((a<RED>><RESET><GREEN>>><RESET>b))\n    ++$((a<RED>&<RESET><GREEN>&&<RESET>b))\n    ++$((a<RED>|<RESET><GREEN>||<RESET>b))\n    ++${a<RED>:<RESET><GREEN>:-<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:=<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:+<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:?<RESET>b}\n    ++${a<RED>#<RESET><GREEN>##<RESET>*/}\n    ++${a<RED>%<RESET><GREEN>%%<RESET>.*}\n    ++${a<RED>^<RESET><GREEN>^^<RESET>}\n    ++${a<RED>,<RESET><GREEN>,,<RESET>}\n    ++${<GREEN>!<RESET>a}\n    ++${a[<RED>*<RESET><GREEN>@<RESET>]}\n    ++${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n     +ls <RED>-a<RESET><GREEN>-x<RESET>\n     +ls <RED>--a<RESET><GREEN>--x<RESET>\n     \n    @@ t/t4034/bash/post (new)\n     +z=.75\n     +echo $USERNAME\n     +${HOMEDIR}\n    -+if [ \"$x\" == \"$y\" ] || [ \"$x\" != \"$y\" ]; then echo \"OK\"; fi\n    -+((x+=y))\n    -+((x-=y))\n    -+$((x<<y))\n    -+$((x>>y))\n    -+${x:-y}\n    -+${x:=y}\n    -+${x##*/}\n    -+${x%.*}\n    -+${x%%.*}\n    -+${x^^}\n    -+${x,}\n    -+${x,,}\n    -+${!x}\n    -+${x[@]}\n    -+${x:?error message}\n    -+${x:2:3}\n    ++((a+=b))\n    ++((a*=b))\n    ++((a/=b))\n    ++((a%=b))\n    ++((a|=b))\n    ++((a^=b))\n    ++((a==b))\n    ++((a!=b))\n    ++((a<=b))\n    ++((a>=b))\n    ++$((a<<b))\n    ++$((a>>b))\n    ++$((a&&b))\n    ++$((a||b))\n    ++${a:-b}\n    ++${a:=b}\n    ++${a:+b}\n    ++${a:?b}\n    ++${a##*/}\n    ++${a%%.*}\n    ++${a^^}\n    ++${a,,}\n    ++${!a}\n    ++${a[@]}\n    ++${a:4:6}\n     +ls -x\n     +ls --x\n     \n    @@ t/t4034/bash/pre (new)\n     +z=.5\n     +echo $USER\n     +${HOME}\n    -+if [ \"$a\" == \"$b\" ] || [ \"$c\" != \"$d\" ]; then echo \"OK\"; fi\n    -+((a+=b))\n    -+((a-=b))\n    -+$((a << b))\n    -+$((a >> b))\n    -+${a:-b}\n    -+${a:=b}\n    -+${a##*/}\n    ++((a+b))\n    ++((a*b))\n    ++((a/b))\n    ++((a%b))\n    ++((a|b))\n    ++((a^b))\n    ++((a=b))\n    ++((a!b))\n    ++((a<b))\n    ++((a>b))\n    ++$((a<b))\n    ++$((a>b))\n    ++$((a&b))\n    ++$((a|b))\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a#*/}\n     +${a%.*}\n    -+${a%%.*}\n    -+${a^^}\n    ++${a^}\n     +${a,}\n    -+${a,,}\n    -+${!a}\n    -+${a[@]}\n    -+${a:?error message}\n    ++${a}\n    ++${a[*]}\n     +${a:2:3}\n     +ls -a\n     +ls --a\n     \n      ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"bash\",\n    + \t \"(\"\n    + \t \"(\"\n    + \t     /* POSIX identifier with mandatory parentheses */\n    +-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    ++\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    + \t \"|\"\n      \t     /* Bashism identifier with optional parentheses */\n    - \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    +-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    ++\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n      \t \")\"\n     -\t /* Optional whitespace */\n     -\t \"[ \\t]*\"\n    @@ userdiff.c: PATTERNS(\"bash\",\n     +\t /* Assignment and arithmetic operators */\n     +\t \"|[-+*/%&|^!=<>]=?\"\n     +\t /* Additional parameter expansion operators */\n    -+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n    ++\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n     +\t /* Command-line options (to avoid splitting -option) */\n     +\t \"|--?[a-zA-Z0-9_-]+\"\n     +\t /* Brackets and grouping symbols */\n-- \n2.48.0\n\n"},{"id":"517776","messageId":"20250511125809.14180-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250511125809.14180-1-dhar61595@gmail.com","subject":"[PATCH v5 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-11T12:58:08Z","receivedAt":"2025-05-11T12:59:57Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"From: Moumita Dhar <dhar61595@gmail.com>\n\nThe previous function regex required explicit matching of function\nbodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n\n- It failed to capture valid functions where `{` was on the next line\n  due to line continuation (`\\`).\n- It did not recognize functions with single  command body, such as\n  `x () echo hello`.\n\nReplacing the function body matching logic with `.*$`, ensures\nthat everything on the function definition line is captured.\n\nAdditionally, the word regex is refined to better recognize shell\nsyntax, including additional parameter expansion operators and\ncommand-line options.\n\nSigned-off-by: Moumita Dhar <dhar61595@gmail.com>\n---\n t/t4018/bash-bashism-style-multiline-function |  4 ++\n .../bash-hunk-header-complete-line-capture    |  4 ++\n t/t4018/bash-posix-style-multiline-function   |  4 ++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 38 +++++++++++++++++++\n t/t4034/bash/post                             | 33 ++++++++++++++++\n t/t4034/bash/pre                              | 33 ++++++++++++++++\n userdiff.c                                    | 28 ++++++++++----\n 9 files changed, 140 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-hunk-header-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\ndiff --git a/t/t4018/bash-bashism-style-multiline-function b/t/t4018/bash-bashism-style-multiline-function\nnew file mode 100644\nindex 0000000000..284d50dd99\n--- /dev/null\n+++ b/t/t4018/bash-bashism-style-multiline-function\n@@ -0,0 +1,4 @@\n+function RIGHT \\\n+{    \n+    echo 'ChangeMe'\n+}\ndiff --git a/t/t4018/bash-hunk-header-complete-line-capture b/t/t4018/bash-hunk-header-complete-line-capture\nnew file mode 100644\nindex 0000000000..818c8c5a5f\n--- /dev/null\n+++ b/t/t4018/bash-hunk-header-complete-line-capture\n@@ -0,0 +1,4 @@\n+func() { # RIGHT\n+\n+    ChangeMe\n+}\n\\ No newline at end of file\ndiff --git a/t/t4018/bash-posix-style-multiline-function b/t/t4018/bash-posix-style-multiline-function\nnew file mode 100644\nindex 0000000000..cc8727cbcd\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-multiline-function\n@@ -0,0 +1,4 @@\n+RIGHT() \\\n+{\n+    ChangeMe\n+}\ndiff --git a/t/t4018/bash-posix-style-single-command-function b/t/t4018/bash-posix-style-single-command-function\nnew file mode 100644\nindex 0000000000..398ae1c5d2\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-single-command-function\n@@ -0,0 +1,3 @@\n+RIGHT() echo \"hello\"\n+\n+    ChangeMe\ndiff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\nindex f51d3557f1..0be647c2fb 100755\n--- a/t/t4034-diff-words.sh\n+++ b/t/t4034-diff-words.sh\n@@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n \n test_language_driver ada\n test_language_driver bibtex\n+test_language_driver bash\n test_language_driver cpp\n test_language_driver csharp\n test_language_driver css\ndiff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\nnew file mode 100644\nindex 0000000000..17755e455f\n--- /dev/null\n+++ b/t/t4034/bash/expect\n@@ -0,0 +1,38 @@\n+<BOLD>diff --git a/pre b/post<RESET>\n+<BOLD>index 09ac008..60ba6a2 100644<RESET>\n+<BOLD>--- a/pre<RESET>\n+<BOLD>+++ b/post<RESET>\n+<CYAN>@@ -1,33 +1,33 @@<RESET>\n+<RED>my_var<RESET><GREEN>new_var<RESET>=10\n+x=<RED>123<RESET><GREEN>456<RESET>\n+y=<RED>3.14<RESET><GREEN>2.71<RESET>\n+z=<RED>.5<RESET><GREEN>.75<RESET>\n+echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n+${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n+((a<RED>+<RESET><GREEN>+=<RESET>b))\n+((a<RED>*<RESET><GREEN>*=<RESET>b))\n+((a<RED>/<RESET><GREEN>/=<RESET>b))\n+((a<RED>%<RESET><GREEN>%=<RESET>b))\n+((a<RED>|<RESET><GREEN>|=<RESET>b))\n+((a<RED>^<RESET><GREEN>^=<RESET>b))\n+((a<RED>=<RESET><GREEN>==<RESET>b))\n+((a<RED>!<RESET><GREEN>!=<RESET>b))\n+((a<RED><<RESET><GREEN><=<RESET>b))\n+((a<RED>><RESET><GREEN>>=<RESET>b))\n+$((a<RED><<RESET><GREEN><<<RESET>b))\n+$((a<RED>><RESET><GREEN>>><RESET>b))\n+$((a<RED>&<RESET><GREEN>&&<RESET>b))\n+$((a<RED>|<RESET><GREEN>||<RESET>b))\n+${a<RED>:<RESET><GREEN>:-<RESET>b}\n+${a<RED>:<RESET><GREEN>:=<RESET>b}\n+${a<RED>:<RESET><GREEN>:+<RESET>b}\n+${a<RED>:<RESET><GREEN>:?<RESET>b}\n+${a<RED>#<RESET><GREEN>##<RESET>*/}\n+${a<RED>%<RESET><GREEN>%%<RESET>.*}\n+${a<RED>^<RESET><GREEN>^^<RESET>}\n+${a<RED>,<RESET><GREEN>,,<RESET>}\n+${<GREEN>!<RESET>a}\n+${a[<RED>*<RESET><GREEN>@<RESET>]}\n+${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n+ls <RED>-a<RESET><GREEN>-x<RESET>\n+ls <RED>--a<RESET><GREEN>--x<RESET>\ndiff --git a/t/t4034/bash/post b/t/t4034/bash/post\nnew file mode 100644\nindex 0000000000..669e218c30\n--- /dev/null\n+++ b/t/t4034/bash/post\n@@ -0,0 +1,33 @@\n+new_var=10\n+x=456\n+y=2.71\n+z=.75\n+echo $USERNAME\n+${HOMEDIR}\n+((a+=b))\n+((a*=b))\n+((a/=b))\n+((a%=b))\n+((a|=b))\n+((a^=b))\n+((a==b))\n+((a!=b))\n+((a<=b))\n+((a>=b))\n+$((a<<b))\n+$((a>>b))\n+$((a&&b))\n+$((a||b))\n+${a:-b}\n+${a:=b}\n+${a:+b}\n+${a:?b}\n+${a##*/}\n+${a%%.*}\n+${a^^}\n+${a,,}\n+${!a}\n+${a[@]}\n+${a:4:6}\n+ls -x\n+ls --x\ndiff --git a/t/t4034/bash/pre b/t/t4034/bash/pre\nnew file mode 100644\nindex 0000000000..ada8470bac\n--- /dev/null\n+++ b/t/t4034/bash/pre\n@@ -0,0 +1,33 @@\n+my_var=10\n+x=123\n+y=3.14\n+z=.5\n+echo $USER\n+${HOME}\n+((a+b))\n+((a*b))\n+((a/b))\n+((a%b))\n+((a|b))\n+((a^b))\n+((a=b))\n+((a!b))\n+((a<b))\n+((a>b))\n+$((a<b))\n+$((a>b))\n+$((a&b))\n+$((a|b))\n+${a:b}\n+${a:b}\n+${a:b}\n+${a:b}\n+${a#*/}\n+${a%.*}\n+${a^}\n+${a,}\n+${a}\n+${a[*]}\n+${a:2:3}\n+ls -a\n+ls --a\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..655c8fe0b1 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -59,20 +59,32 @@ PATTERNS(\"bash\",\n \t \"(\"\n \t \"(\"\n \t     /* POSIX identifier with mandatory parentheses */\n-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n+\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n \t \"|\"\n \t     /* Bashism identifier with optional parentheses */\n-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n+\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n \t \")\"\n-\t /* Optional whitespace */\n-\t \"[ \\t]*\"\n-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n+\t /* Everything after the function header is captured  */\n+\t \".*$\"\n \t /* End of captured text */\n \t \")\",\n \t /* -- */\n-\t /* Characters not in the default $IFS value */\n-\t \"[^ \\t]+\"),\n+\t /* Identifiers: variable and function names */\n+\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Numeric constants: integers and decimals */\n+\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n+\t /* Shell variables: $VAR, ${VAR} */\n+\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n+\t  /* Logical and comparison operators */\n+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n+\t /* Assignment and arithmetic operators */\n+\t \"|[-+*/%&|^!=<>]=?\"\n+\t /* Additional parameter expansion operators */\n+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n+\t /* Command-line options (to avoid splitting -option) */\n+\t \"|--?[a-zA-Z0-9_-]+\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"bibtex\",\n \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n \t /* -- */\n-- \n2.48.0\n\n"},{"id":"517777","messageId":"20250511132802.16338-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250511125809.14180-1-dhar61595@gmail.com","subject":"","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-11T13:28:01Z","receivedAt":"2025-05-11T13:28:57Z","isPatch":false,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"Subject: [PATCH v6 0/1] Added the newline after the test in t/4018\n\nSorry , I forgot to add the newline after the test in t/4018 again so I had send the patch agaain.\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n t/t4018/bash-bashism-style-multiline-function |  4 ++\n .../bash-hunk-header-complete-line-capture    |  4 ++\n t/t4018/bash-posix-style-multiline-function   |  4 ++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 38 +++++++++++++++++++\n t/t4034/bash/post                             | 33 ++++++++++++++++\n t/t4034/bash/pre                              | 33 ++++++++++++++++\n userdiff.c                                    | 28 ++++++++++----\n 9 files changed, 140 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-hunk-header-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\nRange-diff against v5:\n1:  40cffd3b4a ! 1:  464cb8a1eb userdiff: extend Bash pattern to cover more shell function forms\n    @@ Commit message\n           `x () echo hello`.\n     \n         Replacing the function body matching logic with `.*$`, ensures\n    -    that everything on the function definition line is captured,\n    -    aligning with other userdiff drivers and improving hunk headers in\n    -    `git diff`.\n    +    that everything on the function definition line is captured.\n     \n         Additionally, the word regex is refined to better recognize shell\n         syntax, including additional parameter expansion operators and\n    -    command-line options, improving syntax-aware diffs.\n    +    command-line options.\n     \n         Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n     \n    @@ t/t4018/bash-bashism-style-multiline-function (new)\n     +function RIGHT \\\n     +{    \n     +    echo 'ChangeMe'\n    ++}\n    +\n    + ## t/t4018/bash-hunk-header-complete-line-capture (new) ##\n    +@@\n    ++func() { # RIGHT\n    ++\n    ++    ChangeMe\n     +}\n     \n      ## t/t4018/bash-posix-style-multiline-function (new) ##\n    @@ t/t4034/bash/expect (new)\n     +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n     +<BOLD>--- a/pre<RESET>\n     +<BOLD>+++ b/post<RESET>\n    -+<CYAN>@@ -1,25 +1,25 @@<RESET>\n    ++<CYAN>@@ -1,33 +1,33 @@<RESET>\n     +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n     +x=<RED>123<RESET><GREEN>456<RESET>\n     +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n     +z=<RED>.5<RESET><GREEN>.75<RESET>\n     +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n     +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n    -+if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n    -+((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n    -+((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n    -+${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>##*/}\n    -+${<RED>a<RESET><GREEN>x<RESET>%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>%%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>^^}\n    -+${<RED>a<RESET><GREEN>x<RESET>,}\n    -+${<RED>a<RESET><GREEN>x<RESET>,,}\n    -+${!<RED>a<RESET><GREEN>x<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>[@]}\n    -+${<RED>a<RESET><GREEN>x<RESET>:?error message}\n    -+${<RED>a<RESET><GREEN>x<RESET>:2:3}\n    ++((a<RED>+<RESET><GREEN>+=<RESET>b))\n    ++((a<RED>*<RESET><GREEN>*=<RESET>b))\n    ++((a<RED>/<RESET><GREEN>/=<RESET>b))\n    ++((a<RED>%<RESET><GREEN>%=<RESET>b))\n    ++((a<RED>|<RESET><GREEN>|=<RESET>b))\n    ++((a<RED>^<RESET><GREEN>^=<RESET>b))\n    ++((a<RED>=<RESET><GREEN>==<RESET>b))\n    ++((a<RED>!<RESET><GREEN>!=<RESET>b))\n    ++((a<RED><<RESET><GREEN><=<RESET>b))\n    ++((a<RED>><RESET><GREEN>>=<RESET>b))\n    ++$((a<RED><<RESET><GREEN><<<RESET>b))\n    ++$((a<RED>><RESET><GREEN>>><RESET>b))\n    ++$((a<RED>&<RESET><GREEN>&&<RESET>b))\n    ++$((a<RED>|<RESET><GREEN>||<RESET>b))\n    ++${a<RED>:<RESET><GREEN>:-<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:=<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:+<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:?<RESET>b}\n    ++${a<RED>#<RESET><GREEN>##<RESET>*/}\n    ++${a<RED>%<RESET><GREEN>%%<RESET>.*}\n    ++${a<RED>^<RESET><GREEN>^^<RESET>}\n    ++${a<RED>,<RESET><GREEN>,,<RESET>}\n    ++${<GREEN>!<RESET>a}\n    ++${a[<RED>*<RESET><GREEN>@<RESET>]}\n    ++${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n     +ls <RED>-a<RESET><GREEN>-x<RESET>\n     +ls <RED>--a<RESET><GREEN>--x<RESET>\n     \n    @@ t/t4034/bash/post (new)\n     +z=.75\n     +echo $USERNAME\n     +${HOMEDIR}\n    -+if [ \"$x\" == \"$y\" ] || [ \"$x\" != \"$y\" ]; then echo \"OK\"; fi\n    -+((x+=y))\n    -+((x-=y))\n    -+$((x<<y))\n    -+$((x>>y))\n    -+${x:-y}\n    -+${x:=y}\n    -+${x##*/}\n    -+${x%.*}\n    -+${x%%.*}\n    -+${x^^}\n    -+${x,}\n    -+${x,,}\n    -+${!x}\n    -+${x[@]}\n    -+${x:?error message}\n    -+${x:2:3}\n    ++((a+=b))\n    ++((a*=b))\n    ++((a/=b))\n    ++((a%=b))\n    ++((a|=b))\n    ++((a^=b))\n    ++((a==b))\n    ++((a!=b))\n    ++((a<=b))\n    ++((a>=b))\n    ++$((a<<b))\n    ++$((a>>b))\n    ++$((a&&b))\n    ++$((a||b))\n    ++${a:-b}\n    ++${a:=b}\n    ++${a:+b}\n    ++${a:?b}\n    ++${a##*/}\n    ++${a%%.*}\n    ++${a^^}\n    ++${a,,}\n    ++${!a}\n    ++${a[@]}\n    ++${a:4:6}\n     +ls -x\n     +ls --x\n     \n    @@ t/t4034/bash/pre (new)\n     +z=.5\n     +echo $USER\n     +${HOME}\n    -+if [ \"$a\" == \"$b\" ] || [ \"$c\" != \"$d\" ]; then echo \"OK\"; fi\n    -+((a+=b))\n    -+((a-=b))\n    -+$((a << b))\n    -+$((a >> b))\n    -+${a:-b}\n    -+${a:=b}\n    -+${a##*/}\n    ++((a+b))\n    ++((a*b))\n    ++((a/b))\n    ++((a%b))\n    ++((a|b))\n    ++((a^b))\n    ++((a=b))\n    ++((a!b))\n    ++((a<b))\n    ++((a>b))\n    ++$((a<b))\n    ++$((a>b))\n    ++$((a&b))\n    ++$((a|b))\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a#*/}\n     +${a%.*}\n    -+${a%%.*}\n    -+${a^^}\n    ++${a^}\n     +${a,}\n    -+${a,,}\n    -+${!a}\n    -+${a[@]}\n    -+${a:?error message}\n    ++${a}\n    ++${a[*]}\n     +${a:2:3}\n     +ls -a\n     +ls --a\n     \n      ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"bash\",\n    + \t \"(\"\n    + \t \"(\"\n    + \t     /* POSIX identifier with mandatory parentheses */\n    +-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    ++\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    + \t \"|\"\n      \t     /* Bashism identifier with optional parentheses */\n    - \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    +-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    ++\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n      \t \")\"\n     -\t /* Optional whitespace */\n     -\t \"[ \\t]*\"\n    @@ userdiff.c: PATTERNS(\"bash\",\n     +\t /* Assignment and arithmetic operators */\n     +\t \"|[-+*/%&|^!=<>]=?\"\n     +\t /* Additional parameter expansion operators */\n    -+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n    ++\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n     +\t /* Command-line options (to avoid splitting -option) */\n     +\t \"|--?[a-zA-Z0-9_-]+\"\n     +\t /* Brackets and grouping symbols */\n-- \n2.48.0\n\n"},{"id":"517778","messageId":"20250511132802.16338-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250511132802.16338-1-dhar61595@gmail.com","subject":"","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-11T13:28:02Z","receivedAt":"2025-05-11T13:29:07Z","isPatch":false,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"From: Moumita Dhar <dhar61595@gmail.com>\n\nSubject: [PATCH v6 1/1] userdiff: extend Bash pattern to cover more shell\n function forms\n\nThe previous function regex required explicit matching of function\nbodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n\n- It failed to capture valid functions where `{` was on the next line\n  due to line continuation (`\\`).\n- It did not recognize functions with single  command body, such as\n  `x () echo hello`.\n\nReplacing the function body matching logic with `.*$`, ensures\nthat everything on the function definition line is captured.\n\nAdditionally, the word regex is refined to better recognize shell\nsyntax, including additional parameter expansion operators and\ncommand-line options.\n\nSigned-off-by: Moumita Dhar <dhar61595@gmail.com>\n---\n t/t4018/bash-bashism-style-multiline-function |  4 ++\n .../bash-hunk-header-complete-line-capture    |  4 ++\n t/t4018/bash-posix-style-multiline-function   |  4 ++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 38 +++++++++++++++++++\n t/t4034/bash/post                             | 33 ++++++++++++++++\n t/t4034/bash/pre                              | 33 ++++++++++++++++\n userdiff.c                                    | 28 ++++++++++----\n 9 files changed, 140 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-hunk-header-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\ndiff --git a/t/t4018/bash-bashism-style-multiline-function b/t/t4018/bash-bashism-style-multiline-function\nnew file mode 100644\nindex 0000000000..284d50dd99\n--- /dev/null\n+++ b/t/t4018/bash-bashism-style-multiline-function\n@@ -0,0 +1,4 @@\n+function RIGHT \\\n+{    \n+    echo 'ChangeMe'\n+}\ndiff --git a/t/t4018/bash-hunk-header-complete-line-capture b/t/t4018/bash-hunk-header-complete-line-capture\nnew file mode 100644\nindex 0000000000..b56942f322\n--- /dev/null\n+++ b/t/t4018/bash-hunk-header-complete-line-capture\n@@ -0,0 +1,4 @@\n+func() { # RIGHT\n+\n+    ChangeMe\n+}\ndiff --git a/t/t4018/bash-posix-style-multiline-function b/t/t4018/bash-posix-style-multiline-function\nnew file mode 100644\nindex 0000000000..cc8727cbcd\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-multiline-function\n@@ -0,0 +1,4 @@\n+RIGHT() \\\n+{\n+    ChangeMe\n+}\ndiff --git a/t/t4018/bash-posix-style-single-command-function b/t/t4018/bash-posix-style-single-command-function\nnew file mode 100644\nindex 0000000000..398ae1c5d2\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-single-command-function\n@@ -0,0 +1,3 @@\n+RIGHT() echo \"hello\"\n+\n+    ChangeMe\ndiff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\nindex f51d3557f1..0be647c2fb 100755\n--- a/t/t4034-diff-words.sh\n+++ b/t/t4034-diff-words.sh\n@@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n \n test_language_driver ada\n test_language_driver bibtex\n+test_language_driver bash\n test_language_driver cpp\n test_language_driver csharp\n test_language_driver css\ndiff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\nnew file mode 100644\nindex 0000000000..17755e455f\n--- /dev/null\n+++ b/t/t4034/bash/expect\n@@ -0,0 +1,38 @@\n+<BOLD>diff --git a/pre b/post<RESET>\n+<BOLD>index 09ac008..60ba6a2 100644<RESET>\n+<BOLD>--- a/pre<RESET>\n+<BOLD>+++ b/post<RESET>\n+<CYAN>@@ -1,33 +1,33 @@<RESET>\n+<RED>my_var<RESET><GREEN>new_var<RESET>=10\n+x=<RED>123<RESET><GREEN>456<RESET>\n+y=<RED>3.14<RESET><GREEN>2.71<RESET>\n+z=<RED>.5<RESET><GREEN>.75<RESET>\n+echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n+${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n+((a<RED>+<RESET><GREEN>+=<RESET>b))\n+((a<RED>*<RESET><GREEN>*=<RESET>b))\n+((a<RED>/<RESET><GREEN>/=<RESET>b))\n+((a<RED>%<RESET><GREEN>%=<RESET>b))\n+((a<RED>|<RESET><GREEN>|=<RESET>b))\n+((a<RED>^<RESET><GREEN>^=<RESET>b))\n+((a<RED>=<RESET><GREEN>==<RESET>b))\n+((a<RED>!<RESET><GREEN>!=<RESET>b))\n+((a<RED><<RESET><GREEN><=<RESET>b))\n+((a<RED>><RESET><GREEN>>=<RESET>b))\n+$((a<RED><<RESET><GREEN><<<RESET>b))\n+$((a<RED>><RESET><GREEN>>><RESET>b))\n+$((a<RED>&<RESET><GREEN>&&<RESET>b))\n+$((a<RED>|<RESET><GREEN>||<RESET>b))\n+${a<RED>:<RESET><GREEN>:-<RESET>b}\n+${a<RED>:<RESET><GREEN>:=<RESET>b}\n+${a<RED>:<RESET><GREEN>:+<RESET>b}\n+${a<RED>:<RESET><GREEN>:?<RESET>b}\n+${a<RED>#<RESET><GREEN>##<RESET>*/}\n+${a<RED>%<RESET><GREEN>%%<RESET>.*}\n+${a<RED>^<RESET><GREEN>^^<RESET>}\n+${a<RED>,<RESET><GREEN>,,<RESET>}\n+${<GREEN>!<RESET>a}\n+${a[<RED>*<RESET><GREEN>@<RESET>]}\n+${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n+ls <RED>-a<RESET><GREEN>-x<RESET>\n+ls <RED>--a<RESET><GREEN>--x<RESET>\ndiff --git a/t/t4034/bash/post b/t/t4034/bash/post\nnew file mode 100644\nindex 0000000000..669e218c30\n--- /dev/null\n+++ b/t/t4034/bash/post\n@@ -0,0 +1,33 @@\n+new_var=10\n+x=456\n+y=2.71\n+z=.75\n+echo $USERNAME\n+${HOMEDIR}\n+((a+=b))\n+((a*=b))\n+((a/=b))\n+((a%=b))\n+((a|=b))\n+((a^=b))\n+((a==b))\n+((a!=b))\n+((a<=b))\n+((a>=b))\n+$((a<<b))\n+$((a>>b))\n+$((a&&b))\n+$((a||b))\n+${a:-b}\n+${a:=b}\n+${a:+b}\n+${a:?b}\n+${a##*/}\n+${a%%.*}\n+${a^^}\n+${a,,}\n+${!a}\n+${a[@]}\n+${a:4:6}\n+ls -x\n+ls --x\ndiff --git a/t/t4034/bash/pre b/t/t4034/bash/pre\nnew file mode 100644\nindex 0000000000..ada8470bac\n--- /dev/null\n+++ b/t/t4034/bash/pre\n@@ -0,0 +1,33 @@\n+my_var=10\n+x=123\n+y=3.14\n+z=.5\n+echo $USER\n+${HOME}\n+((a+b))\n+((a*b))\n+((a/b))\n+((a%b))\n+((a|b))\n+((a^b))\n+((a=b))\n+((a!b))\n+((a<b))\n+((a>b))\n+$((a<b))\n+$((a>b))\n+$((a&b))\n+$((a|b))\n+${a:b}\n+${a:b}\n+${a:b}\n+${a:b}\n+${a#*/}\n+${a%.*}\n+${a^}\n+${a,}\n+${a}\n+${a[*]}\n+${a:2:3}\n+ls -a\n+ls --a\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..655c8fe0b1 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -59,20 +59,32 @@ PATTERNS(\"bash\",\n \t \"(\"\n \t \"(\"\n \t     /* POSIX identifier with mandatory parentheses */\n-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n+\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n \t \"|\"\n \t     /* Bashism identifier with optional parentheses */\n-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n+\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n \t \")\"\n-\t /* Optional whitespace */\n-\t \"[ \\t]*\"\n-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n+\t /* Everything after the function header is captured  */\n+\t \".*$\"\n \t /* End of captured text */\n \t \")\",\n \t /* -- */\n-\t /* Characters not in the default $IFS value */\n-\t \"[^ \\t]+\"),\n+\t /* Identifiers: variable and function names */\n+\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Numeric constants: integers and decimals */\n+\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n+\t /* Shell variables: $VAR, ${VAR} */\n+\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n+\t  /* Logical and comparison operators */\n+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n+\t /* Assignment and arithmetic operators */\n+\t \"|[-+*/%&|^!=<>]=?\"\n+\t /* Additional parameter expansion operators */\n+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n+\t /* Command-line options (to avoid splitting -option) */\n+\t \"|--?[a-zA-Z0-9_-]+\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"bibtex\",\n \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n \t /* -- */\n-- \n2.48.0\n\n"},{"id":"517779","messageId":"20250511133736.16760-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250511125809.14180-1-dhar61595@gmail.com","subject":"","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-11T13:37:35Z","receivedAt":"2025-05-11T13:38:44Z","isPatch":false,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"Subject: [PATCH v6 0/1] Added the newline after the test in t/4018\n\nSorry , I forgot to add the newline after the test in t/4018 again so I had send the patch agaain.\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n t/t4018/bash-bashism-style-multiline-function |  4 ++\n .../bash-hunk-header-complete-line-capture    |  4 ++\n t/t4018/bash-posix-style-multiline-function   |  4 ++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 38 +++++++++++++++++++\n t/t4034/bash/post                             | 33 ++++++++++++++++\n t/t4034/bash/pre                              | 33 ++++++++++++++++\n userdiff.c                                    | 28 ++++++++++----\n 9 files changed, 140 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-hunk-header-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\nRange-diff against v5:\n1:  40cffd3b4a ! 1:  464cb8a1eb userdiff: extend Bash pattern to cover more shell function forms\n    @@ Commit message\n           `x () echo hello`.\n     \n         Replacing the function body matching logic with `.*$`, ensures\n    -    that everything on the function definition line is captured,\n    -    aligning with other userdiff drivers and improving hunk headers in\n    -    `git diff`.\n    +    that everything on the function definition line is captured.\n     \n         Additionally, the word regex is refined to better recognize shell\n         syntax, including additional parameter expansion operators and\n    -    command-line options, improving syntax-aware diffs.\n    +    command-line options.\n     \n         Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n     \n    @@ t/t4018/bash-bashism-style-multiline-function (new)\n     +function RIGHT \\\n     +{    \n     +    echo 'ChangeMe'\n    ++}\n    +\n    + ## t/t4018/bash-hunk-header-complete-line-capture (new) ##\n    +@@\n    ++func() { # RIGHT\n    ++\n    ++    ChangeMe\n     +}\n     \n      ## t/t4018/bash-posix-style-multiline-function (new) ##\n    @@ t/t4034/bash/expect (new)\n     +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n     +<BOLD>--- a/pre<RESET>\n     +<BOLD>+++ b/post<RESET>\n    -+<CYAN>@@ -1,25 +1,25 @@<RESET>\n    ++<CYAN>@@ -1,33 +1,33 @@<RESET>\n     +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n     +x=<RED>123<RESET><GREEN>456<RESET>\n     +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n     +z=<RED>.5<RESET><GREEN>.75<RESET>\n     +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n     +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n    -+if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n    -+((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n    -+((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n    -+${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>##*/}\n    -+${<RED>a<RESET><GREEN>x<RESET>%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>%%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>^^}\n    -+${<RED>a<RESET><GREEN>x<RESET>,}\n    -+${<RED>a<RESET><GREEN>x<RESET>,,}\n    -+${!<RED>a<RESET><GREEN>x<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>[@]}\n    -+${<RED>a<RESET><GREEN>x<RESET>:?error message}\n    -+${<RED>a<RESET><GREEN>x<RESET>:2:3}\n    ++((a<RED>+<RESET><GREEN>+=<RESET>b))\n    ++((a<RED>*<RESET><GREEN>*=<RESET>b))\n    ++((a<RED>/<RESET><GREEN>/=<RESET>b))\n    ++((a<RED>%<RESET><GREEN>%=<RESET>b))\n    ++((a<RED>|<RESET><GREEN>|=<RESET>b))\n    ++((a<RED>^<RESET><GREEN>^=<RESET>b))\n    ++((a<RED>=<RESET><GREEN>==<RESET>b))\n    ++((a<RED>!<RESET><GREEN>!=<RESET>b))\n    ++((a<RED><<RESET><GREEN><=<RESET>b))\n    ++((a<RED>><RESET><GREEN>>=<RESET>b))\n    ++$((a<RED><<RESET><GREEN><<<RESET>b))\n    ++$((a<RED>><RESET><GREEN>>><RESET>b))\n    ++$((a<RED>&<RESET><GREEN>&&<RESET>b))\n    ++$((a<RED>|<RESET><GREEN>||<RESET>b))\n    ++${a<RED>:<RESET><GREEN>:-<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:=<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:+<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:?<RESET>b}\n    ++${a<RED>#<RESET><GREEN>##<RESET>*/}\n    ++${a<RED>%<RESET><GREEN>%%<RESET>.*}\n    ++${a<RED>^<RESET><GREEN>^^<RESET>}\n    ++${a<RED>,<RESET><GREEN>,,<RESET>}\n    ++${<GREEN>!<RESET>a}\n    ++${a[<RED>*<RESET><GREEN>@<RESET>]}\n    ++${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n     +ls <RED>-a<RESET><GREEN>-x<RESET>\n     +ls <RED>--a<RESET><GREEN>--x<RESET>\n     \n    @@ t/t4034/bash/post (new)\n     +z=.75\n     +echo $USERNAME\n     +${HOMEDIR}\n    -+if [ \"$x\" == \"$y\" ] || [ \"$x\" != \"$y\" ]; then echo \"OK\"; fi\n    -+((x+=y))\n    -+((x-=y))\n    -+$((x<<y))\n    -+$((x>>y))\n    -+${x:-y}\n    -+${x:=y}\n    -+${x##*/}\n    -+${x%.*}\n    -+${x%%.*}\n    -+${x^^}\n    -+${x,}\n    -+${x,,}\n    -+${!x}\n    -+${x[@]}\n    -+${x:?error message}\n    -+${x:2:3}\n    ++((a+=b))\n    ++((a*=b))\n    ++((a/=b))\n    ++((a%=b))\n    ++((a|=b))\n    ++((a^=b))\n    ++((a==b))\n    ++((a!=b))\n    ++((a<=b))\n    ++((a>=b))\n    ++$((a<<b))\n    ++$((a>>b))\n    ++$((a&&b))\n    ++$((a||b))\n    ++${a:-b}\n    ++${a:=b}\n    ++${a:+b}\n    ++${a:?b}\n    ++${a##*/}\n    ++${a%%.*}\n    ++${a^^}\n    ++${a,,}\n    ++${!a}\n    ++${a[@]}\n    ++${a:4:6}\n     +ls -x\n     +ls --x\n     \n    @@ t/t4034/bash/pre (new)\n     +z=.5\n     +echo $USER\n     +${HOME}\n    -+if [ \"$a\" == \"$b\" ] || [ \"$c\" != \"$d\" ]; then echo \"OK\"; fi\n    -+((a+=b))\n    -+((a-=b))\n    -+$((a << b))\n    -+$((a >> b))\n    -+${a:-b}\n    -+${a:=b}\n    -+${a##*/}\n    ++((a+b))\n    ++((a*b))\n    ++((a/b))\n    ++((a%b))\n    ++((a|b))\n    ++((a^b))\n    ++((a=b))\n    ++((a!b))\n    ++((a<b))\n    ++((a>b))\n    ++$((a<b))\n    ++$((a>b))\n    ++$((a&b))\n    ++$((a|b))\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a#*/}\n     +${a%.*}\n    -+${a%%.*}\n    -+${a^^}\n    ++${a^}\n     +${a,}\n    -+${a,,}\n    -+${!a}\n    -+${a[@]}\n    -+${a:?error message}\n    ++${a}\n    ++${a[*]}\n     +${a:2:3}\n     +ls -a\n     +ls --a\n     \n      ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"bash\",\n    + \t \"(\"\n    + \t \"(\"\n    + \t     /* POSIX identifier with mandatory parentheses */\n    +-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    ++\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    + \t \"|\"\n      \t     /* Bashism identifier with optional parentheses */\n    - \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    +-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    ++\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n      \t \")\"\n     -\t /* Optional whitespace */\n     -\t \"[ \\t]*\"\n    @@ userdiff.c: PATTERNS(\"bash\",\n     +\t /* Assignment and arithmetic operators */\n     +\t \"|[-+*/%&|^!=<>]=?\"\n     +\t /* Additional parameter expansion operators */\n    -+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n    ++\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n     +\t /* Command-line options (to avoid splitting -option) */\n     +\t \"|--?[a-zA-Z0-9_-]+\"\n     +\t /* Brackets and grouping symbols */\n-- \n2.48.0\n\n"},{"id":"517784","messageId":"20250511141101.18450-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250511125809.14180-1-dhar61595@gmail.com","subject":"[PATCH v6 0/1] Added the newline after the test in t/4018","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-11T14:11:00Z","receivedAt":"2025-05-11T14:12:53Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"Sorry I forgot to add the newline after the test in t/4018 again so I had to send the patch again.\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n t/t4018/bash-bashism-style-multiline-function |  4 ++\n .../bash-hunk-header-complete-line-capture    |  4 ++\n t/t4018/bash-posix-style-multiline-function   |  4 ++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 38 +++++++++++++++++++\n t/t4034/bash/post                             | 33 ++++++++++++++++\n t/t4034/bash/pre                              | 33 ++++++++++++++++\n userdiff.c                                    | 28 ++++++++++----\n 9 files changed, 140 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-hunk-header-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\nRange-diff against v5:\n1:  40cffd3b4a ! 1:  464cb8a1eb userdiff: extend Bash pattern to cover more shell function forms\n    @@ Commit message\n           `x () echo hello`.\n     \n         Replacing the function body matching logic with `.*$`, ensures\n    -    that everything on the function definition line is captured,\n    -    aligning with other userdiff drivers and improving hunk headers in\n    -    `git diff`.\n    +    that everything on the function definition line is captured.\n     \n         Additionally, the word regex is refined to better recognize shell\n         syntax, including additional parameter expansion operators and\n    -    command-line options, improving syntax-aware diffs.\n    +    command-line options.\n     \n         Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n     \n    @@ t/t4018/bash-bashism-style-multiline-function (new)\n     +function RIGHT \\\n     +{    \n     +    echo 'ChangeMe'\n    ++}\n    +\n    + ## t/t4018/bash-hunk-header-complete-line-capture (new) ##\n    +@@\n    ++func() { # RIGHT\n    ++\n    ++    ChangeMe\n     +}\n     \n      ## t/t4018/bash-posix-style-multiline-function (new) ##\n    @@ t/t4034/bash/expect (new)\n     +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n     +<BOLD>--- a/pre<RESET>\n     +<BOLD>+++ b/post<RESET>\n    -+<CYAN>@@ -1,25 +1,25 @@<RESET>\n    ++<CYAN>@@ -1,33 +1,33 @@<RESET>\n     +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n     +x=<RED>123<RESET><GREEN>456<RESET>\n     +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n     +z=<RED>.5<RESET><GREEN>.75<RESET>\n     +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n     +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n    -+if [ \"<RED>$a<RESET><GREEN>$x<RESET>\" == \"<RED>$b<RESET><GREEN>$y<RESET>\" ] || [ \"<RED>$c<RESET><GREEN>$x<RESET>\" != \"<RED>$d<RESET><GREEN>$y<RESET>\" ]; then echo \"OK\"; fi\n    -+((<RED>a<RESET><GREEN>x<RESET>+=<RED>b<RESET><GREEN>y<RESET>))\n    -+((<RED>a<RESET><GREEN>x<RESET>-=<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET><<<RED>b<RESET><GREEN>y<RESET>))\n    -+$((<RED>a<RESET><GREEN>x<RESET>>><RED>b<RESET><GREEN>y<RESET>))\n    -+${<RED>a<RESET><GREEN>x<RESET>:-<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>:=<RED>b<RESET><GREEN>y<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>##*/}\n    -+${<RED>a<RESET><GREEN>x<RESET>%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>%%.*}\n    -+${<RED>a<RESET><GREEN>x<RESET>^^}\n    -+${<RED>a<RESET><GREEN>x<RESET>,}\n    -+${<RED>a<RESET><GREEN>x<RESET>,,}\n    -+${!<RED>a<RESET><GREEN>x<RESET>}\n    -+${<RED>a<RESET><GREEN>x<RESET>[@]}\n    -+${<RED>a<RESET><GREEN>x<RESET>:?error message}\n    -+${<RED>a<RESET><GREEN>x<RESET>:2:3}\n    ++((a<RED>+<RESET><GREEN>+=<RESET>b))\n    ++((a<RED>*<RESET><GREEN>*=<RESET>b))\n    ++((a<RED>/<RESET><GREEN>/=<RESET>b))\n    ++((a<RED>%<RESET><GREEN>%=<RESET>b))\n    ++((a<RED>|<RESET><GREEN>|=<RESET>b))\n    ++((a<RED>^<RESET><GREEN>^=<RESET>b))\n    ++((a<RED>=<RESET><GREEN>==<RESET>b))\n    ++((a<RED>!<RESET><GREEN>!=<RESET>b))\n    ++((a<RED><<RESET><GREEN><=<RESET>b))\n    ++((a<RED>><RESET><GREEN>>=<RESET>b))\n    ++$((a<RED><<RESET><GREEN><<<RESET>b))\n    ++$((a<RED>><RESET><GREEN>>><RESET>b))\n    ++$((a<RED>&<RESET><GREEN>&&<RESET>b))\n    ++$((a<RED>|<RESET><GREEN>||<RESET>b))\n    ++${a<RED>:<RESET><GREEN>:-<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:=<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:+<RESET>b}\n    ++${a<RED>:<RESET><GREEN>:?<RESET>b}\n    ++${a<RED>#<RESET><GREEN>##<RESET>*/}\n    ++${a<RED>%<RESET><GREEN>%%<RESET>.*}\n    ++${a<RED>^<RESET><GREEN>^^<RESET>}\n    ++${a<RED>,<RESET><GREEN>,,<RESET>}\n    ++${<GREEN>!<RESET>a}\n    ++${a[<RED>*<RESET><GREEN>@<RESET>]}\n    ++${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n     +ls <RED>-a<RESET><GREEN>-x<RESET>\n     +ls <RED>--a<RESET><GREEN>--x<RESET>\n     \n    @@ t/t4034/bash/post (new)\n     +z=.75\n     +echo $USERNAME\n     +${HOMEDIR}\n    -+if [ \"$x\" == \"$y\" ] || [ \"$x\" != \"$y\" ]; then echo \"OK\"; fi\n    -+((x+=y))\n    -+((x-=y))\n    -+$((x<<y))\n    -+$((x>>y))\n    -+${x:-y}\n    -+${x:=y}\n    -+${x##*/}\n    -+${x%.*}\n    -+${x%%.*}\n    -+${x^^}\n    -+${x,}\n    -+${x,,}\n    -+${!x}\n    -+${x[@]}\n    -+${x:?error message}\n    -+${x:2:3}\n    ++((a+=b))\n    ++((a*=b))\n    ++((a/=b))\n    ++((a%=b))\n    ++((a|=b))\n    ++((a^=b))\n    ++((a==b))\n    ++((a!=b))\n    ++((a<=b))\n    ++((a>=b))\n    ++$((a<<b))\n    ++$((a>>b))\n    ++$((a&&b))\n    ++$((a||b))\n    ++${a:-b}\n    ++${a:=b}\n    ++${a:+b}\n    ++${a:?b}\n    ++${a##*/}\n    ++${a%%.*}\n    ++${a^^}\n    ++${a,,}\n    ++${!a}\n    ++${a[@]}\n    ++${a:4:6}\n     +ls -x\n     +ls --x\n     \n    @@ t/t4034/bash/pre (new)\n     +z=.5\n     +echo $USER\n     +${HOME}\n    -+if [ \"$a\" == \"$b\" ] || [ \"$c\" != \"$d\" ]; then echo \"OK\"; fi\n    -+((a+=b))\n    -+((a-=b))\n    -+$((a << b))\n    -+$((a >> b))\n    -+${a:-b}\n    -+${a:=b}\n    -+${a##*/}\n    ++((a+b))\n    ++((a*b))\n    ++((a/b))\n    ++((a%b))\n    ++((a|b))\n    ++((a^b))\n    ++((a=b))\n    ++((a!b))\n    ++((a<b))\n    ++((a>b))\n    ++$((a<b))\n    ++$((a>b))\n    ++$((a&b))\n    ++$((a|b))\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a:b}\n    ++${a#*/}\n     +${a%.*}\n    -+${a%%.*}\n    -+${a^^}\n    ++${a^}\n     +${a,}\n    -+${a,,}\n    -+${!a}\n    -+${a[@]}\n    -+${a:?error message}\n    ++${a}\n    ++${a[*]}\n     +${a:2:3}\n     +ls -a\n     +ls --a\n     \n      ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"bash\",\n    + \t \"(\"\n    + \t \"(\"\n    + \t     /* POSIX identifier with mandatory parentheses */\n    +-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    ++\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n    + \t \"|\"\n      \t     /* Bashism identifier with optional parentheses */\n    - \t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    +-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n    ++\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n      \t \")\"\n     -\t /* Optional whitespace */\n     -\t \"[ \\t]*\"\n    @@ userdiff.c: PATTERNS(\"bash\",\n     +\t /* Assignment and arithmetic operators */\n     +\t \"|[-+*/%&|^!=<>]=?\"\n     +\t /* Additional parameter expansion operators */\n    -+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|/[a-zA-Z0-9_-]+|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n    ++\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n     +\t /* Command-line options (to avoid splitting -option) */\n     +\t \"|--?[a-zA-Z0-9_-]+\"\n     +\t /* Brackets and grouping symbols */\n-- \n2.48.0\n\n"},{"id":"517785","messageId":"20250511141101.18450-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250511141101.18450-1-dhar61595@gmail.com","subject":"[PATCH v6 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-11T14:11:01Z","receivedAt":"2025-05-11T14:13:11Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"From: Moumita Dhar <dhar61595@gmail.com>\n\nThe previous function regex required explicit matching of function\nbodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n\n- It failed to capture valid functions where `{` was on the next line\n  due to line continuation (`\\`).\n- It did not recognize functions with single  command body, such as\n  `x () echo hello`.\n\nReplacing the function body matching logic with `.*$`, ensures\nthat everything on the function definition line is captured.\n\nAdditionally, the word regex is refined to better recognize shell\nsyntax, including additional parameter expansion operators and\ncommand-line options.\n\nSigned-off-by: Moumita Dhar <dhar61595@gmail.com>\n---\n t/t4018/bash-bashism-style-multiline-function |  4 ++\n .../bash-hunk-header-complete-line-capture    |  4 ++\n t/t4018/bash-posix-style-multiline-function   |  4 ++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 38 +++++++++++++++++++\n t/t4034/bash/post                             | 33 ++++++++++++++++\n t/t4034/bash/pre                              | 33 ++++++++++++++++\n userdiff.c                                    | 28 ++++++++++----\n 9 files changed, 140 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-multiline-function\n create mode 100644 t/t4018/bash-hunk-header-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-multiline-function\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\ndiff --git a/t/t4018/bash-bashism-style-multiline-function b/t/t4018/bash-bashism-style-multiline-function\nnew file mode 100644\nindex 0000000000..284d50dd99\n--- /dev/null\n+++ b/t/t4018/bash-bashism-style-multiline-function\n@@ -0,0 +1,4 @@\n+function RIGHT \\\n+{    \n+    echo 'ChangeMe'\n+}\ndiff --git a/t/t4018/bash-hunk-header-complete-line-capture b/t/t4018/bash-hunk-header-complete-line-capture\nnew file mode 100644\nindex 0000000000..b56942f322\n--- /dev/null\n+++ b/t/t4018/bash-hunk-header-complete-line-capture\n@@ -0,0 +1,4 @@\n+func() { # RIGHT\n+\n+    ChangeMe\n+}\ndiff --git a/t/t4018/bash-posix-style-multiline-function b/t/t4018/bash-posix-style-multiline-function\nnew file mode 100644\nindex 0000000000..cc8727cbcd\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-multiline-function\n@@ -0,0 +1,4 @@\n+RIGHT() \\\n+{\n+    ChangeMe\n+}\ndiff --git a/t/t4018/bash-posix-style-single-command-function b/t/t4018/bash-posix-style-single-command-function\nnew file mode 100644\nindex 0000000000..398ae1c5d2\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-single-command-function\n@@ -0,0 +1,3 @@\n+RIGHT() echo \"hello\"\n+\n+    ChangeMe\ndiff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\nindex f51d3557f1..0be647c2fb 100755\n--- a/t/t4034-diff-words.sh\n+++ b/t/t4034-diff-words.sh\n@@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n \n test_language_driver ada\n test_language_driver bibtex\n+test_language_driver bash\n test_language_driver cpp\n test_language_driver csharp\n test_language_driver css\ndiff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\nnew file mode 100644\nindex 0000000000..17755e455f\n--- /dev/null\n+++ b/t/t4034/bash/expect\n@@ -0,0 +1,38 @@\n+<BOLD>diff --git a/pre b/post<RESET>\n+<BOLD>index 09ac008..60ba6a2 100644<RESET>\n+<BOLD>--- a/pre<RESET>\n+<BOLD>+++ b/post<RESET>\n+<CYAN>@@ -1,33 +1,33 @@<RESET>\n+<RED>my_var<RESET><GREEN>new_var<RESET>=10\n+x=<RED>123<RESET><GREEN>456<RESET>\n+y=<RED>3.14<RESET><GREEN>2.71<RESET>\n+z=<RED>.5<RESET><GREEN>.75<RESET>\n+echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n+${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n+((a<RED>+<RESET><GREEN>+=<RESET>b))\n+((a<RED>*<RESET><GREEN>*=<RESET>b))\n+((a<RED>/<RESET><GREEN>/=<RESET>b))\n+((a<RED>%<RESET><GREEN>%=<RESET>b))\n+((a<RED>|<RESET><GREEN>|=<RESET>b))\n+((a<RED>^<RESET><GREEN>^=<RESET>b))\n+((a<RED>=<RESET><GREEN>==<RESET>b))\n+((a<RED>!<RESET><GREEN>!=<RESET>b))\n+((a<RED><<RESET><GREEN><=<RESET>b))\n+((a<RED>><RESET><GREEN>>=<RESET>b))\n+$((a<RED><<RESET><GREEN><<<RESET>b))\n+$((a<RED>><RESET><GREEN>>><RESET>b))\n+$((a<RED>&<RESET><GREEN>&&<RESET>b))\n+$((a<RED>|<RESET><GREEN>||<RESET>b))\n+${a<RED>:<RESET><GREEN>:-<RESET>b}\n+${a<RED>:<RESET><GREEN>:=<RESET>b}\n+${a<RED>:<RESET><GREEN>:+<RESET>b}\n+${a<RED>:<RESET><GREEN>:?<RESET>b}\n+${a<RED>#<RESET><GREEN>##<RESET>*/}\n+${a<RED>%<RESET><GREEN>%%<RESET>.*}\n+${a<RED>^<RESET><GREEN>^^<RESET>}\n+${a<RED>,<RESET><GREEN>,,<RESET>}\n+${<GREEN>!<RESET>a}\n+${a[<RED>*<RESET><GREEN>@<RESET>]}\n+${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n+ls <RED>-a<RESET><GREEN>-x<RESET>\n+ls <RED>--a<RESET><GREEN>--x<RESET>\ndiff --git a/t/t4034/bash/post b/t/t4034/bash/post\nnew file mode 100644\nindex 0000000000..669e218c30\n--- /dev/null\n+++ b/t/t4034/bash/post\n@@ -0,0 +1,33 @@\n+new_var=10\n+x=456\n+y=2.71\n+z=.75\n+echo $USERNAME\n+${HOMEDIR}\n+((a+=b))\n+((a*=b))\n+((a/=b))\n+((a%=b))\n+((a|=b))\n+((a^=b))\n+((a==b))\n+((a!=b))\n+((a<=b))\n+((a>=b))\n+$((a<<b))\n+$((a>>b))\n+$((a&&b))\n+$((a||b))\n+${a:-b}\n+${a:=b}\n+${a:+b}\n+${a:?b}\n+${a##*/}\n+${a%%.*}\n+${a^^}\n+${a,,}\n+${!a}\n+${a[@]}\n+${a:4:6}\n+ls -x\n+ls --x\ndiff --git a/t/t4034/bash/pre b/t/t4034/bash/pre\nnew file mode 100644\nindex 0000000000..ada8470bac\n--- /dev/null\n+++ b/t/t4034/bash/pre\n@@ -0,0 +1,33 @@\n+my_var=10\n+x=123\n+y=3.14\n+z=.5\n+echo $USER\n+${HOME}\n+((a+b))\n+((a*b))\n+((a/b))\n+((a%b))\n+((a|b))\n+((a^b))\n+((a=b))\n+((a!b))\n+((a<b))\n+((a>b))\n+$((a<b))\n+$((a>b))\n+$((a&b))\n+$((a|b))\n+${a:b}\n+${a:b}\n+${a:b}\n+${a:b}\n+${a#*/}\n+${a%.*}\n+${a^}\n+${a,}\n+${a}\n+${a[*]}\n+${a:2:3}\n+ls -a\n+ls --a\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..655c8fe0b1 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -59,20 +59,32 @@ PATTERNS(\"bash\",\n \t \"(\"\n \t \"(\"\n \t     /* POSIX identifier with mandatory parentheses */\n-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n+\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n \t \"|\"\n \t     /* Bashism identifier with optional parentheses */\n-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n+\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n \t \")\"\n-\t /* Optional whitespace */\n-\t \"[ \\t]*\"\n-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n+\t /* Everything after the function header is captured  */\n+\t \".*$\"\n \t /* End of captured text */\n \t \")\",\n \t /* -- */\n-\t /* Characters not in the default $IFS value */\n-\t \"[^ \\t]+\"),\n+\t /* Identifiers: variable and function names */\n+\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Numeric constants: integers and decimals */\n+\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n+\t /* Shell variables: $VAR, ${VAR} */\n+\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n+\t  /* Logical and comparison operators */\n+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n+\t /* Assignment and arithmetic operators */\n+\t \"|[-+*/%&|^!=<>]=?\"\n+\t /* Additional parameter expansion operators */\n+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n+\t /* Command-line options (to avoid splitting -option) */\n+\t \"|--?[a-zA-Z0-9_-]+\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"bibtex\",\n \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n \t /* -- */\n-- \n2.48.0\n\n"},{"id":"517968","messageId":"xmqqv7q4fipn.fsf@gitster.g","threadId":"62935","inReplyTo":"20250511141101.18450-2-dhar61595@gmail.com","subject":"Re: [PATCH v6 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-13T18:50:44Z","receivedAt":"2025-05-13T18:50:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Moumita <dhar61595@gmail.com> writes:\n\n> diff --git a/t/t4018/bash-posix-style-multiline-function b/t/t4018/bash-posix-style-multiline-function\n> new file mode 100644\n> index 0000000000..cc8727cbcd\n> --- /dev/null\n> +++ b/t/t4018/bash-posix-style-multiline-function\n> @@ -0,0 +1,4 @@\n> +RIGHT() \\\n> +{\n> +    ChangeMe\n> +}\n\nNot a review, but I am curious what this test is about.  Is it to\nensure that the pattern does not get confused with the backslash\nthat does not have to be (but it would not hurt to have one) there?\n\nIOW, does\n\n\tRIGHT()\n\t{\n\t\tChangeMe\n\t}\n\nget processed just fine, and the above is to check the corner case\nwhere an unusual \"\\\" on the same line as RIGHT does not break the\nfuncline identification?\n\nThanks.\n"},{"id":"518018","messageId":"CAF=ncLaYAXF-bFjMaxa39oVFNNmk=-gUs0Abdf-dxSAEiD7Ltw@mail.gmail.com","threadId":"62935","inReplyTo":"xmqqv7q4fipn.fsf@gitster.g","subject":"Re: [PATCH v6 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"MOUMITA DHAR","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-14T06:33:51Z","receivedAt":"2025-05-14T06:43:23Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"On Wed, 14 May 2025 at 00:20, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Moumita <dhar61595@gmail.com> writes:\n>\n> > diff --git a/t/t4018/bash-posix-style-multiline-function b/t/t4018/bash-posix-style-multiline-function\n> > new file mode 100644\n> > index 0000000000..cc8727cbcd\n> > --- /dev/null\n> > +++ b/t/t4018/bash-posix-style-multiline-function\n> > @@ -0,0 +1,4 @@\n> > +RIGHT() \\\n> > +{\n> > +    ChangeMe\n> > +}\n>\n> Not a review, but I am curious what this test is about.  Is it to\n> ensure that the pattern does not get confused with the backslash\n> that does not have to be (but it would not hurt to have one) there?\n>\n> IOW, does\n>\n>         RIGHT()\n>         {\n>                 ChangeMe\n>         }\n>\n> get processed just fine, and the above is to check the corner case\n> where an unusual \"\\\" on the same line as RIGHT does not break the\n> funcline identification?\n>\n> Thanks.\n\nI realise this test is redundant , The goal of my test was to ensure\nthat the entire function header line is correctly captured, even when\nthe opening brace is placed on the next line using a backslash.\nHowever, I now realize that the test case Johannes mentioned already\ncovers this behavior. So rather than duplicating that, I think it\nwould be more useful to add a similar test using the alternative,\nBashism-style syntax:-\n\nfunction myfunc # RIGHT\n{\n    echo 'ChangeMe'\n}\n\nThank You\nMoumita\n"},{"id":"518215","messageId":"8343694f-6e05-43c5-862a-63702314760e@kdbg.org","threadId":"62935","inReplyTo":"20250511141101.18450-2-dhar61595@gmail.com","subject":"Re: [PATCH v6 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-05-16T07:25:43Z","receivedAt":"2025-05-16T07:25:59Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 11.05.25 um 16:11 schrieb Moumita:\n> From: Moumita Dhar <dhar61595@gmail.com>\n> \n> The previous function regex required explicit matching of function\n> bodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n> \n> - It failed to capture valid functions where `{` was on the next line\n>   due to line continuation (`\\`).\n> - It did not recognize functions with single  command body, such as\n>   `x () echo hello`.\n> \n> Replacing the function body matching logic with `.*$`, ensures\n> that everything on the function definition line is captured.\n> \n> Additionally, the word regex is refined to better recognize shell\n> syntax, including additional parameter expansion operators and\n> command-line options.\n> \n> Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n> ---\n\nFunction patterns are looking good now. Let's focus on the worddiff\npattern now.\n\n> diff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\n> new file mode 100644\n> index 0000000000..17755e455f\n> --- /dev/null\n> +++ b/t/t4034/bash/expect\n> @@ -0,0 +1,38 @@\n> +<BOLD>diff --git a/pre b/post<RESET>\n> +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n> +<BOLD>--- a/pre<RESET>\n> +<BOLD>+++ b/post<RESET>\n> +<CYAN>@@ -1,33 +1,33 @@<RESET>\n> +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n> +x=<RED>123<RESET><GREEN>456<RESET>\n> +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n> +z=<RED>.5<RESET><GREEN>.75<RESET>\n\nWhen do decimal numbers occur in shell scripts? Wouldn't it be more\noften the case that a fullstop is part of a regular expression or a file\nname or version number that happens to be surrounded by numbers? In that\ncase, we would prefer to capture the digit sequences as separate words.\n\n> +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n> +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n> +((a<RED>+<RESET><GREEN>+=<RESET>b))\n> +((a<RED>*<RESET><GREEN>*=<RESET>b))\n> +((a<RED>/<RESET><GREEN>/=<RESET>b))\n> +((a<RED>%<RESET><GREEN>%=<RESET>b))\n> +((a<RED>|<RESET><GREEN>|=<RESET>b))\n> +((a<RED>^<RESET><GREEN>^=<RESET>b))\n> +((a<RED>=<RESET><GREEN>==<RESET>b))\n> +((a<RED>!<RESET><GREEN>!=<RESET>b))\n> +((a<RED><<RESET><GREEN><=<RESET>b))\n> +((a<RED>><RESET><GREEN>>=<RESET>b))\n> +$((a<RED><<RESET><GREEN><<<RESET>b))\n> +$((a<RED>><RESET><GREEN>>><RESET>b))\n> +$((a<RED>&<RESET><GREEN>&&<RESET>b))\n> +$((a<RED>|<RESET><GREEN>||<RESET>b))\n> +${a<RED>:<RESET><GREEN>:-<RESET>b}\n> +${a<RED>:<RESET><GREEN>:=<RESET>b}\n> +${a<RED>:<RESET><GREEN>:+<RESET>b}\n> +${a<RED>:<RESET><GREEN>:?<RESET>b}\n> +${a<RED>#<RESET><GREEN>##<RESET>*/}\n> +${a<RED>%<RESET><GREEN>%%<RESET>.*}\n> +${a<RED>^<RESET><GREEN>^^<RESET>}\n> +${a<RED>,<RESET><GREEN>,,<RESET>}\n> +${<GREEN>!<RESET>a}\n> +${a[<RED>*<RESET><GREEN>@<RESET>]}\n\nAll good tests!\n\nAll of | || & && would more often than not occur in commands rather than\nexpressions. But the way these operators are tested here is fine, too.\n\n> +${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n\nI am surprised to see :2:3 as a single token. It's bash's substring\nexpansion. It would not be matched anyway in practice because these\nnumbers are often variables or more complicated expressions, I would\nthink, not integer constants.\n\n> +ls <RED>-a<RESET><GREEN>-x<RESET>\n> +ls <RED>--a<RESET><GREEN>--x<RESET>\n\nCan we extend the second of these two to have a multi-letter option?\n\n> diff --git a/userdiff.c b/userdiff.c\n> index 340c4eb4f7..655c8fe0b1 100644\n> --- a/userdiff.c\n> +++ b/userdiff.c\n> @@ -59,20 +59,32 @@ PATTERNS(\"bash\",\n>  \t \"(\"\n>  \t \"(\"\n>  \t     /* POSIX identifier with mandatory parentheses */\n> -\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n> +\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n>  \t \"|\"\n>  \t     /* Bashism identifier with optional parentheses */\n> -\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n> +\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n>  \t \")\"\n> -\t /* Optional whitespace */\n> -\t \"[ \\t]*\"\n> -\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n> -\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n> +\t /* Everything after the function header is captured  */\n> +\t \".*$\"\n>  \t /* End of captured text */\n>  \t \")\",\n>  \t /* -- */\n\nIn the operator patterns that follow, it is not necessary to match\noperators that are just a single punctuation character. They are matched\nautomatically, IIRC (but please correct me if I am wrong). Only\nmulti-character operators should be matched. That means that in all\npatterns where one character is optional due to the '?' in a\ntwo-character-operator, you should drop the '?'.\n\n> -\t /* Characters not in the default $IFS value */\n> -\t \"[^ \\t]+\"),\n> +\t /* Identifiers: variable and function names */\n> +\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n\nOK. But see below.\n\n> +\t /* Numeric constants: integers and decimals */\n> +\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n\nI would recommend not to match decimal numbers and instead have them as\nthree words: integer-fullstop-integer.\n\nGenerally, I advise to not include a sign in the word pattern of a\nnumber, because the sign is usually its own operator. But we do have\nnumeric options, i.e., integers with a leading dash. But see below.\n\n> +\t /* Shell variables: $VAR, ${VAR} */\n> +\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n\nShell variables do not have to begin with a letter. We have $0, $1 etc.\nJust do not require the first character after the $ to be a letter.\n\n> +\t  /* Logical and comparison operators */\n> +\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n\nI'd call || and && \"Command list separators\" in the context of shell\nlanguage.\n\n> +\t /* Assignment and arithmetic operators */\n> +\t \"|[-+*/%&|^!=<>]=?\"\n\nThese would then be only compund assignment operators. These patterns\nalso match <= >= == != again. How about having all those (from this line\nand from the line above) that end in '=' into a single case?\n\n> +\t /* Additional parameter expansion operators */\n> +\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n\nRemove all single-character operators.\n\nAlso, the :digits:digits word is strange. What is your rationale that\nyou included it?\n\n> +\t /* Command-line options (to avoid splitting -option) */\n> +\t \"|--?[a-zA-Z0-9_-]+\"\n\nThis pattern is a beast for the matcher.Would it match the text \"-----\"?\nYes, in more than one way! Would it match \"--\"? Yes! Would it match\n\"-1000\"? Yes!\n\nNone of these are a problem, but actually desired. But it would not need\nthe optional '-' in the second position. The '-' included in the range\nwould match \"--foo\" just as well.\n\nThis patter also matches negative numbers. So, here's the idea: Just\nturn this into\n\n\t\"|[-a-zA-Z0-9_]+\"\n\nAnd we have a pattern that matches identifiers, function names,\nintegers, short options, long options, numeric options a.k.a. negative\nintegers.\n\nIt might match more and I hope that I do not miss a case that we do\n*not* want to match. If you do, let me know. Until then I'd leave it at\nthat and fix it up when we encounter an important case that is\nmishandled by this pattern.\n\n> +\t /* Brackets and grouping symbols */\n> +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n\nOK.\n\n>  PATTERNS(\"bibtex\",\n>  \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n>  \t /* -- */\n\n-- Hannes\n\n"},{"id":"518243","messageId":"20250516144515.49514-1-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250511141101.18450-1-dhar61595@gmail.com","subject":"[PATCH v7 0/1] Updated the word diff regex for Bash scripts","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-16T14:45:11Z","receivedAt":"2025-05-16T14:46:56Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"I cleaned up the parameter expansion regexes to avoid matching single-character operators like `:` or `#` that are already tokenized. \nI also removed the `:[0-9]+(:[0-9]+)?` pattern, which didn’t have a clear use case.\nVariable matching now supports `$1`, `$2`, etc., not just `$foo`, by relaxing \nthe first character requirement after the `$`. Also I removed the '?' after the second \ncharacter of double character operators.\nThere are two test files in t/4018 which test the capturing of the complete line in the hunk header in the cases of both\nposix style functions and bash style functions.\n\n\n\nMoumita Dhar (1):\n  userdiff: extend Bash pattern to cover more shell function forms\n\n .../bash-bashism-style-complete-line-capture  |  4 +++\n .../bash-posix-style-complete-line-capture    |  4 +++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 36 +++++++++++++++++++\n t/t4034/bash/post                             | 31 ++++++++++++++++\n t/t4034/bash/pre                              | 31 ++++++++++++++++\n userdiff.c                                    | 26 +++++++++-----\n 8 files changed, 128 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\nRange-diff against v6:\n1:  464cb8a1eb ! 1:  314ac45fa2 userdiff: extend Bash pattern to cover more shell function forms\n    @@ Commit message\n     \n         Signed-off-by: Moumita Dhar <dhar61595@gmail.com>\n     \n    - ## t/t4018/bash-bashism-style-multiline-function (new) ##\n    + ## t/t4018/bash-bashism-style-complete-line-capture (new) ##\n     @@\n    -+function RIGHT \\\n    -+{    \n    ++function myfunc # RIGHT\n    ++{\n     +    echo 'ChangeMe'\n     +}\n     \n    - ## t/t4018/bash-hunk-header-complete-line-capture (new) ##\n    + ## t/t4018/bash-posix-style-complete-line-capture (new) ##\n     @@\n     +func() { # RIGHT\n     +\n     +    ChangeMe\n    -+}\n    -\n    - ## t/t4018/bash-posix-style-multiline-function (new) ##\n    -@@\n    -+RIGHT() \\\n    -+{\n    -+    ChangeMe\n     +}\n     \n      ## t/t4018/bash-posix-style-single-command-function (new) ##\n    @@ t/t4034/bash/expect (new)\n     +<BOLD>index 09ac008..60ba6a2 100644<RESET>\n     +<BOLD>--- a/pre<RESET>\n     +<BOLD>+++ b/post<RESET>\n    -+<CYAN>@@ -1,33 +1,33 @@<RESET>\n    ++<CYAN>@@ -1,31 +1,31 @@<RESET>\n     +<RED>my_var<RESET><GREEN>new_var<RESET>=10\n     +x=<RED>123<RESET><GREEN>456<RESET>\n    -+y=<RED>3.14<RESET><GREEN>2.71<RESET>\n    -+z=<RED>.5<RESET><GREEN>.75<RESET>\n    ++echo <RED>$1<RESET><GREEN>$2<RESET>\n     +echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n     +${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n     +((a<RED>+<RESET><GREEN>+=<RESET>b))\n    @@ t/t4034/bash/expect (new)\n     +${a<RED>,<RESET><GREEN>,,<RESET>}\n     +${<GREEN>!<RESET>a}\n     +${a[<RED>*<RESET><GREEN>@<RESET>]}\n    -+${a<RED>:2:3<RESET><GREEN>:4:6<RESET>}\n     +ls <RED>-a<RESET><GREEN>-x<RESET>\n    -+ls <RED>--a<RESET><GREEN>--x<RESET>\n    ++ls <RED>--all<RESET><GREEN>--color<RESET>\n     \n      ## t/t4034/bash/post (new) ##\n     @@\n     +new_var=10\n     +x=456\n    -+y=2.71\n    -+z=.75\n    ++echo $2\n     +echo $USERNAME\n     +${HOMEDIR}\n     +((a+=b))\n    @@ t/t4034/bash/post (new)\n     +${a,,}\n     +${!a}\n     +${a[@]}\n    -+${a:4:6}\n     +ls -x\n    -+ls --x\n    ++ls --color\n     \n      ## t/t4034/bash/pre (new) ##\n     @@\n     +my_var=10\n     +x=123\n    -+y=3.14\n    -+z=.5\n    ++echo $1\n     +echo $USER\n     +${HOME}\n     +((a+b))\n    @@ t/t4034/bash/pre (new)\n     +${a,}\n     +${a}\n     +${a[*]}\n    -+${a:2:3}\n     +ls -a\n    -+ls --a\n    ++ls --all\n     \n      ## userdiff.c ##\n     @@ userdiff.c: PATTERNS(\"bash\",\n    @@ userdiff.c: PATTERNS(\"bash\",\n     -\t \"[^ \\t]+\"),\n     +\t /* Identifiers: variable and function names */\n     +\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n    -+\t /* Numeric constants: integers and decimals */\n    -+\t  \"|[0-9]+(\\\\.[0-9]*)?|[-+]?\\\\.[0-9]+\"\n     +\t /* Shell variables: $VAR, ${VAR} */\n    -+\t  \"|\\\\$[a-zA-Z_][a-zA-Z0-9_]*|\\\\$\\\\{\"\n    -+\t  /* Logical and comparison operators */\n    -+\t \"|\\\\|\\\\||&&|<<|>>|==|!=|<=|>=\"\n    -+\t /* Assignment and arithmetic operators */\n    -+\t \"|[-+*/%&|^!=<>]=?\"\n    ++\t  \"|\\\\$[a-zA-Z0-9_]+|\\\\$\\\\{\"\n    ++\t  /*Command list separators and redirection operators  */\n    ++\t \"|\\\\|\\\\||&&|<<|>>\"\n    ++\t /* Operators ending in '=' (comparison + compound assignment) */\n    ++\t \"|==|!=|<=|>=|[-+*/%&|^]=\"\n     +\t /* Additional parameter expansion operators */\n    -+\t \"|:?=|:-|:\\\\+|:\\\\?|:|#|##|%|%%|\\\\^\\\\^?|,|,,?|!|@|:[0-9]+(:[0-9]+)?\"\n    ++\t \"|:=|:-|:\\\\+|:\\\\?|##|%%|\\\\^\\\\^|,,\"\n     +\t /* Command-line options (to avoid splitting -option) */\n    -+\t \"|--?[a-zA-Z0-9_-]+\"\n    ++\t \"|[-a-zA-Z0-9_]+\"\n     +\t /* Brackets and grouping symbols */\n     +\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n      PATTERNS(\"bibtex\",\n-- \n2.48.0\n\n"},{"id":"518244","messageId":"20250516144515.49514-2-dhar61595@gmail.com","threadId":"62935","inReplyTo":"20250516144515.49514-1-dhar61595@gmail.com","subject":"[PATCH v7 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Moumita","fromEmail":"dhar61595@gmail.com","sentAt":"2025-05-16T14:45:12Z","receivedAt":"2025-05-16T14:47:11Z","isPatch":true,"sender":{"key":"dhar61595@gmail.com","avatar":"https://avatars.githubusercontent.com/u/101038146?v=4"},"body":"From: Moumita Dhar <dhar61595@gmail.com>\n\nThe previous function regex required explicit matching of function\nbodies using `{`, `(`, `((`, or `[[`, which caused several issues:\n\n- It failed to capture valid functions where `{` was on the next line\n  due to line continuation (`\\`).\n- It did not recognize functions with single  command body, such as\n  `x () echo hello`.\n\nReplacing the function body matching logic with `.*$`, ensures\nthat everything on the function definition line is captured.\n\nAdditionally, the word regex is refined to better recognize shell\nsyntax, including additional parameter expansion operators and\ncommand-line options.\n\nSigned-off-by: Moumita Dhar <dhar61595@gmail.com>\n---\n .../bash-bashism-style-complete-line-capture  |  4 +++\n .../bash-posix-style-complete-line-capture    |  4 +++\n .../bash-posix-style-single-command-function  |  3 ++\n t/t4034-diff-words.sh                         |  1 +\n t/t4034/bash/expect                           | 36 +++++++++++++++++++\n t/t4034/bash/post                             | 31 ++++++++++++++++\n t/t4034/bash/pre                              | 31 ++++++++++++++++\n userdiff.c                                    | 26 +++++++++-----\n 8 files changed, 128 insertions(+), 8 deletions(-)\n create mode 100644 t/t4018/bash-bashism-style-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-complete-line-capture\n create mode 100644 t/t4018/bash-posix-style-single-command-function\n create mode 100644 t/t4034/bash/expect\n create mode 100644 t/t4034/bash/post\n create mode 100644 t/t4034/bash/pre\n\ndiff --git a/t/t4018/bash-bashism-style-complete-line-capture b/t/t4018/bash-bashism-style-complete-line-capture\nnew file mode 100644\nindex 0000000000..070b979fa6\n--- /dev/null\n+++ b/t/t4018/bash-bashism-style-complete-line-capture\n@@ -0,0 +1,4 @@\n+function myfunc # RIGHT\n+{\n+    echo 'ChangeMe'\n+}\ndiff --git a/t/t4018/bash-posix-style-complete-line-capture b/t/t4018/bash-posix-style-complete-line-capture\nnew file mode 100644\nindex 0000000000..b56942f322\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-complete-line-capture\n@@ -0,0 +1,4 @@\n+func() { # RIGHT\n+\n+    ChangeMe\n+}\ndiff --git a/t/t4018/bash-posix-style-single-command-function b/t/t4018/bash-posix-style-single-command-function\nnew file mode 100644\nindex 0000000000..398ae1c5d2\n--- /dev/null\n+++ b/t/t4018/bash-posix-style-single-command-function\n@@ -0,0 +1,3 @@\n+RIGHT() echo \"hello\"\n+\n+    ChangeMe\ndiff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh\nindex f51d3557f1..0be647c2fb 100755\n--- a/t/t4034-diff-words.sh\n+++ b/t/t4034-diff-words.sh\n@@ -320,6 +320,7 @@ test_expect_success 'unset default driver' '\n \n test_language_driver ada\n test_language_driver bibtex\n+test_language_driver bash\n test_language_driver cpp\n test_language_driver csharp\n test_language_driver css\ndiff --git a/t/t4034/bash/expect b/t/t4034/bash/expect\nnew file mode 100644\nindex 0000000000..1864ab25dc\n--- /dev/null\n+++ b/t/t4034/bash/expect\n@@ -0,0 +1,36 @@\n+<BOLD>diff --git a/pre b/post<RESET>\n+<BOLD>index 09ac008..60ba6a2 100644<RESET>\n+<BOLD>--- a/pre<RESET>\n+<BOLD>+++ b/post<RESET>\n+<CYAN>@@ -1,31 +1,31 @@<RESET>\n+<RED>my_var<RESET><GREEN>new_var<RESET>=10\n+x=<RED>123<RESET><GREEN>456<RESET>\n+echo <RED>$1<RESET><GREEN>$2<RESET>\n+echo <RED>$USER<RESET><GREEN>$USERNAME<RESET>\n+${<RED>HOME<RESET><GREEN>HOMEDIR<RESET>}\n+((a<RED>+<RESET><GREEN>+=<RESET>b))\n+((a<RED>*<RESET><GREEN>*=<RESET>b))\n+((a<RED>/<RESET><GREEN>/=<RESET>b))\n+((a<RED>%<RESET><GREEN>%=<RESET>b))\n+((a<RED>|<RESET><GREEN>|=<RESET>b))\n+((a<RED>^<RESET><GREEN>^=<RESET>b))\n+((a<RED>=<RESET><GREEN>==<RESET>b))\n+((a<RED>!<RESET><GREEN>!=<RESET>b))\n+((a<RED><<RESET><GREEN><=<RESET>b))\n+((a<RED>><RESET><GREEN>>=<RESET>b))\n+$((a<RED><<RESET><GREEN><<<RESET>b))\n+$((a<RED>><RESET><GREEN>>><RESET>b))\n+$((a<RED>&<RESET><GREEN>&&<RESET>b))\n+$((a<RED>|<RESET><GREEN>||<RESET>b))\n+${a<RED>:<RESET><GREEN>:-<RESET>b}\n+${a<RED>:<RESET><GREEN>:=<RESET>b}\n+${a<RED>:<RESET><GREEN>:+<RESET>b}\n+${a<RED>:<RESET><GREEN>:?<RESET>b}\n+${a<RED>#<RESET><GREEN>##<RESET>*/}\n+${a<RED>%<RESET><GREEN>%%<RESET>.*}\n+${a<RED>^<RESET><GREEN>^^<RESET>}\n+${a<RED>,<RESET><GREEN>,,<RESET>}\n+${<GREEN>!<RESET>a}\n+${a[<RED>*<RESET><GREEN>@<RESET>]}\n+ls <RED>-a<RESET><GREEN>-x<RESET>\n+ls <RED>--all<RESET><GREEN>--color<RESET>\ndiff --git a/t/t4034/bash/post b/t/t4034/bash/post\nnew file mode 100644\nindex 0000000000..2bbee8936d\n--- /dev/null\n+++ b/t/t4034/bash/post\n@@ -0,0 +1,31 @@\n+new_var=10\n+x=456\n+echo $2\n+echo $USERNAME\n+${HOMEDIR}\n+((a+=b))\n+((a*=b))\n+((a/=b))\n+((a%=b))\n+((a|=b))\n+((a^=b))\n+((a==b))\n+((a!=b))\n+((a<=b))\n+((a>=b))\n+$((a<<b))\n+$((a>>b))\n+$((a&&b))\n+$((a||b))\n+${a:-b}\n+${a:=b}\n+${a:+b}\n+${a:?b}\n+${a##*/}\n+${a%%.*}\n+${a^^}\n+${a,,}\n+${!a}\n+${a[@]}\n+ls -x\n+ls --color\ndiff --git a/t/t4034/bash/pre b/t/t4034/bash/pre\nnew file mode 100644\nindex 0000000000..8d22039c40\n--- /dev/null\n+++ b/t/t4034/bash/pre\n@@ -0,0 +1,31 @@\n+my_var=10\n+x=123\n+echo $1\n+echo $USER\n+${HOME}\n+((a+b))\n+((a*b))\n+((a/b))\n+((a%b))\n+((a|b))\n+((a^b))\n+((a=b))\n+((a!b))\n+((a<b))\n+((a>b))\n+$((a<b))\n+$((a>b))\n+$((a&b))\n+$((a|b))\n+${a:b}\n+${a:b}\n+${a:b}\n+${a:b}\n+${a#*/}\n+${a%.*}\n+${a^}\n+${a,}\n+${a}\n+${a[*]}\n+ls -a\n+ls --all\ndiff --git a/userdiff.c b/userdiff.c\nindex 340c4eb4f7..7b18cbb669 100644\n--- a/userdiff.c\n+++ b/userdiff.c\n@@ -59,20 +59,30 @@ PATTERNS(\"bash\",\n \t \"(\"\n \t \"(\"\n \t     /* POSIX identifier with mandatory parentheses */\n-\t     \"[a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n+\t     \"([a-zA-Z_][a-zA-Z0-9_]*[ \\t]*\\\\([ \\t]*\\\\))\"\n \t \"|\"\n \t     /* Bashism identifier with optional parentheses */\n-\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+))\"\n+\t     \"(function[ \\t]+[a-zA-Z_][a-zA-Z0-9_]*(([ \\t]*\\\\([ \\t]*\\\\))|([ \\t]+)))\"\n \t \")\"\n-\t /* Optional whitespace */\n-\t \"[ \\t]*\"\n-\t /* Compound command starting with `{`, `(`, `((` or `[[` */\n-\t \"(\\\\{|\\\\(\\\\(?|\\\\[\\\\[)\"\n+\t /* Everything after the function header is captured  */\n+\t \".*$\"\n \t /* End of captured text */\n \t \")\",\n \t /* -- */\n-\t /* Characters not in the default $IFS value */\n-\t \"[^ \\t]+\"),\n+\t /* Identifiers: variable and function names */\n+\t  \"[a-zA-Z_][a-zA-Z0-9_]*\"\n+\t /* Shell variables: $VAR, ${VAR} */\n+\t  \"|\\\\$[a-zA-Z0-9_]+|\\\\$\\\\{\"\n+\t  /*Command list separators and redirection operators  */\n+\t \"|\\\\|\\\\||&&|<<|>>\"\n+\t /* Operators ending in '=' (comparison + compound assignment) */\n+\t \"|==|!=|<=|>=|[-+*/%&|^]=\"\n+\t /* Additional parameter expansion operators */\n+\t \"|:=|:-|:\\\\+|:\\\\?|##|%%|\\\\^\\\\^|,,\"\n+\t /* Command-line options (to avoid splitting -option) */\n+\t \"|[-a-zA-Z0-9_]+\"\n+\t /* Brackets and grouping symbols */\n+\t \"|\\\\(|\\\\)|\\\\{|\\\\}|\\\\[|\\\\]\"),\n PATTERNS(\"bibtex\",\n \t \"(@[a-zA-Z]{1,}[ \\t]*\\\\{{0,1}[ \\t]*[^ \\t\\\"@',\\\\#}{~%]*).*$\",\n \t /* -- */\n-- \n2.48.0\n\n"},{"id":"518271","messageId":"a72235c1-625a-4b90-8111-629b5a6ee7c2@kdbg.org","threadId":"62935","inReplyTo":"20250516144515.49514-2-dhar61595@gmail.com","subject":"Re: [PATCH v7 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-05-16T17:45:32Z","receivedAt":"2025-05-16T17:45:35Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"All my comments have been addressed and this round looks good. Thank you!\n\nAcked-by: Johannes Sixt <j6t@kdbg.org>\n\n-- Hannes\n\n"},{"id":"518313","messageId":"xmqq4ixkte2h.fsf@gitster.g","threadId":"62935","inReplyTo":"a72235c1-625a-4b90-8111-629b5a6ee7c2@kdbg.org","subject":"Re: [PATCH v7 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-16T21:56:22Z","receivedAt":"2025-05-16T21:56:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n> All my comments have been addressed and this round looks good. Thank you!\n>\n> Acked-by: Johannes Sixt <j6t@kdbg.org>\n>\n> -- Hannes\n\nThanks, both.  Queued.\n"},{"id":"518327","messageId":"xmqq8qmvs7sd.fsf@gitster.g","threadId":"62935","inReplyTo":"8343694f-6e05-43c5-862a-63702314760e@kdbg.org","subject":"Re: [PATCH v6 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-05-17T13:09:38Z","receivedAt":"2025-05-17T13:09:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j6t@kdbg.org> writes:\n\n>> +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n>> +z=<RED>.5<RESET><GREEN>.75<RESET>\n>\n> When do decimal numbers occur in shell scripts? Wouldn't it be more\n> often the case that a fullstop is part of a regular expression or a file\n> name or version number that happens to be surrounded by numbers? In that\n> case, we would prefer to capture the digit sequences as separate words.\n\nSorry but I am confused.  \n\nDo you want a filename \"sample.3gp\" treated as having separate\nparts, \"sample\", \".3\", and \"gp\", instead of a single word?\n"},{"id":"518355","messageId":"613e934c-9236-411c-be37-a8db2e3a3b50@kdbg.org","threadId":"62935","inReplyTo":"xmqq8qmvs7sd.fsf@gitster.g","subject":"Re: [PATCH v6 1/1] userdiff: extend Bash pattern to cover more shell function forms","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-05-18T07:41:03Z","receivedAt":"2025-05-18T07:41:25Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 17.05.25 um 15:09 schrieb Junio C Hamano:\n> Johannes Sixt <j6t@kdbg.org> writes:\n> \n>>> +y=<RED>3.14<RESET><GREEN>2.71<RESET>\n>>> +z=<RED>.5<RESET><GREEN>.75<RESET>\n>>\n>> When do decimal numbers occur in shell scripts? Wouldn't it be more\n>> often the case that a fullstop is part of a regular expression or a file\n>> name or version number that happens to be surrounded by numbers? In that\n>> case, we would prefer to capture the digit sequences as separate words.\n> \n> Sorry but I am confused.  \n> \n> Do you want a filename \"sample.3gp\" treated as having separate\n> parts, \"sample\", \".3\", and \"gp\", instead of a single word?\n\nI didn't consider such a case. The cited paragraph could be interpreted\nto say that we want the words \"sample\", \".\", \"3\", and \"gp\". But the\npattern that we have in round v7 by a lucky strike actually gives\n\"sample\", \".\", and \"3gp\" (I think), which is much better.\n\n-- Hannes\n\n"}]}