diff options
Diffstat (limited to 'contrib')
23 files changed, 568 insertions, 920 deletions
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 2f6e0197ff..6b819e2fbd 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -227,7 +227,7 @@ add_compile_definitions(GIT_HOST_CPU="${CMAKE_SYSTEM_PROCESSOR}") add_compile_definitions(SHA256_BLK INTERNAL_QSORT RUNTIME_PREFIX) add_compile_definitions(NO_OPENSSL SHA1_DC SHA1DC_NO_STANDARD_INCLUDES SHA1DC_INIT_SAFE_HASH_DEFAULT=0 - SHA1DC_CUSTOM_INCLUDE_SHA1_C="cache.h" + SHA1DC_CUSTOM_INCLUDE_SHA1_C="git-compat-util.h" SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C="git-compat-util.h" ) list(APPEND compat_SOURCES sha1dc_git.c sha1dc/sha1.c sha1dc/ubc_check.c block-sha1/sha1.c sha256/block/sha256.c compat/qsort_s.c) @@ -738,6 +738,15 @@ if(WIN32) else() message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}") endif() + + add_executable(headless-git ${CMAKE_SOURCE_DIR}/compat/win32/headless.c) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") + target_link_options(headless-git PUBLIC -municode -Wl,-subsystem,windows) + elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") + target_link_options(headless-git PUBLIC /NOLOGO /ENTRY:wWinMainCRTStartup /SUBSYSTEM:WINDOWS) + else() + message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}") + endif() elseif(UNIX) target_link_libraries(common-main pthread rt) endif() diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index 1a25789d28..b2e68a1671 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -76,7 +76,7 @@ sub createProject { my $libs_release = "\n "; my $libs_debug = "\n "; - if (!$static_library) { + if (!$static_library && $name ne 'headless-git') { $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}})); $libs_debug = $libs_release; $libs_debug =~ s/zlib\.lib/zlibd\.lib/g; @@ -230,7 +230,7 @@ EOM print F << "EOM"; </ItemGroup> EOM - if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') { + if ((!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') && !($name =~ /headless-git/)) { my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"}; my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"}; my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"}; diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index ed6c45988a..069be7e4be 100755 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -371,6 +371,7 @@ sub handleLinkLine # exit(1); foreach (@objfiles) { my $sourcefile = $_; + $sourcefile =~ s/^headless-git\.o$/compat\/win32\/headless.c/; $sourcefile =~ s/\.o$/.c/; push(@sources, $sourcefile); push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); diff --git a/contrib/coccinelle/README b/contrib/coccinelle/README index d1daa1f626..055ad0e06a 100644 --- a/contrib/coccinelle/README +++ b/contrib/coccinelle/README @@ -1,7 +1,9 @@ -This directory provides examples of Coccinelle (http://coccinelle.lip6.fr/) -semantic patches that might be useful to developers. += coccinelle -There are two types of semantic patches: +This directory provides Coccinelle (http://coccinelle.lip6.fr/) semantic patches +that might be useful to developers. + +== Types of semantic patches * Using the semantic transformation to check for bad patterns in the code; The target 'make coccicheck' is designed to check for these patterns and @@ -42,7 +44,7 @@ There are two types of semantic patches: This allows to expose plans of pending large scale refactorings without impacting the bad pattern checks. -Git-specific tips & things to know about how we run "spatch": +== Git-specific tips & things to know about how we run "spatch": * The "make coccicheck" will piggy-back on "COMPUTE_HEADER_DEPENDENCIES". If you've built a given object file @@ -90,3 +92,33 @@ Git-specific tips & things to know about how we run "spatch": The absolute times will differ for you, but the relative speedup from caching should be on that order. + +== Authoring and reviewing coccinelle changes + +* When a .cocci is made, both the Git changes and .cocci file should be + reviewed. When reviewing such a change, do your best to understand the .cocci + changes (e.g. by asking the author to explain the change) and be explicit + about your understanding of the changes. This helps us decide whether input + from coccinelle experts is needed or not. If you aren't sure of the cocci + changes, indicate what changes you actively endorse and leave an Acked-by + (instead of Reviewed-by). + +* Authors should consider that reviewers may not be coccinelle experts, thus the + the .cocci changes may not be self-evident. A plain text description of the + changes is strongly encouraged, especially when using more esoteric features + of the language. + +* .cocci rules should target only the problem it is trying to solve; "collateral + damage" is not allowed. Reviewers should look out and flag overly-broad rules. + +* Consider the cost-benefit ratio of .cocci changes. In particular, consider the + effect on the runtime of "make coccicheck", and how often your .cocci check + will catch something valuable. As a rule of thumb, rules that can bail early + if a file doesn't have a particular token will have a small impact on runtime, + and vice-versa. + +* .cocci files used for refactoring should be temporarily kept in-tree to aid + the refactoring of out-of-tree code (e.g. in-flight topics). Periodically + evaluate the cost-benefit ratio to determine when the file should be removed. + For example, consider how many out-of-tree users are left and how much this + slows down "make coccicheck". diff --git a/contrib/coccinelle/config_fn_ctx.pending.cocci b/contrib/coccinelle/config_fn_ctx.pending.cocci new file mode 100644 index 0000000000..6d3d1000a9 --- /dev/null +++ b/contrib/coccinelle/config_fn_ctx.pending.cocci @@ -0,0 +1,144 @@ +@ get_fn @ +identifier fn, R; +@@ +( +( +git_config_from_file +| +git_config_from_file_with_options +| +git_config_from_mem +| +git_config_from_blob_oid +| +read_early_config +| +read_very_early_config +| +config_with_options +| +git_config +| +git_protected_config +| +config_from_gitmodules +) + (fn, ...) +| +repo_config(R, fn, ...) +) + +@ extends get_fn @ +identifier C1, C2, D; +@@ +int fn(const char *C1, const char *C2, ++ const struct config_context *ctx, + void *D); + +@ extends get_fn @ +@@ +int fn(const char *, const char *, ++ const struct config_context *, + void *); + +@ extends get_fn @ +// Don't change fns that look like callback fns but aren't +identifier fn2 != tar_filter_config && != git_diff_heuristic_config && + != git_default_submodule_config && != git_color_config && + != bundle_list_update && != parse_object_filter_config; +identifier C1, C2, D1, D2, S; +attribute name UNUSED; +@@ +int fn(const char *C1, const char *C2, ++ const struct config_context *ctx, + void *D1) { +<+... +( +fn2(C1, C2 ++ , ctx +, D2); +| +if(fn2(C1, C2 ++ , ctx +, D2) < 0) { ... } +| +return fn2(C1, C2 ++ , ctx +, D2); +| +S = fn2(C1, C2 ++ , ctx +, D2); +) +...+> + } + +@ extends get_fn@ +identifier C1, C2, D; +attribute name UNUSED; +@@ +int fn(const char *C1, const char *C2, ++ const struct config_context *ctx UNUSED, + void *D) {...} + + +// The previous rules don't catch all callbacks, especially if they're defined +// in a separate file from the git_config() call. Fix these manually. +@@ +identifier C1, C2, D; +attribute name UNUSED; +@@ +int +( +git_ident_config +| +urlmatch_collect_fn +| +write_one_config +| +forbid_remote_url +| +credential_config_callback +) + (const char *C1, const char *C2, ++ const struct config_context *ctx UNUSED, + void *D) {...} + +@@ +identifier C1, C2, D, D2, S, fn2; +@@ +int +( +http_options +| +git_status_config +| +git_commit_config +| +git_default_core_config +| +grep_config +) + (const char *C1, const char *C2, ++ const struct config_context *ctx, + void *D) { +<+... +( +fn2(C1, C2 ++ , ctx +, D2); +| +if(fn2(C1, C2 ++ , ctx +, D2) < 0) { ... } +| +return fn2(C1, C2 ++ , ctx +, D2); +| +S = fn2(C1, C2 ++ , ctx +, D2); +) +...+> + } diff --git a/contrib/coccinelle/git_config_number.cocci b/contrib/coccinelle/git_config_number.cocci new file mode 100644 index 0000000000..7b57dceefe --- /dev/null +++ b/contrib/coccinelle/git_config_number.cocci @@ -0,0 +1,27 @@ +@@ +identifier C1, C2, C3; +@@ +( +( +git_config_int +| +git_config_int64 +| +git_config_ulong +| +git_config_ssize_t +) + (C1, C2 ++ , ctx->kvi + ) +| +( +git_configset_get_value +| +git_config_bool_or_int +) + (C1, C2 ++ , ctx->kvi + , C3 + ) +) diff --git a/contrib/coccinelle/tests/unused.c b/contrib/coccinelle/tests/unused.c deleted file mode 100644 index 8294d734ba..0000000000 --- a/contrib/coccinelle/tests/unused.c +++ /dev/null @@ -1,82 +0,0 @@ -void test_strbuf(void) -{ - struct strbuf sb1 = STRBUF_INIT; - struct strbuf sb2 = STRBUF_INIT; - struct strbuf sb3 = STRBUF_INIT; - struct strbuf sb4 = STRBUF_INIT; - struct strbuf sb5; - struct strbuf sb6 = { 0 }; - struct strbuf sb7 = STRBUF_INIT; - struct strbuf sb8 = STRBUF_INIT; - struct strbuf *sp1; - struct strbuf *sp2; - struct strbuf *sp3; - struct strbuf *sp4 = xmalloc(sizeof(struct strbuf)); - struct strbuf *sp5 = xmalloc(sizeof(struct strbuf)); - struct strbuf *sp6 = xmalloc(sizeof(struct strbuf)); - struct strbuf *sp7; - - strbuf_init(&sb5, 0); - strbuf_init(sp1, 0); - strbuf_init(sp2, 0); - strbuf_init(sp3, 0); - strbuf_init(sp4, 0); - strbuf_init(sp5, 0); - strbuf_init(sp6, 0); - strbuf_init(sp7, 0); - sp7 = xmalloc(sizeof(struct strbuf)); - - use_before(&sb3); - use_as_str("%s", sb7.buf); - use_as_str("%s", sp1->buf); - use_as_str("%s", sp6->buf); - pass_pp(&sp3); - - strbuf_release(&sb1); - strbuf_reset(&sb2); - strbuf_release(&sb3); - strbuf_release(&sb4); - strbuf_release(&sb5); - strbuf_release(&sb6); - strbuf_release(&sb7); - strbuf_release(sp1); - strbuf_release(sp2); - strbuf_release(sp3); - strbuf_release(sp4); - strbuf_release(sp5); - strbuf_release(sp6); - strbuf_release(sp7); - - use_after(&sb4); - - if (when_strict()) - return; - strbuf_release(&sb8); -} - -void test_other(void) -{ - struct string_list l = STRING_LIST_INIT_DUP; - struct strbuf sb = STRBUF_INIT; - - string_list_clear(&l, 0); - string_list_clear(&sb, 0); -} - -void test_worktrees(void) -{ - struct worktree **w1 = get_worktrees(); - struct worktree **w2 = get_worktrees(); - struct worktree **w3; - struct worktree **w4; - - w3 = get_worktrees(); - w4 = get_worktrees(); - - use_it(w4); - - free_worktrees(w1); - free_worktrees(w2); - free_worktrees(w3); - free_worktrees(w4); -} diff --git a/contrib/coccinelle/tests/unused.res b/contrib/coccinelle/tests/unused.res deleted file mode 100644 index 6d3e745683..0000000000 --- a/contrib/coccinelle/tests/unused.res +++ /dev/null @@ -1,45 +0,0 @@ -void test_strbuf(void) -{ - struct strbuf sb3 = STRBUF_INIT; - struct strbuf sb4 = STRBUF_INIT; - struct strbuf sb7 = STRBUF_INIT; - struct strbuf *sp1; - struct strbuf *sp3; - struct strbuf *sp6 = xmalloc(sizeof(struct strbuf)); - strbuf_init(sp1, 0); - strbuf_init(sp3, 0); - strbuf_init(sp6, 0); - - use_before(&sb3); - use_as_str("%s", sb7.buf); - use_as_str("%s", sp1->buf); - use_as_str("%s", sp6->buf); - pass_pp(&sp3); - - strbuf_release(&sb3); - strbuf_release(&sb4); - strbuf_release(&sb7); - strbuf_release(sp1); - strbuf_release(sp3); - strbuf_release(sp6); - - use_after(&sb4); - - if (when_strict()) - return; -} - -void test_other(void) -{ -} - -void test_worktrees(void) -{ - struct worktree **w4; - - w4 = get_worktrees(); - - use_it(w4); - - free_worktrees(w4); -} diff --git a/contrib/coccinelle/the_repository.cocci b/contrib/coccinelle/the_repository.cocci new file mode 100644 index 0000000000..765ad68967 --- /dev/null +++ b/contrib/coccinelle/the_repository.cocci @@ -0,0 +1,123 @@ +// Fully migrated "the_repository" additions +@@ +@@ +( +// cache.h +- get_oid ++ repo_get_oid +| +- get_oid_commit ++ repo_get_oid_commit +| +- get_oid_committish ++ repo_get_oid_committish +| +- get_oid_tree ++ repo_get_oid_tree +| +- get_oid_treeish ++ repo_get_oid_treeish +| +- get_oid_blob ++ repo_get_oid_blob +| +- get_oid_mb ++ repo_get_oid_mb +| +- find_unique_abbrev ++ repo_find_unique_abbrev +| +- find_unique_abbrev_r ++ repo_find_unique_abbrev_r +| +- for_each_abbrev ++ repo_for_each_abbrev +| +- interpret_branch_name ++ repo_interpret_branch_name +| +- peel_to_type ++ repo_peel_to_type +// commit-reach.h +| +- get_merge_bases ++ repo_get_merge_bases +| +- get_merge_bases_many ++ repo_get_merge_bases_many +| +- get_merge_bases_many_dirty ++ repo_get_merge_bases_many_dirty +| +- in_merge_bases ++ repo_in_merge_bases +| +- in_merge_bases_many ++ repo_in_merge_bases_many +// commit.h +| +- parse_commit_internal ++ repo_parse_commit_internal +| +- parse_commit ++ repo_parse_commit +| +- get_commit_buffer ++ repo_get_commit_buffer +| +- unuse_commit_buffer ++ repo_unuse_commit_buffer +| +- logmsg_reencode ++ repo_logmsg_reencode +| +- get_commit_tree ++ repo_get_commit_tree +// diff.h +| +- diff_setup ++ repo_diff_setup +// object-store.h +| +- read_object_file ++ repo_read_object_file +| +- has_object_file ++ repo_has_object_file +| +- has_object_file_with_flags ++ repo_has_object_file_with_flags +// pretty.h +| +- format_commit_message ++ repo_format_commit_message +// packfile.h +| +- approximate_object_count ++ repo_approximate_object_count +// promisor-remote.h +| +- promisor_remote_reinit ++ repo_promisor_remote_reinit +| +- promisor_remote_find ++ repo_promisor_remote_find +| +- has_promisor_remote ++ repo_has_promisor_remote +// refs.h +| +- dwim_ref ++ repo_dwim_ref +// rerere.h +| +- rerere ++ repo_rerere +// revision.h +| +- init_revisions ++ repo_init_revisions +) + ( ++ the_repository, + ...) diff --git a/contrib/coccinelle/the_repository.pending.cocci b/contrib/coccinelle/the_repository.pending.cocci deleted file mode 100644 index 747d382ff5..0000000000 --- a/contrib/coccinelle/the_repository.pending.cocci +++ /dev/null @@ -1,128 +0,0 @@ -// This file is used for the ongoing refactoring of -// bringing the index or repository struct in all of -// our code base. - -@@ -expression E; -expression F; -expression G; -@@ -- read_object_file( -+ repo_read_object_file(the_repository, - E, F, G) - -@@ -expression E; -@@ -- has_object_file( -+ repo_has_object_file(the_repository, - E) - -@@ -expression E; -@@ -- has_object_file_with_flags( -+ repo_has_object_file_with_flags(the_repository, - E) - -@@ -expression E; -expression F; -expression G; -@@ -- parse_commit_internal( -+ repo_parse_commit_internal(the_repository, - E, F, G) - -@@ -expression E; -expression F; -@@ -- parse_commit_gently( -+ repo_parse_commit_gently(the_repository, - E, F) - -@@ -expression E; -@@ -- parse_commit( -+ repo_parse_commit(the_repository, - E) - -@@ -expression E; -expression F; -@@ -- get_merge_bases( -+ repo_get_merge_bases(the_repository, - E, F); - -@@ -expression E; -expression F; -expression G; -@@ -- get_merge_bases_many( -+ repo_get_merge_bases_many(the_repository, - E, F, G); - -@@ -expression E; -expression F; -expression G; -@@ -- get_merge_bases_many_dirty( -+ repo_get_merge_bases_many_dirty(the_repository, - E, F, G); - -@@ -expression E; -expression F; -@@ -- in_merge_bases( -+ repo_in_merge_bases(the_repository, - E, F); - -@@ -expression E; -expression F; -expression G; -@@ -- in_merge_bases_many( -+ repo_in_merge_bases_many(the_repository, - E, F, G); - -@@ -expression E; -expression F; -@@ -- get_commit_buffer( -+ repo_get_commit_buffer(the_repository, - E, F); - -@@ -expression E; -expression F; -@@ -- unuse_commit_buffer( -+ repo_unuse_commit_buffer(the_repository, - E, F); - -@@ -expression E; -expression F; -expression G; -@@ -- logmsg_reencode( -+ repo_logmsg_reencode(the_repository, - E, F, G); - -@@ -expression E; -expression F; -expression G; -expression H; -@@ -- format_commit_message( -+ repo_format_commit_message(the_repository, - E, F, G, H); diff --git a/contrib/coccinelle/unused.cocci b/contrib/coccinelle/unused.cocci deleted file mode 100644 index d84046f82e..0000000000 --- a/contrib/coccinelle/unused.cocci +++ /dev/null @@ -1,43 +0,0 @@ -// This rule finds sequences of "unused" declerations and uses of a -// variable, where "unused" is defined to include only calling the -// equivalent of alloc, init & free functions on the variable. -@@ -type T; -identifier I; -// STRBUF_INIT, but also e.g. STRING_LIST_INIT_DUP (so no anchoring) -constant INIT_MACRO =~ "_INIT"; -identifier MALLOC1 =~ "^x?[mc]alloc$"; -identifier INIT_ASSIGN1 =~ "^get_worktrees$"; -identifier INIT_CALL1 =~ "^[a-z_]*_init$"; -identifier REL1 =~ "^[a-z_]*_(release|reset|clear|free)$"; -identifier REL2 =~ "^(release|clear|free)_[a-z_]*$"; -@@ - -( -- T I; -| -- T I = { 0 }; -| -- T I = INIT_MACRO; -| -- T I = MALLOC1(...); -| -- T I = INIT_ASSIGN1(...); -) - -<... when != \( I \| &I \) -( -- \( INIT_CALL1 \)( \( I \| &I \), ...); -| -- I = \( INIT_ASSIGN1 \)(...); -| -- I = MALLOC1(...); -) -...> - -( -- \( REL1 \| REL2 \)( \( I \| &I \), ...); -| -- \( REL1 \| REL2 \)( \( &I \| I \) ); -) - ... when != \( I \| &I \) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index b9bdfad591..745dc901fb 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -767,7 +767,7 @@ __git_refs () track="" ;; *) - for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD CHERRY_PICK_HEAD; do + for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD REBASE_HEAD CHERRY_PICK_HEAD REVERT_HEAD BISECT_HEAD AUTO_MERGE; do case "$i" in $match*|$umatch*) if [ -e "$dir/$i" ]; then @@ -1733,32 +1733,44 @@ __git_color_moved_opts="no default plain blocks zebra dimmed-zebra" __git_color_moved_ws_opts="no ignore-space-at-eol ignore-space-change ignore-all-space allow-indentation-change" +__git_ws_error_highlight_opts="context old new all default" + +# Options for the diff machinery (diff, log, show, stash, range-diff, ...) __git_diff_common_options="--stat --numstat --shortstat --summary --patch-with-stat --name-only --name-status --color --no-color --color-words --no-renames --check --color-moved --color-moved= --no-color-moved --color-moved-ws= --no-color-moved-ws --full-index --binary --abbrev --diff-filter= + --find-copies --find-object --find-renames + --no-relative --relative --find-copies-harder --ignore-cr-at-eol --text --ignore-space-at-eol --ignore-space-change --ignore-all-space --ignore-blank-lines --exit-code - --quiet --ext-diff --no-ext-diff + --quiet --ext-diff --no-ext-diff --unified= --no-prefix --src-prefix= --dst-prefix= - --inter-hunk-context= + --inter-hunk-context= --function-context --patience --histogram --minimal --raw --word-diff --word-diff-regex= --dirstat --dirstat= --dirstat-by-file --dirstat-by-file= --cumulative - --diff-algorithm= + --diff-algorithm= --default-prefix --submodule --submodule= --ignore-submodules --indent-heuristic --no-indent-heuristic - --textconv --no-textconv - --patch --no-patch - --anchored= + --textconv --no-textconv --break-rewrites + --patch --no-patch --cc --combined-all-paths + --anchored= --compact-summary --ignore-matching-lines= + --irreversible-delete --line-prefix --no-stat + --output= --output-indicator-context= + --output-indicator-new= --output-indicator-old= + --ws-error-highlight= + --pickaxe-all --pickaxe-regex " -__git_diff_difftool_options="--cached --staged --pickaxe-all --pickaxe-regex - --base --ours --theirs --no-index --relative --merge-base +# Options for diff/difftool +__git_diff_difftool_options="--cached --staged + --base --ours --theirs --no-index --merge-base + --ita-invisible-in-index --ita-visible-in-index $__git_diff_common_options" _git_diff () @@ -1782,6 +1794,10 @@ _git_diff () __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}" return ;; + --ws-error-highlight=*) + __gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}" + return + ;; --*) __gitcomp "$__git_diff_difftool_options" return @@ -2024,6 +2040,12 @@ __git_log_shortlog_options=" --author= --committer= --grep= --all-match --invert-grep " +# Options accepted by log and show +__git_log_show_options=" + --diff-merges --diff-merges= --no-diff-merges --remerge-diff +" + +__git_diff_merges_opts="off none on first-parent 1 separate m combined c dense-combined cc remerge r" __git_log_pretty_formats="oneline short medium full fuller reference email raw format: tformat: mboxrd" __git_log_date_formats="relative iso8601 iso8601-strict rfc2822 short local default human raw unix auto: format:" @@ -2072,15 +2094,24 @@ _git_log () __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}" return ;; + --ws-error-highlight=*) + __gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}" + return + ;; --no-walk=*) __gitcomp "sorted unsorted" "" "${cur##--no-walk=}" return ;; + --diff-merges=*) + __gitcomp "$__git_diff_merges_opts" "" "${cur##--diff-merges=}" + return + ;; --*) __gitcomp " $__git_log_common_options $__git_log_shortlog_options $__git_log_gitk_options + $__git_log_show_options --root --topo-order --date-order --reverse --follow --full-diff --abbrev-commit --no-abbrev-commit --abbrev= @@ -2097,7 +2128,6 @@ _git_log () --expand-tabs --expand-tabs= --no-expand-tabs $merge $__git_diff_common_options - --pickaxe-all --pickaxe-regex " return ;; @@ -2992,10 +3022,19 @@ _git_show () __gitcomp "$__git_color_moved_ws_opts" "" "${cur##--color-moved-ws=}" return ;; + --ws-error-highlight=*) + __gitcomp "$__git_ws_error_highlight_opts" "" "${cur##--ws-error-highlight=}" + return + ;; + --diff-merges=*) + __gitcomp "$__git_diff_merges_opts" "" "${cur##--diff-merges=}" + return + ;; --*) __gitcomp "--pretty= --format= --abbrev-commit --no-abbrev-commit --oneline --show-signature --expand-tabs --expand-tabs= --no-expand-tabs + $__git_log_show_options $__git_diff_common_options " return diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 57972c2845..2c030050ae 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -100,9 +100,7 @@ # # If you would like a colored hint about the current dirty state, set # GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on -# the colored output of "git status -sb" and are available only when -# using __git_ps1 for PROMPT_COMMAND or precmd in Bash, -# but always available in Zsh. +# the colored output of "git status -sb". # # If you would like __git_ps1 to do nothing in the case when the current # directory is set up to be ignored by git, then set @@ -259,12 +257,12 @@ __git_ps1_colorize_gitstring () local c_lblue='%F{blue}' local c_clear='%f' else - # Using \[ and \] around colors is necessary to prevent + # Using \001 and \002 around colors is necessary to prevent # issues with command line editing/browsing/completion! - local c_red='\[\e[31m\]' - local c_green='\[\e[32m\]' - local c_lblue='\[\e[1;34m\]' - local c_clear='\[\e[0m\]' + local c_red=$'\001\e[31m\002' + local c_green=$'\001\e[32m\002' + local c_lblue=$'\001\e[1;34m\002' + local c_clear=$'\001\e[0m\002' fi local bad_color=$c_red local ok_color=$c_green @@ -300,7 +298,7 @@ __git_ps1_colorize_gitstring () # variable, in that order. __git_eread () { - test -r "$1" && IFS=$'\r\n' read "$2" <"$1" + test -r "$1" && IFS=$'\r\n' read -r "$2" <"$1" } # see if a cherry-pick or revert is in progress, if the user has committed a @@ -574,11 +572,8 @@ __git_ps1 () b="\${__git_ps1_branch_name}" fi - # NO color option unless in PROMPT_COMMAND mode or it's Zsh if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then - if [ $pcmode = yes ] || [ -n "${ZSH_VERSION-}" ]; then - __git_ps1_colorize_gitstring - fi + __git_ps1_colorize_gitstring fi local f="$h$w$i$s$u$p" diff --git a/contrib/credential/gnome-keyring/.gitignore b/contrib/credential/gnome-keyring/.gitignore deleted file mode 100644 index 88d8fcdbce..0000000000 --- a/contrib/credential/gnome-keyring/.gitignore +++ /dev/null @@ -1 +0,0 @@ -git-credential-gnome-keyring diff --git a/contrib/credential/gnome-keyring/Makefile b/contrib/credential/gnome-keyring/Makefile deleted file mode 100644 index 22c19df94b..0000000000 --- a/contrib/credential/gnome-keyring/Makefile +++ /dev/null @@ -1,25 +0,0 @@ -MAIN:=git-credential-gnome-keyring -all:: $(MAIN) - -CC = gcc -RM = rm -f -CFLAGS = -g -O2 -Wall -PKG_CONFIG = pkg-config - --include ../../../config.mak.autogen --include ../../../config.mak - -INCS:=$(shell $(PKG_CONFIG) --cflags gnome-keyring-1 glib-2.0) -LIBS:=$(shell $(PKG_CONFIG) --libs gnome-keyring-1 glib-2.0) - -SRCS:=$(MAIN).c -OBJS:=$(SRCS:.c=.o) - -%.o: %.c - $(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $< - -$(MAIN): $(OBJS) - $(CC) -o $@ $(LDFLAGS) $^ $(LIBS) - -clean: - @$(RM) $(MAIN) $(OBJS) diff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c deleted file mode 100644 index 5927e27ae6..0000000000 --- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c +++ /dev/null @@ -1,470 +0,0 @@ -/* - * Copyright (C) 2011 John Szakmeister <john@szakmeister.net> - * 2012 Philipp A. Hartmann <pah@qo.cx> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see <http://www.gnu.org/licenses/>. - */ - -/* - * Credits: - * - GNOME Keyring API handling originally written by John Szakmeister - * - ported to credential helper API by Philipp A. Hartmann - */ - -#include <stdio.h> -#include <string.h> -#include <stdlib.h> -#include <glib.h> -#include <gnome-keyring.h> - -#ifdef GNOME_KEYRING_DEFAULT - - /* Modern gnome-keyring */ - -#include <gnome-keyring-memory.h> - -#else - - /* - * Support ancient gnome-keyring, circ. RHEL 5.X. - * GNOME_KEYRING_DEFAULT seems to have been introduced with Gnome 2.22, - * and the other features roughly around Gnome 2.20, 6 months before. - * Ubuntu 8.04 used Gnome 2.22 (I think). Not sure any distro used 2.20. - * So the existence/non-existence of GNOME_KEYRING_DEFAULT seems like - * a decent thing to use as an indicator. - */ - -#define GNOME_KEYRING_DEFAULT NULL - -/* - * ancient gnome-keyring returns DENIED when an entry is not found. - * Setting NO_MATCH to DENIED will prevent us from reporting DENIED - * errors during get and erase operations, but we will still report - * DENIED errors during a store. - */ -#define GNOME_KEYRING_RESULT_NO_MATCH GNOME_KEYRING_RESULT_DENIED - -#define gnome_keyring_memory_alloc g_malloc -#define gnome_keyring_memory_free gnome_keyring_free_password -#define gnome_keyring_memory_strdup g_strdup - -static const char *gnome_keyring_result_to_message(GnomeKeyringResult result) -{ - switch (result) { - case GNOME_KEYRING_RESULT_OK: - return "OK"; - case GNOME_KEYRING_RESULT_DENIED: - return "Denied"; - case GNOME_KEYRING_RESULT_NO_KEYRING_DAEMON: - return "No Keyring Daemon"; - case GNOME_KEYRING_RESULT_ALREADY_UNLOCKED: - return "Already UnLocked"; - case GNOME_KEYRING_RESULT_NO_SUCH_KEYRING: - return "No Such Keyring"; - case GNOME_KEYRING_RESULT_BAD_ARGUMENTS: - return "Bad Arguments"; - case GNOME_KEYRING_RESULT_IO_ERROR: - return "IO Error"; - case GNOME_KEYRING_RESULT_CANCELLED: - return "Cancelled"; - case GNOME_KEYRING_RESULT_ALREADY_EXISTS: - return "Already Exists"; - default: - return "Unknown Error"; - } -} - -/* - * Support really ancient gnome-keyring, circ. RHEL 4.X. - * Just a guess for the Glib version. Glib 2.8 was roughly Gnome 2.12 ? - * Which was released with gnome-keyring 0.4.3 ?? - */ -#if GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 8 - -static void gnome_keyring_done_cb(GnomeKeyringResult result, gpointer user_data) -{ - gpointer *data = (gpointer *)user_data; - int *done = (int *)data[0]; - GnomeKeyringResult *r = (GnomeKeyringResult *)data[1]; - - *r = result; - *done = 1; -} - -static void wait_for_request_completion(int *done) -{ - GMainContext *mc = g_main_context_default(); - while (!*done) - g_main_context_iteration(mc, TRUE); -} - -static GnomeKeyringResult gnome_keyring_item_delete_sync(const char *keyring, guint32 id) -{ - int done = 0; - GnomeKeyringResult result; - gpointer data[] = { &done, &result }; - - gnome_keyring_item_delete(keyring, id, gnome_keyring_done_cb, data, - NULL); - - wait_for_request_completion(&done); - - return result; -} - -#endif -#endif - -/* - * This credential struct and API is simplified from git's credential.{h,c} - */ -struct credential { - char *protocol; - char *host; - unsigned short port; - char *path; - char *username; - char *password; -}; - -#define CREDENTIAL_INIT { 0 } - -typedef int (*credential_op_cb)(struct credential *); - -struct credential_operation { - char *name; - credential_op_cb op; -}; - -#define CREDENTIAL_OP_END { NULL, NULL } - -/* ----------------- GNOME Keyring functions ----------------- */ - -/* create a special keyring option string, if path is given */ -static char *keyring_object(struct credential *c) -{ - if (!c->path) - return NULL; - - if (c->port) - return g_strdup_printf("%s:%hd/%s", c->host, c->port, c->path); - - return g_strdup_printf("%s/%s", c->host, c->path); -} - -static int keyring_get(struct credential *c) -{ - char *object = NULL; - GList *entries; - GnomeKeyringNetworkPasswordData *password_data; - GnomeKeyringResult result; - - if (!c->protocol || !(c->host || c->path)) - return EXIT_FAILURE; - - object = keyring_object(c); - - result = gnome_keyring_find_network_password_sync( - c->username, - NULL /* domain */, - c->host, - object, - c->protocol, - NULL /* authtype */, - c->port, - &entries); - - g_free(object); - - if (result == GNOME_KEYRING_RESULT_NO_MATCH) - return EXIT_SUCCESS; - - if (result == GNOME_KEYRING_RESULT_CANCELLED) - return EXIT_SUCCESS; - - if (result != GNOME_KEYRING_RESULT_OK) { - g_critical("%s", gnome_keyring_result_to_message(result)); - return EXIT_FAILURE; - } - - /* pick the first one from the list */ - password_data = (GnomeKeyringNetworkPasswordData *)entries->data; - - gnome_keyring_memory_free(c->password); - c->password = gnome_keyring_memory_strdup(password_data->password); - - if (!c->username) - c->username = g_strdup(password_data->user); - - gnome_keyring_network_password_list_free(entries); - - return EXIT_SUCCESS; -} - - -static int keyring_store(struct credential *c) -{ - guint32 item_id; - char *object = NULL; - GnomeKeyringResult result; - - /* - * Sanity check that what we are storing is actually sensible. - * In particular, we can't make a URL without a protocol field. - * Without either a host or pathname (depending on the scheme), - * we have no primary key. And without a username and password, - * we are not actually storing a credential. - */ - if (!c->protocol || !(c->host || c->path) || - !c->username || !c->password) - return EXIT_FAILURE; - - object = keyring_object(c); - - result = gnome_keyring_set_network_password_sync( - GNOME_KEYRING_DEFAULT, - c->username, - NULL /* domain */, - c->host, - object, - c->protocol, - NULL /* authtype */, - c->port, - c->password, - &item_id); - - g_free(object); - - if (result != GNOME_KEYRING_RESULT_OK && - result != GNOME_KEYRING_RESULT_CANCELLED) { - g_critical("%s", gnome_keyring_result_to_message(result)); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} - -static int keyring_erase(struct credential *c) -{ - char *object = NULL; - GList *entries; - GnomeKeyringNetworkPasswordData *password_data; - GnomeKeyringResult result; - - /* - * Sanity check that we actually have something to match - * against. The input we get is a restrictive pattern, - * so technically a blank credential means "erase everything". - * But it is too easy to accidentally send this, since it is equivalent - * to empty input. So explicitly disallow it, and require that the - * pattern have some actual content to match. - */ - if (!c->protocol && !c->host && !c->path && !c->username) - return EXIT_FAILURE; - - object = keyring_object(c); - - result = gnome_keyring_find_network_password_sync( - c->username, - NULL /* domain */, - c->host, - object, - c->protocol, - NULL /* authtype */, - c->port, - &entries); - - g_free(object); - - if (result == GNOME_KEYRING_RESULT_NO_MATCH) - return EXIT_SUCCESS; - - if (result == GNOME_KEYRING_RESULT_CANCELLED) - return EXIT_SUCCESS; - - if (result != GNOME_KEYRING_RESULT_OK) { - g_critical("%s", gnome_keyring_result_to_message(result)); - return EXIT_FAILURE; - } - - /* pick the first one from the list (delete all matches?) */ - password_data = (GnomeKeyringNetworkPasswordData *)entries->data; - - result = gnome_keyring_item_delete_sync( - password_data->keyring, password_data->item_id); - - gnome_keyring_network_password_list_free(entries); - - if (result != GNOME_KEYRING_RESULT_OK) { - g_critical("%s", gnome_keyring_result_to_message(result)); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} - -/* - * Table with helper operation callbacks, used by generic - * credential helper main function. - */ -static struct credential_operation const credential_helper_ops[] = { - { "get", keyring_get }, - { "store", keyring_store }, - { "erase", keyring_erase }, - CREDENTIAL_OP_END -}; - -/* ------------------ credential functions ------------------ */ - -static void credential_init(struct credential *c) -{ - memset(c, 0, sizeof(*c)); -} - -static void credential_clear(struct credential *c) -{ - g_free(c->protocol); - g_free(c->host); - g_free(c->path); - g_free(c->username); - gnome_keyring_memory_free(c->password); - - credential_init(c); -} - -static int credential_read(struct credential *c) -{ - char *buf; - size_t line_len; - char *key; - char *value; - - key = buf = gnome_keyring_memory_alloc(1024); - - while (fgets(buf, 1024, stdin)) { - line_len = strlen(buf); - - if (line_len && buf[line_len-1] == '\n') - buf[--line_len] = '\0'; - - if (!line_len) - break; - - value = strchr(buf, '='); - if (!value) { - g_warning("invalid credential line: %s", key); - gnome_keyring_memory_free(buf); - return -1; - } - *value++ = '\0'; - - if (!strcmp(key, "protocol")) { - g_free(c->protocol); - c->protocol = g_strdup(value); - } else if (!strcmp(key, "host")) { - g_free(c->host); - c->host = g_strdup(value); - value = strrchr(c->host, ':'); - if (value) { - *value++ = '\0'; - c->port = atoi(value); - } - } else if (!strcmp(key, "path")) { - g_free(c->path); - c->path = g_strdup(value); - } else if (!strcmp(key, "username")) { - g_free(c->username); - c->username = g_strdup(value); - } else if (!strcmp(key, "password")) { - gnome_keyring_memory_free(c->password); - c->password = gnome_keyring_memory_strdup(value); - while (*value) - *value++ = '\0'; - } - /* - * Ignore other lines; we don't know what they mean, but - * this future-proofs us when later versions of git do - * learn new lines, and the helpers are updated to match. - */ - } - - gnome_keyring_memory_free(buf); - - return 0; -} - -static void credential_write_item(FILE *fp, const char *key, const char *value) -{ - if (!value) - return; - fprintf(fp, "%s=%s\n", key, value); -} - -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); -} - -static void usage(const char *name) -{ - struct credential_operation const *try_op = credential_helper_ops; - const char *basename = strrchr(name, '/'); - - basename = (basename) ? basename + 1 : name; - fprintf(stderr, "usage: %s <", basename); - while (try_op->name) { - fprintf(stderr, "%s", (try_op++)->name); - if (try_op->name) - fprintf(stderr, "%s", "|"); - } - fprintf(stderr, "%s", ">\n"); -} - -int main(int argc, char *argv[]) -{ - int ret = EXIT_SUCCESS; - - struct credential_operation const *try_op = credential_helper_ops; - struct credential cred = CREDENTIAL_INIT; - - if (!argv[1]) { - usage(argv[0]); - exit(EXIT_FAILURE); - } - - g_set_application_name("Git Credential Helper"); - - /* lookup operation callback */ - while (try_op->name && strcmp(argv[1], try_op->name)) - try_op++; - - /* unsupported operation given -- ignore silently */ - if (!try_op->name || !try_op->op) - goto out; - - ret = credential_read(&cred); - if (ret) - goto out; - - /* perform credential operation */ - ret = (*try_op->op)(&cred); - - credential_write(&cred); - -out: - credential_clear(&cred); - return ret; -} diff --git a/contrib/credential/libsecret/.gitignore b/contrib/credential/libsecret/.gitignore new file mode 100644 index 0000000000..4fa22359e2 --- /dev/null +++ b/contrib/credential/libsecret/.gitignore @@ -0,0 +1 @@ +git-credential-libsecret diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c index 2c5d76d789..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,23 +307,24 @@ 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); } static int credential_read(struct credential *c) { - char *buf; - size_t line_len; + char *buf = NULL; + size_t alloc; + ssize_t line_len; char *key; char *value; - key = buf = g_malloc(1024); - - while (fgets(buf, 1024, stdin)) { - line_len = strlen(buf); + while ((line_len = getline(&buf, &alloc, stdin)) > 0) { + key = buf; - if (line_len && buf[line_len-1] == '\n') + if (buf[line_len-1] == '\n') buf[--line_len] = '\0'; if (!line_len) @@ -285,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 @@ -298,7 +376,7 @@ static int credential_read(struct credential *c) */ } - g_free(buf); + free(buf); return 0; } @@ -315,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/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index e29cc28779..5f2e5f16c8 100644 --- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -113,14 +113,16 @@ static void add_internet_password(void) static void read_credential(void) { - char buf[1024]; + char *buf = NULL; + size_t alloc; + ssize_t line_len; - while (fgets(buf, sizeof(buf), stdin)) { + while ((line_len = getline(&buf, &alloc, stdin)) > 0) { char *v; if (!strcmp(buf, "\n")) break; - buf[strlen(buf)-1] = '\0'; + buf[line_len-1] = '\0'; v = strchr(buf, '='); if (!v) @@ -165,6 +167,8 @@ static void read_credential(void) * learn new lines, and the helpers are updated to match. */ } + + free(buf); } int main(int argc, const char **argv) diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c index ead6e267c7..4cd56c42e2 100644 --- a/contrib/credential/wincred/git-credential-wincred.c +++ b/contrib/credential/wincred/git-credential-wincred.c @@ -6,6 +6,7 @@ #include <stdio.h> #include <io.h> #include <fcntl.h> +#include <wincred.h> /* common helpers */ @@ -33,65 +34,8 @@ static void *xmalloc(size_t size) return ret; } -/* MinGW doesn't have wincred.h, so we need to define stuff */ - -typedef struct _CREDENTIAL_ATTRIBUTEW { - LPWSTR Keyword; - DWORD Flags; - DWORD ValueSize; - LPBYTE Value; -} CREDENTIAL_ATTRIBUTEW, *PCREDENTIAL_ATTRIBUTEW; - -typedef struct _CREDENTIALW { - DWORD Flags; - DWORD Type; - LPWSTR TargetName; - LPWSTR Comment; - FILETIME LastWritten; - DWORD CredentialBlobSize; - LPBYTE CredentialBlob; - DWORD Persist; - DWORD AttributeCount; - PCREDENTIAL_ATTRIBUTEW Attributes; - LPWSTR TargetAlias; - LPWSTR UserName; -} CREDENTIALW, *PCREDENTIALW; - -#define CRED_TYPE_GENERIC 1 -#define CRED_PERSIST_LOCAL_MACHINE 2 -#define CRED_MAX_ATTRIBUTES 64 - -typedef BOOL (WINAPI *CredWriteWT)(PCREDENTIALW, DWORD); -typedef BOOL (WINAPI *CredEnumerateWT)(LPCWSTR, DWORD, DWORD *, - PCREDENTIALW **); -typedef VOID (WINAPI *CredFreeT)(PVOID); -typedef BOOL (WINAPI *CredDeleteWT)(LPCWSTR, DWORD, DWORD); - -static HMODULE advapi; -static CredWriteWT CredWriteW; -static CredEnumerateWT CredEnumerateW; -static CredFreeT CredFree; -static CredDeleteWT CredDeleteW; - -static void load_cred_funcs(void) -{ - /* load DLLs */ - advapi = LoadLibraryExA("advapi32.dll", NULL, - LOAD_LIBRARY_SEARCH_SYSTEM32); - if (!advapi) - die("failed to load advapi32.dll"); - - /* get function pointers */ - CredWriteW = (CredWriteWT)GetProcAddress(advapi, "CredWriteW"); - CredEnumerateW = (CredEnumerateWT)GetProcAddress(advapi, - "CredEnumerateW"); - CredFree = (CredFreeT)GetProcAddress(advapi, "CredFree"); - CredDeleteW = (CredDeleteWT)GetProcAddress(advapi, "CredDeleteW"); - if (!CredWriteW || !CredEnumerateW || !CredFree || !CredDeleteW) - die("failed to load functions"); -} - -static WCHAR *wusername, *password, *protocol, *host, *path, target[1024]; +static WCHAR *wusername, *password, *protocol, *host, *path, target[1024], + *password_expiry_utc; static void write_item(const char *what, LPCWSTR wbuf, int wlen) { @@ -165,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"")) @@ -175,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) @@ -183,18 +139,27 @@ static void get_credential(void) CREDENTIALW **creds; DWORD num_creds; int i; + CREDENTIAL_ATTRIBUTEW *attr; if (!CredEnumerateW(L"git:*", 0, &num_creds, &creds)) return; /* 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", (LPCWSTR)creds[i]->CredentialBlob, creds[i]->CredentialBlobSize / sizeof(WCHAR)); + for (int j = 0; j < creds[i]->AttributeCount; j++) { + attr = creds[i]->Attributes + j; + if (!wcscmp(attr->Keyword, L"git_password_expiry_utc")) { + write_item("password_expiry_utc", (LPCWSTR)attr->Value, + attr->ValueSize / sizeof(WCHAR)); + break; + } + } break; } @@ -204,6 +169,7 @@ static void get_credential(void) static void store_credential(void) { CREDENTIALW cred; + CREDENTIAL_ATTRIBUTEW expiry_attr; if (!wusername || !password) return; @@ -217,6 +183,14 @@ static void store_credential(void) cred.Persist = CRED_PERSIST_LOCAL_MACHINE; cred.AttributeCount = 0; cred.Attributes = NULL; + if (password_expiry_utc != NULL) { + expiry_attr.Keyword = L"git_password_expiry_utc"; + expiry_attr.Value = (LPVOID)password_expiry_utc; + expiry_attr.ValueSize = (wcslen(password_expiry_utc)) * sizeof(WCHAR); + expiry_attr.Flags = 0; + cred.Attributes = &expiry_attr; + cred.AttributeCount = 1; + } cred.TargetAlias = NULL; cred.UserName = wusername; @@ -234,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); } @@ -249,16 +223,27 @@ static WCHAR *utf8_to_utf16_dup(const char *str) return wstr; } +#define KB (1024) + static void read_credential(void) { - char buf[1024]; + size_t alloc = 100 * KB; + char *buf = calloc(alloc, sizeof(*buf)); - while (fgets(buf, sizeof(buf), stdin)) { + while (fgets(buf, alloc, stdin)) { char *v; - int len = strlen(buf); + size_t len = strlen(buf); + int ends_in_newline = 0; /* strip trailing CR / LF */ - while (len && strchr("\r\n", buf[len - 1])) + if (len && buf[len - 1] == '\n') { buf[--len] = 0; + ends_in_newline = 1; + } + if (len && buf[len - 1] == '\r') + buf[--len] = 0; + + if (!ends_in_newline) + die("bad input: %s", buf); if (!*buf) break; @@ -278,12 +263,16 @@ static void read_credential(void) wusername = utf8_to_utf16_dup(v); } else if (!strcmp(buf, "password")) password = utf8_to_utf16_dup(v); + else if (!strcmp(buf, "password_expiry_utc")) + password_expiry_utc = utf8_to_utf16_dup(v); /* * Ignore other lines; we don't know what they mean, but * this future-proofs us when later versions of git do * learn new lines, and the helpers are updated to match. */ } + + free(buf); } int main(int argc, char *argv[]) @@ -292,7 +281,7 @@ int main(int argc, char *argv[]) "usage: git credential-wincred <get|store|erase>\n"; if (!argv[1]) - die(usage); + die("%s", usage); /* git use binary pipes to avoid CRLF-issues */ _setmode(_fileno(stdin), _O_BINARY); @@ -300,8 +289,6 @@ int main(int argc, char *argv[]) read_credential(); - load_cred_funcs(); - if (!protocol || !(host || path)) return 0; diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 10c9c87839..e0c5d3b0de 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 -d 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 diff --git a/contrib/subtree/t/Makefile b/contrib/subtree/t/Makefile index 4655e0987b..093399c788 100644 --- a/contrib/subtree/t/Makefile +++ b/contrib/subtree/t/Makefile @@ -74,9 +74,7 @@ aggregate-results-and-cleanup: $(T) $(MAKE) clean aggregate-results: - for f in '$(TEST_RESULTS_DIRECTORY_SQ)'/t*-*.counts; do \ - echo "$$f"; \ - done | '$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh + @'$(SHELL_PATH_SQ)' ../../../t/aggregate-results.sh '$(TEST_RESULTS_DIRECTORY_SQ)' valgrind: $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" 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 ' # |