aboutsummaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/README4
-rw-r--r--contrib/buildsystems/CMakeLists.txt88
-rw-r--r--contrib/coccinelle/refs.cocci103
-rw-r--r--contrib/coccinelle/xstrncmpz.cocci28
-rw-r--r--contrib/completion/git-completion.bash514
-rw-r--r--contrib/completion/git-completion.zsh1
-rw-r--r--contrib/completion/git-prompt.sh212
-rwxr-xr-xcontrib/coverage-diff.sh9
-rw-r--r--contrib/credential/libsecret/git-credential-libsecret.c98
-rw-r--r--contrib/credential/osxkeychain/Makefile3
-rw-r--r--contrib/credential/osxkeychain/git-credential-osxkeychain.c390
-rw-r--r--contrib/credential/wincred/git-credential-wincred.c66
-rw-r--r--contrib/diff-highlight/DiffHighlight.pm2
-rwxr-xr-xcontrib/git-jump/git-jump6
-rwxr-xr-xcontrib/hg-to-git/hg-to-git.py254
-rw-r--r--contrib/hg-to-git/hg-to-git.txt21
-rw-r--r--contrib/mw-to-git/Git/Mediawiki.pm2
-rwxr-xr-xcontrib/mw-to-git/t/t9363-mw-to-git-export-import.sh2
-rwxr-xr-xcontrib/subtree/git-subtree.sh76
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh46
-rwxr-xr-xcontrib/vscode/init.sh1
-rwxr-xr-xcontrib/workdir/git-new-workdir2
22 files changed, 1361 insertions, 567 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..8c71f5a1d0 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -970,16 +970,79 @@ if(BUILD_TESTING)
add_executable(test-fake-ssh ${CMAKE_SOURCE_DIR}/t/helper/test-fake-ssh.c)
target_link_libraries(test-fake-ssh common-main)
-#reftable-tests
-parse_makefile_for_sources(test-reftable_SOURCES "REFTABLE_TEST_OBJS")
-list(TRANSFORM test-reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/")
+#unit-tests
+parse_makefile_for_sources(unit-test_SOURCES "UNIT_TEST_OBJS")
+list(TRANSFORM unit-test_SOURCES REPLACE "\\$\\(UNIT_TEST_DIR\\)/" "${CMAKE_SOURCE_DIR}/t/unit-tests/")
+add_library(unit-test-lib STATIC ${unit-test_SOURCES})
+
+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()
+
+parse_makefile_for_scripts(clar_test_SUITES "CLAR_TEST_SUITES" "")
+list(TRANSFORM clar_test_SUITES PREPEND "${CMAKE_SOURCE_DIR}/t/unit-tests/")
+list(TRANSFORM clar_test_SUITES APPEND ".c")
+add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h"
+ COMMAND ${SH_EXE} ${CMAKE_SOURCE_DIR}/t/unit-tests/generate-clar-decls.sh
+ "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h"
+ ${clar_test_SUITES}
+ DEPENDS ${CMAKE_SOURCE_DIR}/t/unit-tests/generate-clar-decls.sh
+ ${clar_test_SUITES}
+ VERBATIM)
+add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite"
+ COMMAND ${SH_EXE} "${CMAKE_SOURCE_DIR}/t/unit-tests/generate-clar-suites.sh"
+ "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h"
+ "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite"
+ DEPENDS "${CMAKE_SOURCE_DIR}/t/unit-tests/generate-clar-suites.sh"
+ "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h"
+ VERBATIM)
+
+add_library(unit-tests-lib ${clar_test_SUITES}
+ "${CMAKE_SOURCE_DIR}/t/unit-tests/clar/clar.c"
+ "${CMAKE_BINARY_DIR}/t/unit-tests/clar-decls.h"
+ "${CMAKE_BINARY_DIR}/t/unit-tests/clar.suite"
+)
+target_include_directories(unit-tests-lib PUBLIC "${CMAKE_BINARY_DIR}/t/unit-tests")
+add_executable(unit-tests "${CMAKE_SOURCE_DIR}/t/unit-tests/unit-test.c")
+target_link_libraries(unit-tests unit-tests-lib common-main)
+set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+if(MSVC)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+ set_target_properties(unit-tests
+ PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/t/unit-tests/bin)
+endif()
#test-tool
parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
+add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/")
-add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})
-target_link_libraries(test-tool common-main)
+add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES})
+target_link_libraries(test-tool test-lib common-main)
set_target_properties(test-fake-ssh test-tool
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/helper)
@@ -1028,6 +1091,7 @@ set(DIFF diff)
set(PYTHON_PATH /usr/bin/python)
set(TAR tar)
set(NO_CURL )
+set(NO_ICONV )
set(NO_EXPAT )
set(USE_LIBPCRE2 )
set(NO_PERL )
@@ -1041,6 +1105,10 @@ if(NOT CURL_FOUND)
set(NO_CURL 1)
endif()
+if(NOT Iconv_FOUND)
+ SET(NO_ICONV 1)
+endif()
+
if(NOT EXPAT_FOUND)
set(NO_EXPAT 1)
endif()
@@ -1064,6 +1132,7 @@ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "DIFF='${DIFF}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PYTHON_PATH='${PYTHON_PATH}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "TAR='${TAR}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_CURL='${NO_CURL}'\n")
+file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_ICONV='${NO_ICONV}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_EXPAT='${NO_EXPAT}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PERL='${NO_PERL}'\n")
file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PTHREADS='${NO_PTHREADS}'\n")
@@ -1093,17 +1162,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/coccinelle/refs.cocci b/contrib/coccinelle/refs.cocci
new file mode 100644
index 0000000000..31d9cad8f3
--- /dev/null
+++ b/contrib/coccinelle/refs.cocci
@@ -0,0 +1,103 @@
+// Migrate "refs.h" to not rely on `the_repository` implicitly anymore.
+@@
+@@
+(
+- resolve_ref_unsafe
++ refs_resolve_ref_unsafe
+|
+- resolve_refdup
++ refs_resolve_refdup
+|
+- read_ref_full
++ refs_read_ref_full
+|
+- read_ref
++ refs_read_ref
+|
+- ref_exists
++ refs_ref_exists
+|
+- head_ref
++ refs_head_ref
+|
+- for_each_ref
++ refs_for_each_ref
+|
+- for_each_ref_in
++ refs_for_each_ref_in
+|
+- for_each_fullref_in
++ refs_for_each_fullref_in
+|
+- for_each_tag_ref
++ refs_for_each_tag_ref
+|
+- for_each_branch_ref
++ refs_for_each_branch_ref
+|
+- for_each_remote_ref
++ refs_for_each_remote_ref
+|
+- for_each_glob_ref
++ refs_for_each_glob_ref
+|
+- for_each_glob_ref_in
++ refs_for_each_glob_ref_in
+|
+- head_ref_namespaced
++ refs_head_ref_namespaced
+|
+- for_each_namespaced_ref
++ refs_for_each_namespaced_ref
+|
+- for_each_rawref
++ refs_for_each_rawref
+|
+- safe_create_reflog
++ refs_create_reflog
+|
+- reflog_exists
++ refs_reflog_exists
+|
+- delete_ref
++ refs_delete_ref
+|
+- delete_refs
++ refs_delete_refs
+|
+- delete_reflog
++ refs_delete_reflog
+|
+- for_each_reflog_ent
++ refs_for_each_reflog_ent
+|
+- for_each_reflog_ent_reverse
++ refs_for_each_reflog_ent_reverse
+|
+- for_each_reflog
++ refs_for_each_reflog
+|
+- shorten_unambiguous_ref
++ refs_shorten_unambiguous_ref
+|
+- rename_ref
++ refs_rename_ref
+|
+- copy_existing_ref
++ refs_copy_existing_ref
+|
+- create_symref
++ refs_create_symref
+|
+- ref_transaction_begin
++ ref_store_transaction_begin
+|
+- update_ref
++ refs_update_ref
+|
+- reflog_expire
++ refs_reflog_expire
+)
+ (
++ get_main_ref_store(the_repository),
+ ...)
diff --git a/contrib/coccinelle/xstrncmpz.cocci b/contrib/coccinelle/xstrncmpz.cocci
new file mode 100644
index 0000000000..ccb39e2bc0
--- /dev/null
+++ b/contrib/coccinelle/xstrncmpz.cocci
@@ -0,0 +1,28 @@
+@@
+expression S, T, L;
+@@
+(
+- strncmp(S, T, L) || S[L]
++ !!xstrncmpz(S, T, L)
+|
+- strncmp(S, T, L) || S[L] != '\0'
++ !!xstrncmpz(S, T, L)
+|
+- strncmp(S, T, L) || T[L]
++ !!xstrncmpz(T, S, L)
+|
+- strncmp(S, T, L) || T[L] != '\0'
++ !!xstrncmpz(T, S, L)
+|
+- !strncmp(S, T, L) && !S[L]
++ !xstrncmpz(S, T, L)
+|
+- !strncmp(S, T, L) && S[L] == '\0'
++ !xstrncmpz(S, T, L)
+|
+- !strncmp(S, T, L) && !T[L]
++ !xstrncmpz(T, S, L)
+|
+- !strncmp(S, T, L) && T[L] == '\0'
++ !xstrncmpz(T, S, L)
+)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 55950057c8..3d4dff3185 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -28,17 +28,32 @@
# 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 ; ... '".
-# Be sure to add a space between the command name and the ';'.
+# 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:
+# To add completion for git subcommands that are implemented in external
+# scripts, define a function of the form '_git_${subcommand}' while replacing
+# all dashes with underscores, and the main git completion will make use of it.
+# For example, to add completion for 'git do-stuff' (which could e.g. live
+# in /usr/bin/git-do-stuff), name the completion function '_git_do_stuff'.
+# See _git_show, _git_bisect etc. below for more examples.
+#
+# If you have a shell command that is not part of git (and is not called as a
+# git subcommand), but you would still like git-style completion for it, use
+# __git_complete. For example, to use the same completion as for 'git log' also
+# for the 'gl' command:
#
# __git_complete gl git_log
#
-# Or if it's a main command (i.e. git or gitk):
+# Or if the 'gk' command should be completed the same as 'gitk':
#
# __git_complete gk gitk
#
+# The second parameter of __git_complete gives the completion function; it is
+# resolved as a function named "$2", or "__$2_main", or "_$2" in that order.
+# In the examples above, the actual functions used for completion will be
+# _git_log and __gitk_main.
+#
# Compatible with bash 3.2.57.
#
# You can set the following environment variables to influence the behavior of
@@ -121,6 +136,40 @@ __git ()
${__git_dir:+--git-dir="$__git_dir"} "$@" 2>/dev/null
}
+# Helper function to read the first line of a file into a variable.
+# __git_eread requires 2 arguments, the file path and the name of the
+# variable, in that order.
+#
+# This is taken from git-prompt.sh.
+__git_eread ()
+{
+ test -r "$1" && IFS=$'\r\n' read -r "$2" <"$1"
+}
+
+# Runs git in $__git_repo_path to determine whether a pseudoref exists.
+# 1: The pseudo-ref to search
+__git_pseudoref_exists ()
+{
+ local ref=$1
+ local head
+
+ __git_find_repo_path
+
+ # If the reftable is in use, we have to shell out to 'git rev-parse'
+ # to determine whether the ref exists instead of looking directly in
+ # the filesystem to determine whether the ref exists. Otherwise, use
+ # Bash builtins since executing Git commands are expensive on some
+ # platforms.
+ if __git_eread "$__git_repo_path/HEAD" head; then
+ if [ "$head" == "ref: refs/heads/.invalid" ]; then
+ __git show-ref --exists "$ref"
+ return $?
+ fi
+ fi
+
+ [ -f "$__git_repo_path/$ref" ]
+}
+
# Removes backslash escaping, single quotes and double quotes from a word,
# stores the result in the variable $dequoted_word.
# 1: The word to dequote.
@@ -419,16 +468,18 @@ fi
# This function is equivalent to
#
-# __gitcomp "$(git xxx --git-completion-helper) ..."
+# ___git_resolved_builtins=$(git xxx --git-completion-helper)
#
-# except that the output is cached. Accept 1-3 arguments:
+# except that the result of the execution is cached.
+#
+# Accept 1-3 arguments:
# 1: the git command to execute, this is also the cache key
+# (use "_" when the command contains spaces, e.g. "remote add"
+# becomes "remote_add")
# 2: extra options to be added on top (e.g. negative forms)
# 3: options to be excluded
-__gitcomp_builtin ()
+__git_resolve_builtins ()
{
- # spaces must be replaced with underscore for multi-word
- # commands, e.g. "git remote add" becomes remote_add.
local cmd="$1"
local incl="${2-}"
local excl="${3-}"
@@ -454,7 +505,24 @@ __gitcomp_builtin ()
eval "$var=\"$options\""
fi
- __gitcomp "$options"
+ ___git_resolved_builtins="$options"
+}
+
+# This function is equivalent to
+#
+# __gitcomp "$(git xxx --git-completion-helper) ..."
+#
+# except that the output is cached. Accept 1-3 arguments:
+# 1: the git command to execute, this is also the cache key
+# (use "_" when the command contains spaces, e.g. "remote add"
+# becomes "remote_add")
+# 2: extra options to be added on top (e.g. negative forms)
+# 3: options to be excluded
+__gitcomp_builtin ()
+{
+ __git_resolve_builtins "$1" "$2" "$3"
+
+ __gitcomp "$___git_resolved_builtins"
}
# Variation of __gitcomp_nl () that appends to the existing list of
@@ -521,6 +589,26 @@ __gitcomp_file ()
true
}
+# Find the current subcommand for commands that follow the syntax:
+#
+# git <command> <subcommand>
+#
+# 1: List of possible subcommands.
+# 2: Optional subcommand to return when none is found.
+__git_find_subcommand ()
+{
+ local subcommand subcommands="$1" default_subcommand="$2"
+
+ for subcommand in $subcommands; do
+ if [ "$subcommand" = "${words[__git_cmd_idx+1]}" ]; then
+ echo $subcommand
+ return
+ fi
+ done
+
+ echo $default_subcommand
+}
+
# Execute 'git ls-files', unless the --committable option is specified, in
# which case it runs 'git diff-index' to find out the files that can be
# committed. It return paths relative to the directory specified in the first
@@ -1183,7 +1271,7 @@ __git_aliased_command ()
:) : skip null command ;;
\'*) : skip opening quote after sh -c ;;
*)
- cur="$word"
+ cur="${word%;}"
break
esac
done
@@ -1448,12 +1536,32 @@ _git_bisect ()
{
__git_has_doubledash && return
- local subcommands="start bad good skip reset visualize replay log run"
- local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ __git_find_repo_path
+
+ # If a bisection is in progress get the terms being used.
+ local term_bad term_good
+ if [ -f "$__git_repo_path"/BISECT_TERMS ]; then
+ term_bad=$(__git bisect terms --term-bad)
+ term_good=$(__git bisect terms --term-good)
+ fi
+
+ # We will complete any custom terms, but still always complete the
+ # more usual bad/new/good/old because git bisect gives a good error
+ # message if these are given when not in use, and that's better than
+ # silent refusal to complete if the user is confused.
+ #
+ # We want to recognize 'view' but not complete it, because it overlaps
+ # with 'visualize' too much and is just an alias for it.
+ #
+ local completable_subcommands="start bad new $term_bad good old $term_good terms skip reset visualize replay log run help"
+ local all_subcommands="$completable_subcommands view"
+
+ local subcommand="$(__git_find_on_cmdline "$all_subcommands")"
+
if [ -z "$subcommand" ]; then
__git_find_repo_path
if [ -f "$__git_repo_path"/BISECT_START ]; then
- __gitcomp "$subcommands"
+ __gitcomp "$completable_subcommands"
else
__gitcomp "replay start"
fi
@@ -1461,7 +1569,26 @@ _git_bisect ()
fi
case "$subcommand" in
- bad|good|reset|skip|start)
+ start)
+ case "$cur" in
+ --*)
+ __gitcomp "--first-parent --no-checkout --term-new --term-bad --term-old --term-good"
+ return
+ ;;
+ *)
+ __git_complete_refs
+ ;;
+ esac
+ ;;
+ terms)
+ __gitcomp "--term-good --term-old --term-bad --term-new"
+ return
+ ;;
+ visualize|view)
+ __git_complete_log_opts
+ return
+ ;;
+ bad|new|"$term_bad"|good|old|"$term_good"|reset|skip)
__git_complete_refs
;;
*)
@@ -1623,8 +1750,7 @@ __git_cherry_pick_inprogress_options=$__git_sequencer_inprogress_options
_git_cherry_pick ()
{
- __git_find_repo_path
- if [ -f "$__git_repo_path"/CHERRY_PICK_HEAD ]; then
+ if __git_pseudoref_exists CHERRY_PICK_HEAD; then
__gitcomp "$__git_cherry_pick_inprogress_options"
return
fi
@@ -1678,6 +1804,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
@@ -1702,6 +1833,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
@@ -1765,7 +1900,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
--output= --output-indicator-context=
--output-indicator-new= --output-indicator-old=
--ws-error-highlight=
- --pickaxe-all --pickaxe-regex
+ --pickaxe-all --pickaxe-regex --patch-with-raw
"
# Options for diff/difftool
@@ -2029,6 +2164,16 @@ __git_log_common_options="
--min-age= --until= --before=
--min-parents= --max-parents=
--no-min-parents --no-max-parents
+ --alternate-refs --ancestry-path
+ --author-date-order --basic-regexp
+ --bisect --boundary --exclude-first-parent-only
+ --exclude-hidden --extended-regexp
+ --fixed-strings --grep-reflog
+ --ignore-missing --left-only --perl-regexp
+ --reflog --regexp-ignore-case --remove-empty
+ --right-only --show-linear-break
+ --show-notes-by-default --show-pulls
+ --since-as-filter --single-worktree
"
# Options that go well for log and gitk (not shortlog)
__git_log_gitk_options="
@@ -2043,7 +2188,8 @@ __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
+ --encoding=
"
__git_diff_merges_opts="off none on first-parent 1 separate m combined c dense-combined cc remerge r"
@@ -2051,13 +2197,15 @@ __git_diff_merges_opts="off none on first-parent 1 separate m combined c dense-c
__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:"
-_git_log ()
+# Complete porcelain (i.e. not git-rev-list) options and at least some
+# option arguments accepted by git-log. Note that this same set of options
+# are also accepted by some other git commands besides git-log.
+__git_complete_log_opts ()
{
- __git_has_doubledash && return
- __git_find_repo_path
+ COMPREPLY=()
local merge=""
- if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
+ if __git_pseudoref_exists MERGE_HEAD; then
merge="--merge"
fi
case "$prev,$cur" in
@@ -2127,6 +2275,8 @@ _git_log ()
--no-walk --no-walk= --do-walk
--parents --children
--expand-tabs --expand-tabs= --no-expand-tabs
+ --clear-decorations --decorate-refs=
+ --decorate-refs-exclude=
$merge
$__git_diff_common_options
"
@@ -2148,6 +2298,16 @@ _git_log ()
return
;;
esac
+}
+
+_git_log ()
+{
+ __git_has_doubledash && return
+ __git_find_repo_path
+
+ __git_complete_log_opts
+ [ ${#COMPREPLY[@]} -eq 0 ] || return
+
__git_complete_revlist
}
@@ -2364,13 +2524,30 @@ _git_rebase ()
_git_reflog ()
{
- local subcommands="show delete expire"
- local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ local subcommands subcommand
- if [ -z "$subcommand" ]; then
- __gitcomp "$subcommands"
- else
- __git_complete_refs
+ __git_resolve_builtins "reflog"
+
+ subcommands="$___git_resolved_builtins"
+ subcommand="$(__git_find_subcommand "$subcommands" "show")"
+
+ case "$subcommand,$cur" in
+ show,--*)
+ __gitcomp "
+ $__git_log_common_options
+ "
+ return
+ ;;
+ $subcommand,--*)
+ __gitcomp_builtin "reflog_$subcommand"
+ return
+ ;;
+ esac
+
+ __git_complete_refs
+
+ if [ $((cword - __git_cmd_idx)) -eq 1 ]; then
+ __gitcompappend "$subcommands" "" "$cur" " "
fi
}
@@ -2553,6 +2730,33 @@ __git_compute_config_vars ()
__git_config_vars="$(git help --config-for-completion)"
}
+__git_config_vars_all=
+__git_compute_config_vars_all ()
+{
+ test -n "$__git_config_vars_all" ||
+ __git_config_vars_all="$(git --no-pager help --config)"
+}
+
+__git_compute_first_level_config_vars_for_section ()
+{
+ local section="$1"
+ __git_compute_config_vars
+ local this_section="__git_first_level_config_vars_for_section_${section}"
+ test -n "${!this_section}" ||
+ printf -v "__git_first_level_config_vars_for_section_${section}" %s \
+ "$(echo "$__git_config_vars" | awk -F. "/^${section}\.[a-z]/ { print \$2 }")"
+}
+
+__git_compute_second_level_config_vars_for_section ()
+{
+ local section="$1"
+ __git_compute_config_vars_all
+ local this_section="__git_second_level_config_vars_for_section_${section}"
+ test -n "${!this_section}" ||
+ printf -v "__git_second_level_config_vars_for_section_${section}" %s \
+ "$(echo "$__git_config_vars_all" | awk -F. "/^${section}\.</ { print \$3 }")"
+}
+
__git_config_sections=
__git_compute_config_sections ()
{
@@ -2697,73 +2901,50 @@ __git_complete_config_variable_name ()
done
case "$cur_" in
- branch.*.*)
+ branch.*.*|guitool.*.*|difftool.*.*|man.*.*|mergetool.*.*|remote.*.*|submodule.*.*|url.*.*)
local pfx="${cur_%.*}."
cur_="${cur_##*.}"
- __gitcomp "remote pushRemote merge mergeOptions rebase" "$pfx" "$cur_" "$sfx"
+ local section="${pfx%.*.}"
+ __git_compute_second_level_config_vars_for_section "${section}"
+ local this_section="__git_second_level_config_vars_for_section_${section}"
+ __gitcomp "${!this_section}" "$pfx" "$cur_" "$sfx"
return
;;
branch.*)
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
+ local section="${pfx%.}"
__gitcomp_direct "$(__git_heads "$pfx" "$cur_" ".")"
- __gitcomp_nl_append $'autoSetupMerge\nautoSetupRebase\n' "$pfx" "$cur_" "${sfx- }"
- return
- ;;
- guitool.*.*)
- local pfx="${cur_%.*}."
- cur_="${cur_##*.}"
- __gitcomp "
- argPrompt cmd confirm needsFile noConsole noRescan
- prompt revPrompt revUnmerged title
- " "$pfx" "$cur_" "$sfx"
- return
- ;;
- difftool.*.*)
- local pfx="${cur_%.*}."
- cur_="${cur_##*.}"
- __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
- return
- ;;
- man.*.*)
- local pfx="${cur_%.*}."
- cur_="${cur_##*.}"
- __gitcomp "cmd path" "$pfx" "$cur_" "$sfx"
- return
- ;;
- mergetool.*.*)
- local pfx="${cur_%.*}."
- cur_="${cur_##*.}"
- __gitcomp "cmd path trustExitCode" "$pfx" "$cur_" "$sfx"
+ __git_compute_first_level_config_vars_for_section "${section}"
+ local this_section="__git_first_level_config_vars_for_section_${section}"
+ __gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
return
;;
pager.*)
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
__git_compute_all_commands
- __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx- }"
- return
- ;;
- remote.*.*)
- local pfx="${cur_%.*}."
- cur_="${cur_##*.}"
- __gitcomp "
- url proxy fetch push mirror skipDefaultUpdate
- receivepack uploadpack tagOpt pushurl
- " "$pfx" "$cur_" "$sfx"
+ __gitcomp_nl "$__git_all_commands" "$pfx" "$cur_" "${sfx:- }"
return
;;
remote.*)
local pfx="${cur_%.*}."
cur_="${cur_#*.}"
+ local section="${pfx%.}"
__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
- __gitcomp_nl_append "pushDefault" "$pfx" "$cur_" "${sfx- }"
+ __git_compute_first_level_config_vars_for_section "${section}"
+ local this_section="__git_first_level_config_vars_for_section_${section}"
+ __gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
return
;;
- url.*.*)
+ submodule.*)
local pfx="${cur_%.*}."
- cur_="${cur_##*.}"
- __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_" "$sfx"
+ cur_="${cur_#*.}"
+ local section="${pfx%.}"
+ __gitcomp_nl "$(__git config -f "$(__git rev-parse --show-toplevel)/.gitmodules" --get-regexp 'submodule.*.path' | awk -F. '{print $2}')" "$pfx" "$cur_" "."
+ __git_compute_first_level_config_vars_for_section "${section}"
+ local this_section="__git_first_level_config_vars_for_section_${section}"
+ __gitcomp_nl_append "${!this_section}" "$pfx" "$cur_" "${sfx:- }"
return
;;
*.*)
@@ -2808,22 +2989,42 @@ __git_complete_config_variable_name_and_value ()
_git_config ()
{
- case "$prev" in
- --get|--get-all|--unset|--unset-all)
- __gitcomp_nl "$(__git_config_get_set_variables)"
+ local subcommands subcommand
+
+ __git_resolve_builtins "config"
+
+ subcommands="$___git_resolved_builtins"
+ subcommand="$(__git_find_subcommand "$subcommands")"
+
+ if [ -z "$subcommand" ]
+ then
+ __gitcomp "$subcommands"
return
- ;;
- *.*)
- __git_complete_config_variable_value
+ fi
+
+ case "$cur" in
+ --*)
+ __gitcomp_builtin "config_$subcommand"
return
;;
esac
- case "$cur" in
- --*)
- __gitcomp_builtin config
+
+ case "$subcommand" in
+ get)
+ __gitcomp_nl "$(__git_config_get_set_variables)"
;;
- *)
- __git_complete_config_variable_name
+ set)
+ case "$prev" in
+ *.*)
+ __git_complete_config_variable_value
+ ;;
+ *)
+ __git_complete_config_variable_name
+ ;;
+ esac
+ ;;
+ unset)
+ __gitcomp_nl "$(__git_config_get_set_variables)"
;;
esac
}
@@ -2942,7 +3143,7 @@ _git_restore ()
__gitcomp_builtin restore
;;
*)
- if __git rev-parse --verify --quiet HEAD >/dev/null; then
+ if __git_pseudoref_exists HEAD; then
__git_complete_index_file "--modified"
fi
esac
@@ -2952,8 +3153,7 @@ __git_revert_inprogress_options=$__git_sequencer_inprogress_options
_git_revert ()
{
- __git_find_repo_path
- if [ -f "$__git_repo_path"/REVERT_HEAD ]; then
+ if __git_pseudoref_exists REVERT_HEAD; then
__gitcomp "$__git_revert_inprogress_options"
return
fi
@@ -3074,12 +3274,119 @@ __gitcomp_directories ()
COMPREPLY+=("$c/")
_found=1
fi
- done < <(git ls-tree -z -d --name-only HEAD $_tmp_dir)
+ done < <(__git ls-tree -z -d --name-only HEAD $_tmp_dir)
if [[ $_found == 0 ]] && [[ "$cur" =~ /$ ]]; then
# No possible further completions any deeper, so assume we're at
# a leaf directory and just consider it complete
__gitcomp_direct_append "$cur "
+ elif [[ $_found == 0 ]]; then
+ # No possible completions found. Avoid falling back to
+ # bash's default file and directory completion, because all
+ # valid completions have already been searched and the
+ # fallbacks can do nothing but mislead. In fact, they can
+ # mislead in three different ways:
+ # 1) Fallback file completion makes no sense when asking
+ # for directory completions, as this function does.
+ # 2) Fallback directory completion is bad because
+ # e.g. "/pro" is invalid and should NOT complete to
+ # "/proc".
+ # 3) Fallback file/directory completion only completes
+ # on paths that exist in the current working tree,
+ # i.e. which are *already* part of their
+ # sparse-checkout. Thus, normal file and directory
+ # completion is always useless for "git
+ # sparse-checkout add" and is also problematic for
+ # "git sparse-checkout set" unless using it to
+ # strictly narrow the checkout.
+ COMPREPLY=( "" )
+ fi
+}
+
+# In non-cone mode, the arguments to {set,add} are supposed to be
+# patterns, relative to the toplevel directory. These can be any kind
+# of general pattern, like 'subdir/*.c' and we can't complete on all
+# of those. However, if the user presses Tab to get tab completion, we
+# presume that they are trying to provide a pattern that names a specific
+# path.
+__gitcomp_slash_leading_paths ()
+{
+ local dequoted_word pfx="" cur_ toplevel
+
+ # Since we are dealing with a sparse-checkout, subdirectories may not
+ # exist in the local working copy. Therefore, we want to run all
+ # ls-files commands relative to the repository toplevel.
+ toplevel="$(git rev-parse --show-toplevel)/"
+
+ __git_dequote "$cur"
+
+ # If the paths provided by the user already start with '/', then
+ # they are considered relative to the toplevel of the repository
+ # already. If they do not start with /, then we need to adjust
+ # them to start with the appropriate prefix.
+ case "$cur" in
+ /*)
+ cur="${cur:1}"
+ ;;
+ *)
+ pfx="$(__git rev-parse --show-prefix)"
+ esac
+
+ # Since sparse-index is limited to cone-mode, in non-cone-mode the
+ # list of valid paths is precisely the cached files in the index.
+ #
+ # NEEDSWORK:
+ # 1) We probably need to take care of cases where ls-files
+ # responds with special quoting.
+ # 2) We probably need to take care of cases where ${cur} has
+ # some kind of special quoting.
+ # 3) On top of any quoting from 1 & 2, we have to provide an extra
+ # level of quoting for any paths that contain a '*', '?', '\',
+ # '[', ']', or leading '#' or '!' since those will be
+ # interpreted by sparse-checkout as something other than a
+ # literal path character.
+ # Since there are two types of quoting here, this might get really
+ # complex. For now, just punt on all of this...
+ completions="$(__git -C "${toplevel}" -c core.quotePath=false \
+ ls-files --cached -- "${pfx}${cur}*" \
+ | sed -e s%^%/% -e 's%$% %')"
+ # Note, above, though that we needed all of the completions to be
+ # prefixed with a '/', and we want to add a space so that bash
+ # completion will actually complete an entry and let us move on to
+ # the next one.
+
+ # Return what we've found.
+ if test -n "$completions"; then
+ # We found some completions; return them
+ local IFS=$'\n'
+ COMPREPLY=($completions)
+ else
+ # Do NOT fall back to bash-style all-local-files-and-dirs
+ # when we find no match. Such options are worse than
+ # useless:
+ # 1. "git sparse-checkout add" needs paths that are NOT
+ # currently in the working copy. "git
+ # sparse-checkout set" does as well, except in the
+ # special cases when users are only trying to narrow
+ # their sparse checkout to a subset of what they
+ # already have.
+ #
+ # 2. A path like '.config' is ambiguous as to whether
+ # the user wants all '.config' files throughout the
+ # tree, or just the one under the current directory.
+ # It would result in a warning from the
+ # sparse-checkout command due to this. As such, all
+ # completions of paths should be prefixed with a
+ # '/'.
+ #
+ # 3. We don't want paths prefixed with a '/' to
+ # complete files in the system root directory, we
+ # want it to complete on files relative to the
+ # repository root.
+ #
+ # As such, make sure that NO completions are offered rather
+ # than falling back to bash's default completions.
+ COMPREPLY=( "" )
fi
}
@@ -3087,6 +3394,7 @@ _git_sparse_checkout ()
{
local subcommands="list init set disable add reapply"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ local using_cone=true
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
return
@@ -3097,9 +3405,18 @@ _git_sparse_checkout ()
__gitcomp_builtin sparse-checkout_$subcommand "" "--"
;;
set,*|add,*)
- if [ "$(__git config core.sparseCheckoutCone)" == "true" ] ||
- [ -n "$(__git_find_on_cmdline --cone)" ]; then
+ if [[ "$(__git config core.sparseCheckout)" == "true" &&
+ "$(__git config core.sparseCheckoutCone)" == "false" &&
+ -z "$(__git_find_on_cmdline --cone)" ]]; then
+ using_cone=false
+ fi
+ if [[ -n "$(__git_find_on_cmdline --no-cone)" ]]; then
+ using_cone=false
+ fi
+ if [[ "$using_cone" == "true" ]]; then
__gitcomp_directories
+ else
+ __gitcomp_slash_leading_paths
fi
esac
}
@@ -3298,6 +3615,17 @@ _git_svn ()
fi
}
+_git_symbolic_ref () {
+ case "$cur" in
+ --*)
+ __gitcomp_builtin symbolic-ref
+ return
+ ;;
+ esac
+
+ __git_complete_refs
+}
+
_git_tag ()
{
local i c="$__git_cmd_idx" f=0
@@ -3346,7 +3674,7 @@ __git_complete_worktree_paths ()
# Generate completion reply from worktree list skipping the first
# entry: it's the path of the main worktree, which can't be moved,
# removed, locked, etc.
- __gitcomp_nl "$(git worktree list --porcelain |
+ __gitcomp_nl "$(__git worktree list --porcelain |
sed -n -e '2,$ s/^worktree //p')"
}
@@ -3370,7 +3698,7 @@ _git_worktree ()
# Here we are not completing an --option, it's either the
# path or a ref.
case "$prev" in
- -b|-B) # Complete refs for branch to be created/reseted.
+ -b|-B) # Complete refs for branch to be created/reset.
__git_complete_refs
;;
-*) # The previous word is an -o|--option without an
@@ -3582,7 +3910,7 @@ __gitk_main ()
__git_find_repo_path
local merge=""
- if [ -f "$__git_repo_path/MERGE_HEAD" ]; then
+ if __git_pseudoref_exists MERGE_HEAD; then
merge="--merge"
fi
case "$cur" in
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index cac6f61881..f5877bd7a1 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -272,6 +272,7 @@ _git ()
{
local _ret=1
local cur cword prev
+ local __git_repo_path
cur=${words[CURRENT]}
prev=${words[CURRENT-1]}
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 2c030050ae..6186c474ba 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -8,8 +8,8 @@
# To enable:
#
# 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
-# 2) Add the following line to your .bashrc/.zshrc:
-# source ~/.git-prompt.sh
+# 2) Add the following line to your .bashrc/.zshrc/.profile:
+# . ~/.git-prompt.sh # dot path/to/this-file
# 3a) Change your PS1 to call __git_ps1 as
# command-substitution:
# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
@@ -30,6 +30,8 @@
# Optionally, you can supply a third argument with a printf
# format string to finetune the output of the branch status
#
+# See notes below about compatibility with other shells.
+#
# The repository status will be displayed only if you are currently in a
# git repository. The %s token is the placeholder for the shown status.
#
@@ -106,42 +108,82 @@
# directory is set up to be ignored by git, then set
# GIT_PS1_HIDE_IF_PWD_IGNORED to a nonempty value. Override this on the
# repository level by setting bash.hideIfPwdIgnored to "false".
+#
+# Compatibility with other shells (beyond bash/zsh):
+#
+# We require posix-ish shell plus "local" support, which is most
+# shells (even pdksh), but excluding ksh93 (because no "local").
+#
+# Prompt integration might differ between shells, but the gist is
+# to load it once on shell init with '. path/to/git-prompt.sh',
+# set GIT_PS1* vars once as needed, and either place $(__git_ps1..)
+# inside PS1 once (0/1 args), or, before each prompt is displayed,
+# call __git_ps1 (2/3 args) which sets PS1 with the status embedded.
+#
+# Many shells support the 1st method of command substitution,
+# though some might need to first enable cmd substitution in PS1.
+#
+# When using colors, each escape sequence is wrapped between byte
+# values 1 and 2 (control chars SOH, STX, respectively), which are
+# invisible at the output, but for bash/readline they mark 0-width
+# strings (SGR color sequences) when calculating the on-screen
+# prompt width, to maintain correct input editing at the prompt.
+#
+# To replace or disable the 0-width markers, set GIT_PS1_COLOR_PRE
+# and GIT_PS1_COLOR_POST to other markers, or empty (nul) to not
+# use markers. For instance, some shells support '\[' and '\]' as
+# start/end markers in PS1 - when invoking __git_ps1 with 3/4 args,
+# but it may or may not work in command substitution mode. YMMV.
+#
+# If the shell doesn't support 0-width markers and editing behaves
+# incorrectly when using colors in __git_ps1, then, other than
+# disabling color, it might be solved using multi-line prompt,
+# where the git status is not at the last line, e.g.:
+# PS1='\n\w \u@\h$(__git_ps1 " (%s)")\n\$ '
# check whether printf supports -v
__git_printf_supports_v=
printf -v __git_printf_supports_v -- '%s' yes >/dev/null 2>&1
+# like __git_SOH=$'\001' etc but works also in shells without $'...'
+eval "$(printf '
+ __git_SOH="\001" __git_STX="\002" __git_ESC="\033"
+ __git_LF="\n" __git_CRLF="\r\n"
+')"
+
# stores the divergence from upstream in $p
# used by GIT_PS1_SHOWUPSTREAM
__git_ps1_show_upstream ()
{
local key value
- local svn_remote svn_url_pattern count n
+ local svn_remotes="" svn_url_pattern="" count n
local upstream_type=git legacy="" verbose="" name=""
+ local LF="$__git_LF"
- svn_remote=()
# get some config options from git-config
local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
while read -r key value; do
case "$key" in
bash.showupstream)
GIT_PS1_SHOWUPSTREAM="$value"
- if [[ -z "${GIT_PS1_SHOWUPSTREAM}" ]]; then
+ if [ -z "${GIT_PS1_SHOWUPSTREAM}" ]; then
p=""
return
fi
;;
svn-remote.*.url)
- svn_remote[$((${#svn_remote[@]} + 1))]="$value"
+ svn_remotes=${svn_remotes}${value}${LF} # URI\nURI\n...
svn_url_pattern="$svn_url_pattern\\|$value"
upstream_type=svn+git # default upstream type is SVN if available, else git
;;
esac
- done <<< "$output"
+ done <<-OUTPUT
+ $output
+ OUTPUT
# parse configuration values
local option
- for option in ${GIT_PS1_SHOWUPSTREAM}; do
+ for option in ${GIT_PS1_SHOWUPSTREAM-}; do
case "$option" in
git|svn) upstream_type="$option" ;;
verbose) verbose=1 ;;
@@ -154,33 +196,45 @@ __git_ps1_show_upstream ()
case "$upstream_type" in
git) upstream_type="@{upstream}" ;;
svn*)
- # get the upstream from the "git-svn-id: ..." in a commit message
- # (git-svn uses essentially the same procedure internally)
- local -a svn_upstream
- svn_upstream=($(git log --first-parent -1 \
- --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
- if [[ 0 -ne ${#svn_upstream[@]} ]]; then
- svn_upstream=${svn_upstream[${#svn_upstream[@]} - 2]}
- svn_upstream=${svn_upstream%@*}
- local n_stop="${#svn_remote[@]}"
- for ((n=1; n <= n_stop; n++)); do
- svn_upstream=${svn_upstream#${svn_remote[$n]}}
- done
+ # successful svn-upstream resolution:
+ # - get the list of configured svn-remotes ($svn_remotes set above)
+ # - get the last commit which seems from one of our svn-remotes
+ # - confirm that it is from one of the svn-remotes
+ # - use $GIT_SVN_ID if set, else "git-svn"
- if [[ -z "$svn_upstream" ]]; then
+ # get upstream from "git-svn-id: UPSTRM@N HASH" in a commit message
+ # (git-svn uses essentially the same procedure internally)
+ local svn_upstream="$(
+ git log --first-parent -1 \
+ --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null
+ )"
+
+ if [ -n "$svn_upstream" ]; then
+ # extract the URI, assuming --grep matched the last line
+ svn_upstream=${svn_upstream##*$LF} # last line
+ svn_upstream=${svn_upstream#*: } # UPSTRM@N HASH
+ svn_upstream=${svn_upstream%@*} # UPSTRM
+
+ case ${LF}${svn_remotes} in
+ *"${LF}${svn_upstream}${LF}"*)
+ # grep indeed matched the last line - it's our remote
# default branch name for checkouts with no layout:
upstream_type=${GIT_SVN_ID:-git-svn}
- else
+ ;;
+ *)
+ # the commit message includes one of our remotes, but
+ # it's not at the last line. is $svn_upstream junk?
upstream_type=${svn_upstream#/}
- fi
- elif [[ "svn+git" = "$upstream_type" ]]; then
+ ;;
+ esac
+ elif [ "svn+git" = "$upstream_type" ]; then
upstream_type="@{upstream}"
fi
;;
esac
# Find how many commits we are ahead/behind our upstream
- if [[ -z "$legacy" ]]; then
+ if [ -z "$legacy" ]; then
count="$(git rev-list --count --left-right \
"$upstream_type"...HEAD 2>/dev/null)"
else
@@ -192,8 +246,8 @@ __git_ps1_show_upstream ()
for commit in $commits
do
case "$commit" in
- "<"*) ((behind++)) ;;
- *) ((ahead++)) ;;
+ "<"*) behind=$((behind+1)) ;;
+ *) ahead=$((ahead+1)) ;;
esac
done
count="$behind $ahead"
@@ -203,7 +257,7 @@ __git_ps1_show_upstream ()
fi
# calculate the result
- if [[ -z "$verbose" ]]; then
+ if [ -z "$verbose" ]; then
case "$count" in
"") # no upstream
p="" ;;
@@ -229,10 +283,10 @@ __git_ps1_show_upstream ()
*) # diverged from upstream
upstream="|u+${count#* }-${count% *}" ;;
esac
- if [[ -n "$count" && -n "$name" ]]; then
+ if [ -n "$count" ] && [ -n "$name" ]; then
__git_ps1_upstream_name=$(git rev-parse \
--abbrev-ref "$upstream_type" 2>/dev/null)
- if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
+ if [ "$pcmode" = yes ] && [ "$ps1_expanded" = yes ]; then
upstream="$upstream \${__git_ps1_upstream_name}"
else
upstream="$upstream ${__git_ps1_upstream_name}"
@@ -251,25 +305,29 @@ __git_ps1_show_upstream ()
# their own color.
__git_ps1_colorize_gitstring ()
{
- if [[ -n ${ZSH_VERSION-} ]]; then
+ if [ -n "${ZSH_VERSION-}" ]; then
local c_red='%F{red}'
local c_green='%F{green}'
local c_lblue='%F{blue}'
local c_clear='%f'
else
- # Using \001 and \002 around colors is necessary to prevent
- # issues with command line editing/browsing/completion!
- 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'
+ # \001 (SOH) and \002 (STX) are 0-width substring markers
+ # which bash/readline identify while calculating the prompt
+ # on-screen width - to exclude 0-screen-width esc sequences.
+ local c_pre="${GIT_PS1_COLOR_PRE-$__git_SOH}${__git_ESC}["
+ local c_post="m${GIT_PS1_COLOR_POST-$__git_STX}"
+
+ local c_red="${c_pre}31${c_post}"
+ local c_green="${c_pre}32${c_post}"
+ local c_lblue="${c_pre}1;34${c_post}"
+ local c_clear="${c_pre}0${c_post}"
fi
- local bad_color=$c_red
- local ok_color=$c_green
+ local bad_color="$c_red"
+ local ok_color="$c_green"
local flags_color="$c_lblue"
local branch_color=""
- if [ $detached = no ]; then
+ if [ "$detached" = no ]; then
branch_color="$ok_color"
else
branch_color="$bad_color"
@@ -298,7 +356,7 @@ __git_ps1_colorize_gitstring ()
# variable, in that order.
__git_eread ()
{
- test -r "$1" && IFS=$'\r\n' read -r "$2" <"$1"
+ test -r "$1" && IFS=$__git_CRLF read -r "$2" <"$1"
}
# see if a cherry-pick or revert is in progress, if the user has committed a
@@ -346,7 +404,7 @@ __git_sequencer_status ()
__git_ps1 ()
{
# preserve exit status
- local exit=$?
+ local exit="$?"
local pcmode=no
local detached=no
local ps1pc_start='\u@\h:\w '
@@ -365,7 +423,7 @@ __git_ps1 ()
;;
0|1) printf_format="${1:-$printf_format}"
;;
- *) return $exit
+ *) return "$exit"
;;
esac
@@ -403,37 +461,40 @@ __git_ps1 ()
# incorrect.)
#
local ps1_expanded=yes
- [ -z "${ZSH_VERSION-}" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
+ [ -z "${ZSH_VERSION-}" ] || eval '[[ -o PROMPT_SUBST ]]' || ps1_expanded=no
[ -z "${BASH_VERSION-}" ] || shopt -q promptvars || ps1_expanded=no
local repo_info rev_parse_exit_code
repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
- --is-bare-repository --is-inside-work-tree \
+ --is-bare-repository --is-inside-work-tree --show-ref-format \
--short HEAD 2>/dev/null)"
rev_parse_exit_code="$?"
if [ -z "$repo_info" ]; then
- return $exit
+ return "$exit"
fi
+ local LF="$__git_LF"
local short_sha=""
if [ "$rev_parse_exit_code" = "0" ]; then
- short_sha="${repo_info##*$'\n'}"
- repo_info="${repo_info%$'\n'*}"
+ short_sha="${repo_info##*$LF}"
+ repo_info="${repo_info%$LF*}"
fi
- local inside_worktree="${repo_info##*$'\n'}"
- repo_info="${repo_info%$'\n'*}"
- local bare_repo="${repo_info##*$'\n'}"
- repo_info="${repo_info%$'\n'*}"
- local inside_gitdir="${repo_info##*$'\n'}"
- local g="${repo_info%$'\n'*}"
+ local ref_format="${repo_info##*$LF}"
+ repo_info="${repo_info%$LF*}"
+ local inside_worktree="${repo_info##*$LF}"
+ repo_info="${repo_info%$LF*}"
+ local bare_repo="${repo_info##*$LF}"
+ repo_info="${repo_info%$LF*}"
+ local inside_gitdir="${repo_info##*$LF}"
+ local g="${repo_info%$LF*}"
if [ "true" = "$inside_worktree" ] &&
[ -n "${GIT_PS1_HIDE_IF_PWD_IGNORED-}" ] &&
[ "$(git config --bool bash.hideIfPwdIgnored)" != "false" ] &&
git check-ignore -q .
then
- return $exit
+ return "$exit"
fi
local sparse=""
@@ -479,12 +540,27 @@ __git_ps1 ()
b="$(git symbolic-ref HEAD 2>/dev/null)"
else
local head=""
- if ! __git_eread "$g/HEAD" head; then
- return $exit
- fi
- # is it a symbolic ref?
- b="${head#ref: }"
- if [ "$head" = "$b" ]; then
+
+ case "$ref_format" in
+ files)
+ if ! __git_eread "$g/HEAD" head; then
+ return "$exit"
+ fi
+
+ case $head in
+ "ref: "*)
+ head="${head#ref: }"
+ ;;
+ *)
+ head=""
+ esac
+ ;;
+ *)
+ head="$(git symbolic-ref HEAD 2>/dev/null)"
+ ;;
+ esac
+
+ if test -z "$head"; then
detached=yes
b="$(
case "${GIT_PS1_DESCRIBE_STYLE-}" in
@@ -502,6 +578,8 @@ __git_ps1 ()
b="$short_sha..."
b="($b)"
+ else
+ b="$head"
fi
fi
fi
@@ -511,8 +589,8 @@ __git_ps1 ()
fi
local conflict="" # state indicator for unresolved conflicts
- if [[ "${GIT_PS1_SHOWCONFLICTSTATE}" == "yes" ]] &&
- [[ $(git ls-files --unmerged 2>/dev/null) ]]; then
+ if [ "${GIT_PS1_SHOWCONFLICTSTATE-}" = "yes" ] &&
+ [ "$(git ls-files --unmerged 2>/dev/null)" ]; then
conflict="|CONFLICT"
fi
@@ -564,10 +642,10 @@ __git_ps1 ()
fi
fi
- local z="${GIT_PS1_STATESEPARATOR-" "}"
+ local z="${GIT_PS1_STATESEPARATOR- }"
b=${b##refs/heads/}
- if [ $pcmode = yes ] && [ $ps1_expanded = yes ]; then
+ if [ "$pcmode" = yes ] && [ "$ps1_expanded" = yes ]; then
__git_ps1_branch_name=$b
b="\${__git_ps1_branch_name}"
fi
@@ -579,7 +657,7 @@ __git_ps1 ()
local f="$h$w$i$s$u$p"
local gitstring="$c$b${f:+$z$f}${sparse}$r${upstream}${conflict}"
- if [ $pcmode = yes ]; then
+ if [ "$pcmode" = yes ]; then
if [ "${__git_printf_supports_v-}" != yes ]; then
gitstring=$(printf -- "$printf_format" "$gitstring")
else
@@ -590,5 +668,5 @@ __git_ps1 ()
printf -- "$printf_format" "$gitstring"
fi
- return $exit
+ return "$exit"
}
diff --git a/contrib/coverage-diff.sh b/contrib/coverage-diff.sh
index 4ec419f900..6ce9603568 100755
--- a/contrib/coverage-diff.sh
+++ b/contrib/coverage-diff.sh
@@ -74,8 +74,7 @@ do
sort >uncovered_lines.txt
comm -12 uncovered_lines.txt new_lines.txt |
- sed -e 's/$/\)/' |
- sed -e 's/^/ /' >uncovered_new_lines.txt
+ sed -e 's/$/\)/' -e 's/^/ /' >uncovered_new_lines.txt
grep -q '[^[:space:]]' <uncovered_new_lines.txt &&
echo $file >>coverage-data.txt &&
@@ -91,11 +90,7 @@ cat coverage-data.txt
echo "Commits introducing uncovered code:"
-commit_list=$(cat coverage-data.txt |
- grep -E '^[0-9a-f]{7,} ' |
- awk '{print $1;}' |
- sort |
- uniq)
+commit_list=$(awk '/^[0-9a-f]{7,}/ { print $1 }' coverage-data.txt | sort -u)
(
for commit in $commit_list
diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
index ef681f29d5..90034d0cf1 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,30 @@ 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]);
+ } else {
+ g_free(c->password);
+ c->password = g_strdup("");
+ }
+ 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 +194,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 +209,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 +242,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 +255,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 +310,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 +358,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 +396,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/Makefile b/contrib/credential/osxkeychain/Makefile
index 4b3a08a2ba..238f5f8c36 100644
--- a/contrib/credential/osxkeychain/Makefile
+++ b/contrib/credential/osxkeychain/Makefile
@@ -8,7 +8,8 @@ CFLAGS = -g -O2 -Wall
-include ../../../config.mak
git-credential-osxkeychain: git-credential-osxkeychain.o
- $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -Wl,-framework -Wl,Security
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) \
+ -framework Security -framework CoreFoundation
git-credential-osxkeychain.o: git-credential-osxkeychain.c
$(CC) -c $(CFLAGS) $<
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
index 5f2e5f16c8..1c8310d7fe 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -3,14 +3,52 @@
#include <stdlib.h>
#include <Security/Security.h>
-static SecProtocolType protocol;
-static char *host;
-static char *path;
-static char *username;
-static char *password;
-static UInt16 port;
-
-__attribute__((format (printf, 1, 2)))
+#define ENCODING kCFStringEncodingUTF8
+static CFStringRef protocol; /* Stores constant strings - not memory managed */
+static CFStringRef host;
+static CFNumberRef port;
+static CFStringRef path;
+static CFStringRef username;
+static CFDataRef password;
+static CFDataRef password_expiry_utc;
+static CFDataRef oauth_refresh_token;
+static int state_seen;
+
+static void clear_credential(void)
+{
+ if (host) {
+ CFRelease(host);
+ host = NULL;
+ }
+ if (port) {
+ CFRelease(port);
+ port = NULL;
+ }
+ if (path) {
+ CFRelease(path);
+ path = NULL;
+ }
+ if (username) {
+ CFRelease(username);
+ username = NULL;
+ }
+ if (password) {
+ CFRelease(password);
+ password = NULL;
+ }
+ if (password_expiry_utc) {
+ CFRelease(password_expiry_utc);
+ password_expiry_utc = NULL;
+ }
+ if (oauth_refresh_token) {
+ CFRelease(oauth_refresh_token);
+ oauth_refresh_token = NULL;
+ }
+}
+
+#define STRING_WITH_LENGTH(s) s, sizeof(s) - 1
+
+__attribute__((format (printf, 1, 2), __noreturn__))
static void die(const char *err, ...)
{
char msg[4096];
@@ -19,70 +57,202 @@ static void die(const char *err, ...)
vsnprintf(msg, sizeof(msg), err, params);
fprintf(stderr, "%s\n", msg);
va_end(params);
+ clear_credential();
exit(1);
}
-static void *xstrdup(const char *s1)
+static void *xmalloc(size_t len)
{
- void *ret = strdup(s1);
+ void *ret = malloc(len);
if (!ret)
die("Out of memory");
return ret;
}
-#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
-#define KEYCHAIN_ARGS \
- NULL, /* default keychain */ \
- KEYCHAIN_ITEM(host), \
- 0, NULL, /* account domain */ \
- KEYCHAIN_ITEM(username), \
- KEYCHAIN_ITEM(path), \
- port, \
- protocol, \
- kSecAuthenticationTypeDefault
-
-static void write_item(const char *what, const char *buf, int len)
+static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...)
+{
+ va_list args;
+ const void *key;
+ CFMutableDictionaryRef result;
+
+ result = CFDictionaryCreateMutable(allocator,
+ 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+
+ va_start(args, allocator);
+ while ((key = va_arg(args, const void *)) != NULL) {
+ const void *value;
+ value = va_arg(args, const void *);
+ if (value)
+ CFDictionarySetValue(result, key, value);
+ }
+ va_end(args);
+
+ return result;
+}
+
+#define CREATE_SEC_ATTRIBUTES(...) \
+ create_dictionary(kCFAllocatorDefault, \
+ kSecClass, kSecClassInternetPassword, \
+ kSecAttrServer, host, \
+ kSecAttrAccount, username, \
+ kSecAttrPath, path, \
+ kSecAttrPort, port, \
+ kSecAttrProtocol, protocol, \
+ kSecAttrAuthenticationType, \
+ kSecAttrAuthenticationTypeDefault, \
+ __VA_ARGS__);
+
+static void write_item(const char *what, const char *buf, size_t len)
{
printf("%s=", what);
fwrite(buf, 1, len, stdout);
putchar('\n');
}
-static void find_username_in_item(SecKeychainItemRef item)
+static void find_username_in_item(CFDictionaryRef item)
{
- SecKeychainAttributeList list;
- SecKeychainAttribute attr;
+ CFStringRef account_ref;
+ char *username_buf;
+ CFIndex buffer_len;
- list.count = 1;
- list.attr = &attr;
- attr.tag = kSecAccountItemAttr;
+ account_ref = CFDictionaryGetValue(item, kSecAttrAccount);
+ if (!account_ref)
+ {
+ write_item("username", "", 0);
+ return;
+ }
- if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
+ username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING);
+ if (username_buf)
+ {
+ write_item("username", username_buf, strlen(username_buf));
return;
+ }
- write_item("username", attr.data, attr.length);
- SecKeychainItemFreeContent(&list, NULL);
+ /* If we can't get a CString pointer then
+ * we need to allocate our own buffer */
+ buffer_len = CFStringGetMaximumSizeForEncoding(
+ CFStringGetLength(account_ref), ENCODING) + 1;
+ username_buf = xmalloc(buffer_len);
+ if (CFStringGetCString(account_ref,
+ username_buf,
+ buffer_len,
+ ENCODING)) {
+ write_item("username", username_buf, strlen(username_buf));
+ }
+ free(username_buf);
}
-static void find_internet_password(void)
+static OSStatus find_internet_password(void)
{
- void *buf;
- UInt32 len;
- SecKeychainItemRef item;
+ CFDictionaryRef attrs;
+ CFDictionaryRef item;
+ CFDataRef data;
+ OSStatus result;
- if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
- return;
+ attrs = CREATE_SEC_ATTRIBUTES(kSecMatchLimit, kSecMatchLimitOne,
+ kSecReturnAttributes, kCFBooleanTrue,
+ kSecReturnData, kCFBooleanTrue,
+ NULL);
+ result = SecItemCopyMatching(attrs, (CFTypeRef *)&item);
+ if (result) {
+ goto out;
+ }
+
+ data = CFDictionaryGetValue(item, kSecValueData);
- write_item("password", buf, len);
+ write_item("password",
+ (const char *)CFDataGetBytePtr(data),
+ CFDataGetLength(data));
if (!username)
find_username_in_item(item);
- SecKeychainItemFreeContent(NULL, buf);
+ CFRelease(item);
+
+ write_item("capability[]", "state", strlen("state"));
+ write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1"));
+
+out:
+ CFRelease(attrs);
+
+ /* We consider not found to not be an error */
+ if (result == errSecItemNotFound)
+ result = errSecSuccess;
+
+ return result;
+}
+
+static OSStatus delete_ref(const void *itemRef)
+{
+ CFArrayRef item_ref_list;
+ CFDictionaryRef delete_query;
+ OSStatus result;
+
+ item_ref_list = CFArrayCreate(kCFAllocatorDefault,
+ &itemRef,
+ 1,
+ &kCFTypeArrayCallBacks);
+ delete_query = create_dictionary(kCFAllocatorDefault,
+ kSecClass, kSecClassInternetPassword,
+ kSecMatchItemList, item_ref_list,
+ NULL);
+
+ if (password) {
+ /* We only want to delete items with a matching password */
+ CFIndex capacity;
+ CFMutableDictionaryRef query;
+ CFDataRef data;
+
+ capacity = CFDictionaryGetCount(delete_query) + 1;
+ query = CFDictionaryCreateMutableCopy(kCFAllocatorDefault,
+ capacity,
+ delete_query);
+ CFDictionarySetValue(query, kSecReturnData, kCFBooleanTrue);
+ result = SecItemCopyMatching(query, (CFTypeRef *)&data);
+ if (!result) {
+ CFDataRef kc_password;
+ const UInt8 *raw_data;
+ const UInt8 *line;
+
+ /* Don't match appended metadata */
+ raw_data = CFDataGetBytePtr(data);
+ line = memchr(raw_data, '\n', CFDataGetLength(data));
+ if (line)
+ kc_password = CFDataCreateWithBytesNoCopy(
+ kCFAllocatorDefault,
+ raw_data,
+ line - raw_data,
+ kCFAllocatorNull);
+ else
+ kc_password = data;
+
+ if (CFEqual(kc_password, password))
+ result = SecItemDelete(delete_query);
+
+ if (line)
+ CFRelease(kc_password);
+ CFRelease(data);
+ }
+
+ CFRelease(query);
+ } else {
+ result = SecItemDelete(delete_query);
+ }
+
+ CFRelease(delete_query);
+ CFRelease(item_ref_list);
+
+ return result;
}
-static void delete_internet_password(void)
+static OSStatus delete_internet_password(void)
{
- SecKeychainItemRef item;
+ CFDictionaryRef attrs;
+ CFArrayRef refs;
+ OSStatus result;
/*
* Require at least a protocol and host for removal, which is what git
@@ -90,25 +260,72 @@ static void delete_internet_password(void)
* Keychain manager.
*/
if (!protocol || !host)
- return;
+ return -1;
- if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
- return;
+ attrs = CREATE_SEC_ATTRIBUTES(kSecMatchLimit, kSecMatchLimitAll,
+ kSecReturnRef, kCFBooleanTrue,
+ NULL);
+ result = SecItemCopyMatching(attrs, (CFTypeRef *)&refs);
+ CFRelease(attrs);
+
+ if (!result) {
+ for (CFIndex i = 0; !result && i < CFArrayGetCount(refs); i++)
+ result = delete_ref(CFArrayGetValueAtIndex(refs, i));
+
+ CFRelease(refs);
+ }
- SecKeychainItemDelete(item);
+ /* We consider not found to not be an error */
+ if (result == errSecItemNotFound)
+ result = errSecSuccess;
+
+ return result;
}
-static void add_internet_password(void)
+static OSStatus add_internet_password(void)
{
+ CFMutableDataRef data;
+ CFDictionaryRef attrs;
+ OSStatus result;
+
+ if (state_seen)
+ return errSecSuccess;
+
/* Only store complete credentials */
if (!protocol || !host || !username || !password)
- return;
+ return -1;
- if (SecKeychainAddInternetPassword(
- KEYCHAIN_ARGS,
- KEYCHAIN_ITEM(password),
- NULL))
- return;
+ data = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, password);
+ if (password_expiry_utc) {
+ CFDataAppendBytes(data,
+ (const UInt8 *)STRING_WITH_LENGTH("\npassword_expiry_utc="));
+ CFDataAppendBytes(data,
+ CFDataGetBytePtr(password_expiry_utc),
+ CFDataGetLength(password_expiry_utc));
+ }
+ if (oauth_refresh_token) {
+ CFDataAppendBytes(data,
+ (const UInt8 *)STRING_WITH_LENGTH("\noauth_refresh_token="));
+ CFDataAppendBytes(data,
+ CFDataGetBytePtr(oauth_refresh_token),
+ CFDataGetLength(oauth_refresh_token));
+ }
+
+ attrs = CREATE_SEC_ATTRIBUTES(kSecValueData, data,
+ NULL);
+
+ result = SecItemAdd(attrs, NULL);
+ if (result == errSecDuplicateItem) {
+ CFDictionaryRef query;
+ query = CREATE_SEC_ATTRIBUTES(NULL);
+ result = SecItemUpdate(query, attrs);
+ CFRelease(query);
+ }
+
+ CFRelease(data);
+ CFRelease(attrs);
+
+ return result;
}
static void read_credential(void)
@@ -131,36 +348,64 @@ static void read_credential(void)
if (!strcmp(buf, "protocol")) {
if (!strcmp(v, "imap"))
- protocol = kSecProtocolTypeIMAP;
+ protocol = kSecAttrProtocolIMAP;
else if (!strcmp(v, "imaps"))
- protocol = kSecProtocolTypeIMAPS;
+ protocol = kSecAttrProtocolIMAPS;
else if (!strcmp(v, "ftp"))
- protocol = kSecProtocolTypeFTP;
+ protocol = kSecAttrProtocolFTP;
else if (!strcmp(v, "ftps"))
- protocol = kSecProtocolTypeFTPS;
+ protocol = kSecAttrProtocolFTPS;
else if (!strcmp(v, "https"))
- protocol = kSecProtocolTypeHTTPS;
+ protocol = kSecAttrProtocolHTTPS;
else if (!strcmp(v, "http"))
- protocol = kSecProtocolTypeHTTP;
+ protocol = kSecAttrProtocolHTTP;
else if (!strcmp(v, "smtp"))
- protocol = kSecProtocolTypeSMTP;
- else /* we don't yet handle other protocols */
+ protocol = kSecAttrProtocolSMTP;
+ else {
+ /* we don't yet handle other protocols */
+ clear_credential();
exit(0);
+ }
}
else if (!strcmp(buf, "host")) {
char *colon = strchr(v, ':');
if (colon) {
+ UInt16 port_i;
*colon++ = '\0';
- port = atoi(colon);
+ port_i = atoi(colon);
+ port = CFNumberCreate(kCFAllocatorDefault,
+ kCFNumberShortType,
+ &port_i);
}
- host = xstrdup(v);
+ host = CFStringCreateWithCString(kCFAllocatorDefault,
+ v,
+ ENCODING);
}
else if (!strcmp(buf, "path"))
- path = xstrdup(v);
+ path = CFStringCreateWithCString(kCFAllocatorDefault,
+ v,
+ ENCODING);
else if (!strcmp(buf, "username"))
- username = xstrdup(v);
+ username = CFStringCreateWithCString(
+ kCFAllocatorDefault,
+ v,
+ ENCODING);
else if (!strcmp(buf, "password"))
- password = xstrdup(v);
+ password = CFDataCreate(kCFAllocatorDefault,
+ (UInt8 *)v,
+ strlen(v));
+ else if (!strcmp(buf, "password_expiry_utc"))
+ password_expiry_utc = CFDataCreate(kCFAllocatorDefault,
+ (UInt8 *)v,
+ strlen(v));
+ else if (!strcmp(buf, "oauth_refresh_token"))
+ oauth_refresh_token = CFDataCreate(kCFAllocatorDefault,
+ (UInt8 *)v,
+ strlen(v));
+ else if (!strcmp(buf, "state[]")) {
+ if (!strcmp(v, "osxkeychain:seen=1"))
+ state_seen = 1;
+ }
/*
* Ignore other lines; we don't know what they mean, but
* this future-proofs us when later versions of git do
@@ -173,21 +418,30 @@ static void read_credential(void)
int main(int argc, const char **argv)
{
+ OSStatus result = 0;
const char *usage =
"usage: git credential-osxkeychain <get|store|erase>";
if (!argv[1])
die("%s", usage);
+ if (open(argv[0], O_RDONLY | O_EXLOCK) == -1)
+ die("failed to lock %s", argv[0]);
+
read_credential();
if (!strcmp(argv[1], "get"))
- find_internet_password();
+ result = find_internet_password();
else if (!strcmp(argv[1], "store"))
- add_internet_password();
+ result = add_internet_password();
else if (!strcmp(argv[1], "erase"))
- delete_internet_password();
+ result = delete_internet_password();
/* otherwise, ignore unknown action */
+ if (result)
+ die("failed to %s: %d", argv[1], (int)result);
+
+ clear_credential();
+
return 0;
}
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c
index 96f10613ae..4be0d58cd8 100644
--- a/contrib/credential/wincred/git-credential-wincred.c
+++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -35,7 +35,7 @@ static void *xmalloc(size_t size)
}
static WCHAR *wusername, *password, *protocol, *host, *path, target[1024],
- *password_expiry_utc;
+ *password_expiry_utc, *oauth_refresh_token;
static void write_item(const char *what, LPCWSTR wbuf, int wlen)
{
@@ -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)
@@ -128,18 +140,38 @@ static void get_credential(void)
DWORD num_creds;
int i;
CREDENTIAL_ATTRIBUTEW *attr;
+ WCHAR *secret;
+ WCHAR *line;
+ WCHAR *remaining_lines;
+ WCHAR *part;
+ WCHAR *remaining_parts;
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));
+ if (creds[i]->CredentialBlobSize > 0) {
+ secret = xmalloc(creds[i]->CredentialBlobSize);
+ wcsncpy_s(secret, creds[i]->CredentialBlobSize, (LPCWSTR)creds[i]->CredentialBlob, creds[i]->CredentialBlobSize / sizeof(WCHAR));
+ line = wcstok_s(secret, L"\r\n", &remaining_lines);
+ write_item("password", line, line ? wcslen(line) : 0);
+ while(line != NULL) {
+ part = wcstok_s(line, L"=", &remaining_parts);
+ if (!wcscmp(part, L"oauth_refresh_token")) {
+ write_item("oauth_refresh_token", remaining_parts, remaining_parts ? wcslen(remaining_parts) : 0);
+ }
+ line = wcstok_s(NULL, L"\r\n", &remaining_lines);
+ }
+ free(secret);
+ } else {
+ 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")) {
@@ -158,16 +190,26 @@ static void store_credential(void)
{
CREDENTIALW cred;
CREDENTIAL_ATTRIBUTEW expiry_attr;
+ WCHAR *secret;
+ int wlen;
if (!wusername || !password)
return;
+ if (oauth_refresh_token) {
+ wlen = _scwprintf(L"%s\r\noauth_refresh_token=%s", password, oauth_refresh_token);
+ secret = xmalloc(sizeof(WCHAR) * wlen);
+ _snwprintf_s(secret, sizeof(WCHAR) * wlen, wlen, L"%s\r\noauth_refresh_token=%s", password, oauth_refresh_token);
+ } else {
+ secret = _wcsdup(password);
+ }
+
cred.Flags = 0;
cred.Type = CRED_TYPE_GENERIC;
cred.TargetName = target;
cred.Comment = L"saved by git-credential-wincred";
- cred.CredentialBlobSize = (wcslen(password)) * sizeof(WCHAR);
- cred.CredentialBlob = (LPVOID)password;
+ cred.CredentialBlobSize = wcslen(secret) * sizeof(WCHAR);
+ cred.CredentialBlob = (LPVOID)_wcsdup(secret);
cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
cred.AttributeCount = 0;
cred.Attributes = NULL;
@@ -182,6 +224,8 @@ static void store_credential(void)
cred.TargetAlias = NULL;
cred.UserName = wusername;
+ free(secret);
+
if (!CredWriteW(&cred, 0))
die("CredWrite failed");
}
@@ -196,7 +240,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);
}
@@ -253,6 +297,8 @@ static void read_credential(void)
password = utf8_to_utf16_dup(v);
else if (!strcmp(buf, "password_expiry_utc"))
password_expiry_utc = utf8_to_utf16_dup(v);
+ else if (!strcmp(buf, "oauth_refresh_token"))
+ oauth_refresh_token = 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
diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm
index 376f577737..636add6968 100644
--- a/contrib/diff-highlight/DiffHighlight.pm
+++ b/contrib/diff-highlight/DiffHighlight.pm
@@ -1,6 +1,6 @@
package DiffHighlight;
-use 5.008;
+use 5.008001;
use warnings FATAL => 'all';
use strict;
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
index 40c4b0d111..3f69675961 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`.
@@ -44,13 +44,13 @@ open_editor() {
mode_diff() {
git diff --no-prefix --relative "$@" |
perl -ne '
- if (m{^\+\+\+ (.*)}) { $file = $1; next }
+ if (m{^\+\+\+ (.*)}) { $file = $1 eq "/dev/null" ? undef : $1; next }
defined($file) or next;
if (m/^@@ .*?\+(\d+)/) { $line = $1; next }
defined($line) or next;
if (/^ /) { $line++; next }
if (/^[-+]\s*(.*)/) {
- print "$file:$line: $1\n";
+ print "$file:$line:1: $1\n";
$line = undef;
}
'
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
deleted file mode 100755
index 7eb1b24cc7..0000000000
--- a/contrib/hg-to-git/hg-to-git.py
+++ /dev/null
@@ -1,254 +0,0 @@
-#!/usr/bin/env python
-
-""" hg-to-git.py - A Mercurial to GIT converter
-
- Copyright (C)2007 Stelian Pop <stelian@popies.net>
-
- 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, 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/>.
-"""
-
-import os, os.path, sys
-import tempfile, pickle, getopt
-import re
-
-if sys.hexversion < 0x02030000:
- # The behavior of the pickle module changed significantly in 2.3
- sys.stderr.write("hg-to-git.py: requires Python 2.3 or later.\n")
- sys.exit(1)
-
-# Maps hg version -> git version
-hgvers = {}
-# List of children for each hg revision
-hgchildren = {}
-# List of parents for each hg revision
-hgparents = {}
-# Current branch for each hg revision
-hgbranch = {}
-# Number of new changesets converted from hg
-hgnewcsets = 0
-
-#------------------------------------------------------------------------------
-
-def usage():
-
- print("""\
-%s: [OPTIONS] <hgprj>
-
-options:
- -s, --gitstate=FILE: name of the state to be saved/read
- for incrementals
- -n, --nrepack=INT: number of changesets that will trigger
- a repack (default=0, -1 to deactivate)
- -v, --verbose: be verbose
-
-required:
- hgprj: name of the HG project to import (directory)
-""" % sys.argv[0])
-
-#------------------------------------------------------------------------------
-
-def getgitenv(user, date):
- env = ''
- elems = re.compile('(.*?)\s+<(.*)>').match(user)
- if elems:
- env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
- env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1)
- env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
- env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2)
- else:
- env += 'export GIT_AUTHOR_NAME="%s" ;' % user
- env += 'export GIT_COMMITTER_NAME="%s" ;' % user
- env += 'export GIT_AUTHOR_EMAIL= ;'
- env += 'export GIT_COMMITTER_EMAIL= ;'
-
- env += 'export GIT_AUTHOR_DATE="%s" ;' % date
- env += 'export GIT_COMMITTER_DATE="%s" ;' % date
- return env
-
-#------------------------------------------------------------------------------
-
-state = ''
-opt_nrepack = 0
-verbose = False
-
-try:
- opts, args = getopt.getopt(sys.argv[1:], 's:t:n:v', ['gitstate=', 'tempdir=', 'nrepack=', 'verbose'])
- for o, a in opts:
- if o in ('-s', '--gitstate'):
- state = a
- state = os.path.abspath(state)
- if o in ('-n', '--nrepack'):
- opt_nrepack = int(a)
- if o in ('-v', '--verbose'):
- verbose = True
- if len(args) != 1:
- raise Exception('params')
-except:
- usage()
- sys.exit(1)
-
-hgprj = args[0]
-os.chdir(hgprj)
-
-if state:
- if os.path.exists(state):
- if verbose:
- print('State does exist, reading')
- f = open(state, 'r')
- hgvers = pickle.load(f)
- else:
- print('State does not exist, first run')
-
-sock = os.popen('hg tip --template "{rev}"')
-tip = sock.read()
-if sock.close():
- sys.exit(1)
-if verbose:
- print('tip is', tip)
-
-# Calculate the branches
-if verbose:
- print('analysing the branches...')
-hgchildren["0"] = ()
-hgparents["0"] = (None, None)
-hgbranch["0"] = "master"
-for cset in range(1, int(tip) + 1):
- hgchildren[str(cset)] = ()
- prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().strip().split(' ')
- prnts = map(lambda x: x[:x.find(':')], prnts)
- if prnts[0] != '':
- parent = prnts[0].strip()
- else:
- parent = str(cset - 1)
- hgchildren[parent] += ( str(cset), )
- if len(prnts) > 1:
- mparent = prnts[1].strip()
- hgchildren[mparent] += ( str(cset), )
- else:
- mparent = None
-
- hgparents[str(cset)] = (parent, mparent)
-
- if mparent:
- # For merge changesets, take either one, preferably the 'master' branch
- if hgbranch[mparent] == 'master':
- hgbranch[str(cset)] = 'master'
- else:
- hgbranch[str(cset)] = hgbranch[parent]
- else:
- # Normal changesets
- # For first children, take the parent branch, for the others create a new branch
- if hgchildren[parent][0] == str(cset):
- hgbranch[str(cset)] = hgbranch[parent]
- else:
- hgbranch[str(cset)] = "branch-" + str(cset)
-
-if "0" not in hgvers:
- print('creating repository')
- os.system('git init')
-
-# loop through every hg changeset
-for cset in range(int(tip) + 1):
-
- # incremental, already seen
- if str(cset) in hgvers:
- continue
- hgnewcsets += 1
-
- # get info
- log_data = os.popen('hg log -r %d --template "{tags}\n{date|date}\n{author}\n"' % cset).readlines()
- tag = log_data[0].strip()
- date = log_data[1].strip()
- user = log_data[2].strip()
- parent = hgparents[str(cset)][0]
- mparent = hgparents[str(cset)][1]
-
- #get comment
- (fdcomment, filecomment) = tempfile.mkstemp()
- csetcomment = os.popen('hg log -r %d --template "{desc}"' % cset).read().strip()
- os.write(fdcomment, csetcomment)
- os.close(fdcomment)
-
- print('-----------------------------------------')
- print('cset:', cset)
- print('branch:', hgbranch[str(cset)])
- print('user:', user)
- print('date:', date)
- print('comment:', csetcomment)
- if parent:
- print('parent:', parent)
- if mparent:
- print('mparent:', mparent)
- if tag:
- print('tag:', tag)
- print('-----------------------------------------')
-
- # checkout the parent if necessary
- if cset != 0:
- if hgbranch[str(cset)] == "branch-" + str(cset):
- print('creating new branch', hgbranch[str(cset)])
- os.system('git checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
- else:
- print('checking out branch', hgbranch[str(cset)])
- os.system('git checkout %s' % hgbranch[str(cset)])
-
- # merge
- if mparent:
- if hgbranch[parent] == hgbranch[str(cset)]:
- otherbranch = hgbranch[mparent]
- else:
- otherbranch = hgbranch[parent]
- print('merging', otherbranch, 'into', hgbranch[str(cset)])
- os.system(getgitenv(user, date) + 'git merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
-
- # remove everything except .git and .hg directories
- os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
-
- # repopulate with checkouted files
- os.system('hg update -C %d' % cset)
-
- # add new files
- os.system('git ls-files -x .hg --others | git update-index --add --stdin')
- # delete removed files
- os.system('git ls-files -x .hg --deleted | git update-index --remove --stdin')
-
- # commit
- os.system(getgitenv(user, date) + 'git commit --allow-empty --allow-empty-message -a -F %s' % filecomment)
- os.unlink(filecomment)
-
- # tag
- if tag and tag != 'tip':
- os.system(getgitenv(user, date) + 'git tag %s' % tag)
-
- # delete branch if not used anymore...
- if mparent and len(hgchildren[str(cset)]):
- print("Deleting unused branch:", otherbranch)
- os.system('git branch -d %s' % otherbranch)
-
- # retrieve and record the version
- vvv = os.popen('git show --quiet --pretty=format:%H').read()
- print('record', cset, '->', vvv)
- hgvers[str(cset)] = vvv
-
-if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
- os.system('git repack -a -d')
-
-# write the state for incrementals
-if state:
- if verbose:
- print('Writing state')
- f = open(state, 'w')
- pickle.dump(hgvers, f)
-
-# vim: et ts=8 sw=4 sts=4
diff --git a/contrib/hg-to-git/hg-to-git.txt b/contrib/hg-to-git/hg-to-git.txt
deleted file mode 100644
index 91f8fe6410..0000000000
--- a/contrib/hg-to-git/hg-to-git.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-hg-to-git.py is able to convert a Mercurial repository into a git one,
-and preserves the branches in the process (unlike tailor)
-
-hg-to-git.py can probably be greatly improved (it's a rather crude
-combination of shell and python) but it does already work quite well for
-me. Features:
- - supports incremental conversion
- (for keeping a git repo in sync with a hg one)
- - supports hg branches
- - converts hg tags
-
-Note that the git repository will be created 'in place' (at the same
-location as the source hg repo). You will have to manually remove the
-'.hg' directory after the conversion.
-
-Also note that the incremental conversion uses 'simple' hg changesets
-identifiers (ordinals, as opposed to SHA-1 ids), and since these ids
-are not stable across different repositories the hg-to-git.py state file
-is forever tied to one hg repository.
-
-Stelian Pop <stelian@popies.net>
diff --git a/contrib/mw-to-git/Git/Mediawiki.pm b/contrib/mw-to-git/Git/Mediawiki.pm
index 917d9e2d32..ff7811225e 100644
--- a/contrib/mw-to-git/Git/Mediawiki.pm
+++ b/contrib/mw-to-git/Git/Mediawiki.pm
@@ -1,6 +1,6 @@
package Git::Mediawiki;
-use 5.008;
+use 5.008001;
use strict;
use POSIX;
use Git;
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..15ae86db1b 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 $?
}
@@ -778,6 +787,22 @@ ensure_valid_ref_format () {
die "fatal: '$1' does not look like a ref"
}
+# Usage: check if a commit from another subtree should be
+# ignored from processing for splits
+should_ignore_subtree_split_commit () {
+ assert test $# = 1
+ local rev="$1"
+ if test -n "$(git log -1 --grep="git-subtree-dir:" $rev)"
+ then
+ if test -z "$(git log -1 --grep="git-subtree-mainline:" $rev)" &&
+ test -z "$(git log -1 --grep="git-subtree-dir: $arg_prefix$" $rev)"
+ then
+ return 0
+ fi
+ fi
+ return 1
+}
+
# Usage: process_split_commit REV PARENTS
process_split_commit () {
assert test $# = 2
@@ -916,12 +941,12 @@ 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"
else
- die "fatal: you must provide exactly one revision, and optionnally a repository. Got: '$*'"
+ die "fatal: you must provide exactly one revision, and optionally a repository. Got: '$*'"
fi
repository=""
if test "$#" = 2
@@ -963,7 +988,19 @@ cmd_split () {
eval "$grl" |
while read rev parents
do
- process_split_commit "$rev" "$parents"
+ if should_ignore_subtree_split_commit "$rev"
+ then
+ continue
+ fi
+ parsedparents=''
+ for parent in $parents
+ do
+ if ! should_ignore_subtree_split_commit "$parent"
+ then
+ parsedparents="$parsedparents$parent "
+ fi
+ done
+ process_split_commit "$rev" "$parsedparents"
done || exit $?
latest_new=$(cache_get latest_new) || exit $?
@@ -1006,8 +1043,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..3c6103f6d2 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -47,7 +47,7 @@ last_commit_subject () {
# pre-2.32.0 versions of 'git subtree' would write the hash of the tag
# (sub1 below), instead of the commit (sub1^{commit}) in the
# "git-subtree-split" trailer.
-# We immitate this behaviour below using a replace ref.
+# We imitate this behaviour below using a replace ref.
# This function creates 3 repositories:
# - $1
# - $1-sub (added as subtree "sub" in $1)
@@ -63,7 +63,7 @@ test_create_pre2_32_repo () {
git -C "$1" log -1 --format=%B HEAD^2 >msg &&
test_commit -C "$1-sub" --annotate sub2 &&
git clone --no-local "$1" "$1-clone" &&
- new_commit=$(cat msg | sed -e "s/$commit/$tag/" | git -C "$1-clone" commit-tree HEAD^2^{tree}) &&
+ new_commit=$(sed -e "s/$commit/$tag/" msg | git -C "$1-clone" commit-tree HEAD^2^{tree}) &&
git -C "$1-clone" replace HEAD^2 $new_commit
}
@@ -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
'
#
@@ -385,6 +385,46 @@ test_expect_success 'split sub dir/ with --rejoin' '
)
'
+# Tests that commits from other subtrees are not processed as
+# part of a split.
+#
+# This test performs the following:
+# - Creates Repo with subtrees 'subA' and 'subB'
+# - Creates commits in the repo including changes to subtrees
+# - Runs the following 'split' and commit' commands in order:
+# - Perform 'split' on subtree A
+# - Perform 'split' on subtree B
+# - Create new commits with changes to subtree A and B
+# - Perform split on subtree A
+# - Check that the commits in subtree B are not processed
+# as part of the subtree A split
+test_expect_success 'split with multiple subtrees' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/subA" &&
+ subtree_test_create_repo "$test_count/subB" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/subA" subA1 &&
+ test_create_commit "$test_count/subA" subA2 &&
+ test_create_commit "$test_count/subA" subA3 &&
+ test_create_commit "$test_count/subB" subB1 &&
+ git -C "$test_count" fetch ./subA HEAD &&
+ git -C "$test_count" subtree add --prefix=subADir FETCH_HEAD &&
+ git -C "$test_count" fetch ./subB HEAD &&
+ git -C "$test_count" subtree add --prefix=subBDir FETCH_HEAD &&
+ test_create_commit "$test_count" subADir/main-subA1 &&
+ test_create_commit "$test_count" subBDir/main-subB1 &&
+ git -C "$test_count" subtree split --prefix=subADir \
+ --squash --rejoin -m "Sub A Split 1" &&
+ git -C "$test_count" subtree split --prefix=subBDir \
+ --squash --rejoin -m "Sub B Split 1" &&
+ test_create_commit "$test_count" subADir/main-subA2 &&
+ test_create_commit "$test_count" subBDir/main-subB2 &&
+ git -C "$test_count" subtree split --prefix=subADir \
+ --squash --rejoin -m "Sub A Split 2" &&
+ test "$(git -C "$test_count" subtree split --prefix=subBDir \
+ --squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = ""
+'
+
test_expect_success 'split sub dir/ with --rejoin from scratch' '
subtree_test_create_repo "$test_count" &&
test_create_commit "$test_count" main1 &&
diff --git a/contrib/vscode/init.sh b/contrib/vscode/init.sh
index 521d303722..f2d61bb0e6 100755
--- a/contrib/vscode/init.sh
+++ b/contrib/vscode/init.sh
@@ -92,7 +92,6 @@ cat >.vscode/settings.json.new <<\EOF ||
"isexe",
"iskeychar",
"kompare",
- "mksnpath",
"mktag",
"mktree",
"mmblob",
diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
index 888c34a521..989197aace 100755
--- a/contrib/workdir/git-new-workdir
+++ b/contrib/workdir/git-new-workdir
@@ -79,7 +79,7 @@ trap cleanup $siglist
# create the links to the original repo. explicitly exclude index, HEAD and
# logs/HEAD from the list since they are purely related to the current working
# directory, and should not be shared.
-for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn
+for x in config refs logs/refs objects info hooks packed-refs remotes rr-cache svn reftable
do
# create a containing directory if needed
case $x in