From 21084e84a418e0a6c4da72f632c5cd99386bd64b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 15 Oct 2018 03:14:43 -0700 Subject: [PATCH 1/6] http: add support for selecting SSL backends at runtime As of version 7.56.0, curl supports being compiled with multiple SSL backends. This patch adds the Git side of that feature: by setting http.sslBackend to "openssl" or "schannel", Git for Windows can now choose the SSL backend at runtime. This comes in handy on Windows because Secure Channel ("schannel") is the native solution, accessing the Windows Credential Store, thereby allowing for enterprise-wide management of certificates. For historical reasons, Git for Windows needs to support OpenSSL still, as it has previously been the only supported SSL backend in Git for Windows for almost a decade. The patch has been carried in Git for Windows for over a year, and is considered mature. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/config.txt | 5 +++++ http.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index eb66a11975..c569e72859 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2236,6 +2236,11 @@ http.sslCAPath:: with when fetching or pushing over HTTPS. Can be overridden by the `GIT_SSL_CAPATH` environment variable. +http.sslBackend:: + Name of the SSL backend to use (e.g. "openssl" or "schannel"). + This option is ignored if cURL lacks support for choosing the SSL + backend at runtime. + http.pinnedpubkey:: Public key of the https service. It may either be the filename of a PEM or DER encoded public key file or a string starting with diff --git a/http.c b/http.c index 4162860ee3..fedfb2a207 100644 --- a/http.c +++ b/http.c @@ -155,6 +155,8 @@ static struct active_request_slot *active_queue_head; static char *cached_accept_language; +static char *http_ssl_backend; + size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -302,6 +304,12 @@ static int http_options(const char *var, const char *value, void *cb) curl_ssl_try = git_config_bool(var, value); return 0; } + if (!strcmp("http.sslbackend", var)) { + free(http_ssl_backend); + http_ssl_backend = xstrdup_or_null(value); + return 0; + } + if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value); #ifndef USE_CURL_MULTI @@ -995,6 +1003,33 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) git_config(urlmatch_config_entry, &config); free(normalized_url); +#if LIBCURL_VERSION_NUM >= 0x073800 + if (http_ssl_backend) { + const curl_ssl_backend **backends; + struct strbuf buf = STRBUF_INIT; + int i; + + switch (curl_global_sslset(-1, http_ssl_backend, &backends)) { + case CURLSSLSET_UNKNOWN_BACKEND: + strbuf_addf(&buf, _("Unsupported SSL backend '%s'. " + "Supported SSL backends:"), + http_ssl_backend); + for (i = 0; backends[i]; i++) + strbuf_addf(&buf, "\n\t%s", backends[i]->name); + die("%s", buf.buf); + case CURLSSLSET_NO_BACKENDS: + die(_("Could not set SSL backend to '%s': " + "cURL was built without SSL backends"), + http_ssl_backend); + case CURLSSLSET_TOO_LATE: + die(_("Could not set SSL backend to '%s': already set"), + http_ssl_backend); + case CURLSSLSET_OK: + break; /* Okay! */ + } + } +#endif + if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) die("curl_global_init failed"); From 9ac8125d1ab83f1a0c48f6f30bb8fea92d9770d7 Mon Sep 17 00:00:00 2001 From: Ben Peart Date: Tue, 23 Oct 2018 15:04:21 -0400 Subject: [PATCH 2/6] reset: don't compute unstaged changes after reset when --quiet When git reset is run with the --quiet flag, don't bother finding any additional unstaged changes as they won't be output anyway. This speeds up the git reset command by avoiding having to lstat() every file looking for changes that aren't going to be reported anyway. The savings can be significant. In a repo on Windows with 200K files "git reset" drops from 7.16 seconds to 0.32 seconds for a savings of 96%. Signed-off-by: Ben Peart Signed-off-by: Junio C Hamano --- builtin/reset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/reset.c b/builtin/reset.c index 6d37a35e2e..c2f8e72cb3 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -376,7 +376,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) return 1; - if (get_git_work_tree()) + if (!quiet && get_git_work_tree()) refresh_index(&the_index, flags, NULL, NULL, _("Unstaged changes after reset:")); } else { From 4c3abd0551d8ff1c280de2bc53d6a7657b053d33 Mon Sep 17 00:00:00 2001 From: Ben Peart Date: Tue, 23 Oct 2018 15:04:22 -0400 Subject: [PATCH 3/6] reset: add new reset.quiet config setting Add a reset.quiet config setting that sets the default value of the --quiet flag when running the reset command. This enables users to change the default behavior to take advantage of the performance advantages of avoiding the scan for unstaged changes after reset. Defaults to false. Signed-off-by: Ben Peart Signed-off-by: Junio C Hamano --- Documentation/config.txt | 3 +++ Documentation/git-reset.txt | 5 ++++- builtin/reset.c | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 552827935a..fef680f886 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2753,6 +2753,9 @@ rerere.enabled:: `$GIT_DIR`, e.g. if "rerere" was previously used in the repository. +reset.quiet:: + When set to true, 'git reset' will default to the '--quiet' option. + include::sendemail-config.txt[] sequence.editor:: diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 1d697d9962..2dac95c71a 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -95,7 +95,10 @@ OPTIONS -q:: --quiet:: - Be quiet, only report errors. +--no-quiet:: + Be quiet, only report errors. The default behavior is set by the + `reset.quiet` config option. `--quiet` and `--no-quiet` will + override the default behavior. EXAMPLES diff --git a/builtin/reset.c b/builtin/reset.c index c2f8e72cb3..ff5f1756d5 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -307,6 +307,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) }; git_config(git_reset_config, NULL); + git_config_get_bool("reset.quiet", &quiet); argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); From 649bf3a42f344e71b1b5a7f562576f911a1f7423 Mon Sep 17 00:00:00 2001 From: Ben Peart Date: Tue, 23 Oct 2018 15:04:23 -0400 Subject: [PATCH 4/6] reset: warn when refresh_index() takes more than 2 seconds refresh_index() is done after a reset command as an optimization. Because it can be an expensive call, warn the user if it takes more than 2 seconds and tell them how to avoid it using the --quiet command line option or reset.quiet config setting. Signed-off-by: Ben Peart Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ advice.c | 2 ++ advice.h | 1 + builtin/reset.c | 14 +++++++++++++- 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index fef680f886..ce26a743ba 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -333,6 +333,10 @@ advice.*:: commitBeforeMerge:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwriting local changes. + resetQuiet:: + Advice to consider using the `--quiet` option to linkgit:git-reset[1] + when the command takes more than 2 seconds to enumerate unstaged + changes after reset. resolveConflict:: Advice shown by various commands when conflicts prevent the operation from being performed. diff --git a/advice.c b/advice.c index 3561cd64e9..5f35656409 100644 --- a/advice.c +++ b/advice.c @@ -12,6 +12,7 @@ int advice_push_needs_force = 1; int advice_status_hints = 1; int advice_status_u_option = 1; int advice_commit_before_merge = 1; +int advice_reset_quiet_warning = 1; int advice_resolve_conflict = 1; int advice_implicit_identity = 1; int advice_detached_head = 1; @@ -65,6 +66,7 @@ static struct { { "statusHints", &advice_status_hints }, { "statusUoption", &advice_status_u_option }, { "commitBeforeMerge", &advice_commit_before_merge }, + { "resetQuiet", &advice_reset_quiet_warning }, { "resolveConflict", &advice_resolve_conflict }, { "implicitIdentity", &advice_implicit_identity }, { "detachedHead", &advice_detached_head }, diff --git a/advice.h b/advice.h index ab24df0fd0..696bf0e7d2 100644 --- a/advice.h +++ b/advice.h @@ -12,6 +12,7 @@ extern int advice_push_needs_force; extern int advice_status_hints; extern int advice_status_u_option; extern int advice_commit_before_merge; +extern int advice_reset_quiet_warning; extern int advice_resolve_conflict; extern int advice_implicit_identity; extern int advice_detached_head; diff --git a/builtin/reset.c b/builtin/reset.c index ff5f1756d5..58166964f8 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -25,6 +25,8 @@ #include "submodule.h" #include "submodule-config.h" +#define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000) + static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] []"), N_("git reset [-q] [] [--] ..."), @@ -377,9 +379,19 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; if (read_from_tree(&pathspec, &oid, intent_to_add)) return 1; - if (!quiet && get_git_work_tree()) + if (!quiet && get_git_work_tree()) { + uint64_t t_begin, t_delta_in_ms; + + t_begin = getnanotime(); refresh_index(&the_index, flags, NULL, NULL, _("Unstaged changes after reset:")); + t_delta_in_ms = (getnanotime() - t_begin) / 1000000; + if (advice_reset_quiet_warning && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) { + printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset. You can\n" + "use '--quiet' to avoid this. Set the config setting reset.quiet to true\n" + "to make this the default.\n"), t_delta_in_ms / 1000.0); + } + } } else { int err = reset_index(&oid, reset_type, quiet); if (reset_type == KEEP && !err) From 93aef7c79beb0bde12f2633423f8edd129eed019 Mon Sep 17 00:00:00 2001 From: Brendan Forster Date: Thu, 25 Oct 2018 11:53:55 -0700 Subject: [PATCH 5/6] http: add support for disabling SSL revocation checks in cURL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for a new http.schannelCheckRevoke config value. This config value is only used if http.sslBackend is set to "schannel", which forces cURL to use the Windows Certificate Store when validating server certificates associated with a remote server. This config value should only be set to "false" if you are in an environment where revocation checks are blocked by the network, with no alternative options. This is only supported in cURL 7.44 or later. Note: originally, we wanted to call the config setting `http.schannel.checkRevoke`. This, however, does not work: the `http.*` config settings can be limited to specific URLs via `http..*` (and this feature would mistake `schannel` for a URL). Helped by Agustín Martín Barbero. Signed-off-by: Brendan Forster Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 ++++++++ http.c | 17 +++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index c569e72859..e107f4c1e2 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2241,6 +2241,14 @@ http.sslBackend:: This option is ignored if cURL lacks support for choosing the SSL backend at runtime. +http.schannelCheckRevoke:: + Used to enforce or disable certificate revocation checks in cURL + when http.sslBackend is set to "schannel". Defaults to `true` if + unset. Only necessary to disable this if Git consistently errors + and the message is about checking the revocation status of a + certificate. This option is ignored if cURL lacks support for + setting the relevant SSL option at runtime. + http.pinnedpubkey:: Public key of the https service. It may either be the filename of a PEM or DER encoded public key file or a string starting with diff --git a/http.c b/http.c index fedfb2a207..272584b16e 100644 --- a/http.c +++ b/http.c @@ -157,6 +157,8 @@ static char *cached_accept_language; static char *http_ssl_backend; +static int http_schannel_check_revoke = 1; + size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -310,6 +312,11 @@ static int http_options(const char *var, const char *value, void *cb) return 0; } + if (!strcmp("http.schannelcheckrevoke", var)) { + http_schannel_check_revoke = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value); #ifndef USE_CURL_MULTI @@ -811,6 +818,16 @@ static CURL *get_curl_handle(void) } #endif + if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && + !http_schannel_check_revoke) { +#if LIBCURL_VERSION_NUM >= 0x072c00 + curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE); +#else + warning("CURLSSLOPT_NO_REVOKE not applied to curl SSL options because\n" + "your curl version is too old (< 7.44.0)"); +#endif + } + if (http_proactive_auth) init_curl_http_auth(result); From b67d40adbbaf4f5c4898001bf062a9fd67e43368 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 25 Oct 2018 11:53:56 -0700 Subject: [PATCH 6/6] http: when using Secure Channel, ignore sslCAInfo by default As of cURL v7.60.0, the Secure Channel backend can use the certificate bundle provided via `http.sslCAInfo`, but that would override the Windows Certificate Store. Since this is not desirable by default, let's tell Git to not ask cURL to use that bundle by default when the `schannel` backend was configured via `http.sslBackend`, unless `http.schannelUseSSLCAInfo` overrides this behavior. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/config.txt | 8 ++++++++ http.c | 19 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index e107f4c1e2..56a1104469 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2249,6 +2249,14 @@ http.schannelCheckRevoke:: certificate. This option is ignored if cURL lacks support for setting the relevant SSL option at runtime. +http.schannelUseSSLCAInfo:: + As of cURL v7.60.0, the Secure Channel backend can use the + certificate bundle provided via `http.sslCAInfo`, but that would + override the Windows Certificate Store. Since this is not desirable + by default, Git will tell cURL not to use that bundle by default + when the `schannel` backend was configured via `http.sslBackend`, + unless `http.schannelUseSSLCAInfo` overrides this behavior. + http.pinnedpubkey:: Public key of the https service. It may either be the filename of a PEM or DER encoded public key file or a string starting with diff --git a/http.c b/http.c index 272584b16e..43e75ac583 100644 --- a/http.c +++ b/http.c @@ -158,6 +158,12 @@ static char *cached_accept_language; static char *http_ssl_backend; static int http_schannel_check_revoke = 1; +/* + * With the backend being set to `schannel`, setting sslCAinfo would override + * the Certificate Store in cURL v7.60.0 and later, which is not what we want + * by default. + */ +static int http_schannel_use_ssl_cainfo; size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { @@ -317,6 +323,11 @@ static int http_options(const char *var, const char *value, void *cb) return 0; } + if (!strcmp("http.schannelusesslcainfo", var)) { + http_schannel_use_ssl_cainfo = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value); #ifndef USE_CURL_MULTI @@ -869,7 +880,13 @@ static CURL *get_curl_handle(void) if (ssl_pinnedkey != NULL) curl_easy_setopt(result, CURLOPT_PINNEDPUBLICKEY, ssl_pinnedkey); #endif - if (ssl_cainfo != NULL) + if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && + !http_schannel_use_ssl_cainfo) { + curl_easy_setopt(result, CURLOPT_CAINFO, NULL); +#if LIBCURL_VERSION_NUM >= 0x073400 + curl_easy_setopt(result, CURLOPT_PROXY_CAINFO, NULL); +#endif + } else if (ssl_cainfo != NULL) curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) {