diff --git a/Makefile b/Makefile index 21c0bf1667..b8a7e85141 100644 --- a/Makefile +++ b/Makefile @@ -753,6 +753,7 @@ TEST_BUILTINS_OBJS += test-submodule-nested-repo-config.o TEST_BUILTINS_OBJS += test-subprocess.o TEST_BUILTINS_OBJS += test-trace2.o TEST_BUILTINS_OBJS += test-urlmatch-normalization.o +TEST_BUILTINS_OBJS += test-userdiff.o TEST_BUILTINS_OBJS += test-wildmatch.o TEST_BUILTINS_OBJS += test-windows-named-pipe.o TEST_BUILTINS_OBJS += test-write-cache.o diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index 29ce89090d..d3b299e75c 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -479,22 +479,26 @@ test_expect_success 'blame -L ^:RE (absolute: end-of-file)' ' check_count -f hello.c -L$n -L^:ma.. F 4 G 1 H 1 ' -test_expect_success 'setup -L :funcname with userdiff driver' ' - echo "fortran-* diff=fortran" >.gitattributes && - fortran_file=fortran-external-function && - orig_file="$TEST_DIRECTORY/t4018/$fortran_file" && - cp "$orig_file" . && - git add "$fortran_file" && - GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" \ - git commit -m "add fortran file" && - sed -e "s/ChangeMe/IWasChanged/" <"$orig_file" >"$fortran_file" && - git add "$fortran_file" && - GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" \ - git commit -m "change fortran file" -' - test_expect_success 'blame -L :funcname with userdiff driver' ' - check_count -f fortran-external-function -L:RIGHT A 7 B 1 + cat >file.template <<-\EOF && + DO NOT MATCH THIS LINE + function RIGHT(a, b) result(c) + AS THE DEFAULT DRIVER WOULD + + integer, intent(in) :: ChangeMe + EOF + + fortran_file=file.f03 && + test_when_finished "rm .gitattributes" && + echo "$fortran_file diff=fortran" >.gitattributes && + + test_commit --author "A " \ + "add" "$fortran_file" \ + "$(cat file.template)" && + test_commit --author "B " \ + "change" "$fortran_file" \ + "$(cat file.template | sed -e s/ChangeMe/IWasChanged/)" && + check_count -f "$fortran_file" -L:RIGHT A 3 B 1 ' test_expect_success 'setup incremental' ' diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 25c6a37e93..c5bd0c6d4c 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -73,6 +73,7 @@ static struct test_cmd cmds[] = { { "submodule-nested-repo-config", cmd__submodule_nested_repo_config }, { "subprocess", cmd__subprocess }, { "trace2", cmd__trace2 }, + { "userdiff", cmd__userdiff }, { "urlmatch-normalization", cmd__urlmatch_normalization }, { "xml-encode", cmd__xml_encode }, { "wildmatch", cmd__wildmatch }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index f03c5988b2..e8069a3b22 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -63,6 +63,7 @@ int cmd__submodule_config(int argc, const char **argv); int cmd__submodule_nested_repo_config(int argc, const char **argv); int cmd__subprocess(int argc, const char **argv); int cmd__trace2(int argc, const char **argv); +int cmd__userdiff(int argc, const char **argv); int cmd__urlmatch_normalization(int argc, const char **argv); int cmd__xml_encode(int argc, const char **argv); int cmd__wildmatch(int argc, const char **argv); diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c new file mode 100644 index 0000000000..f013f8a31e --- /dev/null +++ b/t/helper/test-userdiff.c @@ -0,0 +1,46 @@ +#include "test-tool.h" +#include "cache.h" +#include "userdiff.h" +#include "config.h" + +static int driver_cb(struct userdiff_driver *driver, + enum userdiff_driver_type type, void *priv) +{ + enum userdiff_driver_type *want_type = priv; + if (type & *want_type && driver->funcname.pattern) + puts(driver->name); + return 0; +} + +static int cmd__userdiff_config(const char *var, const char *value, void *cb) +{ + if (userdiff_config(var, value) < 0) + return -1; + return 0; +} + +int cmd__userdiff(int argc, const char **argv) +{ + enum userdiff_driver_type want = 0; + if (argc != 2) + return 1; + + if (!strcmp(argv[1], "list-drivers")) + want = (USERDIFF_DRIVER_TYPE_BUILTIN | + USERDIFF_DRIVER_TYPE_CUSTOM); + else if (!strcmp(argv[1], "list-builtin-drivers")) + want = USERDIFF_DRIVER_TYPE_BUILTIN; + else if (!strcmp(argv[1], "list-custom-drivers")) + want = USERDIFF_DRIVER_TYPE_CUSTOM; + else + return error("unknown argument %s", argv[1]); + + if (want & USERDIFF_DRIVER_TYPE_CUSTOM) { + setup_git_directory(); + git_config(cmd__userdiff_config, NULL); + } + + for_each_userdiff_driver(driver_cb, &want); + + return 0; +} diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index 823ea96acb..740696c8f7 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -25,34 +25,26 @@ test_expect_success 'setup' ' echo B >B.java ' +test_expect_success 'setup: test-tool userdiff' ' + # Make sure additions to builtin_drivers are sorted + test_when_finished "rm builtin-drivers.sorted" && + test-tool userdiff list-builtin-drivers >builtin-drivers && + test_file_not_empty builtin-drivers && + sort builtin-drivers.sorted && + test_cmp builtin-drivers.sorted builtin-drivers && + + # Ditto, but "custom" requires the .git directory and config + # to be setup and read. + test_when_finished "rm custom-drivers.sorted" && + test-tool userdiff list-custom-drivers >custom-drivers && + test_file_not_empty custom-drivers && + sort custom-drivers.sorted && + test_cmp custom-drivers.sorted custom-drivers +' + diffpatterns=" - ada - bash - bibtex - cpp - csharp - css - dts - elixir - fortran - fountain - golang - html - java - markdown - matlab - objc - pascal - perl - php - python - ruby - rust - scheme - tex - custom1 - custom2 - custom3 + $(cat builtin-drivers) + $(cat custom-drivers) " for p in $diffpatterns @@ -102,13 +94,7 @@ test_expect_success 'setup hunk header tests' ' # check each individual file for i in $(git ls-files) do - if grep broken "$i" >/dev/null 2>&1 - then - result=failure - else - result=success - fi - test_expect_$result "hunk header: $i" " + test_expect_success "hunk header: $i" " git diff -U1 $i >actual && grep '@@ .* @@.*RIGHT' actual " diff --git a/t/t4018/README b/t/t4018/README index 283e01cca1..2d25b2b4fc 100644 --- a/t/t4018/README +++ b/t/t4018/README @@ -7,9 +7,6 @@ at least two lines from the line that must appear in the hunk header. The text that must appear in the hunk header must contain the word "right", but in all upper-case, like in the title above. -To mark a test case that highlights a malfunction, insert the word -BROKEN in all lower-case somewhere in the file. - This text is a bit twisted and out of order, but it is itself a test case for the default hunk header pattern. Know what you are doing if you change it. diff --git a/userdiff.c b/userdiff.c index 3897317aff..3c3bbe38b0 100644 --- a/userdiff.c +++ b/userdiff.c @@ -44,6 +44,46 @@ PATTERNS("bash", /* -- */ /* Characters not in the default $IFS value */ "[^ \t]+"), +PATTERNS("bibtex", + "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", + /* -- */ + "[={}\"]|[^={}\" \t]+"), +PATTERNS("cpp", + /* Jump targets or access declarations */ + "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n" + /* functions/methods, variables, and compounds at top level */ + "^((::[[:space:]]*)?[A-Za-z_].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"), +PATTERNS("csharp", + /* Keywords */ + "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" + /* Methods and constructors */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" + /* Properties */ + "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" + /* Type definitions */ + "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n" + /* Namespace */ + "^[ \t]*(namespace[ \t]+.*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), +IPATTERN("css", + "![:;][[:space:]]*$\n" + "^[:[@.#]?[_a-z0-9].*$", + /* -- */ + /* + * This regex comes from W3C CSS specs. Should theoretically also + * allow ISO 10646 characters U+00A0 and higher, + * but they are not handled in this regex. + */ + "-?[_a-zA-Z][-_a-zA-Z0-9]*" /* identifiers */ + "|-?[0-9]+|\\#[0-9a-fA-F]+" /* numbers */ +), PATTERNS("dts", "!;\n" "!=\n" @@ -83,7 +123,9 @@ IPATTERN("fortran", * they would have been matched above as a variable anyway. */ "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" "|//|\\*\\*|::|[/<>=]="), -IPATTERN("fountain", "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$", +IPATTERN("fountain", + "^((\\.[^.]|(int|ext|est|int\\.?/ext|i/e)[. ]).*)$", + /* -- */ "[^ \t-]+"), PATTERNS("golang", /* Functions */ @@ -94,7 +136,9 @@ PATTERNS("golang", "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.eE]+i?|0[xX]?[0-9a-fA-F]+i?" "|[-+*/<>%&^|=!:]=|--|\\+\\+|<<=?|>>=?|&\\^=?|&&|\\|\\||<-|\\.{3}"), -PATTERNS("html", "^[ \t]*(<[Hh][1-6]([ \t].*)?>.*)$", +PATTERNS("html", + "^[ \t]*(<[Hh][1-6]([ \t].*)?>.*)$", + /* -- */ "[^<>= \t]+"), PATTERNS("java", "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" @@ -106,6 +150,7 @@ PATTERNS("java", "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|"), PATTERNS("markdown", "^ {0,3}#{1,6}[ \t].*", + /* -- */ "[^<>= \t]+"), PATTERNS("matlab", /* @@ -114,6 +159,7 @@ PATTERNS("matlab", * that is understood by both. */ "^[[:space:]]*((classdef|function)[[:space:]].*)$|^(%%%?|##)[[:space:]].*$", + /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*|[-+0-9.e]+|[=~<>]=|\\.[*/\\^']|\\|\\||&&"), PATTERNS("objc", /* Negate C statements that can look like functions */ @@ -129,9 +175,8 @@ PATTERNS("objc", "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), PATTERNS("pascal", - "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface|" - "implementation|initialization|finalization)[ \t]*.*)$" - "\n" + "^(((class[ \t]+)?(procedure|function)|constructor|destructor|interface" + "|implementation|initialization|finalization)[ \t]*.*)$\n" "^(.*=[ \t]*(class|record).*)$", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" @@ -174,13 +219,15 @@ PATTERNS("php", "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->"), -PATTERNS("python", "^[ \t]*((class|(async[ \t]+)?def)[ \t].*)$", +PATTERNS("python", + "^[ \t]*((class|(async[ \t]+)?def)[ \t].*)$", /* -- */ "[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?"), /* -- */ -PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$", +PATTERNS("ruby", + "^[ \t]*((class|module|def)[ \t].*)$", /* -- */ "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*" "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?." @@ -200,46 +247,8 @@ PATTERNS("scheme", "\\|([^\\\\]*)\\|" /* All other words should be delimited by spaces or parentheses */ "|([^][)(}{[ \t])+"), -PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", - "[={}\"]|[^={}\" \t]+"), PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+"), -PATTERNS("cpp", - /* Jump targets or access declarations */ - "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n" - /* functions/methods, variables, and compounds at top level */ - "^((::[[:space:]]*)?[A-Za-z_].*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"), -PATTERNS("csharp", - /* Keywords */ - "!^[ \t]*(do|while|for|if|else|instanceof|new|return|switch|case|throw|catch|using)\n" - /* Methods and constructors */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe|async)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[<>@._[:alnum:]]+[ \t]*\\(.*\\))[ \t]*$\n" - /* Properties */ - "^[ \t]*(((static|public|internal|private|protected|new|virtual|sealed|override|unsafe)[ \t]+)*[][<>@.~_[:alnum:]]+[ \t]+[@._[:alnum:]]+)[ \t]*$\n" - /* Type definitions */ - "^[ \t]*(((static|public|internal|private|protected|new|unsafe|sealed|abstract|partial)[ \t]+)*(class|enum|interface|struct)[ \t]+.*)$\n" - /* Namespace */ - "^[ \t]*(namespace[ \t]+.*)$", - /* -- */ - "[a-zA-Z_][a-zA-Z0-9_]*" - "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" - "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"), -IPATTERN("css", - "![:;][[:space:]]*$\n" - "^[:[@.#]?[_a-z0-9].*$", - /* -- */ - /* - * This regex comes from W3C CSS specs. Should theoretically also - * allow ISO 10646 characters U+00A0 and higher, - * but they are not handled in this regex. - */ - "-?[_a-zA-Z][-_a-zA-Z0-9]*" /* identifiers */ - "|-?[0-9]+|\\#[0-9a-fA-F]+" /* numbers */ -), { "default", NULL, -1, { NULL, 0 } }, }; #undef PATTERNS @@ -259,20 +268,33 @@ static struct userdiff_driver driver_false = { { NULL, 0 } }; -static struct userdiff_driver *userdiff_find_by_namelen(const char *k, size_t len) +struct find_by_namelen_data { + const char *name; + size_t len; + struct userdiff_driver *driver; +}; + +static int userdiff_find_by_namelen_cb(struct userdiff_driver *driver, + enum userdiff_driver_type type, void *priv) { - int i; - for (i = 0; i < ndrivers; i++) { - struct userdiff_driver *drv = drivers + i; - if (!strncmp(drv->name, k, len) && !drv->name[len]) - return drv; + struct find_by_namelen_data *cb_data = priv; + + if (!strncmp(driver->name, cb_data->name, cb_data->len) && + !driver->name[cb_data->len]) { + cb_data->driver = driver; + return 1; /* tell the caller to stop iterating */ } - for (i = 0; i < ARRAY_SIZE(builtin_drivers); i++) { - struct userdiff_driver *drv = builtin_drivers + i; - if (!strncmp(drv->name, k, len) && !drv->name[len]) - return drv; - } - return NULL; + return 0; +} + +static struct userdiff_driver *userdiff_find_by_namelen(const char *name, size_t len) +{ + struct find_by_namelen_data udcbdata = { + .name = name, + .len = len, + }; + for_each_userdiff_driver(userdiff_find_by_namelen_cb, &udcbdata); + return udcbdata.driver; } static int parse_funcname(struct userdiff_funcname *f, const char *k, @@ -379,3 +401,36 @@ struct userdiff_driver *userdiff_get_textconv(struct repository *r, return driver; } + +static int for_each_userdiff_driver_list(each_userdiff_driver_fn fn, + enum userdiff_driver_type type, void *cb_data, + struct userdiff_driver *drv, + int drv_size) +{ + int i; + int ret; + for (i = 0; i < drv_size; i++) { + struct userdiff_driver *item = drv + i; + if ((ret = fn(item, type, cb_data))) + return ret; + } + return 0; +} + +int for_each_userdiff_driver(each_userdiff_driver_fn fn, void *cb_data) +{ + int ret; + + ret = for_each_userdiff_driver_list(fn, USERDIFF_DRIVER_TYPE_CUSTOM, + cb_data, drivers, ndrivers); + if (ret) + return ret; + + ret = for_each_userdiff_driver_list(fn, USERDIFF_DRIVER_TYPE_BUILTIN, + cb_data, builtin_drivers, + ARRAY_SIZE(builtin_drivers)); + if (ret) + return ret; + + return 0; +} diff --git a/userdiff.h b/userdiff.h index 203057e13e..aee91bc77e 100644 --- a/userdiff.h +++ b/userdiff.h @@ -21,6 +21,12 @@ struct userdiff_driver { struct notes_cache *textconv_cache; int textconv_want_cache; }; +enum userdiff_driver_type { + USERDIFF_DRIVER_TYPE_BUILTIN = 1<<0, + USERDIFF_DRIVER_TYPE_CUSTOM = 1<<1, +}; +typedef int (*each_userdiff_driver_fn)(struct userdiff_driver *, + enum userdiff_driver_type, void *); int userdiff_config(const char *k, const char *v); struct userdiff_driver *userdiff_find_by_name(const char *name); @@ -34,4 +40,11 @@ struct userdiff_driver *userdiff_find_by_path(struct index_state *istate, struct userdiff_driver *userdiff_get_textconv(struct repository *r, struct userdiff_driver *driver); +/* + * Iterate over all userdiff drivers. The userdiff_driver_type + * argument to each_userdiff_driver_fn indicates their type. Return + * non-zero to exit early from the loop. + */ +int for_each_userdiff_driver(each_userdiff_driver_fn, void *); + #endif /* USERDIFF */