diff options
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/README | 4 | ||||
-rw-r--r-- | contrib/buildsystems/CMakeLists.txt | 38 | ||||
-rw-r--r-- | contrib/completion/git-completion.bash | 19 | ||||
-rw-r--r-- | contrib/credential/libsecret/git-credential-libsecret.c | 95 | ||||
-rw-r--r-- | contrib/credential/wincred/git-credential-wincred.c | 20 | ||||
-rwxr-xr-x | contrib/git-jump/git-jump | 2 | ||||
-rwxr-xr-x | contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh | 2 | ||||
-rwxr-xr-x | contrib/subtree/git-subtree.sh | 44 | ||||
-rwxr-xr-x | contrib/subtree/t/t7900-subtree.sh | 2 |
9 files changed, 187 insertions, 39 deletions
diff --git a/contrib/README b/contrib/README index 05f291c1f1..21d3d0e7de 100644 --- a/contrib/README +++ b/contrib/README @@ -23,7 +23,7 @@ This is the same way as how I have been treating gitk, and to a lesser degree various foreign SCM interfaces, so you know the drill. -I expect that things that start their life in the contrib/ area +I expect things that start their life in the contrib/ area to graduate out of contrib/ once they mature, either by becoming projects on their own, or moving to the toplevel directory. On the other hand, I expect I'll be proposing removal of disused @@ -31,7 +31,7 @@ and inactive ones from time to time. If you have new things to add to this area, please first propose it on the git mailing list, and after a list discussion proves -there are some general interests (it does not have to be a +there is general interest (it does not have to be a list-wide consensus for a tool targeted to a relatively narrow audience -- for example I do not work with projects whose upstream is svn, so I have no use for git-svn myself, but it is diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 6b819e2fbd..804629c525 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -974,6 +974,35 @@ target_link_libraries(test-fake-ssh common-main) parse_makefile_for_sources(test-reftable_SOURCES "REFTABLE_TEST_OBJS") list(TRANSFORM test-reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") +#unit-tests +add_library(unit-test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c) + +parse_makefile_for_scripts(unit_test_PROGRAMS "UNIT_TEST_PROGRAMS" "") +foreach(unit_test ${unit_test_PROGRAMS}) + add_executable("${unit_test}" "${CMAKE_SOURCE_DIR}/t/unit-tests/${unit_test}.c") + target_link_libraries("${unit_test}" unit-test-lib common-main) + set_target_properties("${unit_test}" + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin) + if(MSVC) + set_target_properties("${unit_test}" + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin) + set_target_properties("${unit_test}" + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin) + endif() + list(APPEND PROGRAMS_BUILT "${unit_test}") + + # t-basic intentionally fails tests, to validate the unit-test infrastructure. + # Therefore, it should only be run as part of t0080, which verifies that it + # fails only in the expected ways. + # + # All other unit tests should be run. + if(NOT ${unit_test} STREQUAL "t-basic") + add_test(NAME "t.unit-tests.${unit_test}" + COMMAND "./${unit_test}" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/t/unit-tests/bin) + endif() +endforeach() + #test-tool parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS") @@ -1093,17 +1122,18 @@ if(NOT ${CMAKE_BINARY_DIR}/CMakeCache.txt STREQUAL ${CACHE_PATH}) file(COPY ${CMAKE_SOURCE_DIR}/contrib/completion/git-completion.bash DESTINATION ${CMAKE_BINARY_DIR}/contrib/completion/) endif() -file(GLOB test_scipts "${CMAKE_SOURCE_DIR}/t/t[0-9]*.sh") +file(GLOB test_scripts "${CMAKE_SOURCE_DIR}/t/t[0-9]*.sh") #test -foreach(tsh ${test_scipts}) - add_test(NAME ${tsh} +foreach(tsh ${test_scripts}) + string(REGEX REPLACE ".*/(.*)\\.sh" "\\1" test_name ${tsh}) + add_test(NAME "t.suite.${test_name}" COMMAND ${SH_EXE} ${tsh} --no-bin-wrappers --no-chain-lint -vx WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/t) endforeach() # This test script takes an extremely long time and is known to time out even # on fast machines because it requires in excess of one hour to run -set_tests_properties("${CMAKE_SOURCE_DIR}/t/t7112-reset-submodule.sh" PROPERTIES TIMEOUT 4000) +set_tests_properties("t.suite.t7112-reset-submodule" PROPERTIES TIMEOUT 4000) endif()#BUILD_TESTING diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 133ec92bfa..13a39ebd2e 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -28,6 +28,8 @@ # completion style. For example '!f() { : git commit ; ... }; f' will # tell the completion to use commit completion. This also works with aliases # of form "!sh -c '...'". For example, "!sh -c ': git commit ; ... '". +# Note that "git" is optional --- '!f() { : commit; ...}; f' would complete +# just like the 'git commit' command. # # If you have a command that is not part of git, but you would still # like completion, you can use __git_complete: @@ -1182,7 +1184,7 @@ __git_aliased_command () :) : skip null command ;; \'*) : skip opening quote after sh -c ;; *) - cur="$word" + cur="${word%;}" break esac done @@ -1607,7 +1609,7 @@ _git_checkout () if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then __git_complete_refs --mode="refs" - elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + elif [ -n "$(__git_find_on_cmdline "-t --track")" ]; then __git_complete_refs --mode="remote-heads" else __git_complete_refs $dwim_opt --mode="refs" @@ -1677,6 +1679,11 @@ _git_clone () __git_untracked_file_modes="all no normal" +__git_trailer_tokens () +{ + __git config --name-only --get-regexp '^trailer\..*\.key$' | cut -d. -f 2- | rev | cut -d. -f2- | rev +} + _git_commit () { case "$prev" in @@ -1701,6 +1708,10 @@ _git_commit () __gitcomp "$__git_untracked_file_modes" "" "${cur##--untracked-files=}" return ;; + --trailer=*) + __gitcomp_nl "$(__git_trailer_tokens)" "" "${cur##--trailer=}" ":" + return + ;; --*) __gitcomp_builtin commit return @@ -2042,7 +2053,7 @@ __git_log_shortlog_options=" " # Options accepted by log and show __git_log_show_options=" - --diff-merges --diff-merges= --no-diff-merges --remerge-diff + --diff-merges --diff-merges= --no-diff-merges --dd --remerge-diff " __git_diff_merges_opts="off none on first-parent 1 separate m combined c dense-combined cc remerge r" @@ -2514,7 +2525,7 @@ _git_switch () if [ -n "$(__git_find_on_cmdline "-c -C -d --detach")" ]; then __git_complete_refs --mode="refs" - elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + elif [ -n "$(__git_find_on_cmdline "-t --track")" ]; then __git_complete_refs --mode="remote-heads" else __git_complete_refs $dwim_opt --mode="heads" diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c index ef681f29d5..215a81d8ba 100644 --- a/contrib/credential/libsecret/git-credential-libsecret.c +++ b/contrib/credential/libsecret/git-credential-libsecret.c @@ -39,6 +39,8 @@ struct credential { char *path; char *username; char *password; + char *password_expiry_utc; + char *oauth_refresh_token; }; #define CREDENTIAL_INIT { 0 } @@ -52,8 +54,29 @@ struct credential_operation { #define CREDENTIAL_OP_END { NULL, NULL } +static void credential_clear(struct credential *c); + /* ----------------- Secret Service functions ----------------- */ +static const SecretSchema schema = { + "org.git.Password", + /* Ignore schema name during search for backwards compatibility */ + SECRET_SCHEMA_DONT_MATCH_NAME, + { + /* + * libsecret assumes attribute values are non-confidential and + * unchanging, so we can't include oauth_refresh_token or + * password_expiry_utc. + */ + { "user", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "object", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "protocol", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { "port", SECRET_SCHEMA_ATTRIBUTE_INTEGER }, + { "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { NULL, 0 }, + } +}; + static char *make_label(struct credential *c) { if (c->port) @@ -101,7 +124,7 @@ static int keyring_get(struct credential *c) attributes = make_attr_list(c); items = secret_service_search_sync(service, - SECRET_SCHEMA_COMPAT_NETWORK, + &schema, attributes, SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_UNLOCK, NULL, @@ -117,6 +140,7 @@ static int keyring_get(struct credential *c) SecretItem *item; SecretValue *secret; const char *s; + gchar **parts; item = items->data; secret = secret_item_get_secret(item); @@ -130,8 +154,27 @@ static int keyring_get(struct credential *c) s = secret_value_get_text(secret); if (s) { - g_free(c->password); - c->password = g_strdup(s); + /* + * Passwords and other attributes encoded in following format: + * hunter2 + * password_expiry_utc=1684189401 + * oauth_refresh_token=xyzzy + */ + parts = g_strsplit(s, "\n", 0); + if (g_strv_length(parts) >= 1) { + g_free(c->password); + c->password = g_strdup(parts[0]); + } + for (int i = 1; i < g_strv_length(parts); i++) { + if (g_str_has_prefix(parts[i], "password_expiry_utc=")) { + g_free(c->password_expiry_utc); + c->password_expiry_utc = g_strdup(&parts[i][20]); + } else if (g_str_has_prefix(parts[i], "oauth_refresh_token=")) { + g_free(c->oauth_refresh_token); + c->oauth_refresh_token = g_strdup(&parts[i][20]); + } + } + g_strfreev(parts); } g_hash_table_unref(attributes); @@ -148,6 +191,7 @@ static int keyring_store(struct credential *c) char *label = NULL; GHashTable *attributes = NULL; GError *error = NULL; + GString *secret = NULL; /* * Sanity check that what we are storing is actually sensible. @@ -162,13 +206,23 @@ static int keyring_store(struct credential *c) label = make_label(c); attributes = make_attr_list(c); - secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK, + secret = g_string_new(c->password); + if (c->password_expiry_utc) { + g_string_append_printf(secret, "\npassword_expiry_utc=%s", + c->password_expiry_utc); + } + if (c->oauth_refresh_token) { + g_string_append_printf(secret, "\noauth_refresh_token=%s", + c->oauth_refresh_token); + } + secret_password_storev_sync(&schema, attributes, NULL, label, - c->password, + secret->str, NULL, &error); + g_string_free(secret, TRUE); g_free(label); g_hash_table_unref(attributes); @@ -185,6 +239,7 @@ static int keyring_erase(struct credential *c) { GHashTable *attributes = NULL; GError *error = NULL; + struct credential existing = CREDENTIAL_INIT; /* * Sanity check that we actually have something to match @@ -197,8 +252,22 @@ static int keyring_erase(struct credential *c) if (!c->protocol && !c->host && !c->path && !c->username) return EXIT_FAILURE; + if (c->password) { + existing.host = g_strdup(c->host); + existing.path = g_strdup(c->path); + existing.port = c->port; + existing.protocol = g_strdup(c->protocol); + existing.username = g_strdup(c->username); + keyring_get(&existing); + if (existing.password && strcmp(c->password, existing.password)) { + credential_clear(&existing); + return EXIT_SUCCESS; + } + credential_clear(&existing); + } + attributes = make_attr_list(c); - secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK, + secret_password_clearv_sync(&schema, attributes, NULL, &error); @@ -238,6 +307,8 @@ static void credential_clear(struct credential *c) g_free(c->path); g_free(c->username); g_free(c->password); + g_free(c->password_expiry_utc); + g_free(c->oauth_refresh_token); credential_init(c); } @@ -284,11 +355,19 @@ static int credential_read(struct credential *c) } else if (!strcmp(key, "username")) { g_free(c->username); c->username = g_strdup(value); + } else if (!strcmp(key, "password_expiry_utc")) { + g_free(c->password_expiry_utc); + c->password_expiry_utc = g_strdup(value); } else if (!strcmp(key, "password")) { g_free(c->password); c->password = g_strdup(value); while (*value) *value++ = '\0'; + } else if (!strcmp(key, "oauth_refresh_token")) { + g_free(c->oauth_refresh_token); + c->oauth_refresh_token = g_strdup(value); + while (*value) + *value++ = '\0'; } /* * Ignore other lines; we don't know what they mean, but @@ -314,6 +393,10 @@ static void credential_write(const struct credential *c) /* only write username/password, if set */ credential_write_item(stdout, "username", c->username); credential_write_item(stdout, "password", c->password); + credential_write_item(stdout, "password_expiry_utc", + c->password_expiry_utc); + credential_write_item(stdout, "oauth_refresh_token", + c->oauth_refresh_token); } static void usage(const char *name) diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c index 96f10613ae..4cd56c42e2 100644 --- a/contrib/credential/wincred/git-credential-wincred.c +++ b/contrib/credential/wincred/git-credential-wincred.c @@ -109,7 +109,18 @@ static int match_part_last(LPCWSTR *ptarget, LPCWSTR want, LPCWSTR delim) return match_part_with_last(ptarget, want, delim, 1); } -static int match_cred(const CREDENTIALW *cred) +static int match_cred_password(const CREDENTIALW *cred) { + int ret; + WCHAR *cred_password = xmalloc(cred->CredentialBlobSize); + wcsncpy_s(cred_password, cred->CredentialBlobSize, + (LPCWSTR)cred->CredentialBlob, + cred->CredentialBlobSize / sizeof(WCHAR)); + ret = !wcscmp(cred_password, password); + free(cred_password); + return ret; +} + +static int match_cred(const CREDENTIALW *cred, int match_password) { LPCWSTR target = cred->TargetName; if (wusername && wcscmp(wusername, cred->UserName ? cred->UserName : L"")) @@ -119,7 +130,8 @@ static int match_cred(const CREDENTIALW *cred) match_part(&target, protocol, L"://") && match_part_last(&target, wusername, L"@") && match_part(&target, host, L"/") && - match_part(&target, path, L""); + match_part(&target, path, L"") && + (!match_password || match_cred_password(cred)); } static void get_credential(void) @@ -134,7 +146,7 @@ static void get_credential(void) /* search for the first credential that matches username */ for (i = 0; i < num_creds; ++i) - if (match_cred(creds[i])) { + if (match_cred(creds[i], 0)) { write_item("username", creds[i]->UserName, creds[i]->UserName ? wcslen(creds[i]->UserName) : 0); write_item("password", @@ -196,7 +208,7 @@ static void erase_credential(void) return; for (i = 0; i < num_creds; ++i) { - if (match_cred(creds[i])) + if (match_cred(creds[i], password != NULL)) CredDeleteW(creds[i]->TargetName, creds[i]->Type, 0); } diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump index 40c4b0d111..47e0c557e6 100755 --- a/contrib/git-jump/git-jump +++ b/contrib/git-jump/git-jump @@ -9,7 +9,7 @@ The <mode> parameter is one of: diff: elements are diff hunks. Arguments are given to diff. -merge: elements are merge conflicts. Arguments are ignored. +merge: elements are merge conflicts. Arguments are given to ls-files -u. grep: elements are grep hits. Arguments are given to git grep or, if configured, to the command in `jump.grepCmd`. diff --git a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh index 6187ec67fa..7139995a40 100755 --- a/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh +++ b/contrib/mw-to-git/t/t9363-mw-to-git-export-import.sh @@ -161,7 +161,7 @@ test_expect_success 'git push properly warns about insufficient permissions' ' git add foo.forbidden && git commit -m "add a file" && git push 2>actual && - test_i18ngrep "foo.forbidden is not a permitted file" actual + test_grep "foo.forbidden is not a permitted file" actual ) ' diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 7db4c45676..3028029ac2 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -33,19 +33,19 @@ git subtree split --prefix=<prefix> [<commit>] git subtree pull --prefix=<prefix> <repository> <ref> git subtree push --prefix=<prefix> <repository> <refspec> -- -h,help show the help -q,quiet quiet -d,debug show debug messages +h,help! show the help +q,quiet! quiet +d,debug! show debug messages P,prefix= the name of the subdir to split out options for 'split' (also: 'push') annotate= add a prefix to commit message of new commits -b,branch= create a new branch from the split subtree +b,branch!= create a new branch from the split subtree ignore-joins ignore prior --rejoin commits onto= try connecting new tree to an existing one rejoin merge the new branch back into HEAD options for 'add' and 'merge' (also: 'pull', 'split --rejoin', and 'push --rejoin') squash merge subtree changes as a single commit -m,message= use the given message as the commit message for the merge commit +m,message!= use the given message as the commit message for the merge commit " indent=0 @@ -373,7 +373,8 @@ try_remove_previous () { # Usage: process_subtree_split_trailer SPLIT_HASH MAIN_HASH [REPOSITORY] process_subtree_split_trailer () { - assert test $# = 2 -o $# = 3 + assert test $# -ge 2 + assert test $# -le 3 b="$1" sq="$2" repository="" @@ -402,7 +403,8 @@ process_subtree_split_trailer () { # Usage: find_latest_squash DIR [REPOSITORY] find_latest_squash () { - assert test $# = 1 -o $# = 2 + assert test $# -ge 1 + assert test $# -le 2 dir="$1" repository="" if test "$#" = 2 @@ -455,7 +457,8 @@ find_latest_squash () { # Usage: find_existing_splits DIR REV [REPOSITORY] find_existing_splits () { - assert test $# = 2 -o $# = 3 + assert test $# -ge 2 + assert test $# -le 3 debug "Looking for prior splits..." local indent=$(($indent + 1)) @@ -489,13 +492,13 @@ find_existing_splits () { ;; END) debug "Main is: '$main'" - if test -z "$main" -a -n "$sub" + if test -z "$main" && test -n "$sub" then # squash commits refer to a subtree debug " Squash: $sq from $sub" cache_set "$sq" "$sub" fi - if test -n "$main" -a -n "$sub" + if test -n "$main" && test -n "$sub" then debug " Prior: $main -> $sub" cache_set $main $sub @@ -638,10 +641,16 @@ subtree_for_commit () { while read mode type tree name do assert test "$name" = "$dir" - assert test "$type" = "tree" -o "$type" = "commit" - test "$type" = "commit" && continue # ignore submodules - echo $tree - break + + case "$type" in + commit) + continue;; # ignore submodules + tree) + echo $tree + break;; + *) + die "fatal: tree entry is of type ${type}, expected tree or commit";; + esac done || exit $? } @@ -916,7 +925,7 @@ cmd_split () { if test $# -eq 0 then rev=$(git rev-parse HEAD) - elif test $# -eq 1 -o $# -eq 2 + elif test $# -eq 1 || test $# -eq 2 then rev=$(git rev-parse -q --verify "$1^{commit}") || die "fatal: '$1' does not refer to a commit" @@ -1006,8 +1015,11 @@ cmd_split () { # Usage: cmd_merge REV [REPOSITORY] cmd_merge () { - test $# -eq 1 -o $# -eq 2 || + if test $# -lt 1 || test $# -gt 2 + then die "fatal: you must provide exactly one revision, and optionally a repository. Got: '$*'" + fi + rev=$(git rev-parse -q --verify "$1^{commit}") || die "fatal: '$1' does not refer to a commit" repository="" diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 341c169eca..49a21dd7c9 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -71,7 +71,7 @@ test_expect_success 'shows short help text for -h' ' test_expect_code 129 git subtree -h >out 2>err && test_must_be_empty err && grep -e "^ *or: git subtree pull" out && - grep -e --annotate out + grep -F -e "--[no-]annotate" out ' # |