From 0d606d8c2a387189bd5cf453e64519c314283a96 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 9 Sep 2024 16:00:20 -0700 Subject: [PATCH 001/306] ci: remove 'Upload failed tests' directories' step from linux32 jobs Linux32 jobs seem to be getting: Error: This request has been automatically failed because it uses a deprecated version of `actions/upload-artifact: v1`. Learn more: https://github.blog/changelog/2024-02-13-deprecation-notice-v1-and-v2-of-the-artifact-actions/ before doing anything useful. For now, disable the step. Ever since actions/upload-artifact@v1 got disabled, mentioning the offending version of it seems to stop anything from happening. At least this should run the same build and test. See https://github.com/git/git/actions/runs/10780030750/job/29894867249 for example. [Backported from 90f2c7240cc (ci: remove 'Upload failed tests' directories' step from linux32 jobs, 2024-09-09).] Signed-off-by: Junio C Hamano Signed-off-by: Johannes Schindelin --- .github/workflows/main.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bd390ab5870267..8eedf35011e906 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -334,12 +334,6 @@ jobs: with: name: failed-tests-${{matrix.vector.jobname}} path: ${{env.FAILED_TEST_ARTIFACTS}} - - name: Upload failed tests' directories - if: failure() && env.FAILED_TEST_ARTIFACTS != '' && matrix.vector.jobname == 'linux32' - uses: actions/upload-artifact@v1 # cannot be upgraded because Node.js Actions aren't supported in this container - with: - name: failed-tests-${{matrix.vector.jobname}} - path: ${{env.FAILED_TEST_ARTIFACTS}} static-analysis: needs: ci-config if: needs.ci-config.outputs.enabled == 'yes' From 5d828879f320501ba8fa75099ee01dc6f4c3cf77 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 19 Jun 2024 06:09:58 +0000 Subject: [PATCH 002/306] mingw: drop bogus (and unneeded) declaration of `_pgmptr` In 08809c09aa13 (mingw: add a helper function to attach GDB to the current process, 2020-02-13), I added a declaration that was not needed. Back then, that did not matter, but now that the declaration of that symbol was changed in mingw-w64's headers, it causes the following compile error: CC compat/mingw.o compat/mingw.c: In function 'open_in_gdb': compat/mingw.c:35:9: error: function declaration isn't a prototype [-Werror=strict-prototypes] 35 | extern char *_pgmptr; | ^~~~~~ In file included from C:/git-sdk-64/usr/src/git/build-installers/mingw64/lib/gcc/x86_64-w64-mingw32/14.1.0/include/mm_malloc.h:27, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/lib/gcc/x86_64-w64-mingw32/14.1.0/include/xmmintrin.h:34, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/lib/gcc/x86_64-w64-mingw32/14.1.0/include/immintrin.h:31, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/lib/gcc/x86_64-w64-mingw32/14.1.0/include/x86intrin.h:32, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/include/winnt.h:1658, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/include/minwindef.h:163, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/include/windef.h:9, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/include/windows.h:69, from C:/git-sdk-64/usr/src/git/build-installers/mingw64/include/winsock2.h:23, from compat/../git-compat-util.h:215, from compat/mingw.c:1: compat/mingw.c:35:22: error: '__p__pgmptr' redeclared without dllimport attribute: previous dllimport ignored [-Werror=attributes] 35 | extern char *_pgmptr; | ^~~~~~~ Let's just drop the declaration and get rid of this compile error. [Backported from 3c295c87c25 (mingw: drop bogus (and unneeded) declaration of `_pgmptr`, 2024-06-19).] Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano Signed-off-by: Johannes Schindelin --- compat/mingw.c | 1 - 1 file changed, 1 deletion(-) diff --git a/compat/mingw.c b/compat/mingw.c index e433740381be9c..e08db82635932d 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -20,7 +20,6 @@ static const int delay[] = { 0, 1, 10, 20, 40 }; void open_in_gdb(void) { static struct child_process cp = CHILD_PROCESS_INIT; - extern char *_pgmptr; strvec_pushl(&cp.args, "mintty", "gdb", NULL); strvec_pushf(&cp.args, "--pid=%d", getpid()); From 7e6073d27083054773f3c3b21a608f400cf7348d Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 11 May 2024 23:25:04 -0700 Subject: [PATCH 003/306] compat/regex: fix argument order to calloc(3) Windows compiler suddenly started complaining that calloc(3) takes its arguments in order. Indeed, there are many calls that has their arguments in a _wrong_ order. Fix them all. A sample breakage can be seen at https://github.com/git/git/actions/runs/9046793153/job/24857988702#step:4:272 [Backported from f01301aabe1 (compat/regex: fix argument order to calloc(3), 2024-05-11).] Signed-off-by: Junio C Hamano Signed-off-by: Johannes Schindelin --- compat/regex/regcomp.c | 12 ++++++------ compat/regex/regex_internal.c | 4 ++-- compat/regex/regexec.c | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c index d1bc09e49b6675..2bc0f1187a18f2 100644 --- a/compat/regex/regcomp.c +++ b/compat/regex/regcomp.c @@ -868,7 +868,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len) if (table_size > pat_len) break; - dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size); + dfa->state_table = calloc (table_size, sizeof (struct re_state_table_entry)); dfa->state_hash_mask = table_size - 1; dfa->mb_cur_max = MB_CUR_MAX; @@ -936,7 +936,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len) { int i, j, ch; - dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); + dfa->sb_char = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t)); if (BE (dfa->sb_char == NULL, 0)) return REG_ESPACE; @@ -3079,9 +3079,9 @@ parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token, _NL_COLLATE_SYMB_EXTRAMB); } #endif - sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); + sbcset = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t)); #ifdef RE_ENABLE_I18N - mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); + mbcset = (re_charset_t *) calloc (1, sizeof (re_charset_t)); #endif /* RE_ENABLE_I18N */ #ifdef RE_ENABLE_I18N if (BE (sbcset == NULL || mbcset == NULL, 0)) @@ -3626,9 +3626,9 @@ build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans, re_token_t br_token; bin_tree_t *tree; - sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1); + sbcset = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t)); #ifdef RE_ENABLE_I18N - mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1); + mbcset = (re_charset_t *) calloc (1, sizeof (re_charset_t)); #endif /* RE_ENABLE_I18N */ #ifdef RE_ENABLE_I18N diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c index ec51cf34461ef2..ec5cc5d2dd10f8 100644 --- a/compat/regex/regex_internal.c +++ b/compat/regex/regex_internal.c @@ -1628,7 +1628,7 @@ create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes, reg_errcode_t err; re_dfastate_t *newstate; - newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); + newstate = (re_dfastate_t *) calloc (1, sizeof (re_dfastate_t)); if (BE (newstate == NULL, 0)) return NULL; err = re_node_set_init_copy (&newstate->nodes, nodes); @@ -1678,7 +1678,7 @@ create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes, reg_errcode_t err; re_dfastate_t *newstate; - newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1); + newstate = (re_dfastate_t *) calloc (1, sizeof (re_dfastate_t)); if (BE (newstate == NULL, 0)) return NULL; err = re_node_set_init_copy (&newstate->nodes, nodes); diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c index 49358ae475c1f7..e92be5741d14c5 100644 --- a/compat/regex/regexec.c +++ b/compat/regex/regexec.c @@ -2796,8 +2796,8 @@ get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx) continue; /* No. */ if (sub_top->path == NULL) { - sub_top->path = calloc (sizeof (state_array_t), - sl_str - sub_top->str_idx + 1); + sub_top->path = calloc (sl_str - sub_top->str_idx + 1, + sizeof (state_array_t)); if (sub_top->path == NULL) return REG_ESPACE; } @@ -3361,7 +3361,7 @@ build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) if (ndests == 0) { state->trtable = (re_dfastate_t **) - calloc (sizeof (re_dfastate_t *), SBC_MAX); + calloc (SBC_MAX, sizeof (re_dfastate_t *)); return 1; } return 0; @@ -3457,7 +3457,7 @@ build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) discern by looking at the character code: allocate a 256-entry transition table. */ trtable = state->trtable = - (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX); + (re_dfastate_t **) calloc (SBC_MAX, sizeof (re_dfastate_t *)); if (BE (trtable == NULL, 0)) goto out_free; @@ -3488,7 +3488,7 @@ build_trtable (const re_dfa_t *dfa, re_dfastate_t *state) transition tables, one starting at trtable[0] and one starting at trtable[SBC_MAX]. */ trtable = state->word_trtable = - (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX); + (re_dfastate_t **) calloc (2 * SBC_MAX, sizeof (re_dfastate_t *)); if (BE (trtable == NULL, 0)) goto out_free; From 83b08eb19f05710a92d565124606dcaf68bcc68b Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Wed, 9 Oct 2024 15:25:18 +0200 Subject: [PATCH 004/306] t7300: work around platform-specific behaviour with long paths on MinGW Windows by default has a restriction in place to only allow paths up to 260 characters. This restriction can nowadays be lifted by setting a registry key, but is still active by default. In t7300 we have one test that exercises the behaviour of git-clean(1) with such long paths. Interestingly enough, this test fails on my system that uses Windows 10 with mingw-w64 installed via MSYS2: instead of observing ENAMETOOLONG, we observe ENOENT. This behaviour is consistent across multiple different environments I have tried. I cannot say why exactly we observe a different error here, but I would not be surprised if this was either dependent on the Windows version, the version of MinGW, the current working directory of Git or any kind of combination of these. Work around the issue by handling both errors. [Backported from 106834e34a2 (t7300: work around platform-specific behaviour with long paths on MinGW, 2024-10-09).] Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano Signed-off-by: Johannes Schindelin --- t/t7300-clean.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index c975eb54d234d7..a831438f74221b 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -735,7 +735,7 @@ test_expect_success MINGW 'handle clean & core.longpaths = false nicely' ' test_must_fail git clean -xdf 2>.git/err && # grepping for a strerror string is unportable but it is OK here with # MINGW prereq - test_i18ngrep "too long" .git/err + test_i18ngrep -e "too long" -e "No such file or directory" .git/err ' test_expect_success 'clean untracked paths by pathspec' ' From c903985bf7e772e2d08275c1a95c8a55ab011577 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 7 Nov 2024 08:57:52 +0100 Subject: [PATCH 005/306] credential_format(): also encode [:] An upcoming change wants to sanitize the credential password prompt where a URL is displayed that may potentially come from a `.gitmodules` file. To this end, the `credential_format()` function is employed. To sanitize the host name (and optional port) part of the URL, we need a new mode of the `strbuf_add_percentencode()` function because the current mode is both too strict and too lenient: too strict because it encodes `:`, `[` and `]` (which should be left unencoded in `:` and in IPv6 addresses), and too lenient because it does not encode invalid host name characters `/`, `_` and `~`. So let's introduce and use a new mode specifically to encode the host name and optional port part of a URI, leaving alpha-numerical characters, periods, colons and brackets alone and encoding all others. This only leads to a change of behavior for URLs that contain invalid host names. Signed-off-by: Johannes Schindelin --- credential.c | 3 ++- strbuf.c | 4 +++- strbuf.h | 1 + t/t0300-credentials.sh | 13 +++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/credential.c b/credential.c index f32011343f9400..572f1785da7d3e 100644 --- a/credential.c +++ b/credential.c @@ -164,7 +164,8 @@ static void credential_format(struct credential *c, struct strbuf *out) strbuf_addch(out, '@'); } if (c->host) - strbuf_addstr(out, c->host); + strbuf_add_percentencode(out, c->host, + STRBUF_ENCODE_HOST_AND_PORT); if (c->path) { strbuf_addch(out, '/'); strbuf_add_percentencode(out, c->path, 0); diff --git a/strbuf.c b/strbuf.c index c383f41a3c5ccc..756b96c56157c3 100644 --- a/strbuf.c +++ b/strbuf.c @@ -492,7 +492,9 @@ void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags) unsigned char ch = src[i]; if (ch <= 0x1F || ch >= 0x7F || (ch == '/' && (flags & STRBUF_ENCODE_SLASH)) || - strchr(URL_UNSAFE_CHARS, ch)) + ((flags & STRBUF_ENCODE_HOST_AND_PORT) ? + !isalnum(ch) && !strchr("-.:[]", ch) : + !!strchr(URL_UNSAFE_CHARS, ch))) strbuf_addf(dst, "%%%02X", (unsigned char)ch); else strbuf_addch(dst, ch); diff --git a/strbuf.h b/strbuf.h index f6dbb9681ee768..f9f8bb0381b3c5 100644 --- a/strbuf.h +++ b/strbuf.h @@ -380,6 +380,7 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); #define STRBUF_ENCODE_SLASH 1 +#define STRBUF_ENCODE_HOST_AND_PORT 2 /** * Append the contents of a string to a strbuf, percent-encoding any characters diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index c66d91e82d8bc7..cb91be1427f1d2 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -514,6 +514,19 @@ test_expect_success 'match percent-encoded values in username' ' EOF ' +test_expect_success 'match percent-encoded values in hostname' ' + test_config "credential.https://a%20b%20c/.helper" "$HELPER" && + check fill <<-\EOF + url=https://a b c/ + -- + protocol=https + host=a b c + username=foo + password=bar + -- + EOF +' + test_expect_success 'fetch with multiple path components' ' test_unconfig credential.helper && test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" && From 5b257412e25ad29410c389300324886aa59e1f83 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 6 Nov 2024 20:34:50 +0100 Subject: [PATCH 006/306] sideband: mask control characters The output of `git clone` is a vital component for understanding what has happened when things go wrong. However, these logs are partially under the control of the remote server (via the "sideband", which typically contains what the remote `git pack-objects` process sends to `stderr`), and is currently not sanitized by Git. This makes Git susceptible to ANSI escape sequence injection (see CWE-150, https://cwe.mitre.org/data/definitions/150.html), which allows attackers to corrupt terminal state, to hide information, and even to insert characters into the input buffer (i.e. as if the user had typed those characters). To plug this vulnerability, disallow any control character in the sideband, replacing them instead with the common `^` (e.g. `^[` for `\x1b`, `^A` for `\x01`). There is likely a need for more fine-grained controls instead of using a "heavy hammer" like this, which will be introduced subsequently. Signed-off-by: Johannes Schindelin --- sideband.c | 17 +++++++++++++++-- t/t5409-colorize-remote-messages.sh | 12 ++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/sideband.c b/sideband.c index 85bddfdcd4f57a..9384cb02d56a04 100644 --- a/sideband.c +++ b/sideband.c @@ -61,6 +61,19 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref list_config_item(list, prefix, keywords[i].keyword); } +static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) +{ + strbuf_grow(dest, n); + for (; n && *src; src++, n--) { + if (!iscntrl(*src) || *src == '\t' || *src == '\n') + strbuf_addch(dest, *src); + else { + strbuf_addch(dest, '^'); + strbuf_addch(dest, 0x40 + *src); + } + } +} + /* * Optionally highlight one keyword in remote output if it appears at the start * of the line. This should be called for a single line only, which is @@ -73,7 +86,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) int i; if (!want_color_stderr(use_sideband_colors())) { - strbuf_add(dest, src, n); + strbuf_add_sanitized(dest, src, n); return; } @@ -106,7 +119,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n) } } - strbuf_add(dest, src, n); + strbuf_add_sanitized(dest, src, n); } diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh index fa5de4500a4f50..6a6e0d15b21050 100755 --- a/t/t5409-colorize-remote-messages.sh +++ b/t/t5409-colorize-remote-messages.sh @@ -98,4 +98,16 @@ test_expect_success 'fallback to color.ui' ' grep "error: error" decoded ' +test_expect_success 'disallow (color) control sequences in sideband' ' + write_script .git/color-me-surprised <<-\EOF && + printf "error: Have you \\033[31mread\\033[m this?\\n" >&2 + exec "$@" + EOF + test_config_global uploadPack.packObjectshook ./color-me-surprised && + test_commit need-at-least-one-commit && + git clone --no-local . throw-away 2>stderr && + test_decode_color decoded && + test_i18ngrep ! RED decoded +' + test_done From 7725b8100ffbbff2750ee4d61a0fcc1f53a086e8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 30 Oct 2024 13:26:10 +0100 Subject: [PATCH 007/306] credential: sanitize the user prompt When asking the user interactively for credentials, we want to avoid misleading them e.g. via control sequences that pretend that the URL targets a trusted host when it does not. While Git learned, over the course of the preceding commits, to disallow URLs containing URL-encoded control characters by default, credential helpers are still allowed to specify values very freely (apart from Line Feed and NUL characters, anything is allowed), and this would allow, say, a username containing control characters to be specified that would then be displayed in the interactive terminal prompt asking the user for the password, potentially sending those control characters directly to the terminal. This is undesirable because control characters can be used to mislead users to divulge secret information to untrusted sites. To prevent such an attack vector, let's add a `git_prompt()` that forces the displayed text to be sanitized, i.e. displaying question marks instead of control characters. Note: While this commit's diff changes a lot of `user@host` strings to `user%40host`, which may look suspicious on the surface, there is a good reason for that: this string specifies a user name, not a @ combination! In the context of t5541, the actual combination looks like this: `user%40@127.0.0.1:5541`. Therefore, these string replacements document a net improvement introduced by this commit, as `user@host@127.0.0.1` could have left readers wondering where the user name ends and where the host name begins. Hinted-at-by: Jeff King Signed-off-by: Johannes Schindelin --- Documentation/config/credential.txt | 6 ++++++ credential.c | 7 ++++++- credential.h | 4 +++- t/t0300-credentials.sh | 20 ++++++++++++++++++++ t/t5541-http-push-smart.sh | 6 +++--- t/t5550-http-fetch-dumb.sh | 14 +++++++------- t/t5551-http-fetch-smart.sh | 16 ++++++++-------- 7 files changed, 53 insertions(+), 20 deletions(-) diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt index 512f31876e17ed..fd8113d6d42f75 100644 --- a/Documentation/config/credential.txt +++ b/Documentation/config/credential.txt @@ -14,6 +14,12 @@ credential.useHttpPath:: or https URL to be important. Defaults to false. See linkgit:gitcredentials[7] for more information. +credential.sanitizePrompt:: + By default, user names and hosts that are shown as part of the + password prompt are not allowed to contain control characters (they + will be URL-encoded by default). Configure this setting to `false` to + override that behavior. + credential.username:: If no username is set for a network authentication, use this username by default. See credential..* below, and diff --git a/credential.c b/credential.c index 572f1785da7d3e..1392a54d5c2c3e 100644 --- a/credential.c +++ b/credential.c @@ -67,6 +67,8 @@ static int credential_config_callback(const char *var, const char *value, } else if (!strcmp(key, "usehttppath")) c->use_http_path = git_config_bool(var, value); + else if (!strcmp(key, "sanitizeprompt")) + c->sanitize_prompt = git_config_bool(var, value); return 0; } @@ -179,7 +181,10 @@ static char *credential_ask_one(const char *what, struct credential *c, struct strbuf prompt = STRBUF_INIT; char *r; - credential_describe(c, &desc); + if (c->sanitize_prompt) + credential_format(c, &desc); + else + credential_describe(c, &desc); if (desc.len) strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf); else diff --git a/credential.h b/credential.h index 935b28a70f16ec..0364d436d242d6 100644 --- a/credential.h +++ b/credential.h @@ -119,7 +119,8 @@ struct credential { configured:1, quit:1, use_http_path:1, - username_from_proto:1; + username_from_proto:1, + sanitize_prompt:1; char *username; char *password; @@ -132,6 +133,7 @@ struct credential { #define CREDENTIAL_INIT { \ .helpers = STRING_LIST_INIT_DUP, \ .password_expiry_utc = TIME_MAX, \ + .sanitize_prompt = 1, \ } /* Initialize a credential structure, setting all fields to empty. */ diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index cb91be1427f1d2..b62c70c193eff3 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -45,6 +45,10 @@ test_expect_success 'setup helper scripts' ' test -z "$pexpiry" || echo password_expiry_utc=$pexpiry EOF + write_script git-credential-cntrl-in-username <<-\EOF && + printf "username=\\007latrix Lestrange\\n" + EOF + PATH="$PWD:$PATH" ' @@ -825,4 +829,20 @@ test_expect_success 'credential config with partial URLs' ' test_i18ngrep "skipping credential lookup for key" stderr ' +BEL="$(printf '\007')" + +test_expect_success 'interactive prompt is sanitized' ' + check fill cntrl-in-username <<-EOF + protocol=https + host=example.org + -- + protocol=https + host=example.org + username=${BEL}latrix Lestrange + password=askpass-password + -- + askpass: Password for ${SQ}https://%07latrix%20Lestrange@example.org${SQ}: + EOF +' + test_done diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index d0211cd8bef450..2cd2e1a0598760 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -351,7 +351,7 @@ test_expect_success 'push over smart http with auth' ' git push "$HTTPD_URL"/auth/smart/test_repo.git && git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \ log -1 --format=%s >actual && - expect_askpass both user@host && + expect_askpass both user%40host && test_cmp expect actual ' @@ -363,7 +363,7 @@ test_expect_success 'push to auth-only-for-push repo' ' git push "$HTTPD_URL"/auth-push/smart/test_repo.git && git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \ log -1 --format=%s >actual && - expect_askpass both user@host && + expect_askpass both user%40host && test_cmp expect actual ' @@ -393,7 +393,7 @@ test_expect_success 'push into half-auth-complete requires password' ' git push "$HTTPD_URL/half-auth-complete/smart/half-auth.git" && git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/half-auth.git" \ log -1 --format=%s >actual && - expect_askpass both user@host && + expect_askpass both user%40host && test_cmp expect actual ' diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 8f182a3cbfe73c..5d0e394609a08c 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -90,13 +90,13 @@ test_expect_success 'http auth can use user/pass in URL' ' test_expect_success 'http auth can use just user in URL' ' set_askpass wrong pass@host && git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass && - expect_askpass pass user@host + expect_askpass pass user%40host ' test_expect_success 'http auth can request both user and pass' ' set_askpass user@host pass@host && git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both && - expect_askpass both user@host + expect_askpass both user%40host ' test_expect_success 'http auth respects credential helper config' ' @@ -114,14 +114,14 @@ test_expect_success 'http auth can get username from config' ' test_config_global "credential.$HTTPD_URL.username" user@host && set_askpass wrong pass@host && git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user && - expect_askpass pass user@host + expect_askpass pass user%40host ' test_expect_success 'configured username does not override URL' ' test_config_global "credential.$HTTPD_URL.username" wrong && set_askpass wrong pass@host && git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 && - expect_askpass pass user@host + expect_askpass pass user%40host ' test_expect_success 'set up repo with http submodules' ' @@ -142,7 +142,7 @@ test_expect_success 'cmdline credential config passes to submodule via clone' ' set_askpass wrong pass@host && git -c "credential.$HTTPD_URL.username=user@host" \ clone --recursive super super-clone && - expect_askpass pass user@host + expect_askpass pass user%40host ' test_expect_success 'cmdline credential config passes submodule via fetch' ' @@ -153,7 +153,7 @@ test_expect_success 'cmdline credential config passes submodule via fetch' ' git -C super-clone \ -c "credential.$HTTPD_URL.username=user@host" \ fetch --recurse-submodules && - expect_askpass pass user@host + expect_askpass pass user%40host ' test_expect_success 'cmdline credential config passes submodule update' ' @@ -170,7 +170,7 @@ test_expect_success 'cmdline credential config passes submodule update' ' git -C super-clone \ -c "credential.$HTTPD_URL.username=user@host" \ submodule update && - expect_askpass pass user@host + expect_askpass pass user%40host ' test_expect_success 'fetch changes via http' ' diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 0908534f2561f4..8a27768dfba583 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -181,7 +181,7 @@ test_expect_success 'clone from password-protected repository' ' echo two >expect && set_askpass user@host pass@host && git clone --bare "$HTTPD_URL/auth/smart/repo.git" smart-auth && - expect_askpass both user@host && + expect_askpass both user%40host && git --git-dir=smart-auth log -1 --format=%s >actual && test_cmp expect actual ' @@ -199,7 +199,7 @@ test_expect_success 'clone from auth-only-for-objects repository' ' echo two >expect && set_askpass user@host pass@host && git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth && - expect_askpass both user@host && + expect_askpass both user%40host && git --git-dir=half-auth log -1 --format=%s >actual && test_cmp expect actual ' @@ -224,14 +224,14 @@ test_expect_success 'redirects send auth to new location' ' set_askpass user@host pass@host && git -c credential.useHttpPath=true \ clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth && - expect_askpass both user@host auth/smart/repo.git + expect_askpass both user%40host auth/smart/repo.git ' test_expect_success 'GIT_TRACE_CURL redacts auth details' ' rm -rf redact-auth trace && set_askpass user@host pass@host && GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && - expect_askpass both user@host && + expect_askpass both user%40host && # Ensure that there is no "Basic" followed by a base64 string, but that # the auth details are redacted @@ -243,7 +243,7 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' ' rm -rf redact-auth trace && set_askpass user@host pass@host && GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace && - expect_askpass both user@host && + expect_askpass both user%40host && # Ensure that there is no "Basic" followed by a base64 string, but that # the auth details are redacted @@ -256,7 +256,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE set_askpass user@host pass@host && GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \ git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && - expect_askpass both user@host && + expect_askpass both user%40host && grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace ' @@ -568,7 +568,7 @@ test_expect_success 'http auth remembers successful credentials' ' # the first request prompts the user... set_askpass user@host pass@host && git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null && - expect_askpass both user@host && + expect_askpass both user%40host && # ...and the second one uses the stored value rather than # prompting the user. @@ -599,7 +599,7 @@ test_expect_success 'http auth forgets bogus credentials' ' # us to prompt the user again. set_askpass user@host pass@host && git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null && - expect_askpass both user@host + expect_askpass both user%40host ' test_expect_success 'client falls back from v2 to v0 to match server' ' From a8c289b0a531d25336a96eaa5e3584414ed4c6c4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 6 Nov 2024 21:07:51 +0100 Subject: [PATCH 008/306] sideband: introduce an "escape hatch" to allow control characters The preceding commit fixed the vulnerability whereas sideband messages (that are under the control of the remote server) could contain ANSI escape sequences that would be sent to the terminal verbatim. However, this fix may not be desirable under all circumstances, e.g. when remote servers deliberately add coloring to their messages to increase their urgency. To help with those use cases, give users a way to opt-out of the protections: `sideband.allowControlCharacters`. Signed-off-by: Johannes Schindelin --- Documentation/config.txt | 2 ++ Documentation/config/sideband.txt | 5 +++++ sideband.c | 10 ++++++++++ t/t5409-colorize-remote-messages.sh | 8 +++++++- 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 Documentation/config/sideband.txt diff --git a/Documentation/config.txt b/Documentation/config.txt index 0e93aef86264db..abdbfba9bd756a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -511,6 +511,8 @@ include::config/sequencer.txt[] include::config/showbranch.txt[] +include::config/sideband.txt[] + include::config/sparse.txt[] include::config/splitindex.txt[] diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt new file mode 100644 index 00000000000000..3fb5045cd79581 --- /dev/null +++ b/Documentation/config/sideband.txt @@ -0,0 +1,5 @@ +sideband.allowControlCharacters:: + By default, control characters that are delivered via the sideband + are masked, to prevent potentially unwanted ANSI escape sequences + from being sent to the terminal. Use this config setting to override + this behavior. diff --git a/sideband.c b/sideband.c index 9384cb02d56a04..8ebf1f0743e6b6 100644 --- a/sideband.c +++ b/sideband.c @@ -20,6 +20,8 @@ static struct keyword_entry keywords[] = { { "error", GIT_COLOR_BOLD_RED }, }; +static int allow_control_characters; + /* Returns a color setting (GIT_COLOR_NEVER, etc). */ static int use_sideband_colors(void) { @@ -33,6 +35,9 @@ static int use_sideband_colors(void) if (use_sideband_colors_cached >= 0) return use_sideband_colors_cached; + git_config_get_bool("sideband.allowcontrolcharacters", + &allow_control_characters); + if (!git_config_get_string(key, &value)) { use_sideband_colors_cached = git_config_colorbool(key, value); } else if (!git_config_get_string("color.ui", &value)) { @@ -63,6 +68,11 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) { + if (allow_control_characters) { + strbuf_add(dest, src, n); + return; + } + strbuf_grow(dest, n); for (; n && *src; src++, n--) { if (!iscntrl(*src) || *src == '\t' || *src == '\n') diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh index 6a6e0d15b21050..1cd0640f200009 100755 --- a/t/t5409-colorize-remote-messages.sh +++ b/t/t5409-colorize-remote-messages.sh @@ -105,9 +105,15 @@ test_expect_success 'disallow (color) control sequences in sideband' ' EOF test_config_global uploadPack.packObjectshook ./color-me-surprised && test_commit need-at-least-one-commit && + git clone --no-local . throw-away 2>stderr && test_decode_color decoded && - test_i18ngrep ! RED decoded + test_i18ngrep ! RED decoded && + + rm -rf throw-away && + git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr && + test_decode_color decoded && + test_i18ngrep RED decoded ' test_done From b01b9b81d36759cdcd07305e78765199e1bc2060 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 4 Nov 2024 14:48:22 +0100 Subject: [PATCH 009/306] credential: disallow Carriage Returns in the protocol by default While Git has documented that the credential protocol is line-based, with newlines as terminators, the exact shape of a newline has not been documented. From Git's perspective, which is firmly rooted in the Linux ecosystem, it is clear that "a newline" means a Line Feed character. However, even Git's credential protocol respects Windows line endings (a Carriage Return character followed by a Line Feed character, "CR/LF") by virtue of using `strbuf_getline()`. There is a third category of line endings that has been used originally by MacOS, and that is respected by the default line readers of .NET and node.js: bare Carriage Returns. Git cannot handle those, and what is worse: Git's remedy against CVE-2020-5260 does not catch when credential helpers are used that interpret bare Carriage Returns as newlines. Git Credential Manager addressed this as CVE-2024-50338, but other credential helpers may still be vulnerable. So let's not only disallow Line Feed characters as part of the values in the credential protocol, but also disallow Carriage Return characters. In the unlikely event that a credential helper relies on Carriage Returns in the protocol, introduce an escape hatch via the `credential.protectProtocol` config setting. This addresses CVE-2024-52006. Signed-off-by: Johannes Schindelin --- Documentation/config/credential.txt | 5 +++++ credential.c | 21 ++++++++++++++------- credential.h | 4 +++- t/t0300-credentials.sh | 16 ++++++++++++++++ 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt index fd8113d6d42f75..9cadca7f73db67 100644 --- a/Documentation/config/credential.txt +++ b/Documentation/config/credential.txt @@ -20,6 +20,11 @@ credential.sanitizePrompt:: will be URL-encoded by default). Configure this setting to `false` to override that behavior. +credential.protectProtocol:: + By default, Carriage Return characters are not allowed in the protocol + that is used when Git talks to a credential helper. This setting allows + users to override this default. + credential.username:: If no username is set for a network authentication, use this username by default. See credential..* below, and diff --git a/credential.c b/credential.c index 1392a54d5c2c3e..b76a730901f5ea 100644 --- a/credential.c +++ b/credential.c @@ -69,6 +69,8 @@ static int credential_config_callback(const char *var, const char *value, c->use_http_path = git_config_bool(var, value); else if (!strcmp(key, "sanitizeprompt")) c->sanitize_prompt = git_config_bool(var, value); + else if (!strcmp(key, "protectprotocol")) + c->protect_protocol = git_config_bool(var, value); return 0; } @@ -262,7 +264,8 @@ int credential_read(struct credential *c, FILE *fp) return 0; } -static void credential_write_item(FILE *fp, const char *key, const char *value, +static void credential_write_item(const struct credential *c, + FILE *fp, const char *key, const char *value, int required) { if (!value && required) @@ -271,19 +274,23 @@ static void credential_write_item(FILE *fp, const char *key, const char *value, return; if (strchr(value, '\n')) die("credential value for %s contains newline", key); + if (c->protect_protocol && strchr(value, '\r')) + die("credential value for %s contains carriage return\n" + "If this is intended, set `credential.protectProtocol=false`", + key); fprintf(fp, "%s=%s\n", key, value); } void credential_write(const struct credential *c, FILE *fp) { - credential_write_item(fp, "protocol", c->protocol, 1); - credential_write_item(fp, "host", c->host, 1); - credential_write_item(fp, "path", c->path, 0); - credential_write_item(fp, "username", c->username, 0); - credential_write_item(fp, "password", c->password, 0); + credential_write_item(c, fp, "protocol", c->protocol, 1); + credential_write_item(c, fp, "host", c->host, 1); + credential_write_item(c, fp, "path", c->path, 0); + credential_write_item(c, fp, "username", c->username, 0); + credential_write_item(c, fp, "password", c->password, 0); if (c->password_expiry_utc != TIME_MAX) { char *s = xstrfmt("%"PRItime, c->password_expiry_utc); - credential_write_item(fp, "password_expiry_utc", s, 0); + credential_write_item(c, fp, "password_expiry_utc", s, 0); free(s); } } diff --git a/credential.h b/credential.h index 0364d436d242d6..2c0b39a925405f 100644 --- a/credential.h +++ b/credential.h @@ -120,7 +120,8 @@ struct credential { quit:1, use_http_path:1, username_from_proto:1, - sanitize_prompt:1; + sanitize_prompt:1, + protect_protocol:1; char *username; char *password; @@ -134,6 +135,7 @@ struct credential { .helpers = STRING_LIST_INIT_DUP, \ .password_expiry_utc = TIME_MAX, \ .sanitize_prompt = 1, \ + .protect_protocol = 1, \ } /* Initialize a credential structure, setting all fields to empty. */ diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index b62c70c193eff3..168ae76550155d 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -720,6 +720,22 @@ test_expect_success 'url parser rejects embedded newlines' ' test_cmp expect stderr ' +test_expect_success 'url parser rejects embedded carriage returns' ' + test_config credential.helper "!true" && + test_must_fail git credential fill 2>stderr <<-\EOF && + url=https://example%0d.com/ + EOF + cat >expect <<-\EOF && + fatal: credential value for host contains carriage return + If this is intended, set `credential.protectProtocol=false` + EOF + test_cmp expect stderr && + GIT_ASKPASS=true \ + git -c credential.protectProtocol=false credential fill <<-\EOF + url=https://example%0d.com/ + EOF +' + test_expect_success 'host-less URLs are parsed as empty host' ' check fill "verbatim foo bar" <<-\EOF url=cert:///path/to/cert.pem From c7049c2a7f47c99a67fd869f1ee89d7aa1a328d2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 18 Nov 2024 21:42:57 +0100 Subject: [PATCH 010/306] sideband: do allow ANSI color sequences by default The preceding two commits introduced special handling of the sideband channel to neutralize ANSI escape sequences before sending the payload to the terminal, and `sideband.allowControlCharacters` to override that behavior. However, some `pre-receive` hooks that are actively used in practice want to color their messages and therefore rely on the fact that Git passes them through to the terminal. In contrast to other ANSI escape sequences, it is highly unlikely that coloring sequences can be essential tools in attack vectors that mislead Git users e.g. by hiding crucial information. Therefore we can have both: Continue to allow ANSI coloring sequences to be passed to the terminal, and neutralize all other ANSI escape sequences. Signed-off-by: Johannes Schindelin --- Documentation/config/sideband.txt | 17 ++++++-- sideband.c | 61 ++++++++++++++++++++++++++--- t/t5409-colorize-remote-messages.sh | 16 +++++++- 3 files changed, 84 insertions(+), 10 deletions(-) diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt index 3fb5045cd79581..f347fd6b33004a 100644 --- a/Documentation/config/sideband.txt +++ b/Documentation/config/sideband.txt @@ -1,5 +1,16 @@ sideband.allowControlCharacters:: By default, control characters that are delivered via the sideband - are masked, to prevent potentially unwanted ANSI escape sequences - from being sent to the terminal. Use this config setting to override - this behavior. + are masked, except ANSI color sequences. This prevents potentially + unwanted ANSI escape sequences from being sent to the terminal. Use + this config setting to override this behavior: ++ +-- + color:: + Allow ANSI color sequences, line feeds and horizontal tabs, + but mask all other control characters. This is the default. + false:: + Mask all control characters other than line feeds and + horizontal tabs. + true:: + Allow all control characters to be sent to the terminal. +-- diff --git a/sideband.c b/sideband.c index 8ebf1f0743e6b6..afd62aa008154b 100644 --- a/sideband.c +++ b/sideband.c @@ -20,7 +20,11 @@ static struct keyword_entry keywords[] = { { "error", GIT_COLOR_BOLD_RED }, }; -static int allow_control_characters; +static enum { + ALLOW_NO_CONTROL_CHARACTERS = 0, + ALLOW_ALL_CONTROL_CHARACTERS = 1, + ALLOW_ANSI_COLOR_SEQUENCES = 2 +} allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES; /* Returns a color setting (GIT_COLOR_NEVER, etc). */ static int use_sideband_colors(void) @@ -35,8 +39,24 @@ static int use_sideband_colors(void) if (use_sideband_colors_cached >= 0) return use_sideband_colors_cached; - git_config_get_bool("sideband.allowcontrolcharacters", - &allow_control_characters); + switch (git_config_get_maybe_bool("sideband.allowcontrolcharacters", &i)) { + case 0: /* Boolean value */ + allow_control_characters = i ? ALLOW_ALL_CONTROL_CHARACTERS : + ALLOW_NO_CONTROL_CHARACTERS; + break; + case -1: /* non-Boolean value */ + if (git_config_get_string("sideband.allowcontrolcharacters", + &value)) + ; /* huh? `get_maybe_bool()` returned -1 */ + else if (!strcmp(value, "color")) + allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES; + else + warning(_("unrecognized value for `sideband." + "allowControlCharacters`: '%s'"), value); + break; + default: + break; /* not configured */ + } if (!git_config_get_string(key, &value)) { use_sideband_colors_cached = git_config_colorbool(key, value); @@ -66,9 +86,37 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref list_config_item(list, prefix, keywords[i].keyword); } +static int handle_ansi_color_sequence(struct strbuf *dest, const char *src, int n) +{ + int i; + + /* + * Valid ANSI color sequences are of the form + * + * ESC [ [ [; ]*] m + */ + + if (allow_control_characters != ALLOW_ANSI_COLOR_SEQUENCES || + n < 3 || src[0] != '\x1b' || src[1] != '[') + return 0; + + for (i = 2; i < n; i++) { + if (src[i] == 'm') { + strbuf_add(dest, src, i + 1); + return i; + } + if (!isdigit(src[i]) && src[i] != ';') + break; + } + + return 0; +} + static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) { - if (allow_control_characters) { + int i; + + if (allow_control_characters == ALLOW_ALL_CONTROL_CHARACTERS) { strbuf_add(dest, src, n); return; } @@ -77,7 +125,10 @@ static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n) for (; n && *src; src++, n--) { if (!iscntrl(*src) || *src == '\t' || *src == '\n') strbuf_addch(dest, *src); - else { + else if ((i = handle_ansi_color_sequence(dest, src, n))) { + src += i; + n -= i; + } else { strbuf_addch(dest, '^'); strbuf_addch(dest, 0x40 + *src); } diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh index 1cd0640f200009..43296ea51c5db1 100755 --- a/t/t5409-colorize-remote-messages.sh +++ b/t/t5409-colorize-remote-messages.sh @@ -100,7 +100,7 @@ test_expect_success 'fallback to color.ui' ' test_expect_success 'disallow (color) control sequences in sideband' ' write_script .git/color-me-surprised <<-\EOF && - printf "error: Have you \\033[31mread\\033[m this?\\n" >&2 + printf "error: Have you \\033[31mread\\033[m this?\\a\\n" >&2 exec "$@" EOF test_config_global uploadPack.packObjectshook ./color-me-surprised && @@ -108,12 +108,24 @@ test_expect_success 'disallow (color) control sequences in sideband' ' git clone --no-local . throw-away 2>stderr && test_decode_color decoded && + test_i18ngrep RED decoded && + test_i18ngrep "\\^G" stderr && + tr -dc "\\007" actual && + test_must_be_empty actual && + + rm -rf throw-away && + git -c sideband.allowControlCharacters=false \ + clone --no-local . throw-away 2>stderr && + test_decode_color decoded && test_i18ngrep ! RED decoded && + test_i18ngrep "\\^G" stderr && rm -rf throw-away && git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr && test_decode_color decoded && - test_i18ngrep RED decoded + test_i18ngrep RED decoded && + tr -dc "\\007" actual && + test_file_not_empty actual ' test_done From 4099ea91734fe6f56c927413dd83c72131842783 Mon Sep 17 00:00:00 2001 From: Sven Strickroth Date: Tue, 6 Aug 2024 18:16:59 +0200 Subject: [PATCH 011/306] fixup! mingw: optionally enable wsl compability file mode bits Signed-off-by: Johannes Schindelin --- compat/win32/wsl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compat/win32/wsl.c b/compat/win32/wsl.c index 0d4662078aadc6..5dcef67a3fca69 100644 --- a/compat/win32/wsl.c +++ b/compat/win32/wsl.c @@ -53,6 +53,7 @@ int copy_wsl_mode_bits_from_disk(const wchar_t *wpath, ssize_t wpathlen, return ret; } +#ifndef LX_FILE_METADATA_HAS_UID #define LX_FILE_METADATA_HAS_UID 0x1 #define LX_FILE_METADATA_HAS_GID 0x2 #define LX_FILE_METADATA_HAS_MODE 0x4 @@ -77,6 +78,7 @@ typedef struct _FILE_STAT_LX_INFORMATION { uint32_t LxDeviceIdMajor; uint32_t LxDeviceIdMinor; } FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION; +#endif /* * This struct is extended from the original FILE_FULL_EA_INFORMATION of From ff405069c4b7159370bc648456221067fb1f5d93 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 30 Oct 2024 19:48:46 +0100 Subject: [PATCH 012/306] unix-socket: avoid leak when initialization fails When a Unix socket is initialized, the current directory's path is stored so that the cleanup code can `chdir()` back to where it was before exit. If the path that needs to be stored exceeds the default size of the `sun_path` attribute of `struct sockaddr_un` (which is defined as a 108-sized byte array on Linux), a larger buffer needs to be allocated so that it can hold the path, and it is the responsibility of the `unix_sockaddr_cleanup()` function to release that allocated memory. In Git's CI, this stack allocation is not necessary because the code is checked out to `/home/runner/work/git/git`. Concatenate the path `t/trash directory.t0301-credential-cache/.cache/git/credential/socket` and a terminating NUL, and you end up with 96 bytes, 12 shy of the default `sun_path` size. However, I use worktrees with slightly longer paths: `/home/me/projects/git/yes/i/nest/worktrees/to/organize/them/` is more in line with what I have. When I recently tried to locally reproduce a failure of the `linux-leaks` CI job, this t0301 test failed (where it had not failed in CI). The reason: When `credential-cache` tries to reach its daemon initially by calling `unix_sockaddr_init()`, it is expected that the daemon cannot be reached (the idea is to spin up the daemon in that case and try again). However, when this first call to `unix_sockaddr_init()` fails, the code returns early from the `unix_stream_connect()` function _without_ giving the cleanup code a chance to run, skipping the deallocation of above-mentioned path. The fix is easy: do not return early but instead go directly to the cleanup code. Signed-off-by: Johannes Schindelin --- unix-socket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix-socket.c b/unix-socket.c index 79800d80636fc5..eb45bfa56e4852 100644 --- a/unix-socket.c +++ b/unix-socket.c @@ -80,7 +80,7 @@ int unix_stream_connect(const char *path, int disallow_chdir) struct unix_sockaddr_context ctx; if (unix_sockaddr_init(&sa, path, &ctx, disallow_chdir) < 0) - return -1; + goto fail; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) goto fail; From b4d15f73e2819d9f4cbbdd1bc2bd007d962b053a Mon Sep 17 00:00:00 2001 From: Ethiraric Date: Mon, 21 Oct 2024 01:41:15 +0200 Subject: [PATCH 013/306] l10n: fr.po: Minor improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix an occurrence of "dpuis" to "depuis". * Add some entries in the translation index at the beginning of the file. * Harmonize the spelling of various items based on how common each spelling or translation is throughout the file. * superproject -> super-projet * patch -> rustine * regex / regexp -> regex * regular expression -> expression régulière * loose object -> objet esseulé * directory -> répertoire * Fix various typos (e.g.: trailing ".<" or ".", "mêm" -> "même") * Fix minor grammatical errors (e.g: "le valeur" -> "la valeur") * Remove old translations Signed-off-by: Florian Sabourin --- po/fr.po | 478 ++++++++++++++++--------------------------------------- 1 file changed, 133 insertions(+), 345 deletions(-) diff --git a/po/fr.po b/po/fr.po index 481e1bdc1d5d52..b113e3870b925f 100644 --- a/po/fr.po +++ b/po/fr.po @@ -29,6 +29,7 @@ # to debug | déboguer # debugging | débogage # to deflate | compresser +# directory | répertoire # email | courriel # enlistment | enrôlement # entry | élément @@ -45,18 +46,23 @@ # hunk | section # to inflate | décompresser # to list | afficher +# loose object | objet esseulé # mapping | mise en correspondance # merge | fusion # pack | paquet -# patches | patchs +# patch (v) | appliquer une/des rustine(s) +# patches | rustines # pattern | motif # to prune | élaguer # to push | pousser # to rebase | rebaser # scheduler | planificateur # trailers | lignes terminales -# repository | dépôt +# regex | regex +# regular | +# expression | expression régulière # remote | distante (ou serveur distant) +# repository | dépôt # revision | révision # shallow | superficiel # shell | interpréteur de commandes @@ -64,6 +70,7 @@ # split (index) | index scindé # stash | remisage # to stash | remiser +# superproject | super-projet # tag | étiquette # template | modèle # thread | fil @@ -169,7 +176,7 @@ msgid "No changes.\n" msgstr "Aucune modification.\n" msgid "Patch update" -msgstr "Mise à jour par patch" +msgstr "Mise à jour par rustine" msgid "Review diff" msgstr "Réviser la différence" @@ -267,7 +274,7 @@ msgid "" "If the patch applies cleanly, the edited hunk will immediately be marked for " "staging." msgstr "" -"Si le patch s'applique proprement, la section éditée sera immédiatement " +"Si la rustine s'applique proprement, la section éditée sera immédiatement " "marquée comme indexée." msgid "" @@ -303,7 +310,7 @@ msgid "" "If the patch applies cleanly, the edited hunk will immediately be marked for " "stashing." msgstr "" -"Si le patch s'applique proprement, la section éditée sera immédiatement " +"Si la rustine s'applique proprement, la section éditée sera immédiatement " "marquée comme remisée." msgid "" @@ -339,7 +346,7 @@ msgid "" "If the patch applies cleanly, the edited hunk will immediately be marked for " "unstaging." msgstr "" -"Si le patch s'applique proprement, la section éditée sera immédiatement " +"Si la rustine s'applique proprement, la section éditée sera immédiatement " "marquée comme desindexée." msgid "" @@ -375,7 +382,7 @@ msgid "" "If the patch applies cleanly, the edited hunk will immediately be marked for " "applying." msgstr "" -"Si le patch s'applique proprement, la section éditée sera immédiatement " +"Si la rustine s'applique proprement, la section éditée sera immédiatement " "marquée comme appliquée." msgid "" @@ -411,7 +418,7 @@ msgid "" "If the patch applies cleanly, the edited hunk will immediately be marked for " "discarding." msgstr "" -"Si le patch s'applique proprement, la section éditée sera immédiatement " +"Si la rustine s'applique proprement, la section éditée sera immédiatement " "marquée comme éliminée." msgid "" @@ -893,7 +900,7 @@ msgstr "la rustine est trop grosse" #, c-format msgid "Cannot prepare timestamp regexp %s" -msgstr "Impossible de préparer la regexp d'horodatage %s" +msgstr "Impossible de préparer la regex d'horodatage %s" #, c-format msgid "regexec returned %d for input: %s" @@ -901,7 +908,7 @@ msgstr "regexec a retourné %d pour l'entrée : %s" #, c-format msgid "unable to find filename in patch at line %d" -msgstr "nom de fichier du patch introuvable à la ligne %d" +msgstr "nom de fichier de la rustine introuvable à la ligne %d" #, c-format msgid "git apply: bad git-diff - expected /dev/null, got %s on line %d" @@ -912,13 +919,13 @@ msgstr "" #, c-format msgid "git apply: bad git-diff - inconsistent new filename on line %d" msgstr "" -"git apply : mauvais format de git-diff - nouveau nom de fichier inconsistant " +"git apply : mauvais format de git-diff - nouveau nom de fichier incohérent " "à la ligne %d" #, c-format msgid "git apply: bad git-diff - inconsistent old filename on line %d" msgstr "" -"git apply : mauvais format de git-diff - ancien nom de fichier inconsistant " +"git apply : mauvais format de git-diff - ancien nom de fichier incohérent " "à la ligne %d" #, c-format @@ -959,7 +966,7 @@ msgstr "recomptage : ligne inattendue : %.*s" #, c-format msgid "patch fragment without header at line %d: %.*s" -msgstr "fragment de patch sans en-tête à la ligne %d : %.*s" +msgstr "fragment de rustine sans en-tête à la ligne %d : %.*s" msgid "new file depends on old contents" msgstr "le nouveau fichier dépend de contenus anciens" @@ -969,7 +976,7 @@ msgstr "le fichier supprimé a encore du contenu" #, c-format msgid "corrupt patch at line %d" -msgstr "patch corrompu à la ligne %d" +msgstr "rustine corrompue à la ligne %d" #, c-format msgid "new file %s depends on old contents" @@ -985,15 +992,15 @@ msgstr "** attention : le fichier %s devient vide mais n'est pas supprimé" #, c-format msgid "corrupt binary patch at line %d: %.*s" -msgstr "patch binaire corrompu à la ligne %d : %.*s" +msgstr "rustine binaire corrompue à la ligne %d : %.*s" #, c-format msgid "unrecognized binary patch at line %d" -msgstr "patch binaire non reconnu à la ligne %d" +msgstr "rustine binaire non reconnue à la ligne %d" #, c-format msgid "patch with only garbage at line %d" -msgstr "patch totalement incompréhensible à la ligne %d" +msgstr "rustine totalement incompréhensible à la ligne %d" #, c-format msgid "unable to read symlink %s" @@ -1027,28 +1034,29 @@ msgstr "" #, c-format msgid "missing binary patch data for '%s'" -msgstr "données de patch binaire manquantes pour '%s'" +msgstr "données de rustine binaire manquantes pour '%s'" #, c-format msgid "cannot reverse-apply a binary patch without the reverse hunk to '%s'" msgstr "" -"impossible d'appliquer l'inverse d'un patch binaire à '%s' sans la section " +"impossible d'appliquer l'inverse d'une rustine binaire à '%s' sans la section " "inverse" #, c-format msgid "cannot apply binary patch to '%s' without full index line" msgstr "" -"impossible d'appliquer un patch binaire à '%s' sans la ligne complète d'index" +"impossible d'appliquer une rustine binaire à '%s' sans la ligne complète " +"d'index" #, c-format msgid "" "the patch applies to '%s' (%s), which does not match the current contents." msgstr "" -"le patch s'applique à '%s' (%s), ce qui ne correspond pas au contenu actuel." +"la rustine s'applique à '%s' (%s), ce qui ne correspond pas au contenu actuel." #, c-format msgid "the patch applies to an empty '%s' but it is not empty" -msgstr "le patch s'applique à un '%s' vide mais ce n'est pas vide" +msgstr "la rustine s'applique à un '%s' vide mais ce n'est pas vide" #, c-format msgid "the necessary postimage %s for '%s' cannot be read" @@ -1056,17 +1064,17 @@ msgstr "l'image postérieure nécessaire %s pour '%s' ne peut pas être lue" #, c-format msgid "binary patch does not apply to '%s'" -msgstr "le patch binaire ne s'applique par correctement à '%s'" +msgstr "la rustine binaire ne s'applique par correctement à '%s'" #, c-format msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)" msgstr "" -"le patch binaire sur '%s' crée un résultat incorrect (%s attendu, mais %s " +"la rustine binaire sur '%s' crée un résultat incorrect (%s attendu, mais %s " "trouvé)" #, c-format msgid "patch failed: %s:%ld" -msgstr "le patch a échoué : %s:%ld" +msgstr "l'application de la rustine a échoué : %s:%ld" #, c-format msgid "cannot checkout %s" @@ -1110,18 +1118,18 @@ msgstr "Échec de l'application de la fusion à 3 points…\n" #, c-format msgid "Applied patch to '%s' with conflicts.\n" -msgstr "Patch %s appliqué avec des conflits.\n" +msgstr "Rustine %s appliquée avec des conflits.\n" #, c-format msgid "Applied patch to '%s' cleanly.\n" -msgstr "Patch %s appliqué proprement.\n" +msgstr "Rustine %s appliquée proprement.\n" #, c-format msgid "Falling back to direct application...\n" msgstr "Retour à une application directe…\n" msgid "removal patch leaves file contents" -msgstr "le patch de suppression laisse un contenu dans le fichier" +msgstr "la rustine de suppression laisse un contenu dans le fichier" #, c-format msgid "%s: wrong type" @@ -1158,11 +1166,11 @@ msgstr "le fichier affecté '%s' est au-delà d'un lien symbolique" #, c-format msgid "%s: patch does not apply" -msgstr "%s : le patch ne s'applique pas" +msgstr "%s : la rustine ne s'applique pas" #, c-format msgid "Checking patch %s..." -msgstr "Vérification du patch %s..." +msgstr "Vérification de la rustine %s..." #, c-format msgid "sha1 information is lacking or useless for submodule %s" @@ -1191,7 +1199,7 @@ msgstr "suppression de %s dans l'index impossible" #, c-format msgid "corrupt patch for submodule %s" -msgstr "patch corrompu pour le sous-module %s" +msgstr "rustine corrompue pour le sous-module %s" #, c-format msgid "unable to stat newly created file '%s'" @@ -1221,7 +1229,7 @@ msgstr "écriture du fichier '%s' mode %o impossible" #, c-format msgid "Applied patch %s cleanly." -msgstr "Patch %s appliqué proprement." +msgstr "Rustine %s appliquée proprement." msgid "internal error" msgstr "erreur interne" @@ -1229,8 +1237,8 @@ msgstr "erreur interne" #, c-format msgid "Applying patch %%s with %d reject..." msgid_plural "Applying patch %%s with %d rejects..." -msgstr[0] "Application du patch %%s avec %d rejet..." -msgstr[1] "Application du patch %%s avec %d rejets..." +msgstr[0] "Application de la rustine %%s avec %d rejet..." +msgstr[1] "Application de la rustine %%s avec %d rejets..." #, c-format msgid "cannot open %s" @@ -1260,7 +1268,7 @@ msgstr "lecture du fichier d'index impossible" #, c-format msgid "can't open patch '%s': %s" -msgstr "ouverture impossible du patch '%s' :%s" +msgstr "ouverture impossible de la rustine '%s' :%s" #, c-format msgid "squelched %d whitespace error" @@ -1296,35 +1304,35 @@ msgid "remove leading slashes from traditional diff paths" msgstr "supprimer barres obliques des chemins traditionnels de diff" msgid "ignore additions made by the patch" -msgstr "ignorer les additions réalisées par le patch" +msgstr "ignorer les additions réalisées par la rustine" msgid "instead of applying the patch, output diffstat for the input" -msgstr "au lieu d'appliquer le patch, afficher le diffstat de l'entrée" +msgstr "au lieu d'appliquer la rustine, afficher le diffstat de l'entrée" msgid "show number of added and deleted lines in decimal notation" msgstr "" "afficher le nombre de lignes ajoutées et supprimées en notation décimale" msgid "instead of applying the patch, output a summary for the input" -msgstr "au lieu d'appliquer le patch, afficher un résumé de l'entrée" +msgstr "au lieu d'appliquer la rustine, afficher un résumé de l'entrée" msgid "instead of applying the patch, see if the patch is applicable" -msgstr "au lieu d'appliquer le patch, voir si le patch est applicable" +msgstr "au lieu d'appliquer la rustine, voir si la rustine est applicable" msgid "make sure the patch is applicable to the current index" -msgstr "s'assurer que le patch est applicable sur l'index actuel" +msgstr "s'assurer que la rustine est applicable sur l'index actuel" msgid "mark new files with `git add --intent-to-add`" msgstr "marquer les nouveaux fichiers `git add --intent-to-add`" msgid "apply a patch without touching the working tree" -msgstr "appliquer les patch sans toucher à la copie de travail" +msgstr "appliquer les rustines sans toucher à la copie de travail" msgid "accept a patch that touches outside the working area" -msgstr "accepter un patch qui touche hors de la copie de travail" +msgstr "accepter une rustine qui touche hors de la copie de travail" msgid "also apply the patch (use with --stat/--summary/--check)" -msgstr "appliquer aussi le patch (à utiliser avec --stat/--summary/--check)" +msgstr "appliquer aussi la rustine (à utiliser avec --stat/--summary/--check)" msgid "attempt three-way merge, fall back on normal patch if that fails" msgstr "" @@ -1361,7 +1369,7 @@ msgid "ignore changes in whitespace when finding context" msgstr "ignorer des modifications d'espace lors de la recherche de contexte" msgid "apply the patch in reverse" -msgstr "appliquer le patch en sens inverse" +msgstr "appliquer la rustine en sens inverse" msgid "don't expect at least one line of context" msgstr "ne pas s'attendre à au moins une ligne de contexte" @@ -1923,7 +1931,7 @@ msgid "could not read the index" msgstr "impossible de lire l'index" msgid "editing patch failed" -msgstr "échec de l'édition du patch" +msgstr "échec de l'édition de la rustine" #, c-format msgid "could not stat '%s'" @@ -2031,7 +2039,7 @@ msgid "adding embedded git repository: %s" msgstr "dépôt git embarqué ajouté : %s" msgid "Use -f if you really want to add them." -msgstr "Utilisez -f si vous voulez vraiment les ajouter<." +msgstr "Utilisez -f si vous voulez vraiment les ajouter." msgid "adding files failed" msgstr "échec de l'ajout de fichiers" @@ -2103,10 +2111,10 @@ msgstr "impossible d'ouvrir '%s' en écriture" #, c-format msgid "could not parse patch '%s'" -msgstr "impossible d'analyser le patch '%s'" +msgstr "impossible d'analyser la rustine '%s'" msgid "Only one StGIT patch series can be applied at once" -msgstr "Seulement une série de patchs StGIT peut être appliquée à la fois" +msgstr "Seulement une série de rustines StGIT peut être appliquée à la fois" msgid "invalid timestamp" msgstr "horodatage invalide" @@ -2118,14 +2126,14 @@ msgid "invalid timezone offset" msgstr "décalage horaire invalide" msgid "Patch format detection failed." -msgstr "Échec de détection du format du patch." +msgstr "Échec de détection du format de la rustine." #, c-format msgid "failed to create directory '%s'" msgstr "échec de la création du répertoire '%s'" msgid "Failed to split patches." -msgstr "Échec de découpage des patchs." +msgstr "Échec de découpage des rustines." #, c-format msgid "When you have resolved this problem, run \"%s --continue\".\n" @@ -2145,8 +2153,8 @@ msgstr "" #, c-format msgid "To restore the original branch and stop patching, run \"%s --abort\"." msgstr "" -"Pour restaurer la branche originale et arrêter de patcher, lancez \"%s --" -"abort\"." +"Pour restaurer la branche originale et arrêter d'appliquer des rustines, " +"lancez \"%s --abort\"." msgid "Patch sent with format=flowed; space at the end of lines might be lost." msgstr "" @@ -2178,11 +2186,11 @@ msgid "" "Did you hand edit your patch?\n" "It does not apply to blobs recorded in its index." msgstr "" -"Avez-vous édité le patch à la main ?\n" +"Avez-vous édité la rustine à la main ?\n" "Il ne s'applique pas aux blobs enregistrés dans son index." msgid "Falling back to patching base and 3-way merge..." -msgstr "Retour à un patch de la base et fusion à 3 points..." +msgstr "Retour à une rustine de la base et fusion à 3 points..." msgid "Failed to merge in the changes." msgstr "Échec d'intégration des modifications." @@ -2216,7 +2224,7 @@ msgstr "impossible d'écrire le fichier d'index" #, c-format msgid "Dirty index: cannot apply patches (dirty: %s)" -msgstr "Index sale : impossible d'appliquer des patchs (sales : %s)" +msgstr "Index sale : impossible d'appliquer des rustines (sales : %s)" #, c-format msgid "Skipping: %.*s" @@ -2227,7 +2235,7 @@ msgid "Creating an empty commit: %.*s" msgstr "Création d'un commit vide : %.*s" msgid "Patch is empty." -msgstr "Le patch actuel est vide." +msgstr "La rustine actuelle est vide." #, c-format msgid "Applying: %.*s" @@ -2242,7 +2250,8 @@ msgstr "l'application de la rustine a échoué à %s %.*s" msgid "Use 'git am --show-current-patch=diff' to see the failed patch" msgstr "" -"Utilisez 'git am --show-current-patch=diff' pour visualiser le patch en échec" +"Utilisez 'git am --show-current-patch=diff' pour visualiser la rustine en " +"échec" msgid "No changes - recorded it as an empty commit." msgstr "aucune modification - enregistré comme un commit vide." @@ -2254,8 +2263,8 @@ msgid "" msgstr "" "Aucun changement - avez-vous oublié d'utiliser 'git add' ?\n" "S'il n'y a plus rien à indexer, il se peut qu'autre chose ait déjà\n" -"introduit les mêmes changements ; vous pourriez avoir envie de sauter ce " -"patch." +"introduit les mêmes changements ; vous pourriez avoir envie de sauter cette " +"rustine." msgid "" "You still have unmerged paths in your index.\n" @@ -2342,28 +2351,30 @@ msgid "format" msgstr "format" msgid "format the patch(es) are in" -msgstr "format de présentation des patchs" +msgstr "format de présentation des rustines" msgid "override error message when patch failure occurs" -msgstr "surcharger le message d'erreur lors d'un échec d'application de patch" +msgstr "" +"surcharger le message d'erreur lors d'un échec d'application d'une rustine" msgid "continue applying patches after resolving a conflict" -msgstr "continuer à appliquer les patchs après résolution d'un conflit" +msgstr "continuer à appliquer les rustines après résolution d'un conflit" msgid "synonyms for --continue" msgstr "synonymes de --continue" msgid "skip the current patch" -msgstr "sauter le patch courant" +msgstr "sauter la rustine courante" msgid "restore the original branch and abort the patching operation" -msgstr "restaurer la branche originale et abandonner les applications de patch" +msgstr "" +"restaurer la branche originale et abandonner les applications de rustines" msgid "abort the patching operation but keep HEAD where it is" -msgstr "abandonne l'opération de patch mais garde HEAD où il est" +msgstr "abandonne l'opération de rustine mais garde HEAD où il est" msgid "show the patch being applied" -msgstr "afficher le patch en cours d'application" +msgstr "afficher la rustine en cours d'application" msgid "try to apply current patch again" msgstr "essayer d'appliquer de nouveau la rustine" @@ -3581,6 +3592,10 @@ msgstr "git check-mailmap [] ..." msgid "also read contacts from stdin" msgstr "lire aussi les contacts depuis l'entrée standard" +#, c-format +msgid "unable to parse contact: %s" +msgstr "impossible d'analyser le contact : %s" + msgid "read additional mailmap entries from file" msgstr "lire des entrées supplémentaires de mailmap depuis un fichier" @@ -4386,7 +4401,7 @@ msgstr "échec pour délier '%s'" #, c-format msgid "hardlink cannot be checked at '%s'" -msgstr "le lien dur ne peut pas être vérifier à '%s'" +msgstr "le lien dur ne peut pas être vérifié à '%s'" #, c-format msgid "hardlink different from source at '%s'" @@ -5486,7 +5501,7 @@ msgid "value" msgstr "valeur" msgid "use default value when missing entry" -msgstr "utiliser le valeur par défaut quand l'entrée n'existe pas" +msgstr "utiliser la valeur par défaut quand l'entrée n'existe pas" msgid "--fixed-value only applies with 'value-pattern'" msgstr "--fixed-value ne s'applique qu'à 'motif-de-valeur'" @@ -5523,7 +5538,7 @@ msgid "" " Use a regexp, --add or --replace-all to change %s." msgstr "" "impossible de surcharger des valeurs multiples avec une seule valeur\n" -" Utilisez une regexp, --add ou --replace-all pour modifier %s." +" Utilisez une regex, --add ou --replace-all pour modifier %s." #, c-format msgid "no such section: %s" @@ -5549,7 +5564,7 @@ msgid "get all values: key []" msgstr "obtenir toutes les valeurs : clé []" msgid "get values for regexp: name-regex []" -msgstr "obtenir les valeur pour la regexp : name-regex []" +msgstr "obtenir les valeurs pour la regex : name-regex []" msgid "get value specific for the URL: section[.var] URL" msgstr "obtenir la valeur spécifique pour l'URL : section[.var] URL" @@ -5586,7 +5601,7 @@ msgid "find the color setting: slot []" msgstr "trouver le réglage de la couleur : slot []" msgid "with --get, use default value when missing entry" -msgstr "avec --get, utiliser le valeur par défaut quand l'entrée n'existe pas" +msgstr "avec --get, utiliser la valeur par défaut quand l'entrée n'existe pas" msgid "--get-color and variable type are incoherent" msgstr "--get-color et le type de la variable sont incohérents" @@ -5623,7 +5638,7 @@ msgstr "" "Les permissions de votre répertoire de socket sont trop permissives ;\n" "les autres utilisateurs pourraient lire vos identifiants secrets. Lancez :\n" "\n" -" chmod 0700 %s" +"\tchmod 0700 %s" msgid "print debugging messages to stderr" msgstr "afficher les messages de debug sur stderr" @@ -6450,7 +6465,7 @@ msgid "config key storing a list of repository paths" msgstr "clé de config qui stocke la liste des chemins de dépôts" msgid "keep going even if command fails in a repository" -msgstr "continuer mêm si la commande échoue dans un dépôt" +msgstr "continuer même si la commande échoue dans un dépôt" msgid "missing --config=" msgstr "--config= manquant" @@ -6665,7 +6680,7 @@ msgid "check only connectivity" msgstr "ne vérifier que la connectivité" msgid "enable more strict checking" -msgstr "activer une vérification plus strict" +msgstr "activer une vérification plus stricte" msgid "write dangling objects in .git/lost-found" msgstr "écrire les objets en suspens dans .git/lost-found" @@ -6866,7 +6881,7 @@ msgstr "" msgid "" "There are too many unreachable loose objects; run 'git prune' to remove them." msgstr "" -"Il y a trop d'objets seuls inaccessibles ; lancez 'git prune' pour les " +"Il y a trop d'objets esseulés inaccessibles ; lancez 'git prune' pour les " "supprimer." msgid "" @@ -7890,7 +7905,7 @@ msgid "base commit shouldn't be in revision list" msgstr "le commit de base ne devrait pas faire partie de la liste de révisions" msgid "cannot get patch id" -msgstr "impossible d'obtenir l'id du patch" +msgstr "impossible d'obtenir l'id de la rustine" msgid "failed to infer range-diff origin of current series" msgstr "" @@ -7903,13 +7918,13 @@ msgstr "" "utilisation de '%s' comme une différence d'intervalle pour la série actuelle" msgid "use [PATCH n/m] even with a single patch" -msgstr "utiliser [PATCH n/m] même avec un patch unique" +msgstr "utiliser [PATCH n/m] même avec une rustine unique" msgid "use [PATCH] even with multiple patches" -msgstr "utiliser [PATCH] même avec des patchs multiples" +msgstr "utiliser [PATCH] même avec des rustines multiples" msgid "print patches to standard out" -msgstr "afficher les patchs sur la sortie standard" +msgstr "afficher les rustines sur la sortie standard" msgid "generate a cover letter" msgstr "générer une lettre de motivation" @@ -7925,7 +7940,7 @@ msgid "use instead of '.patch'" msgstr "utiliser au lieu de '.patch'" msgid "start numbering patches at instead of 1" -msgstr "démarrer la numérotation des patchs à au lieu de 1" +msgstr "démarrer la numérotation des rustines à au lieu de 1" msgid "reroll-count" msgstr "reroll-count" @@ -7969,10 +7984,10 @@ msgid "output all-zero hash in From header" msgstr "écrire une empreinte à zéro dans l'entête From" msgid "don't include a patch matching a commit upstream" -msgstr "ne pas inclure un patch correspondant à un commit amont" +msgstr "ne pas inclure de rustine correspondant à un commit amont" msgid "show patch format instead of default (patch + stat)" -msgstr "afficher le format du patch au lieu du défaut (patch + stat)" +msgstr "afficher le format de la rustine au lieu du défaut (rustine + stat)" msgid "Messaging" msgstr "Communication" @@ -8009,10 +8024,10 @@ msgid "boundary" msgstr "limite" msgid "attach the patch" -msgstr "attacher le patch" +msgstr "attacher la rustine" msgid "inline the patch" -msgstr "patch à l'intérieur" +msgstr "incorporer la rustine à l'intérieur" msgid "enable message threading, styles: shallow, deep" msgstr "" @@ -8028,17 +8043,17 @@ msgid "base-commit" msgstr "commit-de-base" msgid "add prerequisite tree info to the patch series" -msgstr "ajouter un arbre prérequis à la série de patchs" +msgstr "ajouter un arbre prérequis à la série de rustines" msgid "add a signature from a file" msgstr "ajouter une signature depuis un fichier" msgid "don't print the patch filenames" -msgstr "ne pas afficher les noms de fichiers des patchs" +msgstr "ne pas afficher les noms de fichiers des rustines" msgid "show progress while generating patches" msgstr "" -"afficher la barre de progression durant la phase de génération des patchs" +"afficher la barre de progression durant la phase de génération des rustines" msgid "show changes against in cover letter or single patch" msgstr "" @@ -8103,7 +8118,7 @@ msgid "unable to read signature file '%s'" msgstr "lecture du fichier de signature '%s' impossible" msgid "Generating patches" -msgstr "Génération des patchs" +msgstr "Génération des rustines" msgid "failed to create output files" msgstr "échec de création des fichiers en sortie" @@ -8221,7 +8236,7 @@ msgid "" " [-q | --quiet] [--exit-code] [--get-url] [--sort=]\n" " [--symref] [ [...]]" msgstr "" -"git ls-remote [--brances] [--tags] [--refs] [--upload-pack=]\n" +"git ls-remote [--branches] [--tags] [--refs] [--upload-pack=]\n" " [-q | --quiet] [--exit-code] [--get-url] [--sort=]\n" " [--symref] [ [...]]" @@ -10548,7 +10563,7 @@ msgid "continue" msgstr "continuer" msgid "skip current patch and continue" -msgstr "sauter le patch courant et continuer" +msgstr "sauter la rustine courante et continuer" msgid "abort and check out the original branch" msgstr "abandonner et extraire la branche d'origine" @@ -10560,7 +10575,7 @@ msgid "edit the todo list during an interactive rebase" msgstr "éditer la liste à faire lors d'un rebasage interactif" msgid "show the patch file being applied or merged" -msgstr "afficher le patch en cours d'application ou de fusion" +msgstr "afficher la rustine en cours d'application ou de fusion" msgid "use apply strategies to rebase" msgstr "utiliser des stratégies d'application pour rebaser" @@ -10943,7 +10958,7 @@ msgid "repository already uses '%s' format" msgstr "le dépôt utilise déjà le format '%s'" msgid "enable strict checking" -msgstr "activer une vérification plus strict" +msgstr "activer une vérification plus stricte" msgid "'git refs verify' takes no arguments" msgstr "'git refs verify' n'accepte aucun argument" @@ -12454,7 +12469,7 @@ msgid "" "directory '%s' contains untracked files, but is not in the sparse-checkout " "cone" msgstr "" -"le dossier '%s' contient des fichiers non-suivis, mais n'est pas dans le " +"le répertoire '%s' contient des fichiers non-suivis, mais n'est pas dans le " "cone d'extraction clairsemée" #, c-format @@ -13191,7 +13206,7 @@ msgid "" "the superproject is not on any branch" msgstr "" "La branche du sous-module %s est configurée pour hériter de la branche du " -"superprojet, mais le superprojet n'est sur aucune branche" +"super-projet, mais le super-projet n'est sur aucune branche" #, c-format msgid "Unable to find current revision in submodule path '%s'" @@ -14409,13 +14424,13 @@ msgid "Add file contents to the index" msgstr "Ajouter le contenu de fichiers dans l'index" msgid "Apply a series of patches from a mailbox" -msgstr "Appliquer une série de patchs depuis une boîte mail" +msgstr "Appliquer une série de rustines depuis une boîte mail" msgid "Annotate file lines with commit information" msgstr "Annoter les lignes du fichier avec l'information de commit" msgid "Apply a patch to files and/or to the index" -msgstr "Appliquer une patch à des fichiers ou à l'index" +msgstr "Appliquer une rustine à des fichiers ou à l'index" msgid "Import a GNU Arch repository into Git" msgstr "Importer dans Git un dépôt GNU Arch" @@ -14567,7 +14582,7 @@ msgid "Run a Git command on a list of repositories" msgstr "Lance une commande Git sur une liste de dépôts" msgid "Prepare patches for e-mail submission" -msgstr "Préparer les patchs pour soumission par courriel" +msgstr "Préparer les rustines pour soumission par courriel" msgid "Verifies the connectivity and validity of the objects in the database" msgstr "" @@ -14637,7 +14652,7 @@ msgstr "Afficher le contenu d'un objet arbre" msgid "Extracts patch and authorship from a single e-mail message" msgstr "" -"Extraire le patch et l'information de d'auteur depuis un simple message de " +"Extraire la rustine et l'information de l'auteur depuis un simple message de " "courriel" msgid "Simple UNIX mbox splitter program" @@ -14700,7 +14715,7 @@ msgid "Pack heads and tags for efficient repository access" msgstr "Empaqueter les têtes et les étiquettes pour un accès efficace au dépôt" msgid "Compute unique ID for a patch" -msgstr "Calculer l'ID unique d'un patch" +msgstr "Calculer l'ID unique d'une rustine" msgid "Prune all unreachable objects from the object database" msgstr "Élaguer les objets inatteignables depuis la base de données des objets" @@ -14775,7 +14790,7 @@ msgid "Remove files from the working tree and from the index" msgstr "Supprimer des fichiers de la copie de travail et de l'index" msgid "Send a collection of patches as emails" -msgstr "Envoyer un ensemble de patchs comme courriels" +msgstr "Envoyer un ensemble de rustines comme courriels" msgid "Push objects over Git protocol to another repository" msgstr "Pousser les objets sur un autre dépôt via le protocole Git" @@ -15060,7 +15075,7 @@ msgid "" "disabling Bloom filters for commit-graph layer '%s' due to incompatible " "settings" msgstr "" -"désactivation des filtres de Bloom opur la couche de graphe de commits '%s' " +"désactivation des filtres de Bloom pour la couche de graphe de commits '%s' " "à cause de réglages incompatibles" msgid "commit-graph has no base graphs chunk" @@ -16211,7 +16226,7 @@ msgstr "impossible de charger la regex île pour '%s' : %s" #, c-format msgid "island regex from config has too many capture groups (max=%d)" msgstr "" -"l'expression rationnelle depuis la configuration a trop de groupes de " +"l'expression régulière depuis la configuration a trop de groupes de " "capture (max=%d)" #, c-format @@ -16736,7 +16751,7 @@ msgstr "" msgid "treat in -S as extended POSIX regular expression" msgstr "" -"traiter dans -S comme une expression rationnelle POSIX étendue" +"traiter dans -S comme une expression régulière POSIX étendue" msgid "control the order in which files appear in the output" msgstr "contrôler l'ordre dans lequel les fichiers apparaissent dans la sortie" @@ -18284,7 +18299,7 @@ msgstr "échec de l'écriture de l'index de multi-paquet" msgid "cannot expire packs from an incremental multi-pack-index" msgstr "" -"impossible d'expirer les paquets dpuis un index multi-paquet incrémental" +"impossible d'expirer les paquets depuis un index multi-paquet incrémental" msgid "Counting referenced objects" msgstr "Comptage des objets référencés" @@ -18293,7 +18308,7 @@ msgid "Finding and deleting unreferenced packfiles" msgstr "Recherche et effacement des fichiers paquets non-référencés" msgid "cannot repack an incremental multi-pack-index" -msgstr "impossible de ré-empaqueter un index multi-paquet" +msgstr "impossible de ré-empaqueter un index multi-paquet incrémental" msgid "could not start pack-objects" msgstr "impossible de démarrer le groupement d'objets" @@ -19270,7 +19285,7 @@ msgid "use digits to display object names" msgstr "utiliser chiffres pour afficher les noms des objets" msgid "prefixed path to initial superproject" -msgstr "chemin préfixé vers le superprojet initial" +msgstr "chemin préfixé vers le super-projet initial" msgid "how to strip spaces and #comments from message" msgstr "comment éliminer les espaces et les commentaires # du message" @@ -19484,7 +19499,7 @@ msgstr "" msgid "" "pseudo-merge regex from config has too many capture groups (max=%)" msgstr "" -"l'expression rationnelle de pseudo-fusion a trop de groupes de capture " +"l'expression régulière de pseudo-fusion a trop de groupes de capture " "(max=%)" #, c-format @@ -21359,7 +21374,7 @@ msgstr "impossible d'appliquer %s... %s" #, c-format msgid "dropping %s %s -- patch contents already upstream\n" -msgstr "abandon de %s %s -- le contenu de la rustine déjà en amont\n" +msgstr "abandon de %s %s -- le contenu de la rustine est déjà en amont\n" #, c-format msgid "git %s: failed to read the index" @@ -23266,13 +23281,13 @@ msgid "You are in the middle of an am session." msgstr "Vous êtes au milieu d'une session am." msgid "The current patch is empty." -msgstr "Le patch actuel est vide." +msgstr "La rustine actuelle est vide." msgid " (fix conflicts and then run \"git am --continue\")" msgstr " (réglez les conflits puis lancez \"git am --continue\")" msgid " (use \"git am --skip\" to skip this patch)" -msgstr " (utilisez \"git am --skip\" pour sauter ce patch)" +msgstr " (utilisez \"git am --skip\" pour sauter cette rustine)" msgid "" " (use \"git am --allow-empty\" to record this patch as an empty commit)" @@ -23322,7 +23337,7 @@ msgid " (fix conflicts and then run \"git rebase --continue\")" msgstr " (réglez les conflits puis lancez \"git rebase --continue\")" msgid " (use \"git rebase --skip\" to skip this patch)" -msgstr " (utilisez \"git rebase --skip\" pour sauter ce patch)" +msgstr " (utilisez \"git rebase --skip\" pour sauter cette rustine)" msgid " (use \"git rebase --abort\" to check out the original branch)" msgstr " (utilisez \"git rebase --abort\" pour extraire la branche d'origine)" @@ -23382,7 +23397,7 @@ msgstr "" " (tous les conflits sont réglés : lancez \"git cherry-pick --continue\")" msgid " (use \"git cherry-pick --skip\" to skip this patch)" -msgstr " (utilisez \"git cherry-pick --skip\" pour sauter ce patch)" +msgstr " (utilisez \"git cherry-pick --skip\" pour sauter cette rustine)" msgid " (use \"git cherry-pick --abort\" to cancel the cherry-pick operation)" msgstr " (utilisez \"git cherry-pick --abort\" pour annuler le picorage)" @@ -23404,7 +23419,7 @@ msgid " (all conflicts fixed: run \"git revert --continue\")" msgstr " (tous les conflits sont réglés : lancez \"git revert --continue\")" msgid " (use \"git revert --skip\" to skip this patch)" -msgstr " (utilisez \"git revert --skip\" pour sauter ce patch)" +msgstr " (utilisez \"git revert --skip\" pour sauter cette rustine)" msgid " (use \"git revert --abort\" to cancel the revert operation)" msgstr " (utilisez \"git revert --abort\" pour annuler le rétablissement)" @@ -23728,7 +23743,7 @@ msgid "" "\n" msgstr "" "\n" -"Aucun fichier patch spécifié !\n" +"Aucun fichier de rustine spécifié !\n" "\n" #, perl-format @@ -23748,7 +23763,7 @@ msgid "" msgstr "" "Les lignes commençant par \"GIT:\" seront supprimées.\n" "Envisagez d'inclure un diffstat global ou une table des matières\n" -"pour le patch que vous êtes en train d'écrire.\n" +"pour la rustine que vous êtes en train d'écrire.\n" "\n" "Effacez le corps si vous ne souhaitez pas envoyer un résumé.\n" @@ -23785,7 +23800,7 @@ msgid "" "has the template subject '*** SUBJECT HERE ***'. Pass --force if you really " "want to send.\n" msgstr "" -"Envoi refusé parce que le patch\n" +"Envoi refusé parce que la rustine\n" "\t%s\n" "a un sujet modèle '*** SUBJECT HERE ***'. Passez --force is vous souhaitez " "vraiment envoyer.\n" @@ -23938,7 +23953,7 @@ msgid "" msgstr "" "fatal : %s : rejeté par le crochet %s\n" "%s\n" -"attention : aucun patch envoyé\n" +"attention : aucune rustine envoyée\n" #, perl-format msgid "unable to open %s: %s\n" @@ -23950,7 +23965,7 @@ msgid "" "warning: no patches were sent\n" msgstr "" "fatal : %s : %d est plus long que 998 caractères \n" -"attention : aucun patch envoyé\n" +"attention : aucune rustine envoyée\n" #, perl-format msgid "Skipping %s with backup suffix '%s'.\n" @@ -23960,230 +23975,3 @@ msgstr "%s sauté avec un suffix de sauvegarde '%s'.\n" #, perl-format msgid "Do you really want to send %s? [y|N]: " msgstr "Souhaitez-vous réellement envoyer %s ?[y|N] : " - -#~ msgid "revision walk setup failed\n" -#~ msgstr "échec de la préparation du parcours des révisions\n" - -#, c-format -#~ msgid "unable to parse contact: %s" -#~ msgstr "impossible d'analyser le contact : %s" - -#, c-format -#~ msgid "truncating .rej filename to %.*s.rej" -#~ msgstr "troncature du nom de fichier .rej en %.*s.rej" - -#~ msgid "" -#~ "the add.interactive.useBuiltin setting has been removed!\n" -#~ "See its entry in 'git help config' for details." -#~ msgstr "" -#~ "le réglage add.interactive.useBuiltin a été supprimé !\n" -#~ "Référez-vous à cette entrée dans 'git help config' pour plus de détails." - -#~ msgid "git archive: Remote with no URL" -#~ msgstr "git archive : Dépôt distant sans URL" - -#~ msgid "only one action at a time" -#~ msgstr "une seule action à la fois" - -#~ msgid "use [RFC PATCH] instead of [PATCH]" -#~ msgstr "utiliser [RFC PATCH] au lieu de [PATCH]" - -#, c-format -#~ msgid "no URLs configured for remote '%s'" -#~ msgstr "aucune URL configurée pour le dépôt distant '%s'" - -#, c-format -#~ msgid "remote '%s' has no configured URL" -#~ msgstr "le distant '%s' n'a pas d'URL configuré" - -#~ msgid "" -#~ "Use -f if you really want to add them.\n" -#~ "Turn this message off by running\n" -#~ "\"git config advice.addIgnoredFile false\"" -#~ msgstr "" -#~ "Utilisez -f si vous voulez vraiment les ajouter.\n" -#~ "Éliminez ce message en lançant\n" -#~ "\"git config advice.addIgnoredFile false\"" - -#~ msgid "" -#~ "Maybe you wanted to say 'git add .'?\n" -#~ "Turn this message off by running\n" -#~ "\"git config advice.addEmptyPathspec false\"" -#~ msgstr "" -#~ "Peut-être avez-vous voulu dire 'git add .' ?\n" -#~ "Éliminez ce message en lançant\n" -#~ "\"git config advice.addEmptyPathspec false\"" - -#~ msgid "" -#~ "clean.requireForce defaults to true and neither -i, -n, nor -f given; " -#~ "refusing to clean" -#~ msgstr "" -#~ "clean.requireForce à true par défaut et ni -i, -n ou -f fourni ; refus de " -#~ "nettoyer" - -#, c-format -#~ msgid "bad ls-files format: element '%s' does not start with '('" -#~ msgstr "mauvais format ls-files : l'élément '%s' ne commence pas par '('" - -#, c-format -#~ msgid "bad ls-files format: element '%s' does not end in ')'" -#~ msgstr "mauvais format ls-files : l'élément '%s' ne se termine pas par ')'" - -#, c-format -#~ msgid "bad ls-files format: %%%.*s" -#~ msgstr "mauvais format ls-files : %%%.*s" - -#~ msgid "keep redundant, empty commits" -#~ msgstr "garder les validations redondantes, vides" - -#~ msgid "core.commentChar should only be one ASCII character" -#~ msgstr "core.commentChar ne devrait être qu'un unique caractère ASCII" - -#~ msgid "" -#~ "--bundle-uri is incompatible with --depth, --shallow-since, and --shallow-" -#~ "exclude" -#~ msgstr "" -#~ "--bundle-uri est incompatible avec --depth, --shallow-since, et --shallow-" -#~ "exclude" - -#~ msgid "--merge-base is incompatible with --stdin" -#~ msgstr "--merge-base est incompatible avec --stdin" - -#~ msgid "" -#~ "apply options are incompatible with rebase.autoSquash. Consider adding --" -#~ "no-autosquash" -#~ msgstr "" -#~ "les options d'application sont incompatibles avec rebase.autoSquash. " -#~ "Considérez l'ajout de --no-autosquash" - -#~ msgid "--exclude-hidden cannot be used together with --branches" -#~ msgstr "--exclude-hidden ne peut être utilisé avec --branches" - -#~ msgid "--exclude-hidden cannot be used together with --tags" -#~ msgstr "--exclude-hidden ne peut pas être utilisé avec --tags" - -#~ msgid "--exclude-hidden cannot be used together with --remotes" -#~ msgstr "--exclude-hidden ne peut pas être utilisé avec --remotes" - -#, c-format -#~ msgid "only one of '%s', '%s' or '%s' can be given" -#~ msgstr "les options '%s', '%s' et '%s' sont mutuellement exclusives" - -#, c-format -#~ msgid "'%s' and '%s' cannot be used together" -#~ msgstr "'%s' et '%s' ne peuvent pas être utilisées ensemble" - -#, c-format -#~ msgid "options '%s', and '%s' cannot be used together" -#~ msgstr "les options '%s' et '%s' ne peuvent pas être utilisées ensemble" - -#~ msgid "" -#~ msgstr "" - -#, c-format -#~ msgid "%s is incompatible with %s" -#~ msgstr "%s est incompatible avec %s" - -#~ msgid "unhandled options" -#~ msgstr "options non gérées" - -#, c-format -#~ msgid "options '%s=%s' and '%s=%s' cannot be used together" -#~ msgstr "" -#~ "les options '%s=%s' et '%s=%s' ne peuvent pas être utilisées ensemble" - -#, c-format -#~ msgid "%s : incompatible with something else" -#~ msgstr "%s est incompatible avec toute autre option" - -#~ msgid "Could not write patch" -#~ msgstr "Impossible d'écrire le patch" - -#, c-format -#~ msgid "Could not stat '%s'" -#~ msgstr "Stat de '%s' impossible" - -#, c-format -#~ msgid "Cannot delete branch '%s' checked out at '%s'" -#~ msgstr "Impossible de supprimer la branche '%s' extraite dans '%s'" - -#~ msgid "unable to write new_index file" -#~ msgstr "impossible d'écrire le fichier new_index" - -#~ msgid "do not apply config rules" -#~ msgstr "ne pas appliquer les règles de la configuration" - -#~ msgid "join whitespace-continued values" -#~ msgstr "joindre les valeurs continuées avec des caractères blancs" - -#~ msgid "set parsing options" -#~ msgstr "paramètres d'analyse" - -#~ msgid "cannot move directory over file" -#~ msgstr "impossible de déplacer un répertoire sur un fichier" - -#~ msgid "cannot use --filter without --stdout" -#~ msgstr "impossible d'utiliser --filter sans --stdout" - -#~ msgid "cannot use --max-pack-size with --cruft" -#~ msgstr "impossible d'utiliser --max-pack-size avec --cruft" - -#~ msgid "--strategy requires --merge or --interactive" -#~ msgstr "--strategy requiert --merge ou --interactive" - -#, c-format -#~ msgid "" -#~ "commit-graph has generation number zero for commit %s, but non-zero " -#~ "elsewhere" -#~ msgstr "" -#~ "le graphe de commit a un numéro de génération nul pour le commit %s, mais " -#~ "non-nul ailleurs" - -#~ msgid "--merge-base only works with commits" -#~ msgstr "--merge-base ne fonctionne qu'avec des commits" - -#~ msgid "scalar clone [] [--] []" -#~ msgstr "scalar clone [] [--] []" - -#, c-format -#~ msgid "could not rename '%s' to '%s'" -#~ msgstr "impossible de renommer '%s' en '%s'" - -#, c-format -#~ msgid "It is not possible to %s because you have unmerged files." -#~ msgstr "%s n'est pas possible car vous avez des fichiers non fusionnés." - -#~ msgid "do not pass --keep-cr flag to git-mailsplit independent of am.keepcr" -#~ msgstr "" -#~ "ne pas passer l'option --keep-cr à git-mailsplit indépendamment de am." -#~ "keepcr" - -#~ msgid "" -#~ "Updates were rejected because the tip of the remote-tracking\n" -#~ "branch has been updated since the last checkout. You may want\n" -#~ "to integrate those changes locally (e.g., 'git pull ...')\n" -#~ "before forcing an update.\n" -#~ msgstr "" -#~ "Les mises à jour ont été rejetées, car la pointe de la branche\n" -#~ "de suivi a été mise à jour depuis la dernière extraction. Intégrez\n" -#~ "ces changements localement (par exemple 'git pull ...') avant de\n" -#~ "forcer à nouveau une mise à jour.\n" - -#~ msgid "or do not fetch any tag at all (--no-tags)" -#~ msgstr "ou ne rapatrier aucune étiquette (--no-tags)" - -#~ msgid "current working directory is untracked" -#~ msgstr "l'arbre de travail actuel est non-suivi" - -#~ msgid "cannot use --contents with final commit object name" -#~ msgstr "on ne peut pas utiliser --contents avec un nom d'objet commit final" - -#~ msgid "please commit or stash them." -#~ msgstr "veuillez les valider ou les remiser." - -#, c-format -#~ msgid "Unknown mode: %s" -#~ msgstr "Mode inconnu : %s" - -#~ msgid "could not lock HEAD" -#~ msgstr "impossible de verrouiller HEAD" From 40fdd46b7f90b40f58a2b9f4bba5735c29dc82a8 Mon Sep 17 00:00:00 2001 From: Alexander Shopov Date: Sat, 21 Dec 2024 18:30:14 +0100 Subject: [PATCH 014/306] l10n: bg.po: Updated Bulgarian translation (5804t) Signed-off-by: Alexander Shopov --- po/bg.po | 357 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 270 insertions(+), 87 deletions(-) diff --git a/po/bg.po b/po/bg.po index 1f7222dd9321eb..2fc6c2b60ca2e9 100644 --- a/po/bg.po +++ b/po/bg.po @@ -225,6 +225,14 @@ # mailmap файл за съответствията на имената и адресите на е-поща # unit test поединичен тест # test suite група тестове +# object database базата от данни за обектите +# expecting integer изисква се число +# timeout максимално изчакване +# init timeout максимално първоначално изчакване +# implies option включва опцията +# cache-tree кеша на обектите-дървета +# acquire lock придобивам ключалка +# detached отделѐн, несвързан # # ------------------------ # „$var“ - може да не сработва за shell има gettext и eval_gettext - проверка - намират се лесно по „$ @@ -251,10 +259,10 @@ # for i in `sort -u FILES`; do cnt=`grep $i FILES | wc -l`; echo $cnt $i ;done | sort -n msgid "" msgstr "" -"Project-Id-Version: git 2.45\n" +"Project-Id-Version: git 2.48\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-05 01:20+0000\n" -"PO-Revision-Date: 2024-10-05 13:20+0200\n" +"POT-Creation-Date: 2024-12-27 22:37+0100\n" +"PO-Revision-Date: 2024-12-27 22:40+0100\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -564,8 +572,8 @@ msgstr "" #, c-format msgid "Discard mode change from worktree [y,n,q,a,d%s,?]? " msgstr "" -"Премахване на промяната в права̀та за достъп от работното дърво [y,n,q,a," -"d%s,?]? " +"Премахване на промяната в права̀та за достъп от работното дърво " +"[y,n,q,a,d%s,?]? " #, c-format msgid "Discard deletion from worktree [y,n,q,a,d%s,?]? " @@ -605,8 +613,8 @@ msgstr "" #, c-format msgid "Discard mode change from index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Премахване на промяната в права̀та за достъп от индекса и работното дърво [y," -"n,q,a,d%s,?]? " +"Премахване на промяната в права̀та за достъп от индекса и работното дърво " +"[y,n,q,a,d%s,?]? " #, c-format msgid "Discard deletion from index and worktree [y,n,q,a,d%s,?]? " @@ -641,8 +649,8 @@ msgstr "" #, c-format msgid "Apply mode change to index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Прилагане на промяната в права̀та за достъп от индекса и работното дърво [y,n," -"q,a,d%s,?]? " +"Прилагане на промяната в права̀та за достъп от индекса и работното дърво " +"[y,n,q,a,d%s,?]? " #, c-format msgid "Apply deletion to index and worktree [y,n,q,a,d%s,?]? " @@ -676,8 +684,8 @@ msgstr "" #, c-format msgid "Apply mode change to worktree [y,n,q,a,d%s,?]? " msgstr "" -"Прилагане на промяната в права̀та за достъп към работното дърво [y,n,q,a," -"d%s,?]? " +"Прилагане на промяната в права̀та за достъп към работното дърво " +"[y,n,q,a,d%s,?]? " #, c-format msgid "Apply deletion to worktree [y,n,q,a,d%s,?]? " @@ -893,12 +901,12 @@ msgstr "Променени са само двоични файлове." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" "За да изключите това предупреждение, изпълнете:\n" "\n" -" git config advice.%s false" +" git config set advice.%s false" #, c-format msgid "%shint:%s%.*s%s\n" @@ -1016,8 +1024,8 @@ msgstr "" "\n" " git switch -\n" "\n" -"Може да спрете това съобщение със задаване на настройката „advice." -"detachedHead“\n" +"Може да спрете това съобщение със задаване на настройката " +"„advice.detachedHead“\n" "да е „false“ (лъжа̀).\n" #, c-format @@ -1247,7 +1255,7 @@ msgstr "двоичната кръпка не може да се приложи #, c-format msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)" msgstr "" -"двоичната кръпка за „%s“ води до неправилни резултати (очакваше се: „%s“, а " +"двоичната кръпка за „%s“ води до неправилни резултати (изисква се: „%s“, а " "бе получено: „%s“)" #, c-format @@ -2651,9 +2659,9 @@ msgid "" "git bisect start [--term-(new|bad)= --term-(old|good)=] [--no-" "checkout] [--first-parent] [ [...]] [--] [...]" msgstr "" -"git bisect start [--term-(new,bad)=УПРАВЛЯВАЩА_ДУМА --term-(old," -"good)=УПРАВЛЯВАЩА_ДУМА] [--no-checkout] [--first-parent] [ЛОШО [ДОБРО…]] " -"[--] [ПЪТ…]" +"git bisect start [--term-(new,bad)=УПРАВЛЯВАЩА_ДУМА --term-" +"(old,good)=УПРАВЛЯВАЩА_ДУМА] [--no-checkout] [--first-parent] [ЛОШО " +"[ДОБРО…]] [--] [ПЪТ…]" msgid "git bisect (good|bad) [...]" msgstr "git bisect (good|bad) [ВЕРСИЯ…]" @@ -2936,7 +2944,7 @@ msgstr "ОПЦИИте_ЗА_ВЕРСИЯТА са документирани в #, c-format msgid "expecting a color: %s" -msgstr "трябва да е цвят: %s" +msgstr "изисква се цвят: %s" msgid "must end with a color" msgstr "трябва да завършва с цвят" @@ -3349,8 +3357,8 @@ msgid "HEAD not found below refs/heads!" msgstr "В директорията „refs/heads“ липсва файл „HEAD“" msgid "" -"branch with --recurse-submodules can only be used if submodule." -"propagateBranches is enabled" +"branch with --recurse-submodules can only be used if " +"submodule.propagateBranches is enabled" msgstr "" "може да се ползва клон с опцията „--recurse-submodules“, само ако " "настройката „submodule.propagateBranches“ е зададена" @@ -3653,8 +3661,8 @@ msgstr "позволяване на опциите „-s“ и „-t“ да р msgid "use mail map file" msgstr "" -"използване на файл за съответствията на имената и адресите на е-поща („." -"mailmap“)" +"използване на файл за съответствията на имената и адресите на е-поща " +"(„.mailmap“)" msgid "Batch objects requested on stdin (or --batch-all-objects)" msgstr "" @@ -4206,7 +4214,7 @@ msgstr "" "„zdiff3“)" msgid "detach HEAD at named commit" -msgstr "отделяне на указателя „HEAD“ към указаното подаване" +msgstr "отделяне на указателя „HEAD“ при указаното подаване" msgid "force checkout (throw away local modifications)" msgstr "принудително изтегляне (вашите промѐни ще бъдат занулени)" @@ -4220,8 +4228,8 @@ msgstr "нов неродѐн клон" msgid "update ignored files (default)" msgstr "обновяване на игнорираните файлове (стандартно)" -msgid "do not check if another worktree is holding the given ref" -msgstr "без проверка дали друго работно дърво държи указателя" +msgid "do not check if another worktree is using this branch" +msgstr "без проверка дали друго работно дърво ползва този клон" msgid "checkout our version for unmerged files" msgstr "изтегляне на вашата версия на неслетите файлове" @@ -4525,10 +4533,10 @@ msgstr "плитко клониране до тази ДЪЛБОЧИНА" msgid "create a shallow clone since a specific time" msgstr "плитко клониране до момент във времето" -msgid "revision" -msgstr "ВЕРСИЯ" +msgid "ref" +msgstr "УКАЗ" -msgid "deepen history of shallow clone, excluding rev" +msgid "deepen history of shallow clone, excluding ref" msgstr "задълбочаване на историята на плитко хранилище до изключващ указател" msgid "clone only one branch, HEAD or --branch" @@ -5509,9 +5517,10 @@ msgstr "" msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" -"git config unset [ОПЦИЯ_ЗА_ФАЙЛ] [--all] [--value=СТОЙНОСТ] [--fixed-value]" +"git config unset [ОПЦИЯ_ЗА_ФАЙЛ] [--all] [--value=СТОЙНОСТ] [--fixed-value] " +"ИМЕ" msgid "git config rename-section [] " msgstr "git config rename-section [ОПЦИЯ_ЗА_ФАЙЛ] СТАРО_ИМЕ НОВО_ИМЕ" @@ -5963,12 +5972,8 @@ msgid "traversed %lu commits\n" msgstr "обходени са %lu подавания\n" #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"открити са над %i етикета, изведени са последните %i,\n" -"търсенето бе прекратено при „%s“.\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "открити са %i етикета. Търсенето приключи при „%s“\n" #, c-format msgid "describe %s\n" @@ -6414,6 +6419,27 @@ msgstr "„%s“ е неправилен обект" msgid "the object %s does not exist" msgstr "обектът „%s“ не съществува" +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Изпълнете\n" +"\n" +" git remote set-head %s %s\n" +"\n" +"за да следвате промяната или задайте друга стойност на настройката\n" +"„remote.%s.followRemoteHEAD, ако не искате тези съобщения.\n" +"Изпълнението на\n" +"\n" +" git config set remote.%s.followRemoteHEAD %s\n" +"\n" +"ще изключи предупреждението, докато отдалеченият указател HEAD не\n" +"започне да сочи нещо друго." + msgid "multiple branches detected, incompatible with --set-upstream" msgstr "" "засечени са множество клони, това е несъвместимо с опцията „--set-upstream“" @@ -6553,6 +6579,9 @@ msgstr "КАРТА_С_УКАЗАТЕЛИ" msgid "specify fetch refmap" msgstr "указване на КАРТАта_С_УКАЗАТЕЛИ за доставяне" +msgid "revision" +msgstr "ВЕРСИЯ" + msgid "report that we have only objects reachable from this object" msgstr "докладване, че всички обекти може са достижими при започване от този" @@ -6608,8 +6637,8 @@ msgid "protocol does not support --negotiate-only, exiting" msgstr "протоколът не поддържа опцията „--negotiate-only“, изход от програмата" msgid "" -"--filter can only be used with the remote configured in extensions." -"partialclone" +"--filter can only be used with the remote configured in " +"extensions.partialclone" msgstr "" "опцията „--filter“ може да се ползва само с отдалеченото хранилище указано в " "настройката „extensions.partialclone“" @@ -6859,7 +6888,7 @@ msgstr "„%s“ сочи към нещо необичайно (%s)" #, c-format msgid "%s: detached HEAD points at nothing" -msgstr "%s: несвързаният връх „HEAD“ не сочи към нищо" +msgstr "%s: отделеният връх „HEAD“ не сочи към нищо" #, c-format msgid "notice: %s points to an unborn branch (%s)" @@ -7169,8 +7198,8 @@ msgstr "неуспешно изпълнение на „git multi-pack-index rep msgid "" "skipping incremental-repack task because core.multiPackIndex is disabled" msgstr "" -"задачата „incremental-repack“ се прескача, защото настройката „core." -"multiPackIndex“ е изключена" +"задачата „incremental-repack“ се прескача, защото настройката " +"„core.multiPackIndex“ е изключена" #, c-format msgid "lock file '%s' exists, skipping maintenance" @@ -7291,8 +7320,25 @@ msgstr "липсват както таймери на systemd, така и cront msgid "%s scheduler is not available" msgstr "планиращият модул „%s“ липсва" -msgid "another process is scheduling background maintenance" -msgstr "друг процес задава поддръжката на заден фон" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"Файлът-ключалка „%s.lock“ не може да бъде създаден: %s\n" +"\n" +"Изглежда, че и друг процес на git-maintenance(1) е пуснат в това\n" +"хранилище. Уверете се, че всички подобни процеси са спрени и опитайте\n" +"отново. Ако това не помогне, вероятната причина е, че някой процес на\n" +"git-maintenance(1) в това хранилище е забил. За да продължите работа,\n" +"ще трябва ръчно да изтриете файла:" + +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "не може да се придобие ключалката за поддръжката на заден фон" msgid "git maintenance start [--scheduler=]" msgstr "git maintenance start [--scheduler=ПЛАНИРАЩ_МОДУЛ]" @@ -7876,6 +7922,24 @@ msgid_plural "chain length = %d: %lu objects" msgstr[0] "дължината на веригата е %d: %lu обект" msgstr[1] "дължината на веригата е %d: %lu обекта" +msgid "could not start pack-objects to repack local links" +msgstr "" +"командата „pack-objects“ не може да се стартира за препакетирането на " +"локалните връзки" + +msgid "failed to feed local object to pack-objects" +msgstr "локалните обекти не може да се подадат на командата „pack-objects“" + +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack: от „pack-objects“ се изискват редове само с пълни шестнайсетични " +"указатели." + +msgid "could not finish pack-objects to repack local links" +msgstr "" +"командата „pack-objects“ не може да завърши за препакетирането на локалните " +"връзки" + msgid "Cannot come back to cwd" msgstr "Процесът не може да се върне към предишната работна директория" @@ -7887,6 +7951,9 @@ msgstr "неправилна стойност „%s“" msgid "unknown hash algorithm '%s'" msgstr "непознат алгоритъм за контролни суми „%s“" +msgid "--promisor cannot be used with a pack name" +msgstr "опцията „--promisor“ е несъвместима с име на пакет" + msgid "--stdin requires a git repository" msgstr "„--stdin“ изисква хранилище на git" @@ -9283,11 +9350,14 @@ msgstr "git notes [--ref УКАЗАТЕЛ_ЗА_БЕЛЕЖКА] [list [ОБЕКТ msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" +"git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" +"separator=] [--[no-]stripspace] [-m | -F | (-c " +"| -C) ] [] [-e]\n" "git notes [--ref УКАЗАТЕЛ_ЗА_БЕЛЕЖКА] add [-f] [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace] [-m " -"СЪОБЩЕНИЕ|-F ФАЙЛ|(-c|-C) ОБЕКТ] [ОБЕКТ]" +"СЪОБЩЕНИЕ|-F ФАЙЛ|(-c|-C) ОБЕКТ] [ОБЕКТ] [-e]" msgid "git notes [--ref ] copy [-f] " msgstr "" @@ -9296,11 +9366,11 @@ msgstr "" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref УКАЗАТЕЛ_ЗА_БЕЛЕЖКА] append [--allow-empty] [--" "[no-]separator|--separator=КРАЙ_НА_АБЗАЦ] [--[no-]stripspace] [-m СЪОБЩЕНИЕ " -"| -F ФАЙЛ|(-c|-C) ОБЕКТ] [ОБЕКТ]" +"| -F ФАЙЛ|(-c|-C) ОБЕКТ] [ОБЕКТ] [-e]" msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref УКАЗАТЕЛ_ЗА_БЕЛЕЖКА] edit [--allow-empty] [ОБЕКТ]" @@ -9422,6 +9492,9 @@ msgstr "ФАЙЛ със съдържанието на бележката" msgid "reuse and edit specified note object" msgstr "преизползване и редактиране на указания ОБЕКТ-бележка" +msgid "edit note message in editor" +msgstr "редактиране на съобщението в редактора" + msgid "reuse specified note object" msgstr "преизползване на указания ОБЕКТ-бележка" @@ -9660,7 +9733,7 @@ msgstr "неуспешно записване на индекси на база #, c-format msgid "wrote % objects while expecting %" -msgstr "бяха записани % обекти, а се очакваха %" +msgstr "бяха записани % обекти, а се изискват %" msgid "disabling bitmap writing, as some objects are not being packed" msgstr "" @@ -9940,6 +10013,9 @@ msgstr "как да се обработват липсващите обекти" msgid "do not pack objects in promisor packfiles" msgstr "без пакетиране на обекти в гарантиращи пакети" +msgid "implies --missing=allow-any" +msgstr "включва опцията „--missing=allow-any“" + msgid "respect islands during delta compression" msgstr "без промяна на групите при делта компресия" @@ -10315,7 +10391,7 @@ msgid "" " git push %s HEAD:\n" msgstr "" "В момента не сте на никой клон. За да изтласкате историята до състоянието,\n" -"сочено в момента от указателя „HEAD“, използвайте командата:\n" +"сочено в момента от (несвързания) указател „HEAD“, използвайте командата:\n" "\n" " git push %s HEAD:ИМЕ_НА_ОТДАЛЕЧЕНИЯ_КЛОН\n" @@ -11592,6 +11668,30 @@ msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " Локалният указател, настроен за „git push“%s:" msgstr[1] " Локалните указатели, настроени за „git push“%s:" +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "„%s/HEAD“ не е променен и сочи към „%s“\n" + +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "„%s/HEAD“ е променен от „%s“ и сега сочи към „%s“\n" + +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "„%s/HEAD“ е създаден и сочи към „%s“\n" + +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "„%s/HEAD“ е отделѐн при „%s“ и сочи към „%s“\n" + +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"„%s/HEAD“ сочеше към „%s“ (което не е отдалечен клон), но сега сочи към " +"„%s“\n" + msgid "set refs/remotes//HEAD according to remote" msgstr "задаване на refs/remotes/ИМЕ/HEAD според отдалеченото хранилище" @@ -11614,7 +11714,7 @@ msgid "Not a valid ref: %s" msgstr "Неправилен указател: %s" #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "„%s“ не може да се настрои" #, c-format @@ -12287,8 +12387,8 @@ msgstr "" " или: git rev-parse --sq-quote [АРГУМЕНТ…]\n" " или: git rev-parse [ОПЦИЯ…] [АРГУМЕНТ…]\n" "\n" -"За повече информация за първия вариант изпълнете „git rev-parse --parseopt -" -"h“" +"За повече информация за първия вариант изпълнете „git rev-parse --parseopt " +"-h“" msgid "--resolve-git-dir requires an argument" msgstr "опцията „--resolve-git-dir“ изисква аргумент" @@ -13119,7 +13219,7 @@ msgstr "добавяне на „# “ в началото на всеки ре #, c-format msgid "Expecting a full ref name, got %s" -msgstr "Очаква се пълно име на указател, а не „%s“" +msgstr "Изисква се пълно име на указател, а не „%s“" #, c-format msgid "could not get a repository handle for submodule '%s'" @@ -13546,7 +13646,8 @@ msgid "don't print cloning progress" msgstr "без извеждане на напредъка на клонирането" msgid "disallow cloning into non-empty directory, implies --init" -msgstr "предотвратяване на клониране в непразна история, включва „--init“" +msgstr "" +"предотвратяване на клониране в непразна история, включва опцията „--init“" msgid "" "git submodule [--quiet] update [--init [--filter=]] [--remote] " @@ -14403,6 +14504,9 @@ msgstr "задаване на режима на следене (виж git-branc msgid "try to match the new branch name with a remote-tracking branch" msgstr "опит за напасване на името на новия клон с това на следящ клон" +msgid "use relative paths for worktrees" +msgstr "използване на относителни пътища за работните дървета" + #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" msgstr "опциите „%s“, „%s“ и „%s“ са несъвместими" @@ -14684,6 +14788,26 @@ msgstr "файлът „%s“ не може да бъде създаден" msgid "index-pack died" msgstr "командата „git index-pack“ не завърши успешно" +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "директорията „%s“ е в индекса, но не е частична" + +msgid "corrupted cache-tree has entries not present in index" +msgstr "повреденият кеш на обектите-дървета съдържа записи извън индекса" + +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "„%s“ с флагове 0x%x не трябва да е в кеша на обектите-дървета" + +#, c-format +msgid "bad subtree '%.*s'" +msgstr "неправилно поддърво „%.*s“" + +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "" +"кешираният обект-дърво за пътя „%.*s“ не съвпада. Изисква се „%s“, а не „%s“" + msgid "terminating chunk id appears earlier than expected" msgstr "идентификаторът за краен откъс се явява по-рано от очакваното" @@ -15468,7 +15592,7 @@ msgstr "временният файл на гра̀фа с подаваният #, c-format msgid "cannot merge graphs with %, % commits" msgstr "" -"не може да се слеят графове с % и % подавания (съответно)" +"не може да се слеят гра̀фи с % и % подавания (съответно)" #, c-format msgid "cannot merge graph %s, too many commits: %" @@ -15490,8 +15614,8 @@ msgid "" "attempting to write a commit-graph, but 'commitGraph.changedPathsVersion' " "(%d) is not supported" msgstr "" -"опит за запис на гра̀фа с подаванията, но настройката „commitGraph." -"changedPathsVersion“ (%d) не се поддържа" +"опит за запис на гра̀фа с подаванията, но настройката " +"„commitGraph.changedPathsVersion“ (%d) не се поддържа" msgid "too many commits to write graph" msgstr "прекалено много подавания за записване на гра̀фа" @@ -15578,7 +15702,7 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "Поддръжката на „/info/grafts“ е остаряла.\n" "В бъдеща версия на Git ще бъде премахната.\n" @@ -15590,7 +15714,7 @@ msgstr "" "\n" "За да изключите това съобщение, изпълнете:\n" "\n" -" git config advice.graftFileDeprecated false" +" git config set advice.graftFileDeprecated false" #, c-format msgid "commit %s exists in commit-graph but not in the object database" @@ -16445,6 +16569,24 @@ msgstr "адресът е без схема: %s" msgid "credential url cannot be parsed: %s" msgstr "адресът за идентификация не може да се анализира: „%s“" +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "" +"неправилна стойност за „timeout“ (максимално изчакване) „%s“, изисква се " +"неотрицателно цяло число" + +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "" +"неправилна стойност за „init-timeout“ (максимално първоначално изчакване): " +"„%s“, изисква се неотрицателно цяло число" + +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "" +"неправилна стойност за „max-connections“ (максималния брой връзки): „%s“, " +"изисква се цяло число" + msgid "in the future" msgstr "в бъдещето" @@ -17170,6 +17312,22 @@ msgstr "неправилен път към пространства от име msgid "too many args to run %s" msgstr "прекалено много аргументи за изпълнение „%s“" +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"Пробвате да доставите „%s“, което е в гра̀фа с подаванията, но не и в базата " +"от данни за обектите.\n" +"Най-вероятната причина е повредено хранилище.\n" +"За да поправите хранилището като изтеглите липсващия обект наново, " +"изпълнете:\n" +"\n" +" git fetch --refetch" + msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack: очаква се плитък списък" @@ -17770,11 +17928,12 @@ msgstr[1] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" "Куката „%s“ се прескача, защото липсват права̀ за изпълнение.\n" "За да изключите това предупреждение, изпълнете:\n" -" git config advice.ignoredHook false" +"\n" +" git config set advice.ignoredHook false" msgid "not a git repository" msgstr "не е хранилище на Git" @@ -17791,15 +17950,9 @@ msgstr "" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "Управлението на делегирането не се поддържа от cURL < 7.22.0" -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "Задаването на постоянен публичен ключ не се поддържа от cURL < 7.39.0" - msgid "Unknown value for http.proactiveauth" msgstr "Непозната стойност за „http.proactiveauth“" -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "„CURLSSLOPT_NO_REVOKE“ не се поддържа от cURL < 7.44.0" - #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" msgstr "Неподдържана реализация на SSL „%s“. Поддържат се:" @@ -17991,6 +18144,10 @@ msgstr "цитирани знаци CRLF" msgid "unable to format message: %s" msgstr "съобщението не може да се форматира: %s" +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "неправилен размер на маркер: „%s“, изисква се цяло число" + #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "Неуспешно сливане на подмодула „%s“ (не е изтеглен)" @@ -19147,7 +19304,7 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "При нормална работа Git никога не създава указатели, които завършват\n" "с 40 шестнадесетични знака, защото стандартно те ще бъдат прескачани.\n" @@ -19159,7 +19316,7 @@ msgstr "" "се създава подобен указател. Прегледайте тези указатели и ги изтрийте.\n" "За да изключите това съобщение, изпълнете:\n" "\n" -" git config advice.objectNameWarning false" +" config set advice.objectNameWarning false" #, c-format msgid "log for '%.*s' only goes back to %s" @@ -19330,15 +19487,6 @@ msgstr "задължителният обратен индекс липсва в msgid "could not open pack %s" msgstr "пакетът „%s“ не може да се отвори" -msgid "could not determine MIDX preferred pack" -msgstr "" -"предпочитаният пакет за файла с индекса за множество пакети не може да се " -"определи" - -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "предпочитаният пакет „%s“ е неправилен" - msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "" "повредена таблица със съответствия: местоположението на тройката е извън " @@ -20497,19 +20645,27 @@ msgstr "журналът с подаванията за указателя „%s msgid "log for %s is empty" msgstr "журналът с подаванията за указателя „%s“ е празен" -msgid "refusing to force and skip creation of reflog" -msgstr "" -"принудителна операция без създаване на журнал на указателите няма да се " -"приеме" - #, c-format -msgid "refusing to update ref with bad name '%s'" -msgstr "указател не може да се обнови с грешно име „%s“" +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "журналът на указателите няма да се обнови с псевдо указателя „%s“" #, c-format msgid "refusing to update pseudoref '%s'" msgstr "псевдо указателят „%s“ няма да се обнови" +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "журналът на указателите няма да се обнови с грешно име „%s“" + +#, c-format +msgid "refusing to update ref with bad name '%s'" +msgstr "указател не може да се обнови с грешно име „%s“" + +msgid "refusing to force and skip creation of reflog" +msgstr "" +"принудителна операция без създаване на журнал на указателите няма да се " +"приеме" + #, c-format msgid "update_ref failed for ref '%s': %s" msgstr "неуспешно обновяване на указателя (update_ref) „%s“: %s" @@ -20559,6 +20715,10 @@ msgstr "" "указателят „%s“ не може да се заключи: очакваше се файл с указател с цел " "„%s“, но вместо това е обикновен указател" +#, c-format +msgid "cannot read ref file '%s'" +msgstr "файлът с указател „%s“ не може да се прочете" + #, c-format msgid "cannot open directory %s" msgstr "директорията „%s“ не може да бъде отворена" @@ -20780,6 +20940,10 @@ msgstr "зададен е повече от един пакет за получ msgid "more than one uploadpack given, using the first" msgstr "зададен е повече от един пакет за изпращане, ще се ползва първият" +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "непознатата стойност за „followRemoteHEAD“: „%s“, се прескача" + #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" msgstr "непозната стойност за „transfer.credentialsInUrl“: „%s“" @@ -22788,6 +22952,11 @@ msgstr "подаването „%s“ не е отбелязано като до msgid "too many commits marked reachable" msgstr "прекалено много подавания са отбелязани като достижими" +msgid "could not determine MIDX preferred pack" +msgstr "" +"предпочитаният пакет за файла с индекса за множество пакети не може да се " +"определи" + msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 [ОПЦИЯ…]" @@ -23444,6 +23613,9 @@ msgstr "„.git“ е повреден" msgid ".git file incorrect" msgstr "„.git“ е неправилен" +msgid ".git file absolute/relative path mismatch" +msgstr "несъвпадение на абсолютния/относителния път на „.git“" + msgid "not a valid path" msgstr "неправилен път" @@ -23459,6 +23631,9 @@ msgstr "не може да се открие хранилище: „.git“ е msgid "gitdir unreadable" msgstr "директорията „gitdir“ не може да се прочете" +msgid "gitdir absolute/relative path mismatch" +msgstr "„gitdir“ указва несъвпадение на абсолютния/относителния път" + msgid "gitdir incorrect" msgstr "неправилна директория „gitdir“" @@ -23495,6 +23670,14 @@ msgstr "„%s“ не може да се изчисти в „%s“" msgid "failed to set extensions.worktreeConfig setting" msgstr "неуспешно задаване на настройката „extensions.worktreeConfig“" +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"форматът на хранилището не може да се обнови за поддръжката на относителни " +"работни дървета" + +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "неуспешно задаване на настройката „extensions.relativeWorktrees“" + #, c-format msgid "could not setenv '%s'" msgstr "променливата на средата „%s“ не може да се зададе чрез „setenv“" From ffbd89cbb7933081446e1dbde736d5dfce983383 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Tue, 17 Dec 2024 20:52:43 +0800 Subject: [PATCH 015/306] l10n: zh_TW: Git 2.48 Signed-off-by: Yi-Jyun Pan --- po/zh_TW.po | 545 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 347 insertions(+), 198 deletions(-) diff --git a/po/zh_TW.po b/po/zh_TW.po index 5e6818f4539e48..28afb3662f72a5 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -30,8 +30,8 @@ msgid "" msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-05 01:20+0000\n" -"PO-Revision-Date: 2024-10-05 15:45+0800\n" +"POT-Creation-Date: 2024-12-17 20:14+0800\n" +"PO-Revision-Date: 2024-12-17 20:52+0800\n" "Last-Translator: Yi-Jyun Pan \n" "Language-Team: Chinese (Traditional) \n" @@ -764,10 +764,10 @@ msgstr "只有二進位檔案更動了。" #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"請使用「git config advice.%s false」停用此訊息" +"請使用「git config set advice.%s false」停用此訊息" #: advice.c #, c-format @@ -2620,7 +2620,7 @@ msgstr "" #: builtin/am.c builtin/reset.c #, c-format msgid "Could not parse object '%s'." -msgstr "無法解析「%s」物件。" +msgstr "無法解析物件「%s」。" #: builtin/am.c msgid "failed to clean index" @@ -2917,7 +2917,7 @@ msgstr "「%s」不是有效的提交" #, c-format msgid "" "could not check out original HEAD '%s'. Try 'git bisect reset '." -msgstr "不能簽出原始 HEAD「%s」。請嘗試「git bisect reset 」。" +msgstr "無法簽出原始 HEAD「%s」。請嘗試「git bisect reset 」。" #: builtin/bisect.c #, c-format @@ -3460,7 +3460,7 @@ msgstr "無法解析格式化字串" #: builtin/branch.c msgid "could not resolve HEAD" -msgstr "無法解析 HEAD 指標" +msgstr "無法解析 HEAD" #: builtin/branch.c #, c-format @@ -4718,8 +4718,8 @@ msgid "update ignored files (default)" msgstr "更新忽略的檔案(預設值)" #: builtin/checkout.c -msgid "do not check if another worktree is holding the given ref" -msgstr "不檢查其他工作區是否正在佔用指定的引用" +msgid "do not check if another worktree is using this branch" +msgstr "不檢查其他工作區是否正在使用這個提交" #: builtin/checkout.c msgid "checkout our version for unmerged files" @@ -4875,7 +4875,7 @@ msgstr "無法移除 %s" #: builtin/clean.c #, c-format msgid "could not lstat %s\n" -msgstr "不能對 %s 進行 lstat\n" +msgstr "無法對 %s 進行 lstat\n" #: builtin/clean.c msgid "Refusing to remove current working directory\n" @@ -5099,14 +5099,14 @@ msgstr "建立指定深度的淺層複製" msgid "create a shallow clone since a specific time" msgstr "建立從指定時間到現在的淺層複製" -#: builtin/clone.c builtin/fetch.c builtin/pull.c builtin/rebase.c -#: builtin/replay.c -msgid "revision" -msgstr "revision" +#: builtin/clone.c builtin/fetch.c builtin/pull.c +#| msgid "rev" +msgid "ref" +msgstr "ref" #: builtin/clone.c builtin/fetch.c builtin/pull.c -msgid "deepen history of shallow clone, excluding rev" -msgstr "取得更多淺層複製的過去歷史記錄,除了特定修訂版" +msgid "deepen history of shallow clone, excluding ref" +msgstr "取得更多淺層複製的過往歷史記錄,除了特定修訂版" #: builtin/clone.c builtin/submodule--helper.c msgid "clone only one branch, HEAD or --branch" @@ -5325,12 +5325,12 @@ msgstr "工作區 '%s' 已經存在。" #: builtin/clone.c builtin/difftool.c builtin/log.c builtin/worktree.c #, c-format msgid "could not create leading directories of '%s'" -msgstr "不能為 '%s' 建立先導目錄" +msgstr "無法為「%s」建立前導目錄" #: builtin/clone.c #, c-format msgid "could not create work tree dir '%s'" -msgstr "不能建立工作區目錄 '%s'" +msgstr "無法建立工作區目錄「%s」" #: builtin/clone.c #, c-format @@ -5789,12 +5789,12 @@ msgstr "(正從標準輸入中讀取日誌訊息)\n" #: builtin/commit.c msgid "could not read log from standard input" -msgstr "不能從標準輸入中讀取日誌訊息" +msgstr "無法從標準輸入中讀取記錄" #: builtin/commit.c #, c-format msgid "could not read log file '%s'" -msgstr "不能讀取日誌檔案 '%s'" +msgstr "無法讀取日誌檔案「%s」" #: builtin/commit.c #, c-format @@ -5803,11 +5803,11 @@ msgstr "「%s」和「%s:%s」選項不得同時使用" #: builtin/commit.c msgid "could not read SQUASH_MSG" -msgstr "不能讀取 SQUASH_MSG" +msgstr "無法讀取 SQUASH_MSG" #: builtin/commit.c msgid "could not read MERGE_MSG" -msgstr "不能讀取 MERGE_MSG" +msgstr "無法讀取 MERGE_MSG" #: builtin/commit.c bundle.c rerere.c sequencer.c #, c-format @@ -5816,7 +5816,7 @@ msgstr "無法開啟「%s」" #: builtin/commit.c msgid "could not write commit template" -msgstr "不能寫提交範本" +msgstr "無法寫入提交模板" #: builtin/commit.c #, c-format @@ -6192,7 +6192,7 @@ msgstr "允許空的提交說明" #: builtin/commit.c sequencer.c msgid "could not parse HEAD commit" -msgstr "不能解析 HEAD 提交" +msgstr "無法解析 HEAD 提交" #: builtin/commit.c #, c-format @@ -6201,12 +6201,12 @@ msgstr "損壞的 MERGE_HEAD 檔案(%s)" #: builtin/commit.c msgid "could not read MERGE_MODE" -msgstr "不能讀取 MERGE_MODE" +msgstr "無法讀取 MERGE_MODE" #: builtin/commit.c #, c-format msgid "could not read commit message: %s" -msgstr "不能讀取提交說明:%s" +msgstr "無法讀取提交說明:%s" #: builtin/commit.c #, c-format @@ -6253,12 +6253,15 @@ msgstr "" "value] <名稱> <值>" #: builtin/config.c +#| msgid "" +#| "git config unset [] [--all] [--value=] [--fixed-" +#| "value] " msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" -"git config unset [<檔案選項>] [--all] [--value=<值>] [--fixed-value] <名稱> <" -"值>" +"git config unset [] [--all] [--value=] [--fixed-value] " +"" #: builtin/config.c msgid "git config rename-section [] " @@ -6804,12 +6807,8 @@ msgstr "已遍歷 %lu 個提交\n" #: builtin/describe.c #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"發現多於 %i 個標籤,列出最近的 %i 個\n" -"在 %s 放棄搜尋\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "找到 %i 個標籤;在 %s 放棄搜尋\n" #: builtin/describe.c #, c-format @@ -6997,7 +6996,7 @@ msgstr "工作區檔案被留了下來。" #: builtin/difftool.c sequencer.c #, c-format msgid "could not copy '%s' to '%s'" -msgstr "不能複製 '%s' 至 '%s'" +msgstr "無法將「%s」複製到「%s」" #: builtin/difftool.c #, c-format @@ -7397,12 +7396,12 @@ msgstr "正在取得 %s\n" #: builtin/fetch.c #, c-format msgid "could not fetch %s" -msgstr "不能取得 %s" +msgstr "無法取得 %s" #: builtin/fetch.c #, c-format msgid "could not fetch '%s' (exit code: %d)\n" -msgstr "無法取得 '%s'(離開碼:%d)\n" +msgstr "無法取得「%s」(離開碼:%d)\n" #: builtin/fetch.c msgid "" @@ -7527,6 +7526,10 @@ msgstr "引用映射" msgid "specify fetch refmap" msgstr "指定取得動作的引用映射" +#: builtin/fetch.c builtin/pull.c builtin/rebase.c builtin/replay.c +msgid "revision" +msgstr "revision" + #: builtin/fetch.c builtin/pull.c msgid "report that we have only objects reachable from this object" msgstr "報告我們只擁有從該物件開始可以取得的物件" @@ -7788,18 +7791,18 @@ msgstr "懸空 %s %s" #: builtin/fsck.c msgid "could not create lost-found" -msgstr "不能建立 lost-found" +msgstr "無法建立 lost-found" #: builtin/fsck.c builtin/gc.c builtin/rebase.c rebase-interactive.c rerere.c #: sequencer.c #, c-format msgid "could not write '%s'" -msgstr "不能寫入 '%s'" +msgstr "無法寫入「%s」" #: builtin/fsck.c #, c-format msgid "could not finish '%s'" -msgstr "不能完成 '%s'" +msgstr "無法完成「%s」" #: builtin/fsck.c #, c-format @@ -8069,7 +8072,7 @@ msgstr "無法啟動 fsmonitor 監聽執行緒" #: builtin/fsmonitor--daemon.c msgid "could not start fsmonitor health thread" -msgstr "無法啟動 fsmonitor 健康監聽執行緒" +msgstr "無法啟動 fsmonitor 健康檢查執行緒" #: builtin/fsmonitor--daemon.c msgid "could not initialize listener thread" @@ -8077,7 +8080,7 @@ msgstr "無法初始化監聽執行緒" #: builtin/fsmonitor--daemon.c msgid "could not initialize health thread" -msgstr "無法初始化健康監聽執行緒" +msgstr "無法初始化健康檢查執行緒" #: builtin/fsmonitor--daemon.c #, c-format @@ -8437,8 +8440,26 @@ msgid "%s scheduler is not available" msgstr "無法使用 %s 排程器" #: builtin/gc.c -msgid "another process is scheduling background maintenance" -msgstr "其他處理程序正在排定背景維護工作" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"無法建立「%s.lock」:%s。\n" +"\n" +"似乎有另一個排定的 git-maintenance(1) 程序正在此\n" +"版本庫中執行。請確保沒有其他維護程序正在執行,\n" +"然後再試一次。如果仍然失敗,可能是先前的\n" +"git-maintenance(1) 程序在這個版本庫中意外中斷:\n" +"請手動移除檔案以繼續。" + +#: builtin/gc.c +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "無法取得用來進行排定背景維護的鎖" #: builtin/gc.c msgid "git maintenance start [--scheduler=]" @@ -8727,7 +8748,7 @@ msgstr "--open-files-in-pager 僅用於工作區" #: builtin/grep.c msgid "--[no-]exclude-standard cannot be used for tracked contents" -msgstr "--[no-]exclude-standard 不能用於已追蹤內容" +msgstr "--[no-]exclude-standard 無法用於已追蹤內容" #: builtin/grep.c msgid "both --cached and trees are given" @@ -9157,6 +9178,22 @@ msgid "chain length = %d: %lu object" msgid_plural "chain length = %d: %lu objects" msgstr[0] "鏈長 = %d: %lu 物件" +#: builtin/index-pack.c +msgid "could not start pack-objects to repack local links" +msgstr "無法啟動 pack-objects 來重新打包本機連結" + +#: builtin/index-pack.c +msgid "failed to feed local object to pack-objects" +msgstr "無法將本機物件喂給 pack-objects" + +#: builtin/index-pack.c +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "index-pack:只預期接受來自 pack-objects 的完整十六進位物件 ID。" + +#: builtin/index-pack.c +msgid "could not finish pack-objects to repack local links" +msgstr "無法結束 pack-objects 來重新封包" + #: builtin/index-pack.c msgid "Cannot come back to cwd" msgstr "無法返回目前工作目錄" @@ -9171,6 +9208,10 @@ msgstr "錯誤選項 %s" msgid "unknown hash algorithm '%s'" msgstr "未知的「%s」雜湊算法" +#: builtin/index-pack.c +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor 不能與封包名稱一起使用" + #: builtin/index-pack.c msgid "--stdin requires a git repository" msgstr "--stdin 需要一個 git 版本庫" @@ -9256,7 +9297,7 @@ msgstr "" #: builtin/interpret-trailers.c wrapper.c #, c-format msgid "could not stat %s" -msgstr "不能對 %s 呼叫 stat" +msgstr "無法 stat %s" #: builtin/interpret-trailers.c #, c-format @@ -9270,21 +9311,21 @@ msgstr "檔案 %s 使用者不可寫" #: builtin/interpret-trailers.c msgid "could not open temporary file" -msgstr "不能開啟暫存檔" +msgstr "無法開啟暫存檔" #: builtin/interpret-trailers.c #, c-format msgid "could not read input file '%s'" -msgstr "不能讀取輸入檔案 '%s'" +msgstr "無法讀取輸入檔案「%s」" #: builtin/interpret-trailers.c builtin/mktag.c imap-send.c msgid "could not read from stdin" -msgstr "不能自標準輸入讀取" +msgstr "無法從標準輸入讀取" #: builtin/interpret-trailers.c #, c-format msgid "could not rename temporary file to %s" -msgstr "不能重新命名暫存檔為 %s" +msgstr "無法將暫存檔重新命名為 %s" #: builtin/interpret-trailers.c msgid "edit files in place" @@ -9407,7 +9448,7 @@ msgstr "git show %s: 損壞的檔案" #: builtin/log.c #, c-format msgid "could not read object %s" -msgstr "不能讀取物件 %s" +msgstr "無法讀取物件 %s" #: builtin/log.c #, c-format @@ -9474,7 +9515,7 @@ msgstr "無法將 '%s' 解析為一個有效引用" #: builtin/log.c msgid "could not find exact merge base" -msgstr "不能找到準確的合併基礎" +msgstr "找不到準確的合併基礎" #: builtin/log.c msgid "" @@ -9727,7 +9768,7 @@ msgstr "--remerge-diff 無意義" #: builtin/log.c builtin/submodule--helper.c rerere.c submodule.c #, c-format msgid "could not create directory '%s'" -msgstr "不能建立目錄 '%s'" +msgstr "無法建立「%s」目錄" #: builtin/log.c msgid "--interdiff requires --cover-letter or single patch" @@ -9776,7 +9817,7 @@ msgstr "git cherry [-v] [<上游> [<頭> [<限制>]]]" #, c-format msgid "" "Could not find a tracked remote branch, please specify manually.\n" -msgstr "不能找到追蹤的遠端分支,請手動指定 <上游>。\n" +msgstr "無法找到追蹤的遠端分支,請手動指定 <上游>。\n" #: builtin/ls-files.c builtin/ls-tree.c #, c-format @@ -9900,7 +9941,7 @@ msgid "" "--format cannot be used with -s, -o, -k, -t, --resolve-undo, --deduplicate, " "--eol" msgstr "" -"--format 不能和 -s、-o、-k、-t、--resolve-undo、--deduplicate、--eol 一起使用" +"--format 無法和 -s、-o、-k、-t、--resolve-undo、--deduplicate、--eol 一起使用" #: builtin/ls-remote.c msgid "" @@ -10162,7 +10203,7 @@ msgstr "未知選項 %s" #: builtin/merge-recursive.c #, c-format msgid "could not parse object '%s'" -msgstr "不能解析物件 '%s'" +msgstr "無法解析物件「%s」" #: builtin/merge-recursive.c #, c-format @@ -10177,7 +10218,7 @@ msgstr "不能處理兩個頭合併之外的任何動作。" #: builtin/merge-recursive.c #, c-format msgid "could not resolve ref '%s'" -msgstr "無法解析引用 '%s'" +msgstr "無法解析引用「%s」" #: builtin/merge-recursive.c #, c-format @@ -10280,7 +10321,7 @@ msgstr "選項 `%s' 需要一個值" #: builtin/merge.c #, c-format msgid "Could not find merge strategy '%s'.\n" -msgstr "不能找到合併策略 '%s'。\n" +msgstr "找不到合併策略「%s」。\n" #: builtin/merge.c #, c-format @@ -10368,7 +10409,7 @@ msgstr "繞過 pre-merge-commit 和 commit-msg 掛鉤" #: builtin/merge.c msgid "could not run stash." -msgstr "不能執行貯存。" +msgstr "無法執行貯存。" #: builtin/merge.c msgid "stash failed" @@ -10428,7 +10469,7 @@ msgstr "不能寫 %s" #: builtin/merge.c #, c-format msgid "Could not read from '%s'" -msgstr "不能從 '%s' 讀取" +msgstr "無法從「%s」進行讀取" #: builtin/merge.c #, c-format @@ -10495,7 +10536,7 @@ msgstr "環境 '%2$s' 中存在壞的取值 '%1$s'" #: builtin/merge.c editor.c read-cache.c wrapper.c #, c-format msgid "could not close '%s'" -msgstr "不能關閉 '%s'" +msgstr "無法關閉「%s」" #: builtin/merge.c #, c-format @@ -10882,28 +10923,36 @@ msgid "git notes [--ref ] [list []]" msgstr "git notes [--ref <註解引用>] [list [<物件>]]" #: builtin/notes.c +#| msgid "" +#| "git notes [--ref ] add [-f] [--allow-empty] [--" +#| "[no-]separator|--separator=] [--[no-]stripspace] [-m " +#| " | -F | (-c | -C) ] []" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" #: builtin/notes.c msgid "git notes [--ref ] copy [-f] " msgstr "git notes [--ref <註解引用>] copy [-f] <來源物件> <目標物件>" #: builtin/notes.c +#| msgid "" +#| "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" +#| "separator=] [--[no-]stripspace] [-m | -F | " +#| "(-c | -C) ] []" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" #: builtin/notes.c msgid "git notes [--ref ] edit [--allow-empty] []" @@ -10984,7 +11033,7 @@ msgstr "為下面的物件寫/編輯說明:" #: builtin/notes.c msgid "could not read 'show' output" -msgstr "不能讀取 'show' 的輸出" +msgstr "無法讀取「show」的輸出" #: builtin/notes.c #, c-format @@ -11007,7 +11056,7 @@ msgstr "註解內容被留在 %s 中" #: builtin/notes.c builtin/tag.c #, c-format msgid "could not open or read '%s'" -msgstr "不能開啟或讀取 '%s'" +msgstr "無法開啟或讀取「%s」" #: builtin/notes.c #, c-format @@ -11054,6 +11103,10 @@ msgstr "註解內容到一個檔案中" msgid "reuse and edit specified note object" msgstr "重用和編輯指定的註解物件" +#: builtin/notes.c +msgid "edit note message in editor" +msgstr "在編輯器中編輯備註訊息" + #: builtin/notes.c msgid "reuse specified note object" msgstr "重用指定的註解物件" @@ -11352,7 +11405,7 @@ msgstr "%s 的 delta 基準位移越界" #: builtin/pack-objects.c msgid "Counting objects" -msgstr "物件計數中" +msgstr "正在計算物件數量" #: builtin/pack-objects.c pack-bitmap.c #, c-format @@ -11667,6 +11720,10 @@ msgstr "處理遺失的物件" msgid "do not pack objects in promisor packfiles" msgstr "不要打包 promisor packfile 中的物件" +#: builtin/pack-objects.c +msgid "implies --missing=allow-any" +msgstr "隱含 --missing=allow-any" + #: builtin/pack-objects.c msgid "respect islands during delta compression" msgstr "在差異壓縮時尊重資料島" @@ -11696,7 +11753,7 @@ msgstr "錯誤的打包壓縮級別 %d" #: builtin/pack-objects.c msgid "--max-pack-size cannot be used to build a pack for transfer" -msgstr "不能使用 --max-pack-size 來組建傳輸用的包檔案" +msgstr "不能使用 --max-pack-size 來建構傳輸用的包檔案" #: builtin/pack-objects.c msgid "minimum pack size limit is 1 MiB" @@ -12330,7 +12387,7 @@ msgstr "" #: builtin/push.c msgid "--all can't be combined with refspecs" -msgstr "--all 不能和引用規格同時使用" +msgstr "--all 無法和引用規格同時使用" #: builtin/push.c msgid "--mirror can't be combined with refspecs" @@ -12492,12 +12549,12 @@ msgstr "" #: builtin/rebase.c sequencer.c #, c-format msgid "could not read '%s'." -msgstr "不能讀取 '%s'。" +msgstr "無法讀取「%s」。" #: builtin/rebase.c #, c-format msgid "could not create temporary %s" -msgstr "無法建立暫時的 %s" +msgstr "無法建立暫存用的 %s" #: builtin/rebase.c msgid "could not mark as interactive" @@ -12505,7 +12562,7 @@ msgstr "無法標記為互動式" #: builtin/rebase.c msgid "could not generate todo list" -msgstr "無法生成待辦列表" +msgstr "無法產生待辦列表" #: builtin/rebase.c msgid "a base commit must be provided with --upstream or --onto" @@ -12534,7 +12591,7 @@ msgstr "忽略無效的 allow_rerere_autoupdate:'%s'" #: builtin/rebase.c builtin/rm.c sequencer.c #, c-format msgid "could not remove '%s'" -msgstr "無法刪除 '%s'" +msgstr "無法刪除「%s」" #: builtin/rebase.c #, c-format @@ -12879,7 +12936,7 @@ msgstr "無效的上游 '%s'" #: builtin/rebase.c msgid "Could not create new root commit" -msgstr "不能建立新的根提交" +msgstr "無法建立新的根提交" #: builtin/rebase.c #, c-format @@ -13235,7 +13292,7 @@ msgstr "更新 %s 中" #: builtin/remote.c #, c-format msgid "Could not fetch %s" -msgstr "不能取得 %s" +msgstr "無法取得 %s" #: builtin/remote.c msgid "" @@ -13290,7 +13347,7 @@ msgstr "遠端 %s 已經存在。" #: builtin/remote.c #, c-format msgid "Could not setup master '%s'" -msgstr "無法設定 master '%s'" +msgstr "無法配置 master「%s」" #: builtin/remote.c trailer.c #, c-format @@ -13305,7 +13362,7 @@ msgstr "branch.%s.rebase=%s 未處理。假設成 'true'" #: builtin/remote.c #, c-format msgid "Could not get fetch map for refspec %s" -msgstr "無法得到引用規格 %s 的取得列表" +msgstr "無法取得引用規格 %s 的 fetch 映射" #: builtin/remote.c msgid "(matching)" @@ -13318,12 +13375,12 @@ msgstr "(刪除)" #: builtin/remote.c #, c-format msgid "could not set '%s'" -msgstr "無法設定 '%s'" +msgstr "無法設定「%s」" #: builtin/remote.c config.c #, c-format msgid "could not unset '%s'" -msgstr "不能取消設定 '%s'" +msgstr "無法取消設定「%s」" #: builtin/remote.c #, c-format @@ -13344,7 +13401,7 @@ msgstr "沒有此遠端版本庫:'%s'" #: builtin/remote.c #, c-format msgid "Could not rename config section '%s' to '%s'" -msgstr "不能重新命名設定小節 '%s' 到 '%s'" +msgstr "無法將組態的「%s」區段重新命名為「%s」" #: builtin/remote.c #, c-format @@ -13383,7 +13440,7 @@ msgstr[0] "注意:refs/remotes/ 層級之外的一個分支未被移除。要 #: builtin/remote.c #, c-format msgid "Could not remove config section '%s'" -msgstr "不能移除設定小節 '%s'" +msgstr "無法移除組態的「%s」區段" #: builtin/remote.c #, c-format @@ -13583,7 +13640,7 @@ msgstr "不是一個有效引用:%s" #: builtin/remote.c #, c-format msgid "Could not setup %s" -msgstr "不能設定 %s" +msgstr "無法配置 %s" # 譯者:請維持前導空格 #: builtin/remote.c @@ -13702,7 +13759,7 @@ msgstr "repack:期望來自 pack-objects 的完整十六進位物件 ID。" #: builtin/repack.c msgid "could not finish pack-objects to repack promisor objects" -msgstr "無法完成 pack-objects 來重新打包 promisor 物件" +msgstr "無法結束 pack-objects 來重新打包 promisor 物件" #: builtin/repack.c #, c-format @@ -13722,7 +13779,7 @@ msgstr "%s 包太大,以致不能縮合" #: builtin/repack.c #, c-format msgid "could not open tempfile %s for writing" -msgstr "無法開啟 '%s' 暫存檔進行寫入" +msgstr "無法開啟暫存檔 %s 進行寫入" #: builtin/repack.c msgid "could not close refs snapshot tempfile" @@ -13986,7 +14043,7 @@ msgstr "新物件和舊物件相同:'%s'" #: builtin/replace.c #, c-format msgid "could not parse %s as a commit" -msgstr "無法將 %s 解析為一個提交" +msgstr "無法將 %s 解析為提交" #: builtin/replace.c #, c-format @@ -14017,7 +14074,7 @@ msgstr "在取代的提交中簽名將被移除!" #: builtin/replace.c #, c-format msgid "could not write replacement commit for: '%s'" -msgstr "不能為 '%s' 寫取代提交" +msgstr "無法寫入下列項目的替代提交:「%s」" #: builtin/replace.c #, c-format @@ -14035,7 +14092,7 @@ msgid "" "could not convert the following graft(s):\n" "%s" msgstr "" -"不能轉換下列移植:\n" +"無法轉換下列移植:\n" "%s" #: builtin/replace.c @@ -14072,7 +14129,7 @@ msgstr "使用此格式" #: builtin/replace.c msgid "--format cannot be used when not listing" -msgstr "不列出時不能使用 --format" +msgstr "非列出操作不能使用 --format" #: builtin/replace.c msgid "-f only makes sense when writing a replacement" @@ -14338,11 +14395,11 @@ msgstr "" #: builtin/reset.c #, c-format msgid "Could not reset index file to revision '%s'." -msgstr "不能重設索引檔案至版本 '%s'。" +msgstr "無法將索引檔案重設到修訂版「%s」。" #: builtin/reset.c msgid "Could not write new index file." -msgstr "不能寫入新的索引檔案。" +msgstr "無法寫入新的索引檔案。" #: builtin/rev-list.c #, c-format @@ -15231,7 +15288,7 @@ msgstr "無法在合併過程套用貯存" #: builtin/stash.c #, c-format msgid "could not generate diff %s^!." -msgstr "無法生成差異 %s^!." +msgstr "無法產生差異 %s^!." #: builtin/stash.c msgid "conflicts in index. Try without --index." @@ -15239,7 +15296,7 @@ msgstr "索引中有衝突。請試試看不用 --index。" #: builtin/stash.c msgid "could not save index tree" -msgstr "不能儲存索引樹" +msgstr "無法儲存索引樹" #: builtin/stash.c #, c-format @@ -15415,14 +15472,14 @@ msgstr "期望一個完整的引用名稱,卻得到 %s" #: builtin/submodule--helper.c #, c-format msgid "could not get a repository handle for submodule '%s'" -msgstr "無法獲得子模組 '%s' 的版本庫句柄" +msgstr "無法取得子模組「%s」的版本庫控制代碼" #: builtin/submodule--helper.c #, c-format msgid "" "could not look up configuration '%s'. Assuming this repository is its own " "authoritative upstream." -msgstr "無法找到設定 '%s'。假定這個版本庫是其自身的官方上游。" +msgstr "找不到「%s」組態設定。假定這個版本庫是其自身的官方上游。" #: builtin/submodule--helper.c #, c-format @@ -15501,7 +15558,7 @@ msgstr "在 .gitmodules 中沒有發現路徑 '%s' 的子模組映射" #: builtin/submodule--helper.c #, c-format msgid "could not resolve HEAD ref inside the submodule '%s'" -msgstr "無法解析子模組 '%s' 的 HEAD 引用" +msgstr "無法解析子模組「%s」的 HEAD 引用" #: builtin/submodule--helper.c #, c-format @@ -15540,7 +15597,7 @@ msgstr "%s" #: builtin/submodule--helper.c #, c-format msgid "couldn't hash object from '%s'" -msgstr "無法雜湊來自 '%s' 的物件" +msgstr "無法雜湊來自「%s」的物件" #: builtin/submodule--helper.c #, c-format @@ -15569,7 +15626,7 @@ msgstr "git submodule summary [] [] [--] []" #: builtin/submodule--helper.c msgid "could not fetch a revision for HEAD" -msgstr "無法取得 HEAD 的版本" +msgstr "無法取得 HEAD 的修訂版" #: builtin/submodule--helper.c #, c-format @@ -15617,12 +15674,12 @@ msgstr "已清除目錄 '%s'\n" #: builtin/submodule--helper.c #, c-format msgid "Could not remove submodule work tree '%s'\n" -msgstr "無法移除子模組工作區 '%s'\n" +msgstr "無法移除子模組工作區「%s」\n" #: builtin/submodule--helper.c #, c-format msgid "could not create empty submodule directory %s" -msgstr "不能建立空的子模組目錄 %s" +msgstr "無法建立空的子模組目錄 %s" #: builtin/submodule--helper.c #, c-format @@ -15662,7 +15719,7 @@ msgstr "" #: builtin/submodule--helper.c #, c-format msgid "could not get a repository handle for gitdir '%s'" -msgstr "無法取得 gitdir「%s」的版本庫控點" +msgstr "無法取得 gitdir「%s」的版本庫控制代碼" #: builtin/submodule--helper.c #, c-format @@ -15697,7 +15754,7 @@ msgstr "無法複製 '%s' 到子模組路徑 '%s'" #: builtin/submodule--helper.c #, c-format msgid "could not get submodule directory for '%s'" -msgstr "無法得到 '%s' 的子模組目錄" +msgstr "無法取得「%s」的子模組目錄" #: builtin/submodule--helper.c msgid "alternative anchor for relative paths" @@ -16826,7 +16883,7 @@ msgstr "無法取消「%2$s」中「%1$s」的設定" #: builtin/worktree.c #, c-format msgid "could not create directory of '%s'" -msgstr "不能建立目錄 '%s'" +msgstr "無法建立「%s」的目錄" #: builtin/worktree.c msgid "initializing" @@ -16917,6 +16974,10 @@ msgstr "設定追蹤模式(參見 git-branch(1))" msgid "try to match the new branch name with a remote-tracking branch" msgstr "嘗試為新分支名符合一個遠端追蹤分支" +#: builtin/worktree.c +msgid "use relative paths for worktrees" +msgstr "對工作區使用相對路徑" + #: builtin/worktree.c diff.c parse-options.c #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" @@ -16987,7 +17048,7 @@ msgstr "'%s' 是一個主工作區" #: builtin/worktree.c #, c-format msgid "could not figure out destination name from '%s'" -msgstr "無法從 '%s' 算出目的地名稱" +msgstr "無法從「%s」得出目的地名稱" #: builtin/worktree.c #, c-format @@ -17251,6 +17312,30 @@ msgstr "無法建立「%s」" msgid "index-pack died" msgstr "index-pack 終止" +#: cache-tree.c +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "「%s」目錄已經在索引裡面,但不在稀疏簽出當中。" + +#: cache-tree.c unpack-trees.c +msgid "corrupted cache-tree has entries not present in index" +msgstr "損壞的快取樹有不在索引中的項目" + +#: cache-tree.c +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "有 0x%2$x 標記的 %1$s 不應該在快取樹當中" + +#: cache-tree.c +#, c-format +msgid "bad subtree '%.*s'" +msgstr "「%.*s」子樹損壞" + +#: cache-tree.c +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "%.*s 路徑的快取樹不符。預期是 %s,卻得到 %s" + #: chunk-format.c msgid "terminating chunk id appears earlier than expected" msgstr "終止區塊 ID 比預期還早出現" @@ -18339,7 +18424,7 @@ msgstr "正在驗證提交圖中的提交" #: commit-reach.c sequencer.c #, c-format msgid "could not parse commit %s" -msgstr "不能解析提交 %s" +msgstr "無法解析提交 %s" #: commit.c #, c-format @@ -18355,7 +18440,7 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "對 /info/grafts 的支援已棄用,並將在\n" "未來的 Git 版本中被移除。\n" @@ -18363,8 +18448,8 @@ msgstr "" "請使用「git replace --convert-graft-file」將\n" "grafts 轉換為取代引用。\n" "\n" -"設定「git config advice.graftFileDeprecated false」\n" -"可以將本訊息關閉" +"設定「git config set advice.graftFileDeprecated false」\n" +"可以關閉此訊息" #: commit.c #, c-format @@ -18672,7 +18757,7 @@ msgstr "" #: config.c #, c-format msgid "could not expand include path '%s'" -msgstr "無法展開包含路徑 '%s'" +msgstr "無法展開包含路徑「%s」" #: config.c msgid "relative config includes must come from files" @@ -18971,7 +19056,7 @@ msgstr "不允許多列備註:「%s」" #: config.c #, c-format msgid "could not lock config file %s" -msgstr "不能鎖定設定檔案 %s" +msgstr "無法鎖定組態檔案 %s" #: config.c #, c-format @@ -19001,12 +19086,12 @@ msgstr "對 %s 呼叫 chmod 失敗" #: config.c #, c-format msgid "could not write config file %s" -msgstr "不能寫入設定檔案 %s" +msgstr "無法寫入組態檔案 %s" #: config.c #, c-format msgid "could not set '%s' to '%s'" -msgstr "不能設定 '%s' 為 '%s'" +msgstr "無法將「%s」設為「%s」" #: config.c #, c-format @@ -19205,7 +19290,7 @@ msgstr "無法 fork" #: connected.c msgid "Could not run 'git rev-list'" -msgstr "不能執行 'git rev-list'" +msgstr "無法執行「git rev-list」" #: connected.c msgid "failed write to rev-list" @@ -19362,6 +19447,21 @@ msgstr "URL 沒有 Scheme:%s" msgid "credential url cannot be parsed: %s" msgstr "無法解析憑證 URL:%s" +#: daemon.c +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "無效的 timeout「%s」,預期為非負整數" + +#: daemon.c +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "無效的 init-timeout「%s」,應為非負整數" + +#: daemon.c +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "無效的 max-connections「%s」,應為整數" + #: date.c msgid "in the future" msgstr "在將來" @@ -19458,7 +19558,7 @@ msgstr "無法封存不存在的路徑「%s」" #: diagnose.c dir.c #, c-format msgid "could not open directory '%s'" -msgstr "不能開啟目錄 '%s'" +msgstr "無法開啟目錄「%s」" #: diagnose.c #, c-format @@ -20178,12 +20278,12 @@ msgstr "版本庫 %s 中的索引檔案損壞" #: dir.c #, c-format msgid "could not create directories for %s" -msgstr "不能為 %s 建立目錄" +msgstr "無法建立 %s 的目錄" #: dir.c #, c-format msgid "could not migrate git directory from '%s' to '%s'" -msgstr "不能從 '%s' 遷移 git 目錄到 '%s'" +msgstr "無法從「%s」遷移 git 目錄到「%s」" #: editor.c #, c-format @@ -20193,12 +20293,12 @@ msgstr "提示:等待您的編輯器關閉檔案...%c" #: editor.c sequencer.c wrapper.c #, c-format msgid "could not write to '%s'" -msgstr "不能寫入 '%s'" +msgstr "無法寫入「%s」" #: editor.c #, c-format msgid "could not edit '%s'" -msgstr "無法編輯 '%s'" +msgstr "無法編輯「%s」" #: entry.c msgid "Filtering content" @@ -20207,7 +20307,7 @@ msgstr "過濾內容" #: entry.c #, c-format msgid "could not stat file '%s'" -msgstr "不能對檔案 '%s' 呼叫 stat" +msgstr "無法 stat 檔案「%s」" #: environment.c #, c-format @@ -20219,6 +20319,23 @@ msgstr "錯誤的 git 名字空間路徑 \"%s\"" msgid "too many args to run %s" msgstr "執行 %s 的參數太多" +#: fetch-pack.c +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"你正在嘗試取得 %s。這個物件在提交圖檔案中,\n" +"但不在物件資料庫中。\n" +"\n" +"這可能是由於儲存庫損壞所致。\n" +"\n" +"如果你想透過重新擷取遺失的物件來修復儲存庫損壞,\n" +"請使用「git fetch --refetch」並指定遺失的物件。" + #: fetch-pack.c msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack:應為 shallow 列表" @@ -20647,7 +20764,7 @@ msgstr "執行指令 '%s' 失敗:%s\n" #: gpg-interface.c msgid "could not create temporary file" -msgstr "不能建立暫存檔" +msgstr "無法建立暫存檔" #: gpg-interface.c #, c-format @@ -20925,10 +21042,10 @@ msgstr[0] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" -"因為沒有將掛鉤 '%s' 設定為可執行,掛鉤被忽略。您可以透過\n" -"設定 `git config advice.ignoredHook false` 來關閉這條警告。" +"因為沒有將掛鉤「%s」設定為可執行,因此忽略這個掛鉤。\n" +"您可以透過設定「git config set advice.ignoredHook false」來關閉這則警告。" #: http-fetch.c msgid "not a git repository" @@ -20948,18 +21065,10 @@ msgstr "http.postBuffer 為負值,預設為 %d" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "不支援委託控制,因為 cURL < 7.22.0" -#: http.c -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "不支援公鑰檔案鎖定,因為 cURL < 7.39.0" - #: http.c msgid "Unknown value for http.proactiveauth" msgstr "http.proactiveauth 的值未知" -#: http.c -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "不支援 CURLSSLOPT_NO_REVOKE,因為 cURL < 7.44.0" - #: http.c #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" @@ -20968,12 +21077,12 @@ msgstr "不支援的 SSL 後端 '%s'。支援的 SSL 後端:" #: http.c #, c-format msgid "Could not set SSL backend to '%s': cURL was built without SSL backends" -msgstr "無法將 SSL 後端設定為 '%s':組建 cURL 時未加入 SSL 後端" +msgstr "無法將 SSL 後端設定為「%s」:組建 cURL 時未加入 SSL 後端" #: http.c #, c-format msgid "Could not set SSL backend to '%s': already set" -msgstr "無法將 SSL 後端設定為 '%s':已經設定" +msgstr "無法將 SSL 後端設定為「%s」:已經設定" #: http.c msgid "refusing to read cookies from http.cookiefile '-'" @@ -21089,7 +21198,7 @@ msgstr "不能混用多種過濾規格" #: list-objects-filter-options.c msgid "unable to upgrade repository format to support partial clone" -msgstr "無法升級版本庫格式,以致不支援部分複製" +msgstr "無法升級版本庫格式,以支援部分複製" #: list-objects-filter-options.h msgid "args" @@ -21180,6 +21289,11 @@ msgstr "偵測到由可列印字元 (quoted) 所組成的 CRLF" msgid "unable to format message: %s" msgstr "無法格式化訊息:%s" +#: merge-ll.c +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "無效的 marker-size「%s」,應為整數" + #: merge-ort.c merge-recursive.c #, c-format msgid "Failed to merge submodule %s (not checked out)" @@ -21751,7 +21865,7 @@ msgstr "合併未返回提交" #: merge-recursive.c #, c-format msgid "Could not parse object '%s'" -msgstr "不能解析物件 '%s'" +msgstr "無法解析物件「%s」" #: merge.c msgid "failed to read the cache" @@ -21871,7 +21985,7 @@ msgstr "無法將一個增量多封裝索引的套件設為過期" #: midx-write.c msgid "Counting referenced objects" -msgstr "正在計算引用物件" +msgstr "正在計算引用物件數量" #: midx-write.c msgid "Finding and deleting unreferenced packfiles" @@ -21883,11 +21997,11 @@ msgstr "無法重新封裝增量的多封裝索引" #: midx-write.c msgid "could not start pack-objects" -msgstr "不能開始 pack-objects" +msgstr "無法啟動 pack-objects" #: midx-write.c msgid "could not finish pack-objects" -msgstr "不能結束 pack-objects" +msgstr "無法結束 pack-objects" #: midx.c msgid "multi-pack-index OID fanout is of the wrong size" @@ -22489,17 +22603,17 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" -"Git 通常不會建立以 40 個十六進位字元結尾的引用,\n" -"因為當您只提供 40 個十六進位字元時,其將被忽略。\n" -"這些引用可能被意外建立。例如:\n" +"Git 通常不會建立以 40 個十六進位字元結尾的參照,\n" +"因為在指定 40 個十六進位字元時會被忽略。\n" +"這些參照可能是意外建立的。例如,\n" "\n" " git switch -c $br $(git rev-parse ...)\n" "\n" -"當「$br」因為某些原因空白時,會建立出 40 位十六進位的引用。\n" -"請檢查這些引用,並視需要刪除。執行\n" -"「git config advice.objectNameWarning false」命令以關閉本訊息通知" +"其中「$br」不知何故為空白,導致建立了一個 40 個\n" +"十六進位字元的參照。請檢查這些參照,必要時刪除它們。\n" +"若要關閉此訊息,請執行「git config set advice.objectNameWarning false」" #: object-name.c #, c-format @@ -22774,7 +22888,7 @@ msgstr "偽合併索引超出範圍(% >= %)" #: pack-bitmap.c #, c-format msgid "could not find '%s' in pack '%s' at offset %" -msgstr "在「%2$s」封包,位移 %3$ 的地方找不到「%1$s」" +msgstr "無法在「%2$s」封包,位移 %3$ 的地方找到「%1$s」" #: pack-bitmap.c #, c-format @@ -23071,7 +23185,7 @@ msgstr "解析 %s 失敗" #: path.c #, c-format msgid "Could not make %s writable by group" -msgstr "不能設定 %s 為組可寫" +msgstr "無法設定 %s 為群組可寫" #: pathspec.c msgid "Escape character '\\' not allowed as last character in attr value" @@ -23325,16 +23439,16 @@ msgstr "無法讀取提交 %s 的延伸偽合併表" #: range-diff.c msgid "could not start `log`" -msgstr "不能啟動 `log`" +msgstr "無法啟動「log」" #: range-diff.c msgid "could not read `log` output" -msgstr "不能讀取 `log` 的輸出" +msgstr "無法讀取「log」的輸出" #: range-diff.c sequencer.c #, c-format msgid "could not parse commit '%s'" -msgstr "不能解析提交 '%s'" +msgstr "無法解析提交「%s」" #: range-diff.c #, c-format @@ -23346,7 +23460,7 @@ msgstr "無法解析第一行「log」輸出:開頭不是「commit」:「%s #: range-diff.c #, c-format msgid "could not parse git header '%.*s'" -msgstr "無法解析 git 頭 '%.*s'" +msgstr "無法解析 git 標頭「%.*s」" #: range-diff.c msgid "failed to generate diff" @@ -23355,7 +23469,7 @@ msgstr "生成 diff 失敗" #: range-diff.c #, c-format msgid "could not parse log for '%s'" -msgstr "不能解析 '%s' 的日誌" +msgstr "無法解析「%s」的日誌" #: reachable.c #, c-format @@ -23509,7 +23623,7 @@ msgstr "無法加入 load_index_extensions 執行緒:%s" #: read-cache.c #, c-format msgid "could not freshen shared index '%s'" -msgstr "無法重新整理共享索引 '%s'" +msgstr "無法更新共享索引「%s」" #: read-cache.c #, c-format @@ -23669,7 +23783,7 @@ msgstr "" #: rebase-interactive.c #, c-format msgid "could not write '%s'." -msgstr "不能寫入 '%s'。" +msgstr "無法寫入「%s」。" #: rebase-interactive.c #, c-format @@ -23879,7 +23993,7 @@ msgstr "本命令拒絕 atom %%(%.*s)" #: ref-filter.c #, c-format msgid "--format=%.*s cannot be used with --python, --shell, --tcl" -msgstr "--format=%.*s 不能和 --python、--shell、--tcl 一起使用" +msgstr "--format=%.*s 無法和 --python、--shell、--tcl 一起使用" #: ref-filter.c msgid "failed to run 'describe'" @@ -24109,6 +24223,11 @@ msgid "" "cannot lock ref '%s': expected symref with target '%s': but is a regular ref" msgstr "無法鎖定引用「%s」:預期是指向「%s」的符號引用,但這個是一般引用" +#: refs/files-backend.c +#, c-format +msgid "cannot read ref file '%s'" +msgstr "無法寫入參照檔案「%s」" + #: refs/files-backend.c #, c-format msgid "cannot open directory %s" @@ -24657,7 +24776,7 @@ msgstr "寫入 '%s' (%s) 時發生錯誤" #: rerere.c #, c-format msgid "could not parse conflict hunks in '%s'" -msgstr "不能解析 '%s' 中的衝突區塊" +msgstr "無法解析「%s」中的衝突區塊" #: rerere.c #, c-format @@ -24724,7 +24843,7 @@ msgstr "如果可能,重用衝突解決更新索引" #: reset.c msgid "could not determine HEAD revision" -msgstr "不能確定 HEAD 版本" +msgstr "無法確定 HEAD 修訂版" #: reset.c sequencer.c #, c-format @@ -25110,7 +25229,7 @@ msgstr "無效的提交說明清理模式 '%s'" #: sequencer.c #, c-format msgid "could not delete '%s'" -msgstr "無法刪除 '%s'" +msgstr "無法刪除「%s」" #: sequencer.c msgid "revert" @@ -25185,12 +25304,12 @@ msgstr "" #: sequencer.c #, c-format msgid "could not lock '%s'" -msgstr "不能鎖定 '%s'" +msgstr "無法鎖定「%s」" #: sequencer.c #, c-format msgid "could not write eol to '%s'" -msgstr "不能將換行符號寫入 '%s'" +msgstr "無法將換行符號寫入「%s」" #: sequencer.c #, c-format @@ -25220,7 +25339,7 @@ msgstr "不能更新快取樹" #: sequencer.c msgid "could not resolve HEAD commit" -msgstr "不能解析 HEAD 提交" +msgstr "無法解析 HEAD 提交" #: sequencer.c #, c-format @@ -25347,7 +25466,7 @@ msgstr "無法找到新建立的提交" #: sequencer.c msgid "could not parse newly created commit" -msgstr "不能解析新建立的提交" +msgstr "無法解析新建立的提交" #: sequencer.c msgid "unable to resolve HEAD after creating commit" @@ -25364,7 +25483,7 @@ msgstr " (根提交)" #: sequencer.c msgid "could not parse HEAD" -msgstr "不能解析 HEAD" +msgstr "無法解析 HEAD" #: sequencer.c #, c-format @@ -25392,12 +25511,12 @@ msgstr "作者資訊損壞:缺少日期資訊" #: sequencer.c #, c-format msgid "could not update %s" -msgstr "不能更新 %s" +msgstr "無法更新 %s" #: sequencer.c #, c-format msgid "could not parse parent commit %s" -msgstr "不能解析父提交 %s" +msgstr "無法解析父提交 %s" #: sequencer.c #, c-format @@ -25438,16 +25557,16 @@ msgstr "需要一個 HEAD 來修復" #: sequencer.c msgid "could not read HEAD" -msgstr "不能讀取 HEAD" +msgstr "無法讀取 HEAD" #: sequencer.c msgid "could not read HEAD's commit message" -msgstr "不能讀取 HEAD 的提交說明" +msgstr "無法讀取 HEAD 的提交說明" #: sequencer.c #, c-format msgid "could not read commit message of %s" -msgstr "不能讀取 %s 的提交說明" +msgstr "無法讀取 %s 的提交說明" #: sequencer.c msgid "your index file is unmerged." @@ -25482,12 +25601,12 @@ msgstr "%s:不能解析父提交 %s" #: sequencer.c #, c-format msgid "could not revert %s... %s" -msgstr "不能還原 %s... %s" +msgstr "無法還原 %s... %s" #: sequencer.c #, c-format msgid "could not apply %s... %s" -msgstr "不能套用 %s... %s" +msgstr "無法套用 %s... %s" #: sequencer.c #, c-format @@ -25580,7 +25699,7 @@ msgstr "缺少 %s 的參數" #: sequencer.c #, c-format msgid "could not parse '%s'" -msgstr "無法解析 '%s'" +msgstr "無法解析「%s」" #: sequencer.c #, c-format @@ -25655,7 +25774,7 @@ msgstr "嘗試 \"git cherry-pick (--continue | %s--abort | --quit)\"" #: sequencer.c #, c-format msgid "could not create sequencer directory '%s'" -msgstr "不能建立序列目錄 '%s'" +msgstr "無法建立序列目錄「%s」" #: sequencer.c msgid "no cherry-pick or revert in progress" @@ -25742,12 +25861,12 @@ msgstr "" #: sequencer.c #, c-format msgid "Could not apply %s... %.*s" -msgstr "不能套用 %s... %.*s" +msgstr "無法套用 %s... %.*s" #: sequencer.c #, c-format msgid "Could not merge %.*s" -msgstr "不能合併 %.*s" +msgstr "無法合併 %.*s" #: sequencer.c #, c-format @@ -25799,7 +25918,7 @@ msgstr "非法的標籤名稱:'%.*s'" #: sequencer.c #, c-format msgid "could not resolve '%s'" -msgstr "無法解析 '%s'" +msgstr "無法解析「%s」" #: sequencer.c msgid "writing fake root commit" @@ -25830,12 +25949,12 @@ msgstr "章魚合並不能在一個新的根提交上執行" #: sequencer.c #, c-format msgid "could not get commit message of '%s'" -msgstr "不能取得 '%s' 的提交說明" +msgstr "無法取得「%s」的提交說明" #: sequencer.c #, c-format msgid "could not even attempt to merge '%.*s'" -msgstr "甚至不能嘗試合併 '%.*s'" +msgstr "甚至無法嘗試合併「%.*s」" #: sequencer.c msgid "merge: Unable to write new index file" @@ -25877,7 +25996,7 @@ msgstr "意外的 stash 回應:'%s'" #: sequencer.c #, c-format msgid "Could not create directory for '%s'" -msgstr "不能為 '%s' 建立目錄" +msgstr "無法建立「%s」的目錄" #: sequencer.c #, c-format @@ -25923,7 +26042,7 @@ msgstr "autostash 引用是符號引用" #: sequencer.c msgid "could not detach HEAD" -msgstr "不能分離開頭指標" +msgstr "無法分離開頭指標" #: sequencer.c #, c-format @@ -25950,7 +26069,7 @@ msgstr "" "無法執行待辦指令\n" "\n" " %.*s\n" -"已被重新安排,在繼續之前編輯指令,請先編輯待辦列表:\n" +"已經重新安排。若要在繼續之前編輯指令,請先編輯待辦列表:\n" "\n" " git rebase --edit-todo\n" " git rebase --continue\n" @@ -25972,16 +26091,16 @@ msgstr "未知指令 %d" #: sequencer.c msgid "could not read orig-head" -msgstr "不能讀取 orig-head" +msgstr "無法讀取 orig-head" #: sequencer.c msgid "could not read 'onto'" -msgstr "不能讀取 'onto'" +msgstr "無法讀取「onto」" #: sequencer.c #, c-format msgid "could not update HEAD to %s" -msgstr "不能更新 HEAD 為 %s" +msgstr "無法將 HEAD 更新到 %s" #: sequencer.c #, c-format @@ -26018,15 +26137,15 @@ msgstr "" #: sequencer.c #, c-format msgid "could not write file: '%s'" -msgstr "不能寫入檔案:'%s'" +msgstr "無法寫入檔案:「%s」" #: sequencer.c msgid "could not remove CHERRY_PICK_HEAD" -msgstr "不能刪除 CHERRY_PICK_HEAD" +msgstr "無法刪除 CHERRY_PICK_HEAD" #: sequencer.c msgid "could not commit staged changes." -msgstr "不能提交暫存的修改。" +msgstr "無法提交暫存的修改。" #: sequencer.c #, c-format @@ -26446,7 +26565,7 @@ msgstr "忽略可能被解析為命令列選項的 '%s':%s" #: submodule-config.c #, c-format msgid "Could not update .gitmodules entry %s" -msgstr "不能更新 .gitmodules 條目 %s" +msgstr "無法更新 .gitmodules 條目 %s" #: submodule.c msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first" @@ -26498,7 +26617,7 @@ msgstr "子模組條目 '%s'(%s)是一個 %s,不是一個提交" msgid "" "Could not run 'git rev-list --not --remotes -n 1' command in " "submodule %s" -msgstr "無法在 %s 子模組執行 'git rev-list <提交> --not --remotes -n 1' 命令" +msgstr "無法在 %s 子模組執行「git rev-list <提交> --not --remotes -n 1」命令" #: submodule.c #, c-format @@ -26552,7 +26671,7 @@ msgstr "無法將 '%s' 識別為一個 git 版本庫" #: submodule.c #, c-format msgid "Could not run 'git status --porcelain=2' in submodule %s" -msgstr "無法在 %s 子模組執行 'git status --porcelain=2'" +msgstr "無法在 %s 子模組執行「git status --porcelain=2」" #: submodule.c #, c-format @@ -26562,17 +26681,17 @@ msgstr "%s 子模組執行 'git status --porcelain=2' 失敗" #: submodule.c #, c-format msgid "could not start 'git status' in submodule '%s'" -msgstr "無法在子模組 '%s' 中啟動 'git status'" +msgstr "無法在子模組「%s」中啟動「git status」" #: submodule.c #, c-format msgid "could not run 'git status' in submodule '%s'" -msgstr "無法在子模組 '%s' 中執行 'git status'" +msgstr "無法在子模組「%s」中執行「git status」" #: submodule.c #, c-format msgid "Could not unset core.worktree setting in submodule '%s'" -msgstr "無法在子模組 '%s' 中取消 core.worktree 的設定" +msgstr "無法在子模組「%s」中取消 core.worktree 的設定" #: submodule.c #, c-format @@ -26617,7 +26736,7 @@ msgstr "不支援對有多個工作區的子模組 '%s' 執行 relocate_gitdir" #: submodule.c #, c-format msgid "could not lookup name for submodule '%s'" -msgstr "不能查詢子模組 '%s' 的名稱" +msgstr "無法查詢子模組「%s」的名稱" #: submodule.c #, c-format @@ -26833,7 +26952,7 @@ msgstr "%s 也鎖定了 %s" #: transport-helper.c msgid "couldn't run fast-import" -msgstr "不能執行 fast-import" +msgstr "無法執行 fast-import" #: transport-helper.c msgid "error while running fast-import" @@ -27014,7 +27133,7 @@ msgstr "伺服器不支援「等待完成」(wait-for-done) 功能" #: transport.c msgid "could not parse transport.color.* config" -msgstr "不能解析 transport.color.* 設定" +msgstr "無法解析 transport.color.* 組態" #: transport.c msgid "support for protocol v2 not implemented yet" @@ -27452,6 +27571,10 @@ msgstr ".git 檔案損毀" msgid ".git file incorrect" msgstr ".git 檔案不正確" +#: worktree.c +msgid ".git file absolute/relative path mismatch" +msgstr ".git 檔案的絕對或相對路徑不一致" + #: worktree.c msgid "not a valid path" msgstr "非有效路徑" @@ -27472,6 +27595,10 @@ msgstr "無法定位版本庫;.git 檔案損壞" msgid "gitdir unreadable" msgstr "無法讀取 gitdir" +#: worktree.c +msgid "gitdir absolute/relative path mismatch" +msgstr "gitdir 的絕對或相對路徑不一致" + #: worktree.c msgid "gitdir incorrect" msgstr "不正確的 gitdir" @@ -27516,10 +27643,18 @@ msgstr "無法取消在「%2$s」設定的 %1$s" msgid "failed to set extensions.worktreeConfig setting" msgstr "無法設定 extensions.worktreeConfig 設定" +#: worktree.c +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "無法升級版本庫格式,以支援相對路徑工作區" + +#: worktree.c +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "無法設定 extensions.relativeWorktrees 設定" + #: wrapper.c #, c-format msgid "could not setenv '%s'" -msgstr "無法 setenv '%s'" +msgstr "無法 setenv「%s」" #: wrapper.c #, c-format @@ -27529,7 +27664,7 @@ msgstr "不能建立 '%s'" #: wrapper.c #, c-format msgid "could not open '%s' for reading and writing" -msgstr "無法開啟 '%s' 進行讀寫" +msgstr "無法開啟「%s」進行讀寫" #: wrapper.c #, c-format @@ -28582,6 +28717,20 @@ msgstr "略過 %s 含備份後綴 '%s'。\n" msgid "Do you really want to send %s? [y|N]: " msgstr "您真的要傳送 %s?[y|N]: " +#, c-format +#~ msgid "" +#~ "more than %i tags found; listed %i most recent\n" +#~ "gave up search at %s\n" +#~ msgstr "" +#~ "發現多於 %i 個標籤,列出最近的 %i 個\n" +#~ "在 %s 放棄搜尋\n" + +#~ msgid "Public key pinning not supported with cURL < 7.39.0" +#~ msgstr "不支援公鑰檔案鎖定,因為 cURL < 7.39.0" + +#~ msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" +#~ msgstr "不支援 CURLSSLOPT_NO_REVOKE,因為 cURL < 7.44.0" + #~ msgid "revision walk setup failed\n" #~ msgstr "修訂版遍歷設定失敗\n" From 2c3ca00b48fb06f1b59ee6fa4401346e42806cb2 Mon Sep 17 00:00:00 2001 From: Yi-Jyun Pan Date: Sat, 28 Dec 2024 13:23:19 +0800 Subject: [PATCH 016/306] l10n: zh_TW: Git 2.48 round 2 Co-authored-by: Lumynous Signed-off-by: Yi-Jyun Pan --- po/zh_TW.po | 152 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 98 insertions(+), 54 deletions(-) diff --git a/po/zh_TW.po b/po/zh_TW.po index 28afb3662f72a5..a61f544304d34d 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -30,8 +30,8 @@ msgid "" msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-12-17 20:14+0800\n" -"PO-Revision-Date: 2024-12-17 20:52+0800\n" +"POT-Creation-Date: 2024-12-28 13:16+0800\n" +"PO-Revision-Date: 2024-12-28 13:23+0800\n" "Last-Translator: Yi-Jyun Pan \n" "Language-Team: Chinese (Traditional) \n" @@ -941,12 +941,12 @@ msgstr "引數過多" #: apply.c #, c-format msgid "unrecognized whitespace option '%s'" -msgstr "空白字元選項「%s」無法識別" +msgstr "空白字元選項「%s」不認識" #: apply.c #, c-format msgid "unrecognized whitespace ignore option '%s'" -msgstr "空白字元忽略選項「%s」無法識別" +msgstr "空白字元忽略選項「%s」不認識" #: apply.c archive.c builtin/add.c builtin/branch.c builtin/checkout-index.c #: builtin/checkout.c builtin/clean.c builtin/clone.c builtin/commit.c @@ -1081,7 +1081,7 @@ msgstr "二進位修補檔在第 %d 列損壞:%.*s" #: apply.c #, c-format msgid "unrecognized binary patch at line %d" -msgstr "第 %d 列的二進位修補檔無法識別" +msgstr "第 %d 列的二進位修補檔不認識" #: apply.c #, c-format @@ -3021,7 +3021,7 @@ msgstr "「 」不是有效術語" #: builtin/bisect.c #, c-format msgid "unrecognized option: '%s'" -msgstr "無法識別選項:「%s」" +msgstr "不認識選項:「%s」" #: builtin/bisect.c #, c-format @@ -4689,7 +4689,7 @@ msgstr "未知的衝突輸出風格「%s」" msgid "perform a 3-way merge with the new branch" msgstr "和新分支進行三方合併" -#: builtin/checkout.c builtin/log.c parse-options.h +#: builtin/checkout.c builtin/log.c builtin/range-diff.c parse-options.h msgid "style" msgstr "style" @@ -4719,7 +4719,7 @@ msgstr "更新忽略的檔案(預設值)" #: builtin/checkout.c msgid "do not check if another worktree is using this branch" -msgstr "不檢查其他工作區是否正在使用這個提交" +msgstr "不檢查其他工作區是否正在使用此分支" #: builtin/checkout.c msgid "checkout our version for unmerged files" @@ -5100,7 +5100,6 @@ msgid "create a shallow clone since a specific time" msgstr "建立從指定時間到現在的淺層複製" #: builtin/clone.c builtin/fetch.c builtin/pull.c -#| msgid "rev" msgid "ref" msgstr "ref" @@ -5495,7 +5494,7 @@ msgstr "無法開啟提交圖鏈「%s」" #: builtin/commit-graph.c #, c-format msgid "unrecognized --split argument, %s" -msgstr "無法識別的 --split 參數,%s" +msgstr "不認識的 --split 參數,%s" #: builtin/commit-graph.c #, c-format @@ -6253,9 +6252,6 @@ msgstr "" "value] <名稱> <值>" #: builtin/config.c -#| msgid "" -#| "git config unset [] [--all] [--value=] [--fixed-" -#| "value] " msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " "" @@ -6393,7 +6389,7 @@ msgstr "除了顯示組態值,額外顯示其鍵名" #: builtin/config.c #, c-format msgid "unrecognized --type argument, %s" -msgstr "無法識別的 --type 參數,%s" +msgstr "不認識的 --type 參數,%s" #: builtin/config.c msgid "only one type at a time" @@ -7356,6 +7352,21 @@ msgstr "%s 不是一個有效的物件" msgid "the object %s does not exist" msgstr "%s 物件不存在" +#: builtin/fetch.c +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"執行「git remote set-head %s %s」以追蹤這個變更,或者\n" +"如果您不想看到這則訊息,請將「remote.%s.followRemoteHEAD」\n" +"組態選項設定成不同的值。更具體些來說,執行\n" +"「git config set remote.%s.followRemoteHEAD %s」會停用這個警告,\n" +"直到遠端將 HEAD 變更為其他內容。" + #: builtin/fetch.c msgid "multiple branches detected, incompatible with --set-upstream" msgstr "檢測到多分支,和 --set-upstream 不相容" @@ -8254,7 +8265,7 @@ msgstr "不允許 --no-schedule" #: builtin/gc.c #, c-format msgid "unrecognized --schedule argument '%s'" -msgstr "無法識別的 --schedule 引數 '%s'" +msgstr "不認識的 --schedule 引數 '%s'" #: builtin/gc.c msgid "failed to write commit-graph" @@ -8428,7 +8439,7 @@ msgstr "無法執行 systemctl" #: builtin/gc.c #, c-format msgid "unrecognized --scheduler argument '%s'" -msgstr "無法識別的 --scheduler 引數 '%s'" +msgstr "不認識的 --scheduler 引數 '%s'" #: builtin/gc.c msgid "neither systemd timers nor crontab are available" @@ -8846,7 +8857,7 @@ msgstr "git help [[-i|--info] [-m|--man] [-w|--web]] [|]" #: builtin/help.c #, c-format msgid "unrecognized help format '%s'" -msgstr "無法識別的協助格式 '%s'" +msgstr "不認識的協助格式 '%s'" #: builtin/help.c msgid "Failed to start emacsclient." @@ -9429,7 +9440,7 @@ msgstr "追蹤 <開始>,<結束> 範圍中橫列或 <檔案> 中> :<函數名稱 #: builtin/log.c builtin/replay.c builtin/shortlog.c bundle.c #, c-format msgid "unrecognized argument: %s" -msgstr "無法識別的引數:%s" +msgstr "不認識的引數:%s" #: builtin/log.c msgid "-L: cannot be used with pathspec" @@ -10923,10 +10934,6 @@ msgid "git notes [--ref ] [list []]" msgstr "git notes [--ref <註解引用>] [list [<物件>]]" #: builtin/notes.c -#| msgid "" -#| "git notes [--ref ] add [-f] [--allow-empty] [--" -#| "[no-]separator|--separator=] [--[no-]stripspace] [-m " -#| " | -F | (-c | -C) ] []" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " @@ -10941,10 +10948,6 @@ msgid "git notes [--ref ] copy [-f] " msgstr "git notes [--ref <註解引用>] copy [-f] <來源物件> <目標物件>" #: builtin/notes.c -#| msgid "" -#| "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" -#| "separator=] [--[no-]stripspace] [-m | -F | " -#| "(-c | -C) ] []" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " @@ -12634,7 +12637,7 @@ msgstr "--empty=ask 已棄用。請改用「--empty=stop」。" msgid "" "unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and " "\"stop\"." -msgstr "無法識別空類型「%s」;有效的數值有「drop」、「keep」跟「stop」。" +msgstr "不認識空類型「%s」;有效的數值有「drop」、「keep」跟「stop」。" #: builtin/rebase.c msgid "" @@ -13611,6 +13614,33 @@ msgid " Local ref configured for 'git push'%s:" msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " 為 'git push' 設定的本機引用%s:" +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "「%s/HEAD」沒有變更,指向「%s」\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "「%s/HEAD」已經從「%s」變更,現在指向「%s」\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "「%s/HEAD」現在已經建立並指向「%s」\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "「%s/HEAD」已經在「%s」處分離,現在指向「%s」\n" + +#: builtin/remote.c +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "「%s/HEAD」原本指向「%s」(不是遠端分支),但現在指向「%s」\n" + #: builtin/remote.c msgid "set refs/remotes//HEAD according to remote" msgstr "根據遠端設定 refs/remotes/<名稱>/HEAD" @@ -13639,7 +13669,7 @@ msgstr "不是一個有效引用:%s" #: builtin/remote.c #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "無法配置 %s" # 譯者:請維持前導空格 @@ -17178,7 +17208,7 @@ msgstr "無法儲存最大的建立權杖" #: bundle-uri.c #, c-format msgid "unrecognized bundle mode from URI '%s'" -msgstr "無法識別從 URI「%s」取回的套件包模式" +msgstr "不認識從 URI「%s」取回的套件包模式" #: bundle-uri.c #, c-format @@ -17219,7 +17249,7 @@ msgstr "bundle-uri: 列有空鍵或空值" #: bundle.c #, c-format msgid "unrecognized bundle hash algorithm: %s" -msgstr "無法識別的套件包雜湊演算法:%s" +msgstr "不認識的套件包雜湊演算法:%s" #: bundle.c #, c-format @@ -17234,7 +17264,7 @@ msgstr "「%s」不像是一個 v2 或 v3 版本的套件包檔案" #: bundle.c #, c-format msgid "unrecognized header: %s%s (%d)" -msgstr "無法識別的標頭:%s%s (%d)" +msgstr "不認識的標頭:%s%s (%d)" #: bundle.c msgid "Repository lacks these prerequisite commits:" @@ -20233,12 +20263,12 @@ msgstr "路徑規格 '%s' 未符合任何 git 已知檔案" #: dir.c #, c-format msgid "unrecognized pattern: '%s'" -msgstr "無法識別樣式:「%s」" +msgstr "不認識樣式:「%s」" #: dir.c #, c-format msgid "unrecognized negative pattern: '%s'" -msgstr "無法識別反向模式:「%s」" +msgstr "不認識反向模式:「%s」" #: dir.c #, c-format @@ -22807,15 +22837,6 @@ msgstr "多包位圖缺少需要的反向索引" msgid "could not open pack %s" msgstr "無法開啟封包 %s" -#: pack-bitmap.c t/helper/test-read-midx.c -msgid "could not determine MIDX preferred pack" -msgstr "無法確定 MIDX 偏好的封裝" - -#: pack-bitmap.c -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "偏好的封包 (%s) 無效" - #: pack-bitmap.c msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "位圖查詢表損壞:三元組位置超出索引" @@ -23681,7 +23702,7 @@ msgstr "" #, c-format msgid "" "unrecognized setting %s for option rebase.missingCommitsCheck. Ignoring." -msgstr "選項 rebase.missingCommitsCheck 的值 %s 無法識別。已忽略。" +msgstr "選項 rebase.missingCommitsCheck 的值 %s 不認識。已忽略。" #: rebase-interactive.c msgid "" @@ -23842,7 +23863,7 @@ msgstr "%%(%.*s) 不取引數" #: ref-filter.c #, c-format msgid "unrecognized %%(%.*s) argument: %s" -msgstr "無法識別的 %%(%.*s) 引數:%s" +msgstr "不認識的 %%(%.*s) 引數:%s" #: ref-filter.c #, c-format @@ -23852,7 +23873,7 @@ msgstr "期望的格式:%%(color:<顏色>)" #: ref-filter.c #, c-format msgid "unrecognized color: %%(color:%s)" -msgstr "無法識別的顏色:%%(color:%s)" +msgstr "不認識的顏色:%%(color:%s)" #: ref-filter.c #, c-format @@ -23912,17 +23933,17 @@ msgstr "期望的格式:%%(align:<寬度>,<位置>)" #: ref-filter.c #, c-format msgid "unrecognized position:%s" -msgstr "無法識別的位置:%s" +msgstr "不認識的位置:%s" #: ref-filter.c #, c-format msgid "unrecognized width:%s" -msgstr "無法識別的寬度:%s" +msgstr "不認識的寬度:%s" #: ref-filter.c #, c-format msgid "unrecognized %%(%s) argument: %s" -msgstr "無法識別的 %%(%s) 參數:%s" +msgstr "不認識的 %%(%s) 參數:%s" #: ref-filter.c #, c-format @@ -24151,8 +24172,19 @@ msgid "log for %s is empty" msgstr "%s 的日誌為空" #: refs.c -msgid "refusing to force and skip creation of reflog" -msgstr "拒絕強制並略過建立引用日誌" +#, c-format +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "拒絕更新偽引用「%s」 的 reflog" + +#: refs.c +#, c-format +msgid "refusing to update pseudoref '%s'" +msgstr "拒絕更新偽引用「%s」" + +#: refs.c +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "拒絕更新有錯誤名稱「%s」的 reflog" #: refs.c #, c-format @@ -24160,9 +24192,8 @@ msgid "refusing to update ref with bad name '%s'" msgstr "拒絕更新有錯誤名稱 '%s' 的引用" #: refs.c -#, c-format -msgid "refusing to update pseudoref '%s'" -msgstr "拒絕更新偽引用「%s」" +msgid "refusing to force and skip creation of reflog" +msgstr "拒絕強制並略過建立引用日誌" #: refs.c #, c-format @@ -24484,10 +24515,15 @@ msgstr "提供了一個以上的 receivepack,使用第一個" msgid "more than one uploadpack given, using the first" msgstr "提供了一個以上的 uploadpack,使用第一個" +#: remote.c +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "已經忽略不認識的「%s」數值" + #: remote.c #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" -msgstr "數值 transfer.credentialsInUrl 無法識別:「%s」" +msgstr "數值 transfer.credentialsInUrl 不認識:「%s」" #: remote.c #, c-format @@ -26797,6 +26833,10 @@ msgstr "提交 %s 沒有標記為可以取得" msgid "too many commits marked reachable" msgstr "太多提交標記為可以取得" +#: t/helper/test-read-midx.c +msgid "could not determine MIDX preferred pack" +msgstr "無法確定 MIDX 偏好的封裝" + #: t/helper/test-serve-v2.c msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 [<選項>]" @@ -28717,6 +28757,10 @@ msgstr "略過 %s 含備份後綴 '%s'。\n" msgid "Do you really want to send %s? [y|N]: " msgstr "您真的要傳送 %s?[y|N]: " +#, c-format +#~ msgid "preferred pack (%s) is invalid" +#~ msgstr "偏好的封包 (%s) 無效" + #, c-format #~ msgid "" #~ "more than %i tags found; listed %i most recent\n" From 31f5549c285ec793a67a9d072db38fec087c7e32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-No=C3=ABl=20Avila?= Date: Fri, 20 Dec 2024 18:37:48 +0100 Subject: [PATCH 017/306] l10n: fr: v2.48.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jean-Noël Avila --- po/fr.po | 331 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 243 insertions(+), 88 deletions(-) diff --git a/po/fr.po b/po/fr.po index b113e3870b925f..64a75aecf5859c 100644 --- a/po/fr.po +++ b/po/fr.po @@ -87,8 +87,8 @@ msgid "" msgstr "" "Project-Id-Version: git\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-02 16:57+0000\n" -"PO-Revision-Date: 2024-10-04 23:03+0200\n" +"POT-Creation-Date: 2024-12-23 18:57+0000\n" +"PO-Revision-Date: 2024-12-29 18:26+0100\n" "Last-Translator: Cédric Malard \n" "Language-Team: Jean-Noël Avila \n" "Language: fr\n" @@ -468,14 +468,14 @@ msgstr "" #, c-format msgid "Apply mode change to index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Appliquer le changement de mode dans l'index et l'arbre de travail [y,n,q,a," -"d%s,?] ? " +"Appliquer le changement de mode dans l'index et l'arbre de travail " +"[y,n,q,a,d%s,?] ? " #, c-format msgid "Apply deletion to index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Appliquer la suppression dans l'index et l'arbre de travail [y,n,q,a," -"d%s,?] ? " +"Appliquer la suppression dans l'index et l'arbre de travail " +"[y,n,q,a,d%s,?] ? " #, c-format msgid "Apply addition to index and worktree [y,n,q,a,d%s,?]? " @@ -717,10 +717,10 @@ msgstr "Seuls des fichiers binaires ont changé." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"Désactivez ce message avec \"git config advice.%s false\"" +"Désactivez ce message avec \"git config set advice.%s false\"" #, c-format msgid "%shint:%s%.*s%s\n" @@ -844,8 +844,8 @@ msgstr "" "\n" " git switch -\n" "\n" -"Désactivez ce conseil en renseignant la variable de configuration advice." -"detachedHead à false\n" +"Désactivez ce conseil en renseignant la variable de configuration " +"advice.detachedHead à false\n" "\n" #, c-format @@ -919,14 +919,14 @@ msgstr "" #, c-format msgid "git apply: bad git-diff - inconsistent new filename on line %d" msgstr "" -"git apply : mauvais format de git-diff - nouveau nom de fichier incohérent " -"à la ligne %d" +"git apply : mauvais format de git-diff - nouveau nom de fichier incohérent à " +"la ligne %d" #, c-format msgid "git apply: bad git-diff - inconsistent old filename on line %d" msgstr "" -"git apply : mauvais format de git-diff - ancien nom de fichier incohérent " -"à la ligne %d" +"git apply : mauvais format de git-diff - ancien nom de fichier incohérent à " +"la ligne %d" #, c-format msgid "git apply: bad git-diff - expected /dev/null on line %d" @@ -1039,8 +1039,8 @@ msgstr "données de rustine binaire manquantes pour '%s'" #, c-format msgid "cannot reverse-apply a binary patch without the reverse hunk to '%s'" msgstr "" -"impossible d'appliquer l'inverse d'une rustine binaire à '%s' sans la section " -"inverse" +"impossible d'appliquer l'inverse d'une rustine binaire à '%s' sans la " +"section inverse" #, c-format msgid "cannot apply binary patch to '%s' without full index line" @@ -1052,7 +1052,8 @@ msgstr "" msgid "" "the patch applies to '%s' (%s), which does not match the current contents." msgstr "" -"la rustine s'applique à '%s' (%s), ce qui ne correspond pas au contenu actuel." +"la rustine s'applique à '%s' (%s), ce qui ne correspond pas au contenu " +"actuel." #, c-format msgid "the patch applies to an empty '%s' but it is not empty" @@ -1473,8 +1474,9 @@ msgstr "nom d'objet invalide : %s" msgid "not a tree object: %s" msgstr "objet arbre invalide : %s" -msgid "unable to checkout working tree" -msgstr "impossible d'extraire la copie de travail" +#, c-format +msgid "failed to unpack tree object %s" +msgstr "échec du dépaquetage de l'objet arbre %s" #, c-format msgid "File not found: %s" @@ -3137,11 +3139,11 @@ msgid "HEAD not found below refs/heads!" msgstr "HEAD non trouvée sous refs/heads !" msgid "" -"branch with --recurse-submodules can only be used if submodule." -"propagateBranches is enabled" +"branch with --recurse-submodules can only be used if " +"submodule.propagateBranches is enabled" msgstr "" -"brancher avec --recurse-submodules ne peut être utilisé que si submodule." -"propagateBranches est activé" +"brancher avec --recurse-submodules ne peut être utilisé que si " +"submodule.propagateBranches est activé" msgid "--recurse-submodules can only be used to create branches" msgstr "--recurse-submodules ne peut être utilisé que pour créer des branches" @@ -3592,10 +3594,6 @@ msgstr "git check-mailmap [] ..." msgid "also read contacts from stdin" msgstr "lire aussi les contacts depuis l'entrée standard" -#, c-format -msgid "unable to parse contact: %s" -msgstr "impossible d'analyser le contact : %s" - msgid "read additional mailmap entries from file" msgstr "lire des entrées supplémentaires de mailmap depuis un fichier" @@ -4007,9 +4005,8 @@ msgstr "nouvelle branche non née" msgid "update ignored files (default)" msgstr "mettre à jour les fichiers ignorés (par défaut)" -msgid "do not check if another worktree is holding the given ref" -msgstr "" -"ne pas vérifier si une autre copie de travail contient le référence fournie" +msgid "do not check if another worktree is using this branch" +msgstr "ne pas vérifier si une autre copie-de-travail utilise cette branche" msgid "checkout our version for unmerged files" msgstr "extraire notre version pour les fichiers non fusionnés" @@ -4312,12 +4309,11 @@ msgstr "créer un clone superficiel de cette profondeur" msgid "create a shallow clone since a specific time" msgstr "créer un clone superficiel depuis une date spécifique" -msgid "revision" -msgstr "révision" +msgid "ref" +msgstr "ref" -msgid "deepen history of shallow clone, excluding rev" -msgstr "" -"approfondir l'historique d'un clone superficiel en excluant une révision" +msgid "deepen history of shallow clone, excluding ref" +msgstr "approfondit l'historique d'un clone superficiel en excluant une ref" msgid "clone only one branch, HEAD or --branch" msgstr "cloner seulement une branche, HEAD ou --branch" @@ -4451,6 +4447,9 @@ msgstr "" "la HEAD distante réfère à une référence non existante, impossible de " "l'extraire" +msgid "unable to checkout working tree" +msgstr "impossible d'extraire la copie de travail" + msgid "unable to write parameters to config file" msgstr "impossible d'écrire les paramètres dans le fichier de configuration" @@ -5270,10 +5269,10 @@ msgstr "" msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" "git config unset [] [--all] [--value=] [--fixed-" -"value] " +"value] " msgid "git config rename-section [] " msgstr "" @@ -5719,13 +5718,8 @@ msgid "traversed %lu commits\n" msgstr "%lu commits parcourus\n" #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"plus de %i étiquettes ont été trouvées; seules les %i plus récentes sont " -"affichées\n" -"abandon de la recherche à %s\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "%i étiquettes trouvées ; recherche abandonnée à %s\n" #, c-format msgid "describe %s\n" @@ -6169,6 +6163,20 @@ msgstr "%s n'est pas un objet valide" msgid "the object %s does not exist" msgstr "l'objet %s n'existe pas" +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Lancez 'git remote set-head %s %s' pour suivre la modification, ou\n" +"réglez l'option de configuration 'remote.%s.followRemoteHEAD' à une\n" +"valeur différente si vous ne souhaitez pas voir ce message. Lancer\n" +"spécifiquement 'git config set remote.%s.followRemoteHEAD %s'\n" +"va désactiver l'alerte jusqu'à ce que le distant change HEAD." + msgid "multiple branches detected, incompatible with --set-upstream" msgstr "branches multiples détectées, imcompatible avec --set-upstream" @@ -6309,6 +6317,9 @@ msgstr "correspondance de référence" msgid "specify fetch refmap" msgstr "spécifier une correspondance de référence pour la récupération" +msgid "revision" +msgstr "révision" + msgid "report that we have only objects reachable from this object" msgstr "rapporte que nous n'avons que des objets joignables depuis cet objet" @@ -6366,8 +6377,8 @@ msgid "protocol does not support --negotiate-only, exiting" msgstr "Le protocole ne prend pas en charge --negotiate-only, abandon" msgid "" -"--filter can only be used with the remote configured in extensions." -"partialclone" +"--filter can only be used with the remote configured in " +"extensions.partialclone" msgstr "" "--filter ne peut être utilisé qu'avec le dépôt distant configuré dans " "extensions.partialclone" @@ -7042,9 +7053,27 @@ msgstr "ni les minuteurs systemd ni crontab ne sont disponibles" msgid "%s scheduler is not available" msgstr "le planificateur %s n'est pas disponible" -msgid "another process is scheduling background maintenance" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"impossible de créer '%s.lock' : %s.\n" +"\n" +"Il semble qu'un processus git-maintenance(1) programmé est déjà lancé dans\n" +"ce dépôt. Veuillez vous assurer qu'aucun processus de maintenance n'est " +"lancé\n" +"et réessayez. Si l'échec persiste, un processus git-maintenance(1) peut " +"avoir\n" +"planté dans ce dépôt : supprimez le fichier manuellement pour poursuivre." + +msgid "cannot acquire lock for scheduled background maintenance" msgstr "" -"un autre processus est en train de programmer une maintenance en tâche de " +"impossible d'acquérir le verrou pour une maintenance programmée en tâche de " "fond" msgid "git maintenance start [--scheduler=]" @@ -7625,6 +7654,22 @@ msgid_plural "chain length = %d: %lu objects" msgstr[0] "longueur chaînée = %d : %lu objet" msgstr[1] "longueur chaînée = %d : %lu objets" +msgid "could not start pack-objects to repack local links" +msgstr "" +"impossible de démarrer pack-objects pour ré-empaqueter les liens locaux" + +msgid "failed to feed local object to pack-objects" +msgstr "échéc de la fourniture les objets locaux à pack-objects" + +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack : attente de lignes d'Id d'objets en hexa complet seulement " +"depuis les objects de paquet." + +msgid "could not finish pack-objects to repack local links" +msgstr "" +"impossible de terminer pack-objects pour ré-empaqueter les objets locaux" + msgid "Cannot come back to cwd" msgstr "Impossible de revenir au répertoire de travail courant" @@ -7636,6 +7681,9 @@ msgstr "mauvais %s" msgid "unknown hash algorithm '%s'" msgstr "algorithme d'empreinte inconnu '%s'" +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor ne peut pas être utilisé avec un nom de paquet" + msgid "--stdin requires a git repository" msgstr "--stdin requiert un dépôt git" @@ -9022,11 +9070,11 @@ msgstr "git notes [--ref ] [list []]" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] add [-f] [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace] [-m " -" | -F | (-c | -C) ] []" +" | -F | (-c | -C) ] [] [-e]" msgid "git notes [--ref ] copy [-f] " msgstr "" @@ -9035,11 +9083,11 @@ msgstr "" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] append [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace]-m " -" | -F | (-c | -C) ] []" +" | -F | (-c | -C) ] [] [-e]" msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref ] edit [--allow-empty] []" @@ -9158,6 +9206,9 @@ msgstr "contenu de la note dans un fichier" msgid "reuse and edit specified note object" msgstr "réutiliser et éditer l'objet de note spécifié" +msgid "edit note message in editor" +msgstr "éditer le message de node dans un éditeur" + msgid "reuse specified note object" msgstr "réutiliser l'objet de note spécifié" @@ -9667,6 +9718,9 @@ msgstr "gestion des objets manquants" msgid "do not pack objects in promisor packfiles" msgstr "ne pas empaqueter les objets dans les fichiers paquets prometteurs" +msgid "implies --missing=allow-any" +msgstr "implique --missing=allow-any" + msgid "respect islands during delta compression" msgstr "respecter les îlots pendant la compression des deltas" @@ -10079,8 +10133,8 @@ msgstr "" msgid "" "You didn't specify any refspecs to push, and push.default is \"nothing\"." msgstr "" -"Vous n'avez pas spécifié de spécifications de référence à pousser, et push." -"default est \"nothing\"." +"Vous n'avez pas spécifié de spécifications de référence à pousser, et " +"push.default est \"nothing\"." #, c-format msgid "" @@ -11302,6 +11356,30 @@ msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " Référence locale configurée pour 'git push'%s :" msgstr[1] " Références locales configurées pour 'git push'%s :" +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "'%s/HEAD' est inchangé et pointe sur '%s'\n" + +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' a changé depuis '%s' et pointe à présent sur '%s'\n" + +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "'%s/HEAD' a été créé et pointe sur '%s'\n" + +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' est détaché de '%s' et pointe à présent sur '%s'\n" + +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"'%s/HEAD' pointait sur '%s' (qui n'est pas une branche distante), mais " +"pointe à présent sur '%s'\n" + msgid "set refs/remotes//HEAD according to remote" msgstr "définir refs/remotes//HEAD selon la distante" @@ -11325,7 +11403,7 @@ msgid "Not a valid ref: %s" msgstr "Référence non valide : %s" #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "Impossible de paramétrer %s" #, c-format @@ -14112,6 +14190,9 @@ msgstr "régler le mode de suivi (voir git-branch(1))" msgid "try to match the new branch name with a remote-tracking branch" msgstr "essayer de nommer la nouvelle branche comme la branche amont" +msgid "use relative paths for worktrees" +msgstr "utiliser des chemins relatifs pour les arbres-de-travail" + #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" msgstr "les options '%s', '%s' et '%s' ne peuvent pas être utilisées ensemble" @@ -14394,6 +14475,26 @@ msgstr "impossible de créer '%s'" msgid "index-pack died" msgstr "l'index de groupe a disparu" +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "Le répertoire '%s' est présent dans l'index et pourtant pas clairsemé" + +msgid "corrupted cache-tree has entries not present in index" +msgstr "l'arbre de cache corrompu a des entrées non présentes dans l'index" + +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "%s avec les drapeaux 0x%x ne devrait pas être dans l'arbre de cache" + +#, c-format +msgid "bad subtree '%.*s'" +msgstr "mauvais sous-arbre '%.*s'" + +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "" +"l'arbre de cache pour le chemin %.*s ne correspond pas. %s attendu, %s obtenu" + msgid "terminating chunk id appears earlier than expected" msgstr "l'identifiant de terminaison de tronçon apparaît plus tôt qu'attendu" @@ -15217,8 +15318,8 @@ msgid "" "attempting to write a commit-graph, but 'commitGraph.changedPathsVersion' " "(%d) is not supported" msgstr "" -"essai d'écriture de graphe de commits, mais 'commitGraph." -"changedPathsVersion' (%d) n'est pas pris en charge" +"essai d'écriture de graphe de commits, mais " +"'commitGraph.changedPathsVersion' (%d) n'est pas pris en charge" msgid "too many commits to write graph" msgstr "trop de commits pour écrire un graphe" @@ -15307,13 +15408,13 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "Le support de /info/grafts est déconseillé\n" "et sera supprimé dans une version future de Git.\n" "\n" "Veuillez utiliser \"git replace --convert-graft-file\"\n" -"pour convertir les grafts en référence de remplacement.\n" +"pour convertir les grafts en références de remplacement.\n" "\n" "Supprimez ce message en lançant\n" "\"git config advice.graftFileDeprecated false\"" @@ -16154,6 +16255,18 @@ msgstr "l'url n'a pas de schéma : %s" msgid "credential url cannot be parsed: %s" msgstr "impossible d'analyser l'url d'identification : %s" +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "délai d'attente invalide '%s', entier positif ou nul attendu" + +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "délai d'attente d'init invalide '%s', entier positif ou nul attendu" + +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "max-connections invalide '%s', entier attendu" + msgid "in the future" msgstr "dans le futur" @@ -16226,8 +16339,8 @@ msgstr "impossible de charger la regex île pour '%s' : %s" #, c-format msgid "island regex from config has too many capture groups (max=%d)" msgstr "" -"l'expression régulière depuis la configuration a trop de groupes de " -"capture (max=%d)" +"l'expression régulière depuis la configuration a trop de groupes de capture " +"(max=%d)" #, c-format msgid "Marked %d islands, done.\n" @@ -16750,8 +16863,7 @@ msgstr "" "ou -G" msgid "treat in -S as extended POSIX regular expression" -msgstr "" -"traiter dans -S comme une expression régulière POSIX étendue" +msgstr "traiter dans -S comme une expression régulière POSIX étendue" msgid "control the order in which files appear in the output" msgstr "contrôler l'ordre dans lequel les fichiers apparaissent dans la sortie" @@ -16895,6 +17007,20 @@ msgstr "espaces de nom de Git \"%s\"" msgid "too many args to run %s" msgstr "trop d'arguments pour lancer %s" +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"Vous tentez de récupérer %s, qui est dans le graphe de commit mais pas dans " +"la base de données des objets.\n" +"C'est probablement du à une corruption de dépôt.\n" +"Si vous essayez de réparer cette corruption de dépôt en re-récupérer l'objet " +"manquant, utilisez 'git fetch --refetch' sur les objets manquants." + msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack : liste superficielle attendue" @@ -17484,11 +17610,11 @@ msgstr[1] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" "Le crochet '%s' a été ignoré parce qu'il n'est pas marqué comme exécutable.\n" -"Vous pouvez désactiver cet avertissement avec `git config advice.ignoredHook " -"false`." +"Vous pouvez désactiver cet avertissement avec `git config set " +"advice.ignoredHook false`." msgid "not a git repository" msgstr "pas un dépôt git" @@ -17505,15 +17631,9 @@ msgstr "" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "La délégation de commande n'est pas supporté avec cuRL < 7.22.0" -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "L'épinglage de clé publique n'est pas supporté avec cuRL < 7.39.0" - msgid "Unknown value for http.proactiveauth" msgstr "valeur inconnue pour http.proactiveauth" -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "CURLSSLOPT_NO_REVOKE n'est pas supporté avec cuRL < 7.44.0" - #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" msgstr "Dorsale SSL '%s' non supportée. Dorsales SSL supportées :" @@ -17702,6 +17822,10 @@ msgstr "CRLF citées détectées" msgid "unable to format message: %s" msgstr "impossible de formater le message : %s" +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "taille de marqueur invalide '%s', entier attendu" + #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "Échec de la fusion du sous-module %s (non extrait)" @@ -18824,7 +18948,7 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "Git ne crée normalement jamais de référence qui se termine par 40\n" "caractères hexa car elle serait ignorée si vous spécifiez juste\n" @@ -18836,7 +18960,7 @@ msgstr "" "est créée.\n" "Veuillez examiner ces références et peut-être les supprimer. Désactivez ce " "message\n" -"en lançant \"git config advice.objectNameWarning false\"" +"en lançant \"git config set advice.objectNameWarning false\"" #, c-format msgid "log for '%.*s' only goes back to %s" @@ -19001,13 +19125,6 @@ msgstr "l'index inverse requis manque dans l'index multi-paquet" msgid "could not open pack %s" msgstr "impossible d'ouvrir le paquet '%s'" -msgid "could not determine MIDX preferred pack" -msgstr "impossible de déterminer le paquet préféré de MIDX" - -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "le paquet préféré (%s) est invalide" - msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "" "table de recherche en bitmap corrompue : position de triplet hors d'index" @@ -20143,17 +20260,25 @@ msgstr "le journal pour la réf %s s'arrête de manière inattendue sur %s" msgid "log for %s is empty" msgstr "le journal pour la réf %s est vide" -msgid "refusing to force and skip creation of reflog" -msgstr "refus de forcer et sauter la création du reflog" - #, c-format -msgid "refusing to update ref with bad name '%s'" -msgstr "refus de mettre à jour une réf avec un nom cassé '%s'" +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "refus de mettre à jour le réflog pour la pseudo-réf '%s'" #, c-format msgid "refusing to update pseudoref '%s'" msgstr "refus de mettre à jour la pseudo-réf '%s'" +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "refus de mettre à jour le réflog avec un nom cassé '%s'" + +#, c-format +msgid "refusing to update ref with bad name '%s'" +msgstr "refus de mettre à jour une réf avec un nom cassé '%s'" + +msgid "refusing to force and skip creation of reflog" +msgstr "refus de forcer et sauter la création du reflog" + #, c-format msgid "update_ref failed for ref '%s': %s" msgstr "échec de update_ref pour la réf '%s' : %s" @@ -20204,6 +20329,10 @@ msgstr "" "impossible de vérrouiller '%s' : symref attendu avec la cible '%s', mais réf " "normale trouvée" +#, c-format +msgid "cannot read ref file '%s'" +msgstr "impossible de lire le fichier de référence '%s'" + #, c-format msgid "cannot open directory %s" msgstr "impossible d'ouvrir le répertoire %s" @@ -20336,8 +20465,8 @@ msgstr "le serveur distant a envoyé un paquet de fin de réponse inattendu" msgid "unable to rewind rpc post data - try increasing http.postBuffer" msgstr "" -"impossible de rembobiner le données post rpc - essayer d'augmenter http." -"postBuffer" +"impossible de rembobiner le données post rpc - essayer d'augmenter " +"http.postBuffer" #, c-format msgid "remote-curl: bad line length character: %.4s" @@ -20420,6 +20549,10 @@ msgstr "plus d'un receivepack fournis, utilisation du premier" msgid "more than one uploadpack given, using the first" msgstr "plus d'un uploadpack fournis, utilisation du premier" +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "valeur '%s' de followRemoteHEAD non reconnue et ignorée " + #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" msgstr "valeur non reconnue transfer.credentialsInUrl : '%s'" @@ -22379,6 +22512,9 @@ msgstr "le commit %s n'est pas marqué joignable" msgid "too many commits marked reachable" msgstr "trop de commits marqués joignables" +msgid "could not determine MIDX preferred pack" +msgstr "impossible de déterminer le paquet préféré de MIDX" + msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 []" @@ -23049,6 +23185,10 @@ msgstr "fichier .git cassé" msgid ".git file incorrect" msgstr "fichier .git incorrect" +msgid ".git file absolute/relative path mismatch" +msgstr "" +"non-correspondance entre les chemin absolu entre relatif du fichier .git" + msgid "not a valid path" msgstr "pas un chemin valide" @@ -23065,6 +23205,9 @@ msgstr "impossible de localiser le dépôt ; fichier .git cassé" msgid "gitdir unreadable" msgstr "gitdir non lisible" +msgid "gitdir absolute/relative path mismatch" +msgstr "non-correspondance de chemin absolu/relatif de gitdir" + msgid "gitdir incorrect" msgstr "gitdir incorrect" @@ -23100,6 +23243,14 @@ msgstr "impossible de désinitialiser %s dans '%s'" msgid "failed to set extensions.worktreeConfig setting" msgstr "échec de paramétrage extensions.worktreeConfig" +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"impossible de mettre à jour le format de dépôt pour prendre en charge les " +"arbres-de-travail relatifs" + +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "échec de modification du paramètre extensions.relativeWorktrees" + #, c-format msgid "could not setenv '%s'" msgstr "impossible de configurer l'environnement '%s'" @@ -23975,3 +24126,7 @@ msgstr "%s sauté avec un suffix de sauvegarde '%s'.\n" #, perl-format msgid "Do you really want to send %s? [y|N]: " msgstr "Souhaitez-vous réellement envoyer %s ?[y|N] : " + +#, c-format +#~ msgid "preferred pack (%s) is invalid" +#~ msgstr "le paquet préféré (%s) est invalide" From 956b486cacff05ebbe580eb9d89a1c508c4fa3a3 Mon Sep 17 00:00:00 2001 From: Peter Krefting Date: Mon, 30 Dec 2024 12:04:46 +0100 Subject: [PATCH 018/306] l10n: sv.po: Update Swedish translation Signed-off-by: Peter Krefting --- po/sv.po | 275 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 212 insertions(+), 63 deletions(-) diff --git a/po/sv.po b/po/sv.po index 973dd940acceb4..e36f035bf4bf59 100644 --- a/po/sv.po +++ b/po/sv.po @@ -5,10 +5,10 @@ # msgid "" msgstr "" -"Project-Id-Version: git 2.47.0\n" +"Project-Id-Version: git 2.48.0\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-09-19 02:06+0000\n" -"PO-Revision-Date: 2024-09-28 15:45+0100\n" +"POT-Creation-Date: 2024-12-30 11:57+0100\n" +"PO-Revision-Date: 2024-12-30 12:03+0100\n" "Last-Translator: Peter Krefting \n" "Language-Team: Svenska \n" "Language: sv\n" @@ -624,10 +624,10 @@ msgstr "Endast binära filer ändrade." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"Slå av meddelandet med ”git config advice.%s false”" +"Slå av meddelandet med ”git config set advice.%s false”" #, c-format msgid "%shint:%s%.*s%s\n" @@ -1357,6 +1357,10 @@ msgstr "objektnamnet är inte giltigt: %s" msgid "not a tree object: %s" msgstr "inte ett trädobjekt: %s" +#, c-format +msgid "failed to unpack tree object %s" +msgstr "misslyckades packa upp trädobjektet %s" + #, c-format msgid "File not found: %s" msgstr "Hittar inte filen: %s" @@ -3825,9 +3829,8 @@ msgstr "ny ofödd gren" msgid "update ignored files (default)" msgstr "uppdatera ignorerade filer (standard)" -msgid "do not check if another worktree is holding the given ref" -msgstr "" -"kontrollera inte om en annan arbetskatalog håller den angivna referensen" +msgid "do not check if another worktree is using this branch" +msgstr "kontrollera inte om en annan arbetskatalog använder grenen" msgid "checkout our version for unmerged files" msgstr "checka ut vår version för ej sammanslagna filer" @@ -4125,11 +4128,11 @@ msgstr "skapa en grund klon på detta djup" msgid "create a shallow clone since a specific time" msgstr "skapa en grund klon från en angiven tidpunkt" -msgid "revision" -msgstr "revision" +msgid "ref" +msgstr "ref" -msgid "deepen history of shallow clone, excluding rev" -msgstr "fördjupa historik för grund klon, exkludera revisionen" +msgid "deepen history of shallow clone, excluding ref" +msgstr "fördjupa historik för grund klon, exkludera ref" msgid "clone only one branch, HEAD or --branch" msgstr "klona endast en gren, HEAD eller --branch" @@ -5054,10 +5057,10 @@ msgstr "" msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgid "git config rename-section [] " msgstr "git config rename-section [] " @@ -5490,12 +5493,8 @@ msgid "traversed %lu commits\n" msgstr "traverserade %lu incheckningar\n" #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"mer än %i taggar hittades; listar de %i senaste\n" -"gav upp sökningen vid %s\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "hittade %i taggar; gav upp sökning vid %s\n" #, c-format msgid "describe %s\n" @@ -5927,6 +5926,20 @@ msgstr "%s är inte ett giltigt objekt" msgid "the object %s does not exist" msgstr "objektet %s finns inte" +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Kör ”git remote set-head %s %s” för att följa ändringen, eller sätt\n" +"konfigurationsflaggan ”remote %s.followRemoteHEAD” till ett annat värde\n" +"om du inte vill se det här meddelandet. Du kan specifikt inaktivera\n" +"varningen till fjärren ändrar HEAD till något annat genom att köra\n" +"”git config set remote %s.followRemoteHEAD %s”." + msgid "multiple branches detected, incompatible with --set-upstream" msgstr "flera grenar upptäcktes, inkompatibelt med --set-upstream" @@ -6064,6 +6077,9 @@ msgstr "referenskarta" msgid "specify fetch refmap" msgstr "ange referenskarta för ”fetch”" +msgid "revision" +msgstr "revision" + msgid "report that we have only objects reachable from this object" msgstr "rapportera att vi bara har objekt nåbara från detta objektet" @@ -6789,8 +6805,25 @@ msgstr "varken systemd-timer eller crontab är tillgänglig" msgid "%s scheduler is not available" msgstr "%s-schemaläggare är inte tillgänglig" -msgid "another process is scheduling background maintenance" -msgstr "en annan process schemalägger bakgrundsunderhåll" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"Kunde inte skapa ”%s.lock”: %s.\n" +"\n" +"Det verkar som en annan schemalagd git-maintenance(1)-process kör i det\n" +"här arkivet. Se till att inga andra underhållsprocesser körs och försök\n" +"sedan igen. Om det fortfarande misslyckas kanske en git-maintenance(1)-\n" +"process har kraschat i det här arkivet tidigare: ta bort filen manuellt\n" +"för att fortsätta." + +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "kan inte erhålla låset för schemalagt bakgrundsunderhåll" msgid "git maintenance start [--scheduler=]" msgstr "git maintenance start [--scheduler=]" @@ -7320,7 +7353,7 @@ msgstr "paketfilnamnet ”%s” slutar inte med ”.%s”" #, c-format msgid "cannot write %s file '%s'" -msgstr "kan inte ta skriva %s-fil ”%s”" +msgstr "kan inte skriva %s-fil ”%s”" #, c-format msgid "cannot close written %s file '%s'" @@ -7357,6 +7390,19 @@ msgid_plural "chain length = %d: %lu objects" msgstr[0] "kedjelängd = %d: %lu objekt" msgstr[1] "kedjelängd = %d: %lu objekt" +msgid "could not start pack-objects to repack local links" +msgstr "kunde inte starta pack-objects för att packa om lokala länkar" + +msgid "failed to feed local object to pack-objects" +msgstr "misslyckades sända lokala objekt till pack-objects" + +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack: Förväntar kompletta hex-objekt-ID-rader endast från pack-objects." + +msgid "could not finish pack-objects to repack local links" +msgstr "kunde inte avsluta pack-objects för att packa om lokala länkar" + msgid "Cannot come back to cwd" msgstr "Kan inte gå tillbaka till arbetskatalogen (cwd)" @@ -7368,6 +7414,9 @@ msgstr "felaktig %s" msgid "unknown hash algorithm '%s'" msgstr "okänd hashningsalgoritm ”%s”" +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor kan inte användas med ett paketnamn" + msgid "--stdin requires a git repository" msgstr "--stdin kräver ett git-arkiv" @@ -8708,11 +8757,11 @@ msgstr "git notes [--ref ] [list []]" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] add [-f] [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace] [-m " -"| -F | (-c | -C) ] []" +"| -F | (-c | -C) ] [] [-e]" msgid "git notes [--ref ] copy [-f] " msgstr "" @@ -8721,11 +8770,11 @@ msgstr "" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] append [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace] [-m " -"| -F | (-c | -C) ] []" +"| -F | (-c | -C) ] [] [-e]" msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref ] edit [--allow-empty] []" @@ -8844,6 +8893,9 @@ msgstr "anteckningsinnehåll i en fil" msgid "reuse and edit specified note object" msgstr "återanvänd och redigera angivet anteckningsobjekt" +msgid "edit note message in editor" +msgstr "redigera anteckning i textredigeringsprogram" + msgid "reuse specified note object" msgstr "återanvänd angivet anteckningsobjekt" @@ -9336,6 +9388,9 @@ msgstr "hantering av saknade objekt" msgid "do not pack objects in promisor packfiles" msgstr "packa inte objekt i kontraktspackfiler" +msgid "implies --missing=allow-any" +msgstr "implicerar --missing=allow-any" + msgid "respect islands during delta compression" msgstr "respektera öar under deltakomprimering" @@ -10919,6 +10974,30 @@ msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " Lokal referens konfigurerad för ”git push”%s:" msgstr[1] " Lokala referenser konfigurerade för ”git push”%s:" +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "”%s/HEAD” är oförändrad och pekar på ”%s”\n" + +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "”%s/HEAD” har ändrats från ”%s” och pekar nu på ”%s”\n" + +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "”%s/HEAD” har nu skapats och pekar på ”%s”\n" + +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "”%s/HEAD” kopplades från vid ”%s” och pekar nu på ”%s”\n" + +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"”%s/HEAD” pekade tidigare på ”%s” (som inte är en fjärrgren), men pekar nu " +"på ”%s”\n" + msgid "set refs/remotes//HEAD according to remote" msgstr "sätt refs/remotes//HEAD enligt fjärren" @@ -10940,7 +11019,7 @@ msgid "Not a valid ref: %s" msgstr "Inte en giltig referens: %s" #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "Kunde inte ställa in %s" #, c-format @@ -13641,6 +13720,9 @@ msgstr "ställ in spårningsläge (se git-branch(1))" msgid "try to match the new branch name with a remote-tracking branch" msgstr "försök träffa namn på ny gren mot en fjärrspårande gren" +msgid "use relative paths for worktrees" +msgstr "använd relativa sökvägar för arbetskataloger" + #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" msgstr "flaggorna ”%s”, ”%s” och ”%s” kan inte användas samtidigt" @@ -13912,6 +13994,26 @@ msgstr "kan inte skapa ”%s”" msgid "index-pack died" msgstr "index-pack dog" +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "katalogen ”%s” finns i indexet, men inte glest" + +msgid "corrupted cache-tree has entries not present in index" +msgstr "trasigt cacheträd innehåller poster som inte finns i indexet" + +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "%s med flaggorna 0x%x borde inte finnas i cacheträdet" + +#, c-format +msgid "bad subtree '%.*s'" +msgstr "felaktigt underträd ”%.*s”" + +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "" +"cacheträd för sökvägen %.*s stämmer inte överens. Förväntade %s fick %s" + msgid "terminating chunk id appears earlier than expected" msgstr "avslutande stycke-id förekommer tidigare än förväntat" @@ -14777,7 +14879,7 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "Stöd för /info/grafts avråds från och\n" "kommer tas bort i en framtida version av Git.\n" @@ -14786,7 +14888,7 @@ msgstr "" "för att omvandla grafts till ersättningsreferenser.\n" "\n" "Slå av detta meddelande genom att skriva\n" -"”git config advice.graftFileDeprecated false”" +"”git config set advice.graftFileDeprecated false”" #, c-format msgid "commit %s exists in commit-graph but not in the object database" @@ -15597,6 +15699,19 @@ msgstr "url saknar protokoll: %s" msgid "credential url cannot be parsed: %s" msgstr "kan inte tolka url för inloggingsuppgifter: %s" +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "felaktig tidsgräns ”%s”, förväntade ett icke-negativt heltal" + +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "" +"felaktig värde för init-timeout ”%s”, förväntade ett icke-negativt heltal" + +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "felaktigt värde för max-connections ”%s”, förväntade ett heltal" + msgid "in the future" msgstr "i framtiden" @@ -16296,6 +16411,20 @@ msgstr "felaktig git-namnrymdssökväg ”%s”" msgid "too many args to run %s" msgstr "för många flaggor för att köra %s" +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"Du försöker hämta %s som är i incheckningsgrafen men inte i " +"objektdatabasen.\n" +"Det händer antagligen på grund av att arkivet är trasigt.\n" +"Om du försöker reparera det trasiga arkivet genom att hämta om det saknade " +"objektet, använd ”git fetch --refetch” med det saknade objektet." + msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack: förväntade grund lista" @@ -16880,10 +17009,10 @@ msgstr[1] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" "Kroken ”%s” ignorerades eftersom den inte är markerad som körbar.\n" -"Du kan inaktivera varningen med ”git config advice.ignoredHook false”." +"Du kan inaktivera varningen med ”git config set advice.ignoredHook false”." msgid "not a git repository" msgstr "inte ett git-arkiv" @@ -16900,15 +17029,9 @@ msgstr "http.postBuffer har negativt värde; använder förvalet %d" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "Delegerad styrning stöds inte av cURL < 7.22.0" -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "Fastnålning av öppen nyckel stöds inte av cURL < 7.39.0" - msgid "Unknown value for http.proactiveauth" msgstr "Okänt värde för http.proactiveauth" -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "CURLSSLOPT_NO_REVOKE stöds inte av cURL < 7.44.0" - #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" msgstr "SSL-bakändan ”%s” stöds inte. Dessa SSL-bakändor stöds:" @@ -17091,6 +17214,10 @@ msgstr "citerad CRLF upptäcktes" msgid "unable to format message: %s" msgstr "kan inte formatera meddelandet: %s" +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "felaktigt värde för marker-size ”%s”, förväntade ett heltal" + #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "Misslyckades slå ihop undermodulen %s (ej utcheckad)" @@ -17995,6 +18122,14 @@ msgstr "packat objekt %s (lagrat i %s) är trasigt" msgid "missing mapping of %s to %s" msgstr "saknar koppling av %s till %s" +#, c-format +msgid "unable to open %s" +msgstr "kan inte öppna %s" + +#, c-format +msgid "files '%s' and '%s' differ in contents" +msgstr "filerna ”%s” och ”%s” har olika innehåll" + #, c-format msgid "unable to write file %s" msgstr "kan inte skriva filen %s" @@ -18080,10 +18215,6 @@ msgstr "%s: filtypen stöds ej" msgid "%s is not a valid '%s' object" msgstr "%s är inte ett giltigt ”%s”-objekt" -#, c-format -msgid "unable to open %s" -msgstr "kan inte öppna %s" - #, c-format msgid "hash mismatch for %s (expected %s)" msgstr "hash stämmer inte för %s (förväntade %s)" @@ -18185,7 +18316,7 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "Git skapar normalt aldrig referenser som slutar med 40 hexadecimala\n" "tecken, då detta ignoreras när du anger 40-hex enbart. Dessa\n" @@ -18195,7 +18326,7 @@ msgstr "" "\n" "där ”$br” på något sätt blivit tomt och en 40-hex-referens skapats.\n" "Undersök referenserna och ta kanske bort dem. Stäng av meddelandet\n" -"genom att köra ”git config advice.objectNameWarning false”" +"genom att köra ”git config set advice.objectNameWarning false”" #, c-format msgid "log for '%.*s' only goes back to %s" @@ -18354,13 +18485,6 @@ msgstr "flerpaketsbitkarta saknar nödvändigt omvänt index" msgid "could not open pack %s" msgstr "kunde inte öppna paketfilen %s" -msgid "could not determine MIDX preferred pack" -msgstr "kunde inte bestämma det föredragna MIDX-paketet" - -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "föredragen paketfil (%s) är ogiltig" - msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "trasig bitkarteuppslagstabell: trippelposition utanför indexet" @@ -19475,17 +19599,25 @@ msgstr "loggen för referensen %s slutade oväntat på %s" msgid "log for %s is empty" msgstr "loggen för %s är tom" -msgid "refusing to force and skip creation of reflog" -msgstr "vägrar att tvinga och hoppa över skapande av reflogg" - #, c-format -msgid "refusing to update ref with bad name '%s'" -msgstr "vägrar uppdatera referens med trasigt namn ”%s”" +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "vägrar uppdatera referenslogg för pseudoreferensen ”%s”" #, c-format msgid "refusing to update pseudoref '%s'" msgstr "vägrar uppdatera pseudoreferensen ”%s”" +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "vägrar uppdatera referenslogg med trasigt namn ”%s”" + +#, c-format +msgid "refusing to update ref with bad name '%s'" +msgstr "vägrar uppdatera referens med trasigt namn ”%s”" + +msgid "refusing to force and skip creation of reflog" +msgstr "vägrar att tvinga och hoppa över skapande av reflogg" + #, c-format msgid "update_ref failed for ref '%s': %s" msgstr "update_ref misslyckades för referensen ”%s”: %s" @@ -19535,6 +19667,10 @@ msgstr "" "kan inte läsa referensen ”%s”: förväntade symbolisk referens med målet ”%s”: " "men är en vanlig referens" +#, c-format +msgid "cannot read ref file '%s'" +msgstr "kan inte läsa ref-fil ”%s”" + #, c-format msgid "cannot open directory %s" msgstr "kunde inte öppna katalogen %s" @@ -19740,6 +19876,10 @@ msgstr "mer än en receivepack angavs, använder den första" msgid "more than one uploadpack given, using the first" msgstr "mer än en uploadpack angavs, använder den första" +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "okänt värde ”%s” för followRemoteHEAD ignorerades" + #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" msgstr "okänt värde transfer.credentialsInUrl: ”%s”" @@ -21665,6 +21805,9 @@ msgstr "incheckning %s är inte märkt nåbar" msgid "too many commits marked reachable" msgstr "för många incheckningar markerade nåbara" +msgid "could not determine MIDX preferred pack" +msgstr "kunde inte bestämma det föredragna MIDX-paketet" + msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 []" @@ -22319,6 +22462,9 @@ msgstr ".git-filen är trasig" msgid ".git file incorrect" msgstr ".git-filen är felaktig" +msgid ".git file absolute/relative path mismatch" +msgstr "absolut/relativ sökväg för .git-fil stämmer inte överens" + msgid "not a valid path" msgstr "inte en giltig sökväg" @@ -22334,6 +22480,9 @@ msgstr "kan inte hitta arkivet; ”.git”-filen är trasig" msgid "gitdir unreadable" msgstr "gitdir är oläsbar" +msgid "gitdir absolute/relative path mismatch" +msgstr "absolut/relativ sökväg för git-katalog stämmer inte överens" + msgid "gitdir incorrect" msgstr "gitdir är felaktig" @@ -22368,6 +22517,13 @@ msgstr "kan inte slå av %s i ”%s”" msgid "failed to set extensions.worktreeConfig setting" msgstr "misslyckades ändra inställningen extensions.worktreeConfig" +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"kunde inte uppgradera arkivformat till att stöda relativa arbetskataloger" + +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "misslyckades ändra inställningen extensions.relativeWorktrees" + #, c-format msgid "could not setenv '%s'" msgstr "kunde inte lagra miljövariabeln ”%s”" @@ -23217,10 +23373,3 @@ msgstr "" #, perl-format msgid "Do you really want to send %s? [y|N]: " msgstr "Vill du verkligen sända %s? [y=ja, n=nej]: " - -#~ msgid "revision walk setup failed\n" -#~ msgstr "misslyckades starta revisionstraversering\n" - -#, c-format -#~ msgid "unable to parse contact: %s" -#~ msgstr "kan inte tolka kontakt: %s" From 1a18bf3a5b251557a7b10a496f9036f00790086a Mon Sep 17 00:00:00 2001 From: Emir SARI Date: Wed, 18 Dec 2024 19:10:42 +0300 Subject: [PATCH 019/306] l10n: tr: Update Turkish translations for 2.48 Signed-off-by: Emir SARI --- po/tr.po | 293 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 225 insertions(+), 68 deletions(-) diff --git a/po/tr.po b/po/tr.po index 7aede5cd5fb7dd..1bc43453aa7e21 100644 --- a/po/tr.po +++ b/po/tr.po @@ -2,7 +2,7 @@ # Git Türkçe çevirileri # Copyright (C) 2020-2024 Emir SARI # This file is distributed under the same license as the Git package. -# Emir SARI , 2020-2024 +# Emir SARI , 2020-2025 # # ######################################################### # # Git Türkçe kavramlar dizini / Git Turkish Glossary # @@ -96,8 +96,8 @@ msgid "" msgstr "" "Project-Id-Version: Git Turkish Localization Project\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-03 06:52+0300\n" -"PO-Revision-Date: 2024-10-03 07:00+0300\n" +"POT-Creation-Date: 2024-12-23 18:57+0000\n" +"PO-Revision-Date: 2025-01-01 15:00+0300\n" "Last-Translator: Emir SARI \n" "Language-Team: Turkish (https://github.com/bitigchi/git-po/)\n" "Language: tr\n" @@ -714,10 +714,10 @@ msgstr "Yalnızca ikili dosyalar değiştirildi." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"Bu iletiyi \"git config advice.%s false\" ile devre dışı bırakın" +"Bu iletiyi \"git config set advice.%s false\" ile devre dışı bırakın" #, c-format msgid "%shint:%s%.*s%s\n" @@ -1441,8 +1441,9 @@ msgstr "geçerli bir nesne adı değil: %s" msgid "not a tree object: %s" msgstr "bir ağaç nesnesi değil: %s" -msgid "unable to checkout working tree" -msgstr "çalışma ağacı çıkış yapılamıyor" +#, c-format +msgid "failed to unpack tree object %s" +msgstr "%s ağaç nesnesi açılamadı" #, c-format msgid "File not found: %s" @@ -3072,8 +3073,8 @@ msgid "HEAD not found below refs/heads!" msgstr "HEAD, refs/heads altında bulunamadı!" msgid "" -"branch with --recurse-submodules can only be used if submodule." -"propagateBranches is enabled" +"branch with --recurse-submodules can only be used if " +"submodule.propagateBranches is enabled" msgstr "" "--recurse-submodules ile dallanma, yalnızca submodule.propagateBranches " "etkinleştirilmişse kullanılabilir" @@ -3921,9 +3922,8 @@ msgstr "yeni henüz doğmamış dal" msgid "update ignored files (default)" msgstr "yok sayılan dosyaları güncelle (öntanımlı)" -msgid "do not check if another worktree is holding the given ref" -msgstr "" -"verilen başvuruyu başka bir çalışma ağacının tutup tutmadığını denetleme" +msgid "do not check if another worktree is using this branch" +msgstr "bu dalı başka bir çalışma ağacının kullanıp kullanmadığını denetleme" msgid "checkout our version for unmerged files" msgstr "birleştirilmeyen dosyalar için bizdeki sürümü çıkış yap" @@ -4224,11 +4224,11 @@ msgstr "verilen derinlikte sığ bir depo oluştur" msgid "create a shallow clone since a specific time" msgstr "verilen zamandan sonrasını içeren bir sığ depo oluştur" -msgid "revision" -msgstr "revizyon" +msgid "ref" +msgstr "başvuru" -msgid "deepen history of shallow clone, excluding rev" -msgstr "revizyonu hariç tutarak sığ klonun geçmişini derinleştir" +msgid "deepen history of shallow clone, excluding ref" +msgstr "başvuru hariç tutarak sığ klonun geçmişini derinleştir" msgid "clone only one branch, HEAD or --branch" msgstr "yalnızca bir dal klonla, HEAD veya --branch" @@ -4355,6 +4355,9 @@ msgstr "sparse-checkout ilklendirilemedi" msgid "remote HEAD refers to nonexistent ref, unable to checkout" msgstr "uzak konum HEAD'i, var olmayan başvuruya başvuruyor; çıkış yapılamıyor" +msgid "unable to checkout working tree" +msgstr "çalışma ağacı çıkış yapılamıyor" + msgid "unable to write parameters to config file" msgstr "parametreler yapılandırma dosyasına yazılamıyor" @@ -5163,10 +5166,10 @@ msgstr "" msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" "git config unset [] [--all] [--value=] [--fixed-" -"value] " +"value] " msgid "git config rename-section [] " msgstr "git config rename-section [] " @@ -5601,12 +5604,8 @@ msgid "traversed %lu commits\n" msgstr "%lu işleme katedildi\n" #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"%i etiketten fazla etiket bulundu; en son %i listelendi\n" -"şu konumda arama bırakıldı: %s\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "%i etiket bulundu; arama şurada bitirildi: %s\n" #, c-format msgid "describe %s\n" @@ -6040,6 +6039,21 @@ msgstr "%s geçerli bir nesne değil" msgid "the object %s does not exist" msgstr "%s diye bir nesne yok" +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Değişikliği izlemek için 'git remote set-head %s %s' yapın veya\n" +"'remote.%s.followRemoteHEAD' yapılandırma seçeneğini başka bir\n" +"değere ayarlayın (bu iletiyi görmek istemiyorsanız). Özellikle\n" +"'git config set remote.%s.followRemoteHEAD %s' komutunu çalıştırmak\n" +"uyarıyı HEAD'e veya başka bir şeye uzaktan değişiklik olana dek\n" +"devre dışı bırakır." + msgid "multiple branches detected, incompatible with --set-upstream" msgstr "birden çok dal algılandı, --set-upstream ile uyumsuz" @@ -6178,6 +6192,9 @@ msgstr "ilgili başvuru" msgid "specify fetch refmap" msgstr "getirme ile ilgili başvuruları belirt" +msgid "revision" +msgstr "revizyon" + msgid "report that we have only objects reachable from this object" msgstr "yalnızca bu nesneden ulaşılabilir nesnelerimiz olduğunu bildir" @@ -6232,8 +6249,8 @@ msgid "protocol does not support --negotiate-only, exiting" msgstr "protokol, --negotiate-only desteklemediğinden çıkılıyor" msgid "" -"--filter can only be used with the remote configured in extensions." -"partialclone" +"--filter can only be used with the remote configured in " +"extensions.partialclone" msgstr "" "--filter, yalnızca extensions.partialclone içinde yapılandırılmış uzak konum " "ile kullanılabilir" @@ -6898,8 +6915,27 @@ msgstr "ne systemd zamanlayıcıları ne de crontab kullanılabiliyor" msgid "%s scheduler is not available" msgstr "%s planlayıcısı kullanılamıyor" -msgid "another process is scheduling background maintenance" -msgstr "başka bir işlem arka plan bakımı zamanını planlıyor" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"'%s.lock' oluşturulamıyor: %s.\n" +"\n" +"Bu depoda başka bir planlı git-maintenance(1) süreci çalışıyor gibi\n" +"görünüyor. Lütfen başka bir bakım sürecinin çalışmıyor olduğundan emin\n" +"olun ve yeniden deneyin. Eğer hâlâ başarısız oluyorsa bir git-" +"maintenance(1)\n" +"süreci bu depo içinde daha önceden çakılmış olabilir: Sürdürmek için " +"dosyayı\n" +"elle kaldırın." + +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "planlı arka plan bakımı için kilit alınamıyor" msgid "git maintenance start [--scheduler=]" msgstr "git maintenance start [--scheduler=]" @@ -7472,8 +7508,22 @@ msgid_plural "chain length = %d: %lu objects" msgstr[0] "zincir uzunluğu = %d: %lu nesne" msgstr[1] "zincir uzunluğu = %d: %lu nesne" +msgid "could not start pack-objects to repack local links" +msgstr "yerel bağlantıları yeniden paketleme için pack-objects başlatılamadı" + +msgid "failed to feed local object to pack-objects" +msgstr "yerel nesne pack-objects'e beslenemedi" + +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack: Onaltılı tam nesne kimliği satırları yalnızca pack-objects'ten " +"bekleniyor." + +msgid "could not finish pack-objects to repack local links" +msgstr "yerel bağlantıları yeniden paketleme için pack-objects bitirilemedi" + msgid "Cannot come back to cwd" -msgstr "Şu anki çalışma dizinine geri gelinemiyor" +msgstr "Geçerli çalışma dizinine geri gelinemiyor" #, c-format msgid "bad %s" @@ -7483,6 +7533,9 @@ msgstr "hatalı %s" msgid "unknown hash algorithm '%s'" msgstr "bilinmeyen sağlama algoritması '%s'" +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor bir paket adıyla kullanılamaz" + msgid "--stdin requires a git repository" msgstr "--stdin bir git dizini gerektirir" @@ -8826,11 +8879,11 @@ msgstr "git notes [--ref ] [list []]" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" -"git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" -"separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" +"separator=] [--[no-]stripspace] [-m | -F | (-c " +"| -C) ] [] [-e]" msgid "git notes [--ref ] copy [-f] " msgstr "git notes [--ref ] copy [-f] " @@ -8838,11 +8891,11 @@ msgstr "git notes [--ref ] copy [-f] " msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" -"git notes [--ref ] append [--allow-empty] [--[no-]separator|--" -"separator=] [--[no-]stripspace] [-m | -F | (-" -"c | -C) ] []" +"git notes [--ref ] append [--allow-empty] [--[no-]separator|--" +"separator=] [--[no-]stripspace] [-m | -F | (-c " +"| -C) ] [] [-e]" msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref ] edit [--allow-empty] []" @@ -8960,6 +9013,9 @@ msgstr "not içeriği bir dosyada" msgid "reuse and edit specified note object" msgstr "belirtilen not nesnesini düzenle ve yeniden kullan" +msgid "edit note message in editor" +msgstr "not iletisini düzenleyicide aç" + msgid "reuse specified note object" msgstr "belirtilen not nesnesini yeniden kullan" @@ -9456,6 +9512,9 @@ msgstr "eksik nesneler için işlem" msgid "do not pack objects in promisor packfiles" msgstr "nesneleri vaatçi paket dosyalarıyla paketleme" +msgid "implies --missing=allow-any" +msgstr "--missing=allow-any ima eder" + msgid "respect islands during delta compression" msgstr "delta sıkıştırması sırasında adalara uy" @@ -11057,6 +11116,31 @@ msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " 'git push'%s için yapılandırılan yerel başvuru:" msgstr[1] " 'git push'%s için yapılandırılan yerel başvurular:" +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "'%s/HEAD' değiştirilmedi ve '%s' konumuna işaret ediyor\n" + +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "'%s/HEAD', '%s' konumundan '%s' konumuna değiştirildi\n" + +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "'%s/HEAD' şimdi oluşturuldu ve '%s' konumuna işaret ediyor\n" + +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "" +"'%s/HEAD', '%s' konumunda ayrıldı ve artık '%s' konumuna işaret ediyor\n" + +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"'%s/HEAD', uzak dal olmayan '%s' konumuna işaret ediyordu; ancak şimdi '%s' " +"konumuna işaret ediyor\n" + msgid "set refs/remotes//HEAD according to remote" msgstr "refs/remotes//HEAD'i uzak konuma göre ayarla" @@ -11078,7 +11162,7 @@ msgid "Not a valid ref: %s" msgstr "Geçerli bir başvuru değil: %s" #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "%s ayarlanamadı" #, c-format @@ -13779,6 +13863,9 @@ msgstr "izleme kipini ayarla (bkz: git-branch(1))" msgid "try to match the new branch name with a remote-tracking branch" msgstr "yeni dalın adını bir uzak izleme dalıyla eşleştirmeyi dene" +msgid "use relative paths for worktrees" +msgstr "çalışma ağaçları için göreceli yollar kullan" + #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" msgstr "'%s', '%s' ve '%s' seçenekleri birlikte kullanılamaz" @@ -14050,6 +14137,25 @@ msgstr "'%s' oluşturulamıyor" msgid "index-pack died" msgstr "index-pack sonlandı" +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "'%s' dizini indekste var; ancak aralıklı değil" + +msgid "corrupted cache-tree has entries not present in index" +msgstr "hasarlı cache-tree'de indekste olmayan girdiler var" + +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "%s, 0x%x bayraklı olarak cache-tree'de olmamalı" + +#, c-format +msgid "bad subtree '%.*s'" +msgstr "hatalı alt ağaç '%.*s'" + +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "%.*s yolu için olan cache-tree eşleşmiyor. %s bekleniyordu, %s alındı" + msgid "terminating chunk id appears earlier than expected" msgstr "iri parça numarası sonlandırması beklenenden önce ortaya çıkıyor" @@ -14829,8 +14935,8 @@ msgid "" "attempting to write a commit-graph, but 'commitGraph.changedPathsVersion' " "(%d) is not supported" msgstr "" -"bir commit-graph yazılmaya çalışılıyor; ancak 'commitGraph." -"changedPathsVersion' (%d) desteklenmiyor" +"bir commit-graph yazılmaya çalışılıyor; ancak " +"'commitGraph.changedPathsVersion' (%d) desteklenmiyor" msgid "too many commits to write graph" msgstr "grafik yazımı için pek fazla işleme" @@ -14909,7 +15015,7 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "/info/grafts desteği artık kullanılmamalı\n" "ve ileriki bir Git sürümünde kaldırılacak.\n" @@ -14917,7 +15023,7 @@ msgstr "" "Aşıları değiştirme başvurularına dönüştürmek için\n" "lütfen \"git replace --convert-graft-file\" kullanın.\n" "\n" -"\"git config advice.graftFileDeprecated false\"\n" +"\"git config set advice.graftFileDeprecated false\"\n" "kullanarak bu iletiyi kapatabilirsiniz" #, c-format @@ -15176,8 +15282,8 @@ msgid "" "remote URLs cannot be configured in file directly or indirectly included by " "includeIf.hasconfig:remote.*.url" msgstr "" -"uzak URL'ler dosya içinde doğrudan veya başka türlü includeIf.hasconfig:" -"remote.*.url kullanarak yapılandırılamaz" +"uzak URL'ler dosya içinde doğrudan veya başka türlü " +"includeIf.hasconfig:remote.*.url kullanarak yapılandırılamaz" #, c-format msgid "invalid config format: %s" @@ -15733,6 +15839,19 @@ msgstr "url'nin şeması yok: %s" msgid "credential url cannot be parsed: %s" msgstr "yetki url'si ayrıştırılamıyor: %s" +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "geçersiz zaman aşımı '%s', negatif olmayan bir tamsayı bekleniyor" + +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "" +"geçersiz ilklendirme zaman aşımı '%s', negatif olmayan bir tamsayı bekleniyor" + +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "geçersiz en çok bağlantı '%s', bir tamsayı bekleniyor" + msgid "in the future" msgstr "gelecekte" @@ -16439,6 +16558,20 @@ msgstr "hatalı git ad alanı yolu \"%s\"" msgid "too many args to run %s" msgstr "%s çalıştırmak için pek fazla argüman" +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"İşleme grafiğinde bulunan; ancak nesne veritabanında bulunmayan %s ögesini " +"getirmeyi deniyorsunuz.\n" +"Bu, büyük olasılıkla depo hasarından dolayı oluyor.\n" +"Bu depo hasarını eksik nesneyi yeniden getirerek onarmayı düşünüyorsanız " +"eksik nesneyle birlikte 'git fetch --refetch' kullanmayı deneyin." + msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack: sığ bir liste bekleniyordu" @@ -17024,10 +17157,10 @@ msgstr[1] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" "'%s' kancası yok sayıldı; çünkü bir yürütülebilir olarak ayarlanmamış.\n" -"Bu uyarıyı 'git config advice.ignoredHook false' ile kapatabilirsiniz." +"Bu uyarıyı 'git config set advice.ignoredHook false' ile kapatabilirsiniz." msgid "not a git repository" msgstr "bir git deposu değil" @@ -17043,15 +17176,9 @@ msgstr "http.postBuffer için negatif değer; %d olarak varsayılıyor" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "Delegasyon denetimi cURL < 7.22.0 tarafından desteklenmiyor" -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "Ortak anahtar iğnelemesi cURL < 7.39.0 tarafından desteklenmiyor" - msgid "Unknown value for http.proactiveauth" msgstr "http.proactiveauth için bilinmeyen değer" -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "CURLSSLOPT_NO_REVOKE cURL < 7.44.0 tarafından desteklenmiyor" - #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" msgstr "Desteklenmeyen SSL arka ucu '%s'. Desteklenen SSL arka uçları:" @@ -17237,6 +17364,10 @@ msgstr "alıntılanmış CRLF algılandı" msgid "unable to format message: %s" msgstr "ileti biçimlendirilemiyor: %s" +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "geçersiz imleyici boyutu '%s', bir tamsayı bekleniyor" + #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "%s altmodülü birleştirilemedi (çıkış yapılmadı)" @@ -18339,7 +18470,7 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "Git normalde hiçbir zaman 40 onaltılı karakterlerle biten bir başvuru\n" "oluşturmaz; çünkü 40 onaltılı bir başvuru, onu belirlediğiniz zaman yok\n" @@ -18350,7 +18481,7 @@ msgstr "" "komutunda \"$br\" bir şekilde boş kalmış ve 40 onaltılı bir başvuru\n" "oluşturulmuş. Lütfen bu başvuruları inceleyin ve gerekirse silin. Bu " "iletiyi\n" -"kapatmak için \"git config advice.objectNameWarning\" yapın" +"kapatmak için \"git config set advice.objectNameWarning\" yapın" #, c-format msgid "log for '%.*s' only goes back to %s" @@ -18511,13 +18642,6 @@ msgstr "çoklu paket biteşlemi gereken ters indeksi içermiyor" msgid "could not open pack %s" msgstr "%s paketi açılamadı" -msgid "could not determine MIDX preferred pack" -msgstr "MIDX yeğlenen paketi algılanamadı" - -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "yeğlenen paket (%s) geçersiz" - msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "hasarlı biteşlem arama tablosu: üçlü konum indeks dışında" @@ -19343,8 +19467,8 @@ msgid "" msgstr "" "Bu iletiden kaçınmak için, bir işlemeyi kaldırırken açıkça \"drop\" " "kullanın.\n" -"Uyarıların düzeyini değiştirmek için 'git config rebase." -"missingCommitsCheck'\n" +"Uyarıların düzeyini değiştirmek için 'git config " +"rebase.missingCommitsCheck'\n" "kullanın. Kullanılabilir davranışlar: ignore, warn, error.\n" "\n" @@ -19626,17 +19750,26 @@ msgstr "" msgid "log for %s is empty" msgstr "%s için olan günlük boş" -msgid "refusing to force and skip creation of reflog" -msgstr "başvuru günlüğünün oluşturulma/atlanma zorlanması reddediliyor" - #, c-format -msgid "refusing to update ref with bad name '%s'" -msgstr "hatalı ada iye '%s' başvurusunu güncelleme reddediliyor" +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "" +"'%s' yalancı başvurusu için olan başvuru günlüğünü güncelleme reddediliyor" #, c-format msgid "refusing to update pseudoref '%s'" msgstr "'%s' yalancı başvurusunun güncellenmesi reddediliyor" +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "hatalı adlı '%s' başvuru günlüğünü güncelleme reddediliyor" + +#, c-format +msgid "refusing to update ref with bad name '%s'" +msgstr "hatalı ada iye '%s' başvurusunu güncelleme reddediliyor" + +msgid "refusing to force and skip creation of reflog" +msgstr "başvuru günlüğünün oluşturulma/atlanma zorlanması reddediliyor" + #, c-format msgid "update_ref failed for ref '%s': %s" msgstr "'%s' başvurusu için update_ref başarısız oldu: %s" @@ -19686,6 +19819,10 @@ msgstr "" "'%s' başvurusu kilitlenemiyor: '%s' hedefiyle bir sembolik başvuru " "bekleniyordu; ancak bu normal bir başvuru" +#, c-format +msgid "cannot read ref file '%s'" +msgstr "başvuru dosyası '%s' yazılamıyor" + #, c-format msgid "cannot open directory %s" msgstr "%s dizini açılamıyor" @@ -19893,6 +20030,10 @@ msgstr "birden fazla receivepack verildi, birincisi kullanılıyor" msgid "more than one uploadpack given, using the first" msgstr "birden fazla uploadpack verildi, birincisi kullanılıyor" +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "tanımlanamayan followRemoteHEAD değeri '%s' yok sayıldı" + #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" msgstr "tanımlanamayan değer transfer.credentialsInUrl: '%s'" @@ -21814,6 +21955,9 @@ msgstr "%s işlemesi ulaşılabilir olarak imlenmedi" msgid "too many commits marked reachable" msgstr "pek fazla işleme ulaşılabilir olarak imlenmiş" +msgid "could not determine MIDX preferred pack" +msgstr "MIDX yeğlenen paketi algılanamadı" + msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 []" @@ -22462,6 +22606,9 @@ msgstr ".git dosyası bozuk" msgid ".git file incorrect" msgstr ".git dosyası doğru değil" +msgid ".git file absolute/relative path mismatch" +msgstr ".git dosyası mutlak/göreceli yolu uyumsuzluğu" + msgid "not a valid path" msgstr "geçerli bir yol değil" @@ -22477,6 +22624,9 @@ msgstr "depo konumu bulunamıyor: .git dosyası bozuk" msgid "gitdir unreadable" msgstr "okunamayan gitdir" +msgid "gitdir absolute/relative path mismatch" +msgstr "gitdir mutlak/göreceli yolu uyumsuzluğu" + msgid "gitdir incorrect" msgstr "doğru olmayan gitdir" @@ -22511,6 +22661,13 @@ msgstr "%s, '%s' içinde ayarı kaldırılamıyor" msgid "failed to set extensions.worktreeConfig setting" msgstr "extensions.worktreeConfig yapılandırması ayarlanamadı" +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"depo biçimi göreceli çalışma ağaçlarını desteklemesi için yükseltilemiyor" + +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "extensions.relativeWorktrees yapılandırması ayarlanamadı" + #, c-format msgid "could not setenv '%s'" msgstr "setenv '%s' yapılamadı" From b1dbc87686db1da0d9ee8ece2e6faa78354f2970 Mon Sep 17 00:00:00 2001 From: Ralf Thielow Date: Fri, 20 Dec 2024 17:45:01 +0100 Subject: [PATCH 020/306] l10n: Update German translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Matthias Rüster Signed-off-by: Ralf Thielow --- po/de.po | 304 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 230 insertions(+), 74 deletions(-) diff --git a/po/de.po b/po/de.po index 06055e7611c704..addd5919bdb015 100644 --- a/po/de.po +++ b/po/de.po @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-05 16:17+0200\n" -"PO-Revision-Date: 2024-10-05 16:18+0200\n" +"POT-Creation-Date: 2024-12-20 17:44+0100\n" +"PO-Revision-Date: 2024-12-27 16:43+0100\n" "Last-Translator: Ralf Thielow \n" "Language-Team: German\n" "Language: de\n" @@ -366,11 +366,11 @@ msgstr "" #, c-format msgid "Discard mode change from index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Modusänderung vom Index und Arbeitsverzeichnis verwerfen [y,n,q,a,d%s,?]? " +"Modusänderung im Index und Arbeitsverzeichnis verwerfen [y,n,q,a,d%s,?]? " #, c-format msgid "Discard deletion from index and worktree [y,n,q,a,d%s,?]? " -msgstr "Löschung vom Index und Arbeitsverzeichnis verwerfen [y,n,q,a,d%s,?]? " +msgstr "Löschung im Index und Arbeitsverzeichnis verwerfen [y,n,q,a,d%s,?]? " #, c-format msgid "Discard addition from index and worktree [y,n,q,a,d%s,?]? " @@ -379,7 +379,7 @@ msgstr "Ergänzung im Index und Arbeitsverzeichnis verwerfen [y,n,q,a,d%s,?]? " #, c-format msgid "Discard this hunk from index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Diesen Patch-Block vom Index und Arbeitsverzeichnis verwerfen [y,n,q,a," +"Diesen Patch-Block im Index und Arbeitsverzeichnis verwerfen [y,n,q,a," "d%s,?]? " msgid "" @@ -650,10 +650,10 @@ msgstr "Nur Binärdateien geändert." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"Deaktivieren Sie diese Nachricht mit \"git config advice.%s false\"" +"Deaktivieren Sie diese Meldung mit \"git config set advice.%s false\"" #, c-format msgid "%shint:%s%.*s%s\n" @@ -3962,10 +3962,9 @@ msgstr "neuer ungeborener Branch" msgid "update ignored files (default)" msgstr "ignorierte Dateien aktualisieren (Standard)" -msgid "do not check if another worktree is holding the given ref" +msgid "do not check if another worktree is using this branch" msgstr "" -"Prüfung, ob die Referenz bereits in einem anderen Arbeitsverzeichnis " -"ausgecheckt wurde, deaktivieren" +"nicht prüfen, ob ein anderes Arbeitsverzeichnis diesen Branch verwendet" msgid "checkout our version for unmerged files" msgstr "unsere Variante für nicht zusammengeführte Dateien auschecken" @@ -4272,13 +4271,11 @@ msgstr "" "Zeit\n" "erstellen" -msgid "revision" -msgstr "Commit" +msgid "ref" +msgstr "Referenz" -msgid "deepen history of shallow clone, excluding rev" -msgstr "" -"die Historie eines Klons mit unvollständiger Historie (shallow) mittels\n" -"Ausschluss eines Commits vertiefen" +msgid "deepen history of shallow clone, excluding ref" +msgstr "Historie eines flachen Klons vertiefen, Referenz exkludiert" msgid "clone only one branch, HEAD or --branch" msgstr "nur einen Branch klonen, HEAD oder --branch" @@ -5232,8 +5229,7 @@ msgid "" "regexp] [--value=] [--fixed-value] [--default=] " msgstr "" "git config get [] [] [--includes] [--all] [--" -"regexp] [--value=] [--fixed-value] [--default=] " -"" +"regexp] [--value=] [--fixed-value] [--default=] " msgid "" "git config set [] [--type=] [--all] [--value=] [--" @@ -5244,10 +5240,10 @@ msgstr "" msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgid "git config rename-section [] " msgstr "git config rename-section [] " @@ -5695,12 +5691,8 @@ msgid "traversed %lu commits\n" msgstr "%lu Commits durchlaufen\n" #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"mehr als %i Tags gefunden; führe die ersten %i auf\n" -"Suche bei %s aufgegeben\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "%i Tags gefunden; Suche bei %s aufgegeben\n" #, c-format msgid "describe %s\n" @@ -6142,6 +6134,20 @@ msgstr "%s ist kein gültiges Objekt" msgid "the object %s does not exist" msgstr "das Objekt %s ist nicht vorhanden" +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Führen Sie 'git remote set-head %s %s' aus, um der Änderung zu folgen,\n" +"oder setzen Sie die Konfiguration 'remote.%s.followRemoteHEAD' auf einen\n" +"anderen Wert, wenn Sie diese Meldung nicht sehen wollen. Konkret wird diese\n" +"Warnung mit 'git config set remote.%s.followRemoteHEAD %s' deaktiviert\n" +"bis die Gegenstelle HEAD in etwas anderes ändert." + msgid "multiple branches detected, incompatible with --set-upstream" msgstr "mehrere Branches erkannt, inkompatibel mit --set-upstream" @@ -6287,6 +6293,9 @@ msgstr "Refmap" msgid "specify fetch refmap" msgstr "Refmap für 'fetch' angeben" +msgid "revision" +msgstr "Commit" + msgid "report that we have only objects reachable from this object" msgstr "" "ausgeben, dass wir nur Objekte haben, die von diesem Objekt aus erreichbar " @@ -7030,8 +7039,26 @@ msgstr "weder Timer von systemd, noch crontab ist verfügbar" msgid "%s scheduler is not available" msgstr "%s Scheduler ist nicht verfügbar" -msgid "another process is scheduling background maintenance" -msgstr "ein anderer Prozess plant die Hintergrundwartung" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"Konnte '%s.lock' nicht erstellen: %s.\n" +"\n" +"Ein weiterer geplanter git-maintenance(1)-Prozess scheint in diesem\n" +"Repository zu laufen. Bitte stellen Sie sicher, dass keine anderen\n" +"Wartungsprozesse laufen und versuchen Sie es dann erneut. Wenn es\n" +"immer noch fehlschlägt, ist möglicherweise ein git-maintenance(1)-Prozess\n" +"in diesem Repository abgestürzt: Entfernen Sie die Datei manuell, um\n" +"fortzufahren." + +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "kann keine Sperre für die geplante Hintergrundwartung erhalten" msgid "git maintenance start [--scheduler=]" msgstr "git maintenance start [--scheduler=]" @@ -7612,6 +7639,22 @@ msgid_plural "chain length = %d: %lu objects" msgstr[0] "Länge der Objekt-Liste = %d: %lu Objekt" msgstr[1] "Länge der Objekt-Liste = %d: %lu Objekte" +msgid "could not start pack-objects to repack local links" +msgstr "" +"konnte pack-objects nicht starten, um lokale Verknüpfungen neu zu packen" + +msgid "failed to feed local object to pack-objects" +msgstr "lokales Objekt konnte nicht an pack-objects übergeben werden" + +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack: Erwarte vollständige Hex-Objekt-ID-Zeilen nur von pack-objects." + +msgid "could not finish pack-objects to repack local links" +msgstr "" +"konnte pack-objects nicht vollständig ausführen, um lokale Verknüpfungen neu " +"zu packen" + msgid "Cannot come back to cwd" msgstr "Kann nicht zurück zum Arbeitsverzeichnis wechseln" @@ -7623,6 +7666,9 @@ msgstr "%s ist ungültig" msgid "unknown hash algorithm '%s'" msgstr "unbekannter Hash-Algorithmus '%s'" +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor kann nicht mit einem Paketnamen verwendet werden" + msgid "--stdin requires a git repository" msgstr "--stdin erfordert ein Git-Repository" @@ -9003,11 +9049,11 @@ msgstr "git notes [--ref ] [list []]" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] add [-f] [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace] [-m " -" | -F | (-c | -C) ] []" +" | -F | (-c | -C) ] [] [-e]" msgid "git notes [--ref ] copy [-f] " msgstr "" @@ -9016,11 +9062,11 @@ msgstr "" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] append [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace] [-m " -" | -F | (-c | -C) ] []" +" | -F | (-c | -C) ] [] [-e]" msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref ] edit [--allow-empty] []" @@ -9142,6 +9188,9 @@ msgstr "Notizinhalte in einer Datei" msgid "reuse and edit specified note object" msgstr "Wiederverwendung und Bearbeitung des angegebenen Notiz-Objektes" +msgid "edit note message in editor" +msgstr "Notizmeldung im Editor bearbeiten" + msgid "reuse specified note object" msgstr "Wiederverwendung des angegebenen Notiz-Objektes" @@ -9655,6 +9704,9 @@ msgstr "" "keine Objekte aus Packdateien von partiell geklonten Remote-Repositories " "packen" +msgid "implies --missing=allow-any" +msgstr "impliziert --missing=allow-any" + msgid "respect islands during delta compression" msgstr "Delta-Islands bei Delta-Kompression beachten" @@ -11317,6 +11369,30 @@ msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " Lokale Referenz konfiguriert für 'git push'%s:" msgstr[1] " Lokale Referenzen konfiguriert für 'git push'%s:" +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "'%s/HEAD' ist unverändert und zeigt auf '%s'\n" + +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' hat sich von '%s' geändert und zeigt jetzt auf '%s'\n" + +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "'%s/HEAD' ist nun erstellt und zeigt auf '%s'\n" + +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' war losgelöst bei '%s' und zeigt nun auf '%s'\n" + +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"'%s/HEAD' zeigte vorher auf '%s' (was kein Remote-Branch ist), zeigt jetzt " +"aber auf '%s'\n" + msgid "set refs/remotes//HEAD according to remote" msgstr "setzt refs/remotes//HEAD gemäß dem Remote-Repository" @@ -11340,7 +11416,7 @@ msgid "Not a valid ref: %s" msgstr "keine gültige Referenz: %s" #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "Konnte %s nicht einrichten" #, c-format @@ -14142,6 +14218,9 @@ msgstr "" "versuchen, eine Übereinstimmung des Branchnamens mit einem\n" "Remote-Tracking-Branch herzustellen" +msgid "use relative paths for worktrees" +msgstr "relative Pfade für Arbeitsverzeichnisse verwenden" + #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" msgstr "" @@ -14426,6 +14505,28 @@ msgstr "kann '%s' nicht erstellen" msgid "index-pack died" msgstr "Erstellung der Paketindexdatei abgebrochen" +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "Verzeichnis '%s' ist im Index vorhanden, aber nicht partiell" + +msgid "corrupted cache-tree has entries not present in index" +msgstr "" +"das beschädigte Cache-Verzeichnis enthält Einträge, die nicht im Index " +"enthalten sind" + +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "%s mit Flags 0x%x sollte nicht im Cache-Verzeichnis sein" + +#, c-format +msgid "bad subtree '%.*s'" +msgstr "ungültiges Unterverzeichnis '%.*s'" + +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "" +"Cache-Verzeichnis für Pfad %.*s stimmt nicht überein. Erwartete %s bekam %s" + msgid "terminating chunk id appears earlier than expected" msgstr "abschließende Chunk-ID erscheint eher als erwartet" @@ -15322,17 +15423,18 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "Die Unterstützung für /info/grafts ist veraltet\n" -"und wird in zukünftigen Git Versionen entfernt.\n" +"und wird in einer zukünftigen Git-Version entfernt.\n" "\n" -"Bitte benutzen Sie \"git replace --convert-graft-file\"\n" -"zum Konvertieren der künstlichen Vorgänger (\"grafts\")\n" -"in ersetzende Referenzen.<\n" +"Bitte verwenden Sie \"git replace --convert-graft-file\"\n" +"um die künstlichen Vorgänger (\"graft\") in ersetzende Referenzen\n" +"zu konvertieren.\n" "\n" -"Sie können diese Meldung unterdrücken, indem Sie\n" -"\"git config advice.graftFileDeprecated false\" ausführen." +"Deaktivieren Sie diese Meldung, indem Sie\n" +"\"git config set advice.graftFileDeprecated false\"\n" +"ausführen." #, c-format msgid "commit %s exists in commit-graph but not in the object database" @@ -16166,6 +16268,18 @@ msgstr "URL hat kein Schema: %s" msgid "credential url cannot be parsed: %s" msgstr "URL mit Zugangsdaten konnte nicht geparst werden: %s" +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "ungültiger timeout '%s', nicht-negative ganze Zahl erwartet" + +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "ungültiger init-timeout '%s', nicht-negative ganze Zahl erwartet" + +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "ungültiges max-connections '%s', ganze Zahl erwartet" + msgid "in the future" msgstr "in der Zukunft" @@ -16896,6 +17010,22 @@ msgstr "ungültiger Git-Namespace-Pfad \"%s\"" msgid "too many args to run %s" msgstr "zu viele Argumente angegeben, um %s auszuführen" +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"Sie versuchen %s zu holen, welches sich in der Commit-Graph-Datei, aber " +"nicht in der Objektdatenbank befindet.\n" +"Dies ist wahrscheinlich auf eine Beschädigung des Repositories " +"zurückzuführen.\n" +"Wenn Sie versuchen, die Beschädigung des Repositories zu beheben, indem Sie " +"das fehlende Objekt erneut holen,\n" +"verwenden Sie 'git fetch --refetch' mit dem fehlenden Objekt." + msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack: erwartete shallow-Liste" @@ -17329,8 +17459,8 @@ msgid "" "given pattern contains NULL byte (via -f ). This is only supported " "with -P under PCRE v2" msgstr "" -"Angegebenes Muster enthält NULL Byte (über -f ). Das wird nur mit -" -"Punter PCRE v2 unterstützt." +"Angegebenes Muster enthält NULL Byte (über -f ). Das wird nur mit -P " +"unter PCRE v2 unterstützt." #, c-format msgid "'%s': unable to read %s" @@ -17483,10 +17613,11 @@ msgstr[1] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" -"Der '%s' Hook wurde ignoriert, weil er nicht als ausführbar markiert ist.\n" -"Sie können diese Warnung mit `git config advice.ignoredHook false` " +"Der '%s'-Hook wurde ignoriert, weil er nicht als ausführbar eingestellt " +"ist.\n" +"Sie können diese Warnung mit `git config set advice.ignoredHook false` " "deaktivieren." msgid "not a git repository" @@ -17503,17 +17634,9 @@ msgstr "negativer Wert für http.postBuffer; benutze Standardwert %d" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "Kontrolle über Delegation wird mit cURL < 7.22.0 nicht unterstützt" -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "" -"Das Anheften des öffentlichen Schlüssels wird mit cURL < 7.39.0 nicht " -"unterstützt" - msgid "Unknown value for http.proactiveauth" msgstr "Unbekannter Wert für http.proactiveauth" -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "CURLSSLOPT_NO_REVOKE wird mit cURL < 7.44.0 nicht unterstützt." - #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" msgstr "Nicht unterstütztes SSL-Backend '%s'. Unterstützte SSL-Backends:" @@ -17700,6 +17823,10 @@ msgstr "angeführtes CRLF entdeckt" msgid "unable to format message: %s" msgstr "Meldung kann nicht formatiert werden: %s" +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "ungültige marker-size '%s', ganze Zahl erwartet" + #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "Fehler beim Merge von Submodul %s (nicht ausgecheckt)." @@ -18837,19 +18964,18 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" -"Git erzeugt normalerweise keine Referenzen die mit\n" -"40 Hex-Zeichen enden, da diese ignoriert werden wenn\n" -"Sie diese angeben. Diese Referenzen könnten aus Versehen\n" -"erzeugt worden sein. Zum Beispiel,\n" +"Git erstellt normalerweise nie eine Referenz, die mit 40-Hex-Zeichen endet,\n" +"weil sie ignoriert wird, wenn Sie nur 40-Hex-Zeichen angeben. Diese\n" +"Referenzen können aus Versehen erstellt werden. Zum Beispiel,\n" "\n" " git switch -c $br $(git rev-parse ...)\n" "\n" -"wobei \"$br\" leer ist und eine 40-Hex-Referenz erzeugt\n" -"wurde. Bitte prüfen Sie diese Referenzen und löschen\n" -"Sie sie gegebenenfalls. Unterdrücken Sie diese Meldung\n" -"indem Sie \"git config advice.objectNameWarning false\"\n" +"wobei \"$br\" irgendwie leer ist und eine 40-Hex-Referenz erstellt wird.\n" +"Bitte überprüfen Sie die Referenzen und löschen Sie diese gegebenenfalls.\n" +"Schalten Sie diese Meldung aus, indem Sie\n" +"\"git config set advice.objectNameWarning false\"\n" "ausführen." #, c-format @@ -19010,13 +19136,6 @@ msgstr "Multi-Pack-Bitmap fehlt erforderlicher Reverse-Index" msgid "could not open pack %s" msgstr "konnte Paket '%s' nicht öffnen" -msgid "could not determine MIDX preferred pack" -msgstr "konnte das von MIDX bevorzugte Paket nicht ermitteln" - -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "bevorzugtes Paket (%s) ist ungültig" - msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "Bitmap-Lookup-Tabelle beschädigt: Triplet-Position außerhalb des Index" @@ -20151,17 +20270,25 @@ msgstr "Log für Referenz %s unerwartet bei %s beendet." msgid "log for %s is empty" msgstr "Log für %s ist leer." -msgid "refusing to force and skip creation of reflog" -msgstr "Erzwingen der Aktion verweigert; überspringe Erstellung des Reflogs" - #, c-format -msgid "refusing to update ref with bad name '%s'" -msgstr "verweigere Aktualisierung einer Referenz mit fehlerhaftem Namen '%s'" +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "Aktualisierung des Reflogs für Pseudoreferenz '%s' verweigert" #, c-format msgid "refusing to update pseudoref '%s'" msgstr "Aktualisierung von Pseudoreferenz '%s' verweigert" +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "Aktualisierung des Reflogs mit fehlerhaftem Namen '%s' verweigert" + +#, c-format +msgid "refusing to update ref with bad name '%s'" +msgstr "verweigere Aktualisierung einer Referenz mit fehlerhaftem Namen '%s'" + +msgid "refusing to force and skip creation of reflog" +msgstr "Erzwingen der Aktion verweigert; überspringe Erstellung des Reflogs" + #, c-format msgid "update_ref failed for ref '%s': %s" msgstr "update_ref für Referenz '%s' fehlgeschlagen: %s" @@ -20215,6 +20342,10 @@ msgstr "" "kann Referenz '%s' nicht sperren: erwartete symbolische Referenz mit Ziel " "'%s': ist aber eine reguläre Referenz" +#, c-format +msgid "cannot read ref file '%s'" +msgstr "kann Ref-Datei '%s' nicht lesen" + #, c-format msgid "cannot open directory %s" msgstr "Verzeichnis %s kann nicht geöffnet werden" @@ -20430,6 +20561,10 @@ msgstr "Mehr als ein receivepack-Befehl angegeben, benutze den ersten." msgid "more than one uploadpack given, using the first" msgstr "Mehr als ein uploadpack-Befehl angegeben, benutze den ersten." +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "nicht erkannter followRemoteHEAD-Wert '%s' ignoriert" + #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" msgstr "unbekannter Wert transfer.credentialsInUrl: '%s'" @@ -22391,6 +22526,9 @@ msgstr "Commit %s ist nicht als erreichbar gekennzeichnet." msgid "too many commits marked reachable" msgstr "Zu viele Commits als erreichbar gekennzeichnet." +msgid "could not determine MIDX preferred pack" +msgstr "konnte das von MIDX bevorzugte Paket nicht ermitteln" + msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 []" @@ -23061,6 +23199,9 @@ msgstr ".git-Datei kaputt" msgid ".git file incorrect" msgstr ".git-Datei fehlerhaft" +msgid ".git file absolute/relative path mismatch" +msgstr "absoluter/relativer Pfad der .git-Datei stimmt nicht überein" + msgid "not a valid path" msgstr "kein gültiger Pfad" @@ -23077,6 +23218,9 @@ msgstr "Konnte Repository nicht finden; .git-Datei ist kaputt" msgid "gitdir unreadable" msgstr "gitdir nicht lesbar" +msgid "gitdir absolute/relative path mismatch" +msgstr "absolute/relative Pfadabweichung in gitdir" + msgid "gitdir incorrect" msgstr "gitdir fehlerhaft" @@ -23111,6 +23255,14 @@ msgstr "konnte %s nicht in '%s' aufheben" msgid "failed to set extensions.worktreeConfig setting" msgstr "Einstellung für extensions.worktreeConfig konnte nicht gesetzt werden" +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"Repository-Format konnte nicht aktualisiert werden, um relative " +"Arbeitsverzeichnisse zu unterstützen" + +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "Einstellung extensions.relativeWorktrees kann nicht gesetzt werden" + #, c-format msgid "could not setenv '%s'" msgstr "konnte '%s' nicht setzen" @@ -24038,3 +24190,7 @@ msgstr "Lasse %s mit Backup-Suffix '%s' aus.\n" #, perl-format msgid "Do you really want to send %s? [y|N]: " msgstr "Wollen Sie %s wirklich versenden? [y|N]: " + +#, c-format +#~ msgid "preferred pack (%s) is invalid" +#~ msgstr "bevorzugtes Paket (%s) ist ungültig" From a2df58fb15aa6319e1f4159d0218814245e48e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C5=A9=20Ti=E1=BA=BFn=20H=C6=B0ng?= Date: Sun, 5 Jan 2025 01:54:04 +0700 Subject: [PATCH 021/306] l10n: vi: Updated translation for 2.48 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Vũ Tiến Hưng --- po/vi.po | 336 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 241 insertions(+), 95 deletions(-) diff --git a/po/vi.po b/po/vi.po index 00008b50f517e2..80a5f191a85c7a 100644 --- a/po/vi.po +++ b/po/vi.po @@ -4,11 +4,11 @@ # https://raw.githubusercontent.com/git-l10n/git-po/pot/main/po/git.pot # --- # Copyright (C) 2012-2022, Translation Project, Vietnamese Team -# Copyright (C) 2024, Vũ Tiến Hưng +# Copyright (C) 2024-2025, Vũ Tiến Hưng # Nguyễn Thái Ngọc Duy , 2012. # Đoàn Trần Công Danh , 2020. # Trần Ngọc Quân , 2012-2022. -# Vũ Tiến Hưng , 2024. +# Vũ Tiến Hưng , 2024-2025. # --- # BẢNG THUẬT NGỮ / TERMINOLOGY # Updated: 2024-07-26, git 2.46 @@ -64,10 +64,10 @@ # +------------------------------------------------------------------+ msgid "" msgstr "" -"Project-Id-Version: git 2.47\n" +"Project-Id-Version: git 2.48\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-05 01:20+0000\n" -"PO-Revision-Date: 2024-10-05 16:48+0700\n" +"POT-Creation-Date: 2024-12-23 18:57+0000\n" +"PO-Revision-Date: 2025-01-05 01:20+0700\n" "Last-Translator: Vũ Tiến Hưng \n" "Language-Team: Vietnamese \n" "Language: vi\n" @@ -686,10 +686,10 @@ msgstr "Chỉ có các tập tin nhị phân thay đổi." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"Tắt lời nhắn này bằng \"git config advice.%s false\"" +"Tắt lời nhắn này bằng \"git config set advice.%s false\"" #, c-format msgid "%shint:%s%.*s%s\n" @@ -1904,7 +1904,7 @@ msgid "update tracked files" msgstr "cập nhật các tập tin được theo dõi" msgid "renormalize EOL of tracked files (implies -u)" -msgstr "thường hóa lại EOL của các tập tin được theo dõi (ngụ ý -u)" +msgstr "thường hóa lại EOL của các tập tin được theo dõi (ngầm chỉ định -u)" msgid "record only the fact that the path will be added later" msgstr "chỉ ghi lại sự việc mà đường dẫn sẽ được thêm vào sau" @@ -2017,7 +2017,7 @@ msgstr "không hiểu cú pháp %s" #, c-format msgid "'%s' was deleted by the applypatch-msg hook" -msgstr "'%s' bị xóa bởi móc applypatch-msg" +msgstr "'%s' bị xóa bởi hook applypatch-msg" #, c-format msgid "Malformed input line: '%s'." @@ -3040,11 +3040,11 @@ msgid "HEAD not found below refs/heads!" msgstr "Không tìm thấy HEAD ở dưới refs/heads!" msgid "" -"branch with --recurse-submodules can only be used if submodule." -"propagateBranches is enabled" +"branch with --recurse-submodules can only be used if " +"submodule.propagateBranches is enabled" msgstr "" -"nhánh với --recurse-submodules chỉ có thể được sử dụng nếu submodule." -"propagateBranches được kích hoạt" +"nhánh với --recurse-submodules chỉ có thể được sử dụng nếu " +"submodule.propagateBranches được kích hoạt" msgid "--recurse-submodules can only be used to create branches" msgstr "--recurse-submodules chỉ có thể được sử dụng để tạo ra các nhánh" @@ -3125,7 +3125,7 @@ msgid "libc info: " msgstr "thông tin libc: " msgid "not run from a git repository - no hooks to show\n" -msgstr "không chạy từ một kho git - nên chẳng có móc nào để hiển thị cả\n" +msgstr "không chạy từ một kho git - nên chẳng có hook nào để hiển thị cả\n" msgid "" "git bugreport [(-o | --output-directory) ]\n" @@ -3200,7 +3200,7 @@ msgid "System Info" msgstr "Thông tin hệ thống" msgid "Enabled Hooks" -msgstr "Các Móc đã được bật" +msgstr "Các hook đã được bật" #, c-format msgid "unable to write to %s" @@ -3880,8 +3880,8 @@ msgstr "nhánh chưa sinh mới" msgid "update ignored files (default)" msgstr "cập nhật các tập tin bị bỏ qua (mặc định)" -msgid "do not check if another worktree is holding the given ref" -msgstr "không kiểm tra nếu cây làm việc khác đang giữ tham chiếu đã cho" +msgid "do not check if another worktree is using this branch" +msgstr "không kiểm tra nếu cây làm việc khác đang sử dụng nhánh này" msgid "checkout our version for unmerged files" msgstr "checkout phiên bản của ta cho các tập tin chưa được hòa trộn" @@ -4129,7 +4129,7 @@ msgid "create a bare repository" msgstr "tạo kho bare" msgid "create a mirror repository (implies --bare)" -msgstr "tạo kho bản sao (ngụ ý --bare)" +msgstr "tạo kho bản sao (ngầm chỉ định --bare)" msgid "to clone from a local repository" msgstr "để nhân bản từ kho nội bộ" @@ -4182,11 +4182,11 @@ msgstr "tạo bản sao không đầy đủ cho mức sâu đã cho" msgid "create a shallow clone since a specific time" msgstr "tạo bản sao không đầy đủ từ thời điểm đã cho" -msgid "revision" -msgstr "điểm xét duyệt" +msgid "ref" +msgstr "ref" -msgid "deepen history of shallow clone, excluding rev" -msgstr "làm sâu hơn lịch sử của bản sao shallow, bằng điểm xét duyệt loại trừ" +msgid "deepen history of shallow clone, excluding ref" +msgstr "làm sâu hơn lịch sử của bản sao shallow, loại trừ tham chiếu" msgid "clone only one branch, HEAD or --branch" msgstr "chỉ nhân bản một nhánh, HEAD hoặc --branch" @@ -5068,7 +5068,7 @@ msgid "commit only specified files" msgstr "chỉ chuyển giao các tập tin đã chỉ ra" msgid "bypass pre-commit and commit-msg hooks" -msgstr "vòng qua móc (hook) pre-commit và commit-msg" +msgstr "bỏ qua hook pre-commit và commit-msg" msgid "show what would be committed" msgstr "hiển thị xem cái gì có thể được chuyển giao" @@ -5077,7 +5077,7 @@ msgid "amend previous commit" msgstr "'tu bổ' (amend) lần commit trước" msgid "bypass post-rewrite hook" -msgstr "vòng qua móc (hook) post-rewrite" +msgstr "bỏ qua hook post-rewrite" msgid "ok to record an empty change" msgstr "ok để ghi lại một thay đổi trống rỗng" @@ -5143,10 +5143,10 @@ msgstr "" msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgid "git config rename-section [] " msgstr "git config rename-section [] " @@ -5579,12 +5579,8 @@ msgid "traversed %lu commits\n" msgstr "đã xuyên %lu qua lần chuyển giao\n" #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"tìm thấy nhiều hơn %i thẻ; đã liệt kê %i cái gần\n" -"đây nhất bỏ đi tìm kiếm tại %s\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "tìm thấy %i thẻ; từ bỏ tìm kiếm tại %s\n" #, c-format msgid "describe %s\n" @@ -6020,6 +6016,19 @@ msgstr "%s không phải là một đối tượng hợp lệ" msgid "the object %s does not exist" msgstr "đối tượng '%s' không tồn tại" +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Chạy 'git remote set-head %s %s' để làm theo thay đổi, hoặc đặt tuỳ chọn\n" +"'remote.%s.followRemoteHEAD' sang giá trị khác nếu bạn không muốn thấy\n" +"thông báo này. Cụ thể, 'git config set remote.%s.followRemoteHEAD %s'\n" +"sẽ vô hiệu cảnh báo này tới khi máy chủ đổi HEAD về chỗ khác." + msgid "multiple branches detected, incompatible with --set-upstream" msgstr "phát hiện nhiều nhánh, không tương thích với --set-upstream" @@ -6157,6 +6166,9 @@ msgstr "refmap" msgid "specify fetch refmap" msgstr "chỉ ra refmap cần lấy về" +msgid "revision" +msgstr "điểm xét duyệt" + msgid "report that we have only objects reachable from this object" msgstr "báo rằng ta chỉ có các đối tượng tiếp cận được từ đối tượng này" @@ -6209,11 +6221,11 @@ msgid "protocol does not support --negotiate-only, exiting" msgstr "giao thức không hỗ trợ --negotiate-only, nên thoát" msgid "" -"--filter can only be used with the remote configured in extensions." -"partialclone" +"--filter can only be used with the remote configured in " +"extensions.partialclone" msgstr "" -"--filter chỉ có thể được dùng với máy chủ được cấu hình bằng extensions." -"partialclone" +"--filter chỉ có thể được dùng với máy chủ được cấu hình bằng " +"extensions.partialclone" msgid "--atomic can only be used when fetching from one remote" msgstr "--atomic chỉ có thể dùng khi lấy về từ một máy chủ" @@ -6882,8 +6894,24 @@ msgstr "hoặc là bộ lập lịch systemd hoặc là crontab không sẵn có msgid "%s scheduler is not available" msgstr "bộ lên lịch %s không sẵn có" -msgid "another process is scheduling background maintenance" -msgstr "một tiến trình khác được lập kế hoạch chạy nền để bảo trì" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"Không thể tạo '%s.lock': %s.\n" +"\n" +"Tiến trình git-maintenance(1) khác có lẽ đang chạy ở kho này. Vui lòng\n" +"chắc chắn rằng mọi tiến trình đã kết thúc và sau đó thử lại. Nếu vẫn lỗi,\n" +"một tiến trình git-maintenance(1) có lẽ đã crash ở kho này trước đó:\n" +"gõ bỏ tập tin thủ công để tiếp tục." + +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "không thể lấy được lock để bảo trì nền theo kế hoạch" msgid "git maintenance start [--scheduler=]" msgstr "git maintenance start [--scheduler=]" @@ -7250,8 +7278,8 @@ msgid "" "git hook run [--ignore-missing] [--to-stdin=] [-- ]" msgstr "" -"git hook run [--ignore-missing] [--to-stdin=] [-- " -"]" +"git hook run [--ignore-missing] [--to-stdin=] [-- " +"]" msgid "silently ignore missing requested " msgstr "âm thầm bỏ qua các đã yêu cầu còn thiếu" @@ -7445,6 +7473,20 @@ msgid "chain length = %d: %lu object" msgid_plural "chain length = %d: %lu objects" msgstr[0] "chiều dài chuỗi = %d: %lu đối tượng" +msgid "could not start pack-objects to repack local links" +msgstr "không thể bắt đầu pack-objects để đóng gói lại các link cục bộ" + +msgid "failed to feed local object to pack-objects" +msgstr "gặp lỗi khi đưa local object cho pack-objects" + +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack: Đang chỉ cần các dòng ID đối tượng dạng hexa đầy đủ từ pack-" +"objects." + +msgid "could not finish pack-objects to repack local links" +msgstr "không thể hoàn tất pack-objects để đóng gói các link cục bộ" + msgid "Cannot come back to cwd" msgstr "Không thể quay lại thư mục hiện hành" @@ -7456,6 +7498,9 @@ msgstr "%s sai" msgid "unknown hash algorithm '%s'" msgstr "không hiểu thuật toán băm dữ liệu '%s'" +msgid "--promisor cannot be used with a pack name" +msgstr "không được dùng --promisor với tên pack" + msgid "--stdin requires a git repository" msgstr "--stdin cần một kho git" @@ -8101,7 +8146,8 @@ msgid "use full path names" msgstr "dùng tên đường dẫn đầy đủ" msgid "list entire tree; not just current directory (implies --full-name)" -msgstr "liệt kê cây mục tin; không chỉ thư mục hiện hành (ngụ ý --full-name)" +msgstr "" +"liệt kê cây mục tin; không chỉ thư mục hiện hành (ngầm chỉ định --full-name)" msgid "--format can't be combined with other format-altering options" msgstr "" @@ -8390,10 +8436,10 @@ msgid "continue the current in-progress merge" msgstr "tiếp tục quá trình hòa trộn hiện tại đang thực hiện" msgid "bypass pre-merge-commit and commit-msg hooks" -msgstr "vòng qua móc (hook) pre-merge-commit và commit-msg" +msgstr "bỏ qua hook pre-merge-commit và commit-msg" msgid "could not run stash." -msgstr "không thể chạy stash." +msgstr "không thể chạy tạm cất." msgid "stash failed" msgstr "lệnh tạm cất gặp lỗi" @@ -8815,11 +8861,11 @@ msgstr "git notes [--ref ] [list [<đối-tượng>]]" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c | -C) <đối-tượng>] [<đối-tượng>]" +"tin> | (-c | -C) <đối-tượng>] [<đối-tượng>] [-e]" msgid "git notes [--ref ] copy [-f] " msgstr "git notes [--ref ] copy [-f] <đến-đối-tượng>" @@ -8827,11 +8873,11 @@ msgstr "git notes [--ref ] copy [-f] <đến- msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c | -C) <đối-tượng>] [<đối-tượng>]" +"tin> | (-c | -C) <đối-tượng>] [<đối-tượng>] [-e]" msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref ] edit [--allow-empty] [<đối-tượng>]" @@ -8950,6 +8996,9 @@ msgstr "nội dung ghi chú (note) nằm trong một tập tin" msgid "reuse and edit specified note object" msgstr "dùng lại nhưng có sửa chữa đối tượng note đã chỉ ra" +msgid "edit note message in editor" +msgstr "sửa lại chú thích trong trình soạn thảo" + msgid "reuse specified note object" msgstr "dùng lại đối tượng ghi chú (note) đã chỉ ra" @@ -8988,7 +9037,7 @@ msgid "read objects from stdin" msgstr "đọc các đối tượng từ stdin" msgid "load rewriting config for (implies --stdin)" -msgstr "tải cấu hình chép lại cho (ngụ ý --stdin)" +msgstr "tải cấu hình chép lại cho (ngầm chỉ định --stdin)" msgid "too few arguments" msgstr "quá ít đối số" @@ -9355,7 +9404,7 @@ msgid "limit pack window by memory in addition to object limit" msgstr "giới hạn cửa sổ đóng gói theo bộ nhớ cộng thêm với giới hạn đối tượng" msgid "maximum length of delta chain allowed in the resulting pack" -msgstr "độ dài tối đa của chuỗi móc xích 'delta' được phép trong gói kết quả" +msgstr "độ dài tối đa của chuỗi delta được phép trong gói kết quả" msgid "reuse existing deltas" msgstr "dùng lại các delta sẵn có" @@ -9448,8 +9497,11 @@ msgstr "xử lý cho thiếu đối tượng" msgid "do not pack objects in promisor packfiles" msgstr "không thể đóng gói các đối tượng trong các tập tin gói promisor" +msgid "implies --missing=allow-any" +msgstr "ngầm chỉ định --missing=allow-any" + msgid "respect islands during delta compression" -msgstr "tôn trọng island trong suốt quá trình nén 'delta'" +msgstr "tôn trọng island trong suốt quá trình nén delta" msgid "protocol" msgstr "giao thức" @@ -9584,7 +9636,7 @@ msgid "allow fast-forward" msgstr "cho phép chuyển-tiếp-nhanh" msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "điều khiển cách dùng các móc (hook) pre-merge-commit và commit-msg" +msgstr "điều khiển cách dùng các hook pre-merge-commit và commit-msg" msgid "automatically stash/stash pop before and after" msgstr "tự động stash/stash pop trước và sau" @@ -9785,8 +9837,8 @@ msgid "" msgstr "" "\n" "Để tránh tự động cấu hình nhánh thượng nguồn khi tên của chúng\n" -"không khớp với nhánh nội bộ, xem tùy chọn 'simple' của branch." -"autoSetupMerge\n" +"không khớp với nhánh nội bộ, xem tùy chọn 'simple' của " +"branch.autoSetupMerge\n" "trong 'git help config'.\n" #, c-format @@ -9994,7 +10046,7 @@ msgid "prune locally removed refs" msgstr "xén tỉa những tham chiếu bị gỡ bỏ" msgid "bypass pre-push hook" -msgstr "vòng qua móc tiền-đẩy (pre-push)" +msgstr "bỏ qua hook tiền-đẩy (pre-push)" msgid "push missing but relevant tags" msgstr "push phần bị thiếu nhưng các thẻ lại thích hợp" @@ -10289,10 +10341,10 @@ msgid "use the merge-base of upstream and branch as the current base" msgstr "sử dụng gốc hòa trộn của thượng nguồn và nhánh làm gốc hiện tại" msgid "allow pre-rebase hook to run" -msgstr "cho phép móc (hook) pre-rebase được chạy" +msgstr "cho phép hook pre-rebase được chạy" msgid "be quiet. implies --no-stat" -msgstr "im lặng. ngụ ý --no-stat" +msgstr "im lặng. ngầm chỉ định --no-stat" msgid "display a diffstat of what changed upstream" msgstr "hiển thị diffstat của những thay đổi thượng nguồn" @@ -10526,14 +10578,14 @@ msgid "Current branch %s is up to date.\n" msgstr "Nhánh hiện tại %s đã được cập nhật rồi.\n" msgid "HEAD is up to date, rebase forced." -msgstr "HEAD hiện đã được cập nhật rồi, bị ép buộc rebase." +msgstr "HEAD hiện đã được cập nhật rồi, ép buộc rebase." #, c-format msgid "Current branch %s is up to date, rebase forced.\n" -msgstr "Nhánh hiện tại %s đã được cập nhật rồi, lệnh rebase ép buộc.\n" +msgstr "Nhánh hiện tại %s đã được cập nhật rồi, ép buộc rebase.\n" msgid "The pre-rebase hook refused to rebase." -msgstr "Móc (hook) pre-rebase từ chối rebase." +msgstr "Hook pre-rebase từ chối rebase." #, c-format msgid "Changes to %s:\n" @@ -10546,7 +10598,7 @@ msgstr "Thay đổi từ %s thành %s:\n" #, c-format msgid "First, rewinding head to replay your work on top of it...\n" msgstr "" -"Trước tiên, di chuyển head để xem lại các công việc trên đỉnh của nó...\n" +"Trước tiên, di chuyển lại head để thực hiện lại các thay đổi trên nó...\n" msgid "Could not detach HEAD" msgstr "Không thể tách rời HEAD" @@ -11048,6 +11100,29 @@ msgid " Local ref configured for 'git push'%s:" msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " Những tham chiếu nội bộ được cấu hình cho lệnh 'git push'%s:" +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "'%s/HEAD' không đổi và trỏ đến '%s'\n" + +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' đã đổi từ '%s' để trỏ đến '%s'\n" + +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "'%s/HEAD' đã tạo và trỏ đến '%s'\n" + +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' đã tách ra ở '%s' và trỏ đến '%s'\n" + +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"'%s/HEAD' từng trỏ đến '%s' (không phải nhánh máy chủ) và giờ trỏ đến '%s'\n" + msgid "set refs/remotes//HEAD according to remote" msgstr "đặt refs/remotes//HEAD cho phù hợp với máy chủ" @@ -11069,7 +11144,7 @@ msgid "Not a valid ref: %s" msgstr "Không phải là tham chiếu hợp lệ: %s" #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "Không thể cài đặt %s" #, c-format @@ -12955,7 +13030,7 @@ msgid "don't print cloning progress" msgstr "đừng in tiến trình nhân bản" msgid "disallow cloning into non-empty directory, implies --init" -msgstr "không cho phép nhân bản vào thư mục trống, ngụ ý --init" +msgstr "không cho phép nhân bản vào thư mục trống, ngầm chỉ định --init" msgid "" "git submodule [--quiet] update [--init [--filter=]] [--remote] " @@ -13778,6 +13853,9 @@ msgstr "cài đặt chế độ theo dõi (xem git-branch(1))" msgid "try to match the new branch name with a remote-tracking branch" msgstr "có khớp tên tên nhánh mới với một nhánh theo dõi máy chủ" +msgid "use relative paths for worktrees" +msgstr "dùng đường dẫn tương đối cho thư mục làm việc" + #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" msgstr "tùy chọn '%s', '%s' và '%s' không thể dùng cùng nhau" @@ -14049,6 +14127,25 @@ msgstr "không thể tạo '%s'" msgid "index-pack died" msgstr "index-pack đã chết" +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "Thư mục '%s' có ở trong chỉ mục, mà không phải dạng sparse?" + +msgid "corrupted cache-tree has entries not present in index" +msgstr "cache-tree bị hỏng, chứa mục không có trong chỉ mục" + +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "%s với cờ 0x%x không nên có trong cache-tree" + +#, c-format +msgid "bad subtree '%.*s'" +msgstr "subtree sai '%.*s'" + +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "cache-tree cho đường dẫn %.*s không khớp. Cần %s nhưng có %s" + msgid "terminating chunk id appears earlier than expected" msgstr "id chunk kết thúc sớm hơn bình thường" @@ -14264,7 +14361,7 @@ msgid "Display help information about Git" msgstr "Hiển thị thông tin trợ giúp về Git" msgid "Run git hooks" -msgstr "Chạy các móc git" +msgstr "Chạy các hook git" msgid "Server side implementation of Git over HTTP" msgstr "Hỗ trợ phía máy chủ của Git qua HTTP" @@ -14930,7 +15027,7 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "Hỗ trợ cho /info/grafts đã không còn\n" "và sẽ bị xóa bỏ ở phiên bản Git tương lai.\n" @@ -14939,7 +15036,7 @@ msgstr "" "để chuyển đổi các graft thành các tham chiếu thay thế.\n" "\n" "Tắt lời nhắn này bằng cách chạy\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" #, c-format msgid "commit %s exists in commit-graph but not in the object database" @@ -15750,6 +15847,18 @@ msgstr "url không có lược đồ: %s" msgid "credential url cannot be parsed: %s" msgstr "không hiểu cú pháp giấy chứng thực url: %s" +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "timeout không hợp lệ '%s', cần số nguyên không âm" + +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "init-timeout không hợp lệ '%s', cần số nguyên không âm" + +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "max-connections không hợp lệ '%s', cần số nguyên" + msgid "in the future" msgstr "ở tương lai" @@ -16445,6 +16554,20 @@ msgstr "đường dẫn không gian tên git \"%s\" sai" msgid "too many args to run %s" msgstr "quá nhiều tham số để chạy %s" +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"Bạn đang muốn lấy về %s, nằm trong tập tin đồ-thị-chuyển-giao nhưng nằm " +"ngoài cơ sở dữ liệu đối tượng.\n" +"Nhiều khả năng kho chứa đã bị hỏng.\n" +"Nếu bạn đang cần sửa chữa lại kho chứa bằng cách lấy về đối tượng còn thiếu, " +"dùng 'git fetch --refetch' với đối tượng đó." + msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack: cần danh sách shallow" @@ -16878,8 +17001,8 @@ msgid "" "given pattern contains NULL byte (via -f ). This is only supported " "with -P under PCRE v2" msgstr "" -"mẫu đã cho có chứa NULL byte (qua -f ). Điều này chỉ được hỗ trợ với -" -"P dưới PCRE v2" +"mẫu đã cho có chứa NULL byte (qua -f ). Điều này chỉ được hỗ trợ với " +"-P dưới PCRE v2" #, c-format msgid "'%s': unable to read %s" @@ -17023,10 +17146,10 @@ msgstr[0] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" -"Móc '%s' bị bỏ qua bởi vì nó không có cờ thực thi được.\n" -"Bạn có thể tắt cảnh báo này bằng 'git config advice.ignoredHook false'." +"Hook '%s' bị bỏ qua bởi vì nó không có cờ thực thi được.\n" +"Bạn có thể tắt cảnh báo này bằng 'git config set advice.ignoredHook false'." msgid "not a git repository" msgstr "không phải là kho git" @@ -17042,15 +17165,9 @@ msgstr "giá trị âm cho http.postBuffer; đặt thành mặc định là %d" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "Điều khiển giao quyền không được hỗ trợ với cURL < 7.22.0" -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "Chốt khóa công không được hỗ trợ với cURL < 7.39.0" - msgid "Unknown value for http.proactiveauth" msgstr "không hiểu giá trị cho http.proactiveauth" -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "CURLSSLOPT_NO_REVOKE không được hỗ trợ với cURL < 7.44.0" - #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" msgstr "" @@ -17204,7 +17321,7 @@ msgstr "" "Tiến trình git khác có lẽ đang chạy ở kho này, ví dụ\n" "một trình soạn thảo được mở bởi 'git commit'. Vui lòng chắc chắn\n" "rằng mọi tiến trình đã kết thúc và sau đó thử lại. Nếu vẫn lỗi,\n" -"một tiến trình git có lẽ đã crash khi thực hiện ở kho này trước đó:\n" +"một tiến trình git có lẽ đã crash ở kho này trước đó:\n" "gõ bỏ tập tin một cách thủ công để tiếp tục." #, c-format @@ -17236,9 +17353,13 @@ msgstr "phát hiện CRLF được trích dẫn" msgid "unable to format message: %s" msgstr "không thể định dạng thông điệp: %s" +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "marker-size không hợp lệ '%s', cần số nguyên" + #, c-format msgid "Failed to merge submodule %s (not checked out)" -msgstr "Gặp lỗi khi hòa trộn mô-đun-con %s (không checkout được)" +msgstr "Gặp lỗi khi hòa trộn mô-đun-con %s (không checkout)" #, c-format msgid "Failed to merge submodule %s (no merge base)" @@ -18335,7 +18456,7 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "Git thường không bao giờ tạo tham chiếu kết thúc với 40 ký tự hex\n" "bởi vì nó sẽ bị bỏ qua khi bạn chỉ định 40 ký tự hex. Những tham chiếu\n" @@ -18344,8 +18465,8 @@ msgstr "" " git switch -c $br $(git rev-parse ...)\n" "\n" "với \"$br\" không hiểu lý do vì sao rỗng và tạo ra tham chiếu 40-hex.\n" -" Xin hãy kiểm tra những tham chiếu này và xóa chúng đi nếu cần. Tắt\n" -"lời nhắn này bằng cách chạy lệnh \"git config advice.objectNameWarning " +"Xin hãy kiểm tra những tham chiếu này và xóa chúng đi nếu cần. Tắt\n" +"lời nhắn này bằng cách chạy lệnh \"git config set advice.objectNameWarning " "false\"" #, c-format @@ -18502,13 +18623,6 @@ msgstr "bitmap multi-pack thiếu chỉ mục để dành cần thiết" msgid "could not open pack %s" msgstr "không thể mở gói '%s'" -msgid "could not determine MIDX preferred pack" -msgstr "không thể xác định gói MIDX ưa dùng" - -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "preferred pack (%s) không hợp lệ" - msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "bảng tìm kiếm bitmap bị hỏng: vị trí bộ ba nằm ngoài chỉ mục" @@ -19619,17 +19733,25 @@ msgstr "nhật ký cho tham chiếu %s kết thúc bất ngờ trên %s" msgid "log for %s is empty" msgstr "nhật ký cho %s trống rỗng" -msgid "refusing to force and skip creation of reflog" -msgstr "từ chối bỏ qua việc tạo log tham chiếu" - #, c-format -msgid "refusing to update ref with bad name '%s'" -msgstr "từ chối cập nhật tham chiếu với tên sai '%s'" +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "từ chối cập nhật reflog cho tham chiếu ảo '%s'" #, c-format msgid "refusing to update pseudoref '%s'" msgstr "từ chối cập nhật tham chiếu ảo '%s'" +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "từ chối cập nhật reflog với tên sai '%s'" + +#, c-format +msgid "refusing to update ref with bad name '%s'" +msgstr "từ chối cập nhật tham chiếu với tên sai '%s'" + +msgid "refusing to force and skip creation of reflog" +msgstr "từ chối bỏ qua việc tạo log tham chiếu" + #, c-format msgid "update_ref failed for ref '%s': %s" msgstr "update_ref bị lỗi cho ref '%s': %s" @@ -19642,7 +19764,7 @@ msgid "ref updates forbidden inside quarantine environment" msgstr "cập nhật tham chiếu bị cấm trong môi trường kiểm tra" msgid "ref updates aborted by hook" -msgstr "các cập nhật tham chiếu bị huỷ bỏ bởi móc" +msgstr "các cập nhật tham chiếu bị huỷ bỏ bởi hook" #, c-format msgid "'%s' exists; cannot create '%s'" @@ -19679,6 +19801,10 @@ msgstr "" "không thể lock tham chiếu '%s': cần tham chiếu mềm tới '%s': nhưng lại là " "tham chiếu thường" +#, c-format +msgid "cannot read ref file '%s'" +msgstr "không thể đọc tập tin ref '%s'" + #, c-format msgid "cannot open directory %s" msgstr "không thể mở thư mục %s" @@ -19886,6 +20012,10 @@ msgstr "đã đưa ra nhiều hơn một gói nhận về, đang sử dụng cá msgid "more than one uploadpack given, using the first" msgstr "đã đưa ra nhiều hơn một gói tải lên, đang sử dụng cái đầu tiên" +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "bỏ qua giá trị không chấp nhận cho followRemoteHEAD '%s'" + #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" msgstr "không chấp nhận giá trị transfer.credentialsInUrl: '%s'" @@ -20650,7 +20780,7 @@ msgstr "" " git rebase --continue\n" msgid "'prepare-commit-msg' hook failed" -msgstr "móc 'prepare-commit-msg' bị lỗi" +msgstr "hook 'prepare-commit-msg' bị lỗi" msgid "" "Your name and email address were configured automatically based\n" @@ -21812,6 +21942,9 @@ msgstr "lần chuyển giao %s chưa được đánh dấu là tiếp cận đư msgid "too many commits marked reachable" msgstr "có quá nhiều lần chuyển giao được đánh dấu là tiếp cận được" +msgid "could not determine MIDX preferred pack" +msgstr "không thể xác định gói MIDX ưa dùng" + msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 []" @@ -22478,6 +22611,9 @@ msgstr "tập tin .git bị hỏng" msgid ".git file incorrect" msgstr "tập tin .git không chính xác" +msgid ".git file absolute/relative path mismatch" +msgstr "đường dẫn tương đối/tuyệt đối đến file .git không khớp" + msgid "not a valid path" msgstr "không phải là một đường dẫn hợp lệ" @@ -22493,6 +22629,9 @@ msgstr "không thể định vị kho chứa; tập tin .git bị hỏng" msgid "gitdir unreadable" msgstr "gitdir không thể đọc được" +msgid "gitdir absolute/relative path mismatch" +msgstr "đường dẫn tương đối/tuyệt đối đến gitdir không khớp" + msgid "gitdir incorrect" msgstr "gitdir không chính xác" @@ -22527,6 +22666,13 @@ msgstr "không thể bỏ đặt %s trong '%s'" msgid "failed to set extensions.worktreeConfig setting" msgstr "gặp lỗi khi đặt cài đặt extensions.worktreeConfig" +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"không thể nâng cấp định định dạng kho chứa để hỗ trợ cây làm việc tương đối" + +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "gặp lỗi khi đặt cài đặt extensions.relativeWorktrees" + #, c-format msgid "could not setenv '%s'" msgstr "không thể setenv '%s'" From 087ac486745129377a04d6921cddeac291c664aa Mon Sep 17 00:00:00 2001 From: Fredrik Date: Sat, 4 Jan 2025 19:29:05 +0100 Subject: [PATCH 022/306] l10n: sv.po, fixed swedish typos Signed-off-by: Peter Krefting --- po/sv.po | 138 +++++++++++++++++++++++++++---------------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/po/sv.po b/po/sv.po index e36f035bf4bf59..5377b3ece7d6e2 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1300,7 +1300,7 @@ msgstr "fel i deflate (%d)" #, c-format msgid "unable to start '%s' filter" -msgstr "kane inte starta filtret ”%s”" +msgstr "kan inte starta filtret ”%s”" msgid "unable to redirect descriptor" msgstr "kan inte omdirigera handtag" @@ -2812,7 +2812,7 @@ msgstr "kunde inte slå upp HEAD" #, c-format msgid "HEAD (%s) points outside of refs/heads/" -msgstr "HEAD (%s) pekar utenför refs/heads/" +msgstr "HEAD (%s) pekar utanför refs/heads/" #, c-format msgid "branch %s is being rebased at %s" @@ -2828,7 +2828,7 @@ msgstr "HEAD i arbetskatalogen %s har inte uppdaterats" #, c-format msgid "invalid branch name: '%s'" -msgstr "gelaktigt namn på gren: ”%s”" +msgstr "felaktigt namn på gren: ”%s”" #, c-format msgid "no commit on branch '%s' yet" @@ -3301,7 +3301,7 @@ msgid "read commands from stdin" msgstr "läs kommandon från standard in" msgid "with --batch[-check]: ignores stdin, batches all known objects" -msgstr "med --batch[-check]: ignorear standard in, buntar alla kända objekt" +msgstr "med --batch[-check]: ignorerar standard in, buntar alla kända objekt" msgid "Change or optimize batch output" msgstr "Ändra eller optimera buntutdata" @@ -3687,13 +3687,13 @@ msgid "" "one remote, e.g. the 'origin' remote, consider setting\n" "checkout.defaultRemote=origin in your config." msgstr "" -"Om du menade checka ut en spårad fjärrgren på t.ex ”origin”, kan du\n" +"Om du menade checka ut en spårad fjärrgren på t.ex. ”origin”, kan du\n" "göra det genom att ange hela namnet med flaggan --track:\n" "\n" " git checkout --track origin/\n" "\n" "Om du alltid vill att utcheckningar med tvetydiga ska\n" -"föredra en fjärr, t.ex fjärren ”origin” kan du ställa in\n" +"föredra en fjärr, t.ex. fjärren ”origin” kan du ställa in\n" "checkout.defaultRemote=origin i din konfiguration." #, c-format @@ -3799,7 +3799,7 @@ msgid "Cannot switch branch to a non-commit '%s'" msgstr "Kan inte växla gren till icke-incheckningen ”%s”" msgid "missing branch or commit argument" -msgstr "saknar gren- eller incheckingsargument" +msgstr "saknar gren- eller incheckningsargument" #, c-format msgid "unknown conflict style '%s'" @@ -4425,7 +4425,7 @@ msgid "the object directory to store the graph" msgstr "objektkatalogen där grafen ska lagras" msgid "if the commit-graph is split, only verify the tip file" -msgstr "om inchecknignsgrafen är delad, kontrollera bara spetsfilen" +msgstr "om incheckningsgrafen är delad, kontrollera bara spetsfilen" #, c-format msgid "Could not open commit-graph '%s'" @@ -4471,7 +4471,7 @@ msgstr "tillåt skriva en inkrementell incheckningsgraffil" msgid "maximum number of commits in a non-base split commit-graph" msgstr "" -"maximalt antal incheckningar i en delad incheckingsgraf som inte är bad" +"maximalt antal incheckningar i en delad incheckningsgraf som inte är bad" msgid "maximum ratio between two levels of a split commit-graph" msgstr "maximalt förhållande mellan två nivåer av en delad incheckningsgraf" @@ -4480,7 +4480,7 @@ msgid "only expire files older than a given date-time" msgstr "låt tid endast gå ut för filer äldre än givet datum och tid" msgid "maximum number of changed-path Bloom filters to compute" -msgstr "maximalt antal Bloom-filer med ändrad sökväg att beräkna" +msgstr "maximalt antal Bloom-filter med ändrad sökväg att beräkna" msgid "use at most one of --reachable, --stdin-commits, or --stdin-packs" msgstr "använd som mest en av --reachable, --stdin-commits och --stdin-packs" @@ -5426,7 +5426,7 @@ msgstr "”credential-cache” ej tillgänglig; stöd för unix-uttag saknas" #, c-format msgid "unable to get credential storage lock in %d ms" -msgstr "kan inte erhålla låset för lagring av inlogginsuppgifter på %d ms" +msgstr "kan inte erhålla låset för lagring av inloggningsuppgifter på %d ms" msgid "" "git describe [--all] [--tags] [--contains] [--abbrev=] [...]" @@ -5782,7 +5782,7 @@ msgid "Expected 'to' command, got %s" msgstr "Förväntade ”to”-kommando, fick %s" msgid "Expected format name:filename for submodule rewrite option" -msgstr "Förvändae formatet namn:filnamn för undermodul-omskrivningsflaggan" +msgstr "Förväntade formatet namn:filnamn för undermodul-omskrivningsflaggan" #, c-format msgid "feature '%s' forbidden in input without --allow-unsafe-features" @@ -6093,7 +6093,7 @@ msgid "check for forced-updates on all updated branches" msgstr "se efter tvingade uppdateringar i alla uppdaterade grenar" msgid "write the commit-graph after fetching" -msgstr "skriv incheckingsgrafen efter hämtning" +msgstr "skriv incheckningsgrafen efter hämtning" msgid "accept refspecs from stdin" msgstr "ta emot referenser från standard in" @@ -6141,7 +6141,7 @@ msgid "--atomic can only be used when fetching from one remote" msgstr "--atomic kan bara användas vid hämtning från en fjärr" msgid "--stdin can only be used when fetching from one remote" -msgstr "--stdin kan bara användas vid hämtning fårn en fjärr" +msgstr "--stdin kan bara användas vid hämtning från en fjärr" msgid "" "git fmt-merge-msg [-m ] [--log[=] | --no-log] [--file ]" @@ -6438,7 +6438,7 @@ msgid "report root nodes" msgstr "rapportera rotnoder" msgid "make index objects head nodes" -msgstr "gör indexojekt till huvudnoder" +msgstr "gör indexobjekt till huvudnoder" msgid "make reflogs head nodes (default)" msgstr "gör refloggar till huvudnoder (standard)" @@ -7732,7 +7732,7 @@ msgid "cover-from-description-mode" msgstr "cover-from-description-läge" msgid "generate parts of a cover letter based on a branch's description" -msgstr "skapa delar av omslagsbrevet baserat på grenbeskrivelsen" +msgstr "skapa delar av omslagsbrevet baserat på grenbeskrivningen" msgid "use branch description from file" msgstr "använd grenbeskrivningar från fil" @@ -7974,7 +7974,7 @@ msgid "show debugging data" msgstr "visa felsökningsutdata" msgid "suppress duplicate entries" -msgstr "undertyck dublettposter" +msgstr "undertryck dublettposter" msgid "show sparse directories in the presence of a sparse index" msgstr "visa glesa kataloger när et glest index existerar" @@ -8066,7 +8066,7 @@ msgid "keep subject" msgstr "behåll ärenderad" msgid "keep non patch brackets in subject" -msgstr "behåll hakparanterser som inte är ”patch” i ärenderaden" +msgstr "behåll hakparenteser som inte är ”patch” i ärenderaden" msgid "copy Message-ID to the end of commit message" msgstr "kopiera Message-ID till slutet av incheckningsmeddelandet" @@ -8221,7 +8221,7 @@ msgid "git merge-tree [--write-tree] [] " msgstr "git merge-tree [--write-tree] [] " msgid "git merge-tree [--trivial-merge] " -msgstr "git merge-tree [--trivial-merge] " +msgstr "git merge-tree [--trivial-merge] " msgid "do a real merge instead of a trivial merge" msgstr "gör en riktig sammanslagning istället för en enkel sammanslagning" @@ -9244,7 +9244,7 @@ msgstr "kan inte öppna paketfilen" #, c-format msgid "loose object at %s could not be examined" -msgstr "lösa objekt på %s kunde inte underökas" +msgstr "lösa objekt på %s kunde inte undersökas" msgid "unable to force loose object" msgstr "kan inte tvinga lösa objekt" @@ -9693,7 +9693,7 @@ msgid "Need to specify how to reconcile divergent branches." msgstr "Måste ange hur avvikande grenar skall förlikas." msgid "cannot rebase with locally recorded submodule modifications" -msgstr "kan inte ombasera med lokalt lagrade ändringar i undermoful" +msgstr "kan inte ombasera med lokalt lagrade ändringar i undermodul" msgid "git push [] [ [...]]" msgstr "git push [] [ [...]]" @@ -10211,7 +10211,7 @@ msgid "empty exec command" msgstr "tomt exec-kommando" msgid "rebase onto given branch instead of upstream" -msgstr "ombasera mot given grenen istället för uppström" +msgstr "ombasera mot given gren istället för uppström" msgid "use the merge-base of upstream and branch as the current base" msgstr "använd sammanslagningsbasen mellan uppströms och gren som aktuell bas" @@ -10603,7 +10603,7 @@ msgid "process the reflogs of all references" msgstr "hantera referensloggar för alla referenser" msgid "limits processing to reflogs from the current worktree only" -msgstr "begränsar hantering av referensloggar till endst aktuell arbetskatalog" +msgstr "begränsar hantering av referensloggar till endast aktuell arbetskatalog" #, c-format msgid "Marking reachable objects..." @@ -11866,7 +11866,7 @@ msgstr "Ingen sökvägsangivelse gavs. Vilka filer ska jag ta bort?" msgid "please stage your changes to .gitmodules or stash them to proceed" msgstr "" -"löa dina ändringar i .gitmodules eller använd ”stash” för att fortsätta" +"köa dina ändringar i .gitmodules eller använd ”stash” för att fortsätta" #, c-format msgid "not removing '%s' recursively without -r" @@ -11940,7 +11940,7 @@ msgid "linewrap output" msgstr "radbryt utdata" msgid "field" -msgstr "föt" +msgstr "fält" msgid "group by field" msgstr "gruppera efter fält" @@ -12101,7 +12101,7 @@ msgid "stricter reference checking, requires exact ref path" msgstr "striktare referenskontroll, kräver exakt referenssökväg" msgid "show the HEAD reference, even if it would be filtered out" -msgstr "visa HEAD-refrens, även när den skulle filtreras ut" +msgstr "visa HEAD-referens, även när den skulle filtreras ut" msgid "dereference tags into object IDs" msgstr "avreferera taggar till objekt-id" @@ -12158,7 +12158,7 @@ msgid "initialize the sparse-checkout in cone mode" msgstr "initiera sparse-checkout i konläge" msgid "toggle the use of a sparse index" -msgstr "slå på/av använding av glest index" +msgstr "slå på/av användning av glest index" #, c-format msgid "unable to create leading directories of %s" @@ -12329,7 +12329,7 @@ msgstr "Inga ”stash”-poster hittades." #, c-format msgid "%s is not a valid reference" -msgstr "%s är inte en giltigt referens" +msgstr "%s är inte en giltig referens" msgid "git stash clear with arguments is unimplemented" msgstr "”git stash clear” med argument har inte implementerats" @@ -12580,7 +12580,7 @@ msgstr "" "HEAD" msgid "git submodule status [--quiet] [--cached] [--recursive] [...]" -msgstr "git submodule status [--quitet] [--cached] [--recursive] [...]" +msgstr "git submodule status [--quiet] [--cached] [--recursive] [...]" #, c-format msgid "* %s %s(blob)->%s(submodule)" @@ -12603,7 +12603,7 @@ msgid "unexpected mode %o" msgstr "okänt läge %o" msgid "use the commit stored in the index instead of the submodule HEAD" -msgstr "använd incechkning lagrad i indexet istället för undermodulens HEAD" +msgstr "använd incheckning lagrad i indexet istället för undermodulens HEAD" msgid "compare the commit in the index with that in the submodule HEAD" msgstr "jämför incheckningen i indexet med den i undermodulens HEAD" @@ -13884,7 +13884,7 @@ msgstr "misslyckades lagra maximal skaparsymbol" #, c-format msgid "unrecognized bundle mode from URI '%s'" -msgstr "okänt buntlägre från URI:en ”%s”" +msgstr "okänt buntläge från URI:en ”%s”" #, c-format msgid "exceeded bundle URI recursion limit (%d)" @@ -14233,7 +14233,7 @@ msgid "Send a collection of patches from stdin to an IMAP folder" msgstr "Sänd en samling patchar från stdin till en IMAP-mapp" msgid "Build pack index file for an existing packed archive" -msgstr "SKapa pack-indexfiler för ett befintligt packat arkiv" +msgstr "Skapa pack-indexfiler för ett befintligt packat arkiv" msgid "Create an empty Git repository or reinitialize an existing one" msgstr "Skapa tomt Git-arkiv eller ominitiera ett befintligt" @@ -14455,7 +14455,7 @@ msgid "Creates a temporary file with a blob's contents" msgstr "Skapar temporära filer med innehållet från en blob" msgid "Unpack objects from a packed archive" -msgstr "Packa upp objekt från ett pakat arkiv" +msgstr "Packa upp objekt från ett packat arkiv" msgid "Register file contents in the working tree to the index" msgstr "Registrera filinnehållet från arbetskatalogen i indexet" @@ -14512,7 +14512,7 @@ msgid "Git for CVS users" msgstr "Git för CVS-användare" msgid "Tweaking diff output" -msgstr "Justrea diff-utdata" +msgstr "Justera diff-utdata" msgid "A useful minimum set of commands for Everyday Git" msgstr "Ett användbart minsta uppsättning kommandon för vardags-Git" @@ -14620,7 +14620,7 @@ msgid "commit-graph generations chunk is wrong size" msgstr "incheckningsgrafens generationsstycke har fel storlek" msgid "commit-graph changed-path index chunk is too small" -msgstr "incheckningsgrafens ändrade-sökvägar-indexstycke är förö litet" +msgstr "incheckningsgrafens ändrade-sökvägar-indexstycke är för litet" #, c-format msgid "" @@ -14684,10 +14684,10 @@ msgstr "incheckningsgrafens kedjefil är för liten" #, c-format msgid "invalid commit-graph chain: line '%s' not a hash" -msgstr "ogiltig incheckingsgrafkedja: rad ”%s” är inte ett hash-värde" +msgstr "ogiltig incheckningsgrafkedja: rad ”%s” är inte ett hash-värde" msgid "unable to find all commit-graph files" -msgstr "kan inte hitta alla incheckingsgraffiler" +msgstr "kan inte hitta alla incheckningsgraffiler" msgid "invalid commit position. commit-graph is likely corrupt" msgstr "ogiltig incheckningsposition. incheckningsgrafen är troligtvis trasig" @@ -14729,8 +14729,8 @@ msgstr "Samlar refererade incheckningar" #, c-format msgid "Finding commits for commit graph in % pack" msgid_plural "Finding commits for commit graph in % packs" -msgstr[0] "Söker incheckningar för incheckingsgraf i % paket" -msgstr[1] "Söker incheckningar för incheckingsgraf i % paket" +msgstr[0] "Söker incheckningar för incheckningsgraf i % paket" +msgstr[1] "Söker incheckningar för incheckningsgraf i % paket" #, c-format msgid "error adding pack %s" @@ -14741,10 +14741,10 @@ msgid "error opening index for %s" msgstr "fel vid öppning av indexet för %s" msgid "Finding commits for commit graph among packed objects" -msgstr "Söker incheckningar för incheckingsgraf i packade objekt" +msgstr "Söker incheckningar för incheckningsgraf i packade objekt" msgid "Finding extra edges in commit graph" -msgstr "Söker ytterligare kanter i incheckingsgraf" +msgstr "Söker ytterligare kanter i incheckningsgraf" msgid "failed to write correct number of base graph ids" msgstr "misslyckades skriva korrekt antal bas-graf-id:n" @@ -14766,7 +14766,7 @@ msgid "unable to open commit-graph chain file" msgstr "kan inte öppna incheckningsgrafkedjefilen" msgid "failed to rename base commit-graph file" -msgstr "misslyckades byta namn på bas-incheckingsgraffilen" +msgstr "misslyckades byta namn på bas-incheckningsgraffilen" msgid "failed to rename temporary commit-graph file" msgstr "misslyckades byta namn på temporär incheckningsgraffil" @@ -15335,7 +15335,7 @@ msgstr "referensen ”%s” pekar inte på en blob" #, c-format msgid "unable to resolve config blob '%s'" -msgstr "kan inte slå upp konfigurerings-bloben ”%s”" +msgstr "kan inte slå upp konfigurerings-blobben ”%s”" msgid "unable to parse command-line config" msgstr "kan inte tolka kommandoradskonfiguration" @@ -15697,7 +15697,7 @@ msgstr "url saknar protokoll: %s" #, c-format msgid "credential url cannot be parsed: %s" -msgstr "kan inte tolka url för inloggingsuppgifter: %s" +msgstr "kan inte tolka url för inloggningsuppgifter: %s" #, c-format msgid "invalid timeout '%s', expecting a non-negative integer" @@ -16102,7 +16102,7 @@ msgid "use default prefixes a/ and b/" msgstr "använd standardprefixen a/ och b/" msgid "show context between diff hunks up to the specified number of lines" -msgstr "visa sammnhang mellan diff-stycken upp till angivet antal rader" +msgstr "visa sammanhang mellan diff-stycken upp till angivet antal rader" msgid "" msgstr "" @@ -16622,7 +16622,7 @@ msgstr "Servern tillåter inte förfrågan om ej tillkännagivet objekt %s" #, c-format msgid "fsmonitor_ipc__send_query: invalid path '%s'" -msgstr "fsmonitor_ipc__send_query: ogilitg sökväg ”%s”" +msgstr "fsmonitor_ipc__send_query: ogiltig sökväg ”%s”" #, c-format msgid "fsmonitor_ipc__send_query: unspecified error on '%s'" @@ -16896,7 +16896,7 @@ msgid "Interacting with Others" msgstr "Interaktion med andra" msgid "Low-level Commands / Manipulators" -msgstr "Lågnivåkommandon / maniupulerare" +msgstr "Lågnivåkommandon / manipulerare" msgid "Low-level Commands / Interrogators" msgstr "Lågnivåkommandon / frågare" @@ -16936,7 +16936,7 @@ msgid "External commands" msgstr "Externa kommandon" msgid "Command aliases" -msgstr "Kommadoalias" +msgstr "Kommandoalias" msgid "See 'git help ' to read about a specific subcommand" msgstr "Se ”git help ” för att läsa om ett specifikt underkommando" @@ -17279,7 +17279,7 @@ msgid "" "CONFLICT (implicit dir rename): Existing file/dir at %s in the way of " "implicit directory rename(s) putting the following path(s) there: %s." msgstr "" -"KONFLIKT (implicit nämnändrad kat): Befintlig fil/kat vid %s är i vägen för " +"KONFLIKT (implicit namnändrad kat): Befintlig fil/kat vid %s är i vägen för " "implicit namnändrad(e) katalog(er) som lägger dit följande sökväg(ar): %s." #, c-format @@ -17441,7 +17441,7 @@ msgid "" " - resolve any other conflicts in the superproject\n" " - commit the resulting index in the superproject\n" msgstr "" -"Rekursiv sammanslaning med undermoduler stöder för närvarande endast enkla " +"Rekursiv sammanslagning med undermoduler stöder för närvarande endast enkla " "fall.\n" "Du måste hantera sammanslagning av undermoduler i konflikt manuellt.\n" "Detta kan göras genom att utföra följande steg:\n" @@ -18174,7 +18174,7 @@ msgstr "kan inte utföra ”deflate” på nytt strömobjekt (%d)" #, c-format msgid "deflateEnd on stream object failed (%d)" -msgstr "”deflatEend” på strömobjektet misslyckades (%d)" +msgstr "”deflateEnd” på strömobjektet misslyckades (%d)" #, c-format msgid "unable to create directory %s" @@ -18444,7 +18444,7 @@ msgid "" "corrupted bitmap index file (too short to fit pseudo-merge table header)" msgstr "" "trasig bitkarteindexfil (för kort för att få plats för pseudo-" -"sammanslagningsatbellhuvudet)" +"sammanslagningstabellhuvudet)" msgid "corrupted bitmap index file (too short to fit pseudo-merge table)" msgstr "" @@ -18538,7 +18538,7 @@ msgstr "bitkarteresultat stämmer inte överens" #, c-format msgid "pseudo-merge index out of range (% >= %)" -msgstr "pseudosammanslaningsindex utenför intervallet (% ≥ %)" +msgstr "pseudosammanslagningsindex utanför intervallet (% ≥ %)" #, c-format msgid "could not find '%s' in pack '%s' at offset %" @@ -18633,7 +18633,7 @@ msgstr "offset före slutet av packindex för %s (trasigt index?)" #, c-format msgid "offset beyond end of pack index for %s (truncated index?)" -msgstr "offset borton slutet av packindex för %s (trunkerat index?)" +msgstr "offset bortom slutet av packindex för %s (trunkerat index?)" #, c-format msgid "malformed expiration date '%s'" @@ -19009,7 +19009,7 @@ msgid "" "could not parse first line of `log` output: did not start with 'commit ': " "'%s'" msgstr "" -"kunde inte tolka första raden i ”log”-updata: börjar inte med ”commit ”: ”%s”" +"kunde inte tolka första raden i ”log”-utdata: börjar inte med ”commit ”: ”%s”" #, c-format msgid "could not parse git header '%.*s'" @@ -19124,7 +19124,7 @@ msgstr "%s: öppning av indexfilen misslyckades" #, c-format msgid "%s: cannot stat the open index" -msgstr "%s: kan inte ta startus på det öppna indexet" +msgstr "%s: kan inte ta status på det öppna indexet" #, c-format msgid "%s: index file smaller than expected" @@ -19393,7 +19393,7 @@ msgstr "kan inte helt tolka %s=%s" #, c-format msgid "value expected %s=" -msgstr "vädre förväntades %s=" +msgstr "värde förväntades %s=" #, c-format msgid "positive value expected '%s' in %%(%s)" @@ -19932,7 +19932,7 @@ msgid "" "\n" "Neither worked, so we gave up. You must fully qualify the ref." msgstr "" -"Målet du angav är inte ett komplett referensamn (dvs.,\n" +"Målet du angav är inte ett komplett referensnamn (dvs.,\n" "startar med ”refs/”). Vi försökte gissa vad du menade genom att:\n" "\n" "- Se efter en referens som motsvarar ”%s” på fjärrsidan.\n" @@ -20190,7 +20190,7 @@ msgstr "misslyckades hitta trädet för %s." #, c-format msgid "unsupported section for hidden refs: %s" -msgstr "sktionen för dolda referenser stöds ej: %s" +msgstr "sektionen för dolda referenser stöds ej: %s" msgid "--exclude-hidden= passed more than once" msgstr "--exclude-hidden= angavs mer än en gång" @@ -20473,7 +20473,7 @@ msgid "" "not sending a push certificate since the receiving end does not support --" "signed push" msgstr "" -"sänder inte push-certifikat eftersom mottagarsidan inte stlder push med --" +"sänder inte push-certifikat eftersom mottagarsidan inte stöder push med --" "signed" msgid "the receiving end does not support --atomic push" @@ -20833,7 +20833,7 @@ msgstr "”%s” är inte ett giltigt referensnamn" #, c-format msgid "update-ref requires a fully qualified refname e.g. refs/heads/%s" -msgstr "update-ref kräver ett fullständigt referensnamn, t.ex refs/heads/%s" +msgstr "update-ref kräver ett fullständigt referensnamn, t.ex. refs/heads/%s" #, c-format msgid "'%s' does not accept merge commits" @@ -21841,7 +21841,7 @@ msgstr "" "[]" msgid "name or pathname of unix domain socket" -msgstr "namn eller sövkäg till unixdomän-uttag" +msgstr "namn eller sökväg till unixdomän-uttag" msgid "named-pipe name" msgstr "namn på namngivet rör" @@ -21943,10 +21943,10 @@ msgstr "kunde inte läsa referensen %s" #, c-format msgid "unknown response to connect: %s" -msgstr "okänt svar på ansluntning: %s" +msgstr "okänt svar på anslutning: %s" msgid "setting remote service path not supported by protocol" -msgstr "protkollet stöder inte att sätta sökväg till fjärrtjänst" +msgstr "protokollet stöder inte att sätta sökväg till fjärrtjänst" msgid "invalid remote service path" msgstr "felaktig sökväg till fjärrtjänst" @@ -21959,7 +21959,7 @@ msgid "--negotiate-only requires protocol v2" msgstr "--negotiate-only kräver protokoll v2" msgid "'option' without a matching 'ok/error' directive" -msgstr "”option” utan mostsvarande ”ok/error”-direktiv" +msgstr "”option” utan motsvarande ”ok/error”-direktiv" #, c-format msgid "expected ok/error, helper said '%s'" @@ -23022,7 +23022,7 @@ msgstr "Kan inte byta katalog till $cdup, toppnivån på arbetskatalogen" #, sh-format msgid "fatal: $program_name cannot be used without a working tree." -msgstr "ödesdigetrt: $program_name kan inte användas utan arbetskatalog." +msgstr "ödesdigert: $program_name kan inte användas utan arbetskatalog." msgid "Cannot rewrite branches: You have unstaged changes." msgstr "Kan inte skriva om grenar: Du har oköade ändringar." @@ -23071,7 +23071,7 @@ msgid "--dump-aliases incompatible with other options\n" msgstr "--dump-aliases är inkompatibelt med andra flaggor\n" msgid "--dump-aliases and --translate-aliases are mutually exclusive\n" -msgstr "--dump-aliases och --translate-aliases är ömsesidigt utelsutande\n" +msgstr "--dump-aliases och --translate-aliases är ömsesidigt uteslutande\n" msgid "" "fatal: found configuration options for 'sendmail'\n" @@ -23112,7 +23112,7 @@ msgstr "varning: ”:include:” stöds inte: %s\n" #, perl-format msgid "warning: `/file` or `|pipe` redirection not supported: %s\n" -msgstr "varning: omdirigering til ”/fil” eller ”|rör” stöds inte: %s\n" +msgstr "varning: omdirigering till ”/fil” eller ”|rör” stöds inte: %s\n" #, perl-format msgid "warning: sendmail line is not recognized: %s\n" @@ -23340,7 +23340,7 @@ msgid "cannot send message as 7bit" msgstr "kan inte sända brev som sjubitars" msgid "invalid transfer encoding" -msgstr "ogiltig överföringskondning" +msgstr "ogiltig överföringskodning" #, perl-format msgid "" From 10fd0e120317de28002e510d6becc049b81b851f Mon Sep 17 00:00:00 2001 From: Arkadii Yakovets Date: Sat, 4 Jan 2025 19:26:33 -0800 Subject: [PATCH 023/306] l10n: uk: v2.48 update Co-authored-by: Kate Golovanova Signed-off-by: Arkadii Yakovets Signed-off-by: Kate Golovanova --- po/uk.po | 599 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 443 insertions(+), 156 deletions(-) diff --git a/po/uk.po b/po/uk.po index 297f7b0687295d..5f68f36c7db024 100644 --- a/po/uk.po +++ b/po/uk.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: Git v2.46\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2023-04-11 09:55-0700\n" -"PO-Revision-Date: 2024-07-24 08:45-0700\n" -"Last-Translator: Arkadii Yakovets \n" +"POT-Creation-Date: 2025-01-04 19:26-0800\n" +"PO-Revision-Date: 2025-01-03 14:17-0800\n" +"Last-Translator: Kateryna Golovanova \n" "Language-Team: Ukrainian \n" "Language: uk\n" "MIME-Version: 1.0\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" -"X-Generator: Poedit 3.4.2\n" +"X-Generator: Poedit 3.5\n" #, c-format msgid "Huh (%s)?" @@ -41,7 +41,7 @@ msgstr "Оновити" #, c-format msgid "could not stage '%s'" -msgstr "не вдалося додати до індексу %s" +msgstr "не вдалося додати до індексу \"%s\"" msgid "could not write index" msgstr "не вдалося записати індекс" @@ -549,7 +549,7 @@ msgid "" "/ - search for a hunk matching the given regex\n" "s - split the current hunk into smaller hunks\n" "e - manually edit the current hunk\n" -"p - print the current hunk\n" +"p - print the current hunk, 'P' to use the pager\n" "? - print help\n" msgstr "" "j - залишити цей шматок невизначеним, перейти до наступного невизначеного " @@ -562,7 +562,7 @@ msgstr "" "/ - шукати шматок, що відповідає заданому регвиру\n" "s - розбити поточний шматок на менші шматки\n" "e - редагувати поточний шматок вручну\n" -"p - показати поточний шматок\n" +"p - показати поточний шматок, \"P\" для гортання сторінок\n" "? - показати довідку\n" #, c-format @@ -634,10 +634,11 @@ msgstr "Змінено лише бінарні файли." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"Вимкнути це повідомлення можна за допомогою \"git config advice.%s false\"" +"Вимкнути це повідомлення можна за допомогою \"git config set advice.%s " +"false\"" #, c-format msgid "%shint:%s%.*s%s\n" @@ -761,8 +762,8 @@ msgstr "" "\n" " git switch -\n" "\n" -"Щоб вимкнути цю пораду, встановіть конфігураційний параметр advice." -"detachedHead у false\n" +"Щоб вимкнути цю пораду, встановіть конфігураційний параметр " +"advice.detachedHead у false\n" #, c-format msgid "" @@ -1255,6 +1256,15 @@ msgstr "" "спробувати тристороннє злиття, повернутися до звичайного латання, якщо це не " "вдасться" +msgid "for conflicts, use our version" +msgstr "у разі конфліктів використовувати нашу версію" + +msgid "for conflicts, use their version" +msgstr "у разі конфліктів використовувати їхню версію" + +msgid "for conflicts, use a union version" +msgstr "у разі конфліктів використовувати обʼєднану версію" + msgid "build a temporary index based on embedded index information" msgstr "створити тимчасовий індекс на основі вбудованої індексної інформації" @@ -1300,6 +1310,9 @@ msgstr "додати <корінь> до всіх назв файлів" msgid "don't return error for empty patches" msgstr "не повертати помилку для порожніх латок" +msgid "--ours, --theirs, and --union require --3way" +msgstr "--ours, --theirs, та --union вимагають --3way" + #, c-format msgid "cannot stream blob %s" msgstr "неможливо транслювати blob %s" @@ -1371,6 +1384,10 @@ msgstr "невірне ім’я об’єкта: %s" msgid "not a tree object: %s" msgstr "не є об’єктом дерева: %s" +#, c-format +msgid "failed to unpack tree object %s" +msgstr "не вдалося розпакувати обʼєкт дерева %s" + #, c-format msgid "File not found: %s" msgstr "Файл не знайдено: %s" @@ -2174,7 +2191,7 @@ msgstr "" #, c-format msgid "Could not parse object '%s'." -msgstr "Не вдалося розібрати об'єкт '%s'." +msgstr "Не вдалося розібрати обʼєкт \"%s\"." msgid "failed to clean index" msgstr "не вдалося очистити індекс" @@ -2484,9 +2501,6 @@ msgstr "" "неприпустимий аргумент %s для \"git bisect terms\".\n" "Підтримувані опції: --term-good|--term-old і --term-bad|--term-new." -msgid "revision walk setup failed\n" -msgstr "не вдалося налаштувати проходження по ревізіям\n" - #, c-format msgid "could not open '%s' for appending" msgstr "не вдалося відкрити \"%s\" для додавання" @@ -3026,8 +3040,8 @@ msgid "HEAD not found below refs/heads!" msgstr "HEAD не знайдено під refs/heads!" msgid "" -"branch with --recurse-submodules can only be used if submodule." -"propagateBranches is enabled" +"branch with --recurse-submodules can only be used if " +"submodule.propagateBranches is enabled" msgstr "" "гілку з --recurse-submodules можна використовувати лише якщо увімкнено " "submodule.propagateBranches" @@ -3478,9 +3492,14 @@ msgstr "git check-mailmap [<опції>] <контакт>..." msgid "also read contacts from stdin" msgstr "також читати контакти з stdin" -#, c-format -msgid "unable to parse contact: %s" -msgstr "не вдалося розібрати контакт: %s" +msgid "read additional mailmap entries from file" +msgstr "зчитувати додаткові записи mailmap з файлу" + +msgid "blob" +msgstr "blob" + +msgid "read additional mailmap entries from blob" +msgstr "зчитувати додаткові записи mailmap з blob" msgid "no contacts specified" msgstr "контакти не вказані" @@ -3648,7 +3667,7 @@ msgstr "Вже на \"%s\"\n" #, c-format msgid "Switched to and reset branch '%s'\n" -msgstr "Переключено на та скинуто гілку '%s'\n" +msgstr "Переключено на та скинуто гілку \"%s\"\n" #, c-format msgid "Switched to a new branch '%s'\n" @@ -3842,19 +3861,23 @@ msgstr "шляхи не можуть використовуватись при #, c-format msgid "'%s' cannot be used with switching branches" -msgstr "'%s' не може використовуватись при переключенні гілок" +msgstr "\"%s\" не може використовуватись при переключенні гілок" + +#, c-format +msgid "'%s' needs the paths to check out" +msgstr "\"%s\" потрібні шляхи для переходу" #, c-format msgid "'%s' cannot be used with '%s'" -msgstr "'%s' не може використовуватись з '%s'" +msgstr "\"%s\" не може використовуватись з \"%s\"" #, c-format msgid "'%s' cannot take " -msgstr "'%s' не може прийняти <стартова-точка>" +msgstr "\"%s\" не може прийняти <стартова-точка>" #, c-format msgid "Cannot switch branch to a non-commit '%s'" -msgstr "Неможливо переключити гілку на не коміт '%s'" +msgstr "Неможливо переключити гілку на не коміт \"%s\"" msgid "missing branch or commit argument" msgstr "відсутня гілка або коміт" @@ -3887,8 +3910,8 @@ msgstr "нова ненароджена гілка" msgid "update ignored files (default)" msgstr "оновити ігноровані файли (за замовчуванням)" -msgid "do not check if another worktree is holding the given ref" -msgstr "не перевіряти, чи інше робоче дерево містить дане посилання" +msgid "do not check if another worktree is using this branch" +msgstr "не перевіряти, чи використовує цю гілку інше робоче дерево" msgid "checkout our version for unmerged files" msgstr "використовувати нашу версію для не злитих файлів" @@ -3919,11 +3942,11 @@ msgstr "неприпустиме зазначення шляху" #, c-format msgid "'%s' is not a commit and a branch '%s' cannot be created from it" -msgstr "'%s' не є комітом, і з нього не можна створити гілку '%s'" +msgstr "\"%s\" не є комітом, і з нього не можна створити гілку \"%s\"" #, c-format msgid "git checkout: --detach does not take a path argument '%s'" -msgstr "git checkout: --detach не приймає аргумент шляху '%s'" +msgstr "git checkout: --detach не приймає аргумент шляху \"%s\"" msgid "" "git checkout: --ours/--theirs, --force and --merge are incompatible when\n" @@ -4191,11 +4214,11 @@ msgstr "створити неглибокий клон вказаної глиб msgid "create a shallow clone since a specific time" msgstr "створити неглибокий клон з певного часу" -msgid "revision" -msgstr "ревізія" +msgid "ref" +msgstr "посилання" -msgid "deepen history of shallow clone, excluding rev" -msgstr "поглибити історію неглибокого клону, за винятком ревізії" +msgid "deepen history of shallow clone, excluding ref" +msgstr "поглибити історію неглибокого клону, за винятком посилання" msgid "clone only one branch, HEAD or --branch" msgstr "клонувати лише одну гілку, HEAD або --branch" @@ -4428,7 +4451,7 @@ msgstr "не вдалося ініціалізувати сховище, URI п #, c-format msgid "failed to fetch objects from bundle URI '%s'" -msgstr "не вдалося отримати обʼєкти з пакунка URI '%s'" +msgstr "не вдалося отримати обʼєкти з пакунка URI \"%s\"" msgid "failed to fetch advertised bundles" msgstr "не вдалося отримати обіцяні пакунки" @@ -4618,7 +4641,7 @@ msgstr "git commit-tree: не вдалося прочитати" msgid "" "git commit [-a | --interactive | --patch] [-s] [-v] [-u] [--amend]\n" " [--dry-run] [(-c | -C | --squash) | --fixup [(amend|" -"reword):])]\n" +"reword):]]\n" " [-F | -m ] [--reset-author] [--allow-empty]\n" " [--allow-empty-message] [--no-verify] [-e] [--author=]\n" " [--date=] [--cleanup=] [--[no-]status]\n" @@ -4628,14 +4651,14 @@ msgid "" msgstr "" "git commit [-a | --interactive | --patch] [-s] [-v] [-u<режим>] [--amend]\n" " [--dry-run] [(-c | -C | --squash) <коміт> | --fixup [(amend|" -"reword):]<коміт>)]\n" +"reword):]<коміт>]\n" " [-F <файл> | -m <допис>] [--reset-author] [--allow-empty]\n" " [--allow-empty-message] [--no-verify] [-e] [--author=<автор>]\n" " [--date=<дата>] [--cleanup=<режим>] [--[no-]status]\n" " [-i | -o] [--pathspec-from-file=<файл> [--pathspec-file-nul]]\n" -" [(--trailer <токен>[(=|:)<значення>])...] [-S[<ідентифікатор " +" [(--trailer <токен>[(=|:)<значення>])...] [-S[<ідентифікатор-" "ключа>]]\n" -" [--] [<визначник шляху>...]" +" [--] [<визначник-шляху>...]" msgid "git status [] [--] [...]" msgstr "git status [<опції>] [--] [<визначник шляху>...]" @@ -5121,12 +5144,11 @@ msgstr "git config list [<опція-файлу>] [<опція-відображ msgid "" "git config get [] [] [--includes] [--all] [--" -"regexp=] [--value=] [--fixed-value] [--default=] " -"" +"regexp] [--value=] [--fixed-value] [--default=] " msgstr "" "git config get [<опція-файлу>] [<опція-відображення>] [--includes] [--all] " -"[--regexp=<регвир>] [--value=<значення>] [--fixed-value] [--" -"default=<значення -за-умовчанням>] <назва>" +"[--regexp] [--value=<значення>] [--fixed-value] [--default=<за " +"замовчуванням>] <назва>" msgid "" "git config set [] [--type=] [--all] [--value=] [--" @@ -5137,10 +5159,10 @@ msgstr "" msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" "git config unset [<опція-файлу>] [--all] [--value=<значення>] [--fixed-" -"value] <назва> <значення>" +"value] <назва>" msgid "git config rename-section [] " msgstr "git config rename-section [<опція-файлу>] <стара-назва> <нова-назва>" @@ -5154,6 +5176,15 @@ msgstr "git config edit [<опція-файлу>]" msgid "git config [] --get-colorbool []" msgstr "git config [<опція-файлу>] --get-colorbool <назва> []" +msgid "" +"git config get [] [] [--includes] [--all] [--" +"regexp=] [--value=] [--fixed-value] [--default=] " +"" +msgstr "" +"git config get [<опція-файлу>] [<опція-відображення>] [--includes] [--all] " +"[--regexp=<регвир>] [--value=<значення>] [--fixed-value] [--default=<за-" +"замовчуванням>] <назва>" + msgid "" "git config set [] [--type=] [--comment=] [--all] " "[--value=] [--fixed-value] " @@ -5574,12 +5605,8 @@ msgid "traversed %lu commits\n" msgstr "пройдено через %lu комітів\n" #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"знайдено більше %i тегів; показані %i останніх\n" -"припинено пошук на %s\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "знайдено %i тегів; припинено пошук на %s\n" #, c-format msgid "describe %s\n" @@ -5964,13 +5991,13 @@ msgid "" "to avoid this check\n" msgstr "" "перевірка примусових оновлень зайняла %.2f секунд; ви можете скористатися\n" -"\"--no-show-forced-updates\" або виконати \"git config fetch." -"showForcedUpdates false\"\n" +"\"--no-show-forced-updates\" або виконати \"git config " +"fetch.showForcedUpdates false\"\n" "щоб уникнути цієї перевірки\n" #, c-format -msgid "%s did not send all necessary objects\n" -msgstr "%s не надіслав всіх необхідних обʼєктів\n" +msgid "%s did not send all necessary objects" +msgstr "%s не надіслав усі необхідні обʼєкти" #, c-format msgid "rejected %s because shallow roots are not allowed to be updated" @@ -6007,8 +6034,8 @@ msgid "option \"%s\" value \"%s\" is not valid for %s" msgstr "значення \"%s\" опції \"%s\" неприпустиме для %s" #, c-format -msgid "option \"%s\" is ignored for %s\n" -msgstr "опція \"%s\" ігнорується для %s\n" +msgid "option \"%s\" is ignored for %s" +msgstr "опція \"%s\" ігнорується для %s" #, c-format msgid "%s is not a valid object" @@ -6018,6 +6045,21 @@ msgstr "%s не є припустимим об’єктом" msgid "the object %s does not exist" msgstr "об’єкт %s не існує" +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Запустіть \"git remote set-head %s %s\", щоб відстежити зміни, або " +"встановіть\n" +"\"remote.%s.followRemoteHEAD\" параметр конфігурації на інше значення\n" +"якщо ви не хочете бачити це повідомлення. Зокрема, виконання команди\n" +"\"git config set remote.%s.followRemoteHEAD %s\" вимкне попередження\n" +"доки віддалений сервер не змінить HEAD на щось інше." + msgid "multiple branches detected, incompatible with --set-upstream" msgstr "виявлено кілька гілок, несумісних з --set-upstream" @@ -6158,6 +6200,9 @@ msgstr "refmap" msgid "specify fetch refmap" msgstr "вказати мапу посилань для fetch" +msgid "revision" +msgstr "ревізія" + msgid "report that we have only objects reachable from this object" msgstr "звітувати, що у нас є тільки обʼєкти, доступні з цього обʼєкта" @@ -6212,8 +6257,8 @@ msgid "protocol does not support --negotiate-only, exiting" msgstr "протокол не підтримує --negotiate-only, вихід" msgid "" -"--filter can only be used with the remote configured in extensions." -"partialclone" +"--filter can only be used with the remote configured in " +"extensions.partialclone" msgstr "" "--filter можна використовувати лише з віддаленим призначенням, налаштованим " "у extensions.partialclone" @@ -6686,6 +6731,9 @@ msgstr "працювати ретельніше (збільшує час вик msgid "enable auto-gc mode" msgstr "увімкнути режим автоматичного збору сміття" +msgid "perform garbage collection in the background" +msgstr "виконувати прибирання сміття у фоновому режимі" + msgid "force running gc even if there may be another gc running" msgstr "примусово запускати збирач сміття, навіть якщо інший збирач вже працює" @@ -6786,6 +6834,9 @@ msgstr "завдання \"%s\" не можна вибрати кілька ра msgid "run tasks based on the state of the repository" msgstr "запускати завдання на основі стану сховища" +msgid "perform maintenance in the background" +msgstr "виконувати технічне обслуговування у фоновому режимі" + msgid "frequency" msgstr "частота" @@ -6883,8 +6934,26 @@ msgstr "недоступні ні systemd таймери, ні crontab" msgid "%s scheduler is not available" msgstr "%s планувальник недоступний" -msgid "another process is scheduling background maintenance" -msgstr "ще один процес планує фонове обслуговування" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"не вдалося створити \"%s.lock\": %s.\n" +"\n" +"Здається, у цьому сховищі запущено ще один запланований процес git-" +"maintenance(1). Будь ласка, переконайтеся, що у сховищі не запущено інших " +"процесів обслуговування, і\n" +"і спробуйте ще раз. Якщо все одно не вдасться, можливо, \n" +"раніше у цьому сховищі аварійно завершився git-maintenance(1) процес: " +"видаліть файл вручну, щоб продовжити роботу." + +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "не може отримати блокування для планового фонового обслуговування" msgid "git maintenance start [--scheduler=]" msgstr "git maintenance start [--scheduler=<планувальник>]" @@ -7418,19 +7487,19 @@ msgstr "локальний обʼєкт %s пошкоджено" #, c-format msgid "packfile name '%s' does not end with '.%s'" -msgstr "ім’я файла пакунка '%s' не закінчується на '.%s'" +msgstr "ім’я файла пакунка \"%s\" не закінчується на '.%s'" #, c-format msgid "cannot write %s file '%s'" -msgstr "неможливо записати %s файл '%s'" +msgstr "неможливо записати %s файл \"%s\"" #, c-format msgid "cannot close written %s file '%s'" -msgstr "неможливо закрити записаний %s файл '%s'" +msgstr "неможливо закрити записаний %s файл \"%s\"" #, c-format msgid "unable to rename temporary '*.%s' file to '%s'" -msgstr "не вдається перейменувати тимчасовий файл '*.%s' на '%s'" +msgstr "не вдається перейменувати тимчасовий файл \"*.%s\" на \"%s\"" msgid "error while closing pack file" msgstr "помилка під час закриття файлу пакунка" @@ -7441,11 +7510,11 @@ msgstr "невірний pack.indexVersion=%" #, c-format msgid "Cannot open existing pack file '%s'" -msgstr "Неможливо відкрити існуючий файл пакунка '%s" +msgstr "Неможливо відкрити існуючий файл пакунка \"%s\"" #, c-format msgid "Cannot open existing pack idx file for '%s'" -msgstr "Неможливо відкрити існуючий індексний файл пакунка для '%s" +msgstr "Неможливо відкрити існуючий індексний файл пакунка для \"%s\"" #, c-format msgid "non delta: %d object" @@ -7461,6 +7530,20 @@ msgstr[0] "довжина ланцюжка = %d: %lu об’єкт" msgstr[1] "довжина ланцюжка = %d: %lu об’єкти" msgstr[2] "довжина ланцюжка = %d: %lu об’єктів" +msgid "could not start pack-objects to repack local links" +msgstr "не вдалося розпочати pack-objects для перепакування локальних посилань" + +msgid "failed to feed local object to pack-objects" +msgstr "не вдалося передати локальний обʼєкт до pack-objects" + +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack: очікуються повні рядки шістнадцяткових ідентифікаторів обʼєктів " +"тільки від pack-objects." + +msgid "could not finish pack-objects to repack local links" +msgstr "не вдалося завершити pack-objects для перепакування локальних посилань" + msgid "Cannot come back to cwd" msgstr "Неможливо повернутися до поточної робочої директорії" @@ -7470,7 +7553,10 @@ msgstr "невірний %s" #, c-format msgid "unknown hash algorithm '%s'" -msgstr "невідомий хеш-алгоритм '%s'" +msgstr "невідомий хеш-алгоритм \"%s\"" + +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor не можна використовувати з назвою пакунка" msgid "--stdin requires a git repository" msgstr "--stdin потребує наявності git сховища" @@ -7656,9 +7742,6 @@ msgstr "-L<діапазон>:<файл> не можна використовув msgid "Final output: %d %s\n" msgstr "Кінцевий результат: %d %s\n" -msgid "unable to create temporary object directory" -msgstr "не вдалося створити тимчасову директорію об’єкта" - #, c-format msgid "git show %s: bad file" msgstr "git show %s: невірний файл" @@ -8231,15 +8314,6 @@ msgstr "використовувати злиття на основі diff3" msgid "use a zealous diff3 based merge" msgstr "використовувати ретельне злиття на основі diff3" -msgid "for conflicts, use our version" -msgstr "у разі конфліктів використовувати нашу версію" - -msgid "for conflicts, use their version" -msgstr "у разі конфліктів використовувати їхню версію" - -msgid "for conflicts, use a union version" -msgstr "у разі конфліктів використовувати об’єднану версію" - msgid "" msgstr "<алгоритм>" @@ -8709,6 +8783,9 @@ msgstr "" msgid "write multi-pack bitmap" msgstr "записати multi-pack bitmap" +msgid "write a new incremental MIDX" +msgstr "записати новий інкрементний MIDX" + msgid "write multi-pack index containing only given indexes" msgstr "записати multi-pack індекс, що містить лише задані індекси" @@ -8843,11 +8920,11 @@ msgstr "git notes [--ref <посилання-нотатки>] [list [<об’є msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref <посилання-нотатки>] add [-f] [--allow-empty] [--" "[no-]separator|--separator=<розділювач-абзаців>] [--[no-]stripspace] [-m " -"<допис> | -F <файл> | (-c | -C) <обʼєкт>] [<обʼєкт>]" +"<допис> | -F <файл> | (-c | -C) <обʼєкт>] [<обʼєкт>] [-e]" msgid "git notes [--ref ] copy [-f] " msgstr "" @@ -8856,11 +8933,11 @@ msgstr "" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref <посилання-нотатки>] append [--allow-empty] [--" "[no-]separator|--separator=<розділювач-абзаців>] [--[no-]stripspace] [-m " -"<допис> | -F <файл> | (-c | -C) <обʼєкт>] [<обʼєкт>]" +"<допис> | -F <файл> | (-c | -C) <обʼєкт>] [<обʼєкт>] [-e]" msgid "git notes [--ref ] edit [--allow-empty] []" msgstr "git notes [--ref <посилання-нотатки>] edit [--allow-empty] [<об’єкт>]" @@ -8979,6 +9056,9 @@ msgstr "вміст нотатки у файлі" msgid "reuse and edit specified note object" msgstr "повторно використати та редагувати вказаний обʼєкт нотатки" +msgid "edit note message in editor" +msgstr "редагувати допис нотатки в редакторі" + msgid "reuse specified note object" msgstr "повторно використати вказаний обʼєкт нотатки" @@ -9195,8 +9275,8 @@ msgstr "очікувався обʼєкт на зміщенні % па msgid "disabling bitmap writing, packs are split due to pack.packSizeLimit" msgstr "" -"вимкнення bitmap запису, пакунки розбиваються на частини через pack." -"packSizeLimit" +"вимкнення bitmap запису, пакунки розбиваються на частини через " +"pack.packSizeLimit" msgid "Writing objects" msgstr "Запис обʼєктів" @@ -9478,6 +9558,9 @@ msgstr "обробка для відсутніх обʼєктів" msgid "do not pack objects in promisor packfiles" msgstr "не пакувати обʼєкти у promisor пакунки" +msgid "implies --missing=allow-any" +msgstr "мається на увазі --missing=allow-any" + msgid "respect islands during delta compression" msgstr "поважати острови під час дельта компресії" @@ -9974,7 +10057,7 @@ msgstr "Надсилання до %s\n" #, c-format msgid "failed to push some refs to '%s'" -msgstr "не вдалося надіслати деякі посилання до '%s'" +msgstr "не вдалося надіслати деякі посилання до \"%s\"" msgid "" "recursing into submodule with push.recurseSubmodules=only; using on-demand " @@ -9985,7 +10068,7 @@ msgstr "" #, c-format msgid "invalid value for '%s'" -msgstr "неприпустиме значення для '%s'" +msgstr "неприпустиме значення для \"%s\"" msgid "repository" msgstr "сховище" @@ -10746,6 +10829,9 @@ msgstr "неприпустимий формат посилання: %s" msgid "git refs migrate --ref-format= [--dry-run]" msgstr "git refs migrate --ref-format=<формат> [--dry-run]" +msgid "git refs verify [--strict] [--verbose]" +msgstr "git refs verify [--strict] [--verbose]" + msgid "specify the reference format to convert to" msgstr "вкажіть формат посилання, в який потрібно конвертувати" @@ -10759,6 +10845,12 @@ msgstr "відсутній --ref-format=<формат>" msgid "repository already uses '%s' format" msgstr "сховище вже використовує формат \"%s\"" +msgid "enable strict checking" +msgstr "увімкнути сувору перевірку" + +msgid "'git refs verify' takes no arguments" +msgstr "\"git refs verify\" не потребує аргументів" + msgid "" "git remote add [-t ] [-m ] [-f] [--tags | --no-tags] [--" "mirror=] " @@ -11100,6 +11192,30 @@ msgstr[0] " Локальне посилання налаштовано для \ msgstr[1] " Локальних посилання налаштовано для \"git push\"%s:" msgstr[2] " Локальних посилань налаштовано для \"git push\"%s:" +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "\"%s/HEAD\" не змінився і вказує на \"%s\"\n" + +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "\"%s/HEAD\" змінився з \"%s\" і тепер вказує на \"%s\"\n" + +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "Створено \"%s/HEAD\", який вказує на \"%s\"\n" + +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "\"%s/HEAD\" був відʼєднаний на \"%s\" і тепер вказує на \"%s\"\n" + +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"\"%s/HEAD\" раніше вказував на \"%s\" (який не є віддаленою гілкою), але " +"тепер вказує на \"%s\"\n" + msgid "set refs/remotes//HEAD according to remote" msgstr "" "встановити refs/remotes/<назва>/HEAD відповідно до віддаленого призначення" @@ -11124,7 +11240,7 @@ msgid "Not a valid ref: %s" msgstr "Не є припустимим посиланням: %s" #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "Не вдалося налаштувати %s" #, c-format @@ -11696,11 +11812,11 @@ msgstr "записати лише той факт, що вилучені шля #, c-format msgid "Failed to resolve '%s' as a valid revision." -msgstr "Не вдалося розпізнати '%s' як припустиму ревізію." +msgstr "Не вдалося розпізнати \"%s\" як припустиму ревізію." #, c-format msgid "Failed to resolve '%s' as a valid tree." -msgstr "Не вдалося розпізнати '%s' як припустиме дерево." +msgstr "Не вдалося розпізнати \"%s\" як припустиме дерево." msgid "--mixed with paths is deprecated; use 'git reset -- ' instead." msgstr "" @@ -11729,7 +11845,7 @@ msgstr "" #, c-format msgid "Could not reset index file to revision '%s'." -msgstr "Не вдалося скинути індексний файл до ревізії '%s'." +msgstr "Не вдалося скинути індексний файл до ревізії \"%s\"." msgid "Could not write new index file." msgstr "Не вдалося записати новий індексний файл." @@ -12211,11 +12327,11 @@ msgstr "посилання не існує" msgid "failed to look up reference" msgstr "не вдалося знайти посилання" -msgid "only show tags (can be combined with branches)" -msgstr "показати тільки теги (можна комбінувати з гілками)" +msgid "only show tags (can be combined with --branches)" +msgstr "показувати тільки теги (можна комбінувати з --branches)" -msgid "only show branches (can be combined with tags)" -msgstr "показати тільки гілки (можна комбінувати з тегами)" +msgid "only show branches (can be combined with --tags)" +msgstr "показувати тільки гілки (можна комбінувати з --tags)" msgid "check for reference existence without resolving" msgstr "перевіряти наявність посилання без розвʼязання" @@ -12268,6 +12384,10 @@ msgstr "не вдалося видалити директорію \"%s\"" msgid "failed to create directory for sparse-checkout file" msgstr "не вдалося створити директорію для файлу розрідженого переходу" +#, c-format +msgid "unable to fdopen %s" +msgstr "не вдалося fdopen %s" + msgid "failed to initialize worktree config" msgstr "не вдалося ініціалізувати конфігурацію робочого дерева" @@ -12726,8 +12846,8 @@ msgid "couldn't hash object from '%s'" msgstr "не вдалося хешувати обʼєкт з \"%s\"" #, c-format -msgid "unexpected mode %o\n" -msgstr "неочікуваний режим %o\n" +msgid "unexpected mode %o" +msgstr "неочікуваний режим %o" msgid "use the commit stored in the index instead of the submodule HEAD" msgstr "використати коміт, збережений в індексі, замість підмодуля HEAD" @@ -12771,8 +12891,8 @@ msgid "" "Submodule work tree '%s' contains a .git directory. This will be replaced " "with a .git file by using absorbgitdirs." msgstr "" -"Робоче дерево підмодуля \"%s\" містить директорію .git. Її буде замінено на ." -"git файл за допомогою absorbgitdirs." +"Робоче дерево підмодуля \"%s\" містить директорію .git. Її буде замінено " +"на .git файл за допомогою absorbgitdirs." #, c-format msgid "" @@ -13871,6 +13991,9 @@ msgid "try to match the new branch name with a remote-tracking branch" msgstr "" "спробуйте співставити нову назву гілки з назвою віддалено відстежуваної гілки" +msgid "use relative paths for worktrees" +msgstr "використовувати відносні шляхи для робочих дерев" + #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" msgstr "опції \"%s\", \"%s\" та \"%s\" не можна використовувати разом" @@ -14149,6 +14272,25 @@ msgstr "неможливо створити \"%s\"" msgid "index-pack died" msgstr "index-pack завершився невдало" +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "Директорія \"%s\" присутня в індексі, але не є розрідженою" + +msgid "corrupted cache-tree has entries not present in index" +msgstr "пошкоджене cache-tree має записи, яких немає в індексі" + +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "%s з прапорцями 0x%x не слід бути в cache-tree" + +#, c-format +msgid "bad subtree '%.*s'" +msgstr "невірне піддерево \"%.*s\"" + +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "cache-tree для шляху %.*s не співпадає. Очікувалось %s отримано %s" + msgid "terminating chunk id appears earlier than expected" msgstr "ідентифікатор завершення фрагмента зʼявився раніше, ніж очікувалось" @@ -15007,16 +15149,16 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "Підтримка /info/grafts застаріла\n" -"і буде вилучена в одній з наступних версій Git.\n" +"і буде вилучена у наступній версії Git'у.\n" "\n" -"Будь ласка, скористайтесь \"git replace --convert-graft-file\"\n" -"щоб перетворити щепи на заміни посилань.\n" +"Будь ласка, використовуйте \"git replace --convert-graft-file\"\n" +"щоб перетворити прищепи на замінювані посилання.\n" "\n" "Щоб вимкнути це повідомлення, виконайте\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" #, c-format msgid "commit %s exists in commit-graph but not in the object database" @@ -15834,6 +15976,19 @@ msgstr "url не має схеми: %s" msgid "credential url cannot be parsed: %s" msgstr "неможливо розібрати url облікових даних: %s" +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "неприпустимий таймаут \"%s\", очікується невідʼємне ціле число" + +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "" +"неприпустимий початковий таймаут \"%s\", очікується невідʼємне ціле число" + +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "неприпустимі максимальні з'єднання \"%s\", очікується число" + msgid "in the future" msgstr "у майбутньому" @@ -16552,6 +16707,21 @@ msgstr "невірний шлях до простору імен git \"%s\"" msgid "too many args to run %s" msgstr "забагато аргументів для запуску %s" +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"Ви намагаєтеся отримати %s, який знаходиться у файлі коміт-графа, але не в " +"базі даних обʼєктів.\n" +"Ймовірно, це повʼязано з пошкодженням репозиторію.\n" +"Якщо ви намагаєтеся виправити це пошкодження репозиторію повторним " +"отриманням відсутнього обʼєкта, скористайтеся командою \"git fetch --" +"refetch\" з відсутнім обʼєктом." + msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack: очікувався неглибокий список" @@ -17140,11 +17310,12 @@ msgstr[2] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" -"Гачок \"%s\" було проігноровано, оскільки він не визначений як виконуваний.\n" -"Ви можете вимкнути це попередження за допомогою \"git config advice." -"ignoredHook false\"." +"Гачок \"%s\" було проігноровано, оскільки він не визначений як придатний для " +"виконання.\n" +"Ви можете вимкнути це попередження за допомогою \"git config set " +"advice.ignoredHook false\"." msgid "not a git repository" msgstr "не є git сховищем" @@ -17161,15 +17332,9 @@ msgstr "" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "Контроль делегування не підтримується з cURL < 7.22.0" -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "Закріплення відкритих ключів не підтримується з cURL < 7.39.0" - msgid "Unknown value for http.proactiveauth" msgstr "Невідоме значення для http.proactiveauth" -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "CURLSSLOPT_NO_REVOKE не підтримується з cURL < 7.44.0" - #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" msgstr "Непідтримуваний SSL обробник \"%s\". Підтримувані SSL обробники:" @@ -17330,13 +17495,16 @@ msgstr "" msgid "Unable to create '%s.lock': %s" msgstr "Не вдалося створити \"%s.lock\": %s" +msgid "unable to create temporary object directory" +msgstr "не вдалося створити тимчасову директорію обʼєкта" + #, c-format msgid "could not write loose object index %s" msgstr "не вдалося записати індекс вільного обʼєкта %s" #, c-format -msgid "failed to write loose object index %s\n" -msgstr "не вдалося записати індекс вільного обʼєкта %s\n" +msgid "failed to write loose object index %s" +msgstr "не вдалося записати індекс вільного обʼєкта %s" #, c-format msgid "unexpected line: '%s'" @@ -17352,6 +17520,10 @@ msgstr "виявлено цитований CRLF" msgid "unable to format message: %s" msgstr "не вдалося відформатувати допис: %s" +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "неправильний розмір маркера \"%s\", очікується число" + #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "Не вдалося обʼєднати підмодуль %s (не активне)" @@ -17887,6 +18059,17 @@ msgstr "не вдалося завантажити пакунок" msgid "could not open index for %s" msgstr "не вдалося відкрити індекс для %s" +#, c-format +msgid "unable to link '%s' to '%s'" +msgstr "не вдалося звʼязати \"%s\" з \"%s\"" + +#, c-format +msgid "failed to clear multi-pack-index at %s" +msgstr "не вдалося очистити multi-pack-index при %s" + +msgid "cannot write incremental MIDX with bitmap" +msgstr "неможливо записати інкрементний MIDX з bitmap" + msgid "ignoring existing multi-pack-index; checksum mismatch" msgstr "" "ігнорування існуючого multi-pack-index; невідповідність контрольних сум" @@ -17916,18 +18099,33 @@ msgstr "немає файлів пакунків для індексації." msgid "refusing to write multi-pack .bitmap without any objects" msgstr "відмовлено в записі мультіпакункового .bitmap без обʼєктів" +msgid "unable to create temporary MIDX layer" +msgstr "не вдалося створити тимчасовий шар MIDX" + msgid "could not write multi-pack bitmap" msgstr "не вдалося записати мультіпакунковий bitmap" +msgid "unable to open multi-pack-index chain file" +msgstr "не вдалося відкрити ланцюжковий файл multi-pack-index" + +msgid "unable to rename new multi-pack-index layer" +msgstr "не вдалося перейменувати новий multi-pack-index шар" + msgid "could not write multi-pack-index" msgstr "не вдалося записати multi-pack-index" +msgid "cannot expire packs from an incremental multi-pack-index" +msgstr "неможливо видалити пакунки з інкрементним multi-pack-index" + msgid "Counting referenced objects" msgstr "Підрахунок обʼєктів, на які є посилання" msgid "Finding and deleting unreferenced packfiles" msgstr "Пошук і видалення файлів пакунків без посилань" +msgid "cannot repack an incremental multi-pack-index" +msgstr "неможливо перепакувати інкрементний multi-pack-index" + msgid "could not start pack-objects" msgstr "не вдалося розпочати pack-objects" @@ -17990,6 +18188,27 @@ msgstr "" "multi-pack-index назви пакунків знаходяться у невірній послідовності: \"%s\" " "перед \"%s\"" +msgid "multi-pack-index chain file too small" +msgstr "ланцюжковий файл multi-pack-index занадто малий" + +#, c-format +msgid "pack count in base MIDX too high: %" +msgstr "кількість пакунків у базовому MIDX занадто велика: %" + +#, c-format +msgid "object count in base MIDX too high: %" +msgstr "кількість обʼєктів у базовому MIDX занадто велика: %" + +#, c-format +msgid "invalid multi-pack-index chain: line '%s' not a hash" +msgstr "неприпустимий multi-pack-index ланцюжок: рядок \"%s\" не є хешем" + +msgid "unable to find all multi-pack index files" +msgstr "не вдалося знайти всі файли multi-pack-index" + +msgid "invalid MIDX object position, MIDX is likely corrupt" +msgstr "неприпустима позиція MIDX обʼєкта, ймовірно, MIDX пошкоджено" + #, c-format msgid "bad pack-int-id: %u (%u total packs)" msgstr "невірний pack-int-id: %u (%u всього пакунків)" @@ -18008,10 +18227,6 @@ msgstr "" msgid "multi-pack-index large offset out of bounds" msgstr "large offset multi-pack-index виходить за межі" -#, c-format -msgid "failed to clear multi-pack-index at %s" -msgstr "не вдалося очистити multi-pack-index при %s" - msgid "multi-pack-index file exists, but failed to parse" msgstr "multi-pack-index файл існує, але його не вдалося розібрати" @@ -18220,6 +18435,14 @@ msgstr "упакований обʼєкт %s (що зберігається у % msgid "missing mapping of %s to %s" msgstr "відсутнє зіставлення %s до %s" +#, c-format +msgid "unable to open %s" +msgstr "не вдалося відкрити %s" + +#, c-format +msgid "files '%s' and '%s' differ in contents" +msgstr "файли \"%s\" та \"%s\" відрізняються за вмістом" + #, c-format msgid "unable to write file %s" msgstr "не вдалося записати файл %s" @@ -18304,10 +18527,6 @@ msgstr "%s: непідтримуваний тип файлу" msgid "%s is not a valid '%s' object" msgstr "%s не є допустимим \"%s\" обʼєктом" -#, c-format -msgid "unable to open %s" -msgstr "не вдалося відкрити %s" - #, c-format msgid "hash mismatch for %s (expected %s)" msgstr "невідповідність хешу для %s (очікувалось %s)" @@ -18409,7 +18628,7 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "Зазвичай Git ніколи не створює посилання, яке закінчується 40-hex " "символами,\n" @@ -18584,13 +18803,6 @@ msgstr "у мультіпакунковому bitmap відсутній необ msgid "could not open pack %s" msgstr "не вдалося відкрити пакунок %s" -msgid "could not determine MIDX preferred pack" -msgstr "не вдалося визначити бажаний пакунок MIDX" - -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "бажаний пакунок (%s) є неприпустимим" - msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "пошкоджена bitmap таблиця пошуку: триплетна позиція поза індексом" @@ -19223,7 +19435,7 @@ msgstr "багатоступеневі записи для злитого фай #, c-format msgid "unordered stage entries for '%s'" -msgstr "невпорядковані записи індексу для '%s'" +msgstr "невпорядковані записи індексу для \"%s\"" #, c-format msgid "unable to create load_cache_entries thread: %s" @@ -19536,6 +19748,10 @@ msgstr "очікувалась додатна ширина з %%(align) част msgid "expected format: %%(ahead-behind:)" msgstr "очікуваний формат: %%(ahead-behind:<комітоподібне>)" +#, c-format +msgid "expected format: %%(is-base:)" +msgstr "очікуваний формат: %%(is-base:<комітоподібне>)" + #, c-format msgid "malformed field name: %.*s" msgstr "невірно сформована назва поля: %.*s" @@ -19711,17 +19927,25 @@ msgstr "лог для посилання %s несподівано заверш msgid "log for %s is empty" msgstr "лог для %s порожній" -msgid "refusing to force and skip creation of reflog" -msgstr "відмовлено в примусовому пропуску створення рефлогу" - #, c-format -msgid "refusing to update ref with bad name '%s'" -msgstr "відмовлено в оновленні посилання з невірною назвою \"%s\"" +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "відмовлено в оновленні псевдопосилання \"%s\"" #, c-format msgid "refusing to update pseudoref '%s'" msgstr "відмовлено в оновленні псевдопосилання \"%s\"" +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "відмовлено в оновленні reflog з невірною назвою \"%s\"" + +#, c-format +msgid "refusing to update ref with bad name '%s'" +msgstr "відмовлено в оновленні посилання з невірною назвою \"%s\"" + +msgid "refusing to force and skip creation of reflog" +msgstr "відмовлено в примусовому пропуску створення рефлогу" + #, c-format msgid "update_ref failed for ref '%s': %s" msgstr "update_ref завершився невдало для посилання \"%s\": %s" @@ -19771,6 +19995,17 @@ msgstr "" "неможливо заблокувати посилання \"%s\": очікувалось символьне посилання з " "призначенням \"%s\", але це звичайне посилання" +#, c-format +msgid "cannot read ref file '%s'" +msgstr "неможливо прочитати файл посилання \"%s\"" + +#, c-format +msgid "cannot open directory %s" +msgstr "неможливо відкрити директорію %s" + +msgid "Checking references consistency" +msgstr "Перевірка співпадіння посилань" + #, c-format msgid "refname is dangerous: %s" msgstr "refname є небезпечним: %s" @@ -19897,8 +20132,8 @@ msgstr "віддалений сервер надіслав неочікуван msgid "unable to rewind rpc post data - try increasing http.postBuffer" msgstr "" -"не вдалося перемотати вперед rpc post дані - спробуйте збільшити http." -"postBuffer" +"не вдалося перемотати вперед rpc post дані - спробуйте збільшити " +"http.postBuffer" #, c-format msgid "remote-curl: bad line length character: %.4s" @@ -19973,7 +20208,7 @@ msgstr "remote-curl: невідома команда \"%s\" з git" #, c-format msgid "config remote shorthand cannot begin with '/': %s" -msgstr "скорочення віддаленої конфігураціі не може починатися з '/': %s" +msgstr "скорочення віддаленої конфігураціі не може починатися з \"/\": %s" msgid "more than one receivepack given, using the first" msgstr "надано більше одного пакунка для отримання, використано перший" @@ -19981,9 +20216,13 @@ msgstr "надано більше одного пакунка для отрим msgid "more than one uploadpack given, using the first" msgstr "надано більше одного пакунка для завантаження, використано перший" +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "нерозпізнане followRemoteHEAD значення \"%s\" було проігноровано" + #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" -msgstr "нерозпізнане значення transfer.credentialsInUrl: '%s'" +msgstr "нерозпізнане значення transfer.credentialsInUrl: \"%s\"" #, c-format msgid "URL '%s' uses plaintext credentials" @@ -20003,11 +20242,11 @@ msgstr "%s відстежує як %s, так і %s" #, c-format msgid "key '%s' of pattern had no '*'" -msgstr "ключ '%s' шаблону не містив '*'" +msgstr "ключ \"%s\" шаблону не містив '*'" #, c-format msgid "value '%s' of pattern has no '*'" -msgstr "значення '%s' шаблону не містить '*'" +msgstr "значення \"%s\" шаблону не містить '*'" #, c-format msgid "src refspec %s does not match any" @@ -20106,11 +20345,11 @@ msgstr "HEAD не вказує на гілку" #, c-format msgid "no such branch: '%s'" -msgstr "немає такої гілки: '%s'" +msgstr "немає такої гілки: \"%s\"" #, c-format msgid "no upstream configured for branch '%s'" -msgstr "першоджерельне сховище не налаштовано для гілки '%s'" +msgstr "першоджерельне сховище не налаштовано для гілки \"%s\"" #, c-format msgid "upstream branch '%s' not stored as a remote-tracking branch" @@ -20131,7 +20370,7 @@ msgid "push refspecs for '%s' do not include '%s'" msgstr "надіслані визначники посилань для \"%s\" не включають \"%s\"" msgid "push has no destination (push.default is 'nothing')" -msgstr "надсилання не має призначення (push.default дорівнює 'nothing')" +msgstr "надсилання не має призначення (push.default дорівнює \"nothing\")" msgid "cannot resolve 'simple' push to a single destination" msgstr "" @@ -20184,7 +20423,7 @@ msgstr[0] "" msgstr[1] "" "Ваша гілка відстає від \"%s\" на %d коміти, і її можна перемотати вперед.\n" msgstr[2] "" -"Ваша гілка відстає від гілки '%s' на %d комітів, і її можна перемотати " +"Ваша гілка відстає від гілки \"%s\" на %d комітів, і її можна перемотати " "вперед.\n" msgid " (use \"git pull\" to update your local branch)\n" @@ -20219,7 +20458,7 @@ msgstr "неможливо розібрати очікувану назву об #, c-format msgid "cannot strip one component off url '%s'" -msgstr "неможливо вилучити один компонент з url '%s'" +msgstr "неможливо вилучити один компонент з url \"%s\"" #, c-format msgid "bad replace ref name: %s" @@ -20429,12 +20668,15 @@ msgstr "" msgid "create repository within 'src' directory" msgstr "створити сховище в директорії \"src\"" +msgid "specify if tags should be fetched during clone" +msgstr "вказати, чи потрібно отримувати теги під час клонування" + msgid "" "scalar clone [--single-branch] [--branch ] [--full-clone]\n" -"\t[--[no-]src] []" +"\t[--[no-]src] [--[no-]tags] []" msgstr "" "scalar clone [--single-branch] [--branch <головна-гілка>] [--full-clone]\n" -"\t[--[no-]src] [<коренева-директорія>]" +"\t[--[no-]src] [--[no-]tags] [<коренева-директорія-проекту>]" #, c-format msgid "cannot deduce worktree name from '%s'" @@ -20452,6 +20694,10 @@ msgstr "не вдалося отримати гілку за замовчува msgid "could not configure remote in '%s'" msgstr "не вдалося налаштувати віддалене сховище в \"%s\"" +#, c-format +msgid "could not disable tags in '%s'" +msgstr "не вдалося вимкнути теги в \"%s\"" + #, c-format msgid "could not configure '%s'" msgstr "не вдалося налаштувати \"%s\"" @@ -20983,7 +21229,7 @@ msgid "" msgstr "" "\"reword\" не приймає коміти злиття. Якщо ви хочете\n" "відтворити злиття та змінити текст допису, використовуйте\n" -"\"merge -c\" для коміта." +"\"merge -c\" для коміта" #. TRANSLATORS: 'edit', 'merge -C' and 'break' should #. not be translated. @@ -21525,6 +21771,10 @@ msgstr "неможливо повернутися до поточної робо msgid "failed to stat '%*s%s%s'" msgstr "не вдалося записати \"%*s%s%s\"" +#, c-format +msgid "safe.directory '%s' not absolute" +msgstr "safe.directory \"%s\" не є абсолютною" + #, c-format msgid "" "detected dubious ownership in repository at '%s'\n" @@ -21863,7 +22113,7 @@ msgstr "Не вдалося оновити підмодуль \"%s\"." #, c-format msgid "submodule git dir '%s' is inside git dir '%.*s'" -msgstr "підмодуль git dir \"%s\" знаходиться всередині git директорії \"%*s\"" +msgstr "підмодуль git dir \"%s\" знаходиться всередині git директорії \"%.*s\"" #, c-format msgid "expected '%.*s' in submodule path '%s' not to be a symbolic link" @@ -21936,6 +22186,9 @@ msgstr "коміт %s не позначений як досяжний" msgid "too many commits marked reachable" msgstr "забагато комітів позначено як досяжні" +msgid "could not determine MIDX preferred pack" +msgstr "не вдалося визначити бажаний пакунок MIDX" + msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 [<опції>]" @@ -22001,6 +22254,24 @@ msgstr "токен" msgid "command token to send to the server" msgstr "токен команди для відправки на сервер" +msgid "unit-test []" +msgstr "unit-test [<опції>]" + +msgid "immediately exit upon the first failed test" +msgstr "вихід відразу після першого невдалого тесту" + +msgid "suite[::test]" +msgstr "suite[::test]" + +msgid "run only test suite or individual test " +msgstr "запустити тільки набір тестів або окремий тест " + +msgid "suite" +msgstr "набір" + +msgid "exclude test suite " +msgstr "виключити набір тестів " + #, c-format msgid "running trailer command '%s' failed" msgstr "не вдалося виконати команду трейлера \"%s\"" @@ -22580,6 +22851,9 @@ msgstr ".git файл пошкоджено" msgid ".git file incorrect" msgstr ".git файл не є коректним" +msgid ".git file absolute/relative path mismatch" +msgstr "неспівпадіння абсолютного/відносного шляху до .git файлу" + msgid "not a valid path" msgstr "неприпустимий шлях" @@ -22595,6 +22869,9 @@ msgstr "не вдалося знайти сховище; файл .git пошк msgid "gitdir unreadable" msgstr "нечитабельна git директорія" +msgid "gitdir absolute/relative path mismatch" +msgstr "неспівпадіння абсолютного/відносного шляху git директорії" + msgid "gitdir incorrect" msgstr "невірна git директорія" @@ -22630,6 +22907,13 @@ msgstr "не вдалося скинути %s в \"%s\"" msgid "failed to set extensions.worktreeConfig setting" msgstr "не вдалося встановити extensions.worktreeConfig параметр" +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"не вдалося оновити формат сховища для підтримки відносних робочих дерев" + +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "не вдалося встановити параметр extensions.relativeWorktrees" + #, c-format msgid "could not setenv '%s'" msgstr "не вдалося встановити змінну оточення \"%s\"" @@ -22965,7 +23249,7 @@ msgstr "" #, c-format msgid "You are currently bisecting, started from branch '%s'." -msgstr "Наразі ви робите бісекцію, починаючи з гілки '%s'." +msgstr "Наразі ви робите бісекцію, починаючи з гілки \"%s\"." msgid "You are currently bisecting." msgstr "Наразі ви робите бісекцію." @@ -23198,6 +23482,9 @@ msgstr "\"%s.final\" містить створений лист.\n" msgid "--dump-aliases incompatible with other options\n" msgstr "--dump-aliases несумісна з іншими опціями\n" +msgid "--dump-aliases and --translate-aliases are mutually exclusive\n" +msgstr "--dump-aliases і --translate-aliases є взаємовиключними\n" + msgid "" "fatal: found configuration options for 'sendmail'\n" "git-send-email is configured with the sendemail.* options - note the 'e'.\n" @@ -23370,8 +23657,8 @@ msgstr "" " Наведений вище список копій було розширено додатковими\n" " адресами, знайденими у дописі до коміту латки. Зазвичай\n" " send-email запитує перед надсиланням, коли це трапляється.\n" -" Цю поведінку можна контролювати за допомогою параметра sendemail." -"confirm\n" +" Цю поведінку можна контролювати за допомогою параметра " +"sendemail.confirm\n" " налаштування конфігурації.\n" "\n" " Для отримання додаткової інформації виконайте команду \"git send-email --" From bba9dd6a96c464e2b32226be81cb1f30257c78c0 Mon Sep 17 00:00:00 2001 From: Teng Long Date: Sat, 4 Jan 2025 00:30:11 +0800 Subject: [PATCH 024/306] l10n: zh_CN: updated translation for 2.48 Signed-off-by: Teng Long --- po/zh_CN.po | 301 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 238 insertions(+), 63 deletions(-) diff --git a/po/zh_CN.po b/po/zh_CN.po index 55d2aee6270774..12a0fb510b10cc 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -154,8 +154,8 @@ msgid "" msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-05 03:31+0800\n" -"PO-Revision-Date: 2024-10-05 03:32+0800\n" +"POT-Creation-Date: 2025-01-02 20:43+0800\n" +"PO-Revision-Date: 2025-01-05 19:01+0800\n" "Last-Translator: Teng Long \n" "Language-Team: GitHub \n" "Language: zh_CN\n" @@ -886,10 +886,10 @@ msgstr "只有二进制文件被修改。" #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"使用 \"git config advice.%s false\" 来关闭此消息" +"使用 \"git config set advice.%s false\" 来关闭此消息" #: advice.c #, c-format @@ -1778,9 +1778,10 @@ msgstr "不是一个有效的对象名:%s" msgid "not a tree object: %s" msgstr "不是一个树对象:%s" -#: archive.c builtin/clone.c -msgid "unable to checkout working tree" -msgstr "不能检出工作区" +#: archive.c +#, c-format +msgid "failed to unpack tree object %s" +msgstr "无法解包树对象 %s" #: archive.c #, c-format @@ -1957,7 +1958,7 @@ msgstr "忽略过大的 gitattributes 数据对象 '%s'" #: attr.c msgid "cannot use --attr-source or GIT_ATTR_SOURCE without repo" -msgstr "无法在没有存储库的情况下使用 --attr-source 或 GIT_ATTR_SOURCE" +msgstr "无法在没有仓库的情况下使用 --attr-source 或 GIT_ATTR_SOURCE" #: attr.c msgid "bad --attr-source or GIT_ATTR_SOURCE" @@ -4813,7 +4814,7 @@ msgstr "未知的冲突风格 '%s'" msgid "perform a 3-way merge with the new branch" msgstr "和新的分支执行三方合并" -#: builtin/checkout.c builtin/log.c parse-options.h +#: builtin/checkout.c builtin/log.c builtin/range-diff.c parse-options.h msgid "style" msgstr "风格" @@ -4842,8 +4843,8 @@ msgid "update ignored files (default)" msgstr "更新忽略的文件(默认)" #: builtin/checkout.c -msgid "do not check if another worktree is holding the given ref" -msgstr "不检查指定的引用是否被其他工作区所占用" +msgid "do not check if another worktree is using this branch" +msgstr "不检查其他工作区是否正在使用该分支" #: builtin/checkout.c msgid "checkout our version for unmerged files" @@ -5222,14 +5223,13 @@ msgstr "创建一个指定深度的浅克隆" msgid "create a shallow clone since a specific time" msgstr "从一个特定时间创建一个浅克隆" -#: builtin/clone.c builtin/fetch.c builtin/pull.c builtin/rebase.c -#: builtin/replay.c -msgid "revision" -msgstr "版本" +#: builtin/clone.c builtin/fetch.c builtin/pull.c +msgid "ref" +msgstr "引用" #: builtin/clone.c builtin/fetch.c builtin/pull.c -msgid "deepen history of shallow clone, excluding rev" -msgstr "深化浅克隆的历史,除了特定版本" +msgid "deepen history of shallow clone, excluding ref" +msgstr "深化浅克隆的历史,除了给定的引用" #: builtin/clone.c builtin/submodule--helper.c msgid "clone only one branch, HEAD or --branch" @@ -5390,6 +5390,10 @@ msgstr "无法初始化稀疏检出" msgid "remote HEAD refers to nonexistent ref, unable to checkout" msgstr "远程 HEAD 指向一个不存在的引用,无法检出" +#: builtin/clone.c +msgid "unable to checkout working tree" +msgstr "不能检出工作区" + #: builtin/clone.c msgid "unable to write parameters to config file" msgstr "无法将参数写入配置文件" @@ -6368,10 +6372,9 @@ msgstr "" #: builtin/config.c msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" -"git config unset [<文件选项>] [--all] [--value=<值>] [--fixed-value] <名称> <" -"值>" +"git config unset [<文件选项>] [--all] [--value=<值>] [--fixed-value] <名称>" #: builtin/config.c msgid "git config rename-section [] " @@ -6915,12 +6918,8 @@ msgstr "已遍历 %lu 个提交\n" #: builtin/describe.c #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"发现多于 %i 个标签,列出最近的 %i 个\n" -"在 %s 放弃搜索\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "找到 %i 个标签;在 %s 处放弃搜索\n" #: builtin/describe.c #, c-format @@ -7468,6 +7467,21 @@ msgstr "%s 不是一个有效的对象" msgid "the object %s does not exist" msgstr "对象 '%s' 不存在" +#: builtin/fetch.c +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"运行 'git remote set-head %s %s' 以跟随更改,或者\n" +"如果您不想看到此消息,则将'remote.%s.followRemoteHEAD' 配置选项设置为不同的" +"值。\n" +"特别地,运行 'git config set remote.%s.followRemoteHEAD %s' 将禁用警告,直到" +"远程将 HEAD 更改为其他内容。\"" + #: builtin/fetch.c msgid "multiple branches detected, incompatible with --set-upstream" msgstr "检测到多分支,和 --set-upstream 不兼容" @@ -7635,6 +7649,10 @@ msgstr "引用映射" msgid "specify fetch refmap" msgstr "指定获取操作的引用映射" +#: builtin/fetch.c builtin/pull.c builtin/rebase.c builtin/replay.c +msgid "revision" +msgstr "版本" + #: builtin/fetch.c builtin/pull.c msgid "report that we have only objects reachable from this object" msgstr "报告我们只拥有从该对象开始可达的对象" @@ -7830,7 +7848,7 @@ msgstr "存储着仓库路径列表的配置项键名" #: builtin/for-each-repo.c msgid "keep going even if command fails in a repository" -msgstr "即使存储库中的命令失败,仍继续执行" +msgstr "即使仓库中的命令失败,仍继续执行" #: builtin/for-each-repo.c msgid "missing --config=" @@ -8546,8 +8564,24 @@ msgid "%s scheduler is not available" msgstr "%s 调度器不可用" #: builtin/gc.c -msgid "another process is scheduling background maintenance" -msgstr "另外一个进程正运行于后台维护" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"无法创建 '%s.lock':%s。\n" +"\n" +"另一个已计划的 git-maintenance(1) 进程似乎正在该仓库中运行。\n" +"请确保没有其他维护进程正在运行,然后重试。如果仍然运行失败,则\n" +"git-maintenance(1) 进程可能之前已在此仓库中崩溃:请手动删除该文件以继续。" + +#: builtin/gc.c +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "无法获取计划的后台维护锁" #: builtin/gc.c msgid "git maintenance start [--scheduler=]" @@ -9272,6 +9306,23 @@ msgid_plural "chain length = %d: %lu objects" msgstr[0] "链长 = %d: %lu 对象" msgstr[1] "链长 = %d: %lu 对象" +#: builtin/index-pack.c +msgid "could not start pack-objects to repack local links" +msgstr "无法启动 pack-objects 来重新打包本地链接" + +#: builtin/index-pack.c +msgid "failed to feed local object to pack-objects" +msgstr "无法将本地对象提供给 pack-objects" + +#: builtin/index-pack.c +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack:期望仅从 pack-objects 的输出行中获得完整的十六进制对象 ID。" + +#: builtin/index-pack.c +msgid "could not finish pack-objects to repack local links" +msgstr "无法完成 pack-objects 来重新打包本地链接" + #: builtin/index-pack.c msgid "Cannot come back to cwd" msgstr "无法返回当前工作目录" @@ -9286,6 +9337,10 @@ msgstr "错误选项 %s" msgid "unknown hash algorithm '%s'" msgstr "未知的哈希算法 '%s'" +#: builtin/index-pack.c +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor 无法与包名称一起使用" + #: builtin/index-pack.c msgid "--stdin requires a git repository" msgstr "--stdin 需要 git 仓库" @@ -11002,11 +11057,11 @@ msgstr "git notes [--ref <注解引用>] [list [<对象>]]" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref <注解引用>] add [-f] [--allow-empty] [--[no-]separator|--" "separator=<分段符>] [--[no-]stripspace] [-m <说明> | -F <文件> | (-c | -C) <" -"对象>] [<对象>]" +"对象>] [<对象>] [-e]" #: builtin/notes.c msgid "git notes [--ref ] copy [-f] " @@ -11016,11 +11071,11 @@ msgstr "git notes [--ref <注解引用>] copy [-f] <源对象> <目标对象>" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref <注解引用>] append [--allow-empty] [--[no-]separator|--" "separator=<分段符>] [--[no-]stripspace] [-m <说明> | -F <文件> | (-c | -C) <" -"对象>] [<对象>]" +"对象>] [<对象>] [-e]" #: builtin/notes.c msgid "git notes [--ref ] edit [--allow-empty] []" @@ -11171,6 +11226,10 @@ msgstr "注解内容到一个文件中" msgid "reuse and edit specified note object" msgstr "重用和编辑指定的注解对象" +#: builtin/notes.c +msgid "edit note message in editor" +msgstr "在编辑器中编辑注释说明" + #: builtin/notes.c msgid "reuse specified note object" msgstr "重用指定的注解对象" @@ -11784,6 +11843,10 @@ msgstr "处理丢失的对象" msgid "do not pack objects in promisor packfiles" msgstr "不要打包 promisor 包文件中的对象" +#: builtin/pack-objects.c +msgid "implies --missing=allow-any" +msgstr "暗含 --missing=allow-any" + #: builtin/pack-objects.c msgid "respect islands during delta compression" msgstr "在增量压缩时参考数据岛" @@ -13668,6 +13731,33 @@ msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " 为 'git push' 配置的本地引用%s:" msgstr[1] " 为 'git push' 配置的本地引用%s:" +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "'%s/HEAD' 未改变并指向 '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' 已从 '%s' 更改,现在指向 '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "'%s/HEAD' 现已创建并指向 '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' 在 '%s' 处分离并且现在指向 '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "'%s/HEAD' 曾指向 '%s'(不是远程分支),但现在指向 '%s'\n" + #: builtin/remote.c msgid "set refs/remotes//HEAD according to remote" msgstr "根据远程设置 refs/remotes/<名称>/HEAD" @@ -13696,7 +13786,7 @@ msgstr "不是一个有效引用:%s" #: builtin/remote.c #, c-format -msgid "Could not setup %s" +msgid "Could not set up %s" msgstr "不能设置 %s" # 译者:注意保持前导空格 @@ -17034,6 +17124,10 @@ msgstr "设置跟踪模式(参见 git-branch(1))" msgid "try to match the new branch name with a remote-tracking branch" msgstr "尝试为新分支名匹配一个远程跟踪分支" +#: builtin/worktree.c +msgid "use relative paths for worktrees" +msgstr "对工作区使用相对路径" + #: builtin/worktree.c diff.c parse-options.c #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" @@ -17370,6 +17464,30 @@ msgstr "不能创建 '%s'" msgid "index-pack died" msgstr "index-pack 终止" +#: cache-tree.c +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "目录 '%s' 存在于索引中,但不是稀疏的" + +#: cache-tree.c unpack-trees.c +msgid "corrupted cache-tree has entries not present in index" +msgstr "损坏的缓存树包含索引中不存在的条目" + +#: cache-tree.c +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "标志位为 0x%2$x 的 %1$s 不应位于缓存树中" + +#: cache-tree.c +#, c-format +msgid "bad subtree '%.*s'" +msgstr "损坏的子树 '%.*s'" + +#: cache-tree.c +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "路径 %.*s 的缓存树不匹配。预期为 %s,实际为 %s" + #: chunk-format.c msgid "terminating chunk id appears earlier than expected" msgstr "终止块 ID 比预期更早出现" @@ -18477,15 +18595,15 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "对 /info/grafts 的支持已过时,并将在\n" "未来的Git版本中被移除。\n" "\n" "请使用 \"git replace --convert-graft-file\" 将\n" -"grafts 转换为替换引用。\n" +"(提交)移植转换为替换引用。\n" "\n" -"设置 \"git config advice.graftFileDeprecated false\"\n" +"运行 \"git config set advice.graftFileDeprecated false\"\n" "可关闭本消息" #: commit.c @@ -19485,6 +19603,21 @@ msgstr "URL 没有 scheme:%s" msgid "credential url cannot be parsed: %s" msgstr "不能解析凭据 URL:%s" +#: daemon.c +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "无效的超时值 '%s',应为非负整数" + +#: daemon.c +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "无效的初始超时值 '%s',应为非负整数" + +#: daemon.c +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "无效的最大连接数 '%s',应为一个整数" + #: date.c msgid "in the future" msgstr "在将来" @@ -20351,6 +20484,20 @@ msgstr "错误的 git 名字空间路径 \"%s\"" msgid "too many args to run %s" msgstr "执行 %s 的参数太多" +#: fetch-pack.c +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"您正在尝试获取 %s,它位于提交图文件中,但不在对象数据库中。\n" +"这可能是由于仓库损坏造成的。\n" +"如果您尝试通过重新获取丢失的对象来修复此仓库损坏,请对丢失的对象使用 'git " +"fetch --refetch'。" + #: fetch-pack.c msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack:应为 shallow 列表" @@ -21062,10 +21209,10 @@ msgstr[1] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" "因为没有将钩子 '%s' 设置为可执行,钩子被忽略。您可以通过\n" -"配置 `git config advice.ignoredHook false` 来关闭这条警告。" +"配置 `git config set advice.ignoredHook false` 来关闭这条警告。" #: http-fetch.c msgid "not a git repository" @@ -21085,18 +21232,10 @@ msgstr "http.postBuffer 为负值,默认为 %d" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "不支持委托控制,因为 cURL < 7.22.0" -#: http.c -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "不支持公钥文件锁定,因为 cURL < 7.39.0" - #: http.c msgid "Unknown value for http.proactiveauth" msgstr "http.proactiveauth 为未知取值" -#: http.c -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "不支持 CURLSSLOPT_NO_REVOKE,因为 cURL < 7.44.0" - #: http.c #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" @@ -21316,6 +21455,11 @@ msgstr "检测到被引用的 CRLF" msgid "unable to format message: %s" msgstr "无法格式化消息:%s" +#: merge-ll.c +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "无效的标记大小 '%s',应为一个整数" + #: merge-ort.c merge-recursive.c #, c-format msgid "Failed to merge submodule %s (not checked out)" @@ -22623,7 +22767,7 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "Git 通常不会创建一个以40个十六进制字符结尾的引用,因为当您只提供40\n" "个十六进制字符时将被忽略。这些引用可能被错误地创建。例如:\n" @@ -22631,7 +22775,7 @@ msgstr "" " git switch -c $br $(git rev-parse ...)\n" "\n" "当 \"$br\" 某种原因空白时,一个40位十六进制的引用将被创建。请检查这些\n" -"引用,可能需要删除它们。运行 \"git config advice.objectNameWarning\n" +"引用,可能需要删除它们。运行 \"git config set advice.objectNameWarning\n" "false\" 命令关闭本消息通知。" #: object-name.c @@ -22826,15 +22970,6 @@ msgstr "多包位图缺少必需的反向索引" msgid "could not open pack %s" msgstr "不能打开包 %s" -#: pack-bitmap.c t/helper/test-read-midx.c -msgid "could not determine MIDX preferred pack" -msgstr "不能确定多包索引的首选包" - -#: pack-bitmap.c -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "首选包 (%s) 无效" - #: pack-bitmap.c msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "损坏的位图查询表:三元组位置超出索引" @@ -24170,8 +24305,19 @@ msgid "log for %s is empty" msgstr "%s 的日志为空" #: refs.c -msgid "refusing to force and skip creation of reflog" -msgstr "拒绝既强制又跳过创建引用日志" +#, c-format +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "拒绝为伪引用 '%s' 更新引用日志" + +#: refs.c +#, c-format +msgid "refusing to update pseudoref '%s'" +msgstr "拒绝更新伪引用 '%s'" + +#: refs.c +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "拒绝使用错误名称 '%s' 更新引用日志" #: refs.c #, c-format @@ -24179,9 +24325,8 @@ msgid "refusing to update ref with bad name '%s'" msgstr "拒绝更新有错误名称 '%s' 的引用" #: refs.c -#, c-format -msgid "refusing to update pseudoref '%s'" -msgstr "拒绝更新伪引用 '%s'" +msgid "refusing to force and skip creation of reflog" +msgstr "拒绝强制跳过创建引用日志" #: refs.c #, c-format @@ -24242,6 +24387,11 @@ msgid "" "cannot lock ref '%s': expected symref with target '%s': but is a regular ref" msgstr "无法锁定引用 '%s':预期目标为 '%s' 的符号引用:但是是普通引用" +#: refs/files-backend.c +#, c-format +msgid "cannot read ref file '%s'" +msgstr "无法读取引用文件 '%s'" + #: refs/files-backend.c #, c-format msgid "cannot open directory %s" @@ -24498,6 +24648,11 @@ msgstr "提供了一个以上的 receivepack,使用第一个" msgid "more than one uploadpack given, using the first" msgstr "提供了一个以上的 uploadpack,使用第一个" +#: remote.c +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "已忽略无法识别的 followRemoteHEAD 值 '%s'" + #: remote.c #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" @@ -26810,6 +26965,10 @@ msgstr "提交 %s 没有标记为可达" msgid "too many commits marked reachable" msgstr "太多提交标记为可达" +#: t/helper/test-read-midx.c +msgid "could not determine MIDX preferred pack" +msgstr "不能确定多包索引的首选包" + #: t/helper/test-serve-v2.c msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 [<选项>]" @@ -27584,6 +27743,10 @@ msgstr ".git 文件损坏" msgid ".git file incorrect" msgstr ".git 文件不正确" +#: worktree.c +msgid ".git file absolute/relative path mismatch" +msgstr ".git 文件绝对/相对路径不匹配" + #: worktree.c msgid "not a valid path" msgstr "不是一个有效的路径" @@ -27604,6 +27767,10 @@ msgstr "无法定位仓库,.git 文件损坏" msgid "gitdir unreadable" msgstr "gitdir 不可读" +#: worktree.c +msgid "gitdir absolute/relative path mismatch" +msgstr "gitdir 绝对/相对路径不匹配" + #: worktree.c msgid "gitdir incorrect" msgstr "gitdir 不正确" @@ -27648,6 +27815,14 @@ msgstr "无法在 '%2$s' 中取消设置 %1$s" msgid "failed to set extensions.worktreeConfig setting" msgstr "无法设置 extensions.worktreeConfig" +#: worktree.c +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "无法升级仓库格式以支持相对工作区" + +#: worktree.c +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "无法设定 extensions.relativeWorktrees 的设置" + #: wrapper.c #, c-format msgid "could not setenv '%s'" From 238c0c095f21f919fd268d8519d6b1b07ffc1540 Mon Sep 17 00:00:00 2001 From: Bagas Sanjaya Date: Wed, 18 Dec 2024 14:16:48 +0700 Subject: [PATCH 025/306] l10n: po-id for 2.48 Update following components: * advice.c * archive.c * builtin/checkout.c * builtin/clone.c * builtin/config.c * builtin/describe.c * builtin/fetch.c * builtin/gc.c * builtin/index-pack.c * builtin/notes.c * builtin/pack-objects.c * builtin/remote.c * builtin/worktree.c * commit.c * fetch-pack.c * hook.c * object-name.c * refs.c * refs/files-backend.c * remote.c * worktree.c Translate following new components: * cache-tree.c * daemon.c * merge-ll.c Signed-off-by: Bagas Sanjaya --- po/id.po | 338 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 259 insertions(+), 79 deletions(-) diff --git a/po/id.po b/po/id.po index fc34140776a408..3965c9e941d9c6 100644 --- a/po/id.po +++ b/po/id.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2024-10-04 08:33+0700\n" -"PO-Revision-Date: 2024-10-04 08:52+0700\n" +"POT-Creation-Date: 2024-12-23 18:57+0000\n" +"PO-Revision-Date: 2025-01-06 15:50+0700\n" "Last-Translator: Bagas Sanjaya \n" "Language-Team: Indonesian\n" "Language: id\n" @@ -755,10 +755,10 @@ msgstr "Hanya berkas biner yang berubah." #, c-format msgid "" "\n" -"Disable this message with \"git config advice.%s false\"" +"Disable this message with \"git config set advice.%s false\"" msgstr "" "\n" -"Nonaktifkan pesan ini dengan \"git config advice.%s false\"" +"Nonaktifkan pesan ini dengan \"git config set advice.%s false\"" #: advice.c #, c-format @@ -1670,9 +1670,10 @@ msgstr "bukan nama objek valid: %s" msgid "not a tree object: %s" msgstr "bukan objek pohon: %s" -#: archive.c builtin/clone.c -msgid "unable to checkout working tree" -msgstr "tidak dapat men-checkout pohon kerja" +#: archive.c +#, c-format +msgid "failed to unpack tree object %s" +msgstr "gagal membuka objek pohon %s" #: archive.c #, c-format @@ -3734,11 +3735,11 @@ msgstr "HEAD tidak ditemukan di bawah refs/heads!" #: builtin/branch.c msgid "" -"branch with --recurse-submodules can only be used if submodule." -"propagateBranches is enabled" +"branch with --recurse-submodules can only be used if " +"submodule.propagateBranches is enabled" msgstr "" -"cabang dengan --recurse-submodules hanya dapat digunakan jika submodule." -"propagateBranches diaktifkan" +"cabang dengan --recurse-submodules hanya dapat digunakan jika " +"submodule.propagateBranches diaktifkan" #: builtin/branch.c msgid "--recurse-submodules can only be used to create branches" @@ -4767,7 +4768,7 @@ msgstr "gaya konflik '%s' tidak dikenal" msgid "perform a 3-way merge with the new branch" msgstr "lakukan penggabungan 3 arah dengan cabang baru" -#: builtin/checkout.c builtin/log.c parse-options.h +#: builtin/checkout.c builtin/log.c builtin/range-diff.c parse-options.h msgid "style" msgstr "gaya" @@ -4796,9 +4797,8 @@ msgid "update ignored files (default)" msgstr "perbarui berkas yang diabaikan (default)" #: builtin/checkout.c -msgid "do not check if another worktree is holding the given ref" -msgstr "" -"jangan periksa jika pohon kerja yang lain mempunyai referensi yang diberikan" +msgid "do not check if another worktree is using this branch" +msgstr "jangan periksa jika pohon kerja yang lain menggunakan cabang ini" #: builtin/checkout.c msgid "checkout our version for unmerged files" @@ -5179,14 +5179,13 @@ msgstr "buat klon dangkal sedalam kedalaman tersebut" msgid "create a shallow clone since a specific time" msgstr "buat klon dangkal sejak waktu yang disebutkan" -#: builtin/clone.c builtin/fetch.c builtin/pull.c builtin/rebase.c -#: builtin/replay.c -msgid "revision" -msgstr "revisi" +#: builtin/clone.c builtin/fetch.c builtin/pull.c +msgid "ref" +msgstr "referensi" #: builtin/clone.c builtin/fetch.c builtin/pull.c -msgid "deepen history of shallow clone, excluding rev" -msgstr "perdalam riwayat klon dangkal, tidak termasuk rev" +msgid "deepen history of shallow clone, excluding ref" +msgstr "perdalam riwayat klon dangkal, kecualikan referensi" #: builtin/clone.c builtin/submodule--helper.c msgid "clone only one branch, HEAD or --branch" @@ -5348,6 +5347,10 @@ msgstr "gagal menginisalisasi checkout tipis" msgid "remote HEAD refers to nonexistent ref, unable to checkout" msgstr "HEAD remote merujuk pada ref yang tidak ada, tidak dapat men-checkout" +#: builtin/clone.c +msgid "unable to checkout working tree" +msgstr "tidak dapat men-checkout pohon kerja" + #: builtin/clone.c msgid "unable to write parameters to config file" msgstr "tidak dapat menulis parameter ke berkas konfigurasi" @@ -6347,10 +6350,10 @@ msgstr "" #: builtin/config.c msgid "" "git config unset [] [--all] [--value=] [--fixed-value] " -" " +"" msgstr "" -"git config unset [] [--fixed-value] " -" " +"git config unset [] [--all] [--value=] [--fixed-value] " +"" #: builtin/config.c msgid "git config rename-section [] " @@ -6907,12 +6910,8 @@ msgstr "%lu komit dilintasi\n" #: builtin/describe.c #, c-format -msgid "" -"more than %i tags found; listed %i most recent\n" -"gave up search at %s\n" -msgstr "" -"lebih dari %i tag ditemukan; %i terbaru didaftarkan\n" -"menyerah mencari pada %s\n" +msgid "found %i tags; gave up search at %s\n" +msgstr "dapat %i tag; menyerah mencari pada %s\n" #: builtin/describe.c #, c-format @@ -7389,8 +7388,8 @@ msgstr "" "fetch secara normal mengindikasikan cabang mana ada pembaruan terpaksa,\n" "tapi pemeriksaan tersebut sudah dinonaktifkan. Untuk aktifkan kembali, " "gunakan\n" -"bendera '--show-forced-updates' atau jalankan 'git config fetch." -"showForcedUpdates true'." +"bendera '--show-forced-updates' atau jalankan 'git config " +"fetch.showForcedUpdates true'." #: builtin/fetch.c #, c-format @@ -7467,6 +7466,21 @@ msgstr "%s bukan sebuah objek valid" msgid "the object %s does not exist" msgstr "objek '%s' tidak ada" +#: builtin/fetch.c +#, c-format +msgid "" +"Run 'git remote set-head %s %s' to follow the change, or set\n" +"'remote.%s.followRemoteHEAD' configuration option to a different value\n" +"if you do not want to see this message. Specifically running\n" +"'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" +"until the remote changes HEAD to something else." +msgstr "" +"Jalankan 'git remote set-head %s %s' untuk mengikuti perubahan, atau setel\n" +"opsi konfigurasi 'remote.%s.followRemoteHEAD' ke nilai yang berbeda jika\n" +"Anda tidak ingin melihat pesan ini lagi. Secara rinci menjalakan\n" +"'git config set remote.%s followRemoteHEAD %s' akan mematikan peringatan\n" +"ini sampai remote mengubah HEAD ke yang lain." + #: builtin/fetch.c msgid "multiple branches detected, incompatible with --set-upstream" msgstr "banyak cabang terdeteksi, tidak kompatibel dengan --set-upstream" @@ -7641,6 +7655,10 @@ msgstr "peta referensi" msgid "specify fetch refmap" msgstr "sebutkan ambil peta referensi" +#: builtin/fetch.c builtin/pull.c builtin/rebase.c builtin/replay.c +msgid "revision" +msgstr "revisi" + #: builtin/fetch.c builtin/pull.c msgid "report that we have only objects reachable from this object" msgstr "" @@ -7711,8 +7729,8 @@ msgstr "protokol tidak mendukung --negotiate-only, keluar." #: builtin/fetch.c msgid "" -"--filter can only be used with the remote configured in extensions." -"partialclone" +"--filter can only be used with the remote configured in " +"extensions.partialclone" msgstr "" "--filter hanya dapat digunakan dengan remote yang terkonfigurasi di " "extensions.partialclone" @@ -8565,8 +8583,25 @@ msgid "%s scheduler is not available" msgstr "penjadwal %s tidak tersedia" #: builtin/gc.c -msgid "another process is scheduling background maintenance" -msgstr "proses lainnya sedang menjadwalkan peme" +#, c-format +msgid "" +"unable to create '%s.lock': %s.\n" +"\n" +"Another scheduled git-maintenance(1) process seems to be running in this\n" +"repository. Please make sure no other maintenance processes are running and\n" +"then try again. If it still fails, a git-maintenance(1) process may have\n" +"crashed in this repository earlier: remove the file manually to continue." +msgstr "" +"Tidak dapat membuat '%s.lock': %s.\n" +"\n" +"Sepertinya proses git-maintenance(1) lainnya berjalan pada repositori ini.\n" +"Pastikan tidak ada proses pemeliharaan lainnya yang berjalan dan coba lagi.\n" +"Jika masih gagal, suatu proses git-maintenance(1) bisa jadi hancur pada\n" +"repositori ini sebelumnya: hapus berkas secara manual untuk melanjutkan." + +#: builtin/gc.c +msgid "cannot acquire lock for scheduled background maintenance" +msgstr "tidak dapat memperoleh kunci untuk pemeliharran balik layar terjadwal" #: builtin/gc.c msgid "git maintenance start [--scheduler=]" @@ -9297,6 +9332,23 @@ msgid_plural "chain length = %d: %lu objects" msgstr[0] "panjang rantai = %d: %lu objek" msgstr[1] "panjang rantai = %d: %lu objek" +#: builtin/index-pack.c +msgid "could not start pack-objects to repack local links" +msgstr "tidak dapat memulai pack-objects untuk mempak ulang tautan lokal" + +#: builtin/index-pack.c +msgid "failed to feed local object to pack-objects" +msgstr "tidak dapat memasukkan objek ke pada pack-objects" + +#: builtin/index-pack.c +msgid "index-pack: Expecting full hex object ID lines only from pack-objects." +msgstr "" +"index-pack: Mengharapkan hanya baris hex ID objek penuh daripack-objects." + +#: builtin/index-pack.c +msgid "could not finish pack-objects to repack local links" +msgstr "tidak dapat menyelesaikan pack-objects untuk mempak ulang tautan lokal" + #: builtin/index-pack.c msgid "Cannot come back to cwd" msgstr "tidak dapat kembali ke direktori kerja saat ini" @@ -9311,6 +9363,10 @@ msgstr "%s jelek" msgid "unknown hash algorithm '%s'" msgstr "algoritma hash tak dikenal '%s'" +#: builtin/index-pack.c +msgid "--promisor cannot be used with a pack name" +msgstr "--promisor tidak dapat digunakan dengan nama pak" + #: builtin/index-pack.c msgid "--stdin requires a git repository" msgstr "--stdin memerlukan repositori git" @@ -11055,11 +11111,11 @@ msgstr "git notes [--ref ] [list []]" msgid "" "git notes [--ref ] add [-f] [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] add [-f] [--allow-empty] [--" "[no-]separator|--separator=] [--[no-]stripspace] [-m " -" | (-c | -C) ] []" +" | -F | (-c | -C) ] [] [-e]" #: builtin/notes.c msgid "git notes [--ref ] copy [-f] " @@ -11070,11 +11126,11 @@ msgstr "" msgid "" "git notes [--ref ] append [--allow-empty] [--[no-]separator|--" "separator=] [--[no-]stripspace] [-m | -F | (-c " -"| -C) ] []" +"| -C) ] [] [-e]" msgstr "" "git notes [--ref ] append [--alow-empty] [--" "[no]separator|--separator=] [--[no-]stripspace] [-m " -" | -F | (-c | -C) ] []" +" | -F | (-c | -C) ] [] [-e]" #: builtin/notes.c msgid "git notes [--ref ] edit [--allow-empty] []" @@ -11227,6 +11283,10 @@ msgstr "isi catatan di dalam berkas" msgid "reuse and edit specified note object" msgstr "gunakan ulang dan sunting objek catatan yang disebutkan" +#: builtin/notes.c +msgid "edit note message in editor" +msgstr "sunting pesan catatan dalam penyunting" + #: builtin/notes.c msgid "reuse specified note object" msgstr "gunakan ulang objek catatan yang disebutkan" @@ -11856,6 +11916,10 @@ msgstr "penanganan untuk objek yang hilang" msgid "do not pack objects in promisor packfiles" msgstr "jangan pak objek di dalam pak penjanji" +#: builtin/pack-objects.c +msgid "implies --missing=allow-any" +msgstr "mengimplikasikan --missing=allow-any" + #: builtin/pack-objects.c msgid "respect islands during delta compression" msgstr "patuhi pulau selama pemampatan delta" @@ -12269,8 +12333,8 @@ msgid "" msgstr "" "\n" "Untuk menghindari konfigurasi cabang hulu otomatis ketika namanya\n" -"tidak akan cocok dengan cabang lokal, lihat opsi 'simple' dari branch." -"autoSetupMerge\n" +"tidak akan cocok dengan cabang lokal, lihat opsi 'simple' dari " +"branch.autoSetupMerge\n" "di 'git help config'.\n" #: builtin/push.c @@ -13810,6 +13874,35 @@ msgid_plural " Local refs configured for 'git push'%s:" msgstr[0] " Referensi lokal dikonfigurasi untuk 'git push'%s:" msgstr[1] " Referensi lokal dikonfigurasi untuk 'git push'%s:" +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' is unchanged and points to '%s'\n" +msgstr "'%s/HEAD' tak berubah dan menunjuk pada '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' has changed from '%s' and now points to '%s'\n" +msgstr "'%s/HEAD' berubah dari '%s' dan sekarang menunjuk pada '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' is now created and points to '%s'\n" +msgstr "'%s/HEAD' sekarang dibuat dan menunjuk pada '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "'%s/HEAD' was detached at '%s' and now points to '%s'\n" +msgstr "%s/HEAD' terlepas pada '%s' dan sekarang menunjuk pada '%s'\n" + +#: builtin/remote.c +#, c-format +msgid "" +"'%s/HEAD' used to point to '%s' (which is not a remote branch), but now " +"points to '%s'\n" +msgstr "" +"'%s/HEAD' dulunya menunjuk pada '%s' (yang bukan cabang remote), tetapi " +"sekarang menunjuk pada '%s'\n" + #: builtin/remote.c msgid "set refs/remotes//HEAD according to remote" msgstr "setel refs/remotes//HEAD tergantung remote" @@ -13838,8 +13931,8 @@ msgstr "Bukan referensi valid: %s" #: builtin/remote.c #, c-format -msgid "Could not setup %s" -msgstr "Tidak dapat mengatur %s" +msgid "Could not set up %s" +msgstr "Tidak dapat menyiapkan %s" #: builtin/remote.c #, c-format @@ -17247,6 +17340,10 @@ msgstr "pasang mode pelacakan (lihat git-branch(1))" msgid "try to match the new branch name with a remote-tracking branch" msgstr "coba cocokkan nama cabang baru dengan sebuah cabang pelacakan remote" +#: builtin/worktree.c +msgid "use relative paths for worktrees" +msgstr "gunakan jalur relatif untuk pohon kerja" + #: builtin/worktree.c diff.c parse-options.c #, c-format msgid "options '%s', '%s', and '%s' cannot be used together" @@ -17588,6 +17685,31 @@ msgstr "tidak dapat membuat '%s'" msgid "index-pack died" msgstr "index-pack mati" +#: cache-tree.c +#, c-format +msgid "directory '%s' is present in index, but not sparse" +msgstr "direktori '%s' ada pada indeks, tapi bukan tipis" + +#: cache-tree.c unpack-trees.c +msgid "corrupted cache-tree has entries not present in index" +msgstr "pohon tembolok rusak mempunyai entri yang tidak ada pada indeks" + +#: cache-tree.c +#, c-format +msgid "%s with flags 0x%x should not be in cache-tree" +msgstr "%s dengan bendera 0x%x tidak boleh ada di pohon tembolok" + +#: cache-tree.c +#, c-format +msgid "bad subtree '%.*s'" +msgstr "subpohon jelek '%.*s'" + +#: cache-tree.c +#, c-format +msgid "cache-tree for path %.*s does not match. Expected %s got %s" +msgstr "" +"pohon tembolok untuk jalur %.*s tidak cocok. %s diharapkan tapi dapat %s" + #: chunk-format.c msgid "terminating chunk id appears earlier than expected" msgstr "id bingkah pengakhiran muncul lebih awal dari yang diharapkan" @@ -18714,16 +18836,16 @@ msgid "" "to convert the grafts into replace refs.\n" "\n" "Turn this message off by running\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" msgstr "" "Dukungan untuk /info/grafts usang dan akan dihapus\n" -"pada versi Git di masa yang akan datang.\n" +"pada versi Git mendatang.\n" "\n" "Mohon gunakan \"git replace --convert-graft-file\"\n" "untuk mengkonversi cangkuk ke referensi penggantian.\n" "\n" "Matikan pesan ini dengan menjalankan\n" -"\"git config advice.graftFileDeprecated false\"" +"\"git config set advice.graftFileDeprecated false\"" #: commit.c #, c-format @@ -19739,6 +19861,21 @@ msgstr "url tidak punya skema: %s" msgid "credential url cannot be parsed: %s" msgstr "url kredensial tidak dapat diuraikan: %s" +#: daemon.c +#, c-format +msgid "invalid timeout '%s', expecting a non-negative integer" +msgstr "timeout '%s' tidak valid, bilangan bulat non-negatif diharapkan" + +#: daemon.c +#, c-format +msgid "invalid init-timeout '%s', expecting a non-negative integer" +msgstr "init-timeout '%s' tidak valid, bilangan bulat non-negatif diharapkan" + +#: daemon.c +#, c-format +msgid "invalid max-connections '%s', expecting an integer" +msgstr "max-connections '%s' tidak valid, bilangan bulat diharapkan" + #: date.c msgid "in the future" msgstr "di masa depan" @@ -20631,6 +20768,21 @@ msgstr "jalur ruang nama git jelek \"%s\"" msgid "too many args to run %s" msgstr "terlalu banyak argumen untuk menjalankan %s" +#: fetch-pack.c +#, c-format +msgid "" +"You are attempting to fetch %s, which is in the commit graph file but not in " +"the object database.\n" +"This is probably due to repo corruption.\n" +"If you are attempting to repair this repo corruption by refetching the " +"missing object, use 'git fetch --refetch' with the missing object." +msgstr "" +"Anda mencoba mengambil %s, yang ada di dalam berkas grafik komit tapi bukan " +"di dalam basis data objek.\n" +"Bisa jadi ini dikarenakan kerusakan repositori.\n" +"Apabila Anda mencoba memperbaiki keruskanan repositori ini dengan mengambil " +"ulang objek yang hilang, gunakan 'git fetch --refetch' dengan objek tersebut." + #: fetch-pack.c msgid "git fetch-pack: expected shallow list" msgstr "git fetch-pack: daftar dangkal diharapkan" @@ -21351,11 +21503,11 @@ msgstr[1] "" #, c-format msgid "" "The '%s' hook was ignored because it's not set as executable.\n" -"You can disable this warning with `git config advice.ignoredHook false`." +"You can disable this warning with `git config set advice.ignoredHook false`." msgstr "" -"Kait '%s' diabaikan karena tidak disetel sebagai dapat dieksekusi.\n" -"Anda dapat menonaktifkan peringatan ini dengan `git config advice." -"ignoredHook false`." +"Kait '%s' diabaikan karena tidak disetel sebagai berkas yang dapat\n" +"dieksekusi. Anda dapat menonaktifkan peringatan ini dengan\n" +"`git config set advice.ignoredHook false`." #: http-fetch.c msgid "not a git repository" @@ -21375,18 +21527,10 @@ msgstr "nilai negatif untuk http.postBuffer; asalkan ke %d" msgid "Delegation control is not supported with cURL < 7.22.0" msgstr "Kontrol delegasi tidak didukung oleh cURL < 7.22.0" -#: http.c -msgid "Public key pinning not supported with cURL < 7.39.0" -msgstr "Penyematan kunci publik tidak didukung oleh cURL < 7.39.0" - #: http.c msgid "Unknown value for http.proactiveauth" msgstr "nilai tidak dikenal untuk http.proactiveauth" -#: http.c -msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -msgstr "CURLSSLOPT_NO_REVOKE tidak didukung dengan cURL < 7.44.0" - #: http.c #, c-format msgid "Unsupported SSL backend '%s'. Supported SSL backends:" @@ -21609,6 +21753,11 @@ msgstr "CRLF terkutip terdeteksi" msgid "unable to format message: %s" msgstr "tidak dapat memformat pesan: %s" +#: merge-ll.c +#, c-format +msgid "invalid marker-size '%s', expecting an integer" +msgstr "marker-size '%s' tidak valid, bilangan bulat diharapkan" + #: merge-ort.c merge-recursive.c #, c-format msgid "Failed to merge submodule %s (not checked out)" @@ -22955,19 +23104,17 @@ msgid "" "\n" "where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" "examine these refs and maybe delete them. Turn this message off by\n" -"running \"git config advice.objectNameWarning false\"" +"running \"git config set advice.objectNameWarning false\"" msgstr "" "Biasanya Git tidak pernah membuat sebuah referensi yang diakhiri dengan\n" -"40 karakter hex, karena akan diabaikan ketika Anda hanya menyebutkan 40-" -"hex.\n" +"40 karakter hex, karena akan diabaikan ketika Anda hanya merincikan 40-hex.\n" "Referensi tersebut bisa tidak sengaja dibuat. Misalnya,\n" "\n" " git switch -c $br $(git rev-parse ...)\n" "\n" "dimana \"$br\" entah bagaimana kosong dan referensi 40-hex dibuat.\n" -"Mohon periksa referensi tersebut dan mungkin hapus. Matikan pesan ini " -"dengan\n" -"menjalankan \"git config advice.objectNameWarning false\"" +"Mohon periksa referensi tersebut dan mungkin hapus. Matikan pesan ini\n" +"dengan menjalankan \"git config set advice.objectNameWarning false\"" #: object-name.c #, c-format @@ -23166,15 +23313,6 @@ msgstr "bitmap multipak kehilangan indeks balik yang diperlukan" msgid "could not open pack %s" msgstr "tidak dapat membuka '%s'" -#: pack-bitmap.c t/helper/test-read-midx.c -msgid "could not determine MIDX preferred pack" -msgstr "tidak dapat menentukan pak MIDX terpilih" - -#: pack-bitmap.c -#, c-format -msgid "preferred pack (%s) is invalid" -msgstr "pak yang disukai '%s' kadaluarsa" - #: pack-bitmap.c msgid "corrupt bitmap lookup table: triplet position out of index" msgstr "tabel pencarian bitmap rusak: posisi kembar tiga di luar indeks" @@ -24546,8 +24684,19 @@ msgid "log for %s is empty" msgstr "log untuk %s kosong" #: refs.c -msgid "refusing to force and skip creation of reflog" -msgstr "menolak memaksa dan melewatkan pembuatan reflog" +#, c-format +msgid "refusing to update reflog for pseudoref '%s'" +msgstr "menolak memperbarui reflog untuk referensi semu '%s'" + +#: refs.c +#, c-format +msgid "refusing to update pseudoref '%s'" +msgstr "menolak memperbarui referensi semu '%s'" + +#: refs.c +#, c-format +msgid "refusing to update reflog with bad name '%s'" +msgstr "menolak memperbarui reflog dengan nama jelek '%s'" #: refs.c #, c-format @@ -24555,9 +24704,8 @@ msgid "refusing to update ref with bad name '%s'" msgstr "menolak memperbarui referensi dengan nama jelek '%s'" #: refs.c -#, c-format -msgid "refusing to update pseudoref '%s'" -msgstr "menolak memperbarui referensi semu '%s'" +msgid "refusing to force and skip creation of reflog" +msgstr "menolak memaksa dan melewatkan pembuatan reflog" #: refs.c #, c-format @@ -24620,6 +24768,11 @@ msgstr "" "tidak dapat mengunci referensi '%s': diharapkan referensi simbolik dengan " "target '%s': tetapi bukan referensi reguler" +#: refs/files-backend.c +#, c-format +msgid "cannot read ref file '%s'" +msgstr "tidak dapat membaca berkas referensi '%s'" + #: refs/files-backend.c #, c-format msgid "cannot open directory %s" @@ -24883,6 +25036,11 @@ msgstr "lebih dari satu paket terima diberikan, gunakan yang pertama" msgid "more than one uploadpack given, using the first" msgstr "lebih dari satu paket unggah diberikan, gunakan yang pertama" +#: remote.c +#, c-format +msgid "unrecognized followRemoteHEAD value '%s' ignored" +msgstr "nilai followRemoteHEAD yang tak dikenal '%s' diabaikan" + #: remote.c #, c-format msgid "unrecognized value transfer.credentialsInUrl: '%s'" @@ -27240,6 +27398,10 @@ msgstr "komit %s tidak ditandai sebagai dapat dicapai" msgid "too many commits marked reachable" msgstr "terlalu banyak komit yang ditandai sebagai dapat dicapai" +#: t/helper/test-read-midx.c +msgid "could not determine MIDX preferred pack" +msgstr "tidak dapat menentukan pak MIDX terpilih" + #: t/helper/test-serve-v2.c msgid "test-tool serve-v2 []" msgstr "test-tool serve-v2 []" @@ -28026,6 +28188,10 @@ msgstr "berkas .git rusak" msgid ".git file incorrect" msgstr "berkas .git salah" +#: worktree.c +msgid ".git file absolute/relative path mismatch" +msgstr "jalur absolut/relative berkas .git tidak cocok" + #: worktree.c msgid "not a valid path" msgstr "bukan jalur valid" @@ -28047,6 +28213,10 @@ msgstr "tidak dapat menempatkan repositori; berkas .git rusak" msgid "gitdir unreadable" msgstr "gitdir tidak dapat dibaca" +#: worktree.c +msgid "gitdir absolute/relative path mismatch" +msgstr "jalur absolut/relatif gitdir tidak cocok" + #: worktree.c msgid "gitdir incorrect" msgstr "gitdir salah" @@ -28091,6 +28261,16 @@ msgstr "tidak dapat menyetel balik %s di '%s'" msgid "failed to set extensions.worktreeConfig setting" msgstr "gagal menyetel setelan extensions.worktreeConfig" +#: worktree.c +msgid "unable to upgrade repository format to support relative worktrees" +msgstr "" +"tidak dapat meningkatkan format repositori untuk mendukung pohon kerja " +"relatif" + +#: worktree.c +msgid "unable to set extensions.relativeWorktrees setting" +msgstr "gagal menyetel setelan extensions.relativeWorktrees" + #: wrapper.c #, c-format msgid "could not setenv '%s'" From 233d48f5de1d6cdba0eb64165212dfbf6fa444d7 Mon Sep 17 00:00:00 2001 From: Bence Ferdinandy Date: Sun, 5 Jan 2025 17:09:40 +0100 Subject: [PATCH 026/306] fetch: fix erroneous set_head advice message 9e2b7005be (fetch set_head: add warn-if-not-$branch option, 2024-12-05) tried to expand the advice message for set_head with the new option, but unfortunately did not manage to add the right incantation. Fix the advice message with the correct usage of warn-if-not-$branch. Reported-by: Teng Long Signed-off-by: Bence Ferdinandy Signed-off-by: Junio C Hamano --- builtin/fetch.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin/fetch.c b/builtin/fetch.c index c4257a7ead740d..ba464d5521dc95 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1585,8 +1585,8 @@ static void set_head_advice_msg(const char *remote, const char *head_name) N_("Run 'git remote set-head %s %s' to follow the change, or set\n" "'remote.%s.followRemoteHEAD' configuration option to a different value\n" "if you do not want to see this message. Specifically running\n" - "'git config set remote.%s.followRemoteHEAD %s' will disable the warning\n" - "until the remote changes HEAD to something else."); + "'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n" + "will disable the warning until the remote changes HEAD to something else."); advise_if_enabled(ADVICE_FETCH_SET_HEAD_WARN, _(message_advice_set_head), remote, head_name, remote, remote, head_name); From 14650065b76b28d3cfa9453356ac5669b19e706e Mon Sep 17 00:00:00 2001 From: Kristoffer Haugsbakk Date: Tue, 7 Jan 2025 18:37:06 +0100 Subject: [PATCH 027/306] RelNotes/2.48.0: fix typos etc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct verb tense, add missing words, avoid double blank lines, and rephrase things that don’t read well to me like “Turn this linkage to relative paths”. Signed-off-by: Kristoffer Haugsbakk Signed-off-by: Junio C Hamano --- Documentation/RelNotes/2.48.0.txt | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/Documentation/RelNotes/2.48.0.txt b/Documentation/RelNotes/2.48.0.txt index d62c62dc17ad0b..eff93be37a2269 100644 --- a/Documentation/RelNotes/2.48.0.txt +++ b/Documentation/RelNotes/2.48.0.txt @@ -47,15 +47,15 @@ Performance, Internal Implementation, Development Support etc. * The way AsciiDoc is used for SYNOPSIS part of the manual pages has been revamped. The sources, at least for the simple cases, got - vastly pleasant to work with. + vastly more pleasant to work with. * The reftable library is now prepared to expect that the memory allocation function given to it may fail to allocate and to deal with such an error. * An extra worktree attached to a repository points at each other to - allow finding the repository from the worktree and vice versa - possible. Turn this linkage to relative paths. + allow finding the repository from the worktree (and vice versa) + possible. Use relative paths for this linkage. * Enable Windows-based CI in GitLab. @@ -94,7 +94,7 @@ Performance, Internal Implementation, Development Support etc. * Update the project's CodingGuidelines to discourage naming functions with a "_1()" suffix. - * Updates the '.clang-format' to match project conventions. + * Update '.clang-format' to match project conventions. * Centralize documentation for repository extensions into a single place. @@ -116,7 +116,7 @@ Performance, Internal Implementation, Development Support etc. * The migration procedure between two ref backends has been optimized. * "git fsck" learned to issue warnings on "curiously formatted" ref - contents that have always been taken valid but something Git + contents that have always been treated as valid but that Git wouldn't have written itself (e.g., missing terminating end-of-line after the full object name). @@ -127,13 +127,13 @@ Performance, Internal Implementation, Development Support etc. * Drop support for ancient environments in various CI jobs. - * Isolates the reftable subsystem from the rest of Git's codebase by + * Isolate the reftable subsystem from the rest of Git's codebase by using fewer pieces of Git's infrastructure. * Optimize reading random references out of the reftable backend by allowing reuse of iterator objects. - * Backport oss-fuzz tests for us to our codebase. + * Backport oss-fuzz tests to our codebase. * Introduce a new repository extension to prevent older Git versions from mis-interpreting worktrees created with relative paths. @@ -173,13 +173,13 @@ Fixes since v2.47 ----------------- * Doc update to clarify how periodical maintenance are scheduled, - spread across time to avoid thundering hurds. + spread across time to avoid thundering herds. * Use after free and double freeing at the end in "git log -L... -p" had been identified and fixed. * On macOS, fsmonitor can fall into a race condition that results in - a client waiting forever to be notified for an event that have + a client waiting forever to be notified about an event that has already happened. This problem has been corrected. * "git maintenance start" crashed due to an uninitialized variable @@ -203,7 +203,7 @@ Fixes since v2.47 * The dumb-http code regressed when the result of re-indexing a pack yielded an *.idx file that differs in content from the *.idx file it downloaded from the remote. This has been corrected by no longer - relying on: the *.idx file we got from the remote. + relying on the *.idx file we got from the remote. * When called with '--left-right' and '--use-bitmap-index', 'rev-list' will produce output without any left/right markers, which has been @@ -227,7 +227,7 @@ Fixes since v2.47 * "git gc" discards any objects that are outside promisor packs that are referred to by an object in a promisor pack, and we do not refetch them from the promisor at runtime, resulting an unusable - repository. Work it around by including these objects in the + repository. Work around it by including these objects in the referring promisor pack at the receiving end of the fetch. * Avoid build/test breakage on a system without working malloc debug @@ -282,9 +282,8 @@ Fixes since v2.47 on the path, which was not documented, which has been corrected. (merge bc1a980759 kk/doc-ancestry-path later to maint). - * "git tag" has been taught to refuse to create refs/tags/HEAD - as such a tag will be confusing in the context of UI provided by + since such a tag will be confusing in the context of the UI provided by the Git Porcelain commands. (merge bbd445d5ef jc/forbid-head-as-tagname later to maint). From 45c0897204cfb5d0d88370a4907e41b0eb717b6c Mon Sep 17 00:00:00 2001 From: Sam James Date: Wed, 8 Jan 2025 03:42:37 +0000 Subject: [PATCH 028/306] meson: fix perl dependencies `generate_perl_command` needs `depends: [git_version_file]` and the uses in top-level meson.build were fine, but the ones in perl/ weren't, causing parallel build failures in some cases as GIT-BUILD-OPTIONS wasn't yet available. Signed-off-by: Sam James Acked-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- perl/FromCPAN/Mail/meson.build | 1 + perl/FromCPAN/meson.build | 1 + perl/Git/LoadCPAN/Mail/meson.build | 1 + perl/Git/LoadCPAN/meson.build | 1 + perl/Git/SVN/Memoize/meson.build | 1 + perl/Git/SVN/meson.build | 1 + perl/Git/meson.build | 1 + perl/meson.build | 1 + 8 files changed, 8 insertions(+) diff --git a/perl/FromCPAN/Mail/meson.build b/perl/FromCPAN/Mail/meson.build index 129cff161c5856..b4ff2fc0b24c95 100644 --- a/perl/FromCPAN/Mail/meson.build +++ b/perl/FromCPAN/Mail/meson.build @@ -4,4 +4,5 @@ test_dependencies += custom_target( command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5/FromCPAN/Mail', + depends: [git_version_file], ) diff --git a/perl/FromCPAN/meson.build b/perl/FromCPAN/meson.build index 4e7ea909df3593..1f9ea6ce8e8442 100644 --- a/perl/FromCPAN/meson.build +++ b/perl/FromCPAN/meson.build @@ -4,6 +4,7 @@ test_dependencies += custom_target( command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5/FromCPAN', + depends: [git_version_file], ) subdir('Mail') diff --git a/perl/Git/LoadCPAN/Mail/meson.build b/perl/Git/LoadCPAN/Mail/meson.build index 7da5b37adb278b..89cde56be84912 100644 --- a/perl/Git/LoadCPAN/Mail/meson.build +++ b/perl/Git/LoadCPAN/Mail/meson.build @@ -4,4 +4,5 @@ test_dependencies += custom_target( command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5/Git/LoadCPAN/Mail', + depends: [git_version_file], ) diff --git a/perl/Git/LoadCPAN/meson.build b/perl/Git/LoadCPAN/meson.build index 9468c073aeb5ae..1ee915c650517d 100644 --- a/perl/Git/LoadCPAN/meson.build +++ b/perl/Git/LoadCPAN/meson.build @@ -4,6 +4,7 @@ test_dependencies += custom_target( command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5/Git/LoadCPAN', + depends: [git_version_file], ) subdir('Mail') diff --git a/perl/Git/SVN/Memoize/meson.build b/perl/Git/SVN/Memoize/meson.build index 515ab3dd926618..233ec670d7de91 100644 --- a/perl/Git/SVN/Memoize/meson.build +++ b/perl/Git/SVN/Memoize/meson.build @@ -4,4 +4,5 @@ test_dependencies += custom_target( command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5/Git/SVN', + depends: [git_version_file], ) diff --git a/perl/Git/SVN/meson.build b/perl/Git/SVN/meson.build index 8338531041d8e5..44abaf42b7cea3 100644 --- a/perl/Git/SVN/meson.build +++ b/perl/Git/SVN/meson.build @@ -14,6 +14,7 @@ foreach source : [ command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5/Git/SVN', + depends: [git_version_file], ) endforeach diff --git a/perl/Git/meson.build b/perl/Git/meson.build index 259209d7302809..b21fa5591e7e79 100644 --- a/perl/Git/meson.build +++ b/perl/Git/meson.build @@ -11,6 +11,7 @@ foreach source : [ command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5/Git', + depends: [git_version_file], ) endforeach diff --git a/perl/meson.build b/perl/meson.build index c22d6f8a1a3266..2d4ab1c4a986f7 100644 --- a/perl/meson.build +++ b/perl/meson.build @@ -4,6 +4,7 @@ test_dependencies += custom_target( command: generate_perl_command, install: true, install_dir: get_option('datadir') / 'perl5', + depends: [git_version_file], ) subdir('Git') From d02c37c3e6baf1515e7d1372afa5941b9518ca5b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 8 Jan 2025 16:00:05 +0000 Subject: [PATCH 029/306] t-reftable-basics: allow for `malloc` to be `#define`d MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As indicated by the `#undef malloc` line in `reftable/basics.h`, it is quite common to use allocators other than the default one by defining `malloc` constants and friends. This pattern is used e.g. in Git for Windows, which uses the powerful and performant `mimalloc` allocator. Furthermore, in `reftable/basics.c` this `#undef malloc` is _specifically_ disabled by virtue of defining the `REFTABLE_ALLOW_BANNED_ALLOCATORS` constant before including `reftable/basic.h`, to ensure that such a custom allocator is also used in the reftable code. However, in 8db127d43f5b (reftable: avoid leaks on realloc error, 2024-12-28) and in 2cca185e8517 (reftable: fix allocation count on realloc error, 2024-12-28), `reftable_set_alloc()` function calls were introduced that pass `malloc`, `realloc` and `free` function pointers as parameters _after_ `reftable/basics.h` ensured that they were no longer `#define`d. This would override the custom allocator and re-set it to the default allocator provided by, say, libc or MSVCRT. This causes problems because those calls happen after the initial allocator has already been used to initialize an array, which is subsequently resized using the overridden default `realloc()` allocator. You cannot mix and match allocators like that, which leads to a `STATUS_HEAP_CORRUPTION` (C0000374) on Windows, and when running this unit test through shell and/or `prove` (which only support 7-bit status codes), it surfaces as exit code 127. It is actually unnecessary to use those function pointers to `malloc`/`realloc`/`free`, though: The `reftable` code goes out of its way to fall back to the initial allocator when passing `NULL` parameters instead. So let's do that instead of causing heap corruptions. Signed-off-by: Johannes Schindelin Acked-by: René Scharfe Signed-off-by: Junio C Hamano --- t/unit-tests/t-reftable-basics.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c index 990dc1a2445ddd..1d640b280f9f77 100644 --- a/t/unit-tests/t-reftable-basics.c +++ b/t/unit-tests/t-reftable-basics.c @@ -157,13 +157,13 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED) old_alloc = alloc; old_arr = arr; - reftable_set_alloc(malloc, realloc_stub, free); + reftable_set_alloc(NULL, realloc_stub, NULL); check(REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc)); check(arr == old_arr); check_uint(alloc, ==, old_alloc); old_alloc = alloc; - reftable_set_alloc(malloc, realloc, free); + reftable_set_alloc(NULL, NULL, NULL); check(!REFTABLE_ALLOC_GROW(arr, old_alloc + 1, alloc)); check(arr != NULL); check_uint(alloc, >, old_alloc); @@ -188,11 +188,11 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED) arr[alloc - 1] = 42; old_alloc = alloc; - reftable_set_alloc(malloc, realloc_stub, free); + reftable_set_alloc(NULL, realloc_stub, NULL); REFTABLE_ALLOC_GROW_OR_NULL(arr, old_alloc + 1, alloc); check(arr == NULL); check_uint(alloc, ==, 0); - reftable_set_alloc(malloc, realloc, free); + reftable_set_alloc(NULL, NULL, NULL); reftable_free(arr); } From 1fbb8d7ecb7bd78ac55d226b6b073372a5ea2c2d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 10 Jan 2025 12:26:17 +0100 Subject: [PATCH 030/306] builtin/blame: fix out-of-bounds read with excessive `--abbrev` In 6411a0a896 (builtin/blame: fix type of `length` variable when emitting object ID, 2024-12-06) we have fixed the type of the `length` variable. In order to avoid a cast from `size_t` to `int` in the call to printf(3p) with the "%.*s" formatter we have converted the code to instead use fwrite(3p), which accepts the length as a `size_t`. It was reported though that this makes us read over the end of the OID array when the provided `--abbrev=` length exceeds the length of the object ID. This is because fwrite(3p) of course doesn't stop when it sees a NUL byte, whereas printf(3p) does. Fix the bug by reverting back to printf(3p) and culling the provided length to `GIT_MAX_HEXSZ` to keep it from overflowing when cast to an `int`. Reported-by: Johannes Schindelin Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/blame.c | 3 ++- t/t8002-blame.sh | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/builtin/blame.c b/builtin/blame.c index 867032e4c16878..d7630ac89cb7bd 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -505,7 +505,8 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int length--; putchar('?'); } - fwrite(hex, 1, length, stdout); + + printf("%.*s", (int)(length < GIT_MAX_HEXSZ ? length : GIT_MAX_HEXSZ), hex); if (opt & OUTPUT_ANNOTATE_COMPAT) { const char *name; if (opt & OUTPUT_SHOW_EMAIL) diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index 0147de304b4d10..b3f8b63d2e6744 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -126,6 +126,14 @@ test_expect_success '--no-abbrev works like --abbrev with full length' ' check_abbrev $hexsz --no-abbrev ' +test_expect_success 'blame --abbrev gets truncated' ' + check_abbrev $hexsz --abbrev=9000 HEAD +' + +test_expect_success 'blame --abbrev gets truncated with boundary commit' ' + check_abbrev $hexsz --abbrev=9000 ^HEAD +' + test_expect_success '--exclude-promisor-objects does not BUG-crash' ' test_must_fail git blame --exclude-promisor-objects one ' From e7fb2ca94556e6aadfc3038afaa1c8cc3525258c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Fri, 10 Jan 2025 12:26:18 +0100 Subject: [PATCH 031/306] builtin/blame: fix out-of-bounds write with blank boundary commits When passing the `-b` flag to git-blame(1), then any blamed boundary commits which were marked as uninteresting will not get their actual commit ID printed, but will instead be replaced by a couple of spaces. The flag can lead to an out-of-bounds write as though when combined with `--abbrev=` when the abbreviation length is longer than `GIT_MAX_HEXSZ` as we simply use memset(3p) on that array with the user-provided length directly. The result is most likely that we segfault. An obvious fix would be to cull `length` to `GIT_MAX_HEXSZ` many bytes. But when the underlying object ID is SHA1, and if the abbreviated length exceeds the SHA1 length, it would cause us to print more bytes than desired, and the result would be misaligned. Instead, fix the bug by computing the length via strlen(3p). This makes us write as many bytes as the formatted object ID requires and thus effectively limits the length of what we may end up printing to the length of its hash. If `--abbrev=` asks us to abbreviate to something shorter than the full length of the underlying hash function it would be handled by the call to printf(3p) correctly. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/blame.c | 6 +++--- t/t8002-blame.sh | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/builtin/blame.c b/builtin/blame.c index d7630ac89cb7bd..7555c445abe7ca 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -489,9 +489,9 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int fputs(color, stdout); if (suspect->commit->object.flags & UNINTERESTING) { - if (blank_boundary) - memset(hex, ' ', length); - else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) { + if (blank_boundary) { + memset(hex, ' ', strlen(hex)); + } else if (!(opt & OUTPUT_ANNOTATE_COMPAT)) { length--; putchar('^'); } diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index b3f8b63d2e6744..1ad039e1234828 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -134,6 +134,24 @@ test_expect_success 'blame --abbrev gets truncated with boundary commit' ' check_abbrev $hexsz --abbrev=9000 ^HEAD ' +test_expect_success 'blame --abbrev -b truncates the blank boundary' ' + # Note that `--abbrev=` always gets incremented by 1, which is why we + # expect 11 leading spaces and not 10. + cat >expect <<-EOF && + $(printf "%0.s " $(test_seq 11)) ( 2005-04-07 15:45:13 -0700 1) abbrev + EOF + git blame -b --abbrev=10 ^HEAD -- abbrev.t >actual && + test_cmp expect actual +' + +test_expect_success 'blame with excessive --abbrev and -b culls to hash length' ' + cat >expect <<-EOF && + $(printf "%0.s " $(test_seq $hexsz)) ( 2005-04-07 15:45:13 -0700 1) abbrev + EOF + git blame -b --abbrev=9000 ^HEAD -- abbrev.t >actual && + test_cmp expect actual +' + test_expect_success '--exclude-promisor-objects does not BUG-crash' ' test_must_fail git blame --exclude-promisor-objects one ' From 64f3ff3ffcfed10b8b5bf42cda3fc19776fb29f2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 10 Jan 2025 11:48:37 +0000 Subject: [PATCH 032/306] GIT-VERSION-GEN: allow it to be run in parallel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Why would one want to run it in parallel?" I hear you ask. I am glad you are curious, because a curious story is what it is, indeed. The `GIT-VERSION-GEN` script is quite a pillar of Git's source code, with most lines being unchanged for the past 15 years. Until the v2.48.0 release candidate cycle. Its original purpose was to generate the version string and store it in the `GIT-VERSION-FILE`. This paradigm changed quite dramatically when support for building with Meson was introduced. Most crucially, a38edab7c88b (Makefile: generate doc versions via GIT-VERSION-GEN, 2024-12-06) changed the way the documentation is built by using the `GIT-VERSION-GEN` file to write out the `asciidocor-extensions.rb` and `asciidoc.conf` files with now hard-coded version strings. Crucially, the Makefile rule to generate those files needs to be run in every build because `GIT_VERSION` could have been specified in the `make` command-line, which would require these files to be modified. This introduced a surprising race condition! And this is how that race surfaces: When calling `make -j2 html man` from the top-level directory (a variant of which is invoked in Git for Windows' release process), two sub-processes are spawned, a `make -C Documentation html` one and a `make -C Documentation man` one. Both run the rule to (re-)generate `asciidoctor-extensions.rb` or `asciidoc.conf`, invoking `GIT-VERSION-GEN` to do so. That script first generates a temporary file (appending the `+` character to the filename), then looks whether it contains something different than the already existing file (if it exists, that is), and either replaces it if needed, or removes the temporary file. If one of the two parallel invocations removes that temporary file before the other can compare it, or even worse: if one tries to replace the target file just after the other _started_ writing the temporary file (but did not finish writing it yet), that race condition now causes bad builds. This may sound highly theoretical, but due to the design of Git's build process, Git for Windows is forced to use a (slow) POSIX emulation layer to run that script and in the blink of an eye it becomes very much not theoretical at all. See Exhibit A: These GitHub workflow runs failed because one of the two competing `make` processes tried to remove the temporary file when the other process had already done so: https://github.com/git-for-windows/git-sdk-32/actions/runs/12663456654 https://github.com/git-for-windows/git-sdk-32/actions/runs/12683174970 https://github.com/git-for-windows/git-sdk-64/actions/runs/12649348496 While it is undesirable to run this script over and over again, certainly when this involves above-mentioned slow POSIX emulation layer, the stage of the release cycle in which we are presently finding ourselves does not lend itself to a re-design where this script could be run once, and once only, but instead dictates that a quick and reliable work-around be implemented that prevents the race condition without changing the overall architecture of the build process. This patch does that: By using a filename suffix for the temporary file which is based on the currently-executing script's process ID, We guarantee that the two competing invocations cannot overwrite or remove each others' temporary files. The filename suffix still ends in `+` to ensure that the temporary artifacts are matched by the `*+` pattern in `.gitignore` that was added in f9bbaa384ef (Add intermediate build products to .gitignore, 2009-11-08). Helped-by: Martin Ågren Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- GIT-VERSION-GEN | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 6d1cb66d69a6d0..2e2d0811581fb4 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -86,11 +86,11 @@ sed -e "s|@GIT_VERSION@|$GIT_VERSION|" \ -e "s|@GIT_BUILT_FROM_COMMIT@|$GIT_BUILT_FROM_COMMIT|" \ -e "s|@GIT_USER_AGENT@|$GIT_USER_AGENT|" \ -e "s|@GIT_DATE@|$GIT_DATE|" \ - "$INPUT" >"$OUTPUT"+ + "$INPUT" >"$OUTPUT".$$+ -if ! test -f "$OUTPUT" || ! cmp "$OUTPUT"+ "$OUTPUT" >/dev/null +if ! test -f "$OUTPUT" || ! cmp "$OUTPUT".$$+ "$OUTPUT" >/dev/null then - mv "$OUTPUT"+ "$OUTPUT" + mv "$OUTPUT".$$+ "$OUTPUT" else - rm "$OUTPUT"+ + rm "$OUTPUT".$$+ fi From fbe8d3079d4a96aeb4e4529cc93cc0043b759a05 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 10 Jan 2025 09:20:20 -0800 Subject: [PATCH 033/306] Git 2.48 Signed-off-by: Junio C Hamano --- GIT-VERSION-GEN | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 2e2d0811581fb4..f2af817feaeb38 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.48.0-rc2 +DEF_VER=v2.48.0 LF=' ' From bbc14d71076aa9afffe88a6eae2fc0db60a89bf9 Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Sun, 24 Jul 2011 15:54:04 +0200 Subject: [PATCH 034/306] t9350: point out that refs are not updated correctly This happens only when the corresponding commits are not exported in the current fast-export run. This can happen either when the relevant commit is already marked, or when the commit is explicitly marked as UNINTERESTING with a negative ref by another argument. This breaks fast-export basec remote helpers. Signed-off-by: Sverre Rabbelier --- t/t9350-fast-export.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 40427883ec6dbf..abffd267e259c4 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -801,4 +801,15 @@ test_expect_success 'fast-export handles --end-of-options' ' test_cmp expect actual ' +cat > expected << EOF +reset refs/heads/master +from $(git rev-parse master) + +EOF + +test_expect_failure 'refs are updated even if no commits need to be exported' ' + git fast-export master..master > actual && + test_cmp expected actual +' + test_done From b5fe0f6cb4c6643e73d190a154baf66e69843795 Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Sat, 28 Aug 2010 20:49:01 -0500 Subject: [PATCH 035/306] transport-helper: add trailing -- [PT: ensure we add an additional element to the argv array] Signed-off-by: Sverre Rabbelier Signed-off-by: Johannes Schindelin --- transport-helper.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/transport-helper.c b/transport-helper.c index d457b425501a74..fed5d51a2b3d34 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -499,6 +499,8 @@ static int get_exporter(struct transport *transport, for (size_t i = 0; i < revlist_args->nr; i++) strvec_push(&fastexport->args, revlist_args->items[i].string); + strvec_push(&fastexport->args, "--"); + fastexport->git_cmd = 1; return start_command(fastexport); } From c74f47bdb3d25923d70066dfe70046230dfa18e9 Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Sun, 24 Jul 2011 00:06:00 +0200 Subject: [PATCH 036/306] remote-helper: check helper status after import/export Signed-off-by: Johannes Schindelin Signed-off-by: Sverre Rabbelier --- t/t5801-remote-helpers.sh | 2 +- transport-helper.c | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index d21877150ed82e..3917da47276825 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -262,7 +262,7 @@ test_expect_success 'push update refs failure' ' echo "update fail" >>file && git commit -a -m "update fail" && git rev-parse --verify testgit/origin/heads/update >expect && - test_expect_code 1 env GIT_REMOTE_TESTGIT_FAILURE="non-fast forward" \ + test_must_fail env GIT_REMOTE_TESTGIT_FAILURE="non-fast forward" \ git push origin update && git rev-parse --verify testgit/origin/heads/update >actual && test_cmp expect actual diff --git a/transport-helper.c b/transport-helper.c index fed5d51a2b3d34..72588c4d14e3ff 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -505,6 +505,19 @@ static int get_exporter(struct transport *transport, return start_command(fastexport); } +static void check_helper_status(struct helper_data *data) +{ + int pid, status; + + pid = waitpid(data->helper->pid, &status, WNOHANG); + if (pid < 0) + die("Could not retrieve status of remote helper '%s'", + data->name); + if (pid > 0 && WIFEXITED(status)) + die("Remote helper '%s' died with %d", + data->name, WEXITSTATUS(status)); +} + static int fetch_with_import(struct transport *transport, int nr_heads, struct ref **to_fetch) { @@ -541,6 +554,7 @@ static int fetch_with_import(struct transport *transport, if (finish_command(&fastimport)) die(_("error while running fast-import")); + check_helper_status(data); /* * The fast-import stream of a remote helper that advertises @@ -1160,6 +1174,7 @@ static int push_refs_with_export(struct transport *transport, if (finish_command(&exporter)) die(_("error while running fast-export")); + check_helper_status(data); if (push_update_refs_status(data, remote_refs, flags)) return 1; From 6166ef7964b05c1e3771025e428c576d145c26c2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 19 Jan 2023 13:40:31 +0100 Subject: [PATCH 037/306] gitk(Windows): avoid inadvertently calling executables in the worktree Just like CVE-2022-41953 for Git GUI, there exists a vulnerability of `gitk` where it looks for `taskkill.exe` in the current directory before searching `PATH`. Note that the many `exec git` calls are unaffected, due to an obscure quirk in Tcl's `exec` function. Typically, `git.exe` lives next to `wish.exe` (i.e. the program that is run to execute `gitk` or Git GUI) in Git for Windows, and that is the saving grace for `git.exe because `exec` searches the directory where `wish.exe` lives even before the current directory, according to https://www.tcl-lang.org/man/tcl/TclCmd/exec.htm#M24: If a directory name was not specified as part of the application name, the following directories are automatically searched in order when attempting to locate the application: The directory from which the Tcl executable was loaded. The current directory. The Windows 32-bit system directory. The Windows home directory. The directories listed in the path. The same is not true, however, for `taskkill.exe`: it lives in the Windows system directory (never mind the 32-bit, Tcl's documentation is outdated on that point, it really means `C:\Windows\system32`). Signed-off-by: Johannes Schindelin --- gitk-git/gitk | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/gitk-git/gitk b/gitk-git/gitk index 47a7c1d29c430f..5c8790dccd2683 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -9,6 +9,141 @@ exec wish "$0" -- "$@" package require Tk +###################################################################### +## +## Enabling platform-specific code paths + +proc is_MacOSX {} { + if {[tk windowingsystem] eq {aqua}} { + return 1 + } + return 0 +} + +proc is_Windows {} { + if {$::tcl_platform(platform) eq {windows}} { + return 1 + } + return 0 +} + +set _iscygwin {} +proc is_Cygwin {} { + global _iscygwin + if {$_iscygwin eq {}} { + if {[string match "CYGWIN_*" $::tcl_platform(os)]} { + set _iscygwin 1 + } else { + set _iscygwin 0 + } + } + return $_iscygwin +} + +###################################################################### +## +## PATH lookup + +set _search_path {} +proc _which {what args} { + global env _search_exe _search_path + + if {$_search_path eq {}} { + if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} { + set _search_path [split [exec cygpath \ + --windows \ + --path \ + --absolute \ + $env(PATH)] {;}] + set _search_exe .exe + } elseif {[is_Windows]} { + set gitguidir [file dirname [info script]] + regsub -all ";" $gitguidir "\\;" gitguidir + set env(PATH) "$gitguidir;$env(PATH)" + set _search_path [split $env(PATH) {;}] + # Skip empty `PATH` elements + set _search_path [lsearch -all -inline -not -exact \ + $_search_path ""] + set _search_exe .exe + } else { + set _search_path [split $env(PATH) :] + set _search_exe {} + } + } + + if {[is_Windows] && [lsearch -exact $args -script] >= 0} { + set suffix {} + } else { + set suffix $_search_exe + } + + foreach p $_search_path { + set p [file join $p $what$suffix] + if {[file exists $p]} { + return [file normalize $p] + } + } + return {} +} + +proc sanitize_command_line {command_line from_index} { + set i $from_index + while {$i < [llength $command_line]} { + set cmd [lindex $command_line $i] + if {[file pathtype $cmd] ne "absolute"} { + set fullpath [_which $cmd] + if {$fullpath eq ""} { + throw {NOT-FOUND} "$cmd not found in PATH" + } + lset command_line $i $fullpath + } + + # handle piped commands, e.g. `exec A | B` + for {incr i} {$i < [llength $command_line]} {incr i} { + if {[lindex $command_line $i] eq "|"} { + incr i + break + } + } + } + return $command_line +} + +# Override `exec` to avoid unsafe PATH lookup + +rename exec real_exec + +proc exec {args} { + # skip options + for {set i 0} {$i < [llength $args]} {incr i} { + set arg [lindex $args $i] + if {$arg eq "--"} { + incr i + break + } + if {[string range $arg 0 0] ne "-"} { + break + } + } + set args [sanitize_command_line $args $i] + uplevel 1 real_exec $args +} + +# Override `open` to avoid unsafe PATH lookup + +rename open real_open + +proc open {args} { + set arg0 [lindex $args 0] + if {[string range $arg0 0 0] eq "|"} { + set command_line [string trim [string range $arg0 1 end]] + lset args 0 "| [sanitize_command_line $command_line 0]" + } + uplevel 1 real_open $args +} + +# End of safe PATH lookup stuff + proc hasworktree {} { return [expr {[exec git rev-parse --is-bare-repository] == "false" && [exec git rev-parse --is-inside-git-dir] == "false"}] From dd17fae001d7091f5b52916a9edb0c0a9d2044ce Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 9 Apr 2012 13:04:35 -0500 Subject: [PATCH 038/306] Always auto-gc after calling a fast-import transport After importing anything with fast-import, we should always let the garbage collector do its job, since the objects are written to disk inefficiently. This brings down an initial import of http://selenic.com/hg from about 230 megabytes to about 14. In the future, we may want to make this configurable on a per-remote basis, or maybe teach fast-import about it in the first place. Signed-off-by: Johannes Schindelin --- transport-helper.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/transport-helper.c b/transport-helper.c index 72588c4d14e3ff..7513fd7eea05e4 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -22,6 +22,8 @@ #include "packfile.h" static int debug; +/* TODO: put somewhere sensible, e.g. git_transport_options? */ +static int auto_gc = 1; struct helper_data { char *name; @@ -588,6 +590,13 @@ static int fetch_with_import(struct transport *transport, } } strbuf_release(&buf); + if (auto_gc) { + struct child_process cmd = CHILD_PROCESS_INIT; + + cmd.git_cmd = 1; + strvec_pushl(&cmd.args, "gc", "--auto", "--quiet", NULL); + run_command(&cmd); + } return 0; } From a60cef9296585a4fd303c02935f6280f1fe2cf70 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 20 Oct 2019 22:08:58 +0200 Subject: [PATCH 039/306] win32/pthread: avoid name clashes with winpthread The mingw-w64 GCC seems to link implicitly to libwinpthread, which does implement a pthread emulation (that is more complete than Git's). Let's keep preferring Git's. To avoid linker errors where it thinks that the `pthread_self` and the `pthread_create` symbols are defined twice, let's give our version a `win32_` prefix, just like we already do for `pthread_join()`. Signed-off-by: Johannes Schindelin --- compat/win32/pthread.c | 6 +++--- compat/win32/pthread.h | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index 58980a529c3eb9..4cc6356f757d80 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c @@ -21,8 +21,8 @@ static unsigned __stdcall win32_start_routine(void *arg) return 0; } -int pthread_create(pthread_t *thread, const void *attr UNUSED, - void *(*start_routine)(void *), void *arg) +int win32_pthread_create(pthread_t *thread, const void *attr UNUSED, + void *(*start_routine)(void *), void *arg) { thread->arg = arg; thread->start_routine = start_routine; @@ -53,7 +53,7 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr) } } -pthread_t pthread_self(void) +pthread_t win32_pthread_self(void) { pthread_t t = { NULL }; t.tid = GetCurrentThreadId(); diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index e2b5c4f64c9b91..476e78b911b857 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h @@ -50,8 +50,9 @@ typedef struct { DWORD tid; } pthread_t; -int pthread_create(pthread_t *thread, const void *unused, - void *(*start_routine)(void*), void *arg); +int win32_pthread_create(pthread_t *thread, const void *unused, + void *(*start_routine)(void*), void *arg); +#define pthread_create win32_pthread_create /* * To avoid the need of copying a struct, we use small macro wrapper to pass @@ -62,7 +63,8 @@ int pthread_create(pthread_t *thread, const void *unused, int win32_pthread_join(pthread_t *thread, void **value_ptr); #define pthread_equal(t1, t2) ((t1).tid == (t2).tid) -pthread_t pthread_self(void); +pthread_t win32_pthread_self(void); +#define pthread_self win32_pthread_self static inline void NORETURN pthread_exit(void *ret) { From 3eaa1ce48e62666760a5e30a0d5e3a74c70dfa00 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 12 Aug 2022 12:44:15 +0200 Subject: [PATCH 040/306] git-compat-util: avoid redeclaring _DEFAULT_SOURCE We are about to vendor in `mimalloc`'s source code which we will want to include `git-compat-util.h` after defining that constant. Signed-off-by: Johannes Schindelin --- git-compat-util.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git-compat-util.h b/git-compat-util.h index e283c46c6fa06e..da2986e652fee7 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -201,7 +201,9 @@ DISABLE_WARNING(-Wsign-compare) #define _ALL_SOURCE 1 #define _GNU_SOURCE 1 #define _BSD_SOURCE 1 +#ifndef _DEFAULT_SOURCE #define _DEFAULT_SOURCE 1 +#endif #define _NETBSD_SOURCE 1 #define _SGI_SOURCE 1 From 461207c9e8428c4637ec043bde37f4725013b090 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 24 Jun 2019 21:31:30 +0200 Subject: [PATCH 041/306] Import the source code of mimalloc v2.1.2 This commit imports mimalloc's source code as per v2.1.2, fetched from the tag at https://github.com/microsoft/mimalloc. The .c files are from the src/ subdirectory, and the .h files from the include/ and include/mimalloc/ subdirectories. We will subsequently modify the source code to accommodate building within Git's context. Since we plan on using the `mi_*()` family of functions, we skip the C++-specific source code, some POSIX compliant functions to interact with mimalloc, and the code that wants to support auto-magic overriding of the `malloc()` function (mimalloc-new-delete.h, alloc-posix.c, mimalloc-override.h, alloc-override.c, alloc-override-osx.c, alloc-override-win.c and static.c). To appease the `check-whitespace` job of Git's Continuous Integration, this commit was washed one time via `git rebase --whitespace=fix`. Signed-off-by: Johannes Schindelin --- Makefile | 1 + compat/mimalloc/LICENSE | 21 + compat/mimalloc/alloc-aligned.c | 298 +++++ compat/mimalloc/alloc.c | 1060 ++++++++++++++++++ compat/mimalloc/arena.c | 935 ++++++++++++++++ compat/mimalloc/bitmap.c | 432 +++++++ compat/mimalloc/bitmap.h | 115 ++ compat/mimalloc/heap.c | 626 +++++++++++ compat/mimalloc/init.c | 709 ++++++++++++ compat/mimalloc/mimalloc.h | 565 ++++++++++ compat/mimalloc/mimalloc/atomic.h | 385 +++++++ compat/mimalloc/mimalloc/internal.h | 979 ++++++++++++++++ compat/mimalloc/mimalloc/prim.h | 323 ++++++ compat/mimalloc/mimalloc/track.h | 147 +++ compat/mimalloc/mimalloc/types.h | 670 +++++++++++ compat/mimalloc/options.c | 571 ++++++++++ compat/mimalloc/os.c | 689 ++++++++++++ compat/mimalloc/page-queue.c | 332 ++++++ compat/mimalloc/page.c | 939 ++++++++++++++++ compat/mimalloc/prim/windows/prim.c | 622 +++++++++++ compat/mimalloc/random.c | 254 +++++ compat/mimalloc/segment-cache.c | 0 compat/mimalloc/segment-map.c | 153 +++ compat/mimalloc/segment.c | 1617 +++++++++++++++++++++++++++ compat/mimalloc/stats.c | 467 ++++++++ 25 files changed, 12910 insertions(+) create mode 100644 compat/mimalloc/LICENSE create mode 100644 compat/mimalloc/alloc-aligned.c create mode 100644 compat/mimalloc/alloc.c create mode 100644 compat/mimalloc/arena.c create mode 100644 compat/mimalloc/bitmap.c create mode 100644 compat/mimalloc/bitmap.h create mode 100644 compat/mimalloc/heap.c create mode 100644 compat/mimalloc/init.c create mode 100644 compat/mimalloc/mimalloc.h create mode 100644 compat/mimalloc/mimalloc/atomic.h create mode 100644 compat/mimalloc/mimalloc/internal.h create mode 100644 compat/mimalloc/mimalloc/prim.h create mode 100644 compat/mimalloc/mimalloc/track.h create mode 100644 compat/mimalloc/mimalloc/types.h create mode 100644 compat/mimalloc/options.c create mode 100644 compat/mimalloc/os.c create mode 100644 compat/mimalloc/page-queue.c create mode 100644 compat/mimalloc/page.c create mode 100644 compat/mimalloc/prim/windows/prim.c create mode 100644 compat/mimalloc/random.c create mode 100644 compat/mimalloc/segment-cache.c create mode 100644 compat/mimalloc/segment-map.c create mode 100644 compat/mimalloc/segment.c create mode 100644 compat/mimalloc/stats.c diff --git a/Makefile b/Makefile index 97e8385b6643b9..995afe21733af4 100644 --- a/Makefile +++ b/Makefile @@ -1328,6 +1328,7 @@ BUILTIN_OBJS += builtin/write-tree.o # upstream unnecessarily (making merging in future changes easier). THIRD_PARTY_SOURCES += compat/inet_ntop.c THIRD_PARTY_SOURCES += compat/inet_pton.c +THIRD_PARTY_SOURCES += compat/mimalloc/% THIRD_PARTY_SOURCES += compat/nedmalloc/% THIRD_PARTY_SOURCES += compat/obstack.% THIRD_PARTY_SOURCES += compat/poll/% diff --git a/compat/mimalloc/LICENSE b/compat/mimalloc/LICENSE new file mode 100644 index 00000000000000..670b668a0c928e --- /dev/null +++ b/compat/mimalloc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2021 Microsoft Corporation, Daan Leijen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/compat/mimalloc/alloc-aligned.c b/compat/mimalloc/alloc-aligned.c new file mode 100644 index 00000000000000..e975af5f7c2ad4 --- /dev/null +++ b/compat/mimalloc/alloc-aligned.c @@ -0,0 +1,298 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2021, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/prim.h" // mi_prim_get_default_heap + +#include // memset + +// ------------------------------------------------------ +// Aligned Allocation +// ------------------------------------------------------ + +// Fallback primitive aligned allocation -- split out for better codegen +static mi_decl_noinline void* mi_heap_malloc_zero_aligned_at_fallback(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero) mi_attr_noexcept +{ + mi_assert_internal(size <= PTRDIFF_MAX); + mi_assert_internal(alignment != 0 && _mi_is_power_of_two(alignment)); + + const uintptr_t align_mask = alignment - 1; // for any x, `(x & align_mask) == (x % alignment)` + const size_t padsize = size + MI_PADDING_SIZE; + + // use regular allocation if it is guaranteed to fit the alignment constraints + if (offset==0 && alignment<=padsize && padsize<=MI_MAX_ALIGN_GUARANTEE && (padsize&align_mask)==0) { + void* p = _mi_heap_malloc_zero(heap, size, zero); + mi_assert_internal(p == NULL || ((uintptr_t)p % alignment) == 0); + return p; + } + + void* p; + size_t oversize; + if mi_unlikely(alignment > MI_ALIGNMENT_MAX) { + // use OS allocation for very large alignment and allocate inside a huge page (dedicated segment with 1 page) + // This can support alignments >= MI_SEGMENT_SIZE by ensuring the object can be aligned at a point in the + // first (and single) page such that the segment info is `MI_SEGMENT_SIZE` bytes before it (so it can be found by aligning the pointer down) + if mi_unlikely(offset != 0) { + // todo: cannot support offset alignment for very large alignments yet + #if MI_DEBUG > 0 + _mi_error_message(EOVERFLOW, "aligned allocation with a very large alignment cannot be used with an alignment offset (size %zu, alignment %zu, offset %zu)\n", size, alignment, offset); + #endif + return NULL; + } + oversize = (size <= MI_SMALL_SIZE_MAX ? MI_SMALL_SIZE_MAX + 1 /* ensure we use generic malloc path */ : size); + p = _mi_heap_malloc_zero_ex(heap, oversize, false, alignment); // the page block size should be large enough to align in the single huge page block + // zero afterwards as only the area from the aligned_p may be committed! + if (p == NULL) return NULL; + } + else { + // otherwise over-allocate + oversize = size + alignment - 1; + p = _mi_heap_malloc_zero(heap, oversize, zero); + if (p == NULL) return NULL; + } + + // .. and align within the allocation + const uintptr_t poffset = ((uintptr_t)p + offset) & align_mask; + const uintptr_t adjust = (poffset == 0 ? 0 : alignment - poffset); + mi_assert_internal(adjust < alignment); + void* aligned_p = (void*)((uintptr_t)p + adjust); + if (aligned_p != p) { + mi_page_t* page = _mi_ptr_page(p); + mi_page_set_has_aligned(page, true); + _mi_padding_shrink(page, (mi_block_t*)p, adjust + size); + } + // todo: expand padding if overallocated ? + + mi_assert_internal(mi_page_usable_block_size(_mi_ptr_page(p)) >= adjust + size); + mi_assert_internal(p == _mi_page_ptr_unalign(_mi_ptr_segment(aligned_p), _mi_ptr_page(aligned_p), aligned_p)); + mi_assert_internal(((uintptr_t)aligned_p + offset) % alignment == 0); + mi_assert_internal(mi_usable_size(aligned_p)>=size); + mi_assert_internal(mi_usable_size(p) == mi_usable_size(aligned_p)+adjust); + + // now zero the block if needed + if (alignment > MI_ALIGNMENT_MAX) { + // for the tracker, on huge aligned allocations only from the start of the large block is defined + mi_track_mem_undefined(aligned_p, size); + if (zero) { + _mi_memzero_aligned(aligned_p, mi_usable_size(aligned_p)); + } + } + + if (p != aligned_p) { + mi_track_align(p,aligned_p,adjust,mi_usable_size(aligned_p)); + } + return aligned_p; +} + +// Primitive aligned allocation +static void* mi_heap_malloc_zero_aligned_at(mi_heap_t* const heap, const size_t size, const size_t alignment, const size_t offset, const bool zero) mi_attr_noexcept +{ + // note: we don't require `size > offset`, we just guarantee that the address at offset is aligned regardless of the allocated size. + if mi_unlikely(alignment == 0 || !_mi_is_power_of_two(alignment)) { // require power-of-two (see ) + #if MI_DEBUG > 0 + _mi_error_message(EOVERFLOW, "aligned allocation requires the alignment to be a power-of-two (size %zu, alignment %zu)\n", size, alignment); + #endif + return NULL; + } + + if mi_unlikely(size > PTRDIFF_MAX) { // we don't allocate more than PTRDIFF_MAX (see ) + #if MI_DEBUG > 0 + _mi_error_message(EOVERFLOW, "aligned allocation request is too large (size %zu, alignment %zu)\n", size, alignment); + #endif + return NULL; + } + const uintptr_t align_mask = alignment-1; // for any x, `(x & align_mask) == (x % alignment)` + const size_t padsize = size + MI_PADDING_SIZE; // note: cannot overflow due to earlier size > PTRDIFF_MAX check + + // try first if there happens to be a small block available with just the right alignment + if mi_likely(padsize <= MI_SMALL_SIZE_MAX && alignment <= padsize) { + mi_page_t* page = _mi_heap_get_free_small_page(heap, padsize); + const bool is_aligned = (((uintptr_t)page->free+offset) & align_mask)==0; + if mi_likely(page->free != NULL && is_aligned) + { + #if MI_STAT>1 + mi_heap_stat_increase(heap, malloc, size); + #endif + void* p = _mi_page_malloc(heap, page, padsize, zero); // TODO: inline _mi_page_malloc + mi_assert_internal(p != NULL); + mi_assert_internal(((uintptr_t)p + offset) % alignment == 0); + mi_track_malloc(p,size,zero); + return p; + } + } + // fallback + return mi_heap_malloc_zero_aligned_at_fallback(heap, size, alignment, offset, zero); +} + + +// ------------------------------------------------------ +// Optimized mi_heap_malloc_aligned / mi_malloc_aligned +// ------------------------------------------------------ + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, false); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept { + if mi_unlikely(alignment == 0 || !_mi_is_power_of_two(alignment)) return NULL; + #if !MI_PADDING + // without padding, any small sized allocation is naturally aligned (see also `_mi_segment_page_start`) + if mi_likely(_mi_is_power_of_two(size) && size >= alignment && size <= MI_SMALL_SIZE_MAX) + #else + // with padding, we can only guarantee this for fixed alignments + if mi_likely((alignment == sizeof(void*) || (alignment == MI_MAX_ALIGN_SIZE && size > (MI_MAX_ALIGN_SIZE/2))) + && size <= MI_SMALL_SIZE_MAX) + #endif + { + // fast path for common alignment and size + return mi_heap_malloc_small(heap, size); + } + else { + return mi_heap_malloc_aligned_at(heap, size, alignment, 0); + } +} + +// ensure a definition is emitted +#if defined(__cplusplus) +static void* _mi_heap_malloc_aligned = (void*)&mi_heap_malloc_aligned; +#endif + +// ------------------------------------------------------ +// Aligned Allocation +// ------------------------------------------------------ + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_malloc_zero_aligned_at(heap, size, alignment, offset, true); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_zalloc_aligned_at(heap, size, alignment, 0); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_zalloc_aligned_at(heap, total, alignment, offset); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_calloc_aligned_at(heap,count,size,alignment,0); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_malloc_aligned_at(mi_prim_get_default_heap(), size, alignment, offset); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_malloc_aligned(mi_prim_get_default_heap(), size, alignment); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_zalloc_aligned_at(mi_prim_get_default_heap(), size, alignment, offset); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_zalloc_aligned(mi_prim_get_default_heap(), size, alignment); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_calloc_aligned_at(mi_prim_get_default_heap(), count, size, alignment, offset); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_calloc_aligned(mi_prim_get_default_heap(), count, size, alignment); +} + + +// ------------------------------------------------------ +// Aligned re-allocation +// ------------------------------------------------------ + +static void* mi_heap_realloc_zero_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset, bool zero) mi_attr_noexcept { + mi_assert(alignment > 0); + if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero); + if (p == NULL) return mi_heap_malloc_zero_aligned_at(heap,newsize,alignment,offset,zero); + size_t size = mi_usable_size(p); + if (newsize <= size && newsize >= (size - (size / 2)) + && (((uintptr_t)p + offset) % alignment) == 0) { + return p; // reallocation still fits, is aligned and not more than 50% waste + } + else { + // note: we don't zero allocate upfront so we only zero initialize the expanded part + void* newp = mi_heap_malloc_aligned_at(heap,newsize,alignment,offset); + if (newp != NULL) { + if (zero && newsize > size) { + // also set last word in the previous allocation to zero to ensure any padding is zero-initialized + size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0); + _mi_memzero((uint8_t*)newp + start, newsize - start); + } + _mi_memcpy_aligned(newp, p, (newsize > size ? size : newsize)); + mi_free(p); // only free if successful + } + return newp; + } +} + +static void* mi_heap_realloc_zero_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, bool zero) mi_attr_noexcept { + mi_assert(alignment > 0); + if (alignment <= sizeof(uintptr_t)) return _mi_heap_realloc_zero(heap,p,newsize,zero); + size_t offset = ((uintptr_t)p % alignment); // use offset of previous allocation (p can be NULL) + return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,offset,zero); +} + +mi_decl_nodiscard void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned_at(heap,p,newsize,alignment,offset,false); +} + +mi_decl_nodiscard void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned(heap,p,newsize,alignment,false); +} + +mi_decl_nodiscard void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned_at(heap, p, newsize, alignment, offset, true); +} + +mi_decl_nodiscard void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_realloc_zero_aligned(heap, p, newsize, alignment, true); +} + +mi_decl_nodiscard void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(newcount, size, &total)) return NULL; + return mi_heap_rezalloc_aligned_at(heap, p, total, alignment, offset); +} + +mi_decl_nodiscard void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(newcount, size, &total)) return NULL; + return mi_heap_rezalloc_aligned(heap, p, total, alignment); +} + +mi_decl_nodiscard void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_realloc_aligned_at(mi_prim_get_default_heap(), p, newsize, alignment, offset); +} + +mi_decl_nodiscard void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_realloc_aligned(mi_prim_get_default_heap(), p, newsize, alignment); +} + +mi_decl_nodiscard void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_rezalloc_aligned_at(mi_prim_get_default_heap(), p, newsize, alignment, offset); +} + +mi_decl_nodiscard void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept { + return mi_heap_rezalloc_aligned(mi_prim_get_default_heap(), p, newsize, alignment); +} + +mi_decl_nodiscard void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept { + return mi_heap_recalloc_aligned_at(mi_prim_get_default_heap(), p, newcount, size, alignment, offset); +} + +mi_decl_nodiscard void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept { + return mi_heap_recalloc_aligned(mi_prim_get_default_heap(), p, newcount, size, alignment); +} diff --git a/compat/mimalloc/alloc.c b/compat/mimalloc/alloc.c new file mode 100644 index 00000000000000..961f6d53d0f2c7 --- /dev/null +++ b/compat/mimalloc/alloc.c @@ -0,0 +1,1060 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2022, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE // for realpath() on Linux +#endif + +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" +#include "mimalloc/prim.h" // _mi_prim_thread_id() + +#include // memset, strlen (for mi_strdup) +#include // malloc, abort + +#define MI_IN_ALLOC_C +#include "alloc-override.c" +#undef MI_IN_ALLOC_C + +// ------------------------------------------------------ +// Allocation +// ------------------------------------------------------ + +// Fast allocation in a page: just pop from the free list. +// Fall back to generic allocation only if the list is empty. +extern inline void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero) mi_attr_noexcept { + mi_assert_internal(page->xblock_size==0||mi_page_block_size(page) >= size); + mi_block_t* const block = page->free; + if mi_unlikely(block == NULL) { + return _mi_malloc_generic(heap, size, zero, 0); + } + mi_assert_internal(block != NULL && _mi_ptr_page(block) == page); + // pop from the free list + page->used++; + page->free = mi_block_next(page, block); + mi_assert_internal(page->free == NULL || _mi_ptr_page(page->free) == page); + #if MI_DEBUG>3 + if (page->free_is_zero) { + mi_assert_expensive(mi_mem_is_zero(block+1,size - sizeof(*block))); + } + #endif + + // allow use of the block internally + // note: when tracking we need to avoid ever touching the MI_PADDING since + // that is tracked by valgrind etc. as non-accessible (through the red-zone, see `mimalloc/track.h`) + mi_track_mem_undefined(block, mi_page_usable_block_size(page)); + + // zero the block? note: we need to zero the full block size (issue #63) + if mi_unlikely(zero) { + mi_assert_internal(page->xblock_size != 0); // do not call with zero'ing for huge blocks (see _mi_malloc_generic) + mi_assert_internal(page->xblock_size >= MI_PADDING_SIZE); + if (page->free_is_zero) { + block->next = 0; + mi_track_mem_defined(block, page->xblock_size - MI_PADDING_SIZE); + } + else { + _mi_memzero_aligned(block, page->xblock_size - MI_PADDING_SIZE); + } + } + +#if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN + if (!zero && !mi_page_is_huge(page)) { + memset(block, MI_DEBUG_UNINIT, mi_page_usable_block_size(page)); + } +#elif (MI_SECURE!=0) + if (!zero) { block->next = 0; } // don't leak internal data +#endif + +#if (MI_STAT>0) + const size_t bsize = mi_page_usable_block_size(page); + if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) { + mi_heap_stat_increase(heap, normal, bsize); + mi_heap_stat_counter_increase(heap, normal_count, 1); +#if (MI_STAT>1) + const size_t bin = _mi_bin(bsize); + mi_heap_stat_increase(heap, normal_bins[bin], 1); +#endif + } +#endif + +#if MI_PADDING // && !MI_TRACK_ENABLED + mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + mi_page_usable_block_size(page)); + ptrdiff_t delta = ((uint8_t*)padding - (uint8_t*)block - (size - MI_PADDING_SIZE)); + #if (MI_DEBUG>=2) + mi_assert_internal(delta >= 0 && mi_page_usable_block_size(page) >= (size - MI_PADDING_SIZE + delta)); + #endif + mi_track_mem_defined(padding,sizeof(mi_padding_t)); // note: re-enable since mi_page_usable_block_size may set noaccess + padding->canary = (uint32_t)(mi_ptr_encode(page,block,page->keys)); + padding->delta = (uint32_t)(delta); + #if MI_PADDING_CHECK + if (!mi_page_is_huge(page)) { + uint8_t* fill = (uint8_t*)padding - delta; + const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // set at most N initial padding bytes + for (size_t i = 0; i < maxpad; i++) { fill[i] = MI_DEBUG_PADDING; } + } + #endif +#endif + + return block; +} + +static inline mi_decl_restrict void* mi_heap_malloc_small_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept { + mi_assert(heap != NULL); + #if MI_DEBUG + const uintptr_t tid = _mi_thread_id(); + mi_assert(heap->thread_id == 0 || heap->thread_id == tid); // heaps are thread local + #endif + mi_assert(size <= MI_SMALL_SIZE_MAX); + #if (MI_PADDING) + if (size == 0) { size = sizeof(void*); } + #endif + mi_page_t* page = _mi_heap_get_free_small_page(heap, size + MI_PADDING_SIZE); + void* const p = _mi_page_malloc(heap, page, size + MI_PADDING_SIZE, zero); + mi_track_malloc(p,size,zero); + #if MI_STAT>1 + if (p != NULL) { + if (!mi_heap_is_initialized(heap)) { heap = mi_prim_get_default_heap(); } + mi_heap_stat_increase(heap, malloc, mi_usable_size(p)); + } + #endif + #if MI_DEBUG>3 + if (p != NULL && zero) { + mi_assert_expensive(mi_mem_is_zero(p, size)); + } + #endif + return p; +} + +// allocate a small block +mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept { + return mi_heap_malloc_small_zero(heap, size, false); +} + +mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept { + return mi_heap_malloc_small(mi_prim_get_default_heap(), size); +} + +// The main allocation function +extern inline void* _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept { + if mi_likely(size <= MI_SMALL_SIZE_MAX) { + mi_assert_internal(huge_alignment == 0); + return mi_heap_malloc_small_zero(heap, size, zero); + } + else { + mi_assert(heap!=NULL); + mi_assert(heap->thread_id == 0 || heap->thread_id == _mi_thread_id()); // heaps are thread local + void* const p = _mi_malloc_generic(heap, size + MI_PADDING_SIZE, zero, huge_alignment); // note: size can overflow but it is detected in malloc_generic + mi_track_malloc(p,size,zero); + #if MI_STAT>1 + if (p != NULL) { + if (!mi_heap_is_initialized(heap)) { heap = mi_prim_get_default_heap(); } + mi_heap_stat_increase(heap, malloc, mi_usable_size(p)); + } + #endif + #if MI_DEBUG>3 + if (p != NULL && zero) { + mi_assert_expensive(mi_mem_is_zero(p, size)); + } + #endif + return p; + } +} + +extern inline void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept { + return _mi_heap_malloc_zero_ex(heap, size, zero, 0); +} + +mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept { + return _mi_heap_malloc_zero(heap, size, false); +} + +mi_decl_nodiscard extern inline mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept { + return mi_heap_malloc(mi_prim_get_default_heap(), size); +} + +// zero initialized small block +mi_decl_nodiscard mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept { + return mi_heap_malloc_small_zero(mi_prim_get_default_heap(), size, true); +} + +mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept { + return _mi_heap_malloc_zero(heap, size, true); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept { + return mi_heap_zalloc(mi_prim_get_default_heap(),size); +} + + +// ------------------------------------------------------ +// Check for double free in secure and debug mode +// This is somewhat expensive so only enabled for secure mode 4 +// ------------------------------------------------------ + +#if (MI_ENCODE_FREELIST && (MI_SECURE>=4 || MI_DEBUG!=0)) +// linear check if the free list contains a specific element +static bool mi_list_contains(const mi_page_t* page, const mi_block_t* list, const mi_block_t* elem) { + while (list != NULL) { + if (elem==list) return true; + list = mi_block_next(page, list); + } + return false; +} + +static mi_decl_noinline bool mi_check_is_double_freex(const mi_page_t* page, const mi_block_t* block) { + // The decoded value is in the same page (or NULL). + // Walk the free lists to verify positively if it is already freed + if (mi_list_contains(page, page->free, block) || + mi_list_contains(page, page->local_free, block) || + mi_list_contains(page, mi_page_thread_free(page), block)) + { + _mi_error_message(EAGAIN, "double free detected of block %p with size %zu\n", block, mi_page_block_size(page)); + return true; + } + return false; +} + +#define mi_track_page(page,access) { size_t psize; void* pstart = _mi_page_start(_mi_page_segment(page),page,&psize); mi_track_mem_##access( pstart, psize); } + +static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) { + bool is_double_free = false; + mi_block_t* n = mi_block_nextx(page, block, page->keys); // pretend it is freed, and get the decoded first field + if (((uintptr_t)n & (MI_INTPTR_SIZE-1))==0 && // quick check: aligned pointer? + (n==NULL || mi_is_in_same_page(block, n))) // quick check: in same page or NULL? + { + // Suspicous: decoded value a in block is in the same page (or NULL) -- maybe a double free? + // (continue in separate function to improve code generation) + is_double_free = mi_check_is_double_freex(page, block); + } + return is_double_free; +} +#else +static inline bool mi_check_is_double_free(const mi_page_t* page, const mi_block_t* block) { + MI_UNUSED(page); + MI_UNUSED(block); + return false; +} +#endif + +// --------------------------------------------------------------------------- +// Check for heap block overflow by setting up padding at the end of the block +// --------------------------------------------------------------------------- + +#if MI_PADDING // && !MI_TRACK_ENABLED +static bool mi_page_decode_padding(const mi_page_t* page, const mi_block_t* block, size_t* delta, size_t* bsize) { + *bsize = mi_page_usable_block_size(page); + const mi_padding_t* const padding = (mi_padding_t*)((uint8_t*)block + *bsize); + mi_track_mem_defined(padding,sizeof(mi_padding_t)); + *delta = padding->delta; + uint32_t canary = padding->canary; + uintptr_t keys[2]; + keys[0] = page->keys[0]; + keys[1] = page->keys[1]; + bool ok = ((uint32_t)mi_ptr_encode(page,block,keys) == canary && *delta <= *bsize); + mi_track_mem_noaccess(padding,sizeof(mi_padding_t)); + return ok; +} + +// Return the exact usable size of a block. +static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) { + size_t bsize; + size_t delta; + bool ok = mi_page_decode_padding(page, block, &delta, &bsize); + mi_assert_internal(ok); mi_assert_internal(delta <= bsize); + return (ok ? bsize - delta : 0); +} + +// When a non-thread-local block is freed, it becomes part of the thread delayed free +// list that is freed later by the owning heap. If the exact usable size is too small to +// contain the pointer for the delayed list, then shrink the padding (by decreasing delta) +// so it will later not trigger an overflow error in `mi_free_block`. +void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) { + size_t bsize; + size_t delta; + bool ok = mi_page_decode_padding(page, block, &delta, &bsize); + mi_assert_internal(ok); + if (!ok || (bsize - delta) >= min_size) return; // usually already enough space + mi_assert_internal(bsize >= min_size); + if (bsize < min_size) return; // should never happen + size_t new_delta = (bsize - min_size); + mi_assert_internal(new_delta < bsize); + mi_padding_t* padding = (mi_padding_t*)((uint8_t*)block + bsize); + mi_track_mem_defined(padding,sizeof(mi_padding_t)); + padding->delta = (uint32_t)new_delta; + mi_track_mem_noaccess(padding,sizeof(mi_padding_t)); +} +#else +static size_t mi_page_usable_size_of(const mi_page_t* page, const mi_block_t* block) { + MI_UNUSED(block); + return mi_page_usable_block_size(page); +} + +void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size) { + MI_UNUSED(page); + MI_UNUSED(block); + MI_UNUSED(min_size); +} +#endif + +#if MI_PADDING && MI_PADDING_CHECK + +static bool mi_verify_padding(const mi_page_t* page, const mi_block_t* block, size_t* size, size_t* wrong) { + size_t bsize; + size_t delta; + bool ok = mi_page_decode_padding(page, block, &delta, &bsize); + *size = *wrong = bsize; + if (!ok) return false; + mi_assert_internal(bsize >= delta); + *size = bsize - delta; + if (!mi_page_is_huge(page)) { + uint8_t* fill = (uint8_t*)block + bsize - delta; + const size_t maxpad = (delta > MI_MAX_ALIGN_SIZE ? MI_MAX_ALIGN_SIZE : delta); // check at most the first N padding bytes + mi_track_mem_defined(fill, maxpad); + for (size_t i = 0; i < maxpad; i++) { + if (fill[i] != MI_DEBUG_PADDING) { + *wrong = bsize - delta + i; + ok = false; + break; + } + } + mi_track_mem_noaccess(fill, maxpad); + } + return ok; +} + +static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) { + size_t size; + size_t wrong; + if (!mi_verify_padding(page,block,&size,&wrong)) { + _mi_error_message(EFAULT, "buffer overflow in heap block %p of size %zu: write after %zu bytes\n", block, size, wrong ); + } +} + +#else + +static void mi_check_padding(const mi_page_t* page, const mi_block_t* block) { + MI_UNUSED(page); + MI_UNUSED(block); +} + +#endif + +// only maintain stats for smaller objects if requested +#if (MI_STAT>0) +static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) { + #if (MI_STAT < 2) + MI_UNUSED(block); + #endif + mi_heap_t* const heap = mi_heap_get_default(); + const size_t bsize = mi_page_usable_block_size(page); + #if (MI_STAT>1) + const size_t usize = mi_page_usable_size_of(page, block); + mi_heap_stat_decrease(heap, malloc, usize); + #endif + if (bsize <= MI_MEDIUM_OBJ_SIZE_MAX) { + mi_heap_stat_decrease(heap, normal, bsize); + #if (MI_STAT > 1) + mi_heap_stat_decrease(heap, normal_bins[_mi_bin(bsize)], 1); + #endif + } + else if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { + mi_heap_stat_decrease(heap, large, bsize); + } + else { + mi_heap_stat_decrease(heap, huge, bsize); + } +} +#else +static void mi_stat_free(const mi_page_t* page, const mi_block_t* block) { + MI_UNUSED(page); MI_UNUSED(block); +} +#endif + +#if MI_HUGE_PAGE_ABANDON +#if (MI_STAT>0) +// maintain stats for huge objects +static void mi_stat_huge_free(const mi_page_t* page) { + mi_heap_t* const heap = mi_heap_get_default(); + const size_t bsize = mi_page_block_size(page); // to match stats in `page.c:mi_page_huge_alloc` + if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { + mi_heap_stat_decrease(heap, large, bsize); + } + else { + mi_heap_stat_decrease(heap, huge, bsize); + } +} +#else +static void mi_stat_huge_free(const mi_page_t* page) { + MI_UNUSED(page); +} +#endif +#endif + +// ------------------------------------------------------ +// Free +// ------------------------------------------------------ + +// multi-threaded free (or free in huge block if compiled with MI_HUGE_PAGE_ABANDON) +static mi_decl_noinline void _mi_free_block_mt(mi_page_t* page, mi_block_t* block) +{ + // The padding check may access the non-thread-owned page for the key values. + // that is safe as these are constant and the page won't be freed (as the block is not freed yet). + mi_check_padding(page, block); + _mi_padding_shrink(page, block, sizeof(mi_block_t)); // for small size, ensure we can fit the delayed thread pointers without triggering overflow detection + + // huge page segments are always abandoned and can be freed immediately + mi_segment_t* segment = _mi_page_segment(page); + if (segment->kind == MI_SEGMENT_HUGE) { + #if MI_HUGE_PAGE_ABANDON + // huge page segments are always abandoned and can be freed immediately + mi_stat_huge_free(page); + _mi_segment_huge_page_free(segment, page, block); + return; + #else + // huge pages are special as they occupy the entire segment + // as these are large we reset the memory occupied by the page so it is available to other threads + // (as the owning thread needs to actually free the memory later). + _mi_segment_huge_page_reset(segment, page, block); + #endif + } + + #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN // note: when tracking, cannot use mi_usable_size with multi-threading + if (segment->kind != MI_SEGMENT_HUGE) { // not for huge segments as we just reset the content + memset(block, MI_DEBUG_FREED, mi_usable_size(block)); + } + #endif + + // Try to put the block on either the page-local thread free list, or the heap delayed free list. + mi_thread_free_t tfreex; + bool use_delayed; + mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free); + do { + use_delayed = (mi_tf_delayed(tfree) == MI_USE_DELAYED_FREE); + if mi_unlikely(use_delayed) { + // unlikely: this only happens on the first concurrent free in a page that is in the full list + tfreex = mi_tf_set_delayed(tfree,MI_DELAYED_FREEING); + } + else { + // usual: directly add to page thread_free list + mi_block_set_next(page, block, mi_tf_block(tfree)); + tfreex = mi_tf_set_block(tfree,block); + } + } while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex)); + + if mi_unlikely(use_delayed) { + // racy read on `heap`, but ok because MI_DELAYED_FREEING is set (see `mi_heap_delete` and `mi_heap_collect_abandon`) + mi_heap_t* const heap = (mi_heap_t*)(mi_atomic_load_acquire(&page->xheap)); //mi_page_heap(page); + mi_assert_internal(heap != NULL); + if (heap != NULL) { + // add to the delayed free list of this heap. (do this atomically as the lock only protects heap memory validity) + mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free); + do { + mi_block_set_nextx(heap,block,dfree, heap->keys); + } while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block)); + } + + // and reset the MI_DELAYED_FREEING flag + tfree = mi_atomic_load_relaxed(&page->xthread_free); + do { + tfreex = tfree; + mi_assert_internal(mi_tf_delayed(tfree) == MI_DELAYED_FREEING); + tfreex = mi_tf_set_delayed(tfree,MI_NO_DELAYED_FREE); + } while (!mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex)); + } +} + +// regular free +static inline void _mi_free_block(mi_page_t* page, bool local, mi_block_t* block) +{ + // and push it on the free list + //const size_t bsize = mi_page_block_size(page); + if mi_likely(local) { + // owning thread can free a block directly + if mi_unlikely(mi_check_is_double_free(page, block)) return; + mi_check_padding(page, block); + #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN + if (!mi_page_is_huge(page)) { // huge page content may be already decommitted + memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); + } + #endif + mi_block_set_next(page, block, page->local_free); + page->local_free = block; + page->used--; + if mi_unlikely(mi_page_all_free(page)) { + _mi_page_retire(page); + } + else if mi_unlikely(mi_page_is_in_full(page)) { + _mi_page_unfull(page); + } + } + else { + _mi_free_block_mt(page,block); + } +} + + +// Adjust a block that was allocated aligned, to the actual start of the block in the page. +mi_block_t* _mi_page_ptr_unalign(const mi_segment_t* segment, const mi_page_t* page, const void* p) { + mi_assert_internal(page!=NULL && p!=NULL); + const size_t diff = (uint8_t*)p - _mi_page_start(segment, page, NULL); + const size_t adjust = (diff % mi_page_block_size(page)); + return (mi_block_t*)((uintptr_t)p - adjust); +} + + +void mi_decl_noinline _mi_free_generic(const mi_segment_t* segment, mi_page_t* page, bool is_local, void* p) mi_attr_noexcept { + mi_block_t* const block = (mi_page_has_aligned(page) ? _mi_page_ptr_unalign(segment, page, p) : (mi_block_t*)p); + mi_stat_free(page, block); // stat_free may access the padding + mi_track_free_size(block, mi_page_usable_size_of(page,block)); + _mi_free_block(page, is_local, block); +} + +// Get the segment data belonging to a pointer +// This is just a single `and` in assembly but does further checks in debug mode +// (and secure mode) if this was a valid pointer. +static inline mi_segment_t* mi_checked_ptr_segment(const void* p, const char* msg) +{ + MI_UNUSED(msg); + mi_assert(p != NULL); + +#if (MI_DEBUG>0) + if mi_unlikely(((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0) { + _mi_error_message(EINVAL, "%s: invalid (unaligned) pointer: %p\n", msg, p); + return NULL; + } +#endif + + mi_segment_t* const segment = _mi_ptr_segment(p); + mi_assert_internal(segment != NULL); + +#if (MI_DEBUG>0) + if mi_unlikely(!mi_is_in_heap_region(p)) { + #if (MI_INTPTR_SIZE == 8 && defined(__linux__)) + if (((uintptr_t)p >> 40) != 0x7F) { // linux tends to align large blocks above 0x7F000000000 (issue #640) + #else + { + #endif + _mi_warning_message("%s: pointer might not point to a valid heap region: %p\n" + "(this may still be a valid very large allocation (over 64MiB))\n", msg, p); + if mi_likely(_mi_ptr_cookie(segment) == segment->cookie) { + _mi_warning_message("(yes, the previous pointer %p was valid after all)\n", p); + } + } + } +#endif +#if (MI_DEBUG>0 || MI_SECURE>=4) + if mi_unlikely(_mi_ptr_cookie(segment) != segment->cookie) { + _mi_error_message(EINVAL, "%s: pointer does not point to a valid heap space: %p\n", msg, p); + return NULL; + } +#endif + + return segment; +} + +// Free a block +// fast path written carefully to prevent spilling on the stack +void mi_free(void* p) mi_attr_noexcept +{ + if mi_unlikely(p == NULL) return; + mi_segment_t* const segment = mi_checked_ptr_segment(p,"mi_free"); + const bool is_local= (_mi_prim_thread_id() == mi_atomic_load_relaxed(&segment->thread_id)); + mi_page_t* const page = _mi_segment_page_of(segment, p); + + if mi_likely(is_local) { // thread-local free? + if mi_likely(page->flags.full_aligned == 0) // and it is not a full page (full pages need to move from the full bin), nor has aligned blocks (aligned blocks need to be unaligned) + { + mi_block_t* const block = (mi_block_t*)p; + if mi_unlikely(mi_check_is_double_free(page, block)) return; + mi_check_padding(page, block); + mi_stat_free(page, block); + #if (MI_DEBUG>0) && !MI_TRACK_ENABLED && !MI_TSAN + memset(block, MI_DEBUG_FREED, mi_page_block_size(page)); + #endif + mi_track_free_size(p, mi_page_usable_size_of(page,block)); // faster then mi_usable_size as we already know the page and that p is unaligned + mi_block_set_next(page, block, page->local_free); + page->local_free = block; + if mi_unlikely(--page->used == 0) { // using this expression generates better code than: page->used--; if (mi_page_all_free(page)) + _mi_page_retire(page); + } + } + else { + // page is full or contains (inner) aligned blocks; use generic path + _mi_free_generic(segment, page, true, p); + } + } + else { + // not thread-local; use generic path + _mi_free_generic(segment, page, false, p); + } +} + +// return true if successful +bool _mi_free_delayed_block(mi_block_t* block) { + // get segment and page + const mi_segment_t* const segment = _mi_ptr_segment(block); + mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie); + mi_assert_internal(_mi_thread_id() == segment->thread_id); + mi_page_t* const page = _mi_segment_page_of(segment, block); + + // Clear the no-delayed flag so delayed freeing is used again for this page. + // This must be done before collecting the free lists on this page -- otherwise + // some blocks may end up in the page `thread_free` list with no blocks in the + // heap `thread_delayed_free` list which may cause the page to be never freed! + // (it would only be freed if we happen to scan it in `mi_page_queue_find_free_ex`) + if (!_mi_page_try_use_delayed_free(page, MI_USE_DELAYED_FREE, false /* dont overwrite never delayed */)) { + return false; + } + + // collect all other non-local frees to ensure up-to-date `used` count + _mi_page_free_collect(page, false); + + // and free the block (possibly freeing the page as well since used is updated) + _mi_free_block(page, true, block); + return true; +} + +// Bytes available in a block +mi_decl_noinline static size_t mi_page_usable_aligned_size_of(const mi_segment_t* segment, const mi_page_t* page, const void* p) mi_attr_noexcept { + const mi_block_t* block = _mi_page_ptr_unalign(segment, page, p); + const size_t size = mi_page_usable_size_of(page, block); + const ptrdiff_t adjust = (uint8_t*)p - (uint8_t*)block; + mi_assert_internal(adjust >= 0 && (size_t)adjust <= size); + return (size - adjust); +} + +static inline size_t _mi_usable_size(const void* p, const char* msg) mi_attr_noexcept { + if (p == NULL) return 0; + const mi_segment_t* const segment = mi_checked_ptr_segment(p, msg); + const mi_page_t* const page = _mi_segment_page_of(segment, p); + if mi_likely(!mi_page_has_aligned(page)) { + const mi_block_t* block = (const mi_block_t*)p; + return mi_page_usable_size_of(page, block); + } + else { + // split out to separate routine for improved code generation + return mi_page_usable_aligned_size_of(segment, page, p); + } +} + +mi_decl_nodiscard size_t mi_usable_size(const void* p) mi_attr_noexcept { + return _mi_usable_size(p, "mi_usable_size"); +} + + +// ------------------------------------------------------ +// Allocation extensions +// ------------------------------------------------------ + +void mi_free_size(void* p, size_t size) mi_attr_noexcept { + MI_UNUSED_RELEASE(size); + mi_assert(p == NULL || size <= _mi_usable_size(p,"mi_free_size")); + mi_free(p); +} + +void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept { + MI_UNUSED_RELEASE(alignment); + mi_assert(((uintptr_t)p % alignment) == 0); + mi_free_size(p,size); +} + +void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept { + MI_UNUSED_RELEASE(alignment); + mi_assert(((uintptr_t)p % alignment) == 0); + mi_free(p); +} + +mi_decl_nodiscard extern inline mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count,size,&total)) return NULL; + return mi_heap_zalloc(heap,total); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept { + return mi_heap_calloc(mi_prim_get_default_heap(),count,size); +} + +// Uninitialized `calloc` +mi_decl_nodiscard extern mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_malloc(heap, total); +} + +mi_decl_nodiscard mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept { + return mi_heap_mallocn(mi_prim_get_default_heap(),count,size); +} + +// Expand (or shrink) in place (or fail) +void* mi_expand(void* p, size_t newsize) mi_attr_noexcept { + #if MI_PADDING + // we do not shrink/expand with padding enabled + MI_UNUSED(p); MI_UNUSED(newsize); + return NULL; + #else + if (p == NULL) return NULL; + const size_t size = _mi_usable_size(p,"mi_expand"); + if (newsize > size) return NULL; + return p; // it fits + #endif +} + +void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero) mi_attr_noexcept { + // if p == NULL then behave as malloc. + // else if size == 0 then reallocate to a zero-sized block (and don't return NULL, just as mi_malloc(0)). + // (this means that returning NULL always indicates an error, and `p` will not have been freed in that case.) + const size_t size = _mi_usable_size(p,"mi_realloc"); // also works if p == NULL (with size 0) + if mi_unlikely(newsize <= size && newsize >= (size / 2) && newsize > 0) { // note: newsize must be > 0 or otherwise we return NULL for realloc(NULL,0) + mi_assert_internal(p!=NULL); + // todo: do not track as the usable size is still the same in the free; adjust potential padding? + // mi_track_resize(p,size,newsize) + // if (newsize < size) { mi_track_mem_noaccess((uint8_t*)p + newsize, size - newsize); } + return p; // reallocation still fits and not more than 50% waste + } + void* newp = mi_heap_malloc(heap,newsize); + if mi_likely(newp != NULL) { + if (zero && newsize > size) { + // also set last word in the previous allocation to zero to ensure any padding is zero-initialized + const size_t start = (size >= sizeof(intptr_t) ? size - sizeof(intptr_t) : 0); + _mi_memzero((uint8_t*)newp + start, newsize - start); + } + else if (newsize == 0) { + ((uint8_t*)newp)[0] = 0; // work around for applications that expect zero-reallocation to be zero initialized (issue #725) + } + if mi_likely(p != NULL) { + const size_t copysize = (newsize > size ? size : newsize); + mi_track_mem_defined(p,copysize); // _mi_useable_size may be too large for byte precise memory tracking.. + _mi_memcpy(newp, p, copysize); + mi_free(p); // only free the original pointer if successful + } + } + return newp; +} + +mi_decl_nodiscard void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { + return _mi_heap_realloc_zero(heap, p, newsize, false); +} + +mi_decl_nodiscard void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_realloc(heap, p, total); +} + + +// Reallocate but free `p` on errors +mi_decl_nodiscard void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { + void* newp = mi_heap_realloc(heap, p, newsize); + if (newp==NULL && p!=NULL) mi_free(p); + return newp; +} + +mi_decl_nodiscard void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept { + return _mi_heap_realloc_zero(heap, p, newsize, true); +} + +mi_decl_nodiscard void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept { + size_t total; + if (mi_count_size_overflow(count, size, &total)) return NULL; + return mi_heap_rezalloc(heap, p, total); +} + + +mi_decl_nodiscard void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept { + return mi_heap_realloc(mi_prim_get_default_heap(),p,newsize); +} + +mi_decl_nodiscard void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept { + return mi_heap_reallocn(mi_prim_get_default_heap(),p,count,size); +} + +// Reallocate but free `p` on errors +mi_decl_nodiscard void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept { + return mi_heap_reallocf(mi_prim_get_default_heap(),p,newsize); +} + +mi_decl_nodiscard void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept { + return mi_heap_rezalloc(mi_prim_get_default_heap(), p, newsize); +} + +mi_decl_nodiscard void* mi_recalloc(void* p, size_t count, size_t size) mi_attr_noexcept { + return mi_heap_recalloc(mi_prim_get_default_heap(), p, count, size); +} + + + +// ------------------------------------------------------ +// strdup, strndup, and realpath +// ------------------------------------------------------ + +// `strdup` using mi_malloc +mi_decl_nodiscard mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept { + if (s == NULL) return NULL; + size_t n = strlen(s); + char* t = (char*)mi_heap_malloc(heap,n+1); + if (t == NULL) return NULL; + _mi_memcpy(t, s, n); + t[n] = 0; + return t; +} + +mi_decl_nodiscard mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept { + return mi_heap_strdup(mi_prim_get_default_heap(), s); +} + +// `strndup` using mi_malloc +mi_decl_nodiscard mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept { + if (s == NULL) return NULL; + const char* end = (const char*)memchr(s, 0, n); // find end of string in the first `n` characters (returns NULL if not found) + const size_t m = (end != NULL ? (size_t)(end - s) : n); // `m` is the minimum of `n` or the end-of-string + mi_assert_internal(m <= n); + char* t = (char*)mi_heap_malloc(heap, m+1); + if (t == NULL) return NULL; + _mi_memcpy(t, s, m); + t[m] = 0; + return t; +} + +mi_decl_nodiscard mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept { + return mi_heap_strndup(mi_prim_get_default_heap(),s,n); +} + +#ifndef __wasi__ +// `realpath` using mi_malloc +#ifdef _WIN32 +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif +#include +mi_decl_nodiscard mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept { + // todo: use GetFullPathNameW to allow longer file names + char buf[PATH_MAX]; + DWORD res = GetFullPathNameA(fname, PATH_MAX, (resolved_name == NULL ? buf : resolved_name), NULL); + if (res == 0) { + errno = GetLastError(); return NULL; + } + else if (res > PATH_MAX) { + errno = EINVAL; return NULL; + } + else if (resolved_name != NULL) { + return resolved_name; + } + else { + return mi_heap_strndup(heap, buf, PATH_MAX); + } +} +#else +/* +#include // pathconf +static size_t mi_path_max(void) { + static size_t path_max = 0; + if (path_max <= 0) { + long m = pathconf("/",_PC_PATH_MAX); + if (m <= 0) path_max = 4096; // guess + else if (m < 256) path_max = 256; // at least 256 + else path_max = m; + } + return path_max; +} +*/ +char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept { + if (resolved_name != NULL) { + return realpath(fname,resolved_name); + } + else { + char* rname = realpath(fname, NULL); + if (rname == NULL) return NULL; + char* result = mi_heap_strdup(heap, rname); + free(rname); // use regular free! (which may be redirected to our free but that's ok) + return result; + } + /* + const size_t n = mi_path_max(); + char* buf = (char*)mi_malloc(n+1); + if (buf == NULL) { + errno = ENOMEM; + return NULL; + } + char* rname = realpath(fname,buf); + char* result = mi_heap_strndup(heap,rname,n); // ok if `rname==NULL` + mi_free(buf); + return result; + } + */ +} +#endif + +mi_decl_nodiscard mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept { + return mi_heap_realpath(mi_prim_get_default_heap(),fname,resolved_name); +} +#endif + +/*------------------------------------------------------- +C++ new and new_aligned +The standard requires calling into `get_new_handler` and +throwing the bad_alloc exception on failure. If we compile +with a C++ compiler we can implement this precisely. If we +use a C compiler we cannot throw a `bad_alloc` exception +but we call `exit` instead (i.e. not returning). +-------------------------------------------------------*/ + +#ifdef __cplusplus +#include +static bool mi_try_new_handler(bool nothrow) { + #if defined(_MSC_VER) || (__cplusplus >= 201103L) + std::new_handler h = std::get_new_handler(); + #else + std::new_handler h = std::set_new_handler(); + std::set_new_handler(h); + #endif + if (h==NULL) { + _mi_error_message(ENOMEM, "out of memory in 'new'"); + if (!nothrow) { + throw std::bad_alloc(); + } + return false; + } + else { + h(); + return true; + } +} +#else +typedef void (*std_new_handler_t)(void); + +#if (defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER))) // exclude clang-cl, see issue #631 +std_new_handler_t __attribute__((weak)) _ZSt15get_new_handlerv(void) { + return NULL; +} +static std_new_handler_t mi_get_new_handler(void) { + return _ZSt15get_new_handlerv(); +} +#else +// note: on windows we could dynamically link to `?get_new_handler@std@@YAP6AXXZXZ`. +static std_new_handler_t mi_get_new_handler() { + return NULL; +} +#endif + +static bool mi_try_new_handler(bool nothrow) { + std_new_handler_t h = mi_get_new_handler(); + if (h==NULL) { + _mi_error_message(ENOMEM, "out of memory in 'new'"); + if (!nothrow) { + abort(); // cannot throw in plain C, use abort + } + return false; + } + else { + h(); + return true; + } +} +#endif + +mi_decl_export mi_decl_noinline void* mi_heap_try_new(mi_heap_t* heap, size_t size, bool nothrow ) { + void* p = NULL; + while(p == NULL && mi_try_new_handler(nothrow)) { + p = mi_heap_malloc(heap,size); + } + return p; +} + +static mi_decl_noinline void* mi_try_new(size_t size, bool nothrow) { + return mi_heap_try_new(mi_prim_get_default_heap(), size, nothrow); +} + + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) { + void* p = mi_heap_malloc(heap,size); + if mi_unlikely(p == NULL) return mi_heap_try_new(heap, size, false); + return p; +} + +mi_decl_nodiscard mi_decl_restrict void* mi_new(size_t size) { + return mi_heap_alloc_new(mi_prim_get_default_heap(), size); +} + + +mi_decl_nodiscard mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) { + size_t total; + if mi_unlikely(mi_count_size_overflow(count, size, &total)) { + mi_try_new_handler(false); // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc + return NULL; + } + else { + return mi_heap_alloc_new(heap,total); + } +} + +mi_decl_nodiscard mi_decl_restrict void* mi_new_n(size_t count, size_t size) { + return mi_heap_alloc_new_n(mi_prim_get_default_heap(), size, count); +} + + +mi_decl_nodiscard mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept { + void* p = mi_malloc(size); + if mi_unlikely(p == NULL) return mi_try_new(size, true); + return p; +} + +mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) { + void* p; + do { + p = mi_malloc_aligned(size, alignment); + } + while(p == NULL && mi_try_new_handler(false)); + return p; +} + +mi_decl_nodiscard mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept { + void* p; + do { + p = mi_malloc_aligned(size, alignment); + } + while(p == NULL && mi_try_new_handler(true)); + return p; +} + +mi_decl_nodiscard void* mi_new_realloc(void* p, size_t newsize) { + void* q; + do { + q = mi_realloc(p, newsize); + } while (q == NULL && mi_try_new_handler(false)); + return q; +} + +mi_decl_nodiscard void* mi_new_reallocn(void* p, size_t newcount, size_t size) { + size_t total; + if mi_unlikely(mi_count_size_overflow(newcount, size, &total)) { + mi_try_new_handler(false); // on overflow we invoke the try_new_handler once to potentially throw std::bad_alloc + return NULL; + } + else { + return mi_new_realloc(p, total); + } +} + +// ------------------------------------------------------ +// ensure explicit external inline definitions are emitted! +// ------------------------------------------------------ + +#ifdef __cplusplus +void* _mi_externs[] = { + (void*)&_mi_page_malloc, + (void*)&_mi_heap_malloc_zero, + (void*)&_mi_heap_malloc_zero_ex, + (void*)&mi_malloc, + (void*)&mi_malloc_small, + (void*)&mi_zalloc_small, + (void*)&mi_heap_malloc, + (void*)&mi_heap_zalloc, + (void*)&mi_heap_malloc_small, + // (void*)&mi_heap_alloc_new, + // (void*)&mi_heap_alloc_new_n +}; +#endif diff --git a/compat/mimalloc/arena.c b/compat/mimalloc/arena.c new file mode 100644 index 00000000000000..879ee9e7e773d4 --- /dev/null +++ b/compat/mimalloc/arena.c @@ -0,0 +1,935 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ---------------------------------------------------------------------------- +"Arenas" are fixed area's of OS memory from which we can allocate +large blocks (>= MI_ARENA_MIN_BLOCK_SIZE, 4MiB). +In contrast to the rest of mimalloc, the arenas are shared between +threads and need to be accessed using atomic operations. + +Arenas are used to for huge OS page (1GiB) reservations or for reserving +OS memory upfront which can be improve performance or is sometimes needed +on embedded devices. We can also employ this with WASI or `sbrk` systems +to reserve large arenas upfront and be able to reuse the memory more effectively. + +The arena allocation needs to be thread safe and we use an atomic bitmap to allocate. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" + +#include // memset +#include // ENOMEM + +#include "bitmap.h" // atomic bitmap + +/* ----------------------------------------------------------- + Arena allocation +----------------------------------------------------------- */ + +// Block info: bit 0 contains the `in_use` bit, the upper bits the +// size in count of arena blocks. +typedef uintptr_t mi_block_info_t; +#define MI_ARENA_BLOCK_SIZE (MI_SEGMENT_SIZE) // 64MiB (must be at least MI_SEGMENT_ALIGN) +#define MI_ARENA_MIN_OBJ_SIZE (MI_ARENA_BLOCK_SIZE/2) // 32MiB +#define MI_MAX_ARENAS (112) // not more than 126 (since we use 7 bits in the memid and an arena index + 1) + +// A memory arena descriptor +typedef struct mi_arena_s { + mi_arena_id_t id; // arena id; 0 for non-specific + mi_memid_t memid; // memid of the memory area + _Atomic(uint8_t*) start; // the start of the memory area + size_t block_count; // size of the area in arena blocks (of `MI_ARENA_BLOCK_SIZE`) + size_t field_count; // number of bitmap fields (where `field_count * MI_BITMAP_FIELD_BITS >= block_count`) + size_t meta_size; // size of the arena structure itself (including its bitmaps) + mi_memid_t meta_memid; // memid of the arena structure itself (OS or static allocation) + int numa_node; // associated NUMA node + bool exclusive; // only allow allocations if specifically for this arena + bool is_large; // memory area consists of large- or huge OS pages (always committed) + _Atomic(size_t) search_idx; // optimization to start the search for free blocks + _Atomic(mi_msecs_t) purge_expire; // expiration time when blocks should be decommitted from `blocks_decommit`. + mi_bitmap_field_t* blocks_dirty; // are the blocks potentially non-zero? + mi_bitmap_field_t* blocks_committed; // are the blocks committed? (can be NULL for memory that cannot be decommitted) + mi_bitmap_field_t* blocks_purge; // blocks that can be (reset) decommitted. (can be NULL for memory that cannot be (reset) decommitted) + mi_bitmap_field_t blocks_inuse[1]; // in-place bitmap of in-use blocks (of size `field_count`) +} mi_arena_t; + + +// The available arenas +static mi_decl_cache_align _Atomic(mi_arena_t*) mi_arenas[MI_MAX_ARENAS]; +static mi_decl_cache_align _Atomic(size_t) mi_arena_count; // = 0 + + +//static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int numa_node, bool exclusive, mi_memid_t memid, mi_arena_id_t* arena_id) mi_attr_noexcept; + +/* ----------------------------------------------------------- + Arena id's + id = arena_index + 1 +----------------------------------------------------------- */ + +static size_t mi_arena_id_index(mi_arena_id_t id) { + return (size_t)(id <= 0 ? MI_MAX_ARENAS : id - 1); +} + +static mi_arena_id_t mi_arena_id_create(size_t arena_index) { + mi_assert_internal(arena_index < MI_MAX_ARENAS); + return (int)arena_index + 1; +} + +mi_arena_id_t _mi_arena_id_none(void) { + return 0; +} + +static bool mi_arena_id_is_suitable(mi_arena_id_t arena_id, bool arena_is_exclusive, mi_arena_id_t req_arena_id) { + return ((!arena_is_exclusive && req_arena_id == _mi_arena_id_none()) || + (arena_id == req_arena_id)); +} + +bool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_id) { + if (memid.memkind == MI_MEM_ARENA) { + return mi_arena_id_is_suitable(memid.mem.arena.id, memid.mem.arena.is_exclusive, request_arena_id); + } + else { + return mi_arena_id_is_suitable(0, false, request_arena_id); + } +} + +bool _mi_arena_memid_is_os_allocated(mi_memid_t memid) { + return (memid.memkind == MI_MEM_OS); +} + +/* ----------------------------------------------------------- + Arena allocations get a (currently) 16-bit memory id where the + lower 8 bits are the arena id, and the upper bits the block index. +----------------------------------------------------------- */ + +static size_t mi_block_count_of_size(size_t size) { + return _mi_divide_up(size, MI_ARENA_BLOCK_SIZE); +} + +static size_t mi_arena_block_size(size_t bcount) { + return (bcount * MI_ARENA_BLOCK_SIZE); +} + +static size_t mi_arena_size(mi_arena_t* arena) { + return mi_arena_block_size(arena->block_count); +} + +static mi_memid_t mi_memid_create_arena(mi_arena_id_t id, bool is_exclusive, mi_bitmap_index_t bitmap_index) { + mi_memid_t memid = _mi_memid_create(MI_MEM_ARENA); + memid.mem.arena.id = id; + memid.mem.arena.block_index = bitmap_index; + memid.mem.arena.is_exclusive = is_exclusive; + return memid; +} + +static bool mi_arena_memid_indices(mi_memid_t memid, size_t* arena_index, mi_bitmap_index_t* bitmap_index) { + mi_assert_internal(memid.memkind == MI_MEM_ARENA); + *arena_index = mi_arena_id_index(memid.mem.arena.id); + *bitmap_index = memid.mem.arena.block_index; + return memid.mem.arena.is_exclusive; +} + + + +/* ----------------------------------------------------------- + Special static area for mimalloc internal structures + to avoid OS calls (for example, for the arena metadata) +----------------------------------------------------------- */ + +#define MI_ARENA_STATIC_MAX (MI_INTPTR_SIZE*MI_KiB) // 8 KiB on 64-bit + +static uint8_t mi_arena_static[MI_ARENA_STATIC_MAX]; +static _Atomic(size_t) mi_arena_static_top; + +static void* mi_arena_static_zalloc(size_t size, size_t alignment, mi_memid_t* memid) { + *memid = _mi_memid_none(); + if (size == 0 || size > MI_ARENA_STATIC_MAX) return NULL; + if ((mi_atomic_load_relaxed(&mi_arena_static_top) + size) > MI_ARENA_STATIC_MAX) return NULL; + + // try to claim space + if (alignment == 0) { alignment = 1; } + const size_t oversize = size + alignment - 1; + if (oversize > MI_ARENA_STATIC_MAX) return NULL; + const size_t oldtop = mi_atomic_add_acq_rel(&mi_arena_static_top, oversize); + size_t top = oldtop + oversize; + if (top > MI_ARENA_STATIC_MAX) { + // try to roll back, ok if this fails + mi_atomic_cas_strong_acq_rel(&mi_arena_static_top, &top, oldtop); + return NULL; + } + + // success + *memid = _mi_memid_create(MI_MEM_STATIC); + const size_t start = _mi_align_up(oldtop, alignment); + uint8_t* const p = &mi_arena_static[start]; + _mi_memzero(p, size); + return p; +} + +static void* mi_arena_meta_zalloc(size_t size, mi_memid_t* memid, mi_stats_t* stats) { + *memid = _mi_memid_none(); + + // try static + void* p = mi_arena_static_zalloc(size, MI_ALIGNMENT_MAX, memid); + if (p != NULL) return p; + + // or fall back to the OS + return _mi_os_alloc(size, memid, stats); +} + +static void mi_arena_meta_free(void* p, mi_memid_t memid, size_t size, mi_stats_t* stats) { + if (mi_memkind_is_os(memid.memkind)) { + _mi_os_free(p, size, memid, stats); + } + else { + mi_assert(memid.memkind == MI_MEM_STATIC); + } +} + +static void* mi_arena_block_start(mi_arena_t* arena, mi_bitmap_index_t bindex) { + return (arena->start + mi_arena_block_size(mi_bitmap_index_bit(bindex))); +} + + +/* ----------------------------------------------------------- + Thread safe allocation in an arena +----------------------------------------------------------- */ + +// claim the `blocks_inuse` bits +static bool mi_arena_try_claim(mi_arena_t* arena, size_t blocks, mi_bitmap_index_t* bitmap_idx) +{ + size_t idx = 0; // mi_atomic_load_relaxed(&arena->search_idx); // start from last search; ok to be relaxed as the exact start does not matter + if (_mi_bitmap_try_find_from_claim_across(arena->blocks_inuse, arena->field_count, idx, blocks, bitmap_idx)) { + mi_atomic_store_relaxed(&arena->search_idx, mi_bitmap_index_field(*bitmap_idx)); // start search from found location next time around + return true; + }; + return false; +} + + +/* ----------------------------------------------------------- + Arena Allocation +----------------------------------------------------------- */ + +static mi_decl_noinline void* mi_arena_try_alloc_at(mi_arena_t* arena, size_t arena_index, size_t needed_bcount, + bool commit, mi_memid_t* memid, mi_os_tld_t* tld) +{ + MI_UNUSED(arena_index); + mi_assert_internal(mi_arena_id_index(arena->id) == arena_index); + + mi_bitmap_index_t bitmap_index; + if (!mi_arena_try_claim(arena, needed_bcount, &bitmap_index)) return NULL; + + // claimed it! + void* p = mi_arena_block_start(arena, bitmap_index); + *memid = mi_memid_create_arena(arena->id, arena->exclusive, bitmap_index); + memid->is_pinned = arena->memid.is_pinned; + + // none of the claimed blocks should be scheduled for a decommit + if (arena->blocks_purge != NULL) { + // this is thread safe as a potential purge only decommits parts that are not yet claimed as used (in `blocks_inuse`). + _mi_bitmap_unclaim_across(arena->blocks_purge, arena->field_count, needed_bcount, bitmap_index); + } + + // set the dirty bits (todo: no need for an atomic op here?) + if (arena->memid.initially_zero && arena->blocks_dirty != NULL) { + memid->initially_zero = _mi_bitmap_claim_across(arena->blocks_dirty, arena->field_count, needed_bcount, bitmap_index, NULL); + } + + // set commit state + if (arena->blocks_committed == NULL) { + // always committed + memid->initially_committed = true; + } + else if (commit) { + // commit requested, but the range may not be committed as a whole: ensure it is committed now + memid->initially_committed = true; + bool any_uncommitted; + _mi_bitmap_claim_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index, &any_uncommitted); + if (any_uncommitted) { + bool commit_zero = false; + if (!_mi_os_commit(p, mi_arena_block_size(needed_bcount), &commit_zero, tld->stats)) { + memid->initially_committed = false; + } + else { + if (commit_zero) { memid->initially_zero = true; } + } + } + } + else { + // no need to commit, but check if already fully committed + memid->initially_committed = _mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, needed_bcount, bitmap_index); + } + + return p; +} + +// allocate in a speficic arena +static void* mi_arena_try_alloc_at_id(mi_arena_id_t arena_id, bool match_numa_node, int numa_node, size_t size, size_t alignment, + bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld ) +{ + MI_UNUSED_RELEASE(alignment); + mi_assert_internal(alignment <= MI_SEGMENT_ALIGN); + const size_t bcount = mi_block_count_of_size(size); + const size_t arena_index = mi_arena_id_index(arena_id); + mi_assert_internal(arena_index < mi_atomic_load_relaxed(&mi_arena_count)); + mi_assert_internal(size <= mi_arena_block_size(bcount)); + + // Check arena suitability + mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_index]); + if (arena == NULL) return NULL; + if (!allow_large && arena->is_large) return NULL; + if (!mi_arena_id_is_suitable(arena->id, arena->exclusive, req_arena_id)) return NULL; + if (req_arena_id == _mi_arena_id_none()) { // in not specific, check numa affinity + const bool numa_suitable = (numa_node < 0 || arena->numa_node < 0 || arena->numa_node == numa_node); + if (match_numa_node) { if (!numa_suitable) return NULL; } + else { if (numa_suitable) return NULL; } + } + + // try to allocate + void* p = mi_arena_try_alloc_at(arena, arena_index, bcount, commit, memid, tld); + mi_assert_internal(p == NULL || _mi_is_aligned(p, alignment)); + return p; +} + + +// allocate from an arena with fallback to the OS +static mi_decl_noinline void* mi_arena_try_alloc(int numa_node, size_t size, size_t alignment, + bool commit, bool allow_large, + mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld ) +{ + MI_UNUSED(alignment); + mi_assert_internal(alignment <= MI_SEGMENT_ALIGN); + const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count); + if mi_likely(max_arena == 0) return NULL; + + if (req_arena_id != _mi_arena_id_none()) { + // try a specific arena if requested + if (mi_arena_id_index(req_arena_id) < max_arena) { + void* p = mi_arena_try_alloc_at_id(req_arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld); + if (p != NULL) return p; + } + } + else { + // try numa affine allocation + for (size_t i = 0; i < max_arena; i++) { + void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld); + if (p != NULL) return p; + } + + // try from another numa node instead.. + if (numa_node >= 0) { // if numa_node was < 0 (no specific affinity requested), all arena's have been tried already + for (size_t i = 0; i < max_arena; i++) { + void* p = mi_arena_try_alloc_at_id(mi_arena_id_create(i), false /* only proceed if not numa local */, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld); + if (p != NULL) return p; + } + } + } + return NULL; +} + +// try to reserve a fresh arena space +static bool mi_arena_reserve(size_t req_size, bool allow_large, mi_arena_id_t req_arena_id, mi_arena_id_t *arena_id) +{ + if (_mi_preloading()) return false; // use OS only while pre loading + if (req_arena_id != _mi_arena_id_none()) return false; + + const size_t arena_count = mi_atomic_load_acquire(&mi_arena_count); + if (arena_count > (MI_MAX_ARENAS - 4)) return false; + + size_t arena_reserve = mi_option_get_size(mi_option_arena_reserve); + if (arena_reserve == 0) return false; + + if (!_mi_os_has_virtual_reserve()) { + arena_reserve = arena_reserve/4; // be conservative if virtual reserve is not supported (for some embedded systems for example) + } + arena_reserve = _mi_align_up(arena_reserve, MI_ARENA_BLOCK_SIZE); + if (arena_count >= 8 && arena_count <= 128) { + arena_reserve = ((size_t)1<<(arena_count/8)) * arena_reserve; // scale up the arena sizes exponentially + } + if (arena_reserve < req_size) return false; // should be able to at least handle the current allocation size + + // commit eagerly? + bool arena_commit = false; + if (mi_option_get(mi_option_arena_eager_commit) == 2) { arena_commit = _mi_os_has_overcommit(); } + else if (mi_option_get(mi_option_arena_eager_commit) == 1) { arena_commit = true; } + + return (mi_reserve_os_memory_ex(arena_reserve, arena_commit, allow_large, false /* exclusive */, arena_id) == 0); +} + + +void* _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large, + mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld) +{ + mi_assert_internal(memid != NULL && tld != NULL); + mi_assert_internal(size > 0); + *memid = _mi_memid_none(); + + const int numa_node = _mi_os_numa_node(tld); // current numa node + + // try to allocate in an arena if the alignment is small enough and the object is not too small (as for heap meta data) + if (size >= MI_ARENA_MIN_OBJ_SIZE && alignment <= MI_SEGMENT_ALIGN && align_offset == 0) { + void* p = mi_arena_try_alloc(numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld); + if (p != NULL) return p; + + // otherwise, try to first eagerly reserve a new arena + if (req_arena_id == _mi_arena_id_none()) { + mi_arena_id_t arena_id = 0; + if (mi_arena_reserve(size, allow_large, req_arena_id, &arena_id)) { + // and try allocate in there + mi_assert_internal(req_arena_id == _mi_arena_id_none()); + p = mi_arena_try_alloc_at_id(arena_id, true, numa_node, size, alignment, commit, allow_large, req_arena_id, memid, tld); + if (p != NULL) return p; + } + } + } + + // if we cannot use OS allocation, return NULL + if (mi_option_is_enabled(mi_option_limit_os_alloc) || req_arena_id != _mi_arena_id_none()) { + errno = ENOMEM; + return NULL; + } + + // finally, fall back to the OS + if (align_offset > 0) { + return _mi_os_alloc_aligned_at_offset(size, alignment, align_offset, commit, allow_large, memid, tld->stats); + } + else { + return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid, tld->stats); + } +} + +void* _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld) +{ + return _mi_arena_alloc_aligned(size, MI_ARENA_BLOCK_SIZE, 0, commit, allow_large, req_arena_id, memid, tld); +} + + +void* mi_arena_area(mi_arena_id_t arena_id, size_t* size) { + if (size != NULL) *size = 0; + size_t arena_index = mi_arena_id_index(arena_id); + if (arena_index >= MI_MAX_ARENAS) return NULL; + mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[arena_index]); + if (arena == NULL) return NULL; + if (size != NULL) { *size = mi_arena_block_size(arena->block_count); } + return arena->start; +} + + +/* ----------------------------------------------------------- + Arena purge +----------------------------------------------------------- */ + +static long mi_arena_purge_delay(void) { + // <0 = no purging allowed, 0=immediate purging, >0=milli-second delay + return (mi_option_get(mi_option_purge_delay) * mi_option_get(mi_option_arena_purge_mult)); +} + +// reset or decommit in an arena and update the committed/decommit bitmaps +// assumes we own the area (i.e. blocks_in_use is claimed by us) +static void mi_arena_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks, mi_stats_t* stats) { + mi_assert_internal(arena->blocks_committed != NULL); + mi_assert_internal(arena->blocks_purge != NULL); + mi_assert_internal(!arena->memid.is_pinned); + const size_t size = mi_arena_block_size(blocks); + void* const p = mi_arena_block_start(arena, bitmap_idx); + bool needs_recommit; + if (_mi_bitmap_is_claimed_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx)) { + // all blocks are committed, we can purge freely + needs_recommit = _mi_os_purge(p, size, stats); + } + else { + // some blocks are not committed -- this can happen when a partially committed block is freed + // in `_mi_arena_free` and it is conservatively marked as uncommitted but still scheduled for a purge + // we need to ensure we do not try to reset (as that may be invalid for uncommitted memory), + // and also undo the decommit stats (as it was already adjusted) + mi_assert_internal(mi_option_is_enabled(mi_option_purge_decommits)); + needs_recommit = _mi_os_purge_ex(p, size, false /* allow reset? */, stats); + _mi_stat_increase(&stats->committed, size); + } + + // clear the purged blocks + _mi_bitmap_unclaim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx); + // update committed bitmap + if (needs_recommit) { + _mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx); + } +} + +// Schedule a purge. This is usually delayed to avoid repeated decommit/commit calls. +// Note: assumes we (still) own the area as we may purge immediately +static void mi_arena_schedule_purge(mi_arena_t* arena, size_t bitmap_idx, size_t blocks, mi_stats_t* stats) { + mi_assert_internal(arena->blocks_purge != NULL); + const long delay = mi_arena_purge_delay(); + if (delay < 0) return; // is purging allowed at all? + + if (_mi_preloading() || delay == 0) { + // decommit directly + mi_arena_purge(arena, bitmap_idx, blocks, stats); + } + else { + // schedule decommit + mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire); + if (expire != 0) { + mi_atomic_addi64_acq_rel(&arena->purge_expire, delay/10); // add smallish extra delay + } + else { + mi_atomic_storei64_release(&arena->purge_expire, _mi_clock_now() + delay); + } + _mi_bitmap_claim_across(arena->blocks_purge, arena->field_count, blocks, bitmap_idx, NULL); + } +} + +// purge a range of blocks +// return true if the full range was purged. +// assumes we own the area (i.e. blocks_in_use is claimed by us) +static bool mi_arena_purge_range(mi_arena_t* arena, size_t idx, size_t startidx, size_t bitlen, size_t purge, mi_stats_t* stats) { + const size_t endidx = startidx + bitlen; + size_t bitidx = startidx; + bool all_purged = false; + while (bitidx < endidx) { + // count consequetive ones in the purge mask + size_t count = 0; + while (bitidx + count < endidx && (purge & ((size_t)1 << (bitidx + count))) != 0) { + count++; + } + if (count > 0) { + // found range to be purged + const mi_bitmap_index_t range_idx = mi_bitmap_index_create(idx, bitidx); + mi_arena_purge(arena, range_idx, count, stats); + if (count == bitlen) { + all_purged = true; + } + } + bitidx += (count+1); // +1 to skip the zero bit (or end) + } + return all_purged; +} + +// returns true if anything was purged +static bool mi_arena_try_purge(mi_arena_t* arena, mi_msecs_t now, bool force, mi_stats_t* stats) +{ + if (arena->memid.is_pinned || arena->blocks_purge == NULL) return false; + mi_msecs_t expire = mi_atomic_loadi64_relaxed(&arena->purge_expire); + if (expire == 0) return false; + if (!force && expire > now) return false; + + // reset expire (if not already set concurrently) + mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire, 0); + + // potential purges scheduled, walk through the bitmap + bool any_purged = false; + bool full_purge = true; + for (size_t i = 0; i < arena->field_count; i++) { + size_t purge = mi_atomic_load_relaxed(&arena->blocks_purge[i]); + if (purge != 0) { + size_t bitidx = 0; + while (bitidx < MI_BITMAP_FIELD_BITS) { + // find consequetive range of ones in the purge mask + size_t bitlen = 0; + while (bitidx + bitlen < MI_BITMAP_FIELD_BITS && (purge & ((size_t)1 << (bitidx + bitlen))) != 0) { + bitlen++; + } + // try to claim the longest range of corresponding in_use bits + const mi_bitmap_index_t bitmap_index = mi_bitmap_index_create(i, bitidx); + while( bitlen > 0 ) { + if (_mi_bitmap_try_claim(arena->blocks_inuse, arena->field_count, bitlen, bitmap_index)) { + break; + } + bitlen--; + } + // actual claimed bits at `in_use` + if (bitlen > 0) { + // read purge again now that we have the in_use bits + purge = mi_atomic_load_acquire(&arena->blocks_purge[i]); + if (!mi_arena_purge_range(arena, i, bitidx, bitlen, purge, stats)) { + full_purge = false; + } + any_purged = true; + // release the claimed `in_use` bits again + _mi_bitmap_unclaim(arena->blocks_inuse, arena->field_count, bitlen, bitmap_index); + } + bitidx += (bitlen+1); // +1 to skip the zero (or end) + } // while bitidx + } // purge != 0 + } + // if not fully purged, make sure to purge again in the future + if (!full_purge) { + const long delay = mi_arena_purge_delay(); + mi_msecs_t expected = 0; + mi_atomic_casi64_strong_acq_rel(&arena->purge_expire,&expected,_mi_clock_now() + delay); + } + return any_purged; +} + +static void mi_arenas_try_purge( bool force, bool visit_all, mi_stats_t* stats ) { + if (_mi_preloading() || mi_arena_purge_delay() <= 0) return; // nothing will be scheduled + + const size_t max_arena = mi_atomic_load_acquire(&mi_arena_count); + if (max_arena == 0) return; + + // allow only one thread to purge at a time + static mi_atomic_guard_t purge_guard; + mi_atomic_guard(&purge_guard) + { + mi_msecs_t now = _mi_clock_now(); + size_t max_purge_count = (visit_all ? max_arena : 1); + for (size_t i = 0; i < max_arena; i++) { + mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]); + if (arena != NULL) { + if (mi_arena_try_purge(arena, now, force, stats)) { + if (max_purge_count <= 1) break; + max_purge_count--; + } + } + } + } +} + + +/* ----------------------------------------------------------- + Arena free +----------------------------------------------------------- */ + +void _mi_arena_free(void* p, size_t size, size_t committed_size, mi_memid_t memid, mi_stats_t* stats) { + mi_assert_internal(size > 0 && stats != NULL); + mi_assert_internal(committed_size <= size); + if (p==NULL) return; + if (size==0) return; + const bool all_committed = (committed_size == size); + + if (mi_memkind_is_os(memid.memkind)) { + // was a direct OS allocation, pass through + if (!all_committed && committed_size > 0) { + // if partially committed, adjust the committed stats (as `_mi_os_free` will increase decommit by the full size) + _mi_stat_decrease(&stats->committed, committed_size); + } + _mi_os_free(p, size, memid, stats); + } + else if (memid.memkind == MI_MEM_ARENA) { + // allocated in an arena + size_t arena_idx; + size_t bitmap_idx; + mi_arena_memid_indices(memid, &arena_idx, &bitmap_idx); + mi_assert_internal(arena_idx < MI_MAX_ARENAS); + mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t,&mi_arenas[arena_idx]); + mi_assert_internal(arena != NULL); + const size_t blocks = mi_block_count_of_size(size); + + // checks + if (arena == NULL) { + _mi_error_message(EINVAL, "trying to free from non-existent arena: %p, size %zu, memid: 0x%zx\n", p, size, memid); + return; + } + mi_assert_internal(arena->field_count > mi_bitmap_index_field(bitmap_idx)); + if (arena->field_count <= mi_bitmap_index_field(bitmap_idx)) { + _mi_error_message(EINVAL, "trying to free from non-existent arena block: %p, size %zu, memid: 0x%zx\n", p, size, memid); + return; + } + + // need to set all memory to undefined as some parts may still be marked as no_access (like padding etc.) + mi_track_mem_undefined(p,size); + + // potentially decommit + if (arena->memid.is_pinned || arena->blocks_committed == NULL) { + mi_assert_internal(all_committed); + } + else { + mi_assert_internal(arena->blocks_committed != NULL); + mi_assert_internal(arena->blocks_purge != NULL); + + if (!all_committed) { + // mark the entire range as no longer committed (so we recommit the full range when re-using) + _mi_bitmap_unclaim_across(arena->blocks_committed, arena->field_count, blocks, bitmap_idx); + mi_track_mem_noaccess(p,size); + if (committed_size > 0) { + // if partially committed, adjust the committed stats (is it will be recommitted when re-using) + // in the delayed purge, we now need to not count a decommit if the range is not marked as committed. + _mi_stat_decrease(&stats->committed, committed_size); + } + // note: if not all committed, it may be that the purge will reset/decommit the entire range + // that contains already decommitted parts. Since purge consistently uses reset or decommit that + // works (as we should never reset decommitted parts). + } + // (delay) purge the entire range + mi_arena_schedule_purge(arena, bitmap_idx, blocks, stats); + } + + // and make it available to others again + bool all_inuse = _mi_bitmap_unclaim_across(arena->blocks_inuse, arena->field_count, blocks, bitmap_idx); + if (!all_inuse) { + _mi_error_message(EAGAIN, "trying to free an already freed arena block: %p, size %zu\n", p, size); + return; + }; + } + else { + // arena was none, external, or static; nothing to do + mi_assert_internal(memid.memkind < MI_MEM_OS); + } + + // purge expired decommits + mi_arenas_try_purge(false, false, stats); +} + +// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit` +// for dynamic libraries that are unloaded and need to release all their allocated memory. +static void mi_arenas_unsafe_destroy(void) { + const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count); + size_t new_max_arena = 0; + for (size_t i = 0; i < max_arena; i++) { + mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]); + if (arena != NULL) { + if (arena->start != NULL && mi_memkind_is_os(arena->memid.memkind)) { + mi_atomic_store_ptr_release(mi_arena_t, &mi_arenas[i], NULL); + _mi_os_free(arena->start, mi_arena_size(arena), arena->memid, &_mi_stats_main); + } + else { + new_max_arena = i; + } + mi_arena_meta_free(arena, arena->meta_memid, arena->meta_size, &_mi_stats_main); + } + } + + // try to lower the max arena. + size_t expected = max_arena; + mi_atomic_cas_strong_acq_rel(&mi_arena_count, &expected, new_max_arena); +} + +// Purge the arenas; if `force_purge` is true, amenable parts are purged even if not yet expired +void _mi_arena_collect(bool force_purge, mi_stats_t* stats) { + mi_arenas_try_purge(force_purge, true /* visit all */, stats); +} + +// destroy owned arenas; this is unsafe and should only be done using `mi_option_destroy_on_exit` +// for dynamic libraries that are unloaded and need to release all their allocated memory. +void _mi_arena_unsafe_destroy_all(mi_stats_t* stats) { + mi_arenas_unsafe_destroy(); + _mi_arena_collect(true /* force purge */, stats); // purge non-owned arenas +} + +// Is a pointer inside any of our arenas? +bool _mi_arena_contains(const void* p) { + const size_t max_arena = mi_atomic_load_relaxed(&mi_arena_count); + for (size_t i = 0; i < max_arena; i++) { + mi_arena_t* arena = mi_atomic_load_ptr_acquire(mi_arena_t, &mi_arenas[i]); + if (arena != NULL && arena->start <= (const uint8_t*)p && arena->start + mi_arena_block_size(arena->block_count) > (const uint8_t*)p) { + return true; + } + } + return false; +} + + +/* ----------------------------------------------------------- + Add an arena. +----------------------------------------------------------- */ + +static bool mi_arena_add(mi_arena_t* arena, mi_arena_id_t* arena_id) { + mi_assert_internal(arena != NULL); + mi_assert_internal((uintptr_t)mi_atomic_load_ptr_relaxed(uint8_t,&arena->start) % MI_SEGMENT_ALIGN == 0); + mi_assert_internal(arena->block_count > 0); + if (arena_id != NULL) { *arena_id = -1; } + + size_t i = mi_atomic_increment_acq_rel(&mi_arena_count); + if (i >= MI_MAX_ARENAS) { + mi_atomic_decrement_acq_rel(&mi_arena_count); + return false; + } + arena->id = mi_arena_id_create(i); + mi_atomic_store_ptr_release(mi_arena_t,&mi_arenas[i], arena); + if (arena_id != NULL) { *arena_id = arena->id; } + return true; +} + +static bool mi_manage_os_memory_ex2(void* start, size_t size, bool is_large, int numa_node, bool exclusive, mi_memid_t memid, mi_arena_id_t* arena_id) mi_attr_noexcept +{ + if (arena_id != NULL) *arena_id = _mi_arena_id_none(); + if (size < MI_ARENA_BLOCK_SIZE) return false; + + if (is_large) { + mi_assert_internal(memid.initially_committed && memid.is_pinned); + } + + const size_t bcount = size / MI_ARENA_BLOCK_SIZE; + const size_t fields = _mi_divide_up(bcount, MI_BITMAP_FIELD_BITS); + const size_t bitmaps = (memid.is_pinned ? 2 : 4); + const size_t asize = sizeof(mi_arena_t) + (bitmaps*fields*sizeof(mi_bitmap_field_t)); + mi_memid_t meta_memid; + mi_arena_t* arena = (mi_arena_t*)mi_arena_meta_zalloc(asize, &meta_memid, &_mi_stats_main); // TODO: can we avoid allocating from the OS? + if (arena == NULL) return false; + + // already zero'd due to os_alloc + // _mi_memzero(arena, asize); + arena->id = _mi_arena_id_none(); + arena->memid = memid; + arena->exclusive = exclusive; + arena->meta_size = asize; + arena->meta_memid = meta_memid; + arena->block_count = bcount; + arena->field_count = fields; + arena->start = (uint8_t*)start; + arena->numa_node = numa_node; // TODO: or get the current numa node if -1? (now it allows anyone to allocate on -1) + arena->is_large = is_large; + arena->purge_expire = 0; + arena->search_idx = 0; + arena->blocks_dirty = &arena->blocks_inuse[fields]; // just after inuse bitmap + arena->blocks_committed = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[2*fields]); // just after dirty bitmap + arena->blocks_purge = (arena->memid.is_pinned ? NULL : &arena->blocks_inuse[3*fields]); // just after committed bitmap + // initialize committed bitmap? + if (arena->blocks_committed != NULL && arena->memid.initially_committed) { + memset((void*)arena->blocks_committed, 0xFF, fields*sizeof(mi_bitmap_field_t)); // cast to void* to avoid atomic warning + } + + // and claim leftover blocks if needed (so we never allocate there) + ptrdiff_t post = (fields * MI_BITMAP_FIELD_BITS) - bcount; + mi_assert_internal(post >= 0); + if (post > 0) { + // don't use leftover bits at the end + mi_bitmap_index_t postidx = mi_bitmap_index_create(fields - 1, MI_BITMAP_FIELD_BITS - post); + _mi_bitmap_claim(arena->blocks_inuse, fields, post, postidx, NULL); + } + return mi_arena_add(arena, arena_id); + +} + +bool mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept { + mi_memid_t memid = _mi_memid_create(MI_MEM_EXTERNAL); + memid.initially_committed = is_committed; + memid.initially_zero = is_zero; + memid.is_pinned = is_large; + return mi_manage_os_memory_ex2(start,size,is_large,numa_node,exclusive,memid, arena_id); +} + +// Reserve a range of regular OS memory +int mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept { + if (arena_id != NULL) *arena_id = _mi_arena_id_none(); + size = _mi_align_up(size, MI_ARENA_BLOCK_SIZE); // at least one block + mi_memid_t memid; + void* start = _mi_os_alloc_aligned(size, MI_SEGMENT_ALIGN, commit, allow_large, &memid, &_mi_stats_main); + if (start == NULL) return ENOMEM; + const bool is_large = memid.is_pinned; // todo: use separate is_large field? + if (!mi_manage_os_memory_ex2(start, size, is_large, -1 /* numa node */, exclusive, memid, arena_id)) { + _mi_os_free_ex(start, size, commit, memid, &_mi_stats_main); + _mi_verbose_message("failed to reserve %zu k memory\n", _mi_divide_up(size, 1024)); + return ENOMEM; + } + _mi_verbose_message("reserved %zu KiB memory%s\n", _mi_divide_up(size, 1024), is_large ? " (in large os pages)" : ""); + return 0; +} + + +// Manage a range of regular OS memory +bool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept { + return mi_manage_os_memory_ex(start, size, is_committed, is_large, is_zero, numa_node, false /* exclusive? */, NULL); +} + +// Reserve a range of regular OS memory +int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept { + return mi_reserve_os_memory_ex(size, commit, allow_large, false, NULL); +} + + +/* ----------------------------------------------------------- + Debugging +----------------------------------------------------------- */ + +static size_t mi_debug_show_bitmap(const char* prefix, mi_bitmap_field_t* fields, size_t field_count ) { + size_t inuse_count = 0; + for (size_t i = 0; i < field_count; i++) { + char buf[MI_BITMAP_FIELD_BITS + 1]; + uintptr_t field = mi_atomic_load_relaxed(&fields[i]); + for (size_t bit = 0; bit < MI_BITMAP_FIELD_BITS; bit++) { + bool inuse = ((((uintptr_t)1 << bit) & field) != 0); + if (inuse) inuse_count++; + buf[MI_BITMAP_FIELD_BITS - 1 - bit] = (inuse ? 'x' : '.'); + } + buf[MI_BITMAP_FIELD_BITS] = 0; + _mi_verbose_message("%s%s\n", prefix, buf); + } + return inuse_count; +} + +void mi_debug_show_arenas(void) mi_attr_noexcept { + size_t max_arenas = mi_atomic_load_relaxed(&mi_arena_count); + for (size_t i = 0; i < max_arenas; i++) { + mi_arena_t* arena = mi_atomic_load_ptr_relaxed(mi_arena_t, &mi_arenas[i]); + if (arena == NULL) break; + size_t inuse_count = 0; + _mi_verbose_message("arena %zu: %zu blocks with %zu fields\n", i, arena->block_count, arena->field_count); + inuse_count += mi_debug_show_bitmap(" ", arena->blocks_inuse, arena->field_count); + _mi_verbose_message(" blocks in use ('x'): %zu\n", inuse_count); + } +} + + +/* ----------------------------------------------------------- + Reserve a huge page arena. +----------------------------------------------------------- */ +// reserve at a specific numa node +int mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_msecs, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept { + if (arena_id != NULL) *arena_id = -1; + if (pages==0) return 0; + if (numa_node < -1) numa_node = -1; + if (numa_node >= 0) numa_node = numa_node % _mi_os_numa_node_count(); + size_t hsize = 0; + size_t pages_reserved = 0; + mi_memid_t memid; + void* p = _mi_os_alloc_huge_os_pages(pages, numa_node, timeout_msecs, &pages_reserved, &hsize, &memid); + if (p==NULL || pages_reserved==0) { + _mi_warning_message("failed to reserve %zu GiB huge pages\n", pages); + return ENOMEM; + } + _mi_verbose_message("numa node %i: reserved %zu GiB huge pages (of the %zu GiB requested)\n", numa_node, pages_reserved, pages); + + if (!mi_manage_os_memory_ex2(p, hsize, true, numa_node, exclusive, memid, arena_id)) { + _mi_os_free(p, hsize, memid, &_mi_stats_main); + return ENOMEM; + } + return 0; +} + +int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept { + return mi_reserve_huge_os_pages_at_ex(pages, numa_node, timeout_msecs, false, NULL); +} + +// reserve huge pages evenly among the given number of numa nodes (or use the available ones as detected) +int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept { + if (pages == 0) return 0; + + // pages per numa node + size_t numa_count = (numa_nodes > 0 ? numa_nodes : _mi_os_numa_node_count()); + if (numa_count <= 0) numa_count = 1; + const size_t pages_per = pages / numa_count; + const size_t pages_mod = pages % numa_count; + const size_t timeout_per = (timeout_msecs==0 ? 0 : (timeout_msecs / numa_count) + 50); + + // reserve evenly among numa nodes + for (size_t numa_node = 0; numa_node < numa_count && pages > 0; numa_node++) { + size_t node_pages = pages_per; // can be 0 + if (numa_node < pages_mod) node_pages++; + int err = mi_reserve_huge_os_pages_at(node_pages, (int)numa_node, timeout_per); + if (err) return err; + if (pages < node_pages) { + pages = 0; + } + else { + pages -= node_pages; + } + } + + return 0; +} + +int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept { + MI_UNUSED(max_secs); + _mi_warning_message("mi_reserve_huge_os_pages is deprecated: use mi_reserve_huge_os_pages_interleave/at instead\n"); + if (pages_reserved != NULL) *pages_reserved = 0; + int err = mi_reserve_huge_os_pages_interleave(pages, 0, (size_t)(max_secs * 1000.0)); + if (err==0 && pages_reserved!=NULL) *pages_reserved = pages; + return err; +} diff --git a/compat/mimalloc/bitmap.c b/compat/mimalloc/bitmap.c new file mode 100644 index 00000000000000..878f0ab3250a47 --- /dev/null +++ b/compat/mimalloc/bitmap.c @@ -0,0 +1,432 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019-2023 Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ---------------------------------------------------------------------------- +Concurrent bitmap that can set/reset sequences of bits atomically, +represeted as an array of fields where each field is a machine word (`size_t`) + +There are two api's; the standard one cannot have sequences that cross +between the bitmap fields (and a sequence must be <= MI_BITMAP_FIELD_BITS). + +The `_across` postfixed functions do allow sequences that can cross over +between the fields. (This is used in arena allocation) +---------------------------------------------------------------------------- */ + +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "bitmap.h" + +/* ----------------------------------------------------------- + Bitmap definition +----------------------------------------------------------- */ + +// The bit mask for a given number of blocks at a specified bit index. +static inline size_t mi_bitmap_mask_(size_t count, size_t bitidx) { + mi_assert_internal(count + bitidx <= MI_BITMAP_FIELD_BITS); + mi_assert_internal(count > 0); + if (count >= MI_BITMAP_FIELD_BITS) return MI_BITMAP_FIELD_FULL; + if (count == 0) return 0; + return ((((size_t)1 << count) - 1) << bitidx); +} + + +/* ----------------------------------------------------------- + Claim a bit sequence atomically +----------------------------------------------------------- */ + +// Try to atomically claim a sequence of `count` bits in a single +// field at `idx` in `bitmap`. Returns `true` on success. +inline bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx) +{ + mi_assert_internal(bitmap_idx != NULL); + mi_assert_internal(count <= MI_BITMAP_FIELD_BITS); + mi_assert_internal(count > 0); + mi_bitmap_field_t* field = &bitmap[idx]; + size_t map = mi_atomic_load_relaxed(field); + if (map==MI_BITMAP_FIELD_FULL) return false; // short cut + + // search for 0-bit sequence of length count + const size_t mask = mi_bitmap_mask_(count, 0); + const size_t bitidx_max = MI_BITMAP_FIELD_BITS - count; + +#ifdef MI_HAVE_FAST_BITSCAN + size_t bitidx = mi_ctz(~map); // quickly find the first zero bit if possible +#else + size_t bitidx = 0; // otherwise start at 0 +#endif + size_t m = (mask << bitidx); // invariant: m == mask shifted by bitidx + + // scan linearly for a free range of zero bits + while (bitidx <= bitidx_max) { + const size_t mapm = (map & m); + if (mapm == 0) { // are the mask bits free at bitidx? + mi_assert_internal((m >> bitidx) == mask); // no overflow? + const size_t newmap = (map | m); + mi_assert_internal((newmap^map) >> bitidx == mask); + if (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)) { // TODO: use weak cas here? + // no success, another thread claimed concurrently.. keep going (with updated `map`) + continue; + } + else { + // success, we claimed the bits! + *bitmap_idx = mi_bitmap_index_create(idx, bitidx); + return true; + } + } + else { + // on to the next bit range +#ifdef MI_HAVE_FAST_BITSCAN + mi_assert_internal(mapm != 0); + const size_t shift = (count == 1 ? 1 : (MI_INTPTR_BITS - mi_clz(mapm) - bitidx)); + mi_assert_internal(shift > 0 && shift <= count); +#else + const size_t shift = 1; +#endif + bitidx += shift; + m <<= shift; + } + } + // no bits found + return false; +} + +// Find `count` bits of 0 and set them to 1 atomically; returns `true` on success. +// Starts at idx, and wraps around to search in all `bitmap_fields` fields. +// `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields. +bool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx) { + size_t idx = start_field_idx; + for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) { + if (idx >= bitmap_fields) { idx = 0; } // wrap + if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) { + return true; + } + } + return false; +} + +// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fullfilled +bool _mi_bitmap_try_find_from_claim_pred(mi_bitmap_t bitmap, const size_t bitmap_fields, + const size_t start_field_idx, const size_t count, + mi_bitmap_pred_fun_t pred_fun, void* pred_arg, + mi_bitmap_index_t* bitmap_idx) { + size_t idx = start_field_idx; + for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) { + if (idx >= bitmap_fields) idx = 0; // wrap + if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) { + if (pred_fun == NULL || pred_fun(*bitmap_idx, pred_arg)) { + return true; + } + // predicate returned false, unclaim and look further + _mi_bitmap_unclaim(bitmap, bitmap_fields, count, *bitmap_idx); + } + } + return false; +} + +// Set `count` bits at `bitmap_idx` to 0 atomically +// Returns `true` if all `count` bits were 1 previously. +bool _mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const size_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields); + // mi_assert_internal((bitmap[idx] & mask) == mask); + const size_t prev = mi_atomic_and_acq_rel(&bitmap[idx], ~mask); + return ((prev & mask) == mask); +} + + +// Set `count` bits at `bitmap_idx` to 1 atomically +// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit. +bool _mi_bitmap_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_zero) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const size_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields); + //mi_assert_internal(any_zero != NULL || (bitmap[idx] & mask) == 0); + size_t prev = mi_atomic_or_acq_rel(&bitmap[idx], mask); + if (any_zero != NULL) { *any_zero = ((prev & mask) != mask); } + return ((prev & mask) == 0); +} + +// Returns `true` if all `count` bits were 1. `any_ones` is `true` if there was at least one bit set to one. +static bool mi_bitmap_is_claimedx(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_ones) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const size_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields); + const size_t field = mi_atomic_load_relaxed(&bitmap[idx]); + if (any_ones != NULL) { *any_ones = ((field & mask) != 0); } + return ((field & mask) == mask); +} + +// Try to set `count` bits at `bitmap_idx` from 0 to 1 atomically. +// Returns `true` if successful when all previous `count` bits were 0. +bool _mi_bitmap_try_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + const size_t idx = mi_bitmap_index_field(bitmap_idx); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + const size_t mask = mi_bitmap_mask_(count, bitidx); + mi_assert_internal(bitmap_fields > idx); MI_UNUSED(bitmap_fields); + size_t expected = mi_atomic_load_relaxed(&bitmap[idx]); + do { + if ((expected & mask) != 0) return false; + } + while (!mi_atomic_cas_strong_acq_rel(&bitmap[idx], &expected, expected | mask)); + mi_assert_internal((expected & mask) == 0); + return true; +} + + +bool _mi_bitmap_is_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + return mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, NULL); +} + +bool _mi_bitmap_is_any_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + bool any_ones; + mi_bitmap_is_claimedx(bitmap, bitmap_fields, count, bitmap_idx, &any_ones); + return any_ones; +} + + +//-------------------------------------------------------------------------- +// the `_across` functions work on bitmaps where sequences can cross over +// between the fields. This is used in arena allocation +//-------------------------------------------------------------------------- + +// Try to atomically claim a sequence of `count` bits starting from the field +// at `idx` in `bitmap` and crossing into subsequent fields. Returns `true` on success. +// Only needs to consider crossing into the next fields (see `mi_bitmap_try_find_from_claim_across`) +static bool mi_bitmap_try_find_claim_field_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t idx, const size_t count, const size_t retries, mi_bitmap_index_t* bitmap_idx) +{ + mi_assert_internal(bitmap_idx != NULL); + + // check initial trailing zeros + mi_bitmap_field_t* field = &bitmap[idx]; + size_t map = mi_atomic_load_relaxed(field); + const size_t initial = mi_clz(map); // count of initial zeros starting at idx + mi_assert_internal(initial <= MI_BITMAP_FIELD_BITS); + if (initial == 0) return false; + if (initial >= count) return _mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx); // no need to cross fields (this case won't happen for us) + if (_mi_divide_up(count - initial, MI_BITMAP_FIELD_BITS) >= (bitmap_fields - idx)) return false; // not enough entries + + // scan ahead + size_t found = initial; + size_t mask = 0; // mask bits for the final field + while(found < count) { + field++; + map = mi_atomic_load_relaxed(field); + const size_t mask_bits = (found + MI_BITMAP_FIELD_BITS <= count ? MI_BITMAP_FIELD_BITS : (count - found)); + mi_assert_internal(mask_bits > 0 && mask_bits <= MI_BITMAP_FIELD_BITS); + mask = mi_bitmap_mask_(mask_bits, 0); + if ((map & mask) != 0) return false; // some part is already claimed + found += mask_bits; + } + mi_assert_internal(field < &bitmap[bitmap_fields]); + + // we found a range of contiguous zeros up to the final field; mask contains mask in the final field + // now try to claim the range atomically + mi_bitmap_field_t* const final_field = field; + const size_t final_mask = mask; + mi_bitmap_field_t* const initial_field = &bitmap[idx]; + const size_t initial_idx = MI_BITMAP_FIELD_BITS - initial; + const size_t initial_mask = mi_bitmap_mask_(initial, initial_idx); + + // initial field + size_t newmap; + field = initial_field; + map = mi_atomic_load_relaxed(field); + do { + newmap = (map | initial_mask); + if ((map & initial_mask) != 0) { goto rollback; }; + } while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)); + + // intermediate fields + while (++field < final_field) { + newmap = MI_BITMAP_FIELD_FULL; + map = 0; + if (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)) { goto rollback; } + } + + // final field + mi_assert_internal(field == final_field); + map = mi_atomic_load_relaxed(field); + do { + newmap = (map | final_mask); + if ((map & final_mask) != 0) { goto rollback; } + } while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)); + + // claimed! + *bitmap_idx = mi_bitmap_index_create(idx, initial_idx); + return true; + +rollback: + // roll back intermediate fields + // (we just failed to claim `field` so decrement first) + while (--field > initial_field) { + newmap = 0; + map = MI_BITMAP_FIELD_FULL; + mi_assert_internal(mi_atomic_load_relaxed(field) == map); + mi_atomic_store_release(field, newmap); + } + if (field == initial_field) { // (if we failed on the initial field, `field + 1 == initial_field`) + map = mi_atomic_load_relaxed(field); + do { + mi_assert_internal((map & initial_mask) == initial_mask); + newmap = (map & ~initial_mask); + } while (!mi_atomic_cas_strong_acq_rel(field, &map, newmap)); + } + // retry? (we make a recursive call instead of goto to be able to use const declarations) + if (retries <= 2) { + return mi_bitmap_try_find_claim_field_across(bitmap, bitmap_fields, idx, count, retries+1, bitmap_idx); + } + else { + return false; + } +} + + +// Find `count` bits of zeros and set them to 1 atomically; returns `true` on success. +// Starts at idx, and wraps around to search in all `bitmap_fields` fields. +bool _mi_bitmap_try_find_from_claim_across(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx) { + mi_assert_internal(count > 0); + if (count <= 2) { + // we don't bother with crossover fields for small counts + return _mi_bitmap_try_find_from_claim(bitmap, bitmap_fields, start_field_idx, count, bitmap_idx); + } + + // visit the fields + size_t idx = start_field_idx; + for (size_t visited = 0; visited < bitmap_fields; visited++, idx++) { + if (idx >= bitmap_fields) { idx = 0; } // wrap + // first try to claim inside a field + if (count <= MI_BITMAP_FIELD_BITS) { + if (_mi_bitmap_try_find_claim_field(bitmap, idx, count, bitmap_idx)) { + return true; + } + } + // if that fails, then try to claim across fields + if (mi_bitmap_try_find_claim_field_across(bitmap, bitmap_fields, idx, count, 0, bitmap_idx)) { + return true; + } + } + return false; +} + +// Helper for masks across fields; returns the mid count, post_mask may be 0 +static size_t mi_bitmap_mask_across(mi_bitmap_index_t bitmap_idx, size_t bitmap_fields, size_t count, size_t* pre_mask, size_t* mid_mask, size_t* post_mask) { + MI_UNUSED(bitmap_fields); + const size_t bitidx = mi_bitmap_index_bit_in_field(bitmap_idx); + if mi_likely(bitidx + count <= MI_BITMAP_FIELD_BITS) { + *pre_mask = mi_bitmap_mask_(count, bitidx); + *mid_mask = 0; + *post_mask = 0; + mi_assert_internal(mi_bitmap_index_field(bitmap_idx) < bitmap_fields); + return 0; + } + else { + const size_t pre_bits = MI_BITMAP_FIELD_BITS - bitidx; + mi_assert_internal(pre_bits < count); + *pre_mask = mi_bitmap_mask_(pre_bits, bitidx); + count -= pre_bits; + const size_t mid_count = (count / MI_BITMAP_FIELD_BITS); + *mid_mask = MI_BITMAP_FIELD_FULL; + count %= MI_BITMAP_FIELD_BITS; + *post_mask = (count==0 ? 0 : mi_bitmap_mask_(count, 0)); + mi_assert_internal(mi_bitmap_index_field(bitmap_idx) + mid_count + (count==0 ? 0 : 1) < bitmap_fields); + return mid_count; + } +} + +// Set `count` bits at `bitmap_idx` to 0 atomically +// Returns `true` if all `count` bits were 1 previously. +bool _mi_bitmap_unclaim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + size_t idx = mi_bitmap_index_field(bitmap_idx); + size_t pre_mask; + size_t mid_mask; + size_t post_mask; + size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask); + bool all_one = true; + mi_bitmap_field_t* field = &bitmap[idx]; + size_t prev = mi_atomic_and_acq_rel(field++, ~pre_mask); // clear first part + if ((prev & pre_mask) != pre_mask) all_one = false; + while(mid_count-- > 0) { + prev = mi_atomic_and_acq_rel(field++, ~mid_mask); // clear mid part + if ((prev & mid_mask) != mid_mask) all_one = false; + } + if (post_mask!=0) { + prev = mi_atomic_and_acq_rel(field, ~post_mask); // clear end part + if ((prev & post_mask) != post_mask) all_one = false; + } + return all_one; +} + +// Set `count` bits at `bitmap_idx` to 1 atomically +// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit. +bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero) { + size_t idx = mi_bitmap_index_field(bitmap_idx); + size_t pre_mask; + size_t mid_mask; + size_t post_mask; + size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask); + bool all_zero = true; + bool any_zero = false; + _Atomic(size_t)*field = &bitmap[idx]; + size_t prev = mi_atomic_or_acq_rel(field++, pre_mask); + if ((prev & pre_mask) != 0) all_zero = false; + if ((prev & pre_mask) != pre_mask) any_zero = true; + while (mid_count-- > 0) { + prev = mi_atomic_or_acq_rel(field++, mid_mask); + if ((prev & mid_mask) != 0) all_zero = false; + if ((prev & mid_mask) != mid_mask) any_zero = true; + } + if (post_mask!=0) { + prev = mi_atomic_or_acq_rel(field, post_mask); + if ((prev & post_mask) != 0) all_zero = false; + if ((prev & post_mask) != post_mask) any_zero = true; + } + if (pany_zero != NULL) { *pany_zero = any_zero; } + return all_zero; +} + + +// Returns `true` if all `count` bits were 1. +// `any_ones` is `true` if there was at least one bit set to one. +static bool mi_bitmap_is_claimedx_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_ones) { + size_t idx = mi_bitmap_index_field(bitmap_idx); + size_t pre_mask; + size_t mid_mask; + size_t post_mask; + size_t mid_count = mi_bitmap_mask_across(bitmap_idx, bitmap_fields, count, &pre_mask, &mid_mask, &post_mask); + bool all_ones = true; + bool any_ones = false; + mi_bitmap_field_t* field = &bitmap[idx]; + size_t prev = mi_atomic_load_relaxed(field++); + if ((prev & pre_mask) != pre_mask) all_ones = false; + if ((prev & pre_mask) != 0) any_ones = true; + while (mid_count-- > 0) { + prev = mi_atomic_load_relaxed(field++); + if ((prev & mid_mask) != mid_mask) all_ones = false; + if ((prev & mid_mask) != 0) any_ones = true; + } + if (post_mask!=0) { + prev = mi_atomic_load_relaxed(field); + if ((prev & post_mask) != post_mask) all_ones = false; + if ((prev & post_mask) != 0) any_ones = true; + } + if (pany_ones != NULL) { *pany_ones = any_ones; } + return all_ones; +} + +bool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + return mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, NULL); +} + +bool _mi_bitmap_is_any_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx) { + bool any_ones; + mi_bitmap_is_claimedx_across(bitmap, bitmap_fields, count, bitmap_idx, &any_ones); + return any_ones; +} diff --git a/compat/mimalloc/bitmap.h b/compat/mimalloc/bitmap.h new file mode 100644 index 00000000000000..9ba15d5d6f09ea --- /dev/null +++ b/compat/mimalloc/bitmap.h @@ -0,0 +1,115 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019-2023 Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ---------------------------------------------------------------------------- +Concurrent bitmap that can set/reset sequences of bits atomically, +represeted as an array of fields where each field is a machine word (`size_t`) + +There are two api's; the standard one cannot have sequences that cross +between the bitmap fields (and a sequence must be <= MI_BITMAP_FIELD_BITS). +(this is used in region allocation) + +The `_across` postfixed functions do allow sequences that can cross over +between the fields. (This is used in arena allocation) +---------------------------------------------------------------------------- */ +#pragma once +#ifndef MI_BITMAP_H +#define MI_BITMAP_H + +/* ----------------------------------------------------------- + Bitmap definition +----------------------------------------------------------- */ + +#define MI_BITMAP_FIELD_BITS (8*MI_SIZE_SIZE) +#define MI_BITMAP_FIELD_FULL (~((size_t)0)) // all bits set + +// An atomic bitmap of `size_t` fields +typedef _Atomic(size_t) mi_bitmap_field_t; +typedef mi_bitmap_field_t* mi_bitmap_t; + +// A bitmap index is the index of the bit in a bitmap. +typedef size_t mi_bitmap_index_t; + +// Create a bit index. +static inline mi_bitmap_index_t mi_bitmap_index_create(size_t idx, size_t bitidx) { + mi_assert_internal(bitidx < MI_BITMAP_FIELD_BITS); + return (idx*MI_BITMAP_FIELD_BITS) + bitidx; +} + +// Create a bit index. +static inline mi_bitmap_index_t mi_bitmap_index_create_from_bit(size_t full_bitidx) { + return mi_bitmap_index_create(full_bitidx / MI_BITMAP_FIELD_BITS, full_bitidx % MI_BITMAP_FIELD_BITS); +} + +// Get the field index from a bit index. +static inline size_t mi_bitmap_index_field(mi_bitmap_index_t bitmap_idx) { + return (bitmap_idx / MI_BITMAP_FIELD_BITS); +} + +// Get the bit index in a bitmap field +static inline size_t mi_bitmap_index_bit_in_field(mi_bitmap_index_t bitmap_idx) { + return (bitmap_idx % MI_BITMAP_FIELD_BITS); +} + +// Get the full bit index +static inline size_t mi_bitmap_index_bit(mi_bitmap_index_t bitmap_idx) { + return bitmap_idx; +} + +/* ----------------------------------------------------------- + Claim a bit sequence atomically +----------------------------------------------------------- */ + +// Try to atomically claim a sequence of `count` bits in a single +// field at `idx` in `bitmap`. Returns `true` on success. +bool _mi_bitmap_try_find_claim_field(mi_bitmap_t bitmap, size_t idx, const size_t count, mi_bitmap_index_t* bitmap_idx); + +// Starts at idx, and wraps around to search in all `bitmap_fields` fields. +// For now, `count` can be at most MI_BITMAP_FIELD_BITS and will never cross fields. +bool _mi_bitmap_try_find_from_claim(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx); + +// Like _mi_bitmap_try_find_from_claim but with an extra predicate that must be fullfilled +typedef bool (mi_cdecl *mi_bitmap_pred_fun_t)(mi_bitmap_index_t bitmap_idx, void* pred_arg); +bool _mi_bitmap_try_find_from_claim_pred(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_pred_fun_t pred_fun, void* pred_arg, mi_bitmap_index_t* bitmap_idx); + +// Set `count` bits at `bitmap_idx` to 0 atomically +// Returns `true` if all `count` bits were 1 previously. +bool _mi_bitmap_unclaim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx); + +// Try to set `count` bits at `bitmap_idx` from 0 to 1 atomically. +// Returns `true` if successful when all previous `count` bits were 0. +bool _mi_bitmap_try_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx); + +// Set `count` bits at `bitmap_idx` to 1 atomically +// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit. +bool _mi_bitmap_claim(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* any_zero); + +bool _mi_bitmap_is_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx); +bool _mi_bitmap_is_any_claimed(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx); + + +//-------------------------------------------------------------------------- +// the `_across` functions work on bitmaps where sequences can cross over +// between the fields. This is used in arena allocation +//-------------------------------------------------------------------------- + +// Find `count` bits of zeros and set them to 1 atomically; returns `true` on success. +// Starts at idx, and wraps around to search in all `bitmap_fields` fields. +bool _mi_bitmap_try_find_from_claim_across(mi_bitmap_t bitmap, const size_t bitmap_fields, const size_t start_field_idx, const size_t count, mi_bitmap_index_t* bitmap_idx); + +// Set `count` bits at `bitmap_idx` to 0 atomically +// Returns `true` if all `count` bits were 1 previously. +bool _mi_bitmap_unclaim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx); + +// Set `count` bits at `bitmap_idx` to 1 atomically +// Returns `true` if all `count` bits were 0 previously. `any_zero` is `true` if there was at least one zero bit. +bool _mi_bitmap_claim_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx, bool* pany_zero); + +bool _mi_bitmap_is_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx); +bool _mi_bitmap_is_any_claimed_across(mi_bitmap_t bitmap, size_t bitmap_fields, size_t count, mi_bitmap_index_t bitmap_idx); + +#endif diff --git a/compat/mimalloc/heap.c b/compat/mimalloc/heap.c new file mode 100644 index 00000000000000..dab8c4bf8ae388 --- /dev/null +++ b/compat/mimalloc/heap.c @@ -0,0 +1,626 @@ +/*---------------------------------------------------------------------------- +Copyright (c) 2018-2021, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" +#include "mimalloc/prim.h" // mi_prim_get_default_heap + +#include // memset, memcpy + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#pragma warning(disable:4204) // non-constant aggregate initializer +#endif + +/* ----------------------------------------------------------- + Helpers +----------------------------------------------------------- */ + +// return `true` if ok, `false` to break +typedef bool (heap_page_visitor_fun)(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2); + +// Visit all pages in a heap; returns `false` if break was called. +static bool mi_heap_visit_pages(mi_heap_t* heap, heap_page_visitor_fun* fn, void* arg1, void* arg2) +{ + if (heap==NULL || heap->page_count==0) return 0; + + // visit all pages + #if MI_DEBUG>1 + size_t total = heap->page_count; + size_t count = 0; + #endif + + for (size_t i = 0; i <= MI_BIN_FULL; i++) { + mi_page_queue_t* pq = &heap->pages[i]; + mi_page_t* page = pq->first; + while(page != NULL) { + mi_page_t* next = page->next; // save next in case the page gets removed from the queue + mi_assert_internal(mi_page_heap(page) == heap); + #if MI_DEBUG>1 + count++; + #endif + if (!fn(heap, pq, page, arg1, arg2)) return false; + page = next; // and continue + } + } + mi_assert_internal(count == total); + return true; +} + + +#if MI_DEBUG>=2 +static bool mi_heap_page_is_valid(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) { + MI_UNUSED(arg1); + MI_UNUSED(arg2); + MI_UNUSED(pq); + mi_assert_internal(mi_page_heap(page) == heap); + mi_segment_t* segment = _mi_page_segment(page); + mi_assert_internal(segment->thread_id == heap->thread_id); + mi_assert_expensive(_mi_page_is_valid(page)); + return true; +} +#endif +#if MI_DEBUG>=3 +static bool mi_heap_is_valid(mi_heap_t* heap) { + mi_assert_internal(heap!=NULL); + mi_heap_visit_pages(heap, &mi_heap_page_is_valid, NULL, NULL); + return true; +} +#endif + + + + +/* ----------------------------------------------------------- + "Collect" pages by migrating `local_free` and `thread_free` + lists and freeing empty pages. This is done when a thread + stops (and in that case abandons pages if there are still + blocks alive) +----------------------------------------------------------- */ + +typedef enum mi_collect_e { + MI_NORMAL, + MI_FORCE, + MI_ABANDON +} mi_collect_t; + + +static bool mi_heap_page_collect(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg_collect, void* arg2 ) { + MI_UNUSED(arg2); + MI_UNUSED(heap); + mi_assert_internal(mi_heap_page_is_valid(heap, pq, page, NULL, NULL)); + mi_collect_t collect = *((mi_collect_t*)arg_collect); + _mi_page_free_collect(page, collect >= MI_FORCE); + if (mi_page_all_free(page)) { + // no more used blocks, free the page. + // note: this will free retired pages as well. + _mi_page_free(page, pq, collect >= MI_FORCE); + } + else if (collect == MI_ABANDON) { + // still used blocks but the thread is done; abandon the page + _mi_page_abandon(page, pq); + } + return true; // don't break +} + +static bool mi_heap_page_never_delayed_free(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) { + MI_UNUSED(arg1); + MI_UNUSED(arg2); + MI_UNUSED(heap); + MI_UNUSED(pq); + _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false); + return true; // don't break +} + +static void mi_heap_collect_ex(mi_heap_t* heap, mi_collect_t collect) +{ + if (heap==NULL || !mi_heap_is_initialized(heap)) return; + + const bool force = collect >= MI_FORCE; + _mi_deferred_free(heap, force); + + // note: never reclaim on collect but leave it to threads that need storage to reclaim + const bool force_main = + #ifdef NDEBUG + collect == MI_FORCE + #else + collect >= MI_FORCE + #endif + && _mi_is_main_thread() && mi_heap_is_backing(heap) && !heap->no_reclaim; + + if (force_main) { + // the main thread is abandoned (end-of-program), try to reclaim all abandoned segments. + // if all memory is freed by now, all segments should be freed. + _mi_abandoned_reclaim_all(heap, &heap->tld->segments); + } + + // if abandoning, mark all pages to no longer add to delayed_free + if (collect == MI_ABANDON) { + mi_heap_visit_pages(heap, &mi_heap_page_never_delayed_free, NULL, NULL); + } + + // free all current thread delayed blocks. + // (if abandoning, after this there are no more thread-delayed references into the pages.) + _mi_heap_delayed_free_all(heap); + + // collect retired pages + _mi_heap_collect_retired(heap, force); + + // collect all pages owned by this thread + mi_heap_visit_pages(heap, &mi_heap_page_collect, &collect, NULL); + mi_assert_internal( collect != MI_ABANDON || mi_atomic_load_ptr_acquire(mi_block_t,&heap->thread_delayed_free) == NULL ); + + // collect abandoned segments (in particular, purge expired parts of segments in the abandoned segment list) + // note: forced purge can be quite expensive if many threads are created/destroyed so we do not force on abandonment + _mi_abandoned_collect(heap, collect == MI_FORCE /* force? */, &heap->tld->segments); + + // collect segment local caches + if (force) { + _mi_segment_thread_collect(&heap->tld->segments); + } + + // collect regions on program-exit (or shared library unload) + if (force && _mi_is_main_thread() && mi_heap_is_backing(heap)) { + _mi_thread_data_collect(); // collect thread data cache + _mi_arena_collect(true /* force purge */, &heap->tld->stats); + } +} + +void _mi_heap_collect_abandon(mi_heap_t* heap) { + mi_heap_collect_ex(heap, MI_ABANDON); +} + +void mi_heap_collect(mi_heap_t* heap, bool force) mi_attr_noexcept { + mi_heap_collect_ex(heap, (force ? MI_FORCE : MI_NORMAL)); +} + +void mi_collect(bool force) mi_attr_noexcept { + mi_heap_collect(mi_prim_get_default_heap(), force); +} + + +/* ----------------------------------------------------------- + Heap new +----------------------------------------------------------- */ + +mi_heap_t* mi_heap_get_default(void) { + mi_thread_init(); + return mi_prim_get_default_heap(); +} + +static bool mi_heap_is_default(const mi_heap_t* heap) { + return (heap == mi_prim_get_default_heap()); +} + + +mi_heap_t* mi_heap_get_backing(void) { + mi_heap_t* heap = mi_heap_get_default(); + mi_assert_internal(heap!=NULL); + mi_heap_t* bheap = heap->tld->heap_backing; + mi_assert_internal(bheap!=NULL); + mi_assert_internal(bheap->thread_id == _mi_thread_id()); + return bheap; +} + +mi_decl_nodiscard mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id) { + mi_heap_t* bheap = mi_heap_get_backing(); + mi_heap_t* heap = mi_heap_malloc_tp(bheap, mi_heap_t); // todo: OS allocate in secure mode? + if (heap == NULL) return NULL; + _mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(mi_heap_t)); + heap->tld = bheap->tld; + heap->thread_id = _mi_thread_id(); + heap->arena_id = arena_id; + _mi_random_split(&bheap->random, &heap->random); + heap->cookie = _mi_heap_random_next(heap) | 1; + heap->keys[0] = _mi_heap_random_next(heap); + heap->keys[1] = _mi_heap_random_next(heap); + heap->no_reclaim = true; // don't reclaim abandoned pages or otherwise destroy is unsafe + // push on the thread local heaps list + heap->next = heap->tld->heaps; + heap->tld->heaps = heap; + return heap; +} + +mi_decl_nodiscard mi_heap_t* mi_heap_new(void) { + return mi_heap_new_in_arena(_mi_arena_id_none()); +} + +bool _mi_heap_memid_is_suitable(mi_heap_t* heap, mi_memid_t memid) { + return _mi_arena_memid_is_suitable(memid, heap->arena_id); +} + +uintptr_t _mi_heap_random_next(mi_heap_t* heap) { + return _mi_random_next(&heap->random); +} + +// zero out the page queues +static void mi_heap_reset_pages(mi_heap_t* heap) { + mi_assert_internal(heap != NULL); + mi_assert_internal(mi_heap_is_initialized(heap)); + // TODO: copy full empty heap instead? + memset(&heap->pages_free_direct, 0, sizeof(heap->pages_free_direct)); + _mi_memcpy_aligned(&heap->pages, &_mi_heap_empty.pages, sizeof(heap->pages)); + heap->thread_delayed_free = NULL; + heap->page_count = 0; +} + +// called from `mi_heap_destroy` and `mi_heap_delete` to free the internal heap resources. +static void mi_heap_free(mi_heap_t* heap) { + mi_assert(heap != NULL); + mi_assert_internal(mi_heap_is_initialized(heap)); + if (heap==NULL || !mi_heap_is_initialized(heap)) return; + if (mi_heap_is_backing(heap)) return; // dont free the backing heap + + // reset default + if (mi_heap_is_default(heap)) { + _mi_heap_set_default_direct(heap->tld->heap_backing); + } + + // remove ourselves from the thread local heaps list + // linear search but we expect the number of heaps to be relatively small + mi_heap_t* prev = NULL; + mi_heap_t* curr = heap->tld->heaps; + while (curr != heap && curr != NULL) { + prev = curr; + curr = curr->next; + } + mi_assert_internal(curr == heap); + if (curr == heap) { + if (prev != NULL) { prev->next = heap->next; } + else { heap->tld->heaps = heap->next; } + } + mi_assert_internal(heap->tld->heaps != NULL); + + // and free the used memory + mi_free(heap); +} + + +/* ----------------------------------------------------------- + Heap destroy +----------------------------------------------------------- */ + +static bool _mi_heap_page_destroy(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* arg1, void* arg2) { + MI_UNUSED(arg1); + MI_UNUSED(arg2); + MI_UNUSED(heap); + MI_UNUSED(pq); + + // ensure no more thread_delayed_free will be added + _mi_page_use_delayed_free(page, MI_NEVER_DELAYED_FREE, false); + + // stats + const size_t bsize = mi_page_block_size(page); + if (bsize > MI_MEDIUM_OBJ_SIZE_MAX) { + if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { + mi_heap_stat_decrease(heap, large, bsize); + } + else { + mi_heap_stat_decrease(heap, huge, bsize); + } + } +#if (MI_STAT) + _mi_page_free_collect(page, false); // update used count + const size_t inuse = page->used; + if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { + mi_heap_stat_decrease(heap, normal, bsize * inuse); +#if (MI_STAT>1) + mi_heap_stat_decrease(heap, normal_bins[_mi_bin(bsize)], inuse); +#endif + } + mi_heap_stat_decrease(heap, malloc, bsize * inuse); // todo: off for aligned blocks... +#endif + + /// pretend it is all free now + mi_assert_internal(mi_page_thread_free(page) == NULL); + page->used = 0; + + // and free the page + // mi_page_free(page,false); + page->next = NULL; + page->prev = NULL; + _mi_segment_page_free(page,false /* no force? */, &heap->tld->segments); + + return true; // keep going +} + +void _mi_heap_destroy_pages(mi_heap_t* heap) { + mi_heap_visit_pages(heap, &_mi_heap_page_destroy, NULL, NULL); + mi_heap_reset_pages(heap); +} + +#if MI_TRACK_HEAP_DESTROY +static bool mi_cdecl mi_heap_track_block_free(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg) { + MI_UNUSED(heap); MI_UNUSED(area); MI_UNUSED(arg); MI_UNUSED(block_size); + mi_track_free_size(block,mi_usable_size(block)); + return true; +} +#endif + +void mi_heap_destroy(mi_heap_t* heap) { + mi_assert(heap != NULL); + mi_assert(mi_heap_is_initialized(heap)); + mi_assert(heap->no_reclaim); + mi_assert_expensive(mi_heap_is_valid(heap)); + if (heap==NULL || !mi_heap_is_initialized(heap)) return; + if (!heap->no_reclaim) { + // don't free in case it may contain reclaimed pages + mi_heap_delete(heap); + } + else { + // track all blocks as freed + #if MI_TRACK_HEAP_DESTROY + mi_heap_visit_blocks(heap, true, mi_heap_track_block_free, NULL); + #endif + // free all pages + _mi_heap_destroy_pages(heap); + mi_heap_free(heap); + } +} + +// forcefully destroy all heaps in the current thread +void _mi_heap_unsafe_destroy_all(void) { + mi_heap_t* bheap = mi_heap_get_backing(); + mi_heap_t* curr = bheap->tld->heaps; + while (curr != NULL) { + mi_heap_t* next = curr->next; + if (curr->no_reclaim) { + mi_heap_destroy(curr); + } + else { + _mi_heap_destroy_pages(curr); + } + curr = next; + } +} + +/* ----------------------------------------------------------- + Safe Heap delete +----------------------------------------------------------- */ + +// Transfer the pages from one heap to the other +static void mi_heap_absorb(mi_heap_t* heap, mi_heap_t* from) { + mi_assert_internal(heap!=NULL); + if (from==NULL || from->page_count == 0) return; + + // reduce the size of the delayed frees + _mi_heap_delayed_free_partial(from); + + // transfer all pages by appending the queues; this will set a new heap field + // so threads may do delayed frees in either heap for a while. + // note: appending waits for each page to not be in the `MI_DELAYED_FREEING` state + // so after this only the new heap will get delayed frees + for (size_t i = 0; i <= MI_BIN_FULL; i++) { + mi_page_queue_t* pq = &heap->pages[i]; + mi_page_queue_t* append = &from->pages[i]; + size_t pcount = _mi_page_queue_append(heap, pq, append); + heap->page_count += pcount; + from->page_count -= pcount; + } + mi_assert_internal(from->page_count == 0); + + // and do outstanding delayed frees in the `from` heap + // note: be careful here as the `heap` field in all those pages no longer point to `from`, + // turns out to be ok as `_mi_heap_delayed_free` only visits the list and calls a + // the regular `_mi_free_delayed_block` which is safe. + _mi_heap_delayed_free_all(from); + #if !defined(_MSC_VER) || (_MSC_VER > 1900) // somehow the following line gives an error in VS2015, issue #353 + mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_block_t,&from->thread_delayed_free) == NULL); + #endif + + // and reset the `from` heap + mi_heap_reset_pages(from); +} + +// Safe delete a heap without freeing any still allocated blocks in that heap. +void mi_heap_delete(mi_heap_t* heap) +{ + mi_assert(heap != NULL); + mi_assert(mi_heap_is_initialized(heap)); + mi_assert_expensive(mi_heap_is_valid(heap)); + if (heap==NULL || !mi_heap_is_initialized(heap)) return; + + if (!mi_heap_is_backing(heap)) { + // tranfer still used pages to the backing heap + mi_heap_absorb(heap->tld->heap_backing, heap); + } + else { + // the backing heap abandons its pages + _mi_heap_collect_abandon(heap); + } + mi_assert_internal(heap->page_count==0); + mi_heap_free(heap); +} + +mi_heap_t* mi_heap_set_default(mi_heap_t* heap) { + mi_assert(heap != NULL); + mi_assert(mi_heap_is_initialized(heap)); + if (heap==NULL || !mi_heap_is_initialized(heap)) return NULL; + mi_assert_expensive(mi_heap_is_valid(heap)); + mi_heap_t* old = mi_prim_get_default_heap(); + _mi_heap_set_default_direct(heap); + return old; +} + + + + +/* ----------------------------------------------------------- + Analysis +----------------------------------------------------------- */ + +// static since it is not thread safe to access heaps from other threads. +static mi_heap_t* mi_heap_of_block(const void* p) { + if (p == NULL) return NULL; + mi_segment_t* segment = _mi_ptr_segment(p); + bool valid = (_mi_ptr_cookie(segment) == segment->cookie); + mi_assert_internal(valid); + if mi_unlikely(!valid) return NULL; + return mi_page_heap(_mi_segment_page_of(segment,p)); +} + +bool mi_heap_contains_block(mi_heap_t* heap, const void* p) { + mi_assert(heap != NULL); + if (heap==NULL || !mi_heap_is_initialized(heap)) return false; + return (heap == mi_heap_of_block(p)); +} + + +static bool mi_heap_page_check_owned(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* p, void* vfound) { + MI_UNUSED(heap); + MI_UNUSED(pq); + bool* found = (bool*)vfound; + mi_segment_t* segment = _mi_page_segment(page); + void* start = _mi_page_start(segment, page, NULL); + void* end = (uint8_t*)start + (page->capacity * mi_page_block_size(page)); + *found = (p >= start && p < end); + return (!*found); // continue if not found +} + +bool mi_heap_check_owned(mi_heap_t* heap, const void* p) { + mi_assert(heap != NULL); + if (heap==NULL || !mi_heap_is_initialized(heap)) return false; + if (((uintptr_t)p & (MI_INTPTR_SIZE - 1)) != 0) return false; // only aligned pointers + bool found = false; + mi_heap_visit_pages(heap, &mi_heap_page_check_owned, (void*)p, &found); + return found; +} + +bool mi_check_owned(const void* p) { + return mi_heap_check_owned(mi_prim_get_default_heap(), p); +} + +/* ----------------------------------------------------------- + Visit all heap blocks and areas + Todo: enable visiting abandoned pages, and + enable visiting all blocks of all heaps across threads +----------------------------------------------------------- */ + +// Separate struct to keep `mi_page_t` out of the public interface +typedef struct mi_heap_area_ex_s { + mi_heap_area_t area; + mi_page_t* page; +} mi_heap_area_ex_t; + +static bool mi_heap_area_visit_blocks(const mi_heap_area_ex_t* xarea, mi_block_visit_fun* visitor, void* arg) { + mi_assert(xarea != NULL); + if (xarea==NULL) return true; + const mi_heap_area_t* area = &xarea->area; + mi_page_t* page = xarea->page; + mi_assert(page != NULL); + if (page == NULL) return true; + + _mi_page_free_collect(page,true); + mi_assert_internal(page->local_free == NULL); + if (page->used == 0) return true; + + const size_t bsize = mi_page_block_size(page); + const size_t ubsize = mi_page_usable_block_size(page); // without padding + size_t psize; + uint8_t* pstart = _mi_page_start(_mi_page_segment(page), page, &psize); + + if (page->capacity == 1) { + // optimize page with one block + mi_assert_internal(page->used == 1 && page->free == NULL); + return visitor(mi_page_heap(page), area, pstart, ubsize, arg); + } + + // create a bitmap of free blocks. + #define MI_MAX_BLOCKS (MI_SMALL_PAGE_SIZE / sizeof(void*)) + uintptr_t free_map[MI_MAX_BLOCKS / sizeof(uintptr_t)]; + memset(free_map, 0, sizeof(free_map)); + + #if MI_DEBUG>1 + size_t free_count = 0; + #endif + for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) { + #if MI_DEBUG>1 + free_count++; + #endif + mi_assert_internal((uint8_t*)block >= pstart && (uint8_t*)block < (pstart + psize)); + size_t offset = (uint8_t*)block - pstart; + mi_assert_internal(offset % bsize == 0); + size_t blockidx = offset / bsize; // Todo: avoid division? + mi_assert_internal( blockidx < MI_MAX_BLOCKS); + size_t bitidx = (blockidx / sizeof(uintptr_t)); + size_t bit = blockidx - (bitidx * sizeof(uintptr_t)); + free_map[bitidx] |= ((uintptr_t)1 << bit); + } + mi_assert_internal(page->capacity == (free_count + page->used)); + + // walk through all blocks skipping the free ones + #if MI_DEBUG>1 + size_t used_count = 0; + #endif + for (size_t i = 0; i < page->capacity; i++) { + size_t bitidx = (i / sizeof(uintptr_t)); + size_t bit = i - (bitidx * sizeof(uintptr_t)); + uintptr_t m = free_map[bitidx]; + if (bit == 0 && m == UINTPTR_MAX) { + i += (sizeof(uintptr_t) - 1); // skip a run of free blocks + } + else if ((m & ((uintptr_t)1 << bit)) == 0) { + #if MI_DEBUG>1 + used_count++; + #endif + uint8_t* block = pstart + (i * bsize); + if (!visitor(mi_page_heap(page), area, block, ubsize, arg)) return false; + } + } + mi_assert_internal(page->used == used_count); + return true; +} + +typedef bool (mi_heap_area_visit_fun)(const mi_heap_t* heap, const mi_heap_area_ex_t* area, void* arg); + + +static bool mi_heap_visit_areas_page(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_t* page, void* vfun, void* arg) { + MI_UNUSED(heap); + MI_UNUSED(pq); + mi_heap_area_visit_fun* fun = (mi_heap_area_visit_fun*)vfun; + mi_heap_area_ex_t xarea; + const size_t bsize = mi_page_block_size(page); + const size_t ubsize = mi_page_usable_block_size(page); + xarea.page = page; + xarea.area.reserved = page->reserved * bsize; + xarea.area.committed = page->capacity * bsize; + xarea.area.blocks = _mi_page_start(_mi_page_segment(page), page, NULL); + xarea.area.used = page->used; // number of blocks in use (#553) + xarea.area.block_size = ubsize; + xarea.area.full_block_size = bsize; + return fun(heap, &xarea, arg); +} + +// Visit all heap pages as areas +static bool mi_heap_visit_areas(const mi_heap_t* heap, mi_heap_area_visit_fun* visitor, void* arg) { + if (visitor == NULL) return false; + return mi_heap_visit_pages((mi_heap_t*)heap, &mi_heap_visit_areas_page, (void*)(visitor), arg); // note: function pointer to void* :-{ +} + +// Just to pass arguments +typedef struct mi_visit_blocks_args_s { + bool visit_blocks; + mi_block_visit_fun* visitor; + void* arg; +} mi_visit_blocks_args_t; + +static bool mi_heap_area_visitor(const mi_heap_t* heap, const mi_heap_area_ex_t* xarea, void* arg) { + mi_visit_blocks_args_t* args = (mi_visit_blocks_args_t*)arg; + if (!args->visitor(heap, &xarea->area, NULL, xarea->area.block_size, args->arg)) return false; + if (args->visit_blocks) { + return mi_heap_area_visit_blocks(xarea, args->visitor, args->arg); + } + else { + return true; + } +} + +// Visit all blocks in a heap +bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg) { + mi_visit_blocks_args_t args = { visit_blocks, visitor, arg }; + return mi_heap_visit_areas(heap, &mi_heap_area_visitor, &args); +} diff --git a/compat/mimalloc/init.c b/compat/mimalloc/init.c new file mode 100644 index 00000000000000..4670d5510db187 --- /dev/null +++ b/compat/mimalloc/init.c @@ -0,0 +1,709 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2022, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/prim.h" + +#include // memcpy, memset +#include // atexit + + +// Empty page used to initialize the small free pages array +const mi_page_t _mi_page_empty = { + 0, false, false, false, + 0, // capacity + 0, // reserved capacity + { 0 }, // flags + false, // is_zero + 0, // retire_expire + NULL, // free + 0, // used + 0, // xblock_size + NULL, // local_free + #if (MI_PADDING || MI_ENCODE_FREELIST) + { 0, 0 }, + #endif + MI_ATOMIC_VAR_INIT(0), // xthread_free + MI_ATOMIC_VAR_INIT(0), // xheap + NULL, NULL + #if MI_INTPTR_SIZE==8 + , { 0 } // padding + #endif +}; + +#define MI_PAGE_EMPTY() ((mi_page_t*)&_mi_page_empty) + +#if (MI_SMALL_WSIZE_MAX==128) +#if (MI_PADDING>0) && (MI_INTPTR_SIZE >= 8) +#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() } +#elif (MI_PADDING>0) +#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY(), MI_PAGE_EMPTY(), MI_PAGE_EMPTY() } +#else +#define MI_SMALL_PAGES_EMPTY { MI_INIT128(MI_PAGE_EMPTY), MI_PAGE_EMPTY() } +#endif +#else +#error "define right initialization sizes corresponding to MI_SMALL_WSIZE_MAX" +#endif + +// Empty page queues for every bin +#define QNULL(sz) { NULL, NULL, (sz)*sizeof(uintptr_t) } +#define MI_PAGE_QUEUES_EMPTY \ + { QNULL(1), \ + QNULL( 1), QNULL( 2), QNULL( 3), QNULL( 4), QNULL( 5), QNULL( 6), QNULL( 7), QNULL( 8), /* 8 */ \ + QNULL( 10), QNULL( 12), QNULL( 14), QNULL( 16), QNULL( 20), QNULL( 24), QNULL( 28), QNULL( 32), /* 16 */ \ + QNULL( 40), QNULL( 48), QNULL( 56), QNULL( 64), QNULL( 80), QNULL( 96), QNULL( 112), QNULL( 128), /* 24 */ \ + QNULL( 160), QNULL( 192), QNULL( 224), QNULL( 256), QNULL( 320), QNULL( 384), QNULL( 448), QNULL( 512), /* 32 */ \ + QNULL( 640), QNULL( 768), QNULL( 896), QNULL( 1024), QNULL( 1280), QNULL( 1536), QNULL( 1792), QNULL( 2048), /* 40 */ \ + QNULL( 2560), QNULL( 3072), QNULL( 3584), QNULL( 4096), QNULL( 5120), QNULL( 6144), QNULL( 7168), QNULL( 8192), /* 48 */ \ + QNULL( 10240), QNULL( 12288), QNULL( 14336), QNULL( 16384), QNULL( 20480), QNULL( 24576), QNULL( 28672), QNULL( 32768), /* 56 */ \ + QNULL( 40960), QNULL( 49152), QNULL( 57344), QNULL( 65536), QNULL( 81920), QNULL( 98304), QNULL(114688), QNULL(131072), /* 64 */ \ + QNULL(163840), QNULL(196608), QNULL(229376), QNULL(262144), QNULL(327680), QNULL(393216), QNULL(458752), QNULL(524288), /* 72 */ \ + QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 1 /* 655360, Huge queue */), \ + QNULL(MI_MEDIUM_OBJ_WSIZE_MAX + 2) /* Full queue */ } + +#define MI_STAT_COUNT_NULL() {0,0,0,0} + +// Empty statistics +#if MI_STAT>1 +#define MI_STAT_COUNT_END_NULL() , { MI_STAT_COUNT_NULL(), MI_INIT32(MI_STAT_COUNT_NULL) } +#else +#define MI_STAT_COUNT_END_NULL() +#endif + +#define MI_STATS_NULL \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), MI_STAT_COUNT_NULL(), \ + MI_STAT_COUNT_NULL(), \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } \ + MI_STAT_COUNT_END_NULL() + + +// Empty slice span queues for every bin +#define SQNULL(sz) { NULL, NULL, sz } +#define MI_SEGMENT_SPAN_QUEUES_EMPTY \ + { SQNULL(1), \ + SQNULL( 1), SQNULL( 2), SQNULL( 3), SQNULL( 4), SQNULL( 5), SQNULL( 6), SQNULL( 7), SQNULL( 10), /* 8 */ \ + SQNULL( 12), SQNULL( 14), SQNULL( 16), SQNULL( 20), SQNULL( 24), SQNULL( 28), SQNULL( 32), SQNULL( 40), /* 16 */ \ + SQNULL( 48), SQNULL( 56), SQNULL( 64), SQNULL( 80), SQNULL( 96), SQNULL( 112), SQNULL( 128), SQNULL( 160), /* 24 */ \ + SQNULL( 192), SQNULL( 224), SQNULL( 256), SQNULL( 320), SQNULL( 384), SQNULL( 448), SQNULL( 512), SQNULL( 640), /* 32 */ \ + SQNULL( 768), SQNULL( 896), SQNULL( 1024) /* 35 */ } + + +// -------------------------------------------------------- +// Statically allocate an empty heap as the initial +// thread local value for the default heap, +// and statically allocate the backing heap for the main +// thread so it can function without doing any allocation +// itself (as accessing a thread local for the first time +// may lead to allocation itself on some platforms) +// -------------------------------------------------------- + +mi_decl_cache_align const mi_heap_t _mi_heap_empty = { + NULL, + MI_SMALL_PAGES_EMPTY, + MI_PAGE_QUEUES_EMPTY, + MI_ATOMIC_VAR_INIT(NULL), + 0, // tid + 0, // cookie + 0, // arena id + { 0, 0 }, // keys + { {0}, {0}, 0, true }, // random + 0, // page count + MI_BIN_FULL, 0, // page retired min/max + NULL, // next + false +}; + +#define tld_empty_stats ((mi_stats_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,stats))) +#define tld_empty_os ((mi_os_tld_t*)((uint8_t*)&tld_empty + offsetof(mi_tld_t,os))) + +mi_decl_cache_align static const mi_tld_t tld_empty = { + 0, + false, + NULL, NULL, + { MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, tld_empty_stats, tld_empty_os }, // segments + { 0, tld_empty_stats }, // os + { MI_STATS_NULL } // stats +}; + +mi_threadid_t _mi_thread_id(void) mi_attr_noexcept { + return _mi_prim_thread_id(); +} + +// the thread-local default heap for allocation +mi_decl_thread mi_heap_t* _mi_heap_default = (mi_heap_t*)&_mi_heap_empty; + +extern mi_heap_t _mi_heap_main; + +static mi_tld_t tld_main = { + 0, false, + &_mi_heap_main, & _mi_heap_main, + { MI_SEGMENT_SPAN_QUEUES_EMPTY, 0, 0, 0, 0, &tld_main.stats, &tld_main.os }, // segments + { 0, &tld_main.stats }, // os + { MI_STATS_NULL } // stats +}; + +mi_heap_t _mi_heap_main = { + &tld_main, + MI_SMALL_PAGES_EMPTY, + MI_PAGE_QUEUES_EMPTY, + MI_ATOMIC_VAR_INIT(NULL), + 0, // thread id + 0, // initial cookie + 0, // arena id + { 0, 0 }, // the key of the main heap can be fixed (unlike page keys that need to be secure!) + { {0x846ca68b}, {0}, 0, true }, // random + 0, // page count + MI_BIN_FULL, 0, // page retired min/max + NULL, // next heap + false // can reclaim +}; + +bool _mi_process_is_initialized = false; // set to `true` in `mi_process_init`. + +mi_stats_t _mi_stats_main = { MI_STATS_NULL }; + + +static void mi_heap_main_init(void) { + if (_mi_heap_main.cookie == 0) { + _mi_heap_main.thread_id = _mi_thread_id(); + _mi_heap_main.cookie = 1; + #if defined(_WIN32) && !defined(MI_SHARED_LIB) + _mi_random_init_weak(&_mi_heap_main.random); // prevent allocation failure during bcrypt dll initialization with static linking + #else + _mi_random_init(&_mi_heap_main.random); + #endif + _mi_heap_main.cookie = _mi_heap_random_next(&_mi_heap_main); + _mi_heap_main.keys[0] = _mi_heap_random_next(&_mi_heap_main); + _mi_heap_main.keys[1] = _mi_heap_random_next(&_mi_heap_main); + } +} + +mi_heap_t* _mi_heap_main_get(void) { + mi_heap_main_init(); + return &_mi_heap_main; +} + + +/* ----------------------------------------------------------- + Initialization and freeing of the thread local heaps +----------------------------------------------------------- */ + +// note: in x64 in release build `sizeof(mi_thread_data_t)` is under 4KiB (= OS page size). +typedef struct mi_thread_data_s { + mi_heap_t heap; // must come first due to cast in `_mi_heap_done` + mi_tld_t tld; + mi_memid_t memid; +} mi_thread_data_t; + + +// Thread meta-data is allocated directly from the OS. For +// some programs that do not use thread pools and allocate and +// destroy many OS threads, this may causes too much overhead +// per thread so we maintain a small cache of recently freed metadata. + +#define TD_CACHE_SIZE (16) +static _Atomic(mi_thread_data_t*) td_cache[TD_CACHE_SIZE]; + +static mi_thread_data_t* mi_thread_data_zalloc(void) { + // try to find thread metadata in the cache + bool is_zero = false; + mi_thread_data_t* td = NULL; + for (int i = 0; i < TD_CACHE_SIZE; i++) { + td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]); + if (td != NULL) { + // found cached allocation, try use it + td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL); + if (td != NULL) { + break; + } + } + } + + // if that fails, allocate as meta data + if (td == NULL) { + mi_memid_t memid; + td = (mi_thread_data_t*)_mi_os_alloc(sizeof(mi_thread_data_t), &memid, &_mi_stats_main); + if (td == NULL) { + // if this fails, try once more. (issue #257) + td = (mi_thread_data_t*)_mi_os_alloc(sizeof(mi_thread_data_t), &memid, &_mi_stats_main); + if (td == NULL) { + // really out of memory + _mi_error_message(ENOMEM, "unable to allocate thread local heap metadata (%zu bytes)\n", sizeof(mi_thread_data_t)); + } + } + if (td != NULL) { + td->memid = memid; + is_zero = memid.initially_zero; + } + } + + if (td != NULL && !is_zero) { + _mi_memzero_aligned(td, sizeof(*td)); + } + return td; +} + +static void mi_thread_data_free( mi_thread_data_t* tdfree ) { + // try to add the thread metadata to the cache + for (int i = 0; i < TD_CACHE_SIZE; i++) { + mi_thread_data_t* td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]); + if (td == NULL) { + mi_thread_data_t* expected = NULL; + if (mi_atomic_cas_ptr_weak_acq_rel(mi_thread_data_t, &td_cache[i], &expected, tdfree)) { + return; + } + } + } + // if that fails, just free it directly + _mi_os_free(tdfree, sizeof(mi_thread_data_t), tdfree->memid, &_mi_stats_main); +} + +void _mi_thread_data_collect(void) { + // free all thread metadata from the cache + for (int i = 0; i < TD_CACHE_SIZE; i++) { + mi_thread_data_t* td = mi_atomic_load_ptr_relaxed(mi_thread_data_t, &td_cache[i]); + if (td != NULL) { + td = mi_atomic_exchange_ptr_acq_rel(mi_thread_data_t, &td_cache[i], NULL); + if (td != NULL) { + _mi_os_free(td, sizeof(mi_thread_data_t), td->memid, &_mi_stats_main); + } + } + } +} + +// Initialize the thread local default heap, called from `mi_thread_init` +static bool _mi_heap_init(void) { + if (mi_heap_is_initialized(mi_prim_get_default_heap())) return true; + if (_mi_is_main_thread()) { + // mi_assert_internal(_mi_heap_main.thread_id != 0); // can happen on freeBSD where alloc is called before any initialization + // the main heap is statically allocated + mi_heap_main_init(); + _mi_heap_set_default_direct(&_mi_heap_main); + //mi_assert_internal(_mi_heap_default->tld->heap_backing == mi_prim_get_default_heap()); + } + else { + // use `_mi_os_alloc` to allocate directly from the OS + mi_thread_data_t* td = mi_thread_data_zalloc(); + if (td == NULL) return false; + + mi_tld_t* tld = &td->tld; + mi_heap_t* heap = &td->heap; + _mi_memcpy_aligned(tld, &tld_empty, sizeof(*tld)); + _mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(*heap)); + heap->thread_id = _mi_thread_id(); + _mi_random_init(&heap->random); + heap->cookie = _mi_heap_random_next(heap) | 1; + heap->keys[0] = _mi_heap_random_next(heap); + heap->keys[1] = _mi_heap_random_next(heap); + heap->tld = tld; + tld->heap_backing = heap; + tld->heaps = heap; + tld->segments.stats = &tld->stats; + tld->segments.os = &tld->os; + tld->os.stats = &tld->stats; + _mi_heap_set_default_direct(heap); + } + return false; +} + +// Free the thread local default heap (called from `mi_thread_done`) +static bool _mi_heap_done(mi_heap_t* heap) { + if (!mi_heap_is_initialized(heap)) return true; + + // reset default heap + _mi_heap_set_default_direct(_mi_is_main_thread() ? &_mi_heap_main : (mi_heap_t*)&_mi_heap_empty); + + // switch to backing heap + heap = heap->tld->heap_backing; + if (!mi_heap_is_initialized(heap)) return false; + + // delete all non-backing heaps in this thread + mi_heap_t* curr = heap->tld->heaps; + while (curr != NULL) { + mi_heap_t* next = curr->next; // save `next` as `curr` will be freed + if (curr != heap) { + mi_assert_internal(!mi_heap_is_backing(curr)); + mi_heap_delete(curr); + } + curr = next; + } + mi_assert_internal(heap->tld->heaps == heap && heap->next == NULL); + mi_assert_internal(mi_heap_is_backing(heap)); + + // collect if not the main thread + if (heap != &_mi_heap_main) { + _mi_heap_collect_abandon(heap); + } + + // merge stats + _mi_stats_done(&heap->tld->stats); + + // free if not the main thread + if (heap != &_mi_heap_main) { + // the following assertion does not always hold for huge segments as those are always treated + // as abondened: one may allocate it in one thread, but deallocate in another in which case + // the count can be too large or negative. todo: perhaps not count huge segments? see issue #363 + // mi_assert_internal(heap->tld->segments.count == 0 || heap->thread_id != _mi_thread_id()); + mi_thread_data_free((mi_thread_data_t*)heap); + } + else { + #if 0 + // never free the main thread even in debug mode; if a dll is linked statically with mimalloc, + // there may still be delete/free calls after the mi_fls_done is called. Issue #207 + _mi_heap_destroy_pages(heap); + mi_assert_internal(heap->tld->heap_backing == &_mi_heap_main); + #endif + } + return false; +} + + + +// -------------------------------------------------------- +// Try to run `mi_thread_done()` automatically so any memory +// owned by the thread but not yet released can be abandoned +// and re-owned by another thread. +// +// 1. windows dynamic library: +// call from DllMain on DLL_THREAD_DETACH +// 2. windows static library: +// use `FlsAlloc` to call a destructor when the thread is done +// 3. unix, pthreads: +// use a pthread key to call a destructor when a pthread is done +// +// In the last two cases we also need to call `mi_process_init` +// to set up the thread local keys. +// -------------------------------------------------------- + +// Set up handlers so `mi_thread_done` is called automatically +static void mi_process_setup_auto_thread_done(void) { + static bool tls_initialized = false; // fine if it races + if (tls_initialized) return; + tls_initialized = true; + _mi_prim_thread_init_auto_done(); + _mi_heap_set_default_direct(&_mi_heap_main); +} + + +bool _mi_is_main_thread(void) { + return (_mi_heap_main.thread_id==0 || _mi_heap_main.thread_id == _mi_thread_id()); +} + +static _Atomic(size_t) thread_count = MI_ATOMIC_VAR_INIT(1); + +size_t _mi_current_thread_count(void) { + return mi_atomic_load_relaxed(&thread_count); +} + +// This is called from the `mi_malloc_generic` +void mi_thread_init(void) mi_attr_noexcept +{ + // ensure our process has started already + mi_process_init(); + + // initialize the thread local default heap + // (this will call `_mi_heap_set_default_direct` and thus set the + // fiber/pthread key to a non-zero value, ensuring `_mi_thread_done` is called) + if (_mi_heap_init()) return; // returns true if already initialized + + _mi_stat_increase(&_mi_stats_main.threads, 1); + mi_atomic_increment_relaxed(&thread_count); + //_mi_verbose_message("thread init: 0x%zx\n", _mi_thread_id()); +} + +void mi_thread_done(void) mi_attr_noexcept { + _mi_thread_done(NULL); +} + +void _mi_thread_done(mi_heap_t* heap) +{ + // calling with NULL implies using the default heap + if (heap == NULL) { + heap = mi_prim_get_default_heap(); + if (heap == NULL) return; + } + + // prevent re-entrancy through heap_done/heap_set_default_direct (issue #699) + if (!mi_heap_is_initialized(heap)) { + return; + } + + // adjust stats + mi_atomic_decrement_relaxed(&thread_count); + _mi_stat_decrease(&_mi_stats_main.threads, 1); + + // check thread-id as on Windows shutdown with FLS the main (exit) thread may call this on thread-local heaps... + if (heap->thread_id != _mi_thread_id()) return; + + // abandon the thread local heap + if (_mi_heap_done(heap)) return; // returns true if already ran +} + +void _mi_heap_set_default_direct(mi_heap_t* heap) { + mi_assert_internal(heap != NULL); + #if defined(MI_TLS_SLOT) + mi_prim_tls_slot_set(MI_TLS_SLOT,heap); + #elif defined(MI_TLS_PTHREAD_SLOT_OFS) + *mi_tls_pthread_heap_slot() = heap; + #elif defined(MI_TLS_PTHREAD) + // we use _mi_heap_default_key + #else + _mi_heap_default = heap; + #endif + + // ensure the default heap is passed to `_mi_thread_done` + // setting to a non-NULL value also ensures `mi_thread_done` is called. + _mi_prim_thread_associate_default_heap(heap); +} + + +// -------------------------------------------------------- +// Run functions on process init/done, and thread init/done +// -------------------------------------------------------- +static void mi_cdecl mi_process_done(void); + +static bool os_preloading = true; // true until this module is initialized +static bool mi_redirected = false; // true if malloc redirects to mi_malloc + +// Returns true if this module has not been initialized; Don't use C runtime routines until it returns false. +bool mi_decl_noinline _mi_preloading(void) { + return os_preloading; +} + +mi_decl_nodiscard bool mi_is_redirected(void) mi_attr_noexcept { + return mi_redirected; +} + +// Communicate with the redirection module on Windows +#if defined(_WIN32) && defined(MI_SHARED_LIB) && !defined(MI_WIN_NOREDIRECT) +#ifdef __cplusplus +extern "C" { +#endif +mi_decl_export void _mi_redirect_entry(DWORD reason) { + // called on redirection; careful as this may be called before DllMain + if (reason == DLL_PROCESS_ATTACH) { + mi_redirected = true; + } + else if (reason == DLL_PROCESS_DETACH) { + mi_redirected = false; + } + else if (reason == DLL_THREAD_DETACH) { + mi_thread_done(); + } +} +__declspec(dllimport) bool mi_cdecl mi_allocator_init(const char** message); +__declspec(dllimport) void mi_cdecl mi_allocator_done(void); +#ifdef __cplusplus +} +#endif +#else +static bool mi_allocator_init(const char** message) { + if (message != NULL) *message = NULL; + return true; +} +static void mi_allocator_done(void) { + // nothing to do +} +#endif + +// Called once by the process loader +static void mi_process_load(void) { + mi_heap_main_init(); + #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD) + volatile mi_heap_t* dummy = _mi_heap_default; // access TLS to allocate it before setting tls_initialized to true; + if (dummy == NULL) return; // use dummy or otherwise the access may get optimized away (issue #697) + #endif + os_preloading = false; + mi_assert_internal(_mi_is_main_thread()); + #if !(defined(_WIN32) && defined(MI_SHARED_LIB)) // use Dll process detach (see below) instead of atexit (issue #521) + atexit(&mi_process_done); + #endif + _mi_options_init(); + mi_process_setup_auto_thread_done(); + mi_process_init(); + if (mi_redirected) _mi_verbose_message("malloc is redirected.\n"); + + // show message from the redirector (if present) + const char* msg = NULL; + mi_allocator_init(&msg); + if (msg != NULL && (mi_option_is_enabled(mi_option_verbose) || mi_option_is_enabled(mi_option_show_errors))) { + _mi_fputs(NULL,NULL,NULL,msg); + } + + // reseed random + _mi_random_reinit_if_weak(&_mi_heap_main.random); +} + +#if defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64)) +#include +mi_decl_cache_align bool _mi_cpu_has_fsrm = false; + +static void mi_detect_cpu_features(void) { + // FSRM for fast rep movsb support (AMD Zen3+ (~2020) or Intel Ice Lake+ (~2017)) + int32_t cpu_info[4]; + __cpuid(cpu_info, 7); + _mi_cpu_has_fsrm = ((cpu_info[3] & (1 << 4)) != 0); // bit 4 of EDX : see +} +#else +static void mi_detect_cpu_features(void) { + // nothing +} +#endif + +// Initialize the process; called by thread_init or the process loader +void mi_process_init(void) mi_attr_noexcept { + // ensure we are called once + static mi_atomic_once_t process_init; + #if _MSC_VER < 1920 + mi_heap_main_init(); // vs2017 can dynamically re-initialize _mi_heap_main + #endif + if (!mi_atomic_once(&process_init)) return; + _mi_process_is_initialized = true; + _mi_verbose_message("process init: 0x%zx\n", _mi_thread_id()); + mi_process_setup_auto_thread_done(); + + mi_detect_cpu_features(); + _mi_os_init(); + mi_heap_main_init(); + #if MI_DEBUG + _mi_verbose_message("debug level : %d\n", MI_DEBUG); + #endif + _mi_verbose_message("secure level: %d\n", MI_SECURE); + _mi_verbose_message("mem tracking: %s\n", MI_TRACK_TOOL); + #if MI_TSAN + _mi_verbose_message("thread santizer enabled\n"); + #endif + mi_thread_init(); + + #if defined(_WIN32) + // On windows, when building as a static lib the FLS cleanup happens to early for the main thread. + // To avoid this, set the FLS value for the main thread to NULL so the fls cleanup + // will not call _mi_thread_done on the (still executing) main thread. See issue #508. + _mi_prim_thread_associate_default_heap(NULL); + #endif + + mi_stats_reset(); // only call stat reset *after* thread init (or the heap tld == NULL) + mi_track_init(); + + if (mi_option_is_enabled(mi_option_reserve_huge_os_pages)) { + size_t pages = mi_option_get_clamp(mi_option_reserve_huge_os_pages, 0, 128*1024); + long reserve_at = mi_option_get(mi_option_reserve_huge_os_pages_at); + if (reserve_at != -1) { + mi_reserve_huge_os_pages_at(pages, reserve_at, pages*500); + } else { + mi_reserve_huge_os_pages_interleave(pages, 0, pages*500); + } + } + if (mi_option_is_enabled(mi_option_reserve_os_memory)) { + long ksize = mi_option_get(mi_option_reserve_os_memory); + if (ksize > 0) { + mi_reserve_os_memory((size_t)ksize*MI_KiB, true /* commit? */, true /* allow large pages? */); + } + } +} + +// Called when the process is done (through `at_exit`) +static void mi_cdecl mi_process_done(void) { + // only shutdown if we were initialized + if (!_mi_process_is_initialized) return; + // ensure we are called once + static bool process_done = false; + if (process_done) return; + process_done = true; + + // release any thread specific resources and ensure _mi_thread_done is called on all but the main thread + _mi_prim_thread_done_auto_done(); + + #ifndef MI_SKIP_COLLECT_ON_EXIT + #if (MI_DEBUG || !defined(MI_SHARED_LIB)) + // free all memory if possible on process exit. This is not needed for a stand-alone process + // but should be done if mimalloc is statically linked into another shared library which + // is repeatedly loaded/unloaded, see issue #281. + mi_collect(true /* force */ ); + #endif + #endif + + // Forcefully release all retained memory; this can be dangerous in general if overriding regular malloc/free + // since after process_done there might still be other code running that calls `free` (like at_exit routines, + // or C-runtime termination code. + if (mi_option_is_enabled(mi_option_destroy_on_exit)) { + mi_collect(true /* force */); + _mi_heap_unsafe_destroy_all(); // forcefully release all memory held by all heaps (of this thread only!) + _mi_arena_unsafe_destroy_all(& _mi_heap_main_get()->tld->stats); + } + + if (mi_option_is_enabled(mi_option_show_stats) || mi_option_is_enabled(mi_option_verbose)) { + mi_stats_print(NULL); + } + mi_allocator_done(); + _mi_verbose_message("process done: 0x%zx\n", _mi_heap_main.thread_id); + os_preloading = true; // don't call the C runtime anymore +} + + + +#if defined(_WIN32) && defined(MI_SHARED_LIB) + // Windows DLL: easy to hook into process_init and thread_done + __declspec(dllexport) BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved) { + MI_UNUSED(reserved); + MI_UNUSED(inst); + if (reason==DLL_PROCESS_ATTACH) { + mi_process_load(); + } + else if (reason==DLL_PROCESS_DETACH) { + mi_process_done(); + } + else if (reason==DLL_THREAD_DETACH) { + if (!mi_is_redirected()) { + mi_thread_done(); + } + } + return TRUE; + } + +#elif defined(_MSC_VER) + // MSVC: use data section magic for static libraries + // See + static int _mi_process_init(void) { + mi_process_load(); + return 0; + } + typedef int(*_mi_crt_callback_t)(void); + #if defined(_M_X64) || defined(_M_ARM64) + __pragma(comment(linker, "/include:" "_mi_msvc_initu")) + #pragma section(".CRT$XIU", long, read) + #else + __pragma(comment(linker, "/include:" "__mi_msvc_initu")) + #endif + #pragma data_seg(".CRT$XIU") + mi_decl_externc _mi_crt_callback_t _mi_msvc_initu[] = { &_mi_process_init }; + #pragma data_seg() + +#elif defined(__cplusplus) + // C++: use static initialization to detect process start + static bool _mi_process_init(void) { + mi_process_load(); + return (_mi_heap_main.thread_id != 0); + } + static bool mi_initialized = _mi_process_init(); + +#elif defined(__GNUC__) || defined(__clang__) + // GCC,Clang: use the constructor attribute + static void __attribute__((constructor)) _mi_process_init(void) { + mi_process_load(); + } + +#else +#pragma message("define a way to call mi_process_load on your platform") +#endif diff --git a/compat/mimalloc/mimalloc.h b/compat/mimalloc/mimalloc.h new file mode 100644 index 00000000000000..c0f5e96e51e975 --- /dev/null +++ b/compat/mimalloc/mimalloc.h @@ -0,0 +1,565 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_H +#define MIMALLOC_H + +#define MI_MALLOC_VERSION 212 // major + 2 digits minor + +// ------------------------------------------------------ +// Compiler specific attributes +// ------------------------------------------------------ + +#ifdef __cplusplus + #if (__cplusplus >= 201103L) || (_MSC_VER > 1900) // C++11 + #define mi_attr_noexcept noexcept + #else + #define mi_attr_noexcept throw() + #endif +#else + #define mi_attr_noexcept +#endif + +#if defined(__cplusplus) && (__cplusplus >= 201703) + #define mi_decl_nodiscard [[nodiscard]] +#elif (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) // includes clang, icc, and clang-cl + #define mi_decl_nodiscard __attribute__((warn_unused_result)) +#elif defined(_HAS_NODISCARD) + #define mi_decl_nodiscard _NODISCARD +#elif (_MSC_VER >= 1700) + #define mi_decl_nodiscard _Check_return_ +#else + #define mi_decl_nodiscard +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) + #if !defined(MI_SHARED_LIB) + #define mi_decl_export + #elif defined(MI_SHARED_LIB_EXPORT) + #define mi_decl_export __declspec(dllexport) + #else + #define mi_decl_export __declspec(dllimport) + #endif + #if defined(__MINGW32__) + #define mi_decl_restrict + #define mi_attr_malloc __attribute__((malloc)) + #else + #if (_MSC_VER >= 1900) && !defined(__EDG__) + #define mi_decl_restrict __declspec(allocator) __declspec(restrict) + #else + #define mi_decl_restrict __declspec(restrict) + #endif + #define mi_attr_malloc + #endif + #define mi_cdecl __cdecl + #define mi_attr_alloc_size(s) + #define mi_attr_alloc_size2(s1,s2) + #define mi_attr_alloc_align(p) +#elif defined(__GNUC__) // includes clang and icc + #if defined(MI_SHARED_LIB) && defined(MI_SHARED_LIB_EXPORT) + #define mi_decl_export __attribute__((visibility("default"))) + #else + #define mi_decl_export + #endif + #define mi_cdecl // leads to warnings... __attribute__((cdecl)) + #define mi_decl_restrict + #define mi_attr_malloc __attribute__((malloc)) + #if (defined(__clang_major__) && (__clang_major__ < 4)) || (__GNUC__ < 5) + #define mi_attr_alloc_size(s) + #define mi_attr_alloc_size2(s1,s2) + #define mi_attr_alloc_align(p) + #elif defined(__INTEL_COMPILER) + #define mi_attr_alloc_size(s) __attribute__((alloc_size(s))) + #define mi_attr_alloc_size2(s1,s2) __attribute__((alloc_size(s1,s2))) + #define mi_attr_alloc_align(p) + #else + #define mi_attr_alloc_size(s) __attribute__((alloc_size(s))) + #define mi_attr_alloc_size2(s1,s2) __attribute__((alloc_size(s1,s2))) + #define mi_attr_alloc_align(p) __attribute__((alloc_align(p))) + #endif +#else + #define mi_cdecl + #define mi_decl_export + #define mi_decl_restrict + #define mi_attr_malloc + #define mi_attr_alloc_size(s) + #define mi_attr_alloc_size2(s1,s2) + #define mi_attr_alloc_align(p) +#endif + +// ------------------------------------------------------ +// Includes +// ------------------------------------------------------ + +#include // size_t +#include // bool +#include // INTPTR_MAX + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------------ +// Standard malloc interface +// ------------------------------------------------------ + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); +mi_decl_nodiscard mi_decl_export void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); +mi_decl_export void* mi_expand(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); + +mi_decl_export void mi_free(void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc; + +// ------------------------------------------------------ +// Extended functionality +// ------------------------------------------------------ +#define MI_SMALL_WSIZE_MAX (128) +#define MI_SMALL_SIZE_MAX (MI_SMALL_WSIZE_MAX*sizeof(void*)) + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); +mi_decl_nodiscard mi_decl_export void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); +mi_decl_nodiscard mi_decl_export void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); + +mi_decl_nodiscard mi_decl_export size_t mi_usable_size(const void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export size_t mi_good_size(size_t size) mi_attr_noexcept; + + +// ------------------------------------------------------ +// Internals +// ------------------------------------------------------ + +typedef void (mi_cdecl mi_deferred_free_fun)(bool force, unsigned long long heartbeat, void* arg); +mi_decl_export void mi_register_deferred_free(mi_deferred_free_fun* deferred_free, void* arg) mi_attr_noexcept; + +typedef void (mi_cdecl mi_output_fun)(const char* msg, void* arg); +mi_decl_export void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept; + +typedef void (mi_cdecl mi_error_fun)(int err, void* arg); +mi_decl_export void mi_register_error(mi_error_fun* fun, void* arg); + +mi_decl_export void mi_collect(bool force) mi_attr_noexcept; +mi_decl_export int mi_version(void) mi_attr_noexcept; +mi_decl_export void mi_stats_reset(void) mi_attr_noexcept; +mi_decl_export void mi_stats_merge(void) mi_attr_noexcept; +mi_decl_export void mi_stats_print(void* out) mi_attr_noexcept; // backward compatibility: `out` is ignored and should be NULL +mi_decl_export void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; + +mi_decl_export void mi_process_init(void) mi_attr_noexcept; +mi_decl_export void mi_thread_init(void) mi_attr_noexcept; +mi_decl_export void mi_thread_done(void) mi_attr_noexcept; +mi_decl_export void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; + +mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, + size_t* current_rss, size_t* peak_rss, + size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept; + +// ------------------------------------------------------------------------------------- +// Aligned allocation +// Note that `alignment` always follows `size` for consistency with unaligned +// allocation, but unfortunately this differs from `posix_memalign` and `aligned_alloc`. +// ------------------------------------------------------------------------------------- + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); +mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2); + + +// ------------------------------------------------------------------------------------- +// Heaps: first-class, but can only allocate from the same thread that created it. +// ------------------------------------------------------------------------------------- + +struct mi_heap_s; +typedef struct mi_heap_s mi_heap_t; + +mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new(void); +mi_decl_export void mi_heap_delete(mi_heap_t* heap); +mi_decl_export void mi_heap_destroy(mi_heap_t* heap); +mi_decl_export mi_heap_t* mi_heap_set_default(mi_heap_t* heap); +mi_decl_export mi_heap_t* mi_heap_get_default(void); +mi_decl_export mi_heap_t* mi_heap_get_backing(void); +mi_decl_export void mi_heap_collect(mi_heap_t* heap, bool force) mi_attr_noexcept; + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); + +mi_decl_nodiscard mi_decl_export void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); +mi_decl_nodiscard mi_decl_export void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(3,4); +mi_decl_nodiscard mi_decl_export void* mi_heap_reallocf(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); + +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept mi_attr_malloc; + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); +mi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3); + + +// -------------------------------------------------------------------------------- +// Zero initialized re-allocation. +// Only valid on memory that was originally allocated with zero initialization too. +// e.g. `mi_calloc`, `mi_zalloc`, `mi_zalloc_aligned` etc. +// see +// -------------------------------------------------------------------------------- + +mi_decl_nodiscard mi_decl_export void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export void* mi_recalloc(void* p, size_t newcount, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); + +mi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3); +mi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(2,3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(2,3); + +mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); +mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t newcount, size_t size) mi_attr_noexcept mi_attr_alloc_size2(3,4); + +mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4); +mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3); +mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(3,4) mi_attr_alloc_align(5); +mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(3,4); + + +// ------------------------------------------------------ +// Analysis +// ------------------------------------------------------ + +mi_decl_export bool mi_heap_contains_block(mi_heap_t* heap, const void* p); +mi_decl_export bool mi_heap_check_owned(mi_heap_t* heap, const void* p); +mi_decl_export bool mi_check_owned(const void* p); + +// An area of heap space contains blocks of a single size. +typedef struct mi_heap_area_s { + void* blocks; // start of the area containing heap blocks + size_t reserved; // bytes reserved for this area (virtual) + size_t committed; // current available bytes for this area + size_t used; // number of allocated blocks + size_t block_size; // size in bytes of each block + size_t full_block_size; // size in bytes of a full block including padding and metadata. +} mi_heap_area_t; + +typedef bool (mi_cdecl mi_block_visit_fun)(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg); + +mi_decl_export bool mi_heap_visit_blocks(const mi_heap_t* heap, bool visit_all_blocks, mi_block_visit_fun* visitor, void* arg); + +// Experimental +mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export bool mi_is_redirected(void) mi_attr_noexcept; + +mi_decl_export int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept; +mi_decl_export int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept; + +mi_decl_export int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept; +mi_decl_export bool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node) mi_attr_noexcept; + +mi_decl_export void mi_debug_show_arenas(void) mi_attr_noexcept; + +// Experimental: heaps associated with specific memory arena's +typedef int mi_arena_id_t; +mi_decl_export void* mi_arena_area(mi_arena_id_t arena_id, size_t* size); +mi_decl_export int mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_msecs, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept; +mi_decl_export int mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept; +mi_decl_export bool mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is_large, bool is_zero, int numa_node, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept; + +#if MI_MALLOC_VERSION >= 182 +// Create a heap that only allocates in the specified arena +mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id); +#endif + +// deprecated +mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept; + + +// ------------------------------------------------------ +// Convenience +// ------------------------------------------------------ + +#define mi_malloc_tp(tp) ((tp*)mi_malloc(sizeof(tp))) +#define mi_zalloc_tp(tp) ((tp*)mi_zalloc(sizeof(tp))) +#define mi_calloc_tp(tp,n) ((tp*)mi_calloc(n,sizeof(tp))) +#define mi_mallocn_tp(tp,n) ((tp*)mi_mallocn(n,sizeof(tp))) +#define mi_reallocn_tp(p,tp,n) ((tp*)mi_reallocn(p,n,sizeof(tp))) +#define mi_recalloc_tp(p,tp,n) ((tp*)mi_recalloc(p,n,sizeof(tp))) + +#define mi_heap_malloc_tp(hp,tp) ((tp*)mi_heap_malloc(hp,sizeof(tp))) +#define mi_heap_zalloc_tp(hp,tp) ((tp*)mi_heap_zalloc(hp,sizeof(tp))) +#define mi_heap_calloc_tp(hp,tp,n) ((tp*)mi_heap_calloc(hp,n,sizeof(tp))) +#define mi_heap_mallocn_tp(hp,tp,n) ((tp*)mi_heap_mallocn(hp,n,sizeof(tp))) +#define mi_heap_reallocn_tp(hp,p,tp,n) ((tp*)mi_heap_reallocn(hp,p,n,sizeof(tp))) +#define mi_heap_recalloc_tp(hp,p,tp,n) ((tp*)mi_heap_recalloc(hp,p,n,sizeof(tp))) + + +// ------------------------------------------------------ +// Options +// ------------------------------------------------------ + +typedef enum mi_option_e { + // stable options + mi_option_show_errors, // print error messages + mi_option_show_stats, // print statistics on termination + mi_option_verbose, // print verbose messages + // the following options are experimental (see src/options.h) + mi_option_eager_commit, // eager commit segments? (after `eager_commit_delay` segments) (=1) + mi_option_arena_eager_commit, // eager commit arenas? Use 2 to enable just on overcommit systems (=2) + mi_option_purge_decommits, // should a memory purge decommit (or only reset) (=1) + mi_option_allow_large_os_pages, // allow large (2MiB) OS pages, implies eager commit + mi_option_reserve_huge_os_pages, // reserve N huge OS pages (1GiB/page) at startup + mi_option_reserve_huge_os_pages_at, // reserve huge OS pages at a specific NUMA node + mi_option_reserve_os_memory, // reserve specified amount of OS memory in an arena at startup + mi_option_deprecated_segment_cache, + mi_option_deprecated_page_reset, + mi_option_abandoned_page_purge, // immediately purge delayed purges on thread termination + mi_option_deprecated_segment_reset, + mi_option_eager_commit_delay, + mi_option_purge_delay, // memory purging is delayed by N milli seconds; use 0 for immediate purging or -1 for no purging at all. + mi_option_use_numa_nodes, // 0 = use all available numa nodes, otherwise use at most N nodes. + mi_option_limit_os_alloc, // 1 = do not use OS memory for allocation (but only programmatically reserved arenas) + mi_option_os_tag, // tag used for OS logging (macOS only for now) + mi_option_max_errors, // issue at most N error messages + mi_option_max_warnings, // issue at most N warning messages + mi_option_max_segment_reclaim, + mi_option_destroy_on_exit, // if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe. + mi_option_arena_reserve, // initial memory size in KiB for arena reservation (1GiB on 64-bit) + mi_option_arena_purge_mult, + mi_option_purge_extend_delay, + _mi_option_last, + // legacy option names + mi_option_large_os_pages = mi_option_allow_large_os_pages, + mi_option_eager_region_commit = mi_option_arena_eager_commit, + mi_option_reset_decommits = mi_option_purge_decommits, + mi_option_reset_delay = mi_option_purge_delay, + mi_option_abandoned_page_reset = mi_option_abandoned_page_purge +} mi_option_t; + + +mi_decl_nodiscard mi_decl_export bool mi_option_is_enabled(mi_option_t option); +mi_decl_export void mi_option_enable(mi_option_t option); +mi_decl_export void mi_option_disable(mi_option_t option); +mi_decl_export void mi_option_set_enabled(mi_option_t option, bool enable); +mi_decl_export void mi_option_set_enabled_default(mi_option_t option, bool enable); + +mi_decl_nodiscard mi_decl_export long mi_option_get(mi_option_t option); +mi_decl_nodiscard mi_decl_export long mi_option_get_clamp(mi_option_t option, long min, long max); +mi_decl_nodiscard mi_decl_export size_t mi_option_get_size(mi_option_t option); +mi_decl_export void mi_option_set(mi_option_t option, long value); +mi_decl_export void mi_option_set_default(mi_option_t option, long value); + + +// ------------------------------------------------------------------------------------------------------- +// "mi" prefixed implementations of various posix, Unix, Windows, and C++ allocation functions. +// (This can be convenient when providing overrides of these functions as done in `mimalloc-override.h`.) +// note: we use `mi_cfree` as "checked free" and it checks if the pointer is in our heap before free-ing. +// ------------------------------------------------------------------------------------------------------- + +mi_decl_export void mi_cfree(void* p) mi_attr_noexcept; +mi_decl_export void* mi__expand(void* p, size_t newsize) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export size_t mi_malloc_size(const void* p) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export size_t mi_malloc_good_size(size_t size) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept; + +mi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_valloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1); + +mi_decl_nodiscard mi_decl_export void* mi_reallocarray(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); +mi_decl_nodiscard mi_decl_export int mi_reallocarr(void* p, size_t count, size_t size) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept; +mi_decl_nodiscard mi_decl_export void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept; + +mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned short* mi_wcsdup(const unsigned short* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s) mi_attr_noexcept mi_attr_malloc; +mi_decl_export int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept; +mi_decl_export int mi_wdupenv_s(unsigned short** buf, size_t* size, const unsigned short* name) mi_attr_noexcept; + +mi_decl_export void mi_free_size(void* p, size_t size) mi_attr_noexcept; +mi_decl_export void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept; +mi_decl_export void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept; + +// The `mi_new` wrappers implement C++ semantics on out-of-memory instead of directly returning `NULL`. +// (and call `std::get_new_handler` and potentially raise a `std::bad_alloc` exception). +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new(size_t size) mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_n(size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(1, 2); +mi_decl_nodiscard mi_decl_export void* mi_new_realloc(void* p, size_t newsize) mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export void* mi_new_reallocn(void* p, size_t newcount, size_t size) mi_attr_alloc_size2(2, 3); + +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) mi_attr_malloc mi_attr_alloc_size(2); +mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(2, 3); + +#ifdef __cplusplus +} +#endif + +// --------------------------------------------------------------------------------------------- +// Implement the C++ std::allocator interface for use in STL containers. +// (note: see `mimalloc-new-delete.h` for overriding the new/delete operators globally) +// --------------------------------------------------------------------------------------------- +#ifdef __cplusplus + +#include // std::size_t +#include // PTRDIFF_MAX +#if (__cplusplus >= 201103L) || (_MSC_VER > 1900) // C++11 +#include // std::true_type +#include // std::forward +#endif + +template struct _mi_stl_allocator_common { + typedef T value_type; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef value_type& reference; + typedef value_type const& const_reference; + typedef value_type* pointer; + typedef value_type const* const_pointer; + + #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + template void construct(U* p, Args&& ...args) { ::new(p) U(std::forward(args)...); } + template void destroy(U* p) mi_attr_noexcept { p->~U(); } + #else + void construct(pointer p, value_type const& val) { ::new(p) value_type(val); } + void destroy(pointer p) { p->~value_type(); } + #endif + + size_type max_size() const mi_attr_noexcept { return (PTRDIFF_MAX/sizeof(value_type)); } + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } +}; + +template struct mi_stl_allocator : public _mi_stl_allocator_common { + using typename _mi_stl_allocator_common::size_type; + using typename _mi_stl_allocator_common::value_type; + using typename _mi_stl_allocator_common::pointer; + template struct rebind { typedef mi_stl_allocator other; }; + + mi_stl_allocator() mi_attr_noexcept = default; + mi_stl_allocator(const mi_stl_allocator&) mi_attr_noexcept = default; + template mi_stl_allocator(const mi_stl_allocator&) mi_attr_noexcept { } + mi_stl_allocator select_on_container_copy_construction() const { return *this; } + void deallocate(T* p, size_type) { mi_free(p); } + + #if (__cplusplus >= 201703L) // C++17 + mi_decl_nodiscard T* allocate(size_type count) { return static_cast(mi_new_n(count, sizeof(T))); } + mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); } + #else + mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast(mi_new_n(count, sizeof(value_type))); } + #endif + + #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 + using is_always_equal = std::true_type; + #endif +}; + +template bool operator==(const mi_stl_allocator& , const mi_stl_allocator& ) mi_attr_noexcept { return true; } +template bool operator!=(const mi_stl_allocator& , const mi_stl_allocator& ) mi_attr_noexcept { return false; } + + +#if (__cplusplus >= 201103L) || (_MSC_VER >= 1900) // C++11 +#define MI_HAS_HEAP_STL_ALLOCATOR 1 + +#include // std::shared_ptr + +// Common base class for STL allocators in a specific heap +template struct _mi_heap_stl_allocator_common : public _mi_stl_allocator_common { + using typename _mi_stl_allocator_common::size_type; + using typename _mi_stl_allocator_common::value_type; + using typename _mi_stl_allocator_common::pointer; + + _mi_heap_stl_allocator_common(mi_heap_t* hp) : heap(hp) { } /* will not delete nor destroy the passed in heap */ + + #if (__cplusplus >= 201703L) // C++17 + mi_decl_nodiscard T* allocate(size_type count) { return static_cast(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(T))); } + mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); } + #else + mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(value_type))); } + #endif + + #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 + using is_always_equal = std::false_type; + #endif + + void collect(bool force) { mi_heap_collect(this->heap.get(), force); } + template bool is_equal(const _mi_heap_stl_allocator_common& x) const { return (this->heap == x.heap); } + +protected: + std::shared_ptr heap; + template friend struct _mi_heap_stl_allocator_common; + + _mi_heap_stl_allocator_common() { + mi_heap_t* hp = mi_heap_new(); + this->heap.reset(hp, (_mi_destroy ? &heap_destroy : &heap_delete)); /* calls heap_delete/destroy when the refcount drops to zero */ + } + _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } + template _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } + +private: + static void heap_delete(mi_heap_t* hp) { if (hp != NULL) { mi_heap_delete(hp); } } + static void heap_destroy(mi_heap_t* hp) { if (hp != NULL) { mi_heap_destroy(hp); } } +}; + +// STL allocator allocation in a specific heap +template struct mi_heap_stl_allocator : public _mi_heap_stl_allocator_common { + using typename _mi_heap_stl_allocator_common::size_type; + mi_heap_stl_allocator() : _mi_heap_stl_allocator_common() { } // creates fresh heap that is deleted when the destructor is called + mi_heap_stl_allocator(mi_heap_t* hp) : _mi_heap_stl_allocator_common(hp) { } // no delete nor destroy on the passed in heap + template mi_heap_stl_allocator(const mi_heap_stl_allocator& x) mi_attr_noexcept : _mi_heap_stl_allocator_common(x) { } + + mi_heap_stl_allocator select_on_container_copy_construction() const { return *this; } + void deallocate(T* p, size_type) { mi_free(p); } + template struct rebind { typedef mi_heap_stl_allocator other; }; +}; + +template bool operator==(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (x.is_equal(y)); } +template bool operator!=(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (!x.is_equal(y)); } + + +// STL allocator allocation in a specific heap, where `free` does nothing and +// the heap is destroyed in one go on destruction -- use with care! +template struct mi_heap_destroy_stl_allocator : public _mi_heap_stl_allocator_common { + using typename _mi_heap_stl_allocator_common::size_type; + mi_heap_destroy_stl_allocator() : _mi_heap_stl_allocator_common() { } // creates fresh heap that is destroyed when the destructor is called + mi_heap_destroy_stl_allocator(mi_heap_t* hp) : _mi_heap_stl_allocator_common(hp) { } // no delete nor destroy on the passed in heap + template mi_heap_destroy_stl_allocator(const mi_heap_destroy_stl_allocator& x) mi_attr_noexcept : _mi_heap_stl_allocator_common(x) { } + + mi_heap_destroy_stl_allocator select_on_container_copy_construction() const { return *this; } + void deallocate(T*, size_type) { /* do nothing as we destroy the heap on destruct. */ } + template struct rebind { typedef mi_heap_destroy_stl_allocator other; }; +}; + +template bool operator==(const mi_heap_destroy_stl_allocator& x, const mi_heap_destroy_stl_allocator& y) mi_attr_noexcept { return (x.is_equal(y)); } +template bool operator!=(const mi_heap_destroy_stl_allocator& x, const mi_heap_destroy_stl_allocator& y) mi_attr_noexcept { return (!x.is_equal(y)); } + +#endif // C++11 + +#endif // __cplusplus + +#endif diff --git a/compat/mimalloc/mimalloc/atomic.h b/compat/mimalloc/mimalloc/atomic.h new file mode 100644 index 00000000000000..c6b8146ffdb049 --- /dev/null +++ b/compat/mimalloc/mimalloc/atomic.h @@ -0,0 +1,385 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023 Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_ATOMIC_H +#define MIMALLOC_ATOMIC_H + +// -------------------------------------------------------------------------------------------- +// Atomics +// We need to be portable between C, C++, and MSVC. +// We base the primitives on the C/C++ atomics and create a mimimal wrapper for MSVC in C compilation mode. +// This is why we try to use only `uintptr_t` and `*` as atomic types. +// To gain better insight in the range of used atomics, we use explicitly named memory order operations +// instead of passing the memory order as a parameter. +// ----------------------------------------------------------------------------------------------- + +#if defined(__cplusplus) +// Use C++ atomics +#include +#define _Atomic(tp) std::atomic +#define mi_atomic(name) std::atomic_##name +#define mi_memory_order(name) std::memory_order_##name +#if !defined(ATOMIC_VAR_INIT) || (__cplusplus >= 202002L) // c++20, see issue #571 + #define MI_ATOMIC_VAR_INIT(x) x +#else + #define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x) +#endif +#elif defined(_MSC_VER) +// Use MSVC C wrapper for C11 atomics +#define _Atomic(tp) tp +#define MI_ATOMIC_VAR_INIT(x) x +#define mi_atomic(name) mi_atomic_##name +#define mi_memory_order(name) mi_memory_order_##name +#else +// Use C11 atomics +#include +#define mi_atomic(name) atomic_##name +#define mi_memory_order(name) memory_order_##name +#if !defined(ATOMIC_VAR_INIT) || (__STDC_VERSION__ >= 201710L) // c17, see issue #735 + #define MI_ATOMIC_VAR_INIT(x) x +#else + #define MI_ATOMIC_VAR_INIT(x) ATOMIC_VAR_INIT(x) +#endif +#endif + +// Various defines for all used memory orders in mimalloc +#define mi_atomic_cas_weak(p,expected,desired,mem_success,mem_fail) \ + mi_atomic(compare_exchange_weak_explicit)(p,expected,desired,mem_success,mem_fail) + +#define mi_atomic_cas_strong(p,expected,desired,mem_success,mem_fail) \ + mi_atomic(compare_exchange_strong_explicit)(p,expected,desired,mem_success,mem_fail) + +#define mi_atomic_load_acquire(p) mi_atomic(load_explicit)(p,mi_memory_order(acquire)) +#define mi_atomic_load_relaxed(p) mi_atomic(load_explicit)(p,mi_memory_order(relaxed)) +#define mi_atomic_store_release(p,x) mi_atomic(store_explicit)(p,x,mi_memory_order(release)) +#define mi_atomic_store_relaxed(p,x) mi_atomic(store_explicit)(p,x,mi_memory_order(relaxed)) +#define mi_atomic_exchange_release(p,x) mi_atomic(exchange_explicit)(p,x,mi_memory_order(release)) +#define mi_atomic_exchange_acq_rel(p,x) mi_atomic(exchange_explicit)(p,x,mi_memory_order(acq_rel)) +#define mi_atomic_cas_weak_release(p,exp,des) mi_atomic_cas_weak(p,exp,des,mi_memory_order(release),mi_memory_order(relaxed)) +#define mi_atomic_cas_weak_acq_rel(p,exp,des) mi_atomic_cas_weak(p,exp,des,mi_memory_order(acq_rel),mi_memory_order(acquire)) +#define mi_atomic_cas_strong_release(p,exp,des) mi_atomic_cas_strong(p,exp,des,mi_memory_order(release),mi_memory_order(relaxed)) +#define mi_atomic_cas_strong_acq_rel(p,exp,des) mi_atomic_cas_strong(p,exp,des,mi_memory_order(acq_rel),mi_memory_order(acquire)) + +#define mi_atomic_add_relaxed(p,x) mi_atomic(fetch_add_explicit)(p,x,mi_memory_order(relaxed)) +#define mi_atomic_sub_relaxed(p,x) mi_atomic(fetch_sub_explicit)(p,x,mi_memory_order(relaxed)) +#define mi_atomic_add_acq_rel(p,x) mi_atomic(fetch_add_explicit)(p,x,mi_memory_order(acq_rel)) +#define mi_atomic_sub_acq_rel(p,x) mi_atomic(fetch_sub_explicit)(p,x,mi_memory_order(acq_rel)) +#define mi_atomic_and_acq_rel(p,x) mi_atomic(fetch_and_explicit)(p,x,mi_memory_order(acq_rel)) +#define mi_atomic_or_acq_rel(p,x) mi_atomic(fetch_or_explicit)(p,x,mi_memory_order(acq_rel)) + +#define mi_atomic_increment_relaxed(p) mi_atomic_add_relaxed(p,(uintptr_t)1) +#define mi_atomic_decrement_relaxed(p) mi_atomic_sub_relaxed(p,(uintptr_t)1) +#define mi_atomic_increment_acq_rel(p) mi_atomic_add_acq_rel(p,(uintptr_t)1) +#define mi_atomic_decrement_acq_rel(p) mi_atomic_sub_acq_rel(p,(uintptr_t)1) + +static inline void mi_atomic_yield(void); +static inline intptr_t mi_atomic_addi(_Atomic(intptr_t)*p, intptr_t add); +static inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub); + + +#if defined(__cplusplus) || !defined(_MSC_VER) + +// In C++/C11 atomics we have polymorphic atomics so can use the typed `ptr` variants (where `tp` is the type of atomic value) +// We use these macros so we can provide a typed wrapper in MSVC in C compilation mode as well +#define mi_atomic_load_ptr_acquire(tp,p) mi_atomic_load_acquire(p) +#define mi_atomic_load_ptr_relaxed(tp,p) mi_atomic_load_relaxed(p) + +// In C++ we need to add casts to help resolve templates if NULL is passed +#if defined(__cplusplus) +#define mi_atomic_store_ptr_release(tp,p,x) mi_atomic_store_release(p,(tp*)x) +#define mi_atomic_store_ptr_relaxed(tp,p,x) mi_atomic_store_relaxed(p,(tp*)x) +#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des) mi_atomic_cas_weak_release(p,exp,(tp*)des) +#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des) mi_atomic_cas_weak_acq_rel(p,exp,(tp*)des) +#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des) mi_atomic_cas_strong_release(p,exp,(tp*)des) +#define mi_atomic_exchange_ptr_release(tp,p,x) mi_atomic_exchange_release(p,(tp*)x) +#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) mi_atomic_exchange_acq_rel(p,(tp*)x) +#else +#define mi_atomic_store_ptr_release(tp,p,x) mi_atomic_store_release(p,x) +#define mi_atomic_store_ptr_relaxed(tp,p,x) mi_atomic_store_relaxed(p,x) +#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des) mi_atomic_cas_weak_release(p,exp,des) +#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des) mi_atomic_cas_weak_acq_rel(p,exp,des) +#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des) mi_atomic_cas_strong_release(p,exp,des) +#define mi_atomic_exchange_ptr_release(tp,p,x) mi_atomic_exchange_release(p,x) +#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) mi_atomic_exchange_acq_rel(p,x) +#endif + +// These are used by the statistics +static inline int64_t mi_atomic_addi64_relaxed(volatile int64_t* p, int64_t add) { + return mi_atomic(fetch_add_explicit)((_Atomic(int64_t)*)p, add, mi_memory_order(relaxed)); +} +static inline void mi_atomic_maxi64_relaxed(volatile int64_t* p, int64_t x) { + int64_t current = mi_atomic_load_relaxed((_Atomic(int64_t)*)p); + while (current < x && !mi_atomic_cas_weak_release((_Atomic(int64_t)*)p, ¤t, x)) { /* nothing */ }; +} + +// Used by timers +#define mi_atomic_loadi64_acquire(p) mi_atomic(load_explicit)(p,mi_memory_order(acquire)) +#define mi_atomic_loadi64_relaxed(p) mi_atomic(load_explicit)(p,mi_memory_order(relaxed)) +#define mi_atomic_storei64_release(p,x) mi_atomic(store_explicit)(p,x,mi_memory_order(release)) +#define mi_atomic_storei64_relaxed(p,x) mi_atomic(store_explicit)(p,x,mi_memory_order(relaxed)) + +#define mi_atomic_casi64_strong_acq_rel(p,e,d) mi_atomic_cas_strong_acq_rel(p,e,d) +#define mi_atomic_addi64_acq_rel(p,i) mi_atomic_add_acq_rel(p,i) + + +#elif defined(_MSC_VER) + +// MSVC C compilation wrapper that uses Interlocked operations to model C11 atomics. +#define WIN32_LEAN_AND_MEAN +#include +#include +#ifdef _WIN64 +typedef LONG64 msc_intptr_t; +#define MI_64(f) f##64 +#else +typedef LONG msc_intptr_t; +#define MI_64(f) f +#endif + +typedef enum mi_memory_order_e { + mi_memory_order_relaxed, + mi_memory_order_consume, + mi_memory_order_acquire, + mi_memory_order_release, + mi_memory_order_acq_rel, + mi_memory_order_seq_cst +} mi_memory_order; + +static inline uintptr_t mi_atomic_fetch_add_explicit(_Atomic(uintptr_t)*p, uintptr_t add, mi_memory_order mo) { + (void)(mo); + return (uintptr_t)MI_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, (msc_intptr_t)add); +} +static inline uintptr_t mi_atomic_fetch_sub_explicit(_Atomic(uintptr_t)*p, uintptr_t sub, mi_memory_order mo) { + (void)(mo); + return (uintptr_t)MI_64(_InterlockedExchangeAdd)((volatile msc_intptr_t*)p, -((msc_intptr_t)sub)); +} +static inline uintptr_t mi_atomic_fetch_and_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) { + (void)(mo); + return (uintptr_t)MI_64(_InterlockedAnd)((volatile msc_intptr_t*)p, (msc_intptr_t)x); +} +static inline uintptr_t mi_atomic_fetch_or_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) { + (void)(mo); + return (uintptr_t)MI_64(_InterlockedOr)((volatile msc_intptr_t*)p, (msc_intptr_t)x); +} +static inline bool mi_atomic_compare_exchange_strong_explicit(_Atomic(uintptr_t)*p, uintptr_t* expected, uintptr_t desired, mi_memory_order mo1, mi_memory_order mo2) { + (void)(mo1); (void)(mo2); + uintptr_t read = (uintptr_t)MI_64(_InterlockedCompareExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)desired, (msc_intptr_t)(*expected)); + if (read == *expected) { + return true; + } + else { + *expected = read; + return false; + } +} +static inline bool mi_atomic_compare_exchange_weak_explicit(_Atomic(uintptr_t)*p, uintptr_t* expected, uintptr_t desired, mi_memory_order mo1, mi_memory_order mo2) { + return mi_atomic_compare_exchange_strong_explicit(p, expected, desired, mo1, mo2); +} +static inline uintptr_t mi_atomic_exchange_explicit(_Atomic(uintptr_t)*p, uintptr_t exchange, mi_memory_order mo) { + (void)(mo); + return (uintptr_t)MI_64(_InterlockedExchange)((volatile msc_intptr_t*)p, (msc_intptr_t)exchange); +} +static inline void mi_atomic_thread_fence(mi_memory_order mo) { + (void)(mo); + _Atomic(uintptr_t) x = 0; + mi_atomic_exchange_explicit(&x, 1, mo); +} +static inline uintptr_t mi_atomic_load_explicit(_Atomic(uintptr_t) const* p, mi_memory_order mo) { + (void)(mo); +#if defined(_M_IX86) || defined(_M_X64) + return *p; +#else + uintptr_t x = *p; + if (mo > mi_memory_order_relaxed) { + while (!mi_atomic_compare_exchange_weak_explicit(p, &x, x, mo, mi_memory_order_relaxed)) { /* nothing */ }; + } + return x; +#endif +} +static inline void mi_atomic_store_explicit(_Atomic(uintptr_t)*p, uintptr_t x, mi_memory_order mo) { + (void)(mo); +#if defined(_M_IX86) || defined(_M_X64) + *p = x; +#else + mi_atomic_exchange_explicit(p, x, mo); +#endif +} +static inline int64_t mi_atomic_loadi64_explicit(_Atomic(int64_t)*p, mi_memory_order mo) { + (void)(mo); +#if defined(_M_X64) + return *p; +#else + int64_t old = *p; + int64_t x = old; + while ((old = InterlockedCompareExchange64(p, x, old)) != x) { + x = old; + } + return x; +#endif +} +static inline void mi_atomic_storei64_explicit(_Atomic(int64_t)*p, int64_t x, mi_memory_order mo) { + (void)(mo); +#if defined(x_M_IX86) || defined(_M_X64) + *p = x; +#else + InterlockedExchange64(p, x); +#endif +} + +// These are used by the statistics +static inline int64_t mi_atomic_addi64_relaxed(volatile _Atomic(int64_t)*p, int64_t add) { +#ifdef _WIN64 + return (int64_t)mi_atomic_addi((int64_t*)p, add); +#else + int64_t current; + int64_t sum; + do { + current = *p; + sum = current + add; + } while (_InterlockedCompareExchange64(p, sum, current) != current); + return current; +#endif +} +static inline void mi_atomic_maxi64_relaxed(volatile _Atomic(int64_t)*p, int64_t x) { + int64_t current; + do { + current = *p; + } while (current < x && _InterlockedCompareExchange64(p, x, current) != current); +} + +static inline void mi_atomic_addi64_acq_rel(volatile _Atomic(int64_t*)p, int64_t i) { + mi_atomic_addi64_relaxed(p, i); +} + +static inline bool mi_atomic_casi64_strong_acq_rel(volatile _Atomic(int64_t*)p, int64_t* exp, int64_t des) { + int64_t read = _InterlockedCompareExchange64(p, des, *exp); + if (read == *exp) { + return true; + } + else { + *exp = read; + return false; + } +} + +// The pointer macros cast to `uintptr_t`. +#define mi_atomic_load_ptr_acquire(tp,p) (tp*)mi_atomic_load_acquire((_Atomic(uintptr_t)*)(p)) +#define mi_atomic_load_ptr_relaxed(tp,p) (tp*)mi_atomic_load_relaxed((_Atomic(uintptr_t)*)(p)) +#define mi_atomic_store_ptr_release(tp,p,x) mi_atomic_store_release((_Atomic(uintptr_t)*)(p),(uintptr_t)(x)) +#define mi_atomic_store_ptr_relaxed(tp,p,x) mi_atomic_store_relaxed((_Atomic(uintptr_t)*)(p),(uintptr_t)(x)) +#define mi_atomic_cas_ptr_weak_release(tp,p,exp,des) mi_atomic_cas_weak_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des) +#define mi_atomic_cas_ptr_weak_acq_rel(tp,p,exp,des) mi_atomic_cas_weak_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des) +#define mi_atomic_cas_ptr_strong_release(tp,p,exp,des) mi_atomic_cas_strong_release((_Atomic(uintptr_t)*)(p),(uintptr_t*)exp,(uintptr_t)des) +#define mi_atomic_exchange_ptr_release(tp,p,x) (tp*)mi_atomic_exchange_release((_Atomic(uintptr_t)*)(p),(uintptr_t)x) +#define mi_atomic_exchange_ptr_acq_rel(tp,p,x) (tp*)mi_atomic_exchange_acq_rel((_Atomic(uintptr_t)*)(p),(uintptr_t)x) + +#define mi_atomic_loadi64_acquire(p) mi_atomic(loadi64_explicit)(p,mi_memory_order(acquire)) +#define mi_atomic_loadi64_relaxed(p) mi_atomic(loadi64_explicit)(p,mi_memory_order(relaxed)) +#define mi_atomic_storei64_release(p,x) mi_atomic(storei64_explicit)(p,x,mi_memory_order(release)) +#define mi_atomic_storei64_relaxed(p,x) mi_atomic(storei64_explicit)(p,x,mi_memory_order(relaxed)) + + +#endif + + +// Atomically add a signed value; returns the previous value. +static inline intptr_t mi_atomic_addi(_Atomic(intptr_t)*p, intptr_t add) { + return (intptr_t)mi_atomic_add_acq_rel((_Atomic(uintptr_t)*)p, (uintptr_t)add); +} + +// Atomically subtract a signed value; returns the previous value. +static inline intptr_t mi_atomic_subi(_Atomic(intptr_t)*p, intptr_t sub) { + return (intptr_t)mi_atomic_addi(p, -sub); +} + +typedef _Atomic(uintptr_t) mi_atomic_once_t; + +// Returns true only on the first invocation +static inline bool mi_atomic_once( mi_atomic_once_t* once ) { + if (mi_atomic_load_relaxed(once) != 0) return false; // quick test + uintptr_t expected = 0; + return mi_atomic_cas_strong_acq_rel(once, &expected, (uintptr_t)1); // try to set to 1 +} + +typedef _Atomic(uintptr_t) mi_atomic_guard_t; + +// Allows only one thread to execute at a time +#define mi_atomic_guard(guard) \ + uintptr_t _mi_guard_expected = 0; \ + for(bool _mi_guard_once = true; \ + _mi_guard_once && mi_atomic_cas_strong_acq_rel(guard,&_mi_guard_expected,(uintptr_t)1); \ + (mi_atomic_store_release(guard,(uintptr_t)0), _mi_guard_once = false) ) + + + +// Yield +#if defined(__cplusplus) +#include +static inline void mi_atomic_yield(void) { + std::this_thread::yield(); +} +#elif defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +static inline void mi_atomic_yield(void) { + YieldProcessor(); +} +#elif defined(__SSE2__) +#include +static inline void mi_atomic_yield(void) { + _mm_pause(); +} +#elif (defined(__GNUC__) || defined(__clang__)) && \ + (defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__armel__) || defined(__ARMEL__) || \ + defined(__aarch64__) || defined(__powerpc__) || defined(__ppc__) || defined(__PPC__)) || defined(__POWERPC__) +#if defined(__x86_64__) || defined(__i386__) +static inline void mi_atomic_yield(void) { + __asm__ volatile ("pause" ::: "memory"); +} +#elif defined(__aarch64__) +static inline void mi_atomic_yield(void) { + __asm__ volatile("wfe"); +} +#elif (defined(__arm__) && __ARM_ARCH__ >= 7) +static inline void mi_atomic_yield(void) { + __asm__ volatile("yield" ::: "memory"); +} +#elif defined(__powerpc__) || defined(__ppc__) || defined(__PPC__) || defined(__POWERPC__) +#ifdef __APPLE__ +static inline void mi_atomic_yield(void) { + __asm__ volatile ("or r27,r27,r27" ::: "memory"); +} +#else +static inline void mi_atomic_yield(void) { + __asm__ __volatile__ ("or 27,27,27" ::: "memory"); +} +#endif +#elif defined(__armel__) || defined(__ARMEL__) +static inline void mi_atomic_yield(void) { + __asm__ volatile ("nop" ::: "memory"); +} +#endif +#elif defined(__sun) +// Fallback for other archs +#include +static inline void mi_atomic_yield(void) { + smt_pause(); +} +#elif defined(__wasi__) +#include +static inline void mi_atomic_yield(void) { + sched_yield(); +} +#else +#include +static inline void mi_atomic_yield(void) { + sleep(0); +} +#endif + + +#endif // __MIMALLOC_ATOMIC_H diff --git a/compat/mimalloc/mimalloc/internal.h b/compat/mimalloc/mimalloc/internal.h new file mode 100644 index 00000000000000..f076bc6a40f977 --- /dev/null +++ b/compat/mimalloc/mimalloc/internal.h @@ -0,0 +1,979 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_INTERNAL_H +#define MIMALLOC_INTERNAL_H + + +// -------------------------------------------------------------------------- +// This file contains the interal API's of mimalloc and various utility +// functions and macros. +// -------------------------------------------------------------------------- + +#include "mimalloc/types.h" +#include "mimalloc/track.h" + +#if (MI_DEBUG>0) +#define mi_trace_message(...) _mi_trace_message(__VA_ARGS__) +#else +#define mi_trace_message(...) +#endif + +#define MI_CACHE_LINE 64 +#if defined(_MSC_VER) +#pragma warning(disable:4127) // suppress constant conditional warning (due to MI_SECURE paths) +#pragma warning(disable:26812) // unscoped enum warning +#define mi_decl_noinline __declspec(noinline) +#define mi_decl_thread __declspec(thread) +#define mi_decl_cache_align __declspec(align(MI_CACHE_LINE)) +#elif (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__clang__) // includes clang and icc +#define mi_decl_noinline __attribute__((noinline)) +#define mi_decl_thread __thread +#define mi_decl_cache_align __attribute__((aligned(MI_CACHE_LINE))) +#else +#define mi_decl_noinline +#define mi_decl_thread __thread // hope for the best :-) +#define mi_decl_cache_align +#endif + +#if defined(__EMSCRIPTEN__) && !defined(__wasi__) +#define __wasi__ +#endif + +#if defined(__cplusplus) +#define mi_decl_externc extern "C" +#else +#define mi_decl_externc +#endif + +// pthreads +#if !defined(_WIN32) && !defined(__wasi__) +#define MI_USE_PTHREADS +#include +#endif + +// "options.c" +void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message); +void _mi_fprintf(mi_output_fun* out, void* arg, const char* fmt, ...); +void _mi_warning_message(const char* fmt, ...); +void _mi_verbose_message(const char* fmt, ...); +void _mi_trace_message(const char* fmt, ...); +void _mi_options_init(void); +void _mi_error_message(int err, const char* fmt, ...); + +// random.c +void _mi_random_init(mi_random_ctx_t* ctx); +void _mi_random_init_weak(mi_random_ctx_t* ctx); +void _mi_random_reinit_if_weak(mi_random_ctx_t * ctx); +void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* new_ctx); +uintptr_t _mi_random_next(mi_random_ctx_t* ctx); +uintptr_t _mi_heap_random_next(mi_heap_t* heap); +uintptr_t _mi_os_random_weak(uintptr_t extra_seed); +static inline uintptr_t _mi_random_shuffle(uintptr_t x); + +// init.c +extern mi_decl_cache_align mi_stats_t _mi_stats_main; +extern mi_decl_cache_align const mi_page_t _mi_page_empty; +bool _mi_is_main_thread(void); +size_t _mi_current_thread_count(void); +bool _mi_preloading(void); // true while the C runtime is not initialized yet +mi_threadid_t _mi_thread_id(void) mi_attr_noexcept; +mi_heap_t* _mi_heap_main_get(void); // statically allocated main backing heap +void _mi_thread_done(mi_heap_t* heap); +void _mi_thread_data_collect(void); + +// os.c +void _mi_os_init(void); // called from process init +void* _mi_os_alloc(size_t size, mi_memid_t* memid, mi_stats_t* stats); +void _mi_os_free(void* p, size_t size, mi_memid_t memid, mi_stats_t* stats); +void _mi_os_free_ex(void* p, size_t size, bool still_committed, mi_memid_t memid, mi_stats_t* stats); + +size_t _mi_os_page_size(void); +size_t _mi_os_good_alloc_size(size_t size); +bool _mi_os_has_overcommit(void); +bool _mi_os_has_virtual_reserve(void); + +bool _mi_os_purge(void* p, size_t size, mi_stats_t* stats); +bool _mi_os_reset(void* addr, size_t size, mi_stats_t* tld_stats); +bool _mi_os_commit(void* p, size_t size, bool* is_zero, mi_stats_t* stats); +bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* stats); +bool _mi_os_protect(void* addr, size_t size); +bool _mi_os_unprotect(void* addr, size_t size); +bool _mi_os_purge(void* p, size_t size, mi_stats_t* stats); +bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, mi_stats_t* stats); + +void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid, mi_stats_t* stats); +void* _mi_os_alloc_aligned_at_offset(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large, mi_memid_t* memid, mi_stats_t* tld_stats); + +void* _mi_os_get_aligned_hint(size_t try_alignment, size_t size); +bool _mi_os_use_large_page(size_t size, size_t alignment); +size_t _mi_os_large_page_size(void); + +void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_secs, size_t* pages_reserved, size_t* psize, mi_memid_t* memid); + +// arena.c +mi_arena_id_t _mi_arena_id_none(void); +void _mi_arena_free(void* p, size_t size, size_t still_committed_size, mi_memid_t memid, mi_stats_t* stats); +void* _mi_arena_alloc(size_t size, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld); +void* _mi_arena_alloc_aligned(size_t size, size_t alignment, size_t align_offset, bool commit, bool allow_large, mi_arena_id_t req_arena_id, mi_memid_t* memid, mi_os_tld_t* tld); +bool _mi_arena_memid_is_suitable(mi_memid_t memid, mi_arena_id_t request_arena_id); +bool _mi_arena_contains(const void* p); +void _mi_arena_collect(bool force_purge, mi_stats_t* stats); +void _mi_arena_unsafe_destroy_all(mi_stats_t* stats); + +// "segment-map.c" +void _mi_segment_map_allocated_at(const mi_segment_t* segment); +void _mi_segment_map_freed_at(const mi_segment_t* segment); + +// "segment.c" +mi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_size, size_t page_alignment, mi_segments_tld_t* tld, mi_os_tld_t* os_tld); +void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld); +void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld); +bool _mi_segment_try_reclaim_abandoned( mi_heap_t* heap, bool try_all, mi_segments_tld_t* tld); +void _mi_segment_thread_collect(mi_segments_tld_t* tld); + +#if MI_HUGE_PAGE_ABANDON +void _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block); +#else +void _mi_segment_huge_page_reset(mi_segment_t* segment, mi_page_t* page, mi_block_t* block); +#endif + +uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size); // page start for any page +void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld); +void _mi_abandoned_await_readers(void); +void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld); + +// "page.c" +void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept mi_attr_malloc; + +void _mi_page_retire(mi_page_t* page) mi_attr_noexcept; // free the page if there are no other pages with many free blocks +void _mi_page_unfull(mi_page_t* page); +void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force); // free the page +void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq); // abandon the page, to be picked up by another thread... +void _mi_heap_delayed_free_all(mi_heap_t* heap); +bool _mi_heap_delayed_free_partial(mi_heap_t* heap); +void _mi_heap_collect_retired(mi_heap_t* heap, bool force); + +void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never); +bool _mi_page_try_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never); +size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append); +void _mi_deferred_free(mi_heap_t* heap, bool force); + +void _mi_page_free_collect(mi_page_t* page,bool force); +void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page); // callback from segments + +size_t _mi_bin_size(uint8_t bin); // for stats +uint8_t _mi_bin(size_t size); // for stats + +// "heap.c" +void _mi_heap_destroy_pages(mi_heap_t* heap); +void _mi_heap_collect_abandon(mi_heap_t* heap); +void _mi_heap_set_default_direct(mi_heap_t* heap); +bool _mi_heap_memid_is_suitable(mi_heap_t* heap, mi_memid_t memid); +void _mi_heap_unsafe_destroy_all(void); + +// "stats.c" +void _mi_stats_done(mi_stats_t* stats); +mi_msecs_t _mi_clock_now(void); +mi_msecs_t _mi_clock_end(mi_msecs_t start); +mi_msecs_t _mi_clock_start(void); + +// "alloc.c" +void* _mi_page_malloc(mi_heap_t* heap, mi_page_t* page, size_t size, bool zero) mi_attr_noexcept; // called from `_mi_malloc_generic` +void* _mi_heap_malloc_zero(mi_heap_t* heap, size_t size, bool zero) mi_attr_noexcept; +void* _mi_heap_malloc_zero_ex(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept; // called from `_mi_heap_malloc_aligned` +void* _mi_heap_realloc_zero(mi_heap_t* heap, void* p, size_t newsize, bool zero) mi_attr_noexcept; +mi_block_t* _mi_page_ptr_unalign(const mi_segment_t* segment, const mi_page_t* page, const void* p); +bool _mi_free_delayed_block(mi_block_t* block); +void _mi_free_generic(const mi_segment_t* segment, mi_page_t* page, bool is_local, void* p) mi_attr_noexcept; // for runtime integration +void _mi_padding_shrink(const mi_page_t* page, const mi_block_t* block, const size_t min_size); + +// option.c, c primitives +char _mi_toupper(char c); +int _mi_strnicmp(const char* s, const char* t, size_t n); +void _mi_strlcpy(char* dest, const char* src, size_t dest_size); +void _mi_strlcat(char* dest, const char* src, size_t dest_size); +size_t _mi_strlen(const char* s); +size_t _mi_strnlen(const char* s, size_t max_len); + + +#if MI_DEBUG>1 +bool _mi_page_is_valid(mi_page_t* page); +#endif + + +// ------------------------------------------------------ +// Branches +// ------------------------------------------------------ + +#if defined(__GNUC__) || defined(__clang__) +#define mi_unlikely(x) (__builtin_expect(!!(x),false)) +#define mi_likely(x) (__builtin_expect(!!(x),true)) +#elif (defined(__cplusplus) && (__cplusplus >= 202002L)) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +#define mi_unlikely(x) (x) [[unlikely]] +#define mi_likely(x) (x) [[likely]] +#else +#define mi_unlikely(x) (x) +#define mi_likely(x) (x) +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + + +/* ----------------------------------------------------------- + Error codes passed to `_mi_fatal_error` + All are recoverable but EFAULT is a serious error and aborts by default in secure mode. + For portability define undefined error codes using common Unix codes: + +----------------------------------------------------------- */ +#include +#ifndef EAGAIN // double free +#define EAGAIN (11) +#endif +#ifndef ENOMEM // out of memory +#define ENOMEM (12) +#endif +#ifndef EFAULT // corrupted free-list or meta-data +#define EFAULT (14) +#endif +#ifndef EINVAL // trying to free an invalid pointer +#define EINVAL (22) +#endif +#ifndef EOVERFLOW // count*size overflow +#define EOVERFLOW (75) +#endif + + +/* ----------------------------------------------------------- + Inlined definitions +----------------------------------------------------------- */ +#define MI_UNUSED(x) (void)(x) +#if (MI_DEBUG>0) +#define MI_UNUSED_RELEASE(x) +#else +#define MI_UNUSED_RELEASE(x) MI_UNUSED(x) +#endif + +#define MI_INIT4(x) x(),x(),x(),x() +#define MI_INIT8(x) MI_INIT4(x),MI_INIT4(x) +#define MI_INIT16(x) MI_INIT8(x),MI_INIT8(x) +#define MI_INIT32(x) MI_INIT16(x),MI_INIT16(x) +#define MI_INIT64(x) MI_INIT32(x),MI_INIT32(x) +#define MI_INIT128(x) MI_INIT64(x),MI_INIT64(x) +#define MI_INIT256(x) MI_INIT128(x),MI_INIT128(x) + + +#include +// initialize a local variable to zero; use memset as compilers optimize constant sized memset's +#define _mi_memzero_var(x) memset(&x,0,sizeof(x)) + +// Is `x` a power of two? (0 is considered a power of two) +static inline bool _mi_is_power_of_two(uintptr_t x) { + return ((x & (x - 1)) == 0); +} + +// Is a pointer aligned? +static inline bool _mi_is_aligned(void* p, size_t alignment) { + mi_assert_internal(alignment != 0); + return (((uintptr_t)p % alignment) == 0); +} + +// Align upwards +static inline uintptr_t _mi_align_up(uintptr_t sz, size_t alignment) { + mi_assert_internal(alignment != 0); + uintptr_t mask = alignment - 1; + if ((alignment & mask) == 0) { // power of two? + return ((sz + mask) & ~mask); + } + else { + return (((sz + mask)/alignment)*alignment); + } +} + +// Align downwards +static inline uintptr_t _mi_align_down(uintptr_t sz, size_t alignment) { + mi_assert_internal(alignment != 0); + uintptr_t mask = alignment - 1; + if ((alignment & mask) == 0) { // power of two? + return (sz & ~mask); + } + else { + return ((sz / alignment) * alignment); + } +} + +// Divide upwards: `s <= _mi_divide_up(s,d)*d < s+d`. +static inline uintptr_t _mi_divide_up(uintptr_t size, size_t divider) { + mi_assert_internal(divider != 0); + return (divider == 0 ? size : ((size + divider - 1) / divider)); +} + +// Is memory zero initialized? +static inline bool mi_mem_is_zero(const void* p, size_t size) { + for (size_t i = 0; i < size; i++) { + if (((uint8_t*)p)[i] != 0) return false; + } + return true; +} + + +// Align a byte size to a size in _machine words_, +// i.e. byte size == `wsize*sizeof(void*)`. +static inline size_t _mi_wsize_from_size(size_t size) { + mi_assert_internal(size <= SIZE_MAX - sizeof(uintptr_t)); + return (size + sizeof(uintptr_t) - 1) / sizeof(uintptr_t); +} + +// Overflow detecting multiply +#if __has_builtin(__builtin_umul_overflow) || (defined(__GNUC__) && (__GNUC__ >= 5)) +#include // UINT_MAX, ULONG_MAX +#if defined(_CLOCK_T) // for Illumos +#undef _CLOCK_T +#endif +static inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) { + #if (SIZE_MAX == ULONG_MAX) + return __builtin_umull_overflow(count, size, (unsigned long *)total); + #elif (SIZE_MAX == UINT_MAX) + return __builtin_umul_overflow(count, size, (unsigned int *)total); + #else + return __builtin_umulll_overflow(count, size, (unsigned long long *)total); + #endif +} +#else /* __builtin_umul_overflow is unavailable */ +static inline bool mi_mul_overflow(size_t count, size_t size, size_t* total) { + #define MI_MUL_NO_OVERFLOW ((size_t)1 << (4*sizeof(size_t))) // sqrt(SIZE_MAX) + *total = count * size; + // note: gcc/clang optimize this to directly check the overflow flag + return ((size >= MI_MUL_NO_OVERFLOW || count >= MI_MUL_NO_OVERFLOW) && size > 0 && (SIZE_MAX / size) < count); +} +#endif + +// Safe multiply `count*size` into `total`; return `true` on overflow. +static inline bool mi_count_size_overflow(size_t count, size_t size, size_t* total) { + if (count==1) { // quick check for the case where count is one (common for C++ allocators) + *total = size; + return false; + } + else if mi_unlikely(mi_mul_overflow(count, size, total)) { + #if MI_DEBUG > 0 + _mi_error_message(EOVERFLOW, "allocation request is too large (%zu * %zu bytes)\n", count, size); + #endif + *total = SIZE_MAX; + return true; + } + else return false; +} + + +/*---------------------------------------------------------------------------------------- + Heap functions +------------------------------------------------------------------------------------------- */ + +extern const mi_heap_t _mi_heap_empty; // read-only empty heap, initial value of the thread local default heap + +static inline bool mi_heap_is_backing(const mi_heap_t* heap) { + return (heap->tld->heap_backing == heap); +} + +static inline bool mi_heap_is_initialized(mi_heap_t* heap) { + mi_assert_internal(heap != NULL); + return (heap != &_mi_heap_empty); +} + +static inline uintptr_t _mi_ptr_cookie(const void* p) { + extern mi_heap_t _mi_heap_main; + mi_assert_internal(_mi_heap_main.cookie != 0); + return ((uintptr_t)p ^ _mi_heap_main.cookie); +} + +/* ----------------------------------------------------------- + Pages +----------------------------------------------------------- */ + +static inline mi_page_t* _mi_heap_get_free_small_page(mi_heap_t* heap, size_t size) { + mi_assert_internal(size <= (MI_SMALL_SIZE_MAX + MI_PADDING_SIZE)); + const size_t idx = _mi_wsize_from_size(size); + mi_assert_internal(idx < MI_PAGES_DIRECT); + return heap->pages_free_direct[idx]; +} + +// Segment that contains the pointer +// Large aligned blocks may be aligned at N*MI_SEGMENT_SIZE (inside a huge segment > MI_SEGMENT_SIZE), +// and we need align "down" to the segment info which is `MI_SEGMENT_SIZE` bytes before it; +// therefore we align one byte before `p`. +static inline mi_segment_t* _mi_ptr_segment(const void* p) { + mi_assert_internal(p != NULL); + return (mi_segment_t*)(((uintptr_t)p - 1) & ~MI_SEGMENT_MASK); +} + +static inline mi_page_t* mi_slice_to_page(mi_slice_t* s) { + mi_assert_internal(s->slice_offset== 0 && s->slice_count > 0); + return (mi_page_t*)(s); +} + +static inline mi_slice_t* mi_page_to_slice(mi_page_t* p) { + mi_assert_internal(p->slice_offset== 0 && p->slice_count > 0); + return (mi_slice_t*)(p); +} + +// Segment belonging to a page +static inline mi_segment_t* _mi_page_segment(const mi_page_t* page) { + mi_segment_t* segment = _mi_ptr_segment(page); + mi_assert_internal(segment == NULL || ((mi_slice_t*)page >= segment->slices && (mi_slice_t*)page < segment->slices + segment->slice_entries)); + return segment; +} + +static inline mi_slice_t* mi_slice_first(const mi_slice_t* slice) { + mi_slice_t* start = (mi_slice_t*)((uint8_t*)slice - slice->slice_offset); + mi_assert_internal(start >= _mi_ptr_segment(slice)->slices); + mi_assert_internal(start->slice_offset == 0); + mi_assert_internal(start + start->slice_count > slice); + return start; +} + +// Get the page containing the pointer (performance critical as it is called in mi_free) +static inline mi_page_t* _mi_segment_page_of(const mi_segment_t* segment, const void* p) { + mi_assert_internal(p > (void*)segment); + ptrdiff_t diff = (uint8_t*)p - (uint8_t*)segment; + mi_assert_internal(diff > 0 && diff <= (ptrdiff_t)MI_SEGMENT_SIZE); + size_t idx = (size_t)diff >> MI_SEGMENT_SLICE_SHIFT; + mi_assert_internal(idx <= segment->slice_entries); + mi_slice_t* slice0 = (mi_slice_t*)&segment->slices[idx]; + mi_slice_t* slice = mi_slice_first(slice0); // adjust to the block that holds the page data + mi_assert_internal(slice->slice_offset == 0); + mi_assert_internal(slice >= segment->slices && slice < segment->slices + segment->slice_entries); + return mi_slice_to_page(slice); +} + +// Quick page start for initialized pages +static inline uint8_t* _mi_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size) { + return _mi_segment_page_start(segment, page, page_size); +} + +// Get the page containing the pointer +static inline mi_page_t* _mi_ptr_page(void* p) { + return _mi_segment_page_of(_mi_ptr_segment(p), p); +} + +// Get the block size of a page (special case for huge objects) +static inline size_t mi_page_block_size(const mi_page_t* page) { + const size_t bsize = page->xblock_size; + mi_assert_internal(bsize > 0); + if mi_likely(bsize < MI_HUGE_BLOCK_SIZE) { + return bsize; + } + else { + size_t psize; + _mi_segment_page_start(_mi_page_segment(page), page, &psize); + return psize; + } +} + +static inline bool mi_page_is_huge(const mi_page_t* page) { + return (_mi_page_segment(page)->kind == MI_SEGMENT_HUGE); +} + +// Get the usable block size of a page without fixed padding. +// This may still include internal padding due to alignment and rounding up size classes. +static inline size_t mi_page_usable_block_size(const mi_page_t* page) { + return mi_page_block_size(page) - MI_PADDING_SIZE; +} + +// size of a segment +static inline size_t mi_segment_size(mi_segment_t* segment) { + return segment->segment_slices * MI_SEGMENT_SLICE_SIZE; +} + +static inline uint8_t* mi_segment_end(mi_segment_t* segment) { + return (uint8_t*)segment + mi_segment_size(segment); +} + +// Thread free access +static inline mi_block_t* mi_page_thread_free(const mi_page_t* page) { + return (mi_block_t*)(mi_atomic_load_relaxed(&((mi_page_t*)page)->xthread_free) & ~3); +} + +static inline mi_delayed_t mi_page_thread_free_flag(const mi_page_t* page) { + return (mi_delayed_t)(mi_atomic_load_relaxed(&((mi_page_t*)page)->xthread_free) & 3); +} + +// Heap access +static inline mi_heap_t* mi_page_heap(const mi_page_t* page) { + return (mi_heap_t*)(mi_atomic_load_relaxed(&((mi_page_t*)page)->xheap)); +} + +static inline void mi_page_set_heap(mi_page_t* page, mi_heap_t* heap) { + mi_assert_internal(mi_page_thread_free_flag(page) != MI_DELAYED_FREEING); + mi_atomic_store_release(&page->xheap,(uintptr_t)heap); +} + +// Thread free flag helpers +static inline mi_block_t* mi_tf_block(mi_thread_free_t tf) { + return (mi_block_t*)(tf & ~0x03); +} +static inline mi_delayed_t mi_tf_delayed(mi_thread_free_t tf) { + return (mi_delayed_t)(tf & 0x03); +} +static inline mi_thread_free_t mi_tf_make(mi_block_t* block, mi_delayed_t delayed) { + return (mi_thread_free_t)((uintptr_t)block | (uintptr_t)delayed); +} +static inline mi_thread_free_t mi_tf_set_delayed(mi_thread_free_t tf, mi_delayed_t delayed) { + return mi_tf_make(mi_tf_block(tf),delayed); +} +static inline mi_thread_free_t mi_tf_set_block(mi_thread_free_t tf, mi_block_t* block) { + return mi_tf_make(block, mi_tf_delayed(tf)); +} + +// are all blocks in a page freed? +// note: needs up-to-date used count, (as the `xthread_free` list may not be empty). see `_mi_page_collect_free`. +static inline bool mi_page_all_free(const mi_page_t* page) { + mi_assert_internal(page != NULL); + return (page->used == 0); +} + +// are there any available blocks? +static inline bool mi_page_has_any_available(const mi_page_t* page) { + mi_assert_internal(page != NULL && page->reserved > 0); + return (page->used < page->reserved || (mi_page_thread_free(page) != NULL)); +} + +// are there immediately available blocks, i.e. blocks available on the free list. +static inline bool mi_page_immediate_available(const mi_page_t* page) { + mi_assert_internal(page != NULL); + return (page->free != NULL); +} + +// is more than 7/8th of a page in use? +static inline bool mi_page_mostly_used(const mi_page_t* page) { + if (page==NULL) return true; + uint16_t frac = page->reserved / 8U; + return (page->reserved - page->used <= frac); +} + +static inline mi_page_queue_t* mi_page_queue(const mi_heap_t* heap, size_t size) { + return &((mi_heap_t*)heap)->pages[_mi_bin(size)]; +} + + + +//----------------------------------------------------------- +// Page flags +//----------------------------------------------------------- +static inline bool mi_page_is_in_full(const mi_page_t* page) { + return page->flags.x.in_full; +} + +static inline void mi_page_set_in_full(mi_page_t* page, bool in_full) { + page->flags.x.in_full = in_full; +} + +static inline bool mi_page_has_aligned(const mi_page_t* page) { + return page->flags.x.has_aligned; +} + +static inline void mi_page_set_has_aligned(mi_page_t* page, bool has_aligned) { + page->flags.x.has_aligned = has_aligned; +} + + +/* ------------------------------------------------------------------- +Encoding/Decoding the free list next pointers + +This is to protect against buffer overflow exploits where the +free list is mutated. Many hardened allocators xor the next pointer `p` +with a secret key `k1`, as `p^k1`. This prevents overwriting with known +values but might be still too weak: if the attacker can guess +the pointer `p` this can reveal `k1` (since `p^k1^p == k1`). +Moreover, if multiple blocks can be read as well, the attacker can +xor both as `(p1^k1) ^ (p2^k1) == p1^p2` which may reveal a lot +about the pointers (and subsequently `k1`). + +Instead mimalloc uses an extra key `k2` and encodes as `((p^k2)<<> (MI_INTPTR_BITS - shift)))); +} +static inline uintptr_t mi_rotr(uintptr_t x, uintptr_t shift) { + shift %= MI_INTPTR_BITS; + return (shift==0 ? x : ((x >> shift) | (x << (MI_INTPTR_BITS - shift)))); +} + +static inline void* mi_ptr_decode(const void* null, const mi_encoded_t x, const uintptr_t* keys) { + void* p = (void*)(mi_rotr(x - keys[0], keys[0]) ^ keys[1]); + return (p==null ? NULL : p); +} + +static inline mi_encoded_t mi_ptr_encode(const void* null, const void* p, const uintptr_t* keys) { + uintptr_t x = (uintptr_t)(p==NULL ? null : p); + return mi_rotl(x ^ keys[1], keys[0]) + keys[0]; +} + +static inline mi_block_t* mi_block_nextx( const void* null, const mi_block_t* block, const uintptr_t* keys ) { + mi_track_mem_defined(block,sizeof(mi_block_t)); + mi_block_t* next; + #ifdef MI_ENCODE_FREELIST + next = (mi_block_t*)mi_ptr_decode(null, block->next, keys); + #else + MI_UNUSED(keys); MI_UNUSED(null); + next = (mi_block_t*)block->next; + #endif + mi_track_mem_noaccess(block,sizeof(mi_block_t)); + return next; +} + +static inline void mi_block_set_nextx(const void* null, mi_block_t* block, const mi_block_t* next, const uintptr_t* keys) { + mi_track_mem_undefined(block,sizeof(mi_block_t)); + #ifdef MI_ENCODE_FREELIST + block->next = mi_ptr_encode(null, next, keys); + #else + MI_UNUSED(keys); MI_UNUSED(null); + block->next = (mi_encoded_t)next; + #endif + mi_track_mem_noaccess(block,sizeof(mi_block_t)); +} + +static inline mi_block_t* mi_block_next(const mi_page_t* page, const mi_block_t* block) { + #ifdef MI_ENCODE_FREELIST + mi_block_t* next = mi_block_nextx(page,block,page->keys); + // check for free list corruption: is `next` at least in the same page? + // TODO: check if `next` is `page->block_size` aligned? + if mi_unlikely(next!=NULL && !mi_is_in_same_page(block, next)) { + _mi_error_message(EFAULT, "corrupted free list entry of size %zub at %p: value 0x%zx\n", mi_page_block_size(page), block, (uintptr_t)next); + next = NULL; + } + return next; + #else + MI_UNUSED(page); + return mi_block_nextx(page,block,NULL); + #endif +} + +static inline void mi_block_set_next(const mi_page_t* page, mi_block_t* block, const mi_block_t* next) { + #ifdef MI_ENCODE_FREELIST + mi_block_set_nextx(page,block,next, page->keys); + #else + MI_UNUSED(page); + mi_block_set_nextx(page,block,next,NULL); + #endif +} + + +// ------------------------------------------------------------------- +// commit mask +// ------------------------------------------------------------------- + +static inline void mi_commit_mask_create_empty(mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + cm->mask[i] = 0; + } +} + +static inline void mi_commit_mask_create_full(mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + cm->mask[i] = ~((size_t)0); + } +} + +static inline bool mi_commit_mask_is_empty(const mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + if (cm->mask[i] != 0) return false; + } + return true; +} + +static inline bool mi_commit_mask_is_full(const mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + if (cm->mask[i] != ~((size_t)0)) return false; + } + return true; +} + +// defined in `segment.c`: +size_t _mi_commit_mask_committed_size(const mi_commit_mask_t* cm, size_t total); +size_t _mi_commit_mask_next_run(const mi_commit_mask_t* cm, size_t* idx); + +#define mi_commit_mask_foreach(cm,idx,count) \ + idx = 0; \ + while ((count = _mi_commit_mask_next_run(cm,&idx)) > 0) { + +#define mi_commit_mask_foreach_end() \ + idx += count; \ + } + + + +/* ----------------------------------------------------------- + memory id's +----------------------------------------------------------- */ + +static inline mi_memid_t _mi_memid_create(mi_memkind_t memkind) { + mi_memid_t memid; + _mi_memzero_var(memid); + memid.memkind = memkind; + return memid; +} + +static inline mi_memid_t _mi_memid_none(void) { + return _mi_memid_create(MI_MEM_NONE); +} + +static inline mi_memid_t _mi_memid_create_os(bool committed, bool is_zero, bool is_large) { + mi_memid_t memid = _mi_memid_create(MI_MEM_OS); + memid.initially_committed = committed; + memid.initially_zero = is_zero; + memid.is_pinned = is_large; + return memid; +} + + +// ------------------------------------------------------------------- +// Fast "random" shuffle +// ------------------------------------------------------------------- + +static inline uintptr_t _mi_random_shuffle(uintptr_t x) { + if (x==0) { x = 17; } // ensure we don't get stuck in generating zeros +#if (MI_INTPTR_SIZE==8) + // by Sebastiano Vigna, see: + x ^= x >> 30; + x *= 0xbf58476d1ce4e5b9UL; + x ^= x >> 27; + x *= 0x94d049bb133111ebUL; + x ^= x >> 31; +#elif (MI_INTPTR_SIZE==4) + // by Chris Wellons, see: + x ^= x >> 16; + x *= 0x7feb352dUL; + x ^= x >> 15; + x *= 0x846ca68bUL; + x ^= x >> 16; +#endif + return x; +} + +// ------------------------------------------------------------------- +// Optimize numa node access for the common case (= one node) +// ------------------------------------------------------------------- + +int _mi_os_numa_node_get(mi_os_tld_t* tld); +size_t _mi_os_numa_node_count_get(void); + +extern _Atomic(size_t) _mi_numa_node_count; +static inline int _mi_os_numa_node(mi_os_tld_t* tld) { + if mi_likely(mi_atomic_load_relaxed(&_mi_numa_node_count) == 1) { return 0; } + else return _mi_os_numa_node_get(tld); +} +static inline size_t _mi_os_numa_node_count(void) { + const size_t count = mi_atomic_load_relaxed(&_mi_numa_node_count); + if mi_likely(count > 0) { return count; } + else return _mi_os_numa_node_count_get(); +} + + + +// ----------------------------------------------------------------------- +// Count bits: trailing or leading zeros (with MI_INTPTR_BITS on all zero) +// ----------------------------------------------------------------------- + +#if defined(__GNUC__) + +#include // LONG_MAX +#define MI_HAVE_FAST_BITSCAN +static inline size_t mi_clz(uintptr_t x) { + if (x==0) return MI_INTPTR_BITS; +#if (INTPTR_MAX == LONG_MAX) + return __builtin_clzl(x); +#else + return __builtin_clzll(x); +#endif +} +static inline size_t mi_ctz(uintptr_t x) { + if (x==0) return MI_INTPTR_BITS; +#if (INTPTR_MAX == LONG_MAX) + return __builtin_ctzl(x); +#else + return __builtin_ctzll(x); +#endif +} + +#elif defined(_MSC_VER) + +#include // LONG_MAX +#include // BitScanReverse64 +#define MI_HAVE_FAST_BITSCAN +static inline size_t mi_clz(uintptr_t x) { + if (x==0) return MI_INTPTR_BITS; + unsigned long idx; +#if (INTPTR_MAX == LONG_MAX) + _BitScanReverse(&idx, x); +#else + _BitScanReverse64(&idx, x); +#endif + return ((MI_INTPTR_BITS - 1) - idx); +} +static inline size_t mi_ctz(uintptr_t x) { + if (x==0) return MI_INTPTR_BITS; + unsigned long idx; +#if (INTPTR_MAX == LONG_MAX) + _BitScanForward(&idx, x); +#else + _BitScanForward64(&idx, x); +#endif + return idx; +} + +#else +static inline size_t mi_ctz32(uint32_t x) { + // de Bruijn multiplication, see + static const unsigned char debruijn[32] = { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9 + }; + if (x==0) return 32; + return debruijn[((x & -(int32_t)x) * 0x077CB531UL) >> 27]; +} +static inline size_t mi_clz32(uint32_t x) { + // de Bruijn multiplication, see + static const uint8_t debruijn[32] = { + 31, 22, 30, 21, 18, 10, 29, 2, 20, 17, 15, 13, 9, 6, 28, 1, + 23, 19, 11, 3, 16, 14, 7, 24, 12, 4, 8, 25, 5, 26, 27, 0 + }; + if (x==0) return 32; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return debruijn[(uint32_t)(x * 0x07C4ACDDUL) >> 27]; +} + +static inline size_t mi_clz(uintptr_t x) { + if (x==0) return MI_INTPTR_BITS; +#if (MI_INTPTR_BITS <= 32) + return mi_clz32((uint32_t)x); +#else + size_t count = mi_clz32((uint32_t)(x >> 32)); + if (count < 32) return count; + return (32 + mi_clz32((uint32_t)x)); +#endif +} +static inline size_t mi_ctz(uintptr_t x) { + if (x==0) return MI_INTPTR_BITS; +#if (MI_INTPTR_BITS <= 32) + return mi_ctz32((uint32_t)x); +#else + size_t count = mi_ctz32((uint32_t)x); + if (count < 32) return count; + return (32 + mi_ctz32((uint32_t)(x>>32))); +#endif +} + +#endif + +// "bit scan reverse": Return index of the highest bit (or MI_INTPTR_BITS if `x` is zero) +static inline size_t mi_bsr(uintptr_t x) { + return (x==0 ? MI_INTPTR_BITS : MI_INTPTR_BITS - 1 - mi_clz(x)); +} + + +// --------------------------------------------------------------------------------- +// Provide our own `_mi_memcpy` for potential performance optimizations. +// +// For now, only on Windows with msvc/clang-cl we optimize to `rep movsb` if +// we happen to run on x86/x64 cpu's that have "fast short rep movsb" (FSRM) support +// (AMD Zen3+ (~2020) or Intel Ice Lake+ (~2017). See also issue #201 and pr #253. +// --------------------------------------------------------------------------------- + +#if !MI_TRACK_ENABLED && defined(_WIN32) && (defined(_M_IX86) || defined(_M_X64)) +#include +extern bool _mi_cpu_has_fsrm; +static inline void _mi_memcpy(void* dst, const void* src, size_t n) { + if (_mi_cpu_has_fsrm) { + __movsb((unsigned char*)dst, (const unsigned char*)src, n); + } + else { + memcpy(dst, src, n); + } +} +static inline void _mi_memzero(void* dst, size_t n) { + if (_mi_cpu_has_fsrm) { + __stosb((unsigned char*)dst, 0, n); + } + else { + memset(dst, 0, n); + } +} +#else +static inline void _mi_memcpy(void* dst, const void* src, size_t n) { + memcpy(dst, src, n); +} +static inline void _mi_memzero(void* dst, size_t n) { + memset(dst, 0, n); +} +#endif + +// ------------------------------------------------------------------------------- +// The `_mi_memcpy_aligned` can be used if the pointers are machine-word aligned +// This is used for example in `mi_realloc`. +// ------------------------------------------------------------------------------- + +#if (defined(__GNUC__) && (__GNUC__ >= 4)) || defined(__clang__) +// On GCC/CLang we provide a hint that the pointers are word aligned. +static inline void _mi_memcpy_aligned(void* dst, const void* src, size_t n) { + mi_assert_internal(((uintptr_t)dst % MI_INTPTR_SIZE == 0) && ((uintptr_t)src % MI_INTPTR_SIZE == 0)); + void* adst = __builtin_assume_aligned(dst, MI_INTPTR_SIZE); + const void* asrc = __builtin_assume_aligned(src, MI_INTPTR_SIZE); + _mi_memcpy(adst, asrc, n); +} + +static inline void _mi_memzero_aligned(void* dst, size_t n) { + mi_assert_internal((uintptr_t)dst % MI_INTPTR_SIZE == 0); + void* adst = __builtin_assume_aligned(dst, MI_INTPTR_SIZE); + _mi_memzero(adst, n); +} +#else +// Default fallback on `_mi_memcpy` +static inline void _mi_memcpy_aligned(void* dst, const void* src, size_t n) { + mi_assert_internal(((uintptr_t)dst % MI_INTPTR_SIZE == 0) && ((uintptr_t)src % MI_INTPTR_SIZE == 0)); + _mi_memcpy(dst, src, n); +} + +static inline void _mi_memzero_aligned(void* dst, size_t n) { + mi_assert_internal((uintptr_t)dst % MI_INTPTR_SIZE == 0); + _mi_memzero(dst, n); +} +#endif + + +#endif diff --git a/compat/mimalloc/mimalloc/prim.h b/compat/mimalloc/mimalloc/prim.h new file mode 100644 index 00000000000000..1e55cb5f8802d7 --- /dev/null +++ b/compat/mimalloc/mimalloc/prim.h @@ -0,0 +1,323 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_PRIM_H +#define MIMALLOC_PRIM_H + + +// -------------------------------------------------------------------------- +// This file specifies the primitive portability API. +// Each OS/host needs to implement these primitives, see `src/prim` +// for implementations on Window, macOS, WASI, and Linux/Unix. +// +// note: on all primitive functions, we always have result parameters != NUL, and: +// addr != NULL and page aligned +// size > 0 and page aligned +// return value is an error code an int where 0 is success. +// -------------------------------------------------------------------------- + +// OS memory configuration +typedef struct mi_os_mem_config_s { + size_t page_size; // 4KiB + size_t large_page_size; // 2MiB + size_t alloc_granularity; // smallest allocation size (on Windows 64KiB) + bool has_overcommit; // can we reserve more memory than can be actually committed? + bool must_free_whole; // must allocated blocks be freed as a whole (false for mmap, true for VirtualAlloc) + bool has_virtual_reserve; // supports virtual address space reservation? (if true we can reserve virtual address space without using commit or physical memory) +} mi_os_mem_config_t; + +// Initialize +void _mi_prim_mem_init( mi_os_mem_config_t* config ); + +// Free OS memory +int _mi_prim_free(void* addr, size_t size ); + +// Allocate OS memory. Return NULL on error. +// The `try_alignment` is just a hint and the returned pointer does not have to be aligned. +// If `commit` is false, the virtual memory range only needs to be reserved (with no access) +// which will later be committed explicitly using `_mi_prim_commit`. +// `is_zero` is set to true if the memory was zero initialized (as on most OS's) +// pre: !commit => !allow_large +// try_alignment >= _mi_os_page_size() and a power of 2 +int _mi_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr); + +// Commit memory. Returns error code or 0 on success. +// For example, on Linux this would make the memory PROT_READ|PROT_WRITE. +// `is_zero` is set to true if the memory was zero initialized (e.g. on Windows) +int _mi_prim_commit(void* addr, size_t size, bool* is_zero); + +// Decommit memory. Returns error code or 0 on success. The `needs_recommit` result is true +// if the memory would need to be re-committed. For example, on Windows this is always true, +// but on Linux we could use MADV_DONTNEED to decommit which does not need a recommit. +// pre: needs_recommit != NULL +int _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit); + +// Reset memory. The range keeps being accessible but the content might be reset. +// Returns error code or 0 on success. +int _mi_prim_reset(void* addr, size_t size); + +// Protect memory. Returns error code or 0 on success. +int _mi_prim_protect(void* addr, size_t size, bool protect); + +// Allocate huge (1GiB) pages possibly associated with a NUMA node. +// `is_zero` is set to true if the memory was zero initialized (as on most OS's) +// pre: size > 0 and a multiple of 1GiB. +// numa_node is either negative (don't care), or a numa node number. +int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr); + +// Return the current NUMA node +size_t _mi_prim_numa_node(void); + +// Return the number of logical NUMA nodes +size_t _mi_prim_numa_node_count(void); + +// Clock ticks +mi_msecs_t _mi_prim_clock_now(void); + +// Return process information (only for statistics) +typedef struct mi_process_info_s { + mi_msecs_t elapsed; + mi_msecs_t utime; + mi_msecs_t stime; + size_t current_rss; + size_t peak_rss; + size_t current_commit; + size_t peak_commit; + size_t page_faults; +} mi_process_info_t; + +void _mi_prim_process_info(mi_process_info_t* pinfo); + +// Default stderr output. (only for warnings etc. with verbose enabled) +// msg != NULL && _mi_strlen(msg) > 0 +void _mi_prim_out_stderr( const char* msg ); + +// Get an environment variable. (only for options) +// name != NULL, result != NULL, result_size >= 64 +bool _mi_prim_getenv(const char* name, char* result, size_t result_size); + + +// Fill a buffer with strong randomness; return `false` on error or if +// there is no strong randomization available. +bool _mi_prim_random_buf(void* buf, size_t buf_len); + +// Called on the first thread start, and should ensure `_mi_thread_done` is called on thread termination. +void _mi_prim_thread_init_auto_done(void); + +// Called on process exit and may take action to clean up resources associated with the thread auto done. +void _mi_prim_thread_done_auto_done(void); + +// Called when the default heap for a thread changes +void _mi_prim_thread_associate_default_heap(mi_heap_t* heap); + + +//------------------------------------------------------------------- +// Thread id: `_mi_prim_thread_id()` +// +// Getting the thread id should be performant as it is called in the +// fast path of `_mi_free` and we specialize for various platforms as +// inlined definitions. Regular code should call `init.c:_mi_thread_id()`. +// We only require _mi_prim_thread_id() to return a unique id +// for each thread (unequal to zero). +//------------------------------------------------------------------- + +// defined in `init.c`; do not use these directly +extern mi_decl_thread mi_heap_t* _mi_heap_default; // default heap to allocate from +extern bool _mi_process_is_initialized; // has mi_process_init been called? + +static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept; + +#if defined(_WIN32) + +#define WIN32_LEAN_AND_MEAN +#include +static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept { + // Windows: works on Intel and ARM in both 32- and 64-bit + return (uintptr_t)NtCurrentTeb(); +} + +// We use assembly for a fast thread id on the main platforms. The TLS layout depends on +// both the OS and libc implementation so we use specific tests for each main platform. +// If you test on another platform and it works please send a PR :-) +// see also https://akkadia.org/drepper/tls.pdf for more info on the TLS register. +#elif defined(__GNUC__) && ( \ + (defined(__GLIBC__) && (defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__))) \ + || (defined(__APPLE__) && (defined(__x86_64__) || defined(__aarch64__))) \ + || (defined(__BIONIC__) && (defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__))) \ + || (defined(__FreeBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \ + || (defined(__OpenBSD__) && (defined(__x86_64__) || defined(__i386__) || defined(__aarch64__))) \ + ) + +static inline void* mi_prim_tls_slot(size_t slot) mi_attr_noexcept { + void* res; + const size_t ofs = (slot*sizeof(void*)); + #if defined(__i386__) + __asm__("movl %%gs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86 32-bit always uses GS + #elif defined(__APPLE__) && defined(__x86_64__) + __asm__("movq %%gs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86_64 macOSX uses GS + #elif defined(__x86_64__) && (MI_INTPTR_SIZE==4) + __asm__("movl %%fs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x32 ABI + #elif defined(__x86_64__) + __asm__("movq %%fs:%1, %0" : "=r" (res) : "m" (*((void**)ofs)) : ); // x86_64 Linux, BSD uses FS + #elif defined(__arm__) + void** tcb; MI_UNUSED(ofs); + __asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + res = tcb[slot]; + #elif defined(__aarch64__) + void** tcb; MI_UNUSED(ofs); + #if defined(__APPLE__) // M1, issue #343 + __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb)); + #else + __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); + #endif + res = tcb[slot]; + #endif + return res; +} + +// setting a tls slot is only used on macOS for now +static inline void mi_prim_tls_slot_set(size_t slot, void* value) mi_attr_noexcept { + const size_t ofs = (slot*sizeof(void*)); + #if defined(__i386__) + __asm__("movl %1,%%gs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // 32-bit always uses GS + #elif defined(__APPLE__) && defined(__x86_64__) + __asm__("movq %1,%%gs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x86_64 macOS uses GS + #elif defined(__x86_64__) && (MI_INTPTR_SIZE==4) + __asm__("movl %1,%%fs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x32 ABI + #elif defined(__x86_64__) + __asm__("movq %1,%%fs:%0" : "=m" (*((void**)ofs)) : "rn" (value) : ); // x86_64 Linux, BSD uses FS + #elif defined(__arm__) + void** tcb; MI_UNUSED(ofs); + __asm__ volatile ("mrc p15, 0, %0, c13, c0, 3\nbic %0, %0, #3" : "=r" (tcb)); + tcb[slot] = value; + #elif defined(__aarch64__) + void** tcb; MI_UNUSED(ofs); + #if defined(__APPLE__) // M1, issue #343 + __asm__ volatile ("mrs %0, tpidrro_el0\nbic %0, %0, #7" : "=r" (tcb)); + #else + __asm__ volatile ("mrs %0, tpidr_el0" : "=r" (tcb)); + #endif + tcb[slot] = value; + #endif +} + +static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept { + #if defined(__BIONIC__) + // issue #384, #495: on the Bionic libc (Android), slot 1 is the thread id + // see: https://github.com/aosp-mirror/platform_bionic/blob/c44b1d0676ded732df4b3b21c5f798eacae93228/libc/platform/bionic/tls_defines.h#L86 + return (uintptr_t)mi_prim_tls_slot(1); + #else + // in all our other targets, slot 0 is the thread id + // glibc: https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/x86_64/nptl/tls.h + // apple: https://github.com/apple/darwin-xnu/blob/main/libsyscall/os/tsd.h#L36 + return (uintptr_t)mi_prim_tls_slot(0); + #endif +} + +#else + +// otherwise use portable C, taking the address of a thread local variable (this is still very fast on most platforms). +static inline mi_threadid_t _mi_prim_thread_id(void) mi_attr_noexcept { + return (uintptr_t)&_mi_heap_default; +} + +#endif + + + +/* ---------------------------------------------------------------------------------------- +The thread local default heap: `_mi_prim_get_default_heap()` +This is inlined here as it is on the fast path for allocation functions. + +On most platforms (Windows, Linux, FreeBSD, NetBSD, etc), this just returns a +__thread local variable (`_mi_heap_default`). With the initial-exec TLS model this ensures +that the storage will always be available (allocated on the thread stacks). + +On some platforms though we cannot use that when overriding `malloc` since the underlying +TLS implementation (or the loader) will call itself `malloc` on a first access and recurse. +We try to circumvent this in an efficient way: +- macOSX : we use an unused TLS slot from the OS allocated slots (MI_TLS_SLOT). On OSX, the + loader itself calls `malloc` even before the modules are initialized. +- OpenBSD: we use an unused slot from the pthread block (MI_TLS_PTHREAD_SLOT_OFS). +- DragonFly: defaults are working but seem slow compared to freeBSD (see PR #323) +------------------------------------------------------------------------------------------- */ + +static inline mi_heap_t* mi_prim_get_default_heap(void); + +#if defined(MI_MALLOC_OVERRIDE) +#if defined(__APPLE__) // macOS + #define MI_TLS_SLOT 89 // seems unused? + // #define MI_TLS_RECURSE_GUARD 1 + // other possible unused ones are 9, 29, __PTK_FRAMEWORK_JAVASCRIPTCORE_KEY4 (94), __PTK_FRAMEWORK_GC_KEY9 (112) and __PTK_FRAMEWORK_OLDGC_KEY9 (89) + // see +#elif defined(__OpenBSD__) + // use end bytes of a name; goes wrong if anyone uses names > 23 characters (ptrhread specifies 16) + // see + #define MI_TLS_PTHREAD_SLOT_OFS (6*sizeof(int) + 4*sizeof(void*) + 24) + // #elif defined(__DragonFly__) + // #warning "mimalloc is not working correctly on DragonFly yet." + // #define MI_TLS_PTHREAD_SLOT_OFS (4 + 1*sizeof(void*)) // offset `uniqueid` (also used by gdb?) +#elif defined(__ANDROID__) + // See issue #381 + #define MI_TLS_PTHREAD +#endif +#endif + + +#if defined(MI_TLS_SLOT) + +static inline mi_heap_t* mi_prim_get_default_heap(void) { + mi_heap_t* heap = (mi_heap_t*)mi_prim_tls_slot(MI_TLS_SLOT); + if mi_unlikely(heap == NULL) { + #ifdef __GNUC__ + __asm(""); // prevent conditional load of the address of _mi_heap_empty + #endif + heap = (mi_heap_t*)&_mi_heap_empty; + } + return heap; +} + +#elif defined(MI_TLS_PTHREAD_SLOT_OFS) + +static inline mi_heap_t** mi_prim_tls_pthread_heap_slot(void) { + pthread_t self = pthread_self(); + #if defined(__DragonFly__) + if (self==NULL) return NULL; + #endif + return (mi_heap_t**)((uint8_t*)self + MI_TLS_PTHREAD_SLOT_OFS); +} + +static inline mi_heap_t* mi_prim_get_default_heap(void) { + mi_heap_t** pheap = mi_prim_tls_pthread_heap_slot(); + if mi_unlikely(pheap == NULL) return _mi_heap_main_get(); + mi_heap_t* heap = *pheap; + if mi_unlikely(heap == NULL) return (mi_heap_t*)&_mi_heap_empty; + return heap; +} + +#elif defined(MI_TLS_PTHREAD) + +extern pthread_key_t _mi_heap_default_key; +static inline mi_heap_t* mi_prim_get_default_heap(void) { + mi_heap_t* heap = (mi_unlikely(_mi_heap_default_key == (pthread_key_t)(-1)) ? _mi_heap_main_get() : (mi_heap_t*)pthread_getspecific(_mi_heap_default_key)); + return (mi_unlikely(heap == NULL) ? (mi_heap_t*)&_mi_heap_empty : heap); +} + +#else // default using a thread local variable; used on most platforms. + +static inline mi_heap_t* mi_prim_get_default_heap(void) { + #if defined(MI_TLS_RECURSE_GUARD) + if (mi_unlikely(!_mi_process_is_initialized)) return _mi_heap_main_get(); + #endif + return _mi_heap_default; +} + +#endif // mi_prim_get_default_heap() + + + +#endif // MIMALLOC_PRIM_H diff --git a/compat/mimalloc/mimalloc/track.h b/compat/mimalloc/mimalloc/track.h new file mode 100644 index 00000000000000..fa1a048d846a9c --- /dev/null +++ b/compat/mimalloc/mimalloc/track.h @@ -0,0 +1,147 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_TRACK_H +#define MIMALLOC_TRACK_H + +/* ------------------------------------------------------------------------------------------------------ +Track memory ranges with macros for tools like Valgrind address sanitizer, or other memory checkers. +These can be defined for tracking allocation: + + #define mi_track_malloc_size(p,reqsize,size,zero) + #define mi_track_free_size(p,_size) + +The macros are set up such that the size passed to `mi_track_free_size` +always matches the size of `mi_track_malloc_size`. (currently, `size == mi_usable_size(p)`). +The `reqsize` is what the user requested, and `size >= reqsize`. +The `size` is either byte precise (and `size==reqsize`) if `MI_PADDING` is enabled, +or otherwise it is the usable block size which may be larger than the original request. +Use `_mi_block_size_of(void* p)` to get the full block size that was allocated (including padding etc). +The `zero` parameter is `true` if the allocated block is zero initialized. + +Optional: + + #define mi_track_align(p,alignedp,offset,size) + #define mi_track_resize(p,oldsize,newsize) + #define mi_track_init() + +The `mi_track_align` is called right after a `mi_track_malloc` for aligned pointers in a block. +The corresponding `mi_track_free` still uses the block start pointer and original size (corresponding to the `mi_track_malloc`). +The `mi_track_resize` is currently unused but could be called on reallocations within a block. +`mi_track_init` is called at program start. + +The following macros are for tools like asan and valgrind to track whether memory is +defined, undefined, or not accessible at all: + + #define mi_track_mem_defined(p,size) + #define mi_track_mem_undefined(p,size) + #define mi_track_mem_noaccess(p,size) + +-------------------------------------------------------------------------------------------------------*/ + +#if MI_TRACK_VALGRIND +// valgrind tool + +#define MI_TRACK_ENABLED 1 +#define MI_TRACK_HEAP_DESTROY 1 // track free of individual blocks on heap_destroy +#define MI_TRACK_TOOL "valgrind" + +#include +#include + +#define mi_track_malloc_size(p,reqsize,size,zero) VALGRIND_MALLOCLIKE_BLOCK(p,size,MI_PADDING_SIZE /*red zone*/,zero) +#define mi_track_free_size(p,_size) VALGRIND_FREELIKE_BLOCK(p,MI_PADDING_SIZE /*red zone*/) +#define mi_track_resize(p,oldsize,newsize) VALGRIND_RESIZEINPLACE_BLOCK(p,oldsize,newsize,MI_PADDING_SIZE /*red zone*/) +#define mi_track_mem_defined(p,size) VALGRIND_MAKE_MEM_DEFINED(p,size) +#define mi_track_mem_undefined(p,size) VALGRIND_MAKE_MEM_UNDEFINED(p,size) +#define mi_track_mem_noaccess(p,size) VALGRIND_MAKE_MEM_NOACCESS(p,size) + +#elif MI_TRACK_ASAN +// address sanitizer + +#define MI_TRACK_ENABLED 1 +#define MI_TRACK_HEAP_DESTROY 0 +#define MI_TRACK_TOOL "asan" + +#include + +#define mi_track_malloc_size(p,reqsize,size,zero) ASAN_UNPOISON_MEMORY_REGION(p,size) +#define mi_track_free_size(p,size) ASAN_POISON_MEMORY_REGION(p,size) +#define mi_track_mem_defined(p,size) ASAN_UNPOISON_MEMORY_REGION(p,size) +#define mi_track_mem_undefined(p,size) ASAN_UNPOISON_MEMORY_REGION(p,size) +#define mi_track_mem_noaccess(p,size) ASAN_POISON_MEMORY_REGION(p,size) + +#elif MI_TRACK_ETW +// windows event tracing + +#define MI_TRACK_ENABLED 1 +#define MI_TRACK_HEAP_DESTROY 1 +#define MI_TRACK_TOOL "ETW" + +#define WIN32_LEAN_AND_MEAN +#include +#include "../src/prim/windows/etw.h" + +#define mi_track_init() EventRegistermicrosoft_windows_mimalloc(); +#define mi_track_malloc_size(p,reqsize,size,zero) EventWriteETW_MI_ALLOC((UINT64)(p), size) +#define mi_track_free_size(p,size) EventWriteETW_MI_FREE((UINT64)(p), size) + +#else +// no tracking + +#define MI_TRACK_ENABLED 0 +#define MI_TRACK_HEAP_DESTROY 0 +#define MI_TRACK_TOOL "none" + +#define mi_track_malloc_size(p,reqsize,size,zero) +#define mi_track_free_size(p,_size) + +#endif + +// ------------------- +// Utility definitions + +#ifndef mi_track_resize +#define mi_track_resize(p,oldsize,newsize) mi_track_free_size(p,oldsize); mi_track_malloc(p,newsize,false) +#endif + +#ifndef mi_track_align +#define mi_track_align(p,alignedp,offset,size) mi_track_mem_noaccess(p,offset) +#endif + +#ifndef mi_track_init +#define mi_track_init() +#endif + +#ifndef mi_track_mem_defined +#define mi_track_mem_defined(p,size) +#endif + +#ifndef mi_track_mem_undefined +#define mi_track_mem_undefined(p,size) +#endif + +#ifndef mi_track_mem_noaccess +#define mi_track_mem_noaccess(p,size) +#endif + + +#if MI_PADDING +#define mi_track_malloc(p,reqsize,zero) \ + if ((p)!=NULL) { \ + mi_assert_internal(mi_usable_size(p)==(reqsize)); \ + mi_track_malloc_size(p,reqsize,reqsize,zero); \ + } +#else +#define mi_track_malloc(p,reqsize,zero) \ + if ((p)!=NULL) { \ + mi_assert_internal(mi_usable_size(p)>=(reqsize)); \ + mi_track_malloc_size(p,reqsize,mi_usable_size(p),zero); \ + } +#endif + +#endif diff --git a/compat/mimalloc/mimalloc/types.h b/compat/mimalloc/mimalloc/types.h new file mode 100644 index 00000000000000..7616f37e4b978f --- /dev/null +++ b/compat/mimalloc/mimalloc/types.h @@ -0,0 +1,670 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#pragma once +#ifndef MIMALLOC_TYPES_H +#define MIMALLOC_TYPES_H + +// -------------------------------------------------------------------------- +// This file contains the main type definitions for mimalloc: +// mi_heap_t : all data for a thread-local heap, contains +// lists of all managed heap pages. +// mi_segment_t : a larger chunk of memory (32GiB) from where pages +// are allocated. +// mi_page_t : a mimalloc page (usually 64KiB or 512KiB) from +// where objects are allocated. +// -------------------------------------------------------------------------- + + +#include // ptrdiff_t +#include // uintptr_t, uint16_t, etc +#include "mimalloc/atomic.h" // _Atomic + +#ifdef _MSC_VER +#pragma warning(disable:4214) // bitfield is not int +#endif + +// Minimal alignment necessary. On most platforms 16 bytes are needed +// due to SSE registers for example. This must be at least `sizeof(void*)` +#ifndef MI_MAX_ALIGN_SIZE +#define MI_MAX_ALIGN_SIZE 16 // sizeof(max_align_t) +#endif + +// ------------------------------------------------------ +// Variants +// ------------------------------------------------------ + +// Define NDEBUG in the release version to disable assertions. +// #define NDEBUG + +// Define MI_TRACK_ to enable tracking support +// #define MI_TRACK_VALGRIND 1 +// #define MI_TRACK_ASAN 1 +// #define MI_TRACK_ETW 1 + +// Define MI_STAT as 1 to maintain statistics; set it to 2 to have detailed statistics (but costs some performance). +// #define MI_STAT 1 + +// Define MI_SECURE to enable security mitigations +// #define MI_SECURE 1 // guard page around metadata +// #define MI_SECURE 2 // guard page around each mimalloc page +// #define MI_SECURE 3 // encode free lists (detect corrupted free list (buffer overflow), and invalid pointer free) +// #define MI_SECURE 4 // checks for double free. (may be more expensive) + +#if !defined(MI_SECURE) +#define MI_SECURE 0 +#endif + +// Define MI_DEBUG for debug mode +// #define MI_DEBUG 1 // basic assertion checks and statistics, check double free, corrupted free list, and invalid pointer free. +// #define MI_DEBUG 2 // + internal assertion checks +// #define MI_DEBUG 3 // + extensive internal invariant checking (cmake -DMI_DEBUG_FULL=ON) +#if !defined(MI_DEBUG) +#if !defined(NDEBUG) || defined(_DEBUG) +#define MI_DEBUG 2 +#else +#define MI_DEBUG 0 +#endif +#endif + +// Reserve extra padding at the end of each block to be more resilient against heap block overflows. +// The padding can detect buffer overflow on free. +#if !defined(MI_PADDING) && (MI_SECURE>=3 || MI_DEBUG>=1 || (MI_TRACK_VALGRIND || MI_TRACK_ASAN || MI_TRACK_ETW)) +#define MI_PADDING 1 +#endif + +// Check padding bytes; allows byte-precise buffer overflow detection +#if !defined(MI_PADDING_CHECK) && MI_PADDING && (MI_SECURE>=3 || MI_DEBUG>=1) +#define MI_PADDING_CHECK 1 +#endif + + +// Encoded free lists allow detection of corrupted free lists +// and can detect buffer overflows, modify after free, and double `free`s. +#if (MI_SECURE>=3 || MI_DEBUG>=1) +#define MI_ENCODE_FREELIST 1 +#endif + + +// We used to abandon huge pages but to eagerly deallocate if freed from another thread, +// but that makes it not possible to visit them during a heap walk or include them in a +// `mi_heap_destroy`. We therefore instead reset/decommit the huge blocks if freed from +// another thread so most memory is available until it gets properly freed by the owning thread. +// #define MI_HUGE_PAGE_ABANDON 1 + + +// ------------------------------------------------------ +// Platform specific values +// ------------------------------------------------------ + +// ------------------------------------------------------ +// Size of a pointer. +// We assume that `sizeof(void*)==sizeof(intptr_t)` +// and it holds for all platforms we know of. +// +// However, the C standard only requires that: +// p == (void*)((intptr_t)p)) +// but we also need: +// i == (intptr_t)((void*)i) +// or otherwise one might define an intptr_t type that is larger than a pointer... +// ------------------------------------------------------ + +#if INTPTR_MAX > INT64_MAX +# define MI_INTPTR_SHIFT (4) // assume 128-bit (as on arm CHERI for example) +#elif INTPTR_MAX == INT64_MAX +# define MI_INTPTR_SHIFT (3) +#elif INTPTR_MAX == INT32_MAX +# define MI_INTPTR_SHIFT (2) +#else +#error platform pointers must be 32, 64, or 128 bits +#endif + +#if SIZE_MAX == UINT64_MAX +# define MI_SIZE_SHIFT (3) +typedef int64_t mi_ssize_t; +#elif SIZE_MAX == UINT32_MAX +# define MI_SIZE_SHIFT (2) +typedef int32_t mi_ssize_t; +#else +#error platform objects must be 32 or 64 bits +#endif + +#if (SIZE_MAX/2) > LONG_MAX +# define MI_ZU(x) x##ULL +# define MI_ZI(x) x##LL +#else +# define MI_ZU(x) x##UL +# define MI_ZI(x) x##L +#endif + +#define MI_INTPTR_SIZE (1< 4 +#define MI_SEGMENT_SHIFT ( 9 + MI_SEGMENT_SLICE_SHIFT) // 32MiB +#else +#define MI_SEGMENT_SHIFT ( 7 + MI_SEGMENT_SLICE_SHIFT) // 4MiB on 32-bit +#endif + +#define MI_SMALL_PAGE_SHIFT (MI_SEGMENT_SLICE_SHIFT) // 64KiB +#define MI_MEDIUM_PAGE_SHIFT ( 3 + MI_SMALL_PAGE_SHIFT) // 512KiB + + +// Derived constants +#define MI_SEGMENT_SIZE (MI_ZU(1)<= 655360) +#error "mimalloc internal: define more bins" +#endif + +// Maximum slice offset (15) +#define MI_MAX_SLICE_OFFSET ((MI_ALIGNMENT_MAX / MI_SEGMENT_SLICE_SIZE) - 1) + +// Used as a special value to encode block sizes in 32 bits. +#define MI_HUGE_BLOCK_SIZE ((uint32_t)(2*MI_GiB)) + +// blocks up to this size are always allocated aligned +#define MI_MAX_ALIGN_GUARANTEE (8*MI_MAX_ALIGN_SIZE) + +// Alignments over MI_ALIGNMENT_MAX are allocated in dedicated huge page segments +#define MI_ALIGNMENT_MAX (MI_SEGMENT_SIZE >> 1) + + +// ------------------------------------------------------ +// Mimalloc pages contain allocated blocks +// ------------------------------------------------------ + +// The free lists use encoded next fields +// (Only actually encodes when MI_ENCODED_FREELIST is defined.) +typedef uintptr_t mi_encoded_t; + +// thread id's +typedef size_t mi_threadid_t; + +// free lists contain blocks +typedef struct mi_block_s { + mi_encoded_t next; +} mi_block_t; + + +// The delayed flags are used for efficient multi-threaded free-ing +typedef enum mi_delayed_e { + MI_USE_DELAYED_FREE = 0, // push on the owning heap thread delayed list + MI_DELAYED_FREEING = 1, // temporary: another thread is accessing the owning heap + MI_NO_DELAYED_FREE = 2, // optimize: push on page local thread free queue if another block is already in the heap thread delayed free list + MI_NEVER_DELAYED_FREE = 3 // sticky, only resets on page reclaim +} mi_delayed_t; + + +// The `in_full` and `has_aligned` page flags are put in a union to efficiently +// test if both are false (`full_aligned == 0`) in the `mi_free` routine. +#if !MI_TSAN +typedef union mi_page_flags_s { + uint8_t full_aligned; + struct { + uint8_t in_full : 1; + uint8_t has_aligned : 1; + } x; +} mi_page_flags_t; +#else +// under thread sanitizer, use a byte for each flag to suppress warning, issue #130 +typedef union mi_page_flags_s { + uint16_t full_aligned; + struct { + uint8_t in_full; + uint8_t has_aligned; + } x; +} mi_page_flags_t; +#endif + +// Thread free list. +// We use the bottom 2 bits of the pointer for mi_delayed_t flags +typedef uintptr_t mi_thread_free_t; + +// A page contains blocks of one specific size (`block_size`). +// Each page has three list of free blocks: +// `free` for blocks that can be allocated, +// `local_free` for freed blocks that are not yet available to `mi_malloc` +// `thread_free` for freed blocks by other threads +// The `local_free` and `thread_free` lists are migrated to the `free` list +// when it is exhausted. The separate `local_free` list is necessary to +// implement a monotonic heartbeat. The `thread_free` list is needed for +// avoiding atomic operations in the common case. +// +// +// `used - |thread_free|` == actual blocks that are in use (alive) +// `used - |thread_free| + |free| + |local_free| == capacity` +// +// We don't count `freed` (as |free|) but use `used` to reduce +// the number of memory accesses in the `mi_page_all_free` function(s). +// +// Notes: +// - Access is optimized for `mi_free` and `mi_page_alloc` (in `alloc.c`) +// - Using `uint16_t` does not seem to slow things down +// - The size is 8 words on 64-bit which helps the page index calculations +// (and 10 words on 32-bit, and encoded free lists add 2 words. Sizes 10 +// and 12 are still good for address calculation) +// - To limit the structure size, the `xblock_size` is 32-bits only; for +// blocks > MI_HUGE_BLOCK_SIZE the size is determined from the segment page size +// - `thread_free` uses the bottom bits as a delayed-free flags to optimize +// concurrent frees where only the first concurrent free adds to the owning +// heap `thread_delayed_free` list (see `alloc.c:mi_free_block_mt`). +// The invariant is that no-delayed-free is only set if there is +// at least one block that will be added, or as already been added, to +// the owning heap `thread_delayed_free` list. This guarantees that pages +// will be freed correctly even if only other threads free blocks. +typedef struct mi_page_s { + // "owned" by the segment + uint32_t slice_count; // slices in this page (0 if not a page) + uint32_t slice_offset; // distance from the actual page data slice (0 if a page) + uint8_t is_committed : 1; // `true` if the page virtual memory is committed + uint8_t is_zero_init : 1; // `true` if the page was initially zero initialized + + // layout like this to optimize access in `mi_malloc` and `mi_free` + uint16_t capacity; // number of blocks committed, must be the first field, see `segment.c:page_clear` + uint16_t reserved; // number of blocks reserved in memory + mi_page_flags_t flags; // `in_full` and `has_aligned` flags (8 bits) + uint8_t free_is_zero : 1; // `true` if the blocks in the free list are zero initialized + uint8_t retire_expire : 7; // expiration count for retired blocks + + mi_block_t* free; // list of available free blocks (`malloc` allocates from this list) + uint32_t used; // number of blocks in use (including blocks in `local_free` and `thread_free`) + uint32_t xblock_size; // size available in each block (always `>0`) + mi_block_t* local_free; // list of deferred free blocks by this thread (migrates to `free`) + + #if (MI_ENCODE_FREELIST || MI_PADDING) + uintptr_t keys[2]; // two random keys to encode the free lists (see `_mi_block_next`) or padding canary + #endif + + _Atomic(mi_thread_free_t) xthread_free; // list of deferred free blocks freed by other threads + _Atomic(uintptr_t) xheap; + + struct mi_page_s* next; // next page owned by this thread with the same `block_size` + struct mi_page_s* prev; // previous page owned by this thread with the same `block_size` + + // 64-bit 9 words, 32-bit 12 words, (+2 for secure) + #if MI_INTPTR_SIZE==8 + uintptr_t padding[1]; + #endif +} mi_page_t; + + + +// ------------------------------------------------------ +// Mimalloc segments contain mimalloc pages +// ------------------------------------------------------ + +typedef enum mi_page_kind_e { + MI_PAGE_SMALL, // small blocks go into 64KiB pages inside a segment + MI_PAGE_MEDIUM, // medium blocks go into medium pages inside a segment + MI_PAGE_LARGE, // larger blocks go into a page of just one block + MI_PAGE_HUGE, // huge blocks (> 16 MiB) are put into a single page in a single segment. +} mi_page_kind_t; + +typedef enum mi_segment_kind_e { + MI_SEGMENT_NORMAL, // MI_SEGMENT_SIZE size with pages inside. + MI_SEGMENT_HUGE, // > MI_LARGE_SIZE_MAX segment with just one huge page inside. +} mi_segment_kind_t; + +// ------------------------------------------------------ +// A segment holds a commit mask where a bit is set if +// the corresponding MI_COMMIT_SIZE area is committed. +// The MI_COMMIT_SIZE must be a multiple of the slice +// size. If it is equal we have the most fine grained +// decommit (but setting it higher can be more efficient). +// The MI_MINIMAL_COMMIT_SIZE is the minimal amount that will +// be committed in one go which can be set higher than +// MI_COMMIT_SIZE for efficiency (while the decommit mask +// is still tracked in fine-grained MI_COMMIT_SIZE chunks) +// ------------------------------------------------------ + +#define MI_MINIMAL_COMMIT_SIZE (1*MI_SEGMENT_SLICE_SIZE) +#define MI_COMMIT_SIZE (MI_SEGMENT_SLICE_SIZE) // 64KiB +#define MI_COMMIT_MASK_BITS (MI_SEGMENT_SIZE / MI_COMMIT_SIZE) +#define MI_COMMIT_MASK_FIELD_BITS MI_SIZE_BITS +#define MI_COMMIT_MASK_FIELD_COUNT (MI_COMMIT_MASK_BITS / MI_COMMIT_MASK_FIELD_BITS) + +#if (MI_COMMIT_MASK_BITS != (MI_COMMIT_MASK_FIELD_COUNT * MI_COMMIT_MASK_FIELD_BITS)) +#error "the segment size must be exactly divisible by the (commit size * size_t bits)" +#endif + +typedef struct mi_commit_mask_s { + size_t mask[MI_COMMIT_MASK_FIELD_COUNT]; +} mi_commit_mask_t; + +typedef mi_page_t mi_slice_t; +typedef int64_t mi_msecs_t; + + +// Memory can reside in arena's, direct OS allocated, or statically allocated. The memid keeps track of this. +typedef enum mi_memkind_e { + MI_MEM_NONE, // not allocated + MI_MEM_EXTERNAL, // not owned by mimalloc but provided externally (via `mi_manage_os_memory` for example) + MI_MEM_STATIC, // allocated in a static area and should not be freed (for arena meta data for example) + MI_MEM_OS, // allocated from the OS + MI_MEM_OS_HUGE, // allocated as huge os pages + MI_MEM_OS_REMAP, // allocated in a remapable area (i.e. using `mremap`) + MI_MEM_ARENA // allocated from an arena (the usual case) +} mi_memkind_t; + +static inline bool mi_memkind_is_os(mi_memkind_t memkind) { + return (memkind >= MI_MEM_OS && memkind <= MI_MEM_OS_REMAP); +} + +typedef struct mi_memid_os_info { + void* base; // actual base address of the block (used for offset aligned allocations) + size_t alignment; // alignment at allocation +} mi_memid_os_info_t; + +typedef struct mi_memid_arena_info { + size_t block_index; // index in the arena + mi_arena_id_t id; // arena id (>= 1) + bool is_exclusive; // the arena can only be used for specific arena allocations +} mi_memid_arena_info_t; + +typedef struct mi_memid_s { + union { + mi_memid_os_info_t os; // only used for MI_MEM_OS + mi_memid_arena_info_t arena; // only used for MI_MEM_ARENA + } mem; + bool is_pinned; // `true` if we cannot decommit/reset/protect in this memory (e.g. when allocated using large OS pages) + bool initially_committed;// `true` if the memory was originally allocated as committed + bool initially_zero; // `true` if the memory was originally zero initialized + mi_memkind_t memkind; +} mi_memid_t; + + +// Segments are large allocated memory blocks (8mb on 64 bit) from +// the OS. Inside segments we allocated fixed size _pages_ that +// contain blocks. +typedef struct mi_segment_s { + // constant fields + mi_memid_t memid; // memory id for arena allocation + bool allow_decommit; + bool allow_purge; + size_t segment_size; + + // segment fields + mi_msecs_t purge_expire; + mi_commit_mask_t purge_mask; + mi_commit_mask_t commit_mask; + + _Atomic(struct mi_segment_s*) abandoned_next; + + // from here is zero initialized + struct mi_segment_s* next; // the list of freed segments in the cache (must be first field, see `segment.c:mi_segment_init`) + + size_t abandoned; // abandoned pages (i.e. the original owning thread stopped) (`abandoned <= used`) + size_t abandoned_visits; // count how often this segment is visited in the abandoned list (to force reclaim it it is too long) + size_t used; // count of pages in use + uintptr_t cookie; // verify addresses in debug mode: `mi_ptr_cookie(segment) == segment->cookie` + + size_t segment_slices; // for huge segments this may be different from `MI_SLICES_PER_SEGMENT` + size_t segment_info_slices; // initial slices we are using segment info and possible guard pages. + + // layout like this to optimize access in `mi_free` + mi_segment_kind_t kind; + size_t slice_entries; // entries in the `slices` array, at most `MI_SLICES_PER_SEGMENT` + _Atomic(mi_threadid_t) thread_id; // unique id of the thread owning this segment + + mi_slice_t slices[MI_SLICES_PER_SEGMENT+1]; // one more for huge blocks with large alignment +} mi_segment_t; + + +// ------------------------------------------------------ +// Heaps +// Provide first-class heaps to allocate from. +// A heap just owns a set of pages for allocation and +// can only be allocate/reallocate from the thread that created it. +// Freeing blocks can be done from any thread though. +// Per thread, the segments are shared among its heaps. +// Per thread, there is always a default heap that is +// used for allocation; it is initialized to statically +// point to an empty heap to avoid initialization checks +// in the fast path. +// ------------------------------------------------------ + +// Thread local data +typedef struct mi_tld_s mi_tld_t; + +// Pages of a certain block size are held in a queue. +typedef struct mi_page_queue_s { + mi_page_t* first; + mi_page_t* last; + size_t block_size; +} mi_page_queue_t; + +#define MI_BIN_FULL (MI_BIN_HUGE+1) + +// Random context +typedef struct mi_random_cxt_s { + uint32_t input[16]; + uint32_t output[16]; + int output_available; + bool weak; +} mi_random_ctx_t; + + +// In debug mode there is a padding structure at the end of the blocks to check for buffer overflows +#if (MI_PADDING) +typedef struct mi_padding_s { + uint32_t canary; // encoded block value to check validity of the padding (in case of overflow) + uint32_t delta; // padding bytes before the block. (mi_usable_size(p) - delta == exact allocated bytes) +} mi_padding_t; +#define MI_PADDING_SIZE (sizeof(mi_padding_t)) +#define MI_PADDING_WSIZE ((MI_PADDING_SIZE + MI_INTPTR_SIZE - 1) / MI_INTPTR_SIZE) +#else +#define MI_PADDING_SIZE 0 +#define MI_PADDING_WSIZE 0 +#endif + +#define MI_PAGES_DIRECT (MI_SMALL_WSIZE_MAX + MI_PADDING_WSIZE + 1) + + +// A heap owns a set of pages. +struct mi_heap_s { + mi_tld_t* tld; + mi_page_t* pages_free_direct[MI_PAGES_DIRECT]; // optimize: array where every entry points a page with possibly free blocks in the corresponding queue for that size. + mi_page_queue_t pages[MI_BIN_FULL + 1]; // queue of pages for each size class (or "bin") + _Atomic(mi_block_t*) thread_delayed_free; + mi_threadid_t thread_id; // thread this heap belongs too + mi_arena_id_t arena_id; // arena id if the heap belongs to a specific arena (or 0) + uintptr_t cookie; // random cookie to verify pointers (see `_mi_ptr_cookie`) + uintptr_t keys[2]; // two random keys used to encode the `thread_delayed_free` list + mi_random_ctx_t random; // random number context used for secure allocation + size_t page_count; // total number of pages in the `pages` queues. + size_t page_retired_min; // smallest retired index (retired pages are fully free, but still in the page queues) + size_t page_retired_max; // largest retired index into the `pages` array. + mi_heap_t* next; // list of heaps per thread + bool no_reclaim; // `true` if this heap should not reclaim abandoned pages +}; + + + +// ------------------------------------------------------ +// Debug +// ------------------------------------------------------ + +#if !defined(MI_DEBUG_UNINIT) +#define MI_DEBUG_UNINIT (0xD0) +#endif +#if !defined(MI_DEBUG_FREED) +#define MI_DEBUG_FREED (0xDF) +#endif +#if !defined(MI_DEBUG_PADDING) +#define MI_DEBUG_PADDING (0xDE) +#endif + +#if (MI_DEBUG) +// use our own assertion to print without memory allocation +void _mi_assert_fail(const char* assertion, const char* fname, unsigned int line, const char* func ); +#define mi_assert(expr) ((expr) ? (void)0 : _mi_assert_fail(#expr,__FILE__,__LINE__,__func__)) +#else +#define mi_assert(x) +#endif + +#if (MI_DEBUG>1) +#define mi_assert_internal mi_assert +#else +#define mi_assert_internal(x) +#endif + +#if (MI_DEBUG>2) +#define mi_assert_expensive mi_assert +#else +#define mi_assert_expensive(x) +#endif + +// ------------------------------------------------------ +// Statistics +// ------------------------------------------------------ + +#ifndef MI_STAT +#if (MI_DEBUG>0) +#define MI_STAT 2 +#else +#define MI_STAT 0 +#endif +#endif + +typedef struct mi_stat_count_s { + int64_t allocated; + int64_t freed; + int64_t peak; + int64_t current; +} mi_stat_count_t; + +typedef struct mi_stat_counter_s { + int64_t total; + int64_t count; +} mi_stat_counter_t; + +typedef struct mi_stats_s { + mi_stat_count_t segments; + mi_stat_count_t pages; + mi_stat_count_t reserved; + mi_stat_count_t committed; + mi_stat_count_t reset; + mi_stat_count_t purged; + mi_stat_count_t page_committed; + mi_stat_count_t segments_abandoned; + mi_stat_count_t pages_abandoned; + mi_stat_count_t threads; + mi_stat_count_t normal; + mi_stat_count_t huge; + mi_stat_count_t large; + mi_stat_count_t malloc; + mi_stat_count_t segments_cache; + mi_stat_counter_t pages_extended; + mi_stat_counter_t mmap_calls; + mi_stat_counter_t commit_calls; + mi_stat_counter_t reset_calls; + mi_stat_counter_t purge_calls; + mi_stat_counter_t page_no_retire; + mi_stat_counter_t searches; + mi_stat_counter_t normal_count; + mi_stat_counter_t huge_count; + mi_stat_counter_t large_count; +#if MI_STAT>1 + mi_stat_count_t normal_bins[MI_BIN_HUGE+1]; +#endif +} mi_stats_t; + + +void _mi_stat_increase(mi_stat_count_t* stat, size_t amount); +void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount); +void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount); + +#if (MI_STAT) +#define mi_stat_increase(stat,amount) _mi_stat_increase( &(stat), amount) +#define mi_stat_decrease(stat,amount) _mi_stat_decrease( &(stat), amount) +#define mi_stat_counter_increase(stat,amount) _mi_stat_counter_increase( &(stat), amount) +#else +#define mi_stat_increase(stat,amount) (void)0 +#define mi_stat_decrease(stat,amount) (void)0 +#define mi_stat_counter_increase(stat,amount) (void)0 +#endif + +#define mi_heap_stat_counter_increase(heap,stat,amount) mi_stat_counter_increase( (heap)->tld->stats.stat, amount) +#define mi_heap_stat_increase(heap,stat,amount) mi_stat_increase( (heap)->tld->stats.stat, amount) +#define mi_heap_stat_decrease(heap,stat,amount) mi_stat_decrease( (heap)->tld->stats.stat, amount) + +// ------------------------------------------------------ +// Thread Local data +// ------------------------------------------------------ + +// A "span" is is an available range of slices. The span queues keep +// track of slice spans of at most the given `slice_count` (but more than the previous size class). +typedef struct mi_span_queue_s { + mi_slice_t* first; + mi_slice_t* last; + size_t slice_count; +} mi_span_queue_t; + +#define MI_SEGMENT_BIN_MAX (35) // 35 == mi_segment_bin(MI_SLICES_PER_SEGMENT) + +// OS thread local data +typedef struct mi_os_tld_s { + size_t region_idx; // start point for next allocation + mi_stats_t* stats; // points to tld stats +} mi_os_tld_t; + + +// Segments thread local data +typedef struct mi_segments_tld_s { + mi_span_queue_t spans[MI_SEGMENT_BIN_MAX+1]; // free slice spans inside segments + size_t count; // current number of segments; + size_t peak_count; // peak number of segments + size_t current_size; // current size of all segments + size_t peak_size; // peak size of all segments + mi_stats_t* stats; // points to tld stats + mi_os_tld_t* os; // points to os stats +} mi_segments_tld_t; + +// Thread local data +struct mi_tld_s { + unsigned long long heartbeat; // monotonic heartbeat count + bool recurse; // true if deferred was called; used to prevent infinite recursion. + mi_heap_t* heap_backing; // backing heap of this thread (cannot be deleted) + mi_heap_t* heaps; // list of heaps in this thread (so we can abandon all when the thread terminates) + mi_segments_tld_t segments; // segment tld + mi_os_tld_t os; // os tld + mi_stats_t stats; // statistics +}; + +#endif diff --git a/compat/mimalloc/options.c b/compat/mimalloc/options.c new file mode 100644 index 00000000000000..3a3090d9acfc94 --- /dev/null +++ b/compat/mimalloc/options.c @@ -0,0 +1,571 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2021, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" +#include "mimalloc/prim.h" // mi_prim_out_stderr + +#include // FILE +#include // abort +#include + + +static long mi_max_error_count = 16; // stop outputting errors after this (use < 0 for no limit) +static long mi_max_warning_count = 16; // stop outputting warnings after this (use < 0 for no limit) + +static void mi_add_stderr_output(void); + +int mi_version(void) mi_attr_noexcept { + return MI_MALLOC_VERSION; +} + + +// -------------------------------------------------------- +// Options +// These can be accessed by multiple threads and may be +// concurrently initialized, but an initializing data race +// is ok since they resolve to the same value. +// -------------------------------------------------------- +typedef enum mi_init_e { + UNINIT, // not yet initialized + DEFAULTED, // not found in the environment, use default value + INITIALIZED // found in environment or set explicitly +} mi_init_t; + +typedef struct mi_option_desc_s { + long value; // the value + mi_init_t init; // is it initialized yet? (from the environment) + mi_option_t option; // for debugging: the option index should match the option + const char* name; // option name without `mimalloc_` prefix + const char* legacy_name; // potential legacy option name +} mi_option_desc_t; + +#define MI_OPTION(opt) mi_option_##opt, #opt, NULL +#define MI_OPTION_LEGACY(opt,legacy) mi_option_##opt, #opt, #legacy + +static mi_option_desc_t options[_mi_option_last] = +{ + // stable options + #if MI_DEBUG || defined(MI_SHOW_ERRORS) + { 1, UNINIT, MI_OPTION(show_errors) }, + #else + { 0, UNINIT, MI_OPTION(show_errors) }, + #endif + { 0, UNINIT, MI_OPTION(show_stats) }, + { 0, UNINIT, MI_OPTION(verbose) }, + + // the following options are experimental and not all combinations make sense. + { 1, UNINIT, MI_OPTION(eager_commit) }, // commit per segment directly (4MiB) (but see also `eager_commit_delay`) + { 2, UNINIT, MI_OPTION_LEGACY(arena_eager_commit,eager_region_commit) }, // eager commit arena's? 2 is used to enable this only on an OS that has overcommit (i.e. linux) + { 1, UNINIT, MI_OPTION_LEGACY(purge_decommits,reset_decommits) }, // purge decommits memory (instead of reset) (note: on linux this uses MADV_DONTNEED for decommit) + { 0, UNINIT, MI_OPTION_LEGACY(allow_large_os_pages,large_os_pages) }, // use large OS pages, use only with eager commit to prevent fragmentation of VMA's + { 0, UNINIT, MI_OPTION(reserve_huge_os_pages) }, // per 1GiB huge pages + {-1, UNINIT, MI_OPTION(reserve_huge_os_pages_at) }, // reserve huge pages at node N + { 0, UNINIT, MI_OPTION(reserve_os_memory) }, + { 0, UNINIT, MI_OPTION(deprecated_segment_cache) }, // cache N segments per thread + { 0, UNINIT, MI_OPTION(deprecated_page_reset) }, // reset page memory on free + { 0, UNINIT, MI_OPTION_LEGACY(abandoned_page_purge,abandoned_page_reset) }, // reset free page memory when a thread terminates + { 0, UNINIT, MI_OPTION(deprecated_segment_reset) }, // reset segment memory on free (needs eager commit) +#if defined(__NetBSD__) + { 0, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed +#else + { 1, UNINIT, MI_OPTION(eager_commit_delay) }, // the first N segments per thread are not eagerly committed (but per page in the segment on demand) +#endif + { 10, UNINIT, MI_OPTION_LEGACY(purge_delay,reset_delay) }, // purge delay in milli-seconds + { 0, UNINIT, MI_OPTION(use_numa_nodes) }, // 0 = use available numa nodes, otherwise use at most N nodes. + { 0, UNINIT, MI_OPTION(limit_os_alloc) }, // 1 = do not use OS memory for allocation (but only reserved arenas) + { 100, UNINIT, MI_OPTION(os_tag) }, // only apple specific for now but might serve more or less related purpose + { 16, UNINIT, MI_OPTION(max_errors) }, // maximum errors that are output + { 16, UNINIT, MI_OPTION(max_warnings) }, // maximum warnings that are output + { 8, UNINIT, MI_OPTION(max_segment_reclaim)}, // max. number of segment reclaims from the abandoned segments per try. + { 0, UNINIT, MI_OPTION(destroy_on_exit)}, // release all OS memory on process exit; careful with dangling pointer or after-exit frees! + #if (MI_INTPTR_SIZE>4) + { 1024L * 1024L, UNINIT, MI_OPTION(arena_reserve) }, // reserve memory N KiB at a time + #else + { 128L * 1024L, UNINIT, MI_OPTION(arena_reserve) }, + #endif + { 10, UNINIT, MI_OPTION(arena_purge_mult) }, // purge delay multiplier for arena's + { 1, UNINIT, MI_OPTION_LEGACY(purge_extend_delay, decommit_extend_delay) }, +}; + +static void mi_option_init(mi_option_desc_t* desc); + +void _mi_options_init(void) { + // called on process load; should not be called before the CRT is initialized! + // (e.g. do not call this from process_init as that may run before CRT initialization) + mi_add_stderr_output(); // now it safe to use stderr for output + for(int i = 0; i < _mi_option_last; i++ ) { + mi_option_t option = (mi_option_t)i; + long l = mi_option_get(option); MI_UNUSED(l); // initialize + // if (option != mi_option_verbose) + { + mi_option_desc_t* desc = &options[option]; + _mi_verbose_message("option '%s': %ld\n", desc->name, desc->value); + } + } + mi_max_error_count = mi_option_get(mi_option_max_errors); + mi_max_warning_count = mi_option_get(mi_option_max_warnings); +} + +mi_decl_nodiscard long mi_option_get(mi_option_t option) { + mi_assert(option >= 0 && option < _mi_option_last); + if (option < 0 || option >= _mi_option_last) return 0; + mi_option_desc_t* desc = &options[option]; + mi_assert(desc->option == option); // index should match the option + if mi_unlikely(desc->init == UNINIT) { + mi_option_init(desc); + } + return desc->value; +} + +mi_decl_nodiscard long mi_option_get_clamp(mi_option_t option, long min, long max) { + long x = mi_option_get(option); + return (x < min ? min : (x > max ? max : x)); +} + +mi_decl_nodiscard size_t mi_option_get_size(mi_option_t option) { + mi_assert_internal(option == mi_option_reserve_os_memory || option == mi_option_arena_reserve); + long x = mi_option_get(option); + return (x < 0 ? 0 : (size_t)x * MI_KiB); +} + +void mi_option_set(mi_option_t option, long value) { + mi_assert(option >= 0 && option < _mi_option_last); + if (option < 0 || option >= _mi_option_last) return; + mi_option_desc_t* desc = &options[option]; + mi_assert(desc->option == option); // index should match the option + desc->value = value; + desc->init = INITIALIZED; +} + +void mi_option_set_default(mi_option_t option, long value) { + mi_assert(option >= 0 && option < _mi_option_last); + if (option < 0 || option >= _mi_option_last) return; + mi_option_desc_t* desc = &options[option]; + if (desc->init != INITIALIZED) { + desc->value = value; + } +} + +mi_decl_nodiscard bool mi_option_is_enabled(mi_option_t option) { + return (mi_option_get(option) != 0); +} + +void mi_option_set_enabled(mi_option_t option, bool enable) { + mi_option_set(option, (enable ? 1 : 0)); +} + +void mi_option_set_enabled_default(mi_option_t option, bool enable) { + mi_option_set_default(option, (enable ? 1 : 0)); +} + +void mi_option_enable(mi_option_t option) { + mi_option_set_enabled(option,true); +} + +void mi_option_disable(mi_option_t option) { + mi_option_set_enabled(option,false); +} + +static void mi_cdecl mi_out_stderr(const char* msg, void* arg) { + MI_UNUSED(arg); + if (msg != NULL && msg[0] != 0) { + _mi_prim_out_stderr(msg); + } +} + +// Since an output function can be registered earliest in the `main` +// function we also buffer output that happens earlier. When +// an output function is registered it is called immediately with +// the output up to that point. +#ifndef MI_MAX_DELAY_OUTPUT +#define MI_MAX_DELAY_OUTPUT ((size_t)(32*1024)) +#endif +static char out_buf[MI_MAX_DELAY_OUTPUT+1]; +static _Atomic(size_t) out_len; + +static void mi_cdecl mi_out_buf(const char* msg, void* arg) { + MI_UNUSED(arg); + if (msg==NULL) return; + if (mi_atomic_load_relaxed(&out_len)>=MI_MAX_DELAY_OUTPUT) return; + size_t n = _mi_strlen(msg); + if (n==0) return; + // claim space + size_t start = mi_atomic_add_acq_rel(&out_len, n); + if (start >= MI_MAX_DELAY_OUTPUT) return; + // check bound + if (start+n >= MI_MAX_DELAY_OUTPUT) { + n = MI_MAX_DELAY_OUTPUT-start-1; + } + _mi_memcpy(&out_buf[start], msg, n); +} + +static void mi_out_buf_flush(mi_output_fun* out, bool no_more_buf, void* arg) { + if (out==NULL) return; + // claim (if `no_more_buf == true`, no more output will be added after this point) + size_t count = mi_atomic_add_acq_rel(&out_len, (no_more_buf ? MI_MAX_DELAY_OUTPUT : 1)); + // and output the current contents + if (count>MI_MAX_DELAY_OUTPUT) count = MI_MAX_DELAY_OUTPUT; + out_buf[count] = 0; + out(out_buf,arg); + if (!no_more_buf) { + out_buf[count] = '\n'; // if continue with the buffer, insert a newline + } +} + + +// Once this module is loaded, switch to this routine +// which outputs to stderr and the delayed output buffer. +static void mi_cdecl mi_out_buf_stderr(const char* msg, void* arg) { + mi_out_stderr(msg,arg); + mi_out_buf(msg,arg); +} + + + +// -------------------------------------------------------- +// Default output handler +// -------------------------------------------------------- + +// Should be atomic but gives errors on many platforms as generally we cannot cast a function pointer to a uintptr_t. +// For now, don't register output from multiple threads. +static mi_output_fun* volatile mi_out_default; // = NULL +static _Atomic(void*) mi_out_arg; // = NULL + +static mi_output_fun* mi_out_get_default(void** parg) { + if (parg != NULL) { *parg = mi_atomic_load_ptr_acquire(void,&mi_out_arg); } + mi_output_fun* out = mi_out_default; + return (out == NULL ? &mi_out_buf : out); +} + +void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept { + mi_out_default = (out == NULL ? &mi_out_stderr : out); // stop using the delayed output buffer + mi_atomic_store_ptr_release(void,&mi_out_arg, arg); + if (out!=NULL) mi_out_buf_flush(out,true,arg); // output all the delayed output now +} + +// add stderr to the delayed output after the module is loaded +static void mi_add_stderr_output(void) { + mi_assert_internal(mi_out_default == NULL); + mi_out_buf_flush(&mi_out_stderr, false, NULL); // flush current contents to stderr + mi_out_default = &mi_out_buf_stderr; // and add stderr to the delayed output +} + +// -------------------------------------------------------- +// Messages, all end up calling `_mi_fputs`. +// -------------------------------------------------------- +static _Atomic(size_t) error_count; // = 0; // when >= max_error_count stop emitting errors +static _Atomic(size_t) warning_count; // = 0; // when >= max_warning_count stop emitting warnings + +// When overriding malloc, we may recurse into mi_vfprintf if an allocation +// inside the C runtime causes another message. +// In some cases (like on macOS) the loader already allocates which +// calls into mimalloc; if we then access thread locals (like `recurse`) +// this may crash as the access may call _tlv_bootstrap that tries to +// (recursively) invoke malloc again to allocate space for the thread local +// variables on demand. This is why we use a _mi_preloading test on such +// platforms. However, C code generator may move the initial thread local address +// load before the `if` and we therefore split it out in a separate funcion. +static mi_decl_thread bool recurse = false; + +static mi_decl_noinline bool mi_recurse_enter_prim(void) { + if (recurse) return false; + recurse = true; + return true; +} + +static mi_decl_noinline void mi_recurse_exit_prim(void) { + recurse = false; +} + +static bool mi_recurse_enter(void) { + #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD) + if (_mi_preloading()) return false; + #endif + return mi_recurse_enter_prim(); +} + +static void mi_recurse_exit(void) { + #if defined(__APPLE__) || defined(MI_TLS_RECURSE_GUARD) + if (_mi_preloading()) return; + #endif + mi_recurse_exit_prim(); +} + +void _mi_fputs(mi_output_fun* out, void* arg, const char* prefix, const char* message) { + if (out==NULL || (void*)out==(void*)stdout || (void*)out==(void*)stderr) { // TODO: use mi_out_stderr for stderr? + if (!mi_recurse_enter()) return; + out = mi_out_get_default(&arg); + if (prefix != NULL) out(prefix, arg); + out(message, arg); + mi_recurse_exit(); + } + else { + if (prefix != NULL) out(prefix, arg); + out(message, arg); + } +} + +// Define our own limited `fprintf` that avoids memory allocation. +// We do this using `snprintf` with a limited buffer. +static void mi_vfprintf( mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args ) { + char buf[512]; + if (fmt==NULL) return; + if (!mi_recurse_enter()) return; + vsnprintf(buf,sizeof(buf)-1,fmt,args); + mi_recurse_exit(); + _mi_fputs(out,arg,prefix,buf); +} + +void _mi_fprintf( mi_output_fun* out, void* arg, const char* fmt, ... ) { + va_list args; + va_start(args,fmt); + mi_vfprintf(out,arg,NULL,fmt,args); + va_end(args); +} + +static void mi_vfprintf_thread(mi_output_fun* out, void* arg, const char* prefix, const char* fmt, va_list args) { + if (prefix != NULL && _mi_strnlen(prefix,33) <= 32 && !_mi_is_main_thread()) { + char tprefix[64]; + snprintf(tprefix, sizeof(tprefix), "%sthread 0x%llx: ", prefix, (unsigned long long)_mi_thread_id()); + mi_vfprintf(out, arg, tprefix, fmt, args); + } + else { + mi_vfprintf(out, arg, prefix, fmt, args); + } +} + +void _mi_trace_message(const char* fmt, ...) { + if (mi_option_get(mi_option_verbose) <= 1) return; // only with verbose level 2 or higher + va_list args; + va_start(args, fmt); + mi_vfprintf_thread(NULL, NULL, "mimalloc: ", fmt, args); + va_end(args); +} + +void _mi_verbose_message(const char* fmt, ...) { + if (!mi_option_is_enabled(mi_option_verbose)) return; + va_list args; + va_start(args,fmt); + mi_vfprintf(NULL, NULL, "mimalloc: ", fmt, args); + va_end(args); +} + +static void mi_show_error_message(const char* fmt, va_list args) { + if (!mi_option_is_enabled(mi_option_verbose)) { + if (!mi_option_is_enabled(mi_option_show_errors)) return; + if (mi_max_error_count >= 0 && (long)mi_atomic_increment_acq_rel(&error_count) > mi_max_error_count) return; + } + mi_vfprintf_thread(NULL, NULL, "mimalloc: error: ", fmt, args); +} + +void _mi_warning_message(const char* fmt, ...) { + if (!mi_option_is_enabled(mi_option_verbose)) { + if (!mi_option_is_enabled(mi_option_show_errors)) return; + if (mi_max_warning_count >= 0 && (long)mi_atomic_increment_acq_rel(&warning_count) > mi_max_warning_count) return; + } + va_list args; + va_start(args,fmt); + mi_vfprintf_thread(NULL, NULL, "mimalloc: warning: ", fmt, args); + va_end(args); +} + + +#if MI_DEBUG +void _mi_assert_fail(const char* assertion, const char* fname, unsigned line, const char* func ) { + _mi_fprintf(NULL, NULL, "mimalloc: assertion failed: at \"%s\":%u, %s\n assertion: \"%s\"\n", fname, line, (func==NULL?"":func), assertion); + abort(); +} +#endif + +// -------------------------------------------------------- +// Errors +// -------------------------------------------------------- + +static mi_error_fun* volatile mi_error_handler; // = NULL +static _Atomic(void*) mi_error_arg; // = NULL + +static void mi_error_default(int err) { + MI_UNUSED(err); +#if (MI_DEBUG>0) + if (err==EFAULT) { + #ifdef _MSC_VER + __debugbreak(); + #endif + abort(); + } +#endif +#if (MI_SECURE>0) + if (err==EFAULT) { // abort on serious errors in secure mode (corrupted meta-data) + abort(); + } +#endif +#if defined(MI_XMALLOC) + if (err==ENOMEM || err==EOVERFLOW) { // abort on memory allocation fails in xmalloc mode + abort(); + } +#endif +} + +void mi_register_error(mi_error_fun* fun, void* arg) { + mi_error_handler = fun; // can be NULL + mi_atomic_store_ptr_release(void,&mi_error_arg, arg); +} + +void _mi_error_message(int err, const char* fmt, ...) { + // show detailed error message + va_list args; + va_start(args, fmt); + mi_show_error_message(fmt, args); + va_end(args); + // and call the error handler which may abort (or return normally) + if (mi_error_handler != NULL) { + mi_error_handler(err, mi_atomic_load_ptr_acquire(void,&mi_error_arg)); + } + else { + mi_error_default(err); + } +} + +// -------------------------------------------------------- +// Initialize options by checking the environment +// -------------------------------------------------------- +char _mi_toupper(char c) { + if (c >= 'a' && c <= 'z') return (c - 'a' + 'A'); + else return c; +} + +int _mi_strnicmp(const char* s, const char* t, size_t n) { + if (n == 0) return 0; + for (; *s != 0 && *t != 0 && n > 0; s++, t++, n--) { + if (_mi_toupper(*s) != _mi_toupper(*t)) break; + } + return (n == 0 ? 0 : *s - *t); +} + +void _mi_strlcpy(char* dest, const char* src, size_t dest_size) { + if (dest==NULL || src==NULL || dest_size == 0) return; + // copy until end of src, or when dest is (almost) full + while (*src != 0 && dest_size > 1) { + *dest++ = *src++; + dest_size--; + } + // always zero terminate + *dest = 0; +} + +void _mi_strlcat(char* dest, const char* src, size_t dest_size) { + if (dest==NULL || src==NULL || dest_size == 0) return; + // find end of string in the dest buffer + while (*dest != 0 && dest_size > 1) { + dest++; + dest_size--; + } + // and catenate + _mi_strlcpy(dest, src, dest_size); +} + +size_t _mi_strlen(const char* s) { + if (s==NULL) return 0; + size_t len = 0; + while(s[len] != 0) { len++; } + return len; +} + +size_t _mi_strnlen(const char* s, size_t max_len) { + if (s==NULL) return 0; + size_t len = 0; + while(s[len] != 0 && len < max_len) { len++; } + return len; +} + +#ifdef MI_NO_GETENV +static bool mi_getenv(const char* name, char* result, size_t result_size) { + MI_UNUSED(name); + MI_UNUSED(result); + MI_UNUSED(result_size); + return false; +} +#else +static bool mi_getenv(const char* name, char* result, size_t result_size) { + if (name==NULL || result == NULL || result_size < 64) return false; + return _mi_prim_getenv(name,result,result_size); +} +#endif + +// TODO: implement ourselves to reduce dependencies on the C runtime +#include // strtol +#include // strstr + + +static void mi_option_init(mi_option_desc_t* desc) { + // Read option value from the environment + char s[64 + 1]; + char buf[64+1]; + _mi_strlcpy(buf, "mimalloc_", sizeof(buf)); + _mi_strlcat(buf, desc->name, sizeof(buf)); + bool found = mi_getenv(buf, s, sizeof(s)); + if (!found && desc->legacy_name != NULL) { + _mi_strlcpy(buf, "mimalloc_", sizeof(buf)); + _mi_strlcat(buf, desc->legacy_name, sizeof(buf)); + found = mi_getenv(buf, s, sizeof(s)); + if (found) { + _mi_warning_message("environment option \"mimalloc_%s\" is deprecated -- use \"mimalloc_%s\" instead.\n", desc->legacy_name, desc->name); + } + } + + if (found) { + size_t len = _mi_strnlen(s, sizeof(buf) - 1); + for (size_t i = 0; i < len; i++) { + buf[i] = _mi_toupper(s[i]); + } + buf[len] = 0; + if (buf[0] == 0 || strstr("1;TRUE;YES;ON", buf) != NULL) { + desc->value = 1; + desc->init = INITIALIZED; + } + else if (strstr("0;FALSE;NO;OFF", buf) != NULL) { + desc->value = 0; + desc->init = INITIALIZED; + } + else { + char* end = buf; + long value = strtol(buf, &end, 10); + if (desc->option == mi_option_reserve_os_memory || desc->option == mi_option_arena_reserve) { + // this option is interpreted in KiB to prevent overflow of `long` + if (*end == 'K') { end++; } + else if (*end == 'M') { value *= MI_KiB; end++; } + else if (*end == 'G') { value *= MI_MiB; end++; } + else { value = (value + MI_KiB - 1) / MI_KiB; } + if (end[0] == 'I' && end[1] == 'B') { end += 2; } + else if (*end == 'B') { end++; } + } + if (*end == 0) { + desc->value = value; + desc->init = INITIALIZED; + } + else { + // set `init` first to avoid recursion through _mi_warning_message on mimalloc_verbose. + desc->init = DEFAULTED; + if (desc->option == mi_option_verbose && desc->value == 0) { + // if the 'mimalloc_verbose' env var has a bogus value we'd never know + // (since the value defaults to 'off') so in that case briefly enable verbose + desc->value = 1; + _mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name); + desc->value = 0; + } + else { + _mi_warning_message("environment option mimalloc_%s has an invalid value.\n", desc->name); + } + } + } + mi_assert_internal(desc->init != UNINIT); + } + else if (!_mi_preloading()) { + desc->init = DEFAULTED; + } +} diff --git a/compat/mimalloc/os.c b/compat/mimalloc/os.c new file mode 100644 index 00000000000000..bf9de1be0fdb49 --- /dev/null +++ b/compat/mimalloc/os.c @@ -0,0 +1,689 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" +#include "mimalloc/prim.h" + + +/* ----------------------------------------------------------- + Initialization. + On windows initializes support for aligned allocation and + large OS pages (if MIMALLOC_LARGE_OS_PAGES is true). +----------------------------------------------------------- */ + +static mi_os_mem_config_t mi_os_mem_config = { + 4096, // page size + 0, // large page size (usually 2MiB) + 4096, // allocation granularity + true, // has overcommit? (if true we use MAP_NORESERVE on mmap systems) + false, // must free whole? (on mmap systems we can free anywhere in a mapped range, but on Windows we must free the entire span) + true // has virtual reserve? (if true we can reserve virtual address space without using commit or physical memory) +}; + +bool _mi_os_has_overcommit(void) { + return mi_os_mem_config.has_overcommit; +} + +bool _mi_os_has_virtual_reserve(void) { + return mi_os_mem_config.has_virtual_reserve; +} + + +// OS (small) page size +size_t _mi_os_page_size(void) { + return mi_os_mem_config.page_size; +} + +// if large OS pages are supported (2 or 4MiB), then return the size, otherwise return the small page size (4KiB) +size_t _mi_os_large_page_size(void) { + return (mi_os_mem_config.large_page_size != 0 ? mi_os_mem_config.large_page_size : _mi_os_page_size()); +} + +bool _mi_os_use_large_page(size_t size, size_t alignment) { + // if we have access, check the size and alignment requirements + if (mi_os_mem_config.large_page_size == 0 || !mi_option_is_enabled(mi_option_allow_large_os_pages)) return false; + return ((size % mi_os_mem_config.large_page_size) == 0 && (alignment % mi_os_mem_config.large_page_size) == 0); +} + +// round to a good OS allocation size (bounded by max 12.5% waste) +size_t _mi_os_good_alloc_size(size_t size) { + size_t align_size; + if (size < 512*MI_KiB) align_size = _mi_os_page_size(); + else if (size < 2*MI_MiB) align_size = 64*MI_KiB; + else if (size < 8*MI_MiB) align_size = 256*MI_KiB; + else if (size < 32*MI_MiB) align_size = 1*MI_MiB; + else align_size = 4*MI_MiB; + if mi_unlikely(size >= (SIZE_MAX - align_size)) return size; // possible overflow? + return _mi_align_up(size, align_size); +} + +void _mi_os_init(void) { + _mi_prim_mem_init(&mi_os_mem_config); +} + + +/* ----------------------------------------------------------- + Util +-------------------------------------------------------------- */ +bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* stats); +bool _mi_os_commit(void* addr, size_t size, bool* is_zero, mi_stats_t* tld_stats); + +static void* mi_align_up_ptr(void* p, size_t alignment) { + return (void*)_mi_align_up((uintptr_t)p, alignment); +} + +static void* mi_align_down_ptr(void* p, size_t alignment) { + return (void*)_mi_align_down((uintptr_t)p, alignment); +} + + +/* ----------------------------------------------------------- + aligned hinting +-------------------------------------------------------------- */ + +// On 64-bit systems, we can do efficient aligned allocation by using +// the 2TiB to 30TiB area to allocate those. +#if (MI_INTPTR_SIZE >= 8) +static mi_decl_cache_align _Atomic(uintptr_t)aligned_base; + +// Return a MI_SEGMENT_SIZE aligned address that is probably available. +// If this returns NULL, the OS will determine the address but on some OS's that may not be +// properly aligned which can be more costly as it needs to be adjusted afterwards. +// For a size > 1GiB this always returns NULL in order to guarantee good ASLR randomization; +// (otherwise an initial large allocation of say 2TiB has a 50% chance to include (known) addresses +// in the middle of the 2TiB - 6TiB address range (see issue #372)) + +#define MI_HINT_BASE ((uintptr_t)2 << 40) // 2TiB start +#define MI_HINT_AREA ((uintptr_t)4 << 40) // upto 6TiB (since before win8 there is "only" 8TiB available to processes) +#define MI_HINT_MAX ((uintptr_t)30 << 40) // wrap after 30TiB (area after 32TiB is used for huge OS pages) + +void* _mi_os_get_aligned_hint(size_t try_alignment, size_t size) +{ + if (try_alignment <= 1 || try_alignment > MI_SEGMENT_SIZE) return NULL; + size = _mi_align_up(size, MI_SEGMENT_SIZE); + if (size > 1*MI_GiB) return NULL; // guarantee the chance of fixed valid address is at most 1/(MI_HINT_AREA / 1<<30) = 1/4096. + #if (MI_SECURE>0) + size += MI_SEGMENT_SIZE; // put in `MI_SEGMENT_SIZE` virtual gaps between hinted blocks; this splits VLA's but increases guarded areas. + #endif + + uintptr_t hint = mi_atomic_add_acq_rel(&aligned_base, size); + if (hint == 0 || hint > MI_HINT_MAX) { // wrap or initialize + uintptr_t init = MI_HINT_BASE; + #if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of aligned allocations unless in debug mode + uintptr_t r = _mi_heap_random_next(mi_prim_get_default_heap()); + init = init + ((MI_SEGMENT_SIZE * ((r>>17) & 0xFFFFF)) % MI_HINT_AREA); // (randomly 20 bits)*4MiB == 0 to 4TiB + #endif + uintptr_t expected = hint + size; + mi_atomic_cas_strong_acq_rel(&aligned_base, &expected, init); + hint = mi_atomic_add_acq_rel(&aligned_base, size); // this may still give 0 or > MI_HINT_MAX but that is ok, it is a hint after all + } + if (hint%try_alignment != 0) return NULL; + return (void*)hint; +} +#else +void* _mi_os_get_aligned_hint(size_t try_alignment, size_t size) { + MI_UNUSED(try_alignment); MI_UNUSED(size); + return NULL; +} +#endif + + +/* ----------------------------------------------------------- + Free memory +-------------------------------------------------------------- */ + +static void mi_os_free_huge_os_pages(void* p, size_t size, mi_stats_t* stats); + +static void mi_os_prim_free(void* addr, size_t size, bool still_committed, mi_stats_t* tld_stats) { + MI_UNUSED(tld_stats); + mi_assert_internal((size % _mi_os_page_size()) == 0); + if (addr == NULL || size == 0) return; // || _mi_os_is_huge_reserved(addr) + int err = _mi_prim_free(addr, size); + if (err != 0) { + _mi_warning_message("unable to free OS memory (error: %d (0x%x), size: 0x%zx bytes, address: %p)\n", err, err, size, addr); + } + mi_stats_t* stats = &_mi_stats_main; + if (still_committed) { _mi_stat_decrease(&stats->committed, size); } + _mi_stat_decrease(&stats->reserved, size); +} + +void _mi_os_free_ex(void* addr, size_t size, bool still_committed, mi_memid_t memid, mi_stats_t* tld_stats) { + if (mi_memkind_is_os(memid.memkind)) { + size_t csize = _mi_os_good_alloc_size(size); + void* base = addr; + // different base? (due to alignment) + if (memid.mem.os.base != NULL) { + mi_assert(memid.mem.os.base <= addr); + mi_assert((uint8_t*)memid.mem.os.base + memid.mem.os.alignment >= (uint8_t*)addr); + base = memid.mem.os.base; + csize += ((uint8_t*)addr - (uint8_t*)memid.mem.os.base); + } + // free it + if (memid.memkind == MI_MEM_OS_HUGE) { + mi_assert(memid.is_pinned); + mi_os_free_huge_os_pages(base, csize, tld_stats); + } + else { + mi_os_prim_free(base, csize, still_committed, tld_stats); + } + } + else { + // nothing to do + mi_assert(memid.memkind < MI_MEM_OS); + } +} + +void _mi_os_free(void* p, size_t size, mi_memid_t memid, mi_stats_t* tld_stats) { + _mi_os_free_ex(p, size, true, memid, tld_stats); +} + + +/* ----------------------------------------------------------- + Primitive allocation from the OS. +-------------------------------------------------------------- */ + +// Note: the `try_alignment` is just a hint and the returned pointer is not guaranteed to be aligned. +static void* mi_os_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, mi_stats_t* stats) { + mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0); + mi_assert_internal(is_zero != NULL); + mi_assert_internal(is_large != NULL); + if (size == 0) return NULL; + if (!commit) { allow_large = false; } + if (try_alignment == 0) { try_alignment = 1; } // avoid 0 to ensure there will be no divide by zero when aligning + + *is_zero = false; + void* p = NULL; + int err = _mi_prim_alloc(size, try_alignment, commit, allow_large, is_large, is_zero, &p); + if (err != 0) { + _mi_warning_message("unable to allocate OS memory (error: %d (0x%x), size: 0x%zx bytes, align: 0x%zx, commit: %d, allow large: %d)\n", err, err, size, try_alignment, commit, allow_large); + } + mi_stat_counter_increase(stats->mmap_calls, 1); + if (p != NULL) { + _mi_stat_increase(&stats->reserved, size); + if (commit) { + _mi_stat_increase(&stats->committed, size); + // seems needed for asan (or `mimalloc-test-api` fails) + #ifdef MI_TRACK_ASAN + if (*is_zero) { mi_track_mem_defined(p,size); } + else { mi_track_mem_undefined(p,size); } + #endif + } + } + return p; +} + + +// Primitive aligned allocation from the OS. +// This function guarantees the allocated memory is aligned. +static void* mi_os_prim_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** base, mi_stats_t* stats) { + mi_assert_internal(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0)); + mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0); + mi_assert_internal(is_large != NULL); + mi_assert_internal(is_zero != NULL); + mi_assert_internal(base != NULL); + if (!commit) allow_large = false; + if (!(alignment >= _mi_os_page_size() && ((alignment & (alignment - 1)) == 0))) return NULL; + size = _mi_align_up(size, _mi_os_page_size()); + + // try first with a hint (this will be aligned directly on Win 10+ or BSD) + void* p = mi_os_prim_alloc(size, alignment, commit, allow_large, is_large, is_zero, stats); + if (p == NULL) return NULL; + + // aligned already? + if (((uintptr_t)p % alignment) == 0) { + *base = p; + } + else { + // if not aligned, free it, overallocate, and unmap around it + _mi_warning_message("unable to allocate aligned OS memory directly, fall back to over-allocation (size: 0x%zx bytes, address: %p, alignment: 0x%zx, commit: %d)\n", size, p, alignment, commit); + mi_os_prim_free(p, size, commit, stats); + if (size >= (SIZE_MAX - alignment)) return NULL; // overflow + const size_t over_size = size + alignment; + + if (mi_os_mem_config.must_free_whole) { // win32 virtualAlloc cannot free parts of an allocate block + // over-allocate uncommitted (virtual) memory + p = mi_os_prim_alloc(over_size, 1 /*alignment*/, false /* commit? */, false /* allow_large */, is_large, is_zero, stats); + if (p == NULL) return NULL; + + // set p to the aligned part in the full region + // note: this is dangerous on Windows as VirtualFree needs the actual base pointer + // this is handled though by having the `base` field in the memid's + *base = p; // remember the base + p = mi_align_up_ptr(p, alignment); + + // explicitly commit only the aligned part + if (commit) { + _mi_os_commit(p, size, NULL, stats); + } + } + else { // mmap can free inside an allocation + // overallocate... + p = mi_os_prim_alloc(over_size, 1, commit, false, is_large, is_zero, stats); + if (p == NULL) return NULL; + + // and selectively unmap parts around the over-allocated area. (noop on sbrk) + void* aligned_p = mi_align_up_ptr(p, alignment); + size_t pre_size = (uint8_t*)aligned_p - (uint8_t*)p; + size_t mid_size = _mi_align_up(size, _mi_os_page_size()); + size_t post_size = over_size - pre_size - mid_size; + mi_assert_internal(pre_size < over_size&& post_size < over_size&& mid_size >= size); + if (pre_size > 0) { mi_os_prim_free(p, pre_size, commit, stats); } + if (post_size > 0) { mi_os_prim_free((uint8_t*)aligned_p + mid_size, post_size, commit, stats); } + // we can return the aligned pointer on `mmap` (and sbrk) systems + p = aligned_p; + *base = aligned_p; // since we freed the pre part, `*base == p`. + } + } + + mi_assert_internal(p == NULL || (p != NULL && *base != NULL && ((uintptr_t)p % alignment) == 0)); + return p; +} + + +/* ----------------------------------------------------------- + OS API: alloc and alloc_aligned +----------------------------------------------------------- */ + +void* _mi_os_alloc(size_t size, mi_memid_t* memid, mi_stats_t* tld_stats) { + MI_UNUSED(tld_stats); + *memid = _mi_memid_none(); + mi_stats_t* stats = &_mi_stats_main; + if (size == 0) return NULL; + size = _mi_os_good_alloc_size(size); + bool os_is_large = false; + bool os_is_zero = false; + void* p = mi_os_prim_alloc(size, 0, true, false, &os_is_large, &os_is_zero, stats); + if (p != NULL) { + *memid = _mi_memid_create_os(true, os_is_zero, os_is_large); + } + return p; +} + +void* _mi_os_alloc_aligned(size_t size, size_t alignment, bool commit, bool allow_large, mi_memid_t* memid, mi_stats_t* tld_stats) +{ + MI_UNUSED(&_mi_os_get_aligned_hint); // suppress unused warnings + MI_UNUSED(tld_stats); + *memid = _mi_memid_none(); + if (size == 0) return NULL; + size = _mi_os_good_alloc_size(size); + alignment = _mi_align_up(alignment, _mi_os_page_size()); + + bool os_is_large = false; + bool os_is_zero = false; + void* os_base = NULL; + void* p = mi_os_prim_alloc_aligned(size, alignment, commit, allow_large, &os_is_large, &os_is_zero, &os_base, &_mi_stats_main /*tld->stats*/ ); + if (p != NULL) { + *memid = _mi_memid_create_os(commit, os_is_zero, os_is_large); + memid->mem.os.base = os_base; + memid->mem.os.alignment = alignment; + } + return p; +} + +/* ----------------------------------------------------------- + OS aligned allocation with an offset. This is used + for large alignments > MI_ALIGNMENT_MAX. We use a large mimalloc + page where the object can be aligned at an offset from the start of the segment. + As we may need to overallocate, we need to free such pointers using `mi_free_aligned` + to use the actual start of the memory region. +----------------------------------------------------------- */ + +void* _mi_os_alloc_aligned_at_offset(size_t size, size_t alignment, size_t offset, bool commit, bool allow_large, mi_memid_t* memid, mi_stats_t* tld_stats) { + mi_assert(offset <= MI_SEGMENT_SIZE); + mi_assert(offset <= size); + mi_assert((alignment % _mi_os_page_size()) == 0); + *memid = _mi_memid_none(); + if (offset > MI_SEGMENT_SIZE) return NULL; + if (offset == 0) { + // regular aligned allocation + return _mi_os_alloc_aligned(size, alignment, commit, allow_large, memid, tld_stats); + } + else { + // overallocate to align at an offset + const size_t extra = _mi_align_up(offset, alignment) - offset; + const size_t oversize = size + extra; + void* const start = _mi_os_alloc_aligned(oversize, alignment, commit, allow_large, memid, tld_stats); + if (start == NULL) return NULL; + + void* const p = (uint8_t*)start + extra; + mi_assert(_mi_is_aligned((uint8_t*)p + offset, alignment)); + // decommit the overallocation at the start + if (commit && extra > _mi_os_page_size()) { + _mi_os_decommit(start, extra, tld_stats); + } + return p; + } +} + +/* ----------------------------------------------------------- + OS memory API: reset, commit, decommit, protect, unprotect. +----------------------------------------------------------- */ + +// OS page align within a given area, either conservative (pages inside the area only), +// or not (straddling pages outside the area is possible) +static void* mi_os_page_align_areax(bool conservative, void* addr, size_t size, size_t* newsize) { + mi_assert(addr != NULL && size > 0); + if (newsize != NULL) *newsize = 0; + if (size == 0 || addr == NULL) return NULL; + + // page align conservatively within the range + void* start = (conservative ? mi_align_up_ptr(addr, _mi_os_page_size()) + : mi_align_down_ptr(addr, _mi_os_page_size())); + void* end = (conservative ? mi_align_down_ptr((uint8_t*)addr + size, _mi_os_page_size()) + : mi_align_up_ptr((uint8_t*)addr + size, _mi_os_page_size())); + ptrdiff_t diff = (uint8_t*)end - (uint8_t*)start; + if (diff <= 0) return NULL; + + mi_assert_internal((conservative && (size_t)diff <= size) || (!conservative && (size_t)diff >= size)); + if (newsize != NULL) *newsize = (size_t)diff; + return start; +} + +static void* mi_os_page_align_area_conservative(void* addr, size_t size, size_t* newsize) { + return mi_os_page_align_areax(true, addr, size, newsize); +} + +bool _mi_os_commit(void* addr, size_t size, bool* is_zero, mi_stats_t* tld_stats) { + MI_UNUSED(tld_stats); + mi_stats_t* stats = &_mi_stats_main; + if (is_zero != NULL) { *is_zero = false; } + _mi_stat_increase(&stats->committed, size); // use size for precise commit vs. decommit + _mi_stat_counter_increase(&stats->commit_calls, 1); + + // page align range + size_t csize; + void* start = mi_os_page_align_areax(false /* conservative? */, addr, size, &csize); + if (csize == 0) return true; + + // commit + bool os_is_zero = false; + int err = _mi_prim_commit(start, csize, &os_is_zero); + if (err != 0) { + _mi_warning_message("cannot commit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", err, err, start, csize); + return false; + } + if (os_is_zero && is_zero != NULL) { + *is_zero = true; + mi_assert_expensive(mi_mem_is_zero(start, csize)); + } + // note: the following seems required for asan (otherwise `mimalloc-test-stress` fails) + #ifdef MI_TRACK_ASAN + if (os_is_zero) { mi_track_mem_defined(start,csize); } + else { mi_track_mem_undefined(start,csize); } + #endif + return true; +} + +static bool mi_os_decommit_ex(void* addr, size_t size, bool* needs_recommit, mi_stats_t* tld_stats) { + MI_UNUSED(tld_stats); + mi_stats_t* stats = &_mi_stats_main; + mi_assert_internal(needs_recommit!=NULL); + _mi_stat_decrease(&stats->committed, size); + + // page align + size_t csize; + void* start = mi_os_page_align_area_conservative(addr, size, &csize); + if (csize == 0) return true; + + // decommit + *needs_recommit = true; + int err = _mi_prim_decommit(start,csize,needs_recommit); + if (err != 0) { + _mi_warning_message("cannot decommit OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", err, err, start, csize); + } + mi_assert_internal(err == 0); + return (err == 0); +} + +bool _mi_os_decommit(void* addr, size_t size, mi_stats_t* tld_stats) { + bool needs_recommit; + return mi_os_decommit_ex(addr, size, &needs_recommit, tld_stats); +} + + +// Signal to the OS that the address range is no longer in use +// but may be used later again. This will release physical memory +// pages and reduce swapping while keeping the memory committed. +// We page align to a conservative area inside the range to reset. +bool _mi_os_reset(void* addr, size_t size, mi_stats_t* stats) { + // page align conservatively within the range + size_t csize; + void* start = mi_os_page_align_area_conservative(addr, size, &csize); + if (csize == 0) return true; // || _mi_os_is_huge_reserved(addr) + _mi_stat_increase(&stats->reset, csize); + _mi_stat_counter_increase(&stats->reset_calls, 1); + + #if (MI_DEBUG>1) && !MI_SECURE && !MI_TRACK_ENABLED // && !MI_TSAN + memset(start, 0, csize); // pretend it is eagerly reset + #endif + + int err = _mi_prim_reset(start, csize); + if (err != 0) { + _mi_warning_message("cannot reset OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", err, err, start, csize); + } + return (err == 0); +} + + +// either resets or decommits memory, returns true if the memory needs +// to be recommitted if it is to be re-used later on. +bool _mi_os_purge_ex(void* p, size_t size, bool allow_reset, mi_stats_t* stats) +{ + if (mi_option_get(mi_option_purge_delay) < 0) return false; // is purging allowed? + _mi_stat_counter_increase(&stats->purge_calls, 1); + _mi_stat_increase(&stats->purged, size); + + if (mi_option_is_enabled(mi_option_purge_decommits) && // should decommit? + !_mi_preloading()) // don't decommit during preloading (unsafe) + { + bool needs_recommit = true; + mi_os_decommit_ex(p, size, &needs_recommit, stats); + return needs_recommit; + } + else { + if (allow_reset) { // this can sometimes be not allowed if the range is not fully committed + _mi_os_reset(p, size, stats); + } + return false; // needs no recommit + } +} + +// either resets or decommits memory, returns true if the memory needs +// to be recommitted if it is to be re-used later on. +bool _mi_os_purge(void* p, size_t size, mi_stats_t * stats) { + return _mi_os_purge_ex(p, size, true, stats); +} + +// Protect a region in memory to be not accessible. +static bool mi_os_protectx(void* addr, size_t size, bool protect) { + // page align conservatively within the range + size_t csize = 0; + void* start = mi_os_page_align_area_conservative(addr, size, &csize); + if (csize == 0) return false; + /* + if (_mi_os_is_huge_reserved(addr)) { + _mi_warning_message("cannot mprotect memory allocated in huge OS pages\n"); + } + */ + int err = _mi_prim_protect(start,csize,protect); + if (err != 0) { + _mi_warning_message("cannot %s OS memory (error: %d (0x%x), address: %p, size: 0x%zx bytes)\n", (protect ? "protect" : "unprotect"), err, err, start, csize); + } + return (err == 0); +} + +bool _mi_os_protect(void* addr, size_t size) { + return mi_os_protectx(addr, size, true); +} + +bool _mi_os_unprotect(void* addr, size_t size) { + return mi_os_protectx(addr, size, false); +} + + + +/* ---------------------------------------------------------------------------- +Support for allocating huge OS pages (1Gib) that are reserved up-front +and possibly associated with a specific NUMA node. (use `numa_node>=0`) +-----------------------------------------------------------------------------*/ +#define MI_HUGE_OS_PAGE_SIZE (MI_GiB) + + +#if (MI_INTPTR_SIZE >= 8) +// To ensure proper alignment, use our own area for huge OS pages +static mi_decl_cache_align _Atomic(uintptr_t) mi_huge_start; // = 0 + +// Claim an aligned address range for huge pages +static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) { + if (total_size != NULL) *total_size = 0; + const size_t size = pages * MI_HUGE_OS_PAGE_SIZE; + + uintptr_t start = 0; + uintptr_t end = 0; + uintptr_t huge_start = mi_atomic_load_relaxed(&mi_huge_start); + do { + start = huge_start; + if (start == 0) { + // Initialize the start address after the 32TiB area + start = ((uintptr_t)32 << 40); // 32TiB virtual start address + #if (MI_SECURE>0 || MI_DEBUG==0) // security: randomize start of huge pages unless in debug mode + uintptr_t r = _mi_heap_random_next(mi_prim_get_default_heap()); + start = start + ((uintptr_t)MI_HUGE_OS_PAGE_SIZE * ((r>>17) & 0x0FFF)); // (randomly 12bits)*1GiB == between 0 to 4TiB + #endif + } + end = start + size; + mi_assert_internal(end % MI_SEGMENT_SIZE == 0); + } while (!mi_atomic_cas_strong_acq_rel(&mi_huge_start, &huge_start, end)); + + if (total_size != NULL) *total_size = size; + return (uint8_t*)start; +} +#else +static uint8_t* mi_os_claim_huge_pages(size_t pages, size_t* total_size) { + MI_UNUSED(pages); + if (total_size != NULL) *total_size = 0; + return NULL; +} +#endif + +// Allocate MI_SEGMENT_SIZE aligned huge pages +void* _mi_os_alloc_huge_os_pages(size_t pages, int numa_node, mi_msecs_t max_msecs, size_t* pages_reserved, size_t* psize, mi_memid_t* memid) { + *memid = _mi_memid_none(); + if (psize != NULL) *psize = 0; + if (pages_reserved != NULL) *pages_reserved = 0; + size_t size = 0; + uint8_t* start = mi_os_claim_huge_pages(pages, &size); + if (start == NULL) return NULL; // or 32-bit systems + + // Allocate one page at the time but try to place them contiguously + // We allocate one page at the time to be able to abort if it takes too long + // or to at least allocate as many as available on the system. + mi_msecs_t start_t = _mi_clock_start(); + size_t page = 0; + bool all_zero = true; + while (page < pages) { + // allocate a page + bool is_zero = false; + void* addr = start + (page * MI_HUGE_OS_PAGE_SIZE); + void* p = NULL; + int err = _mi_prim_alloc_huge_os_pages(addr, MI_HUGE_OS_PAGE_SIZE, numa_node, &is_zero, &p); + if (!is_zero) { all_zero = false; } + if (err != 0) { + _mi_warning_message("unable to allocate huge OS page (error: %d (0x%x), address: %p, size: %zx bytes)\n", err, err, addr, MI_HUGE_OS_PAGE_SIZE); + break; + } + + // Did we succeed at a contiguous address? + if (p != addr) { + // no success, issue a warning and break + if (p != NULL) { + _mi_warning_message("could not allocate contiguous huge OS page %zu at %p\n", page, addr); + mi_os_prim_free(p, MI_HUGE_OS_PAGE_SIZE, true, &_mi_stats_main); + } + break; + } + + // success, record it + page++; // increase before timeout check (see issue #711) + _mi_stat_increase(&_mi_stats_main.committed, MI_HUGE_OS_PAGE_SIZE); + _mi_stat_increase(&_mi_stats_main.reserved, MI_HUGE_OS_PAGE_SIZE); + + // check for timeout + if (max_msecs > 0) { + mi_msecs_t elapsed = _mi_clock_end(start_t); + if (page >= 1) { + mi_msecs_t estimate = ((elapsed / (page+1)) * pages); + if (estimate > 2*max_msecs) { // seems like we are going to timeout, break + elapsed = max_msecs + 1; + } + } + if (elapsed > max_msecs) { + _mi_warning_message("huge OS page allocation timed out (after allocating %zu page(s))\n", page); + break; + } + } + } + mi_assert_internal(page*MI_HUGE_OS_PAGE_SIZE <= size); + if (pages_reserved != NULL) { *pages_reserved = page; } + if (psize != NULL) { *psize = page * MI_HUGE_OS_PAGE_SIZE; } + if (page != 0) { + mi_assert(start != NULL); + *memid = _mi_memid_create_os(true /* is committed */, all_zero, true /* is_large */); + memid->memkind = MI_MEM_OS_HUGE; + mi_assert(memid->is_pinned); + #ifdef MI_TRACK_ASAN + if (all_zero) { mi_track_mem_defined(start,size); } + #endif + } + return (page == 0 ? NULL : start); +} + +// free every huge page in a range individually (as we allocated per page) +// note: needed with VirtualAlloc but could potentially be done in one go on mmap'd systems. +static void mi_os_free_huge_os_pages(void* p, size_t size, mi_stats_t* stats) { + if (p==NULL || size==0) return; + uint8_t* base = (uint8_t*)p; + while (size >= MI_HUGE_OS_PAGE_SIZE) { + mi_os_prim_free(base, MI_HUGE_OS_PAGE_SIZE, true, stats); + size -= MI_HUGE_OS_PAGE_SIZE; + base += MI_HUGE_OS_PAGE_SIZE; + } +} + +/* ---------------------------------------------------------------------------- +Support NUMA aware allocation +-----------------------------------------------------------------------------*/ + +_Atomic(size_t) _mi_numa_node_count; // = 0 // cache the node count + +size_t _mi_os_numa_node_count_get(void) { + size_t count = mi_atomic_load_acquire(&_mi_numa_node_count); + if (count <= 0) { + long ncount = mi_option_get(mi_option_use_numa_nodes); // given explicitly? + if (ncount > 0) { + count = (size_t)ncount; + } + else { + count = _mi_prim_numa_node_count(); // or detect dynamically + if (count == 0) count = 1; + } + mi_atomic_store_release(&_mi_numa_node_count, count); // save it + _mi_verbose_message("using %zd numa regions\n", count); + } + return count; +} + +int _mi_os_numa_node_get(mi_os_tld_t* tld) { + MI_UNUSED(tld); + size_t numa_count = _mi_os_numa_node_count(); + if (numa_count<=1) return 0; // optimize on single numa node systems: always node 0 + // never more than the node count and >= 0 + size_t numa_node = _mi_prim_numa_node(); + if (numa_node >= numa_count) { numa_node = numa_node % numa_count; } + return (int)numa_node; +} diff --git a/compat/mimalloc/page-queue.c b/compat/mimalloc/page-queue.c new file mode 100644 index 00000000000000..5619a81f9917fe --- /dev/null +++ b/compat/mimalloc/page-queue.c @@ -0,0 +1,332 @@ +/*---------------------------------------------------------------------------- +Copyright (c) 2018-2020, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ----------------------------------------------------------- + Definition of page queues for each block size +----------------------------------------------------------- */ + +#ifndef MI_IN_PAGE_C +#error "this file should be included from 'page.c'" +#endif + +/* ----------------------------------------------------------- + Minimal alignment in machine words (i.e. `sizeof(void*)`) +----------------------------------------------------------- */ + +#if (MI_MAX_ALIGN_SIZE > 4*MI_INTPTR_SIZE) + #error "define alignment for more than 4x word size for this platform" +#elif (MI_MAX_ALIGN_SIZE > 2*MI_INTPTR_SIZE) + #define MI_ALIGN4W // 4 machine words minimal alignment +#elif (MI_MAX_ALIGN_SIZE > MI_INTPTR_SIZE) + #define MI_ALIGN2W // 2 machine words minimal alignment +#else + // ok, default alignment is 1 word +#endif + + +/* ----------------------------------------------------------- + Queue query +----------------------------------------------------------- */ + + +static inline bool mi_page_queue_is_huge(const mi_page_queue_t* pq) { + return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+sizeof(uintptr_t))); +} + +static inline bool mi_page_queue_is_full(const mi_page_queue_t* pq) { + return (pq->block_size == (MI_MEDIUM_OBJ_SIZE_MAX+(2*sizeof(uintptr_t)))); +} + +static inline bool mi_page_queue_is_special(const mi_page_queue_t* pq) { + return (pq->block_size > MI_MEDIUM_OBJ_SIZE_MAX); +} + +/* ----------------------------------------------------------- + Bins +----------------------------------------------------------- */ + +// Return the bin for a given field size. +// Returns MI_BIN_HUGE if the size is too large. +// We use `wsize` for the size in "machine word sizes", +// i.e. byte size == `wsize*sizeof(void*)`. +static inline uint8_t mi_bin(size_t size) { + size_t wsize = _mi_wsize_from_size(size); + uint8_t bin; + if (wsize <= 1) { + bin = 1; + } + #if defined(MI_ALIGN4W) + else if (wsize <= 4) { + bin = (uint8_t)((wsize+1)&~1); // round to double word sizes + } + #elif defined(MI_ALIGN2W) + else if (wsize <= 8) { + bin = (uint8_t)((wsize+1)&~1); // round to double word sizes + } + #else + else if (wsize <= 8) { + bin = (uint8_t)wsize; + } + #endif + else if (wsize > MI_MEDIUM_OBJ_WSIZE_MAX) { + bin = MI_BIN_HUGE; + } + else { + #if defined(MI_ALIGN4W) + if (wsize <= 16) { wsize = (wsize+3)&~3; } // round to 4x word sizes + #endif + wsize--; + // find the highest bit + uint8_t b = (uint8_t)mi_bsr(wsize); // note: wsize != 0 + // and use the top 3 bits to determine the bin (~12.5% worst internal fragmentation). + // - adjust with 3 because we use do not round the first 8 sizes + // which each get an exact bin + bin = ((b << 2) + (uint8_t)((wsize >> (b - 2)) & 0x03)) - 3; + mi_assert_internal(bin < MI_BIN_HUGE); + } + mi_assert_internal(bin > 0 && bin <= MI_BIN_HUGE); + return bin; +} + + + +/* ----------------------------------------------------------- + Queue of pages with free blocks +----------------------------------------------------------- */ + +uint8_t _mi_bin(size_t size) { + return mi_bin(size); +} + +size_t _mi_bin_size(uint8_t bin) { + return _mi_heap_empty.pages[bin].block_size; +} + +// Good size for allocation +size_t mi_good_size(size_t size) mi_attr_noexcept { + if (size <= MI_MEDIUM_OBJ_SIZE_MAX) { + return _mi_bin_size(mi_bin(size)); + } + else { + return _mi_align_up(size,_mi_os_page_size()); + } +} + +#if (MI_DEBUG>1) +static bool mi_page_queue_contains(mi_page_queue_t* queue, const mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_page_t* list = queue->first; + while (list != NULL) { + mi_assert_internal(list->next == NULL || list->next->prev == list); + mi_assert_internal(list->prev == NULL || list->prev->next == list); + if (list == page) break; + list = list->next; + } + return (list == page); +} + +#endif + +#if (MI_DEBUG>1) +static bool mi_heap_contains_queue(const mi_heap_t* heap, const mi_page_queue_t* pq) { + return (pq >= &heap->pages[0] && pq <= &heap->pages[MI_BIN_FULL]); +} +#endif + +static mi_page_queue_t* mi_page_queue_of(const mi_page_t* page) { + uint8_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : mi_bin(page->xblock_size)); + mi_heap_t* heap = mi_page_heap(page); + mi_assert_internal(heap != NULL && bin <= MI_BIN_FULL); + mi_page_queue_t* pq = &heap->pages[bin]; + mi_assert_internal(bin >= MI_BIN_HUGE || page->xblock_size == pq->block_size); + mi_assert_expensive(mi_page_queue_contains(pq, page)); + return pq; +} + +static mi_page_queue_t* mi_heap_page_queue_of(mi_heap_t* heap, const mi_page_t* page) { + uint8_t bin = (mi_page_is_in_full(page) ? MI_BIN_FULL : mi_bin(page->xblock_size)); + mi_assert_internal(bin <= MI_BIN_FULL); + mi_page_queue_t* pq = &heap->pages[bin]; + mi_assert_internal(mi_page_is_in_full(page) || page->xblock_size == pq->block_size); + return pq; +} + +// The current small page array is for efficiency and for each +// small size (up to 256) it points directly to the page for that +// size without having to compute the bin. This means when the +// current free page queue is updated for a small bin, we need to update a +// range of entries in `_mi_page_small_free`. +static inline void mi_heap_queue_first_update(mi_heap_t* heap, const mi_page_queue_t* pq) { + mi_assert_internal(mi_heap_contains_queue(heap,pq)); + size_t size = pq->block_size; + if (size > MI_SMALL_SIZE_MAX) return; + + mi_page_t* page = pq->first; + if (pq->first == NULL) page = (mi_page_t*)&_mi_page_empty; + + // find index in the right direct page array + size_t start; + size_t idx = _mi_wsize_from_size(size); + mi_page_t** pages_free = heap->pages_free_direct; + + if (pages_free[idx] == page) return; // already set + + // find start slot + if (idx<=1) { + start = 0; + } + else { + // find previous size; due to minimal alignment upto 3 previous bins may need to be skipped + uint8_t bin = mi_bin(size); + const mi_page_queue_t* prev = pq - 1; + while( bin == mi_bin(prev->block_size) && prev > &heap->pages[0]) { + prev--; + } + start = 1 + _mi_wsize_from_size(prev->block_size); + if (start > idx) start = idx; + } + + // set size range to the right page + mi_assert(start <= idx); + for (size_t sz = start; sz <= idx; sz++) { + pages_free[sz] = page; + } +} + +/* +static bool mi_page_queue_is_empty(mi_page_queue_t* queue) { + return (queue->first == NULL); +} +*/ + +static void mi_page_queue_remove(mi_page_queue_t* queue, mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_assert_expensive(mi_page_queue_contains(queue, page)); + mi_assert_internal(page->xblock_size == queue->block_size || (page->xblock_size > MI_MEDIUM_OBJ_SIZE_MAX && mi_page_queue_is_huge(queue)) || (mi_page_is_in_full(page) && mi_page_queue_is_full(queue))); + mi_heap_t* heap = mi_page_heap(page); + + if (page->prev != NULL) page->prev->next = page->next; + if (page->next != NULL) page->next->prev = page->prev; + if (page == queue->last) queue->last = page->prev; + if (page == queue->first) { + queue->first = page->next; + // update first + mi_assert_internal(mi_heap_contains_queue(heap, queue)); + mi_heap_queue_first_update(heap,queue); + } + heap->page_count--; + page->next = NULL; + page->prev = NULL; + // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), NULL); + mi_page_set_in_full(page,false); +} + + +static void mi_page_queue_push(mi_heap_t* heap, mi_page_queue_t* queue, mi_page_t* page) { + mi_assert_internal(mi_page_heap(page) == heap); + mi_assert_internal(!mi_page_queue_contains(queue, page)); + #if MI_HUGE_PAGE_ABANDON + mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE); + #endif + mi_assert_internal(page->xblock_size == queue->block_size || + (page->xblock_size > MI_MEDIUM_OBJ_SIZE_MAX) || + (mi_page_is_in_full(page) && mi_page_queue_is_full(queue))); + + mi_page_set_in_full(page, mi_page_queue_is_full(queue)); + // mi_atomic_store_ptr_release(mi_atomic_cast(void*, &page->heap), heap); + page->next = queue->first; + page->prev = NULL; + if (queue->first != NULL) { + mi_assert_internal(queue->first->prev == NULL); + queue->first->prev = page; + queue->first = page; + } + else { + queue->first = queue->last = page; + } + + // update direct + mi_heap_queue_first_update(heap, queue); + heap->page_count++; +} + + +static void mi_page_queue_enqueue_from(mi_page_queue_t* to, mi_page_queue_t* from, mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_assert_expensive(mi_page_queue_contains(from, page)); + mi_assert_expensive(!mi_page_queue_contains(to, page)); + + mi_assert_internal((page->xblock_size == to->block_size && page->xblock_size == from->block_size) || + (page->xblock_size == to->block_size && mi_page_queue_is_full(from)) || + (page->xblock_size == from->block_size && mi_page_queue_is_full(to)) || + (page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_huge(to)) || + (page->xblock_size > MI_LARGE_OBJ_SIZE_MAX && mi_page_queue_is_full(to))); + + mi_heap_t* heap = mi_page_heap(page); + if (page->prev != NULL) page->prev->next = page->next; + if (page->next != NULL) page->next->prev = page->prev; + if (page == from->last) from->last = page->prev; + if (page == from->first) { + from->first = page->next; + // update first + mi_assert_internal(mi_heap_contains_queue(heap, from)); + mi_heap_queue_first_update(heap, from); + } + + page->prev = to->last; + page->next = NULL; + if (to->last != NULL) { + mi_assert_internal(heap == mi_page_heap(to->last)); + to->last->next = page; + to->last = page; + } + else { + to->first = page; + to->last = page; + mi_heap_queue_first_update(heap, to); + } + + mi_page_set_in_full(page, mi_page_queue_is_full(to)); +} + +// Only called from `mi_heap_absorb`. +size_t _mi_page_queue_append(mi_heap_t* heap, mi_page_queue_t* pq, mi_page_queue_t* append) { + mi_assert_internal(mi_heap_contains_queue(heap,pq)); + mi_assert_internal(pq->block_size == append->block_size); + + if (append->first==NULL) return 0; + + // set append pages to new heap and count + size_t count = 0; + for (mi_page_t* page = append->first; page != NULL; page = page->next) { + // inline `mi_page_set_heap` to avoid wrong assertion during absorption; + // in this case it is ok to be delayed freeing since both "to" and "from" heap are still alive. + mi_atomic_store_release(&page->xheap, (uintptr_t)heap); + // set the flag to delayed free (not overriding NEVER_DELAYED_FREE) which has as a + // side effect that it spins until any DELAYED_FREEING is finished. This ensures + // that after appending only the new heap will be used for delayed free operations. + _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, false); + count++; + } + + if (pq->last==NULL) { + // take over afresh + mi_assert_internal(pq->first==NULL); + pq->first = append->first; + pq->last = append->last; + mi_heap_queue_first_update(heap, pq); + } + else { + // append to end + mi_assert_internal(pq->last!=NULL); + mi_assert_internal(append->first!=NULL); + pq->last->next = append->first; + append->first->prev = pq->last; + pq->last = append->last; + } + return count; +} diff --git a/compat/mimalloc/page.c b/compat/mimalloc/page.c new file mode 100644 index 00000000000000..211204aa79e59d --- /dev/null +++ b/compat/mimalloc/page.c @@ -0,0 +1,939 @@ +/*---------------------------------------------------------------------------- +Copyright (c) 2018-2020, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ----------------------------------------------------------- + The core of the allocator. Every segment contains + pages of a certain block size. The main function + exported is `mi_malloc_generic`. +----------------------------------------------------------- */ + +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" + +/* ----------------------------------------------------------- + Definition of page queues for each block size +----------------------------------------------------------- */ + +#define MI_IN_PAGE_C +#include "page-queue.c" +#undef MI_IN_PAGE_C + + +/* ----------------------------------------------------------- + Page helpers +----------------------------------------------------------- */ + +// Index a block in a page +static inline mi_block_t* mi_page_block_at(const mi_page_t* page, void* page_start, size_t block_size, size_t i) { + MI_UNUSED(page); + mi_assert_internal(page != NULL); + mi_assert_internal(i <= page->reserved); + return (mi_block_t*)((uint8_t*)page_start + (i * block_size)); +} + +static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t size, mi_tld_t* tld); +static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld); + +#if (MI_DEBUG>=3) +static size_t mi_page_list_count(mi_page_t* page, mi_block_t* head) { + size_t count = 0; + while (head != NULL) { + mi_assert_internal(page == _mi_ptr_page(head)); + count++; + head = mi_block_next(page, head); + } + return count; +} + +/* +// Start of the page available memory +static inline uint8_t* mi_page_area(const mi_page_t* page) { + return _mi_page_start(_mi_page_segment(page), page, NULL); +} +*/ + +static bool mi_page_list_is_valid(mi_page_t* page, mi_block_t* p) { + size_t psize; + uint8_t* page_area = _mi_page_start(_mi_page_segment(page), page, &psize); + mi_block_t* start = (mi_block_t*)page_area; + mi_block_t* end = (mi_block_t*)(page_area + psize); + while(p != NULL) { + if (p < start || p >= end) return false; + p = mi_block_next(page, p); + } +#if MI_DEBUG>3 // generally too expensive to check this + if (page->free_is_zero) { + const size_t ubsize = mi_page_usable_block_size(page); + for (mi_block_t* block = page->free; block != NULL; block = mi_block_next(page, block)) { + mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t))); + } + } +#endif + return true; +} + +static bool mi_page_is_valid_init(mi_page_t* page) { + mi_assert_internal(page->xblock_size > 0); + mi_assert_internal(page->used <= page->capacity); + mi_assert_internal(page->capacity <= page->reserved); + + mi_segment_t* segment = _mi_page_segment(page); + uint8_t* start = _mi_page_start(segment,page,NULL); + mi_assert_internal(start == _mi_segment_page_start(segment,page,NULL)); + //const size_t bsize = mi_page_block_size(page); + //mi_assert_internal(start + page->capacity*page->block_size == page->top); + + mi_assert_internal(mi_page_list_is_valid(page,page->free)); + mi_assert_internal(mi_page_list_is_valid(page,page->local_free)); + + #if MI_DEBUG>3 // generally too expensive to check this + if (page->free_is_zero) { + const size_t ubsize = mi_page_usable_block_size(page); + for(mi_block_t* block = page->free; block != NULL; block = mi_block_next(page,block)) { + mi_assert_expensive(mi_mem_is_zero(block + 1, ubsize - sizeof(mi_block_t))); + } + } + #endif + + #if !MI_TRACK_ENABLED && !MI_TSAN + mi_block_t* tfree = mi_page_thread_free(page); + mi_assert_internal(mi_page_list_is_valid(page, tfree)); + //size_t tfree_count = mi_page_list_count(page, tfree); + //mi_assert_internal(tfree_count <= page->thread_freed + 1); + #endif + + size_t free_count = mi_page_list_count(page, page->free) + mi_page_list_count(page, page->local_free); + mi_assert_internal(page->used + free_count == page->capacity); + + return true; +} + +extern bool _mi_process_is_initialized; // has mi_process_init been called? + +bool _mi_page_is_valid(mi_page_t* page) { + mi_assert_internal(mi_page_is_valid_init(page)); + #if MI_SECURE + mi_assert_internal(page->keys[0] != 0); + #endif + if (mi_page_heap(page)!=NULL) { + mi_segment_t* segment = _mi_page_segment(page); + + mi_assert_internal(!_mi_process_is_initialized || segment->thread_id==0 || segment->thread_id == mi_page_heap(page)->thread_id); + #if MI_HUGE_PAGE_ABANDON + if (segment->kind != MI_SEGMENT_HUGE) + #endif + { + mi_page_queue_t* pq = mi_page_queue_of(page); + mi_assert_internal(mi_page_queue_contains(pq, page)); + mi_assert_internal(pq->block_size==mi_page_block_size(page) || mi_page_block_size(page) > MI_MEDIUM_OBJ_SIZE_MAX || mi_page_is_in_full(page)); + mi_assert_internal(mi_heap_contains_queue(mi_page_heap(page),pq)); + } + } + return true; +} +#endif + +void _mi_page_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) { + while (!_mi_page_try_use_delayed_free(page, delay, override_never)) { + mi_atomic_yield(); + } +} + +bool _mi_page_try_use_delayed_free(mi_page_t* page, mi_delayed_t delay, bool override_never) { + mi_thread_free_t tfreex; + mi_delayed_t old_delay; + mi_thread_free_t tfree; + size_t yield_count = 0; + do { + tfree = mi_atomic_load_acquire(&page->xthread_free); // note: must acquire as we can break/repeat this loop and not do a CAS; + tfreex = mi_tf_set_delayed(tfree, delay); + old_delay = mi_tf_delayed(tfree); + if mi_unlikely(old_delay == MI_DELAYED_FREEING) { + if (yield_count >= 4) return false; // give up after 4 tries + yield_count++; + mi_atomic_yield(); // delay until outstanding MI_DELAYED_FREEING are done. + // tfree = mi_tf_set_delayed(tfree, MI_NO_DELAYED_FREE); // will cause CAS to busy fail + } + else if (delay == old_delay) { + break; // avoid atomic operation if already equal + } + else if (!override_never && old_delay == MI_NEVER_DELAYED_FREE) { + break; // leave never-delayed flag set + } + } while ((old_delay == MI_DELAYED_FREEING) || + !mi_atomic_cas_weak_release(&page->xthread_free, &tfree, tfreex)); + + return true; // success +} + +/* ----------------------------------------------------------- + Page collect the `local_free` and `thread_free` lists +----------------------------------------------------------- */ + +// Collect the local `thread_free` list using an atomic exchange. +// Note: The exchange must be done atomically as this is used right after +// moving to the full list in `mi_page_collect_ex` and we need to +// ensure that there was no race where the page became unfull just before the move. +static void _mi_page_thread_free_collect(mi_page_t* page) +{ + mi_block_t* head; + mi_thread_free_t tfreex; + mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free); + do { + head = mi_tf_block(tfree); + tfreex = mi_tf_set_block(tfree,NULL); + } while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tfree, tfreex)); + + // return if the list is empty + if (head == NULL) return; + + // find the tail -- also to get a proper count (without data races) + uint32_t max_count = page->capacity; // cannot collect more than capacity + uint32_t count = 1; + mi_block_t* tail = head; + mi_block_t* next; + while ((next = mi_block_next(page,tail)) != NULL && count <= max_count) { + count++; + tail = next; + } + // if `count > max_count` there was a memory corruption (possibly infinite list due to double multi-threaded free) + if (count > max_count) { + _mi_error_message(EFAULT, "corrupted thread-free list\n"); + return; // the thread-free items cannot be freed + } + + // and append the current local free list + mi_block_set_next(page,tail, page->local_free); + page->local_free = head; + + // update counts now + page->used -= count; +} + +void _mi_page_free_collect(mi_page_t* page, bool force) { + mi_assert_internal(page!=NULL); + + // collect the thread free list + if (force || mi_page_thread_free(page) != NULL) { // quick test to avoid an atomic operation + _mi_page_thread_free_collect(page); + } + + // and the local free list + if (page->local_free != NULL) { + if mi_likely(page->free == NULL) { + // usual case + page->free = page->local_free; + page->local_free = NULL; + page->free_is_zero = false; + } + else if (force) { + // append -- only on shutdown (force) as this is a linear operation + mi_block_t* tail = page->local_free; + mi_block_t* next; + while ((next = mi_block_next(page, tail)) != NULL) { + tail = next; + } + mi_block_set_next(page, tail, page->free); + page->free = page->local_free; + page->local_free = NULL; + page->free_is_zero = false; + } + } + + mi_assert_internal(!force || page->local_free == NULL); +} + + + +/* ----------------------------------------------------------- + Page fresh and retire +----------------------------------------------------------- */ + +// called from segments when reclaiming abandoned pages +void _mi_page_reclaim(mi_heap_t* heap, mi_page_t* page) { + mi_assert_expensive(mi_page_is_valid_init(page)); + + mi_assert_internal(mi_page_heap(page) == heap); + mi_assert_internal(mi_page_thread_free_flag(page) != MI_NEVER_DELAYED_FREE); + #if MI_HUGE_PAGE_ABANDON + mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE); + #endif + + // TODO: push on full queue immediately if it is full? + mi_page_queue_t* pq = mi_page_queue(heap, mi_page_block_size(page)); + mi_page_queue_push(heap, pq, page); + mi_assert_expensive(_mi_page_is_valid(page)); +} + +// allocate a fresh page from a segment +static mi_page_t* mi_page_fresh_alloc(mi_heap_t* heap, mi_page_queue_t* pq, size_t block_size, size_t page_alignment) { + #if !MI_HUGE_PAGE_ABANDON + mi_assert_internal(pq != NULL); + mi_assert_internal(mi_heap_contains_queue(heap, pq)); + mi_assert_internal(page_alignment > 0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || block_size == pq->block_size); + #endif + mi_page_t* page = _mi_segment_page_alloc(heap, block_size, page_alignment, &heap->tld->segments, &heap->tld->os); + if (page == NULL) { + // this may be out-of-memory, or an abandoned page was reclaimed (and in our queue) + return NULL; + } + mi_assert_internal(page_alignment >0 || block_size > MI_MEDIUM_OBJ_SIZE_MAX || _mi_page_segment(page)->kind != MI_SEGMENT_HUGE); + mi_assert_internal(pq!=NULL || page->xblock_size != 0); + mi_assert_internal(pq!=NULL || mi_page_block_size(page) >= block_size); + // a fresh page was found, initialize it + const size_t full_block_size = ((pq == NULL || mi_page_queue_is_huge(pq)) ? mi_page_block_size(page) : block_size); // see also: mi_segment_huge_page_alloc + mi_assert_internal(full_block_size >= block_size); + mi_page_init(heap, page, full_block_size, heap->tld); + mi_heap_stat_increase(heap, pages, 1); + if (pq != NULL) { mi_page_queue_push(heap, pq, page); } + mi_assert_expensive(_mi_page_is_valid(page)); + return page; +} + +// Get a fresh page to use +static mi_page_t* mi_page_fresh(mi_heap_t* heap, mi_page_queue_t* pq) { + mi_assert_internal(mi_heap_contains_queue(heap, pq)); + mi_page_t* page = mi_page_fresh_alloc(heap, pq, pq->block_size, 0); + if (page==NULL) return NULL; + mi_assert_internal(pq->block_size==mi_page_block_size(page)); + mi_assert_internal(pq==mi_page_queue(heap, mi_page_block_size(page))); + return page; +} + +/* ----------------------------------------------------------- + Do any delayed frees + (put there by other threads if they deallocated in a full page) +----------------------------------------------------------- */ +void _mi_heap_delayed_free_all(mi_heap_t* heap) { + while (!_mi_heap_delayed_free_partial(heap)) { + mi_atomic_yield(); + } +} + +// returns true if all delayed frees were processed +bool _mi_heap_delayed_free_partial(mi_heap_t* heap) { + // take over the list (note: no atomic exchange since it is often NULL) + mi_block_t* block = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free); + while (block != NULL && !mi_atomic_cas_ptr_weak_acq_rel(mi_block_t, &heap->thread_delayed_free, &block, NULL)) { /* nothing */ }; + bool all_freed = true; + + // and free them all + while(block != NULL) { + mi_block_t* next = mi_block_nextx(heap,block, heap->keys); + // use internal free instead of regular one to keep stats etc correct + if (!_mi_free_delayed_block(block)) { + // we might already start delayed freeing while another thread has not yet + // reset the delayed_freeing flag; in that case delay it further by reinserting the current block + // into the delayed free list + all_freed = false; + mi_block_t* dfree = mi_atomic_load_ptr_relaxed(mi_block_t, &heap->thread_delayed_free); + do { + mi_block_set_nextx(heap, block, dfree, heap->keys); + } while (!mi_atomic_cas_ptr_weak_release(mi_block_t,&heap->thread_delayed_free, &dfree, block)); + } + block = next; + } + return all_freed; +} + +/* ----------------------------------------------------------- + Unfull, abandon, free and retire +----------------------------------------------------------- */ + +// Move a page from the full list back to a regular list +void _mi_page_unfull(mi_page_t* page) { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(mi_page_is_in_full(page)); + if (!mi_page_is_in_full(page)) return; + + mi_heap_t* heap = mi_page_heap(page); + mi_page_queue_t* pqfull = &heap->pages[MI_BIN_FULL]; + mi_page_set_in_full(page, false); // to get the right queue + mi_page_queue_t* pq = mi_heap_page_queue_of(heap, page); + mi_page_set_in_full(page, true); + mi_page_queue_enqueue_from(pq, pqfull, page); +} + +static void mi_page_to_full(mi_page_t* page, mi_page_queue_t* pq) { + mi_assert_internal(pq == mi_page_queue_of(page)); + mi_assert_internal(!mi_page_immediate_available(page)); + mi_assert_internal(!mi_page_is_in_full(page)); + + if (mi_page_is_in_full(page)) return; + mi_page_queue_enqueue_from(&mi_page_heap(page)->pages[MI_BIN_FULL], pq, page); + _mi_page_free_collect(page,false); // try to collect right away in case another thread freed just before MI_USE_DELAYED_FREE was set +} + + +// Abandon a page with used blocks at the end of a thread. +// Note: only call if it is ensured that no references exist from +// the `page->heap->thread_delayed_free` into this page. +// Currently only called through `mi_heap_collect_ex` which ensures this. +void _mi_page_abandon(mi_page_t* page, mi_page_queue_t* pq) { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(pq == mi_page_queue_of(page)); + mi_assert_internal(mi_page_heap(page) != NULL); + + mi_heap_t* pheap = mi_page_heap(page); + + // remove from our page list + mi_segments_tld_t* segments_tld = &pheap->tld->segments; + mi_page_queue_remove(pq, page); + + // page is no longer associated with our heap + mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE); + mi_page_set_heap(page, NULL); + +#if (MI_DEBUG>1) && !MI_TRACK_ENABLED + // check there are no references left.. + for (mi_block_t* block = (mi_block_t*)pheap->thread_delayed_free; block != NULL; block = mi_block_nextx(pheap, block, pheap->keys)) { + mi_assert_internal(_mi_ptr_page(block) != page); + } +#endif + + // and abandon it + mi_assert_internal(mi_page_heap(page) == NULL); + _mi_segment_page_abandon(page,segments_tld); +} + + +// Free a page with no more free blocks +void _mi_page_free(mi_page_t* page, mi_page_queue_t* pq, bool force) { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(pq == mi_page_queue_of(page)); + mi_assert_internal(mi_page_all_free(page)); + mi_assert_internal(mi_page_thread_free_flag(page)!=MI_DELAYED_FREEING); + + // no more aligned blocks in here + mi_page_set_has_aligned(page, false); + + mi_heap_t* heap = mi_page_heap(page); + + // remove from the page list + // (no need to do _mi_heap_delayed_free first as all blocks are already free) + mi_segments_tld_t* segments_tld = &heap->tld->segments; + mi_page_queue_remove(pq, page); + + // and free it + mi_page_set_heap(page,NULL); + _mi_segment_page_free(page, force, segments_tld); +} + +// Retire parameters +#define MI_MAX_RETIRE_SIZE (MI_MEDIUM_OBJ_SIZE_MAX) +#define MI_RETIRE_CYCLES (16) + +// Retire a page with no more used blocks +// Important to not retire too quickly though as new +// allocations might coming. +// Note: called from `mi_free` and benchmarks often +// trigger this due to freeing everything and then +// allocating again so careful when changing this. +void _mi_page_retire(mi_page_t* page) mi_attr_noexcept { + mi_assert_internal(page != NULL); + mi_assert_expensive(_mi_page_is_valid(page)); + mi_assert_internal(mi_page_all_free(page)); + + mi_page_set_has_aligned(page, false); + + // don't retire too often.. + // (or we end up retiring and re-allocating most of the time) + // NOTE: refine this more: we should not retire if this + // is the only page left with free blocks. It is not clear + // how to check this efficiently though... + // for now, we don't retire if it is the only page left of this size class. + mi_page_queue_t* pq = mi_page_queue_of(page); + if mi_likely(page->xblock_size <= MI_MAX_RETIRE_SIZE && !mi_page_queue_is_special(pq)) { // not too large && not full or huge queue? + if (pq->last==page && pq->first==page) { // the only page in the queue? + mi_stat_counter_increase(_mi_stats_main.page_no_retire,1); + page->retire_expire = 1 + (page->xblock_size <= MI_SMALL_OBJ_SIZE_MAX ? MI_RETIRE_CYCLES : MI_RETIRE_CYCLES/4); + mi_heap_t* heap = mi_page_heap(page); + mi_assert_internal(pq >= heap->pages); + const size_t index = pq - heap->pages; + mi_assert_internal(index < MI_BIN_FULL && index < MI_BIN_HUGE); + if (index < heap->page_retired_min) heap->page_retired_min = index; + if (index > heap->page_retired_max) heap->page_retired_max = index; + mi_assert_internal(mi_page_all_free(page)); + return; // dont't free after all + } + } + _mi_page_free(page, pq, false); +} + +// free retired pages: we don't need to look at the entire queues +// since we only retire pages that are at the head position in a queue. +void _mi_heap_collect_retired(mi_heap_t* heap, bool force) { + size_t min = MI_BIN_FULL; + size_t max = 0; + for(size_t bin = heap->page_retired_min; bin <= heap->page_retired_max; bin++) { + mi_page_queue_t* pq = &heap->pages[bin]; + mi_page_t* page = pq->first; + if (page != NULL && page->retire_expire != 0) { + if (mi_page_all_free(page)) { + page->retire_expire--; + if (force || page->retire_expire == 0) { + _mi_page_free(pq->first, pq, force); + } + else { + // keep retired, update min/max + if (bin < min) min = bin; + if (bin > max) max = bin; + } + } + else { + page->retire_expire = 0; + } + } + } + heap->page_retired_min = min; + heap->page_retired_max = max; +} + + +/* ----------------------------------------------------------- + Initialize the initial free list in a page. + In secure mode we initialize a randomized list by + alternating between slices. +----------------------------------------------------------- */ + +#define MI_MAX_SLICE_SHIFT (6) // at most 64 slices +#define MI_MAX_SLICES (1UL << MI_MAX_SLICE_SHIFT) +#define MI_MIN_SLICES (2) + +static void mi_page_free_list_extend_secure(mi_heap_t* const heap, mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) { + MI_UNUSED(stats); + #if (MI_SECURE<=2) + mi_assert_internal(page->free == NULL); + mi_assert_internal(page->local_free == NULL); + #endif + mi_assert_internal(page->capacity + extend <= page->reserved); + mi_assert_internal(bsize == mi_page_block_size(page)); + void* const page_area = _mi_page_start(_mi_page_segment(page), page, NULL); + + // initialize a randomized free list + // set up `slice_count` slices to alternate between + size_t shift = MI_MAX_SLICE_SHIFT; + while ((extend >> shift) == 0) { + shift--; + } + const size_t slice_count = (size_t)1U << shift; + const size_t slice_extend = extend / slice_count; + mi_assert_internal(slice_extend >= 1); + mi_block_t* blocks[MI_MAX_SLICES]; // current start of the slice + size_t counts[MI_MAX_SLICES]; // available objects in the slice + for (size_t i = 0; i < slice_count; i++) { + blocks[i] = mi_page_block_at(page, page_area, bsize, page->capacity + i*slice_extend); + counts[i] = slice_extend; + } + counts[slice_count-1] += (extend % slice_count); // final slice holds the modulus too (todo: distribute evenly?) + + // and initialize the free list by randomly threading through them + // set up first element + const uintptr_t r = _mi_heap_random_next(heap); + size_t current = r % slice_count; + counts[current]--; + mi_block_t* const free_start = blocks[current]; + // and iterate through the rest; use `random_shuffle` for performance + uintptr_t rnd = _mi_random_shuffle(r|1); // ensure not 0 + for (size_t i = 1; i < extend; i++) { + // call random_shuffle only every INTPTR_SIZE rounds + const size_t round = i%MI_INTPTR_SIZE; + if (round == 0) rnd = _mi_random_shuffle(rnd); + // select a random next slice index + size_t next = ((rnd >> 8*round) & (slice_count-1)); + while (counts[next]==0) { // ensure it still has space + next++; + if (next==slice_count) next = 0; + } + // and link the current block to it + counts[next]--; + mi_block_t* const block = blocks[current]; + blocks[current] = (mi_block_t*)((uint8_t*)block + bsize); // bump to the following block + mi_block_set_next(page, block, blocks[next]); // and set next; note: we may have `current == next` + current = next; + } + // prepend to the free list (usually NULL) + mi_block_set_next(page, blocks[current], page->free); // end of the list + page->free = free_start; +} + +static mi_decl_noinline void mi_page_free_list_extend( mi_page_t* const page, const size_t bsize, const size_t extend, mi_stats_t* const stats) +{ + MI_UNUSED(stats); + #if (MI_SECURE <= 2) + mi_assert_internal(page->free == NULL); + mi_assert_internal(page->local_free == NULL); + #endif + mi_assert_internal(page->capacity + extend <= page->reserved); + mi_assert_internal(bsize == mi_page_block_size(page)); + void* const page_area = _mi_page_start(_mi_page_segment(page), page, NULL ); + + mi_block_t* const start = mi_page_block_at(page, page_area, bsize, page->capacity); + + // initialize a sequential free list + mi_block_t* const last = mi_page_block_at(page, page_area, bsize, page->capacity + extend - 1); + mi_block_t* block = start; + while(block <= last) { + mi_block_t* next = (mi_block_t*)((uint8_t*)block + bsize); + mi_block_set_next(page,block,next); + block = next; + } + // prepend to free list (usually `NULL`) + mi_block_set_next(page, last, page->free); + page->free = start; +} + +/* ----------------------------------------------------------- + Page initialize and extend the capacity +----------------------------------------------------------- */ + +#define MI_MAX_EXTEND_SIZE (4*1024) // heuristic, one OS page seems to work well. +#if (MI_SECURE>0) +#define MI_MIN_EXTEND (8*MI_SECURE) // extend at least by this many +#else +#define MI_MIN_EXTEND (4) +#endif + +// Extend the capacity (up to reserved) by initializing a free list +// We do at most `MI_MAX_EXTEND` to avoid touching too much memory +// Note: we also experimented with "bump" allocation on the first +// allocations but this did not speed up any benchmark (due to an +// extra test in malloc? or cache effects?) +static void mi_page_extend_free(mi_heap_t* heap, mi_page_t* page, mi_tld_t* tld) { + MI_UNUSED(tld); + mi_assert_expensive(mi_page_is_valid_init(page)); + #if (MI_SECURE<=2) + mi_assert(page->free == NULL); + mi_assert(page->local_free == NULL); + if (page->free != NULL) return; + #endif + if (page->capacity >= page->reserved) return; + + size_t page_size; + _mi_page_start(_mi_page_segment(page), page, &page_size); + mi_stat_counter_increase(tld->stats.pages_extended, 1); + + // calculate the extend count + const size_t bsize = (page->xblock_size < MI_HUGE_BLOCK_SIZE ? page->xblock_size : page_size); + size_t extend = page->reserved - page->capacity; + mi_assert_internal(extend > 0); + + size_t max_extend = (bsize >= MI_MAX_EXTEND_SIZE ? MI_MIN_EXTEND : MI_MAX_EXTEND_SIZE/(uint32_t)bsize); + if (max_extend < MI_MIN_EXTEND) { max_extend = MI_MIN_EXTEND; } + mi_assert_internal(max_extend > 0); + + if (extend > max_extend) { + // ensure we don't touch memory beyond the page to reduce page commit. + // the `lean` benchmark tests this. Going from 1 to 8 increases rss by 50%. + extend = max_extend; + } + + mi_assert_internal(extend > 0 && extend + page->capacity <= page->reserved); + mi_assert_internal(extend < (1UL<<16)); + + // and append the extend the free list + if (extend < MI_MIN_SLICES || MI_SECURE==0) { //!mi_option_is_enabled(mi_option_secure)) { + mi_page_free_list_extend(page, bsize, extend, &tld->stats ); + } + else { + mi_page_free_list_extend_secure(heap, page, bsize, extend, &tld->stats); + } + // enable the new free list + page->capacity += (uint16_t)extend; + mi_stat_increase(tld->stats.page_committed, extend * bsize); + mi_assert_expensive(mi_page_is_valid_init(page)); +} + +// Initialize a fresh page +static void mi_page_init(mi_heap_t* heap, mi_page_t* page, size_t block_size, mi_tld_t* tld) { + mi_assert(page != NULL); + mi_segment_t* segment = _mi_page_segment(page); + mi_assert(segment != NULL); + mi_assert_internal(block_size > 0); + // set fields + mi_page_set_heap(page, heap); + page->xblock_size = (block_size < MI_HUGE_BLOCK_SIZE ? (uint32_t)block_size : MI_HUGE_BLOCK_SIZE); // initialize before _mi_segment_page_start + size_t page_size; + const void* page_start = _mi_segment_page_start(segment, page, &page_size); + MI_UNUSED(page_start); + mi_track_mem_noaccess(page_start,page_size); + mi_assert_internal(mi_page_block_size(page) <= page_size); + mi_assert_internal(page_size <= page->slice_count*MI_SEGMENT_SLICE_SIZE); + mi_assert_internal(page_size / block_size < (1L<<16)); + page->reserved = (uint16_t)(page_size / block_size); + mi_assert_internal(page->reserved > 0); + #if (MI_PADDING || MI_ENCODE_FREELIST) + page->keys[0] = _mi_heap_random_next(heap); + page->keys[1] = _mi_heap_random_next(heap); + #endif + page->free_is_zero = page->is_zero_init; + #if MI_DEBUG>2 + if (page->is_zero_init) { + mi_track_mem_defined(page_start, page_size); + mi_assert_expensive(mi_mem_is_zero(page_start, page_size)); + } + #endif + + mi_assert_internal(page->is_committed); + mi_assert_internal(page->capacity == 0); + mi_assert_internal(page->free == NULL); + mi_assert_internal(page->used == 0); + mi_assert_internal(page->xthread_free == 0); + mi_assert_internal(page->next == NULL); + mi_assert_internal(page->prev == NULL); + mi_assert_internal(page->retire_expire == 0); + mi_assert_internal(!mi_page_has_aligned(page)); + #if (MI_PADDING || MI_ENCODE_FREELIST) + mi_assert_internal(page->keys[0] != 0); + mi_assert_internal(page->keys[1] != 0); + #endif + mi_assert_expensive(mi_page_is_valid_init(page)); + + // initialize an initial free list + mi_page_extend_free(heap,page,tld); + mi_assert(mi_page_immediate_available(page)); +} + + +/* ----------------------------------------------------------- + Find pages with free blocks +-------------------------------------------------------------*/ + +// Find a page with free blocks of `page->block_size`. +static mi_page_t* mi_page_queue_find_free_ex(mi_heap_t* heap, mi_page_queue_t* pq, bool first_try) +{ + // search through the pages in "next fit" order + #if MI_STAT + size_t count = 0; + #endif + mi_page_t* page = pq->first; + while (page != NULL) + { + mi_page_t* next = page->next; // remember next + #if MI_STAT + count++; + #endif + + // 0. collect freed blocks by us and other threads + _mi_page_free_collect(page, false); + + // 1. if the page contains free blocks, we are done + if (mi_page_immediate_available(page)) { + break; // pick this one + } + + // 2. Try to extend + if (page->capacity < page->reserved) { + mi_page_extend_free(heap, page, heap->tld); + mi_assert_internal(mi_page_immediate_available(page)); + break; + } + + // 3. If the page is completely full, move it to the `mi_pages_full` + // queue so we don't visit long-lived pages too often. + mi_assert_internal(!mi_page_is_in_full(page) && !mi_page_immediate_available(page)); + mi_page_to_full(page, pq); + + page = next; + } // for each page + + mi_heap_stat_counter_increase(heap, searches, count); + + if (page == NULL) { + _mi_heap_collect_retired(heap, false); // perhaps make a page available? + page = mi_page_fresh(heap, pq); + if (page == NULL && first_try) { + // out-of-memory _or_ an abandoned page with free blocks was reclaimed, try once again + page = mi_page_queue_find_free_ex(heap, pq, false); + } + } + else { + mi_assert(pq->first == page); + page->retire_expire = 0; + } + mi_assert_internal(page == NULL || mi_page_immediate_available(page)); + return page; +} + + + +// Find a page with free blocks of `size`. +static inline mi_page_t* mi_find_free_page(mi_heap_t* heap, size_t size) { + mi_page_queue_t* pq = mi_page_queue(heap,size); + mi_page_t* page = pq->first; + if (page != NULL) { + #if (MI_SECURE>=3) // in secure mode, we extend half the time to increase randomness + if (page->capacity < page->reserved && ((_mi_heap_random_next(heap) & 1) == 1)) { + mi_page_extend_free(heap, page, heap->tld); + mi_assert_internal(mi_page_immediate_available(page)); + } + else + #endif + { + _mi_page_free_collect(page,false); + } + + if (mi_page_immediate_available(page)) { + page->retire_expire = 0; + return page; // fast path + } + } + return mi_page_queue_find_free_ex(heap, pq, true); +} + + +/* ----------------------------------------------------------- + Users can register a deferred free function called + when the `free` list is empty. Since the `local_free` + is separate this is deterministically called after + a certain number of allocations. +----------------------------------------------------------- */ + +static mi_deferred_free_fun* volatile deferred_free = NULL; +static _Atomic(void*) deferred_arg; // = NULL + +void _mi_deferred_free(mi_heap_t* heap, bool force) { + heap->tld->heartbeat++; + if (deferred_free != NULL && !heap->tld->recurse) { + heap->tld->recurse = true; + deferred_free(force, heap->tld->heartbeat, mi_atomic_load_ptr_relaxed(void,&deferred_arg)); + heap->tld->recurse = false; + } +} + +void mi_register_deferred_free(mi_deferred_free_fun* fn, void* arg) mi_attr_noexcept { + deferred_free = fn; + mi_atomic_store_ptr_release(void,&deferred_arg, arg); +} + + +/* ----------------------------------------------------------- + General allocation +----------------------------------------------------------- */ + +// Large and huge page allocation. +// Huge pages are allocated directly without being in a queue. +// Because huge pages contain just one block, and the segment contains +// just that page, we always treat them as abandoned and any thread +// that frees the block can free the whole page and segment directly. +// Huge pages are also use if the requested alignment is very large (> MI_ALIGNMENT_MAX). +static mi_page_t* mi_large_huge_page_alloc(mi_heap_t* heap, size_t size, size_t page_alignment) { + size_t block_size = _mi_os_good_alloc_size(size); + mi_assert_internal(mi_bin(block_size) == MI_BIN_HUGE || page_alignment > 0); + bool is_huge = (block_size > MI_LARGE_OBJ_SIZE_MAX || page_alignment > 0); + #if MI_HUGE_PAGE_ABANDON + mi_page_queue_t* pq = (is_huge ? NULL : mi_page_queue(heap, block_size)); + #else + mi_page_queue_t* pq = mi_page_queue(heap, is_huge ? MI_HUGE_BLOCK_SIZE : block_size); // not block_size as that can be low if the page_alignment > 0 + mi_assert_internal(!is_huge || mi_page_queue_is_huge(pq)); + #endif + mi_page_t* page = mi_page_fresh_alloc(heap, pq, block_size, page_alignment); + if (page != NULL) { + mi_assert_internal(mi_page_immediate_available(page)); + + if (is_huge) { + mi_assert_internal(_mi_page_segment(page)->kind == MI_SEGMENT_HUGE); + mi_assert_internal(_mi_page_segment(page)->used==1); + #if MI_HUGE_PAGE_ABANDON + mi_assert_internal(_mi_page_segment(page)->thread_id==0); // abandoned, not in the huge queue + mi_page_set_heap(page, NULL); + #endif + } + else { + mi_assert_internal(_mi_page_segment(page)->kind != MI_SEGMENT_HUGE); + } + + const size_t bsize = mi_page_usable_block_size(page); // note: not `mi_page_block_size` to account for padding + if (bsize <= MI_LARGE_OBJ_SIZE_MAX) { + mi_heap_stat_increase(heap, large, bsize); + mi_heap_stat_counter_increase(heap, large_count, 1); + } + else { + mi_heap_stat_increase(heap, huge, bsize); + mi_heap_stat_counter_increase(heap, huge_count, 1); + } + } + return page; +} + + +// Allocate a page +// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed. +static mi_page_t* mi_find_page(mi_heap_t* heap, size_t size, size_t huge_alignment) mi_attr_noexcept { + // huge allocation? + const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size` + if mi_unlikely(req_size > (MI_MEDIUM_OBJ_SIZE_MAX - MI_PADDING_SIZE) || huge_alignment > 0) { + if mi_unlikely(req_size > PTRDIFF_MAX) { // we don't allocate more than PTRDIFF_MAX (see ) + _mi_error_message(EOVERFLOW, "allocation request is too large (%zu bytes)\n", req_size); + return NULL; + } + else { + return mi_large_huge_page_alloc(heap,size,huge_alignment); + } + } + else { + // otherwise find a page with free blocks in our size segregated queues + #if MI_PADDING + mi_assert_internal(size >= MI_PADDING_SIZE); + #endif + return mi_find_free_page(heap, size); + } +} + +// Generic allocation routine if the fast path (`alloc.c:mi_page_malloc`) does not succeed. +// Note: in debug mode the size includes MI_PADDING_SIZE and might have overflowed. +// The `huge_alignment` is normally 0 but is set to a multiple of MI_SEGMENT_SIZE for +// very large requested alignments in which case we use a huge segment. +void* _mi_malloc_generic(mi_heap_t* heap, size_t size, bool zero, size_t huge_alignment) mi_attr_noexcept +{ + mi_assert_internal(heap != NULL); + + // initialize if necessary + if mi_unlikely(!mi_heap_is_initialized(heap)) { + heap = mi_heap_get_default(); // calls mi_thread_init + if mi_unlikely(!mi_heap_is_initialized(heap)) { return NULL; } + } + mi_assert_internal(mi_heap_is_initialized(heap)); + + // call potential deferred free routines + _mi_deferred_free(heap, false); + + // free delayed frees from other threads (but skip contended ones) + _mi_heap_delayed_free_partial(heap); + + // find (or allocate) a page of the right size + mi_page_t* page = mi_find_page(heap, size, huge_alignment); + if mi_unlikely(page == NULL) { // first time out of memory, try to collect and retry the allocation once more + mi_heap_collect(heap, true /* force */); + page = mi_find_page(heap, size, huge_alignment); + } + + if mi_unlikely(page == NULL) { // out of memory + const size_t req_size = size - MI_PADDING_SIZE; // correct for padding_size in case of an overflow on `size` + _mi_error_message(ENOMEM, "unable to allocate memory (%zu bytes)\n", req_size); + return NULL; + } + + mi_assert_internal(mi_page_immediate_available(page)); + mi_assert_internal(mi_page_block_size(page) >= size); + + // and try again, this time succeeding! (i.e. this should never recurse through _mi_page_malloc) + if mi_unlikely(zero && page->xblock_size == 0) { + // note: we cannot call _mi_page_malloc with zeroing for huge blocks; we zero it afterwards in that case. + void* p = _mi_page_malloc(heap, page, size, false); + mi_assert_internal(p != NULL); + _mi_memzero_aligned(p, mi_page_usable_block_size(page)); + return p; + } + else { + return _mi_page_malloc(heap, page, size, zero); + } +} diff --git a/compat/mimalloc/prim/windows/prim.c b/compat/mimalloc/prim/windows/prim.c new file mode 100644 index 00000000000000..d060833c5b644d --- /dev/null +++ b/compat/mimalloc/prim/windows/prim.c @@ -0,0 +1,622 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +// This file is included in `src/prim/prim.c` + +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" +#include "mimalloc/prim.h" +#include // fputs, stderr + + +//--------------------------------------------- +// Dynamically bind Windows API points for portability +//--------------------------------------------- + +// We use VirtualAlloc2 for aligned allocation, but it is only supported on Windows 10 and Windows Server 2016. +// So, we need to look it up dynamically to run on older systems. (use __stdcall for 32-bit compatibility) +// NtAllocateVirtualAllocEx is used for huge OS page allocation (1GiB) +// We define a minimal MEM_EXTENDED_PARAMETER ourselves in order to be able to compile with older SDK's. +typedef enum MI_MEM_EXTENDED_PARAMETER_TYPE_E { + MiMemExtendedParameterInvalidType = 0, + MiMemExtendedParameterAddressRequirements, + MiMemExtendedParameterNumaNode, + MiMemExtendedParameterPartitionHandle, + MiMemExtendedParameterUserPhysicalHandle, + MiMemExtendedParameterAttributeFlags, + MiMemExtendedParameterMax +} MI_MEM_EXTENDED_PARAMETER_TYPE; + +typedef struct DECLSPEC_ALIGN(8) MI_MEM_EXTENDED_PARAMETER_S { + struct { DWORD64 Type : 8; DWORD64 Reserved : 56; } Type; + union { DWORD64 ULong64; PVOID Pointer; SIZE_T Size; HANDLE Handle; DWORD ULong; } Arg; +} MI_MEM_EXTENDED_PARAMETER; + +typedef struct MI_MEM_ADDRESS_REQUIREMENTS_S { + PVOID LowestStartingAddress; + PVOID HighestEndingAddress; + SIZE_T Alignment; +} MI_MEM_ADDRESS_REQUIREMENTS; + +#define MI_MEM_EXTENDED_PARAMETER_NONPAGED_HUGE 0x00000010 + +#include +typedef PVOID (__stdcall *PVirtualAlloc2)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MI_MEM_EXTENDED_PARAMETER*, ULONG); +typedef NTSTATUS (__stdcall *PNtAllocateVirtualMemoryEx)(HANDLE, PVOID*, SIZE_T*, ULONG, ULONG, MI_MEM_EXTENDED_PARAMETER*, ULONG); +static PVirtualAlloc2 pVirtualAlloc2 = NULL; +static PNtAllocateVirtualMemoryEx pNtAllocateVirtualMemoryEx = NULL; + +// Similarly, GetNumaProcesorNodeEx is only supported since Windows 7 +typedef struct MI_PROCESSOR_NUMBER_S { WORD Group; BYTE Number; BYTE Reserved; } MI_PROCESSOR_NUMBER; + +typedef VOID (__stdcall *PGetCurrentProcessorNumberEx)(MI_PROCESSOR_NUMBER* ProcNumber); +typedef BOOL (__stdcall *PGetNumaProcessorNodeEx)(MI_PROCESSOR_NUMBER* Processor, PUSHORT NodeNumber); +typedef BOOL (__stdcall* PGetNumaNodeProcessorMaskEx)(USHORT Node, PGROUP_AFFINITY ProcessorMask); +typedef BOOL (__stdcall *PGetNumaProcessorNode)(UCHAR Processor, PUCHAR NodeNumber); +static PGetCurrentProcessorNumberEx pGetCurrentProcessorNumberEx = NULL; +static PGetNumaProcessorNodeEx pGetNumaProcessorNodeEx = NULL; +static PGetNumaNodeProcessorMaskEx pGetNumaNodeProcessorMaskEx = NULL; +static PGetNumaProcessorNode pGetNumaProcessorNode = NULL; + +//--------------------------------------------- +// Enable large page support dynamically (if possible) +//--------------------------------------------- + +static bool win_enable_large_os_pages(size_t* large_page_size) +{ + static bool large_initialized = false; + if (large_initialized) return (_mi_os_large_page_size() > 0); + large_initialized = true; + + // Try to see if large OS pages are supported + // To use large pages on Windows, we first need access permission + // Set "Lock pages in memory" permission in the group policy editor + // + unsigned long err = 0; + HANDLE token = NULL; + BOOL ok = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token); + if (ok) { + TOKEN_PRIVILEGES tp; + ok = LookupPrivilegeValue(NULL, TEXT("SeLockMemoryPrivilege"), &tp.Privileges[0].Luid); + if (ok) { + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + ok = AdjustTokenPrivileges(token, FALSE, &tp, 0, (PTOKEN_PRIVILEGES)NULL, 0); + if (ok) { + err = GetLastError(); + ok = (err == ERROR_SUCCESS); + if (ok && large_page_size != NULL) { + *large_page_size = GetLargePageMinimum(); + } + } + } + CloseHandle(token); + } + if (!ok) { + if (err == 0) err = GetLastError(); + _mi_warning_message("cannot enable large OS page support, error %lu\n", err); + } + return (ok!=0); +} + + +//--------------------------------------------- +// Initialize +//--------------------------------------------- + +void _mi_prim_mem_init( mi_os_mem_config_t* config ) +{ + config->has_overcommit = false; + config->must_free_whole = true; + config->has_virtual_reserve = true; + // get the page size + SYSTEM_INFO si; + GetSystemInfo(&si); + if (si.dwPageSize > 0) { config->page_size = si.dwPageSize; } + if (si.dwAllocationGranularity > 0) { config->alloc_granularity = si.dwAllocationGranularity; } + // get the VirtualAlloc2 function + HINSTANCE hDll; + hDll = LoadLibrary(TEXT("kernelbase.dll")); + if (hDll != NULL) { + // use VirtualAlloc2FromApp if possible as it is available to Windows store apps + pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, "VirtualAlloc2FromApp"); + if (pVirtualAlloc2==NULL) pVirtualAlloc2 = (PVirtualAlloc2)(void (*)(void))GetProcAddress(hDll, "VirtualAlloc2"); + FreeLibrary(hDll); + } + // NtAllocateVirtualMemoryEx is used for huge page allocation + hDll = LoadLibrary(TEXT("ntdll.dll")); + if (hDll != NULL) { + pNtAllocateVirtualMemoryEx = (PNtAllocateVirtualMemoryEx)(void (*)(void))GetProcAddress(hDll, "NtAllocateVirtualMemoryEx"); + FreeLibrary(hDll); + } + // Try to use Win7+ numa API + hDll = LoadLibrary(TEXT("kernel32.dll")); + if (hDll != NULL) { + pGetCurrentProcessorNumberEx = (PGetCurrentProcessorNumberEx)(void (*)(void))GetProcAddress(hDll, "GetCurrentProcessorNumberEx"); + pGetNumaProcessorNodeEx = (PGetNumaProcessorNodeEx)(void (*)(void))GetProcAddress(hDll, "GetNumaProcessorNodeEx"); + pGetNumaNodeProcessorMaskEx = (PGetNumaNodeProcessorMaskEx)(void (*)(void))GetProcAddress(hDll, "GetNumaNodeProcessorMaskEx"); + pGetNumaProcessorNode = (PGetNumaProcessorNode)(void (*)(void))GetProcAddress(hDll, "GetNumaProcessorNode"); + FreeLibrary(hDll); + } + if (mi_option_is_enabled(mi_option_allow_large_os_pages) || mi_option_is_enabled(mi_option_reserve_huge_os_pages)) { + win_enable_large_os_pages(&config->large_page_size); + } +} + + +//--------------------------------------------- +// Free +//--------------------------------------------- + +int _mi_prim_free(void* addr, size_t size ) { + MI_UNUSED(size); + DWORD errcode = 0; + bool err = (VirtualFree(addr, 0, MEM_RELEASE) == 0); + if (err) { errcode = GetLastError(); } + if (errcode == ERROR_INVALID_ADDRESS) { + // In mi_os_mem_alloc_aligned the fallback path may have returned a pointer inside + // the memory region returned by VirtualAlloc; in that case we need to free using + // the start of the region. + MEMORY_BASIC_INFORMATION info = { 0 }; + VirtualQuery(addr, &info, sizeof(info)); + if (info.AllocationBase < addr && ((uint8_t*)addr - (uint8_t*)info.AllocationBase) < (ptrdiff_t)MI_SEGMENT_SIZE) { + errcode = 0; + err = (VirtualFree(info.AllocationBase, 0, MEM_RELEASE) == 0); + if (err) { errcode = GetLastError(); } + } + } + return (int)errcode; +} + + +//--------------------------------------------- +// VirtualAlloc +//--------------------------------------------- + +static void* win_virtual_alloc_prim(void* addr, size_t size, size_t try_alignment, DWORD flags) { + #if (MI_INTPTR_SIZE >= 8) + // on 64-bit systems, try to use the virtual address area after 2TiB for 4MiB aligned allocations + if (addr == NULL) { + void* hint = _mi_os_get_aligned_hint(try_alignment,size); + if (hint != NULL) { + void* p = VirtualAlloc(hint, size, flags, PAGE_READWRITE); + if (p != NULL) return p; + _mi_verbose_message("warning: unable to allocate hinted aligned OS memory (%zu bytes, error code: 0x%x, address: %p, alignment: %zu, flags: 0x%x)\n", size, GetLastError(), hint, try_alignment, flags); + // fall through on error + } + } + #endif + // on modern Windows try use VirtualAlloc2 for aligned allocation + if (try_alignment > 1 && (try_alignment % _mi_os_page_size()) == 0 && pVirtualAlloc2 != NULL) { + MI_MEM_ADDRESS_REQUIREMENTS reqs = { 0, 0, 0 }; + reqs.Alignment = try_alignment; + MI_MEM_EXTENDED_PARAMETER param = { {0, 0}, {0} }; + param.Type.Type = MiMemExtendedParameterAddressRequirements; + param.Arg.Pointer = &reqs; + void* p = (*pVirtualAlloc2)(GetCurrentProcess(), addr, size, flags, PAGE_READWRITE, ¶m, 1); + if (p != NULL) return p; + _mi_warning_message("unable to allocate aligned OS memory (%zu bytes, error code: 0x%x, address: %p, alignment: %zu, flags: 0x%x)\n", size, GetLastError(), addr, try_alignment, flags); + // fall through on error + } + // last resort + return VirtualAlloc(addr, size, flags, PAGE_READWRITE); +} + +static void* win_virtual_alloc(void* addr, size_t size, size_t try_alignment, DWORD flags, bool large_only, bool allow_large, bool* is_large) { + mi_assert_internal(!(large_only && !allow_large)); + static _Atomic(size_t) large_page_try_ok; // = 0; + void* p = NULL; + // Try to allocate large OS pages (2MiB) if allowed or required. + if ((large_only || _mi_os_use_large_page(size, try_alignment)) + && allow_large && (flags&MEM_COMMIT)!=0 && (flags&MEM_RESERVE)!=0) { + size_t try_ok = mi_atomic_load_acquire(&large_page_try_ok); + if (!large_only && try_ok > 0) { + // if a large page allocation fails, it seems the calls to VirtualAlloc get very expensive. + // therefore, once a large page allocation failed, we don't try again for `large_page_try_ok` times. + mi_atomic_cas_strong_acq_rel(&large_page_try_ok, &try_ok, try_ok - 1); + } + else { + // large OS pages must always reserve and commit. + *is_large = true; + p = win_virtual_alloc_prim(addr, size, try_alignment, flags | MEM_LARGE_PAGES); + if (large_only) return p; + // fall back to non-large page allocation on error (`p == NULL`). + if (p == NULL) { + mi_atomic_store_release(&large_page_try_ok,10UL); // on error, don't try again for the next N allocations + } + } + } + // Fall back to regular page allocation + if (p == NULL) { + *is_large = ((flags&MEM_LARGE_PAGES) != 0); + p = win_virtual_alloc_prim(addr, size, try_alignment, flags); + } + //if (p == NULL) { _mi_warning_message("unable to allocate OS memory (%zu bytes, error code: 0x%x, address: %p, alignment: %zu, flags: 0x%x, large only: %d, allow large: %d)\n", size, GetLastError(), addr, try_alignment, flags, large_only, allow_large); } + return p; +} + +int _mi_prim_alloc(size_t size, size_t try_alignment, bool commit, bool allow_large, bool* is_large, bool* is_zero, void** addr) { + mi_assert_internal(size > 0 && (size % _mi_os_page_size()) == 0); + mi_assert_internal(commit || !allow_large); + mi_assert_internal(try_alignment > 0); + *is_zero = true; + int flags = MEM_RESERVE; + if (commit) { flags |= MEM_COMMIT; } + *addr = win_virtual_alloc(NULL, size, try_alignment, flags, false, allow_large, is_large); + return (*addr != NULL ? 0 : (int)GetLastError()); +} + + +//--------------------------------------------- +// Commit/Reset/Protect +//--------------------------------------------- +#ifdef _MSC_VER +#pragma warning(disable:6250) // suppress warning calling VirtualFree without MEM_RELEASE (for decommit) +#endif + +int _mi_prim_commit(void* addr, size_t size, bool* is_zero) { + *is_zero = false; + /* + // zero'ing only happens on an initial commit... but checking upfront seems expensive.. + _MEMORY_BASIC_INFORMATION meminfo; _mi_memzero_var(meminfo); + if (VirtualQuery(addr, &meminfo, size) > 0) { + if ((meminfo.State & MEM_COMMIT) == 0) { + *is_zero = true; + } + } + */ + // commit + void* p = VirtualAlloc(addr, size, MEM_COMMIT, PAGE_READWRITE); + if (p == NULL) return (int)GetLastError(); + return 0; +} + +int _mi_prim_decommit(void* addr, size_t size, bool* needs_recommit) { + BOOL ok = VirtualFree(addr, size, MEM_DECOMMIT); + *needs_recommit = true; // for safety, assume always decommitted even in the case of an error. + return (ok ? 0 : (int)GetLastError()); +} + +int _mi_prim_reset(void* addr, size_t size) { + void* p = VirtualAlloc(addr, size, MEM_RESET, PAGE_READWRITE); + mi_assert_internal(p == addr); + #if 0 + if (p != NULL) { + VirtualUnlock(addr,size); // VirtualUnlock after MEM_RESET removes the memory directly from the working set + } + #endif + return (p != NULL ? 0 : (int)GetLastError()); +} + +int _mi_prim_protect(void* addr, size_t size, bool protect) { + DWORD oldprotect = 0; + BOOL ok = VirtualProtect(addr, size, protect ? PAGE_NOACCESS : PAGE_READWRITE, &oldprotect); + return (ok ? 0 : (int)GetLastError()); +} + + +//--------------------------------------------- +// Huge page allocation +//--------------------------------------------- + +static void* _mi_prim_alloc_huge_os_pagesx(void* hint_addr, size_t size, int numa_node) +{ + const DWORD flags = MEM_LARGE_PAGES | MEM_COMMIT | MEM_RESERVE; + + win_enable_large_os_pages(NULL); + + MI_MEM_EXTENDED_PARAMETER params[3] = { {{0,0},{0}},{{0,0},{0}},{{0,0},{0}} }; + // on modern Windows try use NtAllocateVirtualMemoryEx for 1GiB huge pages + static bool mi_huge_pages_available = true; + if (pNtAllocateVirtualMemoryEx != NULL && mi_huge_pages_available) { + params[0].Type.Type = MiMemExtendedParameterAttributeFlags; + params[0].Arg.ULong64 = MI_MEM_EXTENDED_PARAMETER_NONPAGED_HUGE; + ULONG param_count = 1; + if (numa_node >= 0) { + param_count++; + params[1].Type.Type = MiMemExtendedParameterNumaNode; + params[1].Arg.ULong = (unsigned)numa_node; + } + SIZE_T psize = size; + void* base = hint_addr; + NTSTATUS err = (*pNtAllocateVirtualMemoryEx)(GetCurrentProcess(), &base, &psize, flags, PAGE_READWRITE, params, param_count); + if (err == 0 && base != NULL) { + return base; + } + else { + // fall back to regular large pages + mi_huge_pages_available = false; // don't try further huge pages + _mi_warning_message("unable to allocate using huge (1GiB) pages, trying large (2MiB) pages instead (status 0x%lx)\n", err); + } + } + // on modern Windows try use VirtualAlloc2 for numa aware large OS page allocation + if (pVirtualAlloc2 != NULL && numa_node >= 0) { + params[0].Type.Type = MiMemExtendedParameterNumaNode; + params[0].Arg.ULong = (unsigned)numa_node; + return (*pVirtualAlloc2)(GetCurrentProcess(), hint_addr, size, flags, PAGE_READWRITE, params, 1); + } + + // otherwise use regular virtual alloc on older windows + return VirtualAlloc(hint_addr, size, flags, PAGE_READWRITE); +} + +int _mi_prim_alloc_huge_os_pages(void* hint_addr, size_t size, int numa_node, bool* is_zero, void** addr) { + *is_zero = true; + *addr = _mi_prim_alloc_huge_os_pagesx(hint_addr,size,numa_node); + return (*addr != NULL ? 0 : (int)GetLastError()); +} + + +//--------------------------------------------- +// Numa nodes +//--------------------------------------------- + +size_t _mi_prim_numa_node(void) { + USHORT numa_node = 0; + if (pGetCurrentProcessorNumberEx != NULL && pGetNumaProcessorNodeEx != NULL) { + // Extended API is supported + MI_PROCESSOR_NUMBER pnum; + (*pGetCurrentProcessorNumberEx)(&pnum); + USHORT nnode = 0; + BOOL ok = (*pGetNumaProcessorNodeEx)(&pnum, &nnode); + if (ok) { numa_node = nnode; } + } + else if (pGetNumaProcessorNode != NULL) { + // Vista or earlier, use older API that is limited to 64 processors. Issue #277 + DWORD pnum = GetCurrentProcessorNumber(); + UCHAR nnode = 0; + BOOL ok = pGetNumaProcessorNode((UCHAR)pnum, &nnode); + if (ok) { numa_node = nnode; } + } + return numa_node; +} + +size_t _mi_prim_numa_node_count(void) { + ULONG numa_max = 0; + GetNumaHighestNodeNumber(&numa_max); + // find the highest node number that has actual processors assigned to it. Issue #282 + while(numa_max > 0) { + if (pGetNumaNodeProcessorMaskEx != NULL) { + // Extended API is supported + GROUP_AFFINITY affinity; + if ((*pGetNumaNodeProcessorMaskEx)((USHORT)numa_max, &affinity)) { + if (affinity.Mask != 0) break; // found the maximum non-empty node + } + } + else { + // Vista or earlier, use older API that is limited to 64 processors. + ULONGLONG mask; + if (GetNumaNodeProcessorMask((UCHAR)numa_max, &mask)) { + if (mask != 0) break; // found the maximum non-empty node + }; + } + // max node was invalid or had no processor assigned, try again + numa_max--; + } + return ((size_t)numa_max + 1); +} + + +//---------------------------------------------------------------- +// Clock +//---------------------------------------------------------------- + +static mi_msecs_t mi_to_msecs(LARGE_INTEGER t) { + static LARGE_INTEGER mfreq; // = 0 + if (mfreq.QuadPart == 0LL) { + LARGE_INTEGER f; + QueryPerformanceFrequency(&f); + mfreq.QuadPart = f.QuadPart/1000LL; + if (mfreq.QuadPart == 0) mfreq.QuadPart = 1; + } + return (mi_msecs_t)(t.QuadPart / mfreq.QuadPart); +} + +mi_msecs_t _mi_prim_clock_now(void) { + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return mi_to_msecs(t); +} + + +//---------------------------------------------------------------- +// Process Info +//---------------------------------------------------------------- + +#include +#include + +static mi_msecs_t filetime_msecs(const FILETIME* ftime) { + ULARGE_INTEGER i; + i.LowPart = ftime->dwLowDateTime; + i.HighPart = ftime->dwHighDateTime; + mi_msecs_t msecs = (i.QuadPart / 10000); // FILETIME is in 100 nano seconds + return msecs; +} + +typedef BOOL (WINAPI *PGetProcessMemoryInfo)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD); +static PGetProcessMemoryInfo pGetProcessMemoryInfo = NULL; + +void _mi_prim_process_info(mi_process_info_t* pinfo) +{ + FILETIME ct; + FILETIME ut; + FILETIME st; + FILETIME et; + GetProcessTimes(GetCurrentProcess(), &ct, &et, &st, &ut); + pinfo->utime = filetime_msecs(&ut); + pinfo->stime = filetime_msecs(&st); + + // load psapi on demand + if (pGetProcessMemoryInfo == NULL) { + HINSTANCE hDll = LoadLibrary(TEXT("psapi.dll")); + if (hDll != NULL) { + pGetProcessMemoryInfo = (PGetProcessMemoryInfo)(void (*)(void))GetProcAddress(hDll, "GetProcessMemoryInfo"); + } + } + + // get process info + PROCESS_MEMORY_COUNTERS info; + memset(&info, 0, sizeof(info)); + if (pGetProcessMemoryInfo != NULL) { + pGetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info)); + } + pinfo->current_rss = (size_t)info.WorkingSetSize; + pinfo->peak_rss = (size_t)info.PeakWorkingSetSize; + pinfo->current_commit = (size_t)info.PagefileUsage; + pinfo->peak_commit = (size_t)info.PeakPagefileUsage; + pinfo->page_faults = (size_t)info.PageFaultCount; +} + +//---------------------------------------------------------------- +// Output +//---------------------------------------------------------------- + +void _mi_prim_out_stderr( const char* msg ) +{ + // on windows with redirection, the C runtime cannot handle locale dependent output + // after the main thread closes so we use direct console output. + if (!_mi_preloading()) { + // _cputs(msg); // _cputs cannot be used at is aborts if it fails to lock the console + static HANDLE hcon = INVALID_HANDLE_VALUE; + static bool hconIsConsole; + if (hcon == INVALID_HANDLE_VALUE) { + CONSOLE_SCREEN_BUFFER_INFO sbi; + hcon = GetStdHandle(STD_ERROR_HANDLE); + hconIsConsole = ((hcon != INVALID_HANDLE_VALUE) && GetConsoleScreenBufferInfo(hcon, &sbi)); + } + const size_t len = _mi_strlen(msg); + if (len > 0 && len < UINT32_MAX) { + DWORD written = 0; + if (hconIsConsole) { + WriteConsoleA(hcon, msg, (DWORD)len, &written, NULL); + } + else if (hcon != INVALID_HANDLE_VALUE) { + // use direct write if stderr was redirected + WriteFile(hcon, msg, (DWORD)len, &written, NULL); + } + else { + // finally fall back to fputs after all + fputs(msg, stderr); + } + } + } +} + + +//---------------------------------------------------------------- +// Environment +//---------------------------------------------------------------- + +// On Windows use GetEnvironmentVariable instead of getenv to work +// reliably even when this is invoked before the C runtime is initialized. +// i.e. when `_mi_preloading() == true`. +// Note: on windows, environment names are not case sensitive. +bool _mi_prim_getenv(const char* name, char* result, size_t result_size) { + result[0] = 0; + size_t len = GetEnvironmentVariableA(name, result, (DWORD)result_size); + return (len > 0 && len < result_size); +} + + + +//---------------------------------------------------------------- +// Random +//---------------------------------------------------------------- + +#if defined(MI_USE_RTLGENRANDOM) // || defined(__cplusplus) +// We prefer to use BCryptGenRandom instead of (the unofficial) RtlGenRandom but when using +// dynamic overriding, we observed it can raise an exception when compiled with C++, and +// sometimes deadlocks when also running under the VS debugger. +// In contrast, issue #623 implies that on Windows Server 2019 we need to use BCryptGenRandom. +// To be continued.. +#pragma comment (lib,"advapi32.lib") +#define RtlGenRandom SystemFunction036 +mi_decl_externc BOOLEAN NTAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength); + +bool _mi_prim_random_buf(void* buf, size_t buf_len) { + return (RtlGenRandom(buf, (ULONG)buf_len) != 0); +} + +#else + +#ifndef BCRYPT_USE_SYSTEM_PREFERRED_RNG +#define BCRYPT_USE_SYSTEM_PREFERRED_RNG 0x00000002 +#endif + +typedef LONG (NTAPI *PBCryptGenRandom)(HANDLE, PUCHAR, ULONG, ULONG); +static PBCryptGenRandom pBCryptGenRandom = NULL; + +bool _mi_prim_random_buf(void* buf, size_t buf_len) { + if (pBCryptGenRandom == NULL) { + HINSTANCE hDll = LoadLibrary(TEXT("bcrypt.dll")); + if (hDll != NULL) { + pBCryptGenRandom = (PBCryptGenRandom)(void (*)(void))GetProcAddress(hDll, "BCryptGenRandom"); + } + if (pBCryptGenRandom == NULL) return false; + } + return (pBCryptGenRandom(NULL, (PUCHAR)buf, (ULONG)buf_len, BCRYPT_USE_SYSTEM_PREFERRED_RNG) >= 0); +} + +#endif // MI_USE_RTLGENRANDOM + +//---------------------------------------------------------------- +// Thread init/done +//---------------------------------------------------------------- + +#if !defined(MI_SHARED_LIB) + +// use thread local storage keys to detect thread ending +#include +#if (_WIN32_WINNT < 0x600) // before Windows Vista +WINBASEAPI DWORD WINAPI FlsAlloc( _In_opt_ PFLS_CALLBACK_FUNCTION lpCallback ); +WINBASEAPI PVOID WINAPI FlsGetValue( _In_ DWORD dwFlsIndex ); +WINBASEAPI BOOL WINAPI FlsSetValue( _In_ DWORD dwFlsIndex, _In_opt_ PVOID lpFlsData ); +WINBASEAPI BOOL WINAPI FlsFree(_In_ DWORD dwFlsIndex); +#endif + +static DWORD mi_fls_key = (DWORD)(-1); + +static void NTAPI mi_fls_done(PVOID value) { + mi_heap_t* heap = (mi_heap_t*)value; + if (heap != NULL) { + _mi_thread_done(heap); + FlsSetValue(mi_fls_key, NULL); // prevent recursion as _mi_thread_done may set it back to the main heap, issue #672 + } +} + +void _mi_prim_thread_init_auto_done(void) { + mi_fls_key = FlsAlloc(&mi_fls_done); +} + +void _mi_prim_thread_done_auto_done(void) { + // call thread-done on all threads (except the main thread) to prevent + // dangling callback pointer if statically linked with a DLL; Issue #208 + FlsFree(mi_fls_key); +} + +void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) { + mi_assert_internal(mi_fls_key != (DWORD)(-1)); + FlsSetValue(mi_fls_key, heap); +} + +#else + +// Dll; nothing to do as in that case thread_done is handled through the DLL_THREAD_DETACH event. + +void _mi_prim_thread_init_auto_done(void) { +} + +void _mi_prim_thread_done_auto_done(void) { +} + +void _mi_prim_thread_associate_default_heap(mi_heap_t* heap) { + MI_UNUSED(heap); +} + +#endif diff --git a/compat/mimalloc/random.c b/compat/mimalloc/random.c new file mode 100644 index 00000000000000..2a18b5aa992dad --- /dev/null +++ b/compat/mimalloc/random.c @@ -0,0 +1,254 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019-2021, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/prim.h" // _mi_prim_random_buf +#include // memset + +/* ---------------------------------------------------------------------------- +We use our own PRNG to keep predictable performance of random number generation +and to avoid implementations that use a lock. We only use the OS provided +random source to initialize the initial seeds. Since we do not need ultimate +performance but we do rely on the security (for secret cookies in secure mode) +we use a cryptographically secure generator (chacha20). +-----------------------------------------------------------------------------*/ + +#define MI_CHACHA_ROUNDS (20) // perhaps use 12 for better performance? + + +/* ---------------------------------------------------------------------------- +Chacha20 implementation as the original algorithm with a 64-bit nonce +and counter: https://en.wikipedia.org/wiki/Salsa20 +The input matrix has sixteen 32-bit values: +Position 0 to 3: constant key +Position 4 to 11: the key +Position 12 to 13: the counter. +Position 14 to 15: the nonce. + +The implementation uses regular C code which compiles very well on modern compilers. +(gcc x64 has no register spills, and clang 6+ uses SSE instructions) +-----------------------------------------------------------------------------*/ + +static inline uint32_t rotl(uint32_t x, uint32_t shift) { + return (x << shift) | (x >> (32 - shift)); +} + +static inline void qround(uint32_t x[16], size_t a, size_t b, size_t c, size_t d) { + x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 16); + x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 12); + x[a] += x[b]; x[d] = rotl(x[d] ^ x[a], 8); + x[c] += x[d]; x[b] = rotl(x[b] ^ x[c], 7); +} + +static void chacha_block(mi_random_ctx_t* ctx) +{ + // scramble into `x` + uint32_t x[16]; + for (size_t i = 0; i < 16; i++) { + x[i] = ctx->input[i]; + } + for (size_t i = 0; i < MI_CHACHA_ROUNDS; i += 2) { + qround(x, 0, 4, 8, 12); + qround(x, 1, 5, 9, 13); + qround(x, 2, 6, 10, 14); + qround(x, 3, 7, 11, 15); + qround(x, 0, 5, 10, 15); + qround(x, 1, 6, 11, 12); + qround(x, 2, 7, 8, 13); + qround(x, 3, 4, 9, 14); + } + + // add scrambled data to the initial state + for (size_t i = 0; i < 16; i++) { + ctx->output[i] = x[i] + ctx->input[i]; + } + ctx->output_available = 16; + + // increment the counter for the next round + ctx->input[12] += 1; + if (ctx->input[12] == 0) { + ctx->input[13] += 1; + if (ctx->input[13] == 0) { // and keep increasing into the nonce + ctx->input[14] += 1; + } + } +} + +static uint32_t chacha_next32(mi_random_ctx_t* ctx) { + if (ctx->output_available <= 0) { + chacha_block(ctx); + ctx->output_available = 16; // (assign again to suppress static analysis warning) + } + const uint32_t x = ctx->output[16 - ctx->output_available]; + ctx->output[16 - ctx->output_available] = 0; // reset once the data is handed out + ctx->output_available--; + return x; +} + +static inline uint32_t read32(const uint8_t* p, size_t idx32) { + const size_t i = 4*idx32; + return ((uint32_t)p[i+0] | (uint32_t)p[i+1] << 8 | (uint32_t)p[i+2] << 16 | (uint32_t)p[i+3] << 24); +} + +static void chacha_init(mi_random_ctx_t* ctx, const uint8_t key[32], uint64_t nonce) +{ + // since we only use chacha for randomness (and not encryption) we + // do not _need_ to read 32-bit values as little endian but we do anyways + // just for being compatible :-) + memset(ctx, 0, sizeof(*ctx)); + for (size_t i = 0; i < 4; i++) { + const uint8_t* sigma = (uint8_t*)"expand 32-byte k"; + ctx->input[i] = read32(sigma,i); + } + for (size_t i = 0; i < 8; i++) { + ctx->input[i + 4] = read32(key,i); + } + ctx->input[12] = 0; + ctx->input[13] = 0; + ctx->input[14] = (uint32_t)nonce; + ctx->input[15] = (uint32_t)(nonce >> 32); +} + +static void chacha_split(mi_random_ctx_t* ctx, uint64_t nonce, mi_random_ctx_t* ctx_new) { + memset(ctx_new, 0, sizeof(*ctx_new)); + _mi_memcpy(ctx_new->input, ctx->input, sizeof(ctx_new->input)); + ctx_new->input[12] = 0; + ctx_new->input[13] = 0; + ctx_new->input[14] = (uint32_t)nonce; + ctx_new->input[15] = (uint32_t)(nonce >> 32); + mi_assert_internal(ctx->input[14] != ctx_new->input[14] || ctx->input[15] != ctx_new->input[15]); // do not reuse nonces! + chacha_block(ctx_new); +} + + +/* ---------------------------------------------------------------------------- +Random interface +-----------------------------------------------------------------------------*/ + +#if MI_DEBUG>1 +static bool mi_random_is_initialized(mi_random_ctx_t* ctx) { + return (ctx != NULL && ctx->input[0] != 0); +} +#endif + +void _mi_random_split(mi_random_ctx_t* ctx, mi_random_ctx_t* ctx_new) { + mi_assert_internal(mi_random_is_initialized(ctx)); + mi_assert_internal(ctx != ctx_new); + chacha_split(ctx, (uintptr_t)ctx_new /*nonce*/, ctx_new); +} + +uintptr_t _mi_random_next(mi_random_ctx_t* ctx) { + mi_assert_internal(mi_random_is_initialized(ctx)); + #if MI_INTPTR_SIZE <= 4 + return chacha_next32(ctx); + #elif MI_INTPTR_SIZE == 8 + return (((uintptr_t)chacha_next32(ctx) << 32) | chacha_next32(ctx)); + #else + # error "define mi_random_next for this platform" + #endif +} + + +/* ---------------------------------------------------------------------------- +To initialize a fresh random context. +If we cannot get good randomness, we fall back to weak randomness based on a timer and ASLR. +-----------------------------------------------------------------------------*/ + +uintptr_t _mi_os_random_weak(uintptr_t extra_seed) { + uintptr_t x = (uintptr_t)&_mi_os_random_weak ^ extra_seed; // ASLR makes the address random + x ^= _mi_prim_clock_now(); + // and do a few randomization steps + uintptr_t max = ((x ^ (x >> 17)) & 0x0F) + 1; + for (uintptr_t i = 0; i < max; i++) { + x = _mi_random_shuffle(x); + } + mi_assert_internal(x != 0); + return x; +} + +static void mi_random_init_ex(mi_random_ctx_t* ctx, bool use_weak) { + uint8_t key[32]; + if (use_weak || !_mi_prim_random_buf(key, sizeof(key))) { + // if we fail to get random data from the OS, we fall back to a + // weak random source based on the current time + #if !defined(__wasi__) + if (!use_weak) { _mi_warning_message("unable to use secure randomness\n"); } + #endif + uintptr_t x = _mi_os_random_weak(0); + for (size_t i = 0; i < 8; i++) { // key is eight 32-bit words. + x = _mi_random_shuffle(x); + ((uint32_t*)key)[i] = (uint32_t)x; + } + ctx->weak = true; + } + else { + ctx->weak = false; + } + chacha_init(ctx, key, (uintptr_t)ctx /*nonce*/ ); +} + +void _mi_random_init(mi_random_ctx_t* ctx) { + mi_random_init_ex(ctx, false); +} + +void _mi_random_init_weak(mi_random_ctx_t * ctx) { + mi_random_init_ex(ctx, true); +} + +void _mi_random_reinit_if_weak(mi_random_ctx_t * ctx) { + if (ctx->weak) { + _mi_random_init(ctx); + } +} + +/* -------------------------------------------------------- +test vectors from +----------------------------------------------------------- */ +/* +static bool array_equals(uint32_t* x, uint32_t* y, size_t n) { + for (size_t i = 0; i < n; i++) { + if (x[i] != y[i]) return false; + } + return true; +} +static void chacha_test(void) +{ + uint32_t x[4] = { 0x11111111, 0x01020304, 0x9b8d6f43, 0x01234567 }; + uint32_t x_out[4] = { 0xea2a92f4, 0xcb1cf8ce, 0x4581472e, 0x5881c4bb }; + qround(x, 0, 1, 2, 3); + mi_assert_internal(array_equals(x, x_out, 4)); + + uint32_t y[16] = { + 0x879531e0, 0xc5ecf37d, 0x516461b1, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0x2a5f714c, + 0x53372767, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0x3d631689, 0x2098d9d6, 0x91dbd320 }; + uint32_t y_out[16] = { + 0x879531e0, 0xc5ecf37d, 0xbdb886dc, 0xc9a62f8a, + 0x44c20ef3, 0x3390af7f, 0xd9fc690b, 0xcfacafd2, + 0xe46bea80, 0xb00a5631, 0x974c541a, 0x359e9963, + 0x5c971061, 0xccc07c79, 0x2098d9d6, 0x91dbd320 }; + qround(y, 2, 7, 8, 13); + mi_assert_internal(array_equals(y, y_out, 16)); + + mi_random_ctx_t r = { + { 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, + 0x00000001, 0x09000000, 0x4a000000, 0x00000000 }, + {0}, + 0 + }; + uint32_t r_out[16] = { + 0xe4e7f110, 0x15593bd1, 0x1fdd0f50, 0xc47120a3, + 0xc7f4d1c7, 0x0368c033, 0x9aaa2204, 0x4e6cd4c3, + 0x466482d2, 0x09aa9f07, 0x05d7c214, 0xa2028bd9, + 0xd19c12b5, 0xb94e16de, 0xe883d0cb, 0x4e3c50a2 }; + chacha_block(&r); + mi_assert_internal(array_equals(r.output, r_out, 16)); +} +*/ diff --git a/compat/mimalloc/segment-cache.c b/compat/mimalloc/segment-cache.c new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/compat/mimalloc/segment-map.c b/compat/mimalloc/segment-map.c new file mode 100644 index 00000000000000..3cd2127e56c1a7 --- /dev/null +++ b/compat/mimalloc/segment-map.c @@ -0,0 +1,153 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2019-2023, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ + +/* ----------------------------------------------------------- + The following functions are to reliably find the segment or + block that encompasses any pointer p (or NULL if it is not + in any of our segments). + We maintain a bitmap of all memory with 1 bit per MI_SEGMENT_SIZE (64MiB) + set to 1 if it contains the segment meta data. +----------------------------------------------------------- */ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" + +#if (MI_INTPTR_SIZE==8) +#define MI_MAX_ADDRESS ((size_t)40 << 40) // 40TB (to include huge page areas) +#else +#define MI_MAX_ADDRESS ((size_t)2 << 30) // 2Gb +#endif + +#define MI_SEGMENT_MAP_BITS (MI_MAX_ADDRESS / MI_SEGMENT_SIZE) +#define MI_SEGMENT_MAP_SIZE (MI_SEGMENT_MAP_BITS / 8) +#define MI_SEGMENT_MAP_WSIZE (MI_SEGMENT_MAP_SIZE / MI_INTPTR_SIZE) + +static _Atomic(uintptr_t) mi_segment_map[MI_SEGMENT_MAP_WSIZE + 1]; // 2KiB per TB with 64MiB segments + +static size_t mi_segment_map_index_of(const mi_segment_t* segment, size_t* bitidx) { + mi_assert_internal(_mi_ptr_segment(segment + 1) == segment); // is it aligned on MI_SEGMENT_SIZE? + if ((uintptr_t)segment >= MI_MAX_ADDRESS) { + *bitidx = 0; + return MI_SEGMENT_MAP_WSIZE; + } + else { + const uintptr_t segindex = ((uintptr_t)segment) / MI_SEGMENT_SIZE; + *bitidx = segindex % MI_INTPTR_BITS; + const size_t mapindex = segindex / MI_INTPTR_BITS; + mi_assert_internal(mapindex < MI_SEGMENT_MAP_WSIZE); + return mapindex; + } +} + +void _mi_segment_map_allocated_at(const mi_segment_t* segment) { + size_t bitidx; + size_t index = mi_segment_map_index_of(segment, &bitidx); + mi_assert_internal(index <= MI_SEGMENT_MAP_WSIZE); + if (index==MI_SEGMENT_MAP_WSIZE) return; + uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]); + uintptr_t newmask; + do { + newmask = (mask | ((uintptr_t)1 << bitidx)); + } while (!mi_atomic_cas_weak_release(&mi_segment_map[index], &mask, newmask)); +} + +void _mi_segment_map_freed_at(const mi_segment_t* segment) { + size_t bitidx; + size_t index = mi_segment_map_index_of(segment, &bitidx); + mi_assert_internal(index <= MI_SEGMENT_MAP_WSIZE); + if (index == MI_SEGMENT_MAP_WSIZE) return; + uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]); + uintptr_t newmask; + do { + newmask = (mask & ~((uintptr_t)1 << bitidx)); + } while (!mi_atomic_cas_weak_release(&mi_segment_map[index], &mask, newmask)); +} + +// Determine the segment belonging to a pointer or NULL if it is not in a valid segment. +static mi_segment_t* _mi_segment_of(const void* p) { + if (p == NULL) return NULL; + mi_segment_t* segment = _mi_ptr_segment(p); + mi_assert_internal(segment != NULL); + size_t bitidx; + size_t index = mi_segment_map_index_of(segment, &bitidx); + // fast path: for any pointer to valid small/medium/large object or first MI_SEGMENT_SIZE in huge + const uintptr_t mask = mi_atomic_load_relaxed(&mi_segment_map[index]); + if mi_likely((mask & ((uintptr_t)1 << bitidx)) != 0) { + return segment; // yes, allocated by us + } + if (index==MI_SEGMENT_MAP_WSIZE) return NULL; + + // TODO: maintain max/min allocated range for efficiency for more efficient rejection of invalid pointers? + + // search downwards for the first segment in case it is an interior pointer + // could be slow but searches in MI_INTPTR_SIZE * MI_SEGMENT_SIZE (512MiB) steps trough + // valid huge objects + // note: we could maintain a lowest index to speed up the path for invalid pointers? + size_t lobitidx; + size_t loindex; + uintptr_t lobits = mask & (((uintptr_t)1 << bitidx) - 1); + if (lobits != 0) { + loindex = index; + lobitidx = mi_bsr(lobits); // lobits != 0 + } + else if (index == 0) { + return NULL; + } + else { + mi_assert_internal(index > 0); + uintptr_t lomask = mask; + loindex = index; + do { + loindex--; + lomask = mi_atomic_load_relaxed(&mi_segment_map[loindex]); + } while (lomask != 0 && loindex > 0); + if (lomask == 0) return NULL; + lobitidx = mi_bsr(lomask); // lomask != 0 + } + mi_assert_internal(loindex < MI_SEGMENT_MAP_WSIZE); + // take difference as the addresses could be larger than the MAX_ADDRESS space. + size_t diff = (((index - loindex) * (8*MI_INTPTR_SIZE)) + bitidx - lobitidx) * MI_SEGMENT_SIZE; + segment = (mi_segment_t*)((uint8_t*)segment - diff); + + if (segment == NULL) return NULL; + mi_assert_internal((void*)segment < p); + bool cookie_ok = (_mi_ptr_cookie(segment) == segment->cookie); + mi_assert_internal(cookie_ok); + if mi_unlikely(!cookie_ok) return NULL; + if (((uint8_t*)segment + mi_segment_size(segment)) <= (uint8_t*)p) return NULL; // outside the range + mi_assert_internal(p >= (void*)segment && (uint8_t*)p < (uint8_t*)segment + mi_segment_size(segment)); + return segment; +} + +// Is this a valid pointer in our heap? +static bool mi_is_valid_pointer(const void* p) { + return ((_mi_segment_of(p) != NULL) || (_mi_arena_contains(p))); +} + +mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept { + return mi_is_valid_pointer(p); +} + +/* +// Return the full segment range belonging to a pointer +static void* mi_segment_range_of(const void* p, size_t* size) { + mi_segment_t* segment = _mi_segment_of(p); + if (segment == NULL) { + if (size != NULL) *size = 0; + return NULL; + } + else { + if (size != NULL) *size = segment->segment_size; + return segment; + } + mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld)); + mi_assert_internal(page == NULL || (mi_segment_page_size(_mi_page_segment(page)) - (MI_SECURE == 0 ? 0 : _mi_os_page_size())) >= block_size); + mi_reset_delayed(tld); + mi_assert_internal(page == NULL || mi_page_not_in_queue(page, tld)); + return page; +} +*/ diff --git a/compat/mimalloc/segment.c b/compat/mimalloc/segment.c new file mode 100644 index 00000000000000..6b901f6cc80f13 --- /dev/null +++ b/compat/mimalloc/segment.c @@ -0,0 +1,1617 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2020, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" + +#include // memset +#include + +#define MI_PAGE_HUGE_ALIGN (256*1024) + +static void mi_segment_try_purge(mi_segment_t* segment, bool force, mi_stats_t* stats); + + +// ------------------------------------------------------------------- +// commit mask +// ------------------------------------------------------------------- + +static bool mi_commit_mask_all_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + if ((commit->mask[i] & cm->mask[i]) != cm->mask[i]) return false; + } + return true; +} + +static bool mi_commit_mask_any_set(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + if ((commit->mask[i] & cm->mask[i]) != 0) return true; + } + return false; +} + +static void mi_commit_mask_create_intersect(const mi_commit_mask_t* commit, const mi_commit_mask_t* cm, mi_commit_mask_t* res) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + res->mask[i] = (commit->mask[i] & cm->mask[i]); + } +} + +static void mi_commit_mask_clear(mi_commit_mask_t* res, const mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + res->mask[i] &= ~(cm->mask[i]); + } +} + +static void mi_commit_mask_set(mi_commit_mask_t* res, const mi_commit_mask_t* cm) { + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + res->mask[i] |= cm->mask[i]; + } +} + +static void mi_commit_mask_create(size_t bitidx, size_t bitcount, mi_commit_mask_t* cm) { + mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS); + mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS); + if (bitcount == MI_COMMIT_MASK_BITS) { + mi_assert_internal(bitidx==0); + mi_commit_mask_create_full(cm); + } + else if (bitcount == 0) { + mi_commit_mask_create_empty(cm); + } + else { + mi_commit_mask_create_empty(cm); + size_t i = bitidx / MI_COMMIT_MASK_FIELD_BITS; + size_t ofs = bitidx % MI_COMMIT_MASK_FIELD_BITS; + while (bitcount > 0) { + mi_assert_internal(i < MI_COMMIT_MASK_FIELD_COUNT); + size_t avail = MI_COMMIT_MASK_FIELD_BITS - ofs; + size_t count = (bitcount > avail ? avail : bitcount); + size_t mask = (count >= MI_COMMIT_MASK_FIELD_BITS ? ~((size_t)0) : (((size_t)1 << count) - 1) << ofs); + cm->mask[i] = mask; + bitcount -= count; + ofs = 0; + i++; + } + } +} + +size_t _mi_commit_mask_committed_size(const mi_commit_mask_t* cm, size_t total) { + mi_assert_internal((total%MI_COMMIT_MASK_BITS)==0); + size_t count = 0; + for (size_t i = 0; i < MI_COMMIT_MASK_FIELD_COUNT; i++) { + size_t mask = cm->mask[i]; + if (~mask == 0) { + count += MI_COMMIT_MASK_FIELD_BITS; + } + else { + for (; mask != 0; mask >>= 1) { // todo: use popcount + if ((mask&1)!=0) count++; + } + } + } + // we use total since for huge segments each commit bit may represent a larger size + return ((total / MI_COMMIT_MASK_BITS) * count); +} + + +size_t _mi_commit_mask_next_run(const mi_commit_mask_t* cm, size_t* idx) { + size_t i = (*idx) / MI_COMMIT_MASK_FIELD_BITS; + size_t ofs = (*idx) % MI_COMMIT_MASK_FIELD_BITS; + size_t mask = 0; + // find first ones + while (i < MI_COMMIT_MASK_FIELD_COUNT) { + mask = cm->mask[i]; + mask >>= ofs; + if (mask != 0) { + while ((mask&1) == 0) { + mask >>= 1; + ofs++; + } + break; + } + i++; + ofs = 0; + } + if (i >= MI_COMMIT_MASK_FIELD_COUNT) { + // not found + *idx = MI_COMMIT_MASK_BITS; + return 0; + } + else { + // found, count ones + size_t count = 0; + *idx = (i*MI_COMMIT_MASK_FIELD_BITS) + ofs; + do { + mi_assert_internal(ofs < MI_COMMIT_MASK_FIELD_BITS && (mask&1) == 1); + do { + count++; + mask >>= 1; + } while ((mask&1) == 1); + if ((((*idx + count) % MI_COMMIT_MASK_FIELD_BITS) == 0)) { + i++; + if (i >= MI_COMMIT_MASK_FIELD_COUNT) break; + mask = cm->mask[i]; + ofs = 0; + } + } while ((mask&1) == 1); + mi_assert_internal(count > 0); + return count; + } +} + + +/* -------------------------------------------------------------------------------- + Segment allocation + + If a thread ends, it "abandons" pages with used blocks + and there is an abandoned segment list whose segments can + be reclaimed by still running threads, much like work-stealing. +-------------------------------------------------------------------------------- */ + + +/* ----------------------------------------------------------- + Slices +----------------------------------------------------------- */ + + +static const mi_slice_t* mi_segment_slices_end(const mi_segment_t* segment) { + return &segment->slices[segment->slice_entries]; +} + +static uint8_t* mi_slice_start(const mi_slice_t* slice) { + mi_segment_t* segment = _mi_ptr_segment(slice); + mi_assert_internal(slice >= segment->slices && slice < mi_segment_slices_end(segment)); + return ((uint8_t*)segment + ((slice - segment->slices)*MI_SEGMENT_SLICE_SIZE)); +} + + +/* ----------------------------------------------------------- + Bins +----------------------------------------------------------- */ +// Use bit scan forward to quickly find the first zero bit if it is available + +static inline size_t mi_slice_bin8(size_t slice_count) { + if (slice_count<=1) return slice_count; + mi_assert_internal(slice_count <= MI_SLICES_PER_SEGMENT); + slice_count--; + size_t s = mi_bsr(slice_count); // slice_count > 1 + if (s <= 2) return slice_count + 1; + size_t bin = ((s << 2) | ((slice_count >> (s - 2))&0x03)) - 4; + return bin; +} + +static inline size_t mi_slice_bin(size_t slice_count) { + mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_SEGMENT_SIZE); + mi_assert_internal(mi_slice_bin8(MI_SLICES_PER_SEGMENT) <= MI_SEGMENT_BIN_MAX); + size_t bin = mi_slice_bin8(slice_count); + mi_assert_internal(bin <= MI_SEGMENT_BIN_MAX); + return bin; +} + +static inline size_t mi_slice_index(const mi_slice_t* slice) { + mi_segment_t* segment = _mi_ptr_segment(slice); + ptrdiff_t index = slice - segment->slices; + mi_assert_internal(index >= 0 && index < (ptrdiff_t)segment->slice_entries); + return index; +} + + +/* ----------------------------------------------------------- + Slice span queues +----------------------------------------------------------- */ + +static void mi_span_queue_push(mi_span_queue_t* sq, mi_slice_t* slice) { + // todo: or push to the end? + mi_assert_internal(slice->prev == NULL && slice->next==NULL); + slice->prev = NULL; // paranoia + slice->next = sq->first; + sq->first = slice; + if (slice->next != NULL) slice->next->prev = slice; + else sq->last = slice; + slice->xblock_size = 0; // free +} + +static mi_span_queue_t* mi_span_queue_for(size_t slice_count, mi_segments_tld_t* tld) { + size_t bin = mi_slice_bin(slice_count); + mi_span_queue_t* sq = &tld->spans[bin]; + mi_assert_internal(sq->slice_count >= slice_count); + return sq; +} + +static void mi_span_queue_delete(mi_span_queue_t* sq, mi_slice_t* slice) { + mi_assert_internal(slice->xblock_size==0 && slice->slice_count>0 && slice->slice_offset==0); + // should work too if the queue does not contain slice (which can happen during reclaim) + if (slice->prev != NULL) slice->prev->next = slice->next; + if (slice == sq->first) sq->first = slice->next; + if (slice->next != NULL) slice->next->prev = slice->prev; + if (slice == sq->last) sq->last = slice->prev; + slice->prev = NULL; + slice->next = NULL; + slice->xblock_size = 1; // no more free +} + + +/* ----------------------------------------------------------- + Invariant checking +----------------------------------------------------------- */ + +static bool mi_slice_is_used(const mi_slice_t* slice) { + return (slice->xblock_size > 0); +} + + +#if (MI_DEBUG>=3) +static bool mi_span_queue_contains(mi_span_queue_t* sq, mi_slice_t* slice) { + for (mi_slice_t* s = sq->first; s != NULL; s = s->next) { + if (s==slice) return true; + } + return false; +} + +static bool mi_segment_is_valid(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_assert_internal(segment != NULL); + mi_assert_internal(_mi_ptr_cookie(segment) == segment->cookie); + mi_assert_internal(segment->abandoned <= segment->used); + mi_assert_internal(segment->thread_id == 0 || segment->thread_id == _mi_thread_id()); + mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask)); // can only decommit committed blocks + //mi_assert_internal(segment->segment_info_size % MI_SEGMENT_SLICE_SIZE == 0); + mi_slice_t* slice = &segment->slices[0]; + const mi_slice_t* end = mi_segment_slices_end(segment); + size_t used_count = 0; + mi_span_queue_t* sq; + while(slice < end) { + mi_assert_internal(slice->slice_count > 0); + mi_assert_internal(slice->slice_offset == 0); + size_t index = mi_slice_index(slice); + size_t maxindex = (index + slice->slice_count >= segment->slice_entries ? segment->slice_entries : index + slice->slice_count) - 1; + if (mi_slice_is_used(slice)) { // a page in use, we need at least MAX_SLICE_OFFSET valid back offsets + used_count++; + for (size_t i = 0; i <= MI_MAX_SLICE_OFFSET && index + i <= maxindex; i++) { + mi_assert_internal(segment->slices[index + i].slice_offset == i*sizeof(mi_slice_t)); + mi_assert_internal(i==0 || segment->slices[index + i].slice_count == 0); + mi_assert_internal(i==0 || segment->slices[index + i].xblock_size == 1); + } + // and the last entry as well (for coalescing) + const mi_slice_t* last = slice + slice->slice_count - 1; + if (last > slice && last < mi_segment_slices_end(segment)) { + mi_assert_internal(last->slice_offset == (slice->slice_count-1)*sizeof(mi_slice_t)); + mi_assert_internal(last->slice_count == 0); + mi_assert_internal(last->xblock_size == 1); + } + } + else { // free range of slices; only last slice needs a valid back offset + mi_slice_t* last = &segment->slices[maxindex]; + if (segment->kind != MI_SEGMENT_HUGE || slice->slice_count <= (segment->slice_entries - segment->segment_info_slices)) { + mi_assert_internal((uint8_t*)slice == (uint8_t*)last - last->slice_offset); + } + mi_assert_internal(slice == last || last->slice_count == 0 ); + mi_assert_internal(last->xblock_size == 0 || (segment->kind==MI_SEGMENT_HUGE && last->xblock_size==1)); + if (segment->kind != MI_SEGMENT_HUGE && segment->thread_id != 0) { // segment is not huge or abandoned + sq = mi_span_queue_for(slice->slice_count,tld); + mi_assert_internal(mi_span_queue_contains(sq,slice)); + } + } + slice = &segment->slices[maxindex+1]; + } + mi_assert_internal(slice == end); + mi_assert_internal(used_count == segment->used + 1); + return true; +} +#endif + +/* ----------------------------------------------------------- + Segment size calculations +----------------------------------------------------------- */ + +static size_t mi_segment_info_size(mi_segment_t* segment) { + return segment->segment_info_slices * MI_SEGMENT_SLICE_SIZE; +} + +static uint8_t* _mi_segment_page_start_from_slice(const mi_segment_t* segment, const mi_slice_t* slice, size_t xblock_size, size_t* page_size) +{ + ptrdiff_t idx = slice - segment->slices; + size_t psize = (size_t)slice->slice_count * MI_SEGMENT_SLICE_SIZE; + // make the start not OS page aligned for smaller blocks to avoid page/cache effects + // note: the offset must always be an xblock_size multiple since we assume small allocations + // are aligned (see `mi_heap_malloc_aligned`). + size_t start_offset = 0; + if (xblock_size >= MI_INTPTR_SIZE) { + if (xblock_size <= 64) { start_offset = 3*xblock_size; } + else if (xblock_size <= 512) { start_offset = xblock_size; } + } + if (page_size != NULL) { *page_size = psize - start_offset; } + return (uint8_t*)segment + ((idx*MI_SEGMENT_SLICE_SIZE) + start_offset); +} + +// Start of the page available memory; can be used on uninitialized pages +uint8_t* _mi_segment_page_start(const mi_segment_t* segment, const mi_page_t* page, size_t* page_size) +{ + const mi_slice_t* slice = mi_page_to_slice((mi_page_t*)page); + uint8_t* p = _mi_segment_page_start_from_slice(segment, slice, page->xblock_size, page_size); + mi_assert_internal(page->xblock_size > 0 || _mi_ptr_page(p) == page); + mi_assert_internal(_mi_ptr_segment(p) == segment); + return p; +} + + +static size_t mi_segment_calculate_slices(size_t required, size_t* pre_size, size_t* info_slices) { + size_t page_size = _mi_os_page_size(); + size_t isize = _mi_align_up(sizeof(mi_segment_t), page_size); + size_t guardsize = 0; + + if (MI_SECURE>0) { + // in secure mode, we set up a protected page in between the segment info + // and the page data (and one at the end of the segment) + guardsize = page_size; + if (required > 0) { + required = _mi_align_up(required, MI_SEGMENT_SLICE_SIZE) + page_size; + } + } + + if (pre_size != NULL) *pre_size = isize; + isize = _mi_align_up(isize + guardsize, MI_SEGMENT_SLICE_SIZE); + if (info_slices != NULL) *info_slices = isize / MI_SEGMENT_SLICE_SIZE; + size_t segment_size = (required==0 ? MI_SEGMENT_SIZE : _mi_align_up( required + isize + guardsize, MI_SEGMENT_SLICE_SIZE) ); + mi_assert_internal(segment_size % MI_SEGMENT_SLICE_SIZE == 0); + return (segment_size / MI_SEGMENT_SLICE_SIZE); +} + + +/* ---------------------------------------------------------------------------- +Segment caches +We keep a small segment cache per thread to increase local +reuse and avoid setting/clearing guard pages in secure mode. +------------------------------------------------------------------------------- */ + +static void mi_segments_track_size(long segment_size, mi_segments_tld_t* tld) { + if (segment_size>=0) _mi_stat_increase(&tld->stats->segments,1); + else _mi_stat_decrease(&tld->stats->segments,1); + tld->count += (segment_size >= 0 ? 1 : -1); + if (tld->count > tld->peak_count) tld->peak_count = tld->count; + tld->current_size += segment_size; + if (tld->current_size > tld->peak_size) tld->peak_size = tld->current_size; +} + +static void mi_segment_os_free(mi_segment_t* segment, mi_segments_tld_t* tld) { + segment->thread_id = 0; + _mi_segment_map_freed_at(segment); + mi_segments_track_size(-((long)mi_segment_size(segment)),tld); + if (MI_SECURE>0) { + // _mi_os_unprotect(segment, mi_segment_size(segment)); // ensure no more guard pages are set + // unprotect the guard pages; we cannot just unprotect the whole segment size as part may be decommitted + size_t os_pagesize = _mi_os_page_size(); + _mi_os_unprotect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize); + uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize; + _mi_os_unprotect(end, os_pagesize); + } + + // purge delayed decommits now? (no, leave it to the arena) + // mi_segment_try_purge(segment,true,tld->stats); + + const size_t size = mi_segment_size(segment); + const size_t csize = _mi_commit_mask_committed_size(&segment->commit_mask, size); + + _mi_abandoned_await_readers(); // wait until safe to free + _mi_arena_free(segment, mi_segment_size(segment), csize, segment->memid, tld->stats); +} + +// called by threads that are terminating +void _mi_segment_thread_collect(mi_segments_tld_t* tld) { + MI_UNUSED(tld); + // nothing to do +} + + +/* ----------------------------------------------------------- + Commit/Decommit ranges +----------------------------------------------------------- */ + +static void mi_segment_commit_mask(mi_segment_t* segment, bool conservative, uint8_t* p, size_t size, uint8_t** start_p, size_t* full_size, mi_commit_mask_t* cm) { + mi_assert_internal(_mi_ptr_segment(p + 1) == segment); + mi_assert_internal(segment->kind != MI_SEGMENT_HUGE); + mi_commit_mask_create_empty(cm); + if (size == 0 || size > MI_SEGMENT_SIZE || segment->kind == MI_SEGMENT_HUGE) return; + const size_t segstart = mi_segment_info_size(segment); + const size_t segsize = mi_segment_size(segment); + if (p >= (uint8_t*)segment + segsize) return; + + size_t pstart = (p - (uint8_t*)segment); + mi_assert_internal(pstart + size <= segsize); + + size_t start; + size_t end; + if (conservative) { + // decommit conservative + start = _mi_align_up(pstart, MI_COMMIT_SIZE); + end = _mi_align_down(pstart + size, MI_COMMIT_SIZE); + mi_assert_internal(start >= segstart); + mi_assert_internal(end <= segsize); + } + else { + // commit liberal + start = _mi_align_down(pstart, MI_MINIMAL_COMMIT_SIZE); + end = _mi_align_up(pstart + size, MI_MINIMAL_COMMIT_SIZE); + } + if (pstart >= segstart && start < segstart) { // note: the mask is also calculated for an initial commit of the info area + start = segstart; + } + if (end > segsize) { + end = segsize; + } + + mi_assert_internal(start <= pstart && (pstart + size) <= end); + mi_assert_internal(start % MI_COMMIT_SIZE==0 && end % MI_COMMIT_SIZE == 0); + *start_p = (uint8_t*)segment + start; + *full_size = (end > start ? end - start : 0); + if (*full_size == 0) return; + + size_t bitidx = start / MI_COMMIT_SIZE; + mi_assert_internal(bitidx < MI_COMMIT_MASK_BITS); + + size_t bitcount = *full_size / MI_COMMIT_SIZE; // can be 0 + if (bitidx + bitcount > MI_COMMIT_MASK_BITS) { + _mi_warning_message("commit mask overflow: idx=%zu count=%zu start=%zx end=%zx p=0x%p size=%zu fullsize=%zu\n", bitidx, bitcount, start, end, p, size, *full_size); + } + mi_assert_internal((bitidx + bitcount) <= MI_COMMIT_MASK_BITS); + mi_commit_mask_create(bitidx, bitcount, cm); +} + +static bool mi_segment_commit(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) { + mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask)); + + // commit liberal + uint8_t* start = NULL; + size_t full_size = 0; + mi_commit_mask_t mask; + mi_segment_commit_mask(segment, false /* conservative? */, p, size, &start, &full_size, &mask); + if (mi_commit_mask_is_empty(&mask) || full_size == 0) return true; + + if (!mi_commit_mask_all_set(&segment->commit_mask, &mask)) { + // committing + bool is_zero = false; + mi_commit_mask_t cmask; + mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask); + _mi_stat_decrease(&_mi_stats_main.committed, _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for overlap + if (!_mi_os_commit(start, full_size, &is_zero, stats)) return false; + mi_commit_mask_set(&segment->commit_mask, &mask); + } + + // increase purge expiration when using part of delayed purges -- we assume more allocations are coming soon. + if (mi_commit_mask_any_set(&segment->purge_mask, &mask)) { + segment->purge_expire = _mi_clock_now() + mi_option_get(mi_option_purge_delay); + } + + // always clear any delayed purges in our range (as they are either committed now) + mi_commit_mask_clear(&segment->purge_mask, &mask); + return true; +} + +static bool mi_segment_ensure_committed(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) { + mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask)); + // note: assumes commit_mask is always full for huge segments as otherwise the commit mask bits can overflow + if (mi_commit_mask_is_full(&segment->commit_mask) && mi_commit_mask_is_empty(&segment->purge_mask)) return true; // fully committed + mi_assert_internal(segment->kind != MI_SEGMENT_HUGE); + return mi_segment_commit(segment, p, size, stats); +} + +static bool mi_segment_purge(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) { + mi_assert_internal(mi_commit_mask_all_set(&segment->commit_mask, &segment->purge_mask)); + if (!segment->allow_purge) return true; + + // purge conservative + uint8_t* start = NULL; + size_t full_size = 0; + mi_commit_mask_t mask; + mi_segment_commit_mask(segment, true /* conservative? */, p, size, &start, &full_size, &mask); + if (mi_commit_mask_is_empty(&mask) || full_size==0) return true; + + if (mi_commit_mask_any_set(&segment->commit_mask, &mask)) { + // purging + mi_assert_internal((void*)start != (void*)segment); + mi_assert_internal(segment->allow_decommit); + const bool decommitted = _mi_os_purge(start, full_size, stats); // reset or decommit + if (decommitted) { + mi_commit_mask_t cmask; + mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask); + _mi_stat_increase(&_mi_stats_main.committed, full_size - _mi_commit_mask_committed_size(&cmask, MI_SEGMENT_SIZE)); // adjust for double counting + mi_commit_mask_clear(&segment->commit_mask, &mask); + } + } + + // always clear any scheduled purges in our range + mi_commit_mask_clear(&segment->purge_mask, &mask); + return true; +} + +static void mi_segment_schedule_purge(mi_segment_t* segment, uint8_t* p, size_t size, mi_stats_t* stats) { + if (!segment->allow_purge) return; + + if (mi_option_get(mi_option_purge_delay) == 0) { + mi_segment_purge(segment, p, size, stats); + } + else { + // register for future purge in the purge mask + uint8_t* start = NULL; + size_t full_size = 0; + mi_commit_mask_t mask; + mi_segment_commit_mask(segment, true /*conservative*/, p, size, &start, &full_size, &mask); + if (mi_commit_mask_is_empty(&mask) || full_size==0) return; + + // update delayed commit + mi_assert_internal(segment->purge_expire > 0 || mi_commit_mask_is_empty(&segment->purge_mask)); + mi_commit_mask_t cmask; + mi_commit_mask_create_intersect(&segment->commit_mask, &mask, &cmask); // only purge what is committed; span_free may try to decommit more + mi_commit_mask_set(&segment->purge_mask, &cmask); + mi_msecs_t now = _mi_clock_now(); + if (segment->purge_expire == 0) { + // no previous purgess, initialize now + segment->purge_expire = now + mi_option_get(mi_option_purge_delay); + } + else if (segment->purge_expire <= now) { + // previous purge mask already expired + if (segment->purge_expire + mi_option_get(mi_option_purge_extend_delay) <= now) { + mi_segment_try_purge(segment, true, stats); + } + else { + segment->purge_expire = now + mi_option_get(mi_option_purge_extend_delay); // (mi_option_get(mi_option_purge_delay) / 8); // wait a tiny bit longer in case there is a series of free's + } + } + else { + // previous purge mask is not yet expired, increase the expiration by a bit. + segment->purge_expire += mi_option_get(mi_option_purge_extend_delay); + } + } +} + +static void mi_segment_try_purge(mi_segment_t* segment, bool force, mi_stats_t* stats) { + if (!segment->allow_purge || mi_commit_mask_is_empty(&segment->purge_mask)) return; + mi_msecs_t now = _mi_clock_now(); + if (!force && now < segment->purge_expire) return; + + mi_commit_mask_t mask = segment->purge_mask; + segment->purge_expire = 0; + mi_commit_mask_create_empty(&segment->purge_mask); + + size_t idx; + size_t count; + mi_commit_mask_foreach(&mask, idx, count) { + // if found, decommit that sequence + if (count > 0) { + uint8_t* p = (uint8_t*)segment + (idx*MI_COMMIT_SIZE); + size_t size = count * MI_COMMIT_SIZE; + mi_segment_purge(segment, p, size, stats); + } + } + mi_commit_mask_foreach_end() + mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask)); +} + + +/* ----------------------------------------------------------- + Span free +----------------------------------------------------------- */ + +static bool mi_segment_is_abandoned(mi_segment_t* segment) { + return (segment->thread_id == 0); +} + +// note: can be called on abandoned segments +static void mi_segment_span_free(mi_segment_t* segment, size_t slice_index, size_t slice_count, bool allow_purge, mi_segments_tld_t* tld) { + mi_assert_internal(slice_index < segment->slice_entries); + mi_span_queue_t* sq = (segment->kind == MI_SEGMENT_HUGE || mi_segment_is_abandoned(segment) + ? NULL : mi_span_queue_for(slice_count,tld)); + if (slice_count==0) slice_count = 1; + mi_assert_internal(slice_index + slice_count - 1 < segment->slice_entries); + + // set first and last slice (the intermediates can be undetermined) + mi_slice_t* slice = &segment->slices[slice_index]; + slice->slice_count = (uint32_t)slice_count; + mi_assert_internal(slice->slice_count == slice_count); // no overflow? + slice->slice_offset = 0; + if (slice_count > 1) { + mi_slice_t* last = &segment->slices[slice_index + slice_count - 1]; + last->slice_count = 0; + last->slice_offset = (uint32_t)(sizeof(mi_page_t)*(slice_count - 1)); + last->xblock_size = 0; + } + + // perhaps decommit + if (allow_purge) { + mi_segment_schedule_purge(segment, mi_slice_start(slice), slice_count * MI_SEGMENT_SLICE_SIZE, tld->stats); + } + + // and push it on the free page queue (if it was not a huge page) + if (sq != NULL) mi_span_queue_push( sq, slice ); + else slice->xblock_size = 0; // mark huge page as free anyways +} + +/* +// called from reclaim to add existing free spans +static void mi_segment_span_add_free(mi_slice_t* slice, mi_segments_tld_t* tld) { + mi_segment_t* segment = _mi_ptr_segment(slice); + mi_assert_internal(slice->xblock_size==0 && slice->slice_count>0 && slice->slice_offset==0); + size_t slice_index = mi_slice_index(slice); + mi_segment_span_free(segment,slice_index,slice->slice_count,tld); +} +*/ + +static void mi_segment_span_remove_from_queue(mi_slice_t* slice, mi_segments_tld_t* tld) { + mi_assert_internal(slice->slice_count > 0 && slice->slice_offset==0 && slice->xblock_size==0); + mi_assert_internal(_mi_ptr_segment(slice)->kind != MI_SEGMENT_HUGE); + mi_span_queue_t* sq = mi_span_queue_for(slice->slice_count, tld); + mi_span_queue_delete(sq, slice); +} + +// note: can be called on abandoned segments +static mi_slice_t* mi_segment_span_free_coalesce(mi_slice_t* slice, mi_segments_tld_t* tld) { + mi_assert_internal(slice != NULL && slice->slice_count > 0 && slice->slice_offset == 0); + mi_segment_t* segment = _mi_ptr_segment(slice); + bool is_abandoned = mi_segment_is_abandoned(segment); + + // for huge pages, just mark as free but don't add to the queues + if (segment->kind == MI_SEGMENT_HUGE) { + // issue #691: segment->used can be 0 if the huge page block was freed while abandoned (reclaim will get here in that case) + mi_assert_internal((segment->used==0 && slice->xblock_size==0) || segment->used == 1); // decreased right after this call in `mi_segment_page_clear` + slice->xblock_size = 0; // mark as free anyways + // we should mark the last slice `xblock_size=0` now to maintain invariants but we skip it to + // avoid a possible cache miss (and the segment is about to be freed) + return slice; + } + + // otherwise coalesce the span and add to the free span queues + size_t slice_count = slice->slice_count; + mi_slice_t* next = slice + slice->slice_count; + mi_assert_internal(next <= mi_segment_slices_end(segment)); + if (next < mi_segment_slices_end(segment) && next->xblock_size==0) { + // free next block -- remove it from free and merge + mi_assert_internal(next->slice_count > 0 && next->slice_offset==0); + slice_count += next->slice_count; // extend + if (!is_abandoned) { mi_segment_span_remove_from_queue(next, tld); } + } + if (slice > segment->slices) { + mi_slice_t* prev = mi_slice_first(slice - 1); + mi_assert_internal(prev >= segment->slices); + if (prev->xblock_size==0) { + // free previous slice -- remove it from free and merge + mi_assert_internal(prev->slice_count > 0 && prev->slice_offset==0); + slice_count += prev->slice_count; + if (!is_abandoned) { mi_segment_span_remove_from_queue(prev, tld); } + slice = prev; + } + } + + // and add the new free page + mi_segment_span_free(segment, mi_slice_index(slice), slice_count, true, tld); + return slice; +} + + + +/* ----------------------------------------------------------- + Page allocation +----------------------------------------------------------- */ + +// Note: may still return NULL if committing the memory failed +static mi_page_t* mi_segment_span_allocate(mi_segment_t* segment, size_t slice_index, size_t slice_count, mi_segments_tld_t* tld) { + mi_assert_internal(slice_index < segment->slice_entries); + mi_slice_t* const slice = &segment->slices[slice_index]; + mi_assert_internal(slice->xblock_size==0 || slice->xblock_size==1); + + // commit before changing the slice data + if (!mi_segment_ensure_committed(segment, _mi_segment_page_start_from_slice(segment, slice, 0, NULL), slice_count * MI_SEGMENT_SLICE_SIZE, tld->stats)) { + return NULL; // commit failed! + } + + // convert the slices to a page + slice->slice_offset = 0; + slice->slice_count = (uint32_t)slice_count; + mi_assert_internal(slice->slice_count == slice_count); + const size_t bsize = slice_count * MI_SEGMENT_SLICE_SIZE; + slice->xblock_size = (uint32_t)(bsize >= MI_HUGE_BLOCK_SIZE ? MI_HUGE_BLOCK_SIZE : bsize); + mi_page_t* page = mi_slice_to_page(slice); + mi_assert_internal(mi_page_block_size(page) == bsize); + + // set slice back pointers for the first MI_MAX_SLICE_OFFSET entries + size_t extra = slice_count-1; + if (extra > MI_MAX_SLICE_OFFSET) extra = MI_MAX_SLICE_OFFSET; + if (slice_index + extra >= segment->slice_entries) extra = segment->slice_entries - slice_index - 1; // huge objects may have more slices than avaiable entries in the segment->slices + + mi_slice_t* slice_next = slice + 1; + for (size_t i = 1; i <= extra; i++, slice_next++) { + slice_next->slice_offset = (uint32_t)(sizeof(mi_slice_t)*i); + slice_next->slice_count = 0; + slice_next->xblock_size = 1; + } + + // and also for the last one (if not set already) (the last one is needed for coalescing and for large alignments) + // note: the cast is needed for ubsan since the index can be larger than MI_SLICES_PER_SEGMENT for huge allocations (see #543) + mi_slice_t* last = slice + slice_count - 1; + mi_slice_t* end = (mi_slice_t*)mi_segment_slices_end(segment); + if (last > end) last = end; + if (last > slice) { + last->slice_offset = (uint32_t)(sizeof(mi_slice_t) * (last - slice)); + last->slice_count = 0; + last->xblock_size = 1; + } + + // and initialize the page + page->is_committed = true; + segment->used++; + return page; +} + +static void mi_segment_slice_split(mi_segment_t* segment, mi_slice_t* slice, size_t slice_count, mi_segments_tld_t* tld) { + mi_assert_internal(_mi_ptr_segment(slice) == segment); + mi_assert_internal(slice->slice_count >= slice_count); + mi_assert_internal(slice->xblock_size > 0); // no more in free queue + if (slice->slice_count <= slice_count) return; + mi_assert_internal(segment->kind != MI_SEGMENT_HUGE); + size_t next_index = mi_slice_index(slice) + slice_count; + size_t next_count = slice->slice_count - slice_count; + mi_segment_span_free(segment, next_index, next_count, false /* don't purge left-over part */, tld); + slice->slice_count = (uint32_t)slice_count; +} + +static mi_page_t* mi_segments_page_find_and_allocate(size_t slice_count, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld) { + mi_assert_internal(slice_count*MI_SEGMENT_SLICE_SIZE <= MI_LARGE_OBJ_SIZE_MAX); + // search from best fit up + mi_span_queue_t* sq = mi_span_queue_for(slice_count, tld); + if (slice_count == 0) slice_count = 1; + while (sq <= &tld->spans[MI_SEGMENT_BIN_MAX]) { + for (mi_slice_t* slice = sq->first; slice != NULL; slice = slice->next) { + if (slice->slice_count >= slice_count) { + // found one + mi_segment_t* segment = _mi_ptr_segment(slice); + if (_mi_arena_memid_is_suitable(segment->memid, req_arena_id)) { + // found a suitable page span + mi_span_queue_delete(sq, slice); + + if (slice->slice_count > slice_count) { + mi_segment_slice_split(segment, slice, slice_count, tld); + } + mi_assert_internal(slice != NULL && slice->slice_count == slice_count && slice->xblock_size > 0); + mi_page_t* page = mi_segment_span_allocate(segment, mi_slice_index(slice), slice->slice_count, tld); + if (page == NULL) { + // commit failed; return NULL but first restore the slice + mi_segment_span_free_coalesce(slice, tld); + return NULL; + } + return page; + } + } + } + sq++; + } + // could not find a page.. + return NULL; +} + + +/* ----------------------------------------------------------- + Segment allocation +----------------------------------------------------------- */ + +static mi_segment_t* mi_segment_os_alloc( size_t required, size_t page_alignment, bool eager_delayed, mi_arena_id_t req_arena_id, + size_t* psegment_slices, size_t* ppre_size, size_t* pinfo_slices, + bool commit, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) + +{ + mi_memid_t memid; + bool allow_large = (!eager_delayed && (MI_SECURE == 0)); // only allow large OS pages once we are no longer lazy + size_t align_offset = 0; + size_t alignment = MI_SEGMENT_ALIGN; + + if (page_alignment > 0) { + // mi_assert_internal(huge_page != NULL); + mi_assert_internal(page_alignment >= MI_SEGMENT_ALIGN); + alignment = page_alignment; + const size_t info_size = (*pinfo_slices) * MI_SEGMENT_SLICE_SIZE; + align_offset = _mi_align_up( info_size, MI_SEGMENT_ALIGN ); + const size_t extra = align_offset - info_size; + // recalculate due to potential guard pages + *psegment_slices = mi_segment_calculate_slices(required + extra, ppre_size, pinfo_slices); + } + + const size_t segment_size = (*psegment_slices) * MI_SEGMENT_SLICE_SIZE; + mi_segment_t* segment = (mi_segment_t*)_mi_arena_alloc_aligned(segment_size, alignment, align_offset, commit, allow_large, req_arena_id, &memid, os_tld); + if (segment == NULL) { + return NULL; // failed to allocate + } + + // ensure metadata part of the segment is committed + mi_commit_mask_t commit_mask; + if (memid.initially_committed) { + mi_commit_mask_create_full(&commit_mask); + } + else { + // at least commit the info slices + const size_t commit_needed = _mi_divide_up((*pinfo_slices)*MI_SEGMENT_SLICE_SIZE, MI_COMMIT_SIZE); + mi_assert_internal(commit_needed>0); + mi_commit_mask_create(0, commit_needed, &commit_mask); + mi_assert_internal(commit_needed*MI_COMMIT_SIZE >= (*pinfo_slices)*MI_SEGMENT_SLICE_SIZE); + if (!_mi_os_commit(segment, commit_needed*MI_COMMIT_SIZE, NULL, tld->stats)) { + _mi_arena_free(segment,segment_size,0,memid,tld->stats); + return NULL; + } + } + mi_assert_internal(segment != NULL && (uintptr_t)segment % MI_SEGMENT_SIZE == 0); + + segment->memid = memid; + segment->allow_decommit = !memid.is_pinned; + segment->allow_purge = segment->allow_decommit && (mi_option_get(mi_option_purge_delay) >= 0); + segment->segment_size = segment_size; + segment->commit_mask = commit_mask; + segment->purge_expire = 0; + mi_commit_mask_create_empty(&segment->purge_mask); + mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL); // tsan + + mi_segments_track_size((long)(segment_size), tld); + _mi_segment_map_allocated_at(segment); + return segment; +} + + +// Allocate a segment from the OS aligned to `MI_SEGMENT_SIZE` . +static mi_segment_t* mi_segment_alloc(size_t required, size_t page_alignment, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld, mi_os_tld_t* os_tld, mi_page_t** huge_page) +{ + mi_assert_internal((required==0 && huge_page==NULL) || (required>0 && huge_page != NULL)); + + // calculate needed sizes first + size_t info_slices; + size_t pre_size; + size_t segment_slices = mi_segment_calculate_slices(required, &pre_size, &info_slices); + + // Commit eagerly only if not the first N lazy segments (to reduce impact of many threads that allocate just a little) + const bool eager_delay = (// !_mi_os_has_overcommit() && // never delay on overcommit systems + _mi_current_thread_count() > 1 && // do not delay for the first N threads + tld->count < (size_t)mi_option_get(mi_option_eager_commit_delay)); + const bool eager = !eager_delay && mi_option_is_enabled(mi_option_eager_commit); + bool commit = eager || (required > 0); + + // Allocate the segment from the OS + mi_segment_t* segment = mi_segment_os_alloc(required, page_alignment, eager_delay, req_arena_id, + &segment_slices, &pre_size, &info_slices, commit, tld, os_tld); + if (segment == NULL) return NULL; + + // zero the segment info? -- not always needed as it may be zero initialized from the OS + if (!segment->memid.initially_zero) { + ptrdiff_t ofs = offsetof(mi_segment_t, next); + size_t prefix = offsetof(mi_segment_t, slices) - ofs; + size_t zsize = prefix + (sizeof(mi_slice_t) * (segment_slices + 1)); // one more + _mi_memzero((uint8_t*)segment + ofs, zsize); + } + + // initialize the rest of the segment info + const size_t slice_entries = (segment_slices > MI_SLICES_PER_SEGMENT ? MI_SLICES_PER_SEGMENT : segment_slices); + segment->segment_slices = segment_slices; + segment->segment_info_slices = info_slices; + segment->thread_id = _mi_thread_id(); + segment->cookie = _mi_ptr_cookie(segment); + segment->slice_entries = slice_entries; + segment->kind = (required == 0 ? MI_SEGMENT_NORMAL : MI_SEGMENT_HUGE); + + // _mi_memzero(segment->slices, sizeof(mi_slice_t)*(info_slices+1)); + _mi_stat_increase(&tld->stats->page_committed, mi_segment_info_size(segment)); + + // set up guard pages + size_t guard_slices = 0; + if (MI_SECURE>0) { + // in secure mode, we set up a protected page in between the segment info + // and the page data, and at the end of the segment. + size_t os_pagesize = _mi_os_page_size(); + mi_assert_internal(mi_segment_info_size(segment) - os_pagesize >= pre_size); + _mi_os_protect((uint8_t*)segment + mi_segment_info_size(segment) - os_pagesize, os_pagesize); + uint8_t* end = (uint8_t*)segment + mi_segment_size(segment) - os_pagesize; + mi_segment_ensure_committed(segment, end, os_pagesize, tld->stats); + _mi_os_protect(end, os_pagesize); + if (slice_entries == segment_slices) segment->slice_entries--; // don't use the last slice :-( + guard_slices = 1; + } + + // reserve first slices for segment info + mi_page_t* page0 = mi_segment_span_allocate(segment, 0, info_slices, tld); + mi_assert_internal(page0!=NULL); if (page0==NULL) return NULL; // cannot fail as we always commit in advance + mi_assert_internal(segment->used == 1); + segment->used = 0; // don't count our internal slices towards usage + + // initialize initial free pages + if (segment->kind == MI_SEGMENT_NORMAL) { // not a huge page + mi_assert_internal(huge_page==NULL); + mi_segment_span_free(segment, info_slices, segment->slice_entries - info_slices, false /* don't purge */, tld); + } + else { + mi_assert_internal(huge_page!=NULL); + mi_assert_internal(mi_commit_mask_is_empty(&segment->purge_mask)); + mi_assert_internal(mi_commit_mask_is_full(&segment->commit_mask)); + *huge_page = mi_segment_span_allocate(segment, info_slices, segment_slices - info_slices - guard_slices, tld); + mi_assert_internal(*huge_page != NULL); // cannot fail as we commit in advance + } + + mi_assert_expensive(mi_segment_is_valid(segment,tld)); + return segment; +} + + +static void mi_segment_free(mi_segment_t* segment, bool force, mi_segments_tld_t* tld) { + MI_UNUSED(force); + mi_assert_internal(segment != NULL); + mi_assert_internal(segment->next == NULL); + mi_assert_internal(segment->used == 0); + + // Remove the free pages + mi_slice_t* slice = &segment->slices[0]; + const mi_slice_t* end = mi_segment_slices_end(segment); + #if MI_DEBUG>1 + size_t page_count = 0; + #endif + while (slice < end) { + mi_assert_internal(slice->slice_count > 0); + mi_assert_internal(slice->slice_offset == 0); + mi_assert_internal(mi_slice_index(slice)==0 || slice->xblock_size == 0); // no more used pages .. + if (slice->xblock_size == 0 && segment->kind != MI_SEGMENT_HUGE) { + mi_segment_span_remove_from_queue(slice, tld); + } + #if MI_DEBUG>1 + page_count++; + #endif + slice = slice + slice->slice_count; + } + mi_assert_internal(page_count == 2); // first page is allocated by the segment itself + + // stats + _mi_stat_decrease(&tld->stats->page_committed, mi_segment_info_size(segment)); + + // return it to the OS + mi_segment_os_free(segment, tld); +} + + +/* ----------------------------------------------------------- + Page Free +----------------------------------------------------------- */ + +static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld); + +// note: can be called on abandoned pages +static mi_slice_t* mi_segment_page_clear(mi_page_t* page, mi_segments_tld_t* tld) { + mi_assert_internal(page->xblock_size > 0); + mi_assert_internal(mi_page_all_free(page)); + mi_segment_t* segment = _mi_ptr_segment(page); + mi_assert_internal(segment->used > 0); + + size_t inuse = page->capacity * mi_page_block_size(page); + _mi_stat_decrease(&tld->stats->page_committed, inuse); + _mi_stat_decrease(&tld->stats->pages, 1); + + // reset the page memory to reduce memory pressure? + if (segment->allow_decommit && mi_option_is_enabled(mi_option_deprecated_page_reset)) { + size_t psize; + uint8_t* start = _mi_page_start(segment, page, &psize); + _mi_os_reset(start, psize, tld->stats); + } + + // zero the page data, but not the segment fields + page->is_zero_init = false; + ptrdiff_t ofs = offsetof(mi_page_t, capacity); + _mi_memzero((uint8_t*)page + ofs, sizeof(*page) - ofs); + page->xblock_size = 1; + + // and free it + mi_slice_t* slice = mi_segment_span_free_coalesce(mi_page_to_slice(page), tld); + segment->used--; + // cannot assert segment valid as it is called during reclaim + // mi_assert_expensive(mi_segment_is_valid(segment, tld)); + return slice; +} + +void _mi_segment_page_free(mi_page_t* page, bool force, mi_segments_tld_t* tld) +{ + mi_assert(page != NULL); + + mi_segment_t* segment = _mi_page_segment(page); + mi_assert_expensive(mi_segment_is_valid(segment,tld)); + + // mark it as free now + mi_segment_page_clear(page, tld); + mi_assert_expensive(mi_segment_is_valid(segment, tld)); + + if (segment->used == 0) { + // no more used pages; remove from the free list and free the segment + mi_segment_free(segment, force, tld); + } + else if (segment->used == segment->abandoned) { + // only abandoned pages; remove from free list and abandon + mi_segment_abandon(segment,tld); + } +} + + +/* ----------------------------------------------------------- +Abandonment + +When threads terminate, they can leave segments with +live blocks (reachable through other threads). Such segments +are "abandoned" and will be reclaimed by other threads to +reuse their pages and/or free them eventually + +We maintain a global list of abandoned segments that are +reclaimed on demand. Since this is shared among threads +the implementation needs to avoid the A-B-A problem on +popping abandoned segments: +We use tagged pointers to avoid accidentally identifying +reused segments, much like stamped references in Java. +Secondly, we maintain a reader counter to avoid resetting +or decommitting segments that have a pending read operation. + +Note: the current implementation is one possible design; +another way might be to keep track of abandoned segments +in the arenas/segment_cache's. This would have the advantage of keeping +all concurrent code in one place and not needing to deal +with ABA issues. The drawback is that it is unclear how to +scan abandoned segments efficiently in that case as they +would be spread among all other segments in the arenas. +----------------------------------------------------------- */ + +// Use the bottom 20-bits (on 64-bit) of the aligned segment pointers +// to put in a tag that increments on update to avoid the A-B-A problem. +#define MI_TAGGED_MASK MI_SEGMENT_MASK +typedef uintptr_t mi_tagged_segment_t; + +static mi_segment_t* mi_tagged_segment_ptr(mi_tagged_segment_t ts) { + return (mi_segment_t*)(ts & ~MI_TAGGED_MASK); +} + +static mi_tagged_segment_t mi_tagged_segment(mi_segment_t* segment, mi_tagged_segment_t ts) { + mi_assert_internal(((uintptr_t)segment & MI_TAGGED_MASK) == 0); + uintptr_t tag = ((ts & MI_TAGGED_MASK) + 1) & MI_TAGGED_MASK; + return ((uintptr_t)segment | tag); +} + +// This is a list of visited abandoned pages that were full at the time. +// this list migrates to `abandoned` when that becomes NULL. The use of +// this list reduces contention and the rate at which segments are visited. +static mi_decl_cache_align _Atomic(mi_segment_t*) abandoned_visited; // = NULL + +// The abandoned page list (tagged as it supports pop) +static mi_decl_cache_align _Atomic(mi_tagged_segment_t) abandoned; // = NULL + +// Maintain these for debug purposes (these counts may be a bit off) +static mi_decl_cache_align _Atomic(size_t) abandoned_count; +static mi_decl_cache_align _Atomic(size_t) abandoned_visited_count; + +// We also maintain a count of current readers of the abandoned list +// in order to prevent resetting/decommitting segment memory if it might +// still be read. +static mi_decl_cache_align _Atomic(size_t) abandoned_readers; // = 0 + +// Push on the visited list +static void mi_abandoned_visited_push(mi_segment_t* segment) { + mi_assert_internal(segment->thread_id == 0); + mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t,&segment->abandoned_next) == NULL); + mi_assert_internal(segment->next == NULL); + mi_assert_internal(segment->used > 0); + mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &abandoned_visited); + do { + mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, anext); + } while (!mi_atomic_cas_ptr_weak_release(mi_segment_t, &abandoned_visited, &anext, segment)); + mi_atomic_increment_relaxed(&abandoned_visited_count); +} + +// Move the visited list to the abandoned list. +static bool mi_abandoned_visited_revisit(void) +{ + // quick check if the visited list is empty + if (mi_atomic_load_ptr_relaxed(mi_segment_t, &abandoned_visited) == NULL) return false; + + // grab the whole visited list + mi_segment_t* first = mi_atomic_exchange_ptr_acq_rel(mi_segment_t, &abandoned_visited, NULL); + if (first == NULL) return false; + + // first try to swap directly if the abandoned list happens to be NULL + mi_tagged_segment_t afirst; + mi_tagged_segment_t ts = mi_atomic_load_relaxed(&abandoned); + if (mi_tagged_segment_ptr(ts)==NULL) { + size_t count = mi_atomic_load_relaxed(&abandoned_visited_count); + afirst = mi_tagged_segment(first, ts); + if (mi_atomic_cas_strong_acq_rel(&abandoned, &ts, afirst)) { + mi_atomic_add_relaxed(&abandoned_count, count); + mi_atomic_sub_relaxed(&abandoned_visited_count, count); + return true; + } + } + + // find the last element of the visited list: O(n) + mi_segment_t* last = first; + mi_segment_t* next; + while ((next = mi_atomic_load_ptr_relaxed(mi_segment_t, &last->abandoned_next)) != NULL) { + last = next; + } + + // and atomically prepend to the abandoned list + // (no need to increase the readers as we don't access the abandoned segments) + mi_tagged_segment_t anext = mi_atomic_load_relaxed(&abandoned); + size_t count; + do { + count = mi_atomic_load_relaxed(&abandoned_visited_count); + mi_atomic_store_ptr_release(mi_segment_t, &last->abandoned_next, mi_tagged_segment_ptr(anext)); + afirst = mi_tagged_segment(first, anext); + } while (!mi_atomic_cas_weak_release(&abandoned, &anext, afirst)); + mi_atomic_add_relaxed(&abandoned_count, count); + mi_atomic_sub_relaxed(&abandoned_visited_count, count); + return true; +} + +// Push on the abandoned list. +static void mi_abandoned_push(mi_segment_t* segment) { + mi_assert_internal(segment->thread_id == 0); + mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL); + mi_assert_internal(segment->next == NULL); + mi_assert_internal(segment->used > 0); + mi_tagged_segment_t next; + mi_tagged_segment_t ts = mi_atomic_load_relaxed(&abandoned); + do { + mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, mi_tagged_segment_ptr(ts)); + next = mi_tagged_segment(segment, ts); + } while (!mi_atomic_cas_weak_release(&abandoned, &ts, next)); + mi_atomic_increment_relaxed(&abandoned_count); +} + +// Wait until there are no more pending reads on segments that used to be in the abandoned list +// called for example from `arena.c` before decommitting +void _mi_abandoned_await_readers(void) { + size_t n; + do { + n = mi_atomic_load_acquire(&abandoned_readers); + if (n != 0) mi_atomic_yield(); + } while (n != 0); +} + +// Pop from the abandoned list +static mi_segment_t* mi_abandoned_pop(void) { + mi_segment_t* segment; + // Check efficiently if it is empty (or if the visited list needs to be moved) + mi_tagged_segment_t ts = mi_atomic_load_relaxed(&abandoned); + segment = mi_tagged_segment_ptr(ts); + if mi_likely(segment == NULL) { + if mi_likely(!mi_abandoned_visited_revisit()) { // try to swap in the visited list on NULL + return NULL; + } + } + + // Do a pop. We use a reader count to prevent + // a segment to be decommitted while a read is still pending, + // and a tagged pointer to prevent A-B-A link corruption. + // (this is called from `region.c:_mi_mem_free` for example) + mi_atomic_increment_relaxed(&abandoned_readers); // ensure no segment gets decommitted + mi_tagged_segment_t next = 0; + ts = mi_atomic_load_acquire(&abandoned); + do { + segment = mi_tagged_segment_ptr(ts); + if (segment != NULL) { + mi_segment_t* anext = mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next); + next = mi_tagged_segment(anext, ts); // note: reads the segment's `abandoned_next` field so should not be decommitted + } + } while (segment != NULL && !mi_atomic_cas_weak_acq_rel(&abandoned, &ts, next)); + mi_atomic_decrement_relaxed(&abandoned_readers); // release reader lock + if (segment != NULL) { + mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL); + mi_atomic_decrement_relaxed(&abandoned_count); + } + return segment; +} + +/* ----------------------------------------------------------- + Abandon segment/page +----------------------------------------------------------- */ + +static void mi_segment_abandon(mi_segment_t* segment, mi_segments_tld_t* tld) { + mi_assert_internal(segment->used == segment->abandoned); + mi_assert_internal(segment->used > 0); + mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL); + mi_assert_internal(segment->abandoned_visits == 0); + mi_assert_expensive(mi_segment_is_valid(segment,tld)); + + // remove the free pages from the free page queues + mi_slice_t* slice = &segment->slices[0]; + const mi_slice_t* end = mi_segment_slices_end(segment); + while (slice < end) { + mi_assert_internal(slice->slice_count > 0); + mi_assert_internal(slice->slice_offset == 0); + if (slice->xblock_size == 0) { // a free page + mi_segment_span_remove_from_queue(slice,tld); + slice->xblock_size = 0; // but keep it free + } + slice = slice + slice->slice_count; + } + + // perform delayed decommits (forcing is much slower on mstress) + mi_segment_try_purge(segment, mi_option_is_enabled(mi_option_abandoned_page_purge) /* force? */, tld->stats); + + // all pages in the segment are abandoned; add it to the abandoned list + _mi_stat_increase(&tld->stats->segments_abandoned, 1); + mi_segments_track_size(-((long)mi_segment_size(segment)), tld); + segment->thread_id = 0; + mi_atomic_store_ptr_release(mi_segment_t, &segment->abandoned_next, NULL); + segment->abandoned_visits = 1; // from 0 to 1 to signify it is abandoned + mi_abandoned_push(segment); +} + +void _mi_segment_page_abandon(mi_page_t* page, mi_segments_tld_t* tld) { + mi_assert(page != NULL); + mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE); + mi_assert_internal(mi_page_heap(page) == NULL); + mi_segment_t* segment = _mi_page_segment(page); + + mi_assert_expensive(mi_segment_is_valid(segment,tld)); + segment->abandoned++; + + _mi_stat_increase(&tld->stats->pages_abandoned, 1); + mi_assert_internal(segment->abandoned <= segment->used); + if (segment->used == segment->abandoned) { + // all pages are abandoned, abandon the entire segment + mi_segment_abandon(segment, tld); + } +} + +/* ----------------------------------------------------------- + Reclaim abandoned pages +----------------------------------------------------------- */ + +static mi_slice_t* mi_slices_start_iterate(mi_segment_t* segment, const mi_slice_t** end) { + mi_slice_t* slice = &segment->slices[0]; + *end = mi_segment_slices_end(segment); + mi_assert_internal(slice->slice_count>0 && slice->xblock_size>0); // segment allocated page + slice = slice + slice->slice_count; // skip the first segment allocated page + return slice; +} + +// Possibly free pages and check if free space is available +static bool mi_segment_check_free(mi_segment_t* segment, size_t slices_needed, size_t block_size, mi_segments_tld_t* tld) +{ + mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE); + mi_assert_internal(mi_segment_is_abandoned(segment)); + bool has_page = false; + + // for all slices + const mi_slice_t* end; + mi_slice_t* slice = mi_slices_start_iterate(segment, &end); + while (slice < end) { + mi_assert_internal(slice->slice_count > 0); + mi_assert_internal(slice->slice_offset == 0); + if (mi_slice_is_used(slice)) { // used page + // ensure used count is up to date and collect potential concurrent frees + mi_page_t* const page = mi_slice_to_page(slice); + _mi_page_free_collect(page, false); + if (mi_page_all_free(page)) { + // if this page is all free now, free it without adding to any queues (yet) + mi_assert_internal(page->next == NULL && page->prev==NULL); + _mi_stat_decrease(&tld->stats->pages_abandoned, 1); + segment->abandoned--; + slice = mi_segment_page_clear(page, tld); // re-assign slice due to coalesce! + mi_assert_internal(!mi_slice_is_used(slice)); + if (slice->slice_count >= slices_needed) { + has_page = true; + } + } + else { + if (page->xblock_size == block_size && mi_page_has_any_available(page)) { + // a page has available free blocks of the right size + has_page = true; + } + } + } + else { + // empty span + if (slice->slice_count >= slices_needed) { + has_page = true; + } + } + slice = slice + slice->slice_count; + } + return has_page; +} + +// Reclaim an abandoned segment; returns NULL if the segment was freed +// set `right_page_reclaimed` to `true` if it reclaimed a page of the right `block_size` that was not full. +static mi_segment_t* mi_segment_reclaim(mi_segment_t* segment, mi_heap_t* heap, size_t requested_block_size, bool* right_page_reclaimed, mi_segments_tld_t* tld) { + mi_assert_internal(mi_atomic_load_ptr_relaxed(mi_segment_t, &segment->abandoned_next) == NULL); + mi_assert_expensive(mi_segment_is_valid(segment, tld)); + if (right_page_reclaimed != NULL) { *right_page_reclaimed = false; } + + segment->thread_id = _mi_thread_id(); + segment->abandoned_visits = 0; + mi_segments_track_size((long)mi_segment_size(segment), tld); + mi_assert_internal(segment->next == NULL); + _mi_stat_decrease(&tld->stats->segments_abandoned, 1); + + // for all slices + const mi_slice_t* end; + mi_slice_t* slice = mi_slices_start_iterate(segment, &end); + while (slice < end) { + mi_assert_internal(slice->slice_count > 0); + mi_assert_internal(slice->slice_offset == 0); + if (mi_slice_is_used(slice)) { + // in use: reclaim the page in our heap + mi_page_t* page = mi_slice_to_page(slice); + mi_assert_internal(page->is_committed); + mi_assert_internal(mi_page_thread_free_flag(page)==MI_NEVER_DELAYED_FREE); + mi_assert_internal(mi_page_heap(page) == NULL); + mi_assert_internal(page->next == NULL && page->prev==NULL); + _mi_stat_decrease(&tld->stats->pages_abandoned, 1); + segment->abandoned--; + // set the heap again and allow delayed free again + mi_page_set_heap(page, heap); + _mi_page_use_delayed_free(page, MI_USE_DELAYED_FREE, true); // override never (after heap is set) + _mi_page_free_collect(page, false); // ensure used count is up to date + if (mi_page_all_free(page)) { + // if everything free by now, free the page + slice = mi_segment_page_clear(page, tld); // set slice again due to coalesceing + } + else { + // otherwise reclaim it into the heap + _mi_page_reclaim(heap, page); + if (requested_block_size == page->xblock_size && mi_page_has_any_available(page)) { + if (right_page_reclaimed != NULL) { *right_page_reclaimed = true; } + } + } + } + else { + // the span is free, add it to our page queues + slice = mi_segment_span_free_coalesce(slice, tld); // set slice again due to coalesceing + } + mi_assert_internal(slice->slice_count>0 && slice->slice_offset==0); + slice = slice + slice->slice_count; + } + + mi_assert(segment->abandoned == 0); + if (segment->used == 0) { // due to page_clear + mi_assert_internal(right_page_reclaimed == NULL || !(*right_page_reclaimed)); + mi_segment_free(segment, false, tld); + return NULL; + } + else { + return segment; + } +} + + +void _mi_abandoned_reclaim_all(mi_heap_t* heap, mi_segments_tld_t* tld) { + mi_segment_t* segment; + while ((segment = mi_abandoned_pop()) != NULL) { + mi_segment_reclaim(segment, heap, 0, NULL, tld); + } +} + +static mi_segment_t* mi_segment_try_reclaim(mi_heap_t* heap, size_t needed_slices, size_t block_size, bool* reclaimed, mi_segments_tld_t* tld) +{ + *reclaimed = false; + mi_segment_t* segment; + long max_tries = mi_option_get_clamp(mi_option_max_segment_reclaim, 8, 1024); // limit the work to bound allocation times + while ((max_tries-- > 0) && ((segment = mi_abandoned_pop()) != NULL)) { + segment->abandoned_visits++; + // todo: an arena exclusive heap will potentially visit many abandoned unsuitable segments + // and push them into the visited list and use many tries. Perhaps we can skip non-suitable ones in a better way? + bool is_suitable = _mi_heap_memid_is_suitable(heap, segment->memid); + bool has_page = mi_segment_check_free(segment,needed_slices,block_size,tld); // try to free up pages (due to concurrent frees) + if (segment->used == 0) { + // free the segment (by forced reclaim) to make it available to other threads. + // note1: we prefer to free a segment as that might lead to reclaiming another + // segment that is still partially used. + // note2: we could in principle optimize this by skipping reclaim and directly + // freeing but that would violate some invariants temporarily) + mi_segment_reclaim(segment, heap, 0, NULL, tld); + } + else if (has_page && is_suitable) { + // found a large enough free span, or a page of the right block_size with free space + // we return the result of reclaim (which is usually `segment`) as it might free + // the segment due to concurrent frees (in which case `NULL` is returned). + return mi_segment_reclaim(segment, heap, block_size, reclaimed, tld); + } + else if (segment->abandoned_visits > 3 && is_suitable) { + // always reclaim on 3rd visit to limit the abandoned queue length. + mi_segment_reclaim(segment, heap, 0, NULL, tld); + } + else { + // otherwise, push on the visited list so it gets not looked at too quickly again + mi_segment_try_purge(segment, true /* force? */, tld->stats); // force purge if needed as we may not visit soon again + mi_abandoned_visited_push(segment); + } + } + return NULL; +} + + +void _mi_abandoned_collect(mi_heap_t* heap, bool force, mi_segments_tld_t* tld) +{ + mi_segment_t* segment; + int max_tries = (force ? 16*1024 : 1024); // limit latency + if (force) { + mi_abandoned_visited_revisit(); + } + while ((max_tries-- > 0) && ((segment = mi_abandoned_pop()) != NULL)) { + mi_segment_check_free(segment,0,0,tld); // try to free up pages (due to concurrent frees) + if (segment->used == 0) { + // free the segment (by forced reclaim) to make it available to other threads. + // note: we could in principle optimize this by skipping reclaim and directly + // freeing but that would violate some invariants temporarily) + mi_segment_reclaim(segment, heap, 0, NULL, tld); + } + else { + // otherwise, purge if needed and push on the visited list + // note: forced purge can be expensive if many threads are destroyed/created as in mstress. + mi_segment_try_purge(segment, force, tld->stats); + mi_abandoned_visited_push(segment); + } + } +} + +/* ----------------------------------------------------------- + Reclaim or allocate +----------------------------------------------------------- */ + +static mi_segment_t* mi_segment_reclaim_or_alloc(mi_heap_t* heap, size_t needed_slices, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) +{ + mi_assert_internal(block_size < MI_HUGE_BLOCK_SIZE); + mi_assert_internal(block_size <= MI_LARGE_OBJ_SIZE_MAX); + + // 1. try to reclaim an abandoned segment + bool reclaimed; + mi_segment_t* segment = mi_segment_try_reclaim(heap, needed_slices, block_size, &reclaimed, tld); + if (reclaimed) { + // reclaimed the right page right into the heap + mi_assert_internal(segment != NULL); + return NULL; // pretend out-of-memory as the page will be in the page queue of the heap with available blocks + } + else if (segment != NULL) { + // reclaimed a segment with a large enough empty span in it + return segment; + } + // 2. otherwise allocate a fresh segment + return mi_segment_alloc(0, 0, heap->arena_id, tld, os_tld, NULL); +} + + +/* ----------------------------------------------------------- + Page allocation +----------------------------------------------------------- */ + +static mi_page_t* mi_segments_page_alloc(mi_heap_t* heap, mi_page_kind_t page_kind, size_t required, size_t block_size, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) +{ + mi_assert_internal(required <= MI_LARGE_OBJ_SIZE_MAX && page_kind <= MI_PAGE_LARGE); + + // find a free page + size_t page_size = _mi_align_up(required, (required > MI_MEDIUM_PAGE_SIZE ? MI_MEDIUM_PAGE_SIZE : MI_SEGMENT_SLICE_SIZE)); + size_t slices_needed = page_size / MI_SEGMENT_SLICE_SIZE; + mi_assert_internal(slices_needed * MI_SEGMENT_SLICE_SIZE == page_size); + mi_page_t* page = mi_segments_page_find_and_allocate(slices_needed, heap->arena_id, tld); //(required <= MI_SMALL_SIZE_MAX ? 0 : slices_needed), tld); + if (page==NULL) { + // no free page, allocate a new segment and try again + if (mi_segment_reclaim_or_alloc(heap, slices_needed, block_size, tld, os_tld) == NULL) { + // OOM or reclaimed a good page in the heap + return NULL; + } + else { + // otherwise try again + return mi_segments_page_alloc(heap, page_kind, required, block_size, tld, os_tld); + } + } + mi_assert_internal(page != NULL && page->slice_count*MI_SEGMENT_SLICE_SIZE == page_size); + mi_assert_internal(_mi_ptr_segment(page)->thread_id == _mi_thread_id()); + mi_segment_try_purge(_mi_ptr_segment(page), false, tld->stats); + return page; +} + + + +/* ----------------------------------------------------------- + Huge page allocation +----------------------------------------------------------- */ + +static mi_page_t* mi_segment_huge_page_alloc(size_t size, size_t page_alignment, mi_arena_id_t req_arena_id, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) +{ + mi_page_t* page = NULL; + mi_segment_t* segment = mi_segment_alloc(size,page_alignment,req_arena_id,tld,os_tld,&page); + if (segment == NULL || page==NULL) return NULL; + mi_assert_internal(segment->used==1); + mi_assert_internal(mi_page_block_size(page) >= size); + #if MI_HUGE_PAGE_ABANDON + segment->thread_id = 0; // huge segments are immediately abandoned + #endif + + // for huge pages we initialize the xblock_size as we may + // overallocate to accommodate large alignments. + size_t psize; + uint8_t* start = _mi_segment_page_start(segment, page, &psize); + page->xblock_size = (psize > MI_HUGE_BLOCK_SIZE ? MI_HUGE_BLOCK_SIZE : (uint32_t)psize); + + // decommit the part of the prefix of a page that will not be used; this can be quite large (close to MI_SEGMENT_SIZE) + if (page_alignment > 0 && segment->allow_decommit) { + uint8_t* aligned_p = (uint8_t*)_mi_align_up((uintptr_t)start, page_alignment); + mi_assert_internal(_mi_is_aligned(aligned_p, page_alignment)); + mi_assert_internal(psize - (aligned_p - start) >= size); + uint8_t* decommit_start = start + sizeof(mi_block_t); // for the free list + ptrdiff_t decommit_size = aligned_p - decommit_start; + _mi_os_reset(decommit_start, decommit_size, &_mi_stats_main); // note: cannot use segment_decommit on huge segments + } + + return page; +} + +#if MI_HUGE_PAGE_ABANDON +// free huge block from another thread +void _mi_segment_huge_page_free(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) { + // huge page segments are always abandoned and can be freed immediately by any thread + mi_assert_internal(segment->kind==MI_SEGMENT_HUGE); + mi_assert_internal(segment == _mi_page_segment(page)); + mi_assert_internal(mi_atomic_load_relaxed(&segment->thread_id)==0); + + // claim it and free + mi_heap_t* heap = mi_heap_get_default(); // issue #221; don't use the internal get_default_heap as we need to ensure the thread is initialized. + // paranoia: if this it the last reference, the cas should always succeed + size_t expected_tid = 0; + if (mi_atomic_cas_strong_acq_rel(&segment->thread_id, &expected_tid, heap->thread_id)) { + mi_block_set_next(page, block, page->free); + page->free = block; + page->used--; + page->is_zero = false; + mi_assert(page->used == 0); + mi_tld_t* tld = heap->tld; + _mi_segment_page_free(page, true, &tld->segments); + } +#if (MI_DEBUG!=0) + else { + mi_assert_internal(false); + } +#endif +} + +#else +// reset memory of a huge block from another thread +void _mi_segment_huge_page_reset(mi_segment_t* segment, mi_page_t* page, mi_block_t* block) { + MI_UNUSED(page); + mi_assert_internal(segment->kind == MI_SEGMENT_HUGE); + mi_assert_internal(segment == _mi_page_segment(page)); + mi_assert_internal(page->used == 1); // this is called just before the free + mi_assert_internal(page->free == NULL); + if (segment->allow_decommit) { + size_t csize = mi_usable_size(block); + if (csize > sizeof(mi_block_t)) { + csize = csize - sizeof(mi_block_t); + uint8_t* p = (uint8_t*)block + sizeof(mi_block_t); + _mi_os_reset(p, csize, &_mi_stats_main); // note: cannot use segment_decommit on huge segments + } + } +} +#endif + +/* ----------------------------------------------------------- + Page allocation and free +----------------------------------------------------------- */ +mi_page_t* _mi_segment_page_alloc(mi_heap_t* heap, size_t block_size, size_t page_alignment, mi_segments_tld_t* tld, mi_os_tld_t* os_tld) { + mi_page_t* page; + if mi_unlikely(page_alignment > MI_ALIGNMENT_MAX) { + mi_assert_internal(_mi_is_power_of_two(page_alignment)); + mi_assert_internal(page_alignment >= MI_SEGMENT_SIZE); + if (page_alignment < MI_SEGMENT_SIZE) { page_alignment = MI_SEGMENT_SIZE; } + page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld,os_tld); + } + else if (block_size <= MI_SMALL_OBJ_SIZE_MAX) { + page = mi_segments_page_alloc(heap,MI_PAGE_SMALL,block_size,block_size,tld,os_tld); + } + else if (block_size <= MI_MEDIUM_OBJ_SIZE_MAX) { + page = mi_segments_page_alloc(heap,MI_PAGE_MEDIUM,MI_MEDIUM_PAGE_SIZE,block_size,tld, os_tld); + } + else if (block_size <= MI_LARGE_OBJ_SIZE_MAX) { + page = mi_segments_page_alloc(heap,MI_PAGE_LARGE,block_size,block_size,tld, os_tld); + } + else { + page = mi_segment_huge_page_alloc(block_size,page_alignment,heap->arena_id,tld,os_tld); + } + mi_assert_internal(page == NULL || _mi_heap_memid_is_suitable(heap, _mi_page_segment(page)->memid)); + mi_assert_expensive(page == NULL || mi_segment_is_valid(_mi_page_segment(page),tld)); + return page; +} diff --git a/compat/mimalloc/stats.c b/compat/mimalloc/stats.c new file mode 100644 index 00000000000000..6817e07aa1ee9f --- /dev/null +++ b/compat/mimalloc/stats.c @@ -0,0 +1,467 @@ +/* ---------------------------------------------------------------------------- +Copyright (c) 2018-2021, Microsoft Research, Daan Leijen +This is free software; you can redistribute it and/or modify it under the +terms of the MIT license. A copy of the license can be found in the file +"LICENSE" at the root of this distribution. +-----------------------------------------------------------------------------*/ +#include "mimalloc.h" +#include "mimalloc/internal.h" +#include "mimalloc/atomic.h" +#include "mimalloc/prim.h" + +#include // snprintf +#include // memset + +#if defined(_MSC_VER) && (_MSC_VER < 1920) +#pragma warning(disable:4204) // non-constant aggregate initializer +#endif + +/* ----------------------------------------------------------- + Statistics operations +----------------------------------------------------------- */ + +static bool mi_is_in_main(void* stat) { + return ((uint8_t*)stat >= (uint8_t*)&_mi_stats_main + && (uint8_t*)stat < ((uint8_t*)&_mi_stats_main + sizeof(mi_stats_t))); +} + +static void mi_stat_update(mi_stat_count_t* stat, int64_t amount) { + if (amount == 0) return; + if (mi_is_in_main(stat)) + { + // add atomically (for abandoned pages) + int64_t current = mi_atomic_addi64_relaxed(&stat->current, amount); + mi_atomic_maxi64_relaxed(&stat->peak, current + amount); + if (amount > 0) { + mi_atomic_addi64_relaxed(&stat->allocated,amount); + } + else { + mi_atomic_addi64_relaxed(&stat->freed, -amount); + } + } + else { + // add thread local + stat->current += amount; + if (stat->current > stat->peak) stat->peak = stat->current; + if (amount > 0) { + stat->allocated += amount; + } + else { + stat->freed += -amount; + } + } +} + +void _mi_stat_counter_increase(mi_stat_counter_t* stat, size_t amount) { + if (mi_is_in_main(stat)) { + mi_atomic_addi64_relaxed( &stat->count, 1 ); + mi_atomic_addi64_relaxed( &stat->total, (int64_t)amount ); + } + else { + stat->count++; + stat->total += amount; + } +} + +void _mi_stat_increase(mi_stat_count_t* stat, size_t amount) { + mi_stat_update(stat, (int64_t)amount); +} + +void _mi_stat_decrease(mi_stat_count_t* stat, size_t amount) { + mi_stat_update(stat, -((int64_t)amount)); +} + +// must be thread safe as it is called from stats_merge +static void mi_stat_add(mi_stat_count_t* stat, const mi_stat_count_t* src, int64_t unit) { + if (stat==src) return; + if (src->allocated==0 && src->freed==0) return; + mi_atomic_addi64_relaxed( &stat->allocated, src->allocated * unit); + mi_atomic_addi64_relaxed( &stat->current, src->current * unit); + mi_atomic_addi64_relaxed( &stat->freed, src->freed * unit); + // peak scores do not work across threads.. + mi_atomic_addi64_relaxed( &stat->peak, src->peak * unit); +} + +static void mi_stat_counter_add(mi_stat_counter_t* stat, const mi_stat_counter_t* src, int64_t unit) { + if (stat==src) return; + mi_atomic_addi64_relaxed( &stat->total, src->total * unit); + mi_atomic_addi64_relaxed( &stat->count, src->count * unit); +} + +// must be thread safe as it is called from stats_merge +static void mi_stats_add(mi_stats_t* stats, const mi_stats_t* src) { + if (stats==src) return; + mi_stat_add(&stats->segments, &src->segments,1); + mi_stat_add(&stats->pages, &src->pages,1); + mi_stat_add(&stats->reserved, &src->reserved, 1); + mi_stat_add(&stats->committed, &src->committed, 1); + mi_stat_add(&stats->reset, &src->reset, 1); + mi_stat_add(&stats->purged, &src->purged, 1); + mi_stat_add(&stats->page_committed, &src->page_committed, 1); + + mi_stat_add(&stats->pages_abandoned, &src->pages_abandoned, 1); + mi_stat_add(&stats->segments_abandoned, &src->segments_abandoned, 1); + mi_stat_add(&stats->threads, &src->threads, 1); + + mi_stat_add(&stats->malloc, &src->malloc, 1); + mi_stat_add(&stats->segments_cache, &src->segments_cache, 1); + mi_stat_add(&stats->normal, &src->normal, 1); + mi_stat_add(&stats->huge, &src->huge, 1); + mi_stat_add(&stats->large, &src->large, 1); + + mi_stat_counter_add(&stats->pages_extended, &src->pages_extended, 1); + mi_stat_counter_add(&stats->mmap_calls, &src->mmap_calls, 1); + mi_stat_counter_add(&stats->commit_calls, &src->commit_calls, 1); + mi_stat_counter_add(&stats->reset_calls, &src->reset_calls, 1); + mi_stat_counter_add(&stats->purge_calls, &src->purge_calls, 1); + + mi_stat_counter_add(&stats->page_no_retire, &src->page_no_retire, 1); + mi_stat_counter_add(&stats->searches, &src->searches, 1); + mi_stat_counter_add(&stats->normal_count, &src->normal_count, 1); + mi_stat_counter_add(&stats->huge_count, &src->huge_count, 1); + mi_stat_counter_add(&stats->large_count, &src->large_count, 1); +#if MI_STAT>1 + for (size_t i = 0; i <= MI_BIN_HUGE; i++) { + if (src->normal_bins[i].allocated > 0 || src->normal_bins[i].freed > 0) { + mi_stat_add(&stats->normal_bins[i], &src->normal_bins[i], 1); + } + } +#endif +} + +/* ----------------------------------------------------------- + Display statistics +----------------------------------------------------------- */ + +// unit > 0 : size in binary bytes +// unit == 0: count as decimal +// unit < 0 : count in binary +static void mi_printf_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg, const char* fmt) { + char buf[32]; buf[0] = 0; + int len = 32; + const char* suffix = (unit <= 0 ? " " : "B"); + const int64_t base = (unit == 0 ? 1000 : 1024); + if (unit>0) n *= unit; + + const int64_t pos = (n < 0 ? -n : n); + if (pos < base) { + if (n!=1 || suffix[0] != 'B') { // skip printing 1 B for the unit column + snprintf(buf, len, "%d %-3s", (int)n, (n==0 ? "" : suffix)); + } + } + else { + int64_t divider = base; + const char* magnitude = "K"; + if (pos >= divider*base) { divider *= base; magnitude = "M"; } + if (pos >= divider*base) { divider *= base; magnitude = "G"; } + const int64_t tens = (n / (divider/10)); + const long whole = (long)(tens/10); + const long frac1 = (long)(tens%10); + char unitdesc[8]; + snprintf(unitdesc, 8, "%s%s%s", magnitude, (base==1024 ? "i" : ""), suffix); + snprintf(buf, len, "%ld.%ld %-3s", whole, (frac1 < 0 ? -frac1 : frac1), unitdesc); + } + _mi_fprintf(out, arg, (fmt==NULL ? "%12s" : fmt), buf); +} + + +static void mi_print_amount(int64_t n, int64_t unit, mi_output_fun* out, void* arg) { + mi_printf_amount(n,unit,out,arg,NULL); +} + +static void mi_print_count(int64_t n, int64_t unit, mi_output_fun* out, void* arg) { + if (unit==1) _mi_fprintf(out, arg, "%12s"," "); + else mi_print_amount(n,0,out,arg); +} + +static void mi_stat_print_ex(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg, const char* notok ) { + _mi_fprintf(out, arg,"%10s:", msg); + if (unit > 0) { + mi_print_amount(stat->peak, unit, out, arg); + mi_print_amount(stat->allocated, unit, out, arg); + mi_print_amount(stat->freed, unit, out, arg); + mi_print_amount(stat->current, unit, out, arg); + mi_print_amount(unit, 1, out, arg); + mi_print_count(stat->allocated, unit, out, arg); + if (stat->allocated > stat->freed) { + _mi_fprintf(out, arg, " "); + _mi_fprintf(out, arg, (notok == NULL ? "not all freed" : notok)); + _mi_fprintf(out, arg, "\n"); + } + else { + _mi_fprintf(out, arg, " ok\n"); + } + } + else if (unit<0) { + mi_print_amount(stat->peak, -1, out, arg); + mi_print_amount(stat->allocated, -1, out, arg); + mi_print_amount(stat->freed, -1, out, arg); + mi_print_amount(stat->current, -1, out, arg); + if (unit==-1) { + _mi_fprintf(out, arg, "%24s", ""); + } + else { + mi_print_amount(-unit, 1, out, arg); + mi_print_count((stat->allocated / -unit), 0, out, arg); + } + if (stat->allocated > stat->freed) + _mi_fprintf(out, arg, " not all freed!\n"); + else + _mi_fprintf(out, arg, " ok\n"); + } + else { + mi_print_amount(stat->peak, 1, out, arg); + mi_print_amount(stat->allocated, 1, out, arg); + _mi_fprintf(out, arg, "%11s", " "); // no freed + mi_print_amount(stat->current, 1, out, arg); + _mi_fprintf(out, arg, "\n"); + } +} + +static void mi_stat_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) { + mi_stat_print_ex(stat, msg, unit, out, arg, NULL); +} + +static void mi_stat_peak_print(const mi_stat_count_t* stat, const char* msg, int64_t unit, mi_output_fun* out, void* arg) { + _mi_fprintf(out, arg, "%10s:", msg); + mi_print_amount(stat->peak, unit, out, arg); + _mi_fprintf(out, arg, "\n"); +} + +static void mi_stat_counter_print(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg ) { + _mi_fprintf(out, arg, "%10s:", msg); + mi_print_amount(stat->total, -1, out, arg); + _mi_fprintf(out, arg, "\n"); +} + + +static void mi_stat_counter_print_avg(const mi_stat_counter_t* stat, const char* msg, mi_output_fun* out, void* arg) { + const int64_t avg_tens = (stat->count == 0 ? 0 : (stat->total*10 / stat->count)); + const long avg_whole = (long)(avg_tens/10); + const long avg_frac1 = (long)(avg_tens%10); + _mi_fprintf(out, arg, "%10s: %5ld.%ld avg\n", msg, avg_whole, avg_frac1); +} + + +static void mi_print_header(mi_output_fun* out, void* arg ) { + _mi_fprintf(out, arg, "%10s: %11s %11s %11s %11s %11s %11s\n", "heap stats", "peak ", "total ", "freed ", "current ", "unit ", "count "); +} + +#if MI_STAT>1 +static void mi_stats_print_bins(const mi_stat_count_t* bins, size_t max, const char* fmt, mi_output_fun* out, void* arg) { + bool found = false; + char buf[64]; + for (size_t i = 0; i <= max; i++) { + if (bins[i].allocated > 0) { + found = true; + int64_t unit = _mi_bin_size((uint8_t)i); + snprintf(buf, 64, "%s %3lu", fmt, (long)i); + mi_stat_print(&bins[i], buf, unit, out, arg); + } + } + if (found) { + _mi_fprintf(out, arg, "\n"); + mi_print_header(out, arg); + } +} +#endif + + + +//------------------------------------------------------------ +// Use an output wrapper for line-buffered output +// (which is nice when using loggers etc.) +//------------------------------------------------------------ +typedef struct buffered_s { + mi_output_fun* out; // original output function + void* arg; // and state + char* buf; // local buffer of at least size `count+1` + size_t used; // currently used chars `used <= count` + size_t count; // total chars available for output +} buffered_t; + +static void mi_buffered_flush(buffered_t* buf) { + buf->buf[buf->used] = 0; + _mi_fputs(buf->out, buf->arg, NULL, buf->buf); + buf->used = 0; +} + +static void mi_cdecl mi_buffered_out(const char* msg, void* arg) { + buffered_t* buf = (buffered_t*)arg; + if (msg==NULL || buf==NULL) return; + for (const char* src = msg; *src != 0; src++) { + char c = *src; + if (buf->used >= buf->count) mi_buffered_flush(buf); + mi_assert_internal(buf->used < buf->count); + buf->buf[buf->used++] = c; + if (c == '\n') mi_buffered_flush(buf); + } +} + +//------------------------------------------------------------ +// Print statistics +//------------------------------------------------------------ + +static void _mi_stats_print(mi_stats_t* stats, mi_output_fun* out0, void* arg0) mi_attr_noexcept { + // wrap the output function to be line buffered + char buf[256]; + buffered_t buffer = { out0, arg0, NULL, 0, 255 }; + buffer.buf = buf; + mi_output_fun* out = &mi_buffered_out; + void* arg = &buffer; + + // and print using that + mi_print_header(out,arg); + #if MI_STAT>1 + mi_stats_print_bins(stats->normal_bins, MI_BIN_HUGE, "normal",out,arg); + #endif + #if MI_STAT + mi_stat_print(&stats->normal, "normal", (stats->normal_count.count == 0 ? 1 : -(stats->normal.allocated / stats->normal_count.count)), out, arg); + mi_stat_print(&stats->large, "large", (stats->large_count.count == 0 ? 1 : -(stats->large.allocated / stats->large_count.count)), out, arg); + mi_stat_print(&stats->huge, "huge", (stats->huge_count.count == 0 ? 1 : -(stats->huge.allocated / stats->huge_count.count)), out, arg); + mi_stat_count_t total = { 0,0,0,0 }; + mi_stat_add(&total, &stats->normal, 1); + mi_stat_add(&total, &stats->large, 1); + mi_stat_add(&total, &stats->huge, 1); + mi_stat_print(&total, "total", 1, out, arg); + #endif + #if MI_STAT>1 + mi_stat_print(&stats->malloc, "malloc req", 1, out, arg); + _mi_fprintf(out, arg, "\n"); + #endif + mi_stat_print_ex(&stats->reserved, "reserved", 1, out, arg, ""); + mi_stat_print_ex(&stats->committed, "committed", 1, out, arg, ""); + mi_stat_peak_print(&stats->reset, "reset", 1, out, arg ); + mi_stat_peak_print(&stats->purged, "purged", 1, out, arg ); + mi_stat_print(&stats->page_committed, "touched", 1, out, arg); + mi_stat_print(&stats->segments, "segments", -1, out, arg); + mi_stat_print(&stats->segments_abandoned, "-abandoned", -1, out, arg); + mi_stat_print(&stats->segments_cache, "-cached", -1, out, arg); + mi_stat_print(&stats->pages, "pages", -1, out, arg); + mi_stat_print(&stats->pages_abandoned, "-abandoned", -1, out, arg); + mi_stat_counter_print(&stats->pages_extended, "-extended", out, arg); + mi_stat_counter_print(&stats->page_no_retire, "-noretire", out, arg); + mi_stat_counter_print(&stats->mmap_calls, "mmaps", out, arg); + mi_stat_counter_print(&stats->commit_calls, "commits", out, arg); + mi_stat_counter_print(&stats->reset_calls, "resets", out, arg); + mi_stat_counter_print(&stats->purge_calls, "purges", out, arg); + mi_stat_print(&stats->threads, "threads", -1, out, arg); + mi_stat_counter_print_avg(&stats->searches, "searches", out, arg); + _mi_fprintf(out, arg, "%10s: %5zu\n", "numa nodes", _mi_os_numa_node_count()); + + size_t elapsed; + size_t user_time; + size_t sys_time; + size_t current_rss; + size_t peak_rss; + size_t current_commit; + size_t peak_commit; + size_t page_faults; + mi_process_info(&elapsed, &user_time, &sys_time, ¤t_rss, &peak_rss, ¤t_commit, &peak_commit, &page_faults); + _mi_fprintf(out, arg, "%10s: %5ld.%03ld s\n", "elapsed", elapsed/1000, elapsed%1000); + _mi_fprintf(out, arg, "%10s: user: %ld.%03ld s, system: %ld.%03ld s, faults: %lu, rss: ", "process", + user_time/1000, user_time%1000, sys_time/1000, sys_time%1000, (unsigned long)page_faults ); + mi_printf_amount((int64_t)peak_rss, 1, out, arg, "%s"); + if (peak_commit > 0) { + _mi_fprintf(out, arg, ", commit: "); + mi_printf_amount((int64_t)peak_commit, 1, out, arg, "%s"); + } + _mi_fprintf(out, arg, "\n"); +} + +static mi_msecs_t mi_process_start; // = 0 + +static mi_stats_t* mi_stats_get_default(void) { + mi_heap_t* heap = mi_heap_get_default(); + return &heap->tld->stats; +} + +static void mi_stats_merge_from(mi_stats_t* stats) { + if (stats != &_mi_stats_main) { + mi_stats_add(&_mi_stats_main, stats); + memset(stats, 0, sizeof(mi_stats_t)); + } +} + +void mi_stats_reset(void) mi_attr_noexcept { + mi_stats_t* stats = mi_stats_get_default(); + if (stats != &_mi_stats_main) { memset(stats, 0, sizeof(mi_stats_t)); } + memset(&_mi_stats_main, 0, sizeof(mi_stats_t)); + if (mi_process_start == 0) { mi_process_start = _mi_clock_start(); }; +} + +void mi_stats_merge(void) mi_attr_noexcept { + mi_stats_merge_from( mi_stats_get_default() ); +} + +void _mi_stats_done(mi_stats_t* stats) { // called from `mi_thread_done` + mi_stats_merge_from(stats); +} + +void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { + mi_stats_merge_from(mi_stats_get_default()); + _mi_stats_print(&_mi_stats_main, out, arg); +} + +void mi_stats_print(void* out) mi_attr_noexcept { + // for compatibility there is an `out` parameter (which can be `stdout` or `stderr`) + mi_stats_print_out((mi_output_fun*)out, NULL); +} + +void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept { + _mi_stats_print(mi_stats_get_default(), out, arg); +} + + +// ---------------------------------------------------------------- +// Basic timer for convenience; use milli-seconds to avoid doubles +// ---------------------------------------------------------------- + +static mi_msecs_t mi_clock_diff; + +mi_msecs_t _mi_clock_now(void) { + return _mi_prim_clock_now(); +} + +mi_msecs_t _mi_clock_start(void) { + if (mi_clock_diff == 0.0) { + mi_msecs_t t0 = _mi_clock_now(); + mi_clock_diff = _mi_clock_now() - t0; + } + return _mi_clock_now(); +} + +mi_msecs_t _mi_clock_end(mi_msecs_t start) { + mi_msecs_t end = _mi_clock_now(); + return (end - start - mi_clock_diff); +} + + +// -------------------------------------------------------- +// Basic process statistics +// -------------------------------------------------------- + +mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, size_t* current_rss, size_t* peak_rss, size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept +{ + mi_process_info_t pinfo; + _mi_memzero_var(pinfo); + pinfo.elapsed = _mi_clock_end(mi_process_start); + pinfo.current_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.current)); + pinfo.peak_commit = (size_t)(mi_atomic_loadi64_relaxed((_Atomic(int64_t)*)&_mi_stats_main.committed.peak)); + pinfo.current_rss = pinfo.current_commit; + pinfo.peak_rss = pinfo.peak_commit; + pinfo.utime = 0; + pinfo.stime = 0; + pinfo.page_faults = 0; + + _mi_prim_process_info(&pinfo); + + if (elapsed_msecs!=NULL) *elapsed_msecs = (pinfo.elapsed < 0 ? 0 : (pinfo.elapsed < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.elapsed : PTRDIFF_MAX)); + if (user_msecs!=NULL) *user_msecs = (pinfo.utime < 0 ? 0 : (pinfo.utime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.utime : PTRDIFF_MAX)); + if (system_msecs!=NULL) *system_msecs = (pinfo.stime < 0 ? 0 : (pinfo.stime < (mi_msecs_t)PTRDIFF_MAX ? (size_t)pinfo.stime : PTRDIFF_MAX)); + if (current_rss!=NULL) *current_rss = pinfo.current_rss; + if (peak_rss!=NULL) *peak_rss = pinfo.peak_rss; + if (current_commit!=NULL) *current_commit = pinfo.current_commit; + if (peak_commit!=NULL) *peak_commit = pinfo.peak_commit; + if (page_faults!=NULL) *page_faults = pinfo.page_faults; +} From dfda515d0ead0d5e78fc3c83c96f869c7f2556c1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 24 Jun 2019 23:41:27 +0200 Subject: [PATCH 042/306] mimalloc: adjust for building inside Git We want to compile mimalloc's source code as part of Git, rather than requiring the code to be built as an external library: mimalloc uses a CMake-based build, which is not necessarily easy to integrate into the flavors of Git for Windows (which will be the main benefitting port). Signed-off-by: Johannes Schindelin --- compat/mimalloc/alloc.c | 4 ---- compat/mimalloc/mimalloc.h | 3 ++- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compat/mimalloc/alloc.c b/compat/mimalloc/alloc.c index 961f6d53d0f2c7..ae272c1fb54504 100644 --- a/compat/mimalloc/alloc.c +++ b/compat/mimalloc/alloc.c @@ -16,10 +16,6 @@ terms of the MIT license. A copy of the license can be found in the file #include // memset, strlen (for mi_strdup) #include // malloc, abort -#define MI_IN_ALLOC_C -#include "alloc-override.c" -#undef MI_IN_ALLOC_C - // ------------------------------------------------------ // Allocation // ------------------------------------------------------ diff --git a/compat/mimalloc/mimalloc.h b/compat/mimalloc/mimalloc.h index c0f5e96e51e975..7e3b5dd66e91a0 100644 --- a/compat/mimalloc/mimalloc.h +++ b/compat/mimalloc/mimalloc.h @@ -95,7 +95,8 @@ terms of the MIT license. A copy of the license can be found in the file // Includes // ------------------------------------------------------ -#include // size_t +#include "git-compat-util.h" + #include // bool #include // INTPTR_MAX From 1499dbda5643a1e1417e5be7831a876ec173f75a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 24 Jun 2019 23:43:06 +0200 Subject: [PATCH 043/306] mimalloc: offer a build-time option to enable it By defining `USE_MIMALLOC`, Git can now be compiled with that nicely-fast and small allocator. Note that we have to disable a couple `DEVELOPER` options to build mimalloc's source code, as it makes heavy use of declarations after statements, among other things that disagree with Git's conventions. We even have to silence some GCC warnings in non-DEVELOPER mode. For example, the `-Wno-array-bounds` flag is needed because in `-O2` builds, trying to call `NtCurrentTeb()` (which `_mi_thread_id()` does on Windows) causes the bogus warning about a system header, likely related to https://sourceforge.net/p/mingw-w64/mailman/message/37674519/ and to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99578: C:/git-sdk-64-minimal/mingw64/include/psdk_inc/intrin-impl.h:838:1: error: array subscript 0 is outside array bounds of 'long long unsigned int[0]' [-Werror=array-bounds] 838 | __buildreadseg(__readgsqword, unsigned __int64, "gs", "q") | ^~~~~~~~~~~~~~ Also: The `mimalloc` library uses C11-style atomics, therefore we must require that standard when compiling with GCC if we want to use `mimalloc` (instead of requiring "only" C99). This is what we do in the CMake definition already, therefore this commit does not need to touch `contrib/buildsystems/`. Signed-off-by: Johannes Schindelin --- Makefile | 37 +++++++++++++++++++++++++++++++++++++ config.mak.dev | 2 ++ config.mak.uname | 2 +- git-compat-util.h | 10 ++++++++++ 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 995afe21733af4..9617ae86807cc4 100644 --- a/Makefile +++ b/Makefile @@ -2124,6 +2124,43 @@ ifdef USE_NED_ALLOCATOR OVERRIDE_STRDUP = YesPlease endif +ifdef USE_MIMALLOC + MIMALLOC_OBJS = \ + compat/mimalloc/alloc-aligned.o \ + compat/mimalloc/alloc.o \ + compat/mimalloc/arena.o \ + compat/mimalloc/bitmap.o \ + compat/mimalloc/heap.o \ + compat/mimalloc/init.o \ + compat/mimalloc/options.o \ + compat/mimalloc/os.o \ + compat/mimalloc/page.o \ + compat/mimalloc/random.o \ + compat/mimalloc/prim/windows/prim.o \ + compat/mimalloc/segment.o \ + compat/mimalloc/segment-cache.o \ + compat/mimalloc/segment-map.o \ + compat/mimalloc/stats.o + + COMPAT_CFLAGS += -Icompat/mimalloc -DMI_DEBUG=0 -DUSE_MIMALLOC --std=gnu11 + COMPAT_OBJS += $(MIMALLOC_OBJS) + +$(MIMALLOC_OBJS): COMPAT_CFLAGS += -DBANNED_H + +$(MIMALLOC_OBJS): COMPAT_CFLAGS += \ + -Wno-attributes \ + -Wno-unknown-pragmas \ + -Wno-array-bounds + +ifdef DEVELOPER +$(MIMALLOC_OBJS): COMPAT_CFLAGS += \ + -Wno-pedantic \ + -Wno-declaration-after-statement \ + -Wno-old-style-definition \ + -Wno-missing-prototypes +endif +endif + ifdef OVERRIDE_STRDUP COMPAT_CFLAGS += -DOVERRIDE_STRDUP COMPAT_OBJS += compat/strdup.o diff --git a/config.mak.dev b/config.mak.dev index 0fd8cc4d355ebb..a5d6dd67dfd77e 100644 --- a/config.mak.dev +++ b/config.mak.dev @@ -22,8 +22,10 @@ endif ifneq ($(uname_S),FreeBSD) ifneq ($(or $(filter gcc6,$(COMPILER_FEATURES)),$(filter clang7,$(COMPILER_FEATURES))),) +ifndef USE_MIMALLOC DEVELOPER_CFLAGS += -std=gnu99 endif +endif else # FreeBSD cannot limit to C99 because its system headers unconditionally # rely on C11 features. diff --git a/config.mak.uname b/config.mak.uname index 7fba3e595c5cd5..cdfb606f010ba4 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -494,7 +494,7 @@ endif CC = compat/vcbuild/scripts/clink.pl AR = compat/vcbuild/scripts/lib.pl CFLAGS = - BASIC_CFLAGS = -nologo -I. -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE + BASIC_CFLAGS = -nologo -I. -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -MP -std:c11 COMPAT_OBJS = compat/msvc.o compat/winansi.o \ compat/win32/flush.o \ compat/win32/path-utils.o \ diff --git a/git-compat-util.h b/git-compat-util.h index da2986e652fee7..dd055abdfad4c8 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -443,6 +443,16 @@ char *gitdirname(char *); # include #endif +#ifdef USE_MIMALLOC +#include "mimalloc.h" +#define malloc mi_malloc +#define calloc mi_calloc +#define realloc mi_realloc +#define free mi_free +#define strdup mi_strdup +#define strndup mi_strndup +#endif + /* On most systems would have given us this, but * not on some systems (e.g. z/OS). */ From ec0016cb64d84862fa049d94a0e647701523c703 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 18 Apr 2017 12:09:08 +0200 Subject: [PATCH 044/306] mingw: demonstrate a problem with certain absolute paths On Windows, there are several categories of absolute paths. One such category starts with a backslash and is implicitly relative to the drive associated with the current working directory. Example: c: git clone https://github.com/git-for-windows/git \G4W should clone into C:\G4W. There is currently a problem with that, in that mingw_mktemp() does not expect the _wmktemp() function to prefix the absolute path with the drive prefix, and as a consequence, the resulting path does not fit into the originally-passed string buffer. The symptom is a "Result too large" error. Reported by Juan Carlos Arevalo Baeza. Signed-off-by: Johannes Schindelin --- t/t5580-unc-paths.sh | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/t/t5580-unc-paths.sh b/t/t5580-unc-paths.sh index 65ef1a3628ee94..2c30f3b51d3183 100755 --- a/t/t5580-unc-paths.sh +++ b/t/t5580-unc-paths.sh @@ -20,14 +20,11 @@ fi UNCPATH="$(winpwd)" case "$UNCPATH" in [A-Z]:*) + WITHOUTDRIVE="${UNCPATH#?:}" # Use administrative share e.g. \\localhost\C$\git-sdk-64\usr\src\git # (we use forward slashes here because MSYS2 and Git accept them, and # they are easier on the eyes) - UNCPATH="//localhost/${UNCPATH%%:*}\$/${UNCPATH#?:}" - test -d "$UNCPATH" || { - skip_all='could not access administrative share; skipping' - test_done - } + UNCPATH="//localhost/${UNCPATH%%:*}\$$WITHOUTDRIVE" ;; *) skip_all='skipping UNC path tests, cannot determine current path as UNC' @@ -35,6 +32,18 @@ case "$UNCPATH" in ;; esac +test_expect_failure 'clone into absolute path lacking a drive prefix' ' + USINGBACKSLASHES="$(echo "$WITHOUTDRIVE"/without-drive-prefix | + tr / \\\\)" && + git clone . "$USINGBACKSLASHES" && + test -f without-drive-prefix/.git/HEAD +' + +test -d "$UNCPATH" || { + skip_all='could not access administrative share; skipping' + test_done +} + test_expect_success setup ' test_commit initial ' From c2b8b3d3d96884d82f61aa312ded8ae98b9af4e7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 7 Dec 2018 13:39:30 +0100 Subject: [PATCH 045/306] clean: do not traverse mount points It seems to be not exactly rare on Windows to install NTFS junction points (the equivalent of "bind mounts" on Linux/Unix) in worktrees, e.g. to map some development tools into a subdirectory. In such a scenario, it is pretty horrible if `git clean -dfx` traverses into the mapped directory and starts to "clean up". Let's just not do that. Let's make sure before we traverse into a directory that it is not a mount point (or junction). This addresses https://github.com/git-for-windows/git/issues/607 Signed-off-by: Johannes Schindelin --- builtin/clean.c | 14 ++++++++++++++ compat/mingw.c | 22 ++++++++++++++++++++++ compat/mingw.h | 3 +++ git-compat-util.h | 4 ++++ path.c | 39 +++++++++++++++++++++++++++++++++++++++ path.h | 1 + t/t7300-clean.sh | 9 +++++++++ 7 files changed, 92 insertions(+) diff --git a/builtin/clean.c b/builtin/clean.c index 053c94fc6bd12a..aeb7eb2d1ed2c3 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -40,6 +40,8 @@ static const char *msg_remove = N_("Removing %s\n"); static const char *msg_would_remove = N_("Would remove %s\n"); static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); +static const char *msg_skip_mount_point = N_("Skipping mount point %s\n"); +static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n"); static const char *msg_warn_remove_failed = N_("failed to remove %s"); static const char *msg_warn_lstat_failed = N_("could not lstat %s\n"); static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n"); @@ -184,6 +186,18 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, goto out; } + if (is_mount_point(path)) { + if (!quiet) { + quote_path(path->buf, prefix, "ed, 0); + printf(dry_run ? + _(msg_would_skip_mount_point) : + _(msg_skip_mount_point), quoted.buf); + } + *dir_gone = 0; + + goto out; + } + dir = opendir(path->buf); if (!dir) { /* an empty dir could be removed even if it is unreadble */ diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..c616269ab56fc6 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2686,6 +2686,28 @@ pid_t waitpid(pid_t pid, int *status, int options) return -1; } +int mingw_is_mount_point(struct strbuf *path) +{ + WIN32_FIND_DATAW findbuf = { 0 }; + HANDLE handle; + wchar_t wfilename[MAX_PATH]; + int wlen = xutftowcs_path(wfilename, path->buf); + if (wlen < 0) + die(_("could not get long path for '%s'"), path->buf); + + /* remove trailing slash, if any */ + if (wlen > 0 && wfilename[wlen - 1] == L'/') + wfilename[--wlen] = L'\0'; + + handle = FindFirstFileW(wfilename, &findbuf); + if (handle == INVALID_HANDLE_VALUE) + return 0; + FindClose(handle); + + return (findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + (findbuf.dwReserved0 == IO_REPARSE_TAG_MOUNT_POINT); +} + int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen) { int upos = 0, wpos = 0; diff --git a/compat/mingw.h b/compat/mingw.h index ebfb8ba423b754..92830de32a40e3 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -454,6 +454,9 @@ static inline void convert_slashes(char *path) if (*path == '\\') *path = '/'; } +struct strbuf; +int mingw_is_mount_point(struct strbuf *path); +#define is_mount_point mingw_is_mount_point #define PATH_SEP ';' char *mingw_query_user_email(void); #define query_user_email mingw_query_user_email diff --git a/git-compat-util.h b/git-compat-util.h index e283c46c6fa06e..a9d455612fd62f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -626,6 +626,10 @@ static inline int git_has_dir_sep(const char *path) #define has_dir_sep(path) git_has_dir_sep(path) #endif +#ifndef is_mount_point +#define is_mount_point is_mount_point_via_stat +#endif + #ifndef query_user_email #define query_user_email() NULL #endif diff --git a/path.c b/path.c index 07964f5d32c977..7defc7bbefbb38 100644 --- a/path.c +++ b/path.c @@ -1226,6 +1226,45 @@ char *strip_path_suffix(const char *path, const char *suffix) return offset == -1 ? NULL : xstrndup(path, offset); } +int is_mount_point_via_stat(struct strbuf *path) +{ + size_t len = path->len; + dev_t current_dev; + struct stat st; + + if (!strcmp("/", path->buf)) + return 1; + + strbuf_addstr(path, "/."); + if (lstat(path->buf, &st)) { + /* + * If we cannot access the current directory, we cannot say + * that it is a bind mount. + */ + strbuf_setlen(path, len); + return 0; + } + current_dev = st.st_dev; + + /* Now look at the parent directory */ + strbuf_addch(path, '.'); + if (lstat(path->buf, &st)) { + /* + * If we cannot access the parent directory, we cannot say + * that it is a bind mount. + */ + strbuf_setlen(path, len); + return 0; + } + strbuf_setlen(path, len); + + /* + * If the device ID differs between current and parent directory, + * then it is a bind mount. + */ + return current_dev != st.st_dev; +} + int daemon_avoid_alias(const char *p) { int sl, ndot; diff --git a/path.h b/path.h index 5f6c85e5f8d795..9b0b5d2718e5f8 100644 --- a/path.h +++ b/path.h @@ -183,6 +183,7 @@ int normalize_path_copy(char *dst, const char *src); int strbuf_normalize_path(struct strbuf *src); int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); +int is_mount_point_via_stat(struct strbuf *path); int daemon_avoid_alias(const char *path); /* diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 00d4070156243b..7c3a1ca91df534 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -800,4 +800,13 @@ test_expect_success 'traverse into directories that may have ignored entries' ' ) ' +test_expect_success MINGW 'clean does not traverse mount points' ' + mkdir target && + >target/dont-clean-me && + git init with-mountpoint && + cmd //c "mklink /j with-mountpoint\\mountpoint target" && + git -C with-mountpoint clean -dfx && + test_path_is_file target/dont-clean-me +' + test_done From 080c8517efd81f60aed4830cb2f3071159f5bcea Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Fri, 12 May 2023 15:54:11 -0400 Subject: [PATCH 046/306] mimalloc: use "weak" random seed when statically linked Always use the internal "use_weak" random seed when initializing the "mimalloc" heap when statically linked on Windows. The imported "mimalloc" routines support several random sources to seed the heap data structures, including BCrypt.dll and RtlGenRandom. Crashes have been reported when using BCrypt.dll if it initialized during an `atexit()` handler function. Granted, such DLL initialization should not happen in an atexit handler, but yet the crashes remain. It should be noted that on Windows when statically linked, the mimalloc startup code (called by the GCC CRT to initialize static data prior to calling `main()`) always uses the internal "weak" random seed. "mimalloc" does not try to load an alternate random source until after the OS initialization has completed. Heap data is stored in `__declspec(thread)` TLS data and in theory each Git thread will have its own heap data. However, testing shows that the "mimalloc" library doesn't actually call `os_random_buf()` (to load a new random source) when creating these new per-thread heap structures. However, if an atexit handler is forced to run on a non-main thread, the "mimalloc" library *WILL* try to create a new heap and seed it with `os_random_buf()`. (The reason for this is still a mystery to this author.) The `os_random_buf()` call can cause the (previously uninitialized BCrypt.dll library) to be dynamically loaded and a call made into it. Crashes have been reported in v2.40.1.vfs.0.0 while in this call. As a workaround, the fix here forces the use of the internal "use_weak" random code for the subsequent `os_random_buf()` calls. Since we have been using that random generator for the majority of the program, it seems safe to use it for the final few mallocs in the atexit handler (of which there really shouldn't be that many. Signed-off-by: Jeff Hostetler Signed-off-by: Johannes Schindelin --- compat/mimalloc/init.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compat/mimalloc/init.c b/compat/mimalloc/init.c index 4670d5510db187..4ec5812e3ce1d0 100644 --- a/compat/mimalloc/init.c +++ b/compat/mimalloc/init.c @@ -302,7 +302,11 @@ static bool _mi_heap_init(void) { _mi_memcpy_aligned(tld, &tld_empty, sizeof(*tld)); _mi_memcpy_aligned(heap, &_mi_heap_empty, sizeof(*heap)); heap->thread_id = _mi_thread_id(); + #if defined(_WIN32) && !defined(MI_SHARED_LIB) + _mi_random_init_weak(&heap->random); // match mi_heap_main_init() + #else _mi_random_init(&heap->random); + #endif heap->cookie = _mi_heap_random_next(heap) | 1; heap->keys[0] = _mi_heap_random_next(heap); heap->keys[1] = _mi_heap_random_next(heap); From e75cb3b514434d0ff6372940cf0f509125c18ec3 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 18 Apr 2017 12:38:30 +0200 Subject: [PATCH 047/306] mingw: allow absolute paths without drive prefix When specifying an absolute path without a drive prefix, we convert that path internally. Let's make sure that we handle that case properly, too ;-) This fixes the command git clone https://github.com/git-for-windows/git \G4W Signed-off-by: Johannes Schindelin --- compat/mingw.c | 10 +++++++++- t/t5580-unc-paths.sh | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..c26c99ef505225 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1141,11 +1141,19 @@ unsigned int sleep (unsigned int seconds) char *mingw_mktemp(char *template) { wchar_t wtemplate[MAX_PATH]; + int offset = 0; + if (xutftowcs_path(wtemplate, template) < 0) return NULL; + + if (is_dir_sep(template[0]) && !is_dir_sep(template[1]) && + iswalpha(wtemplate[0]) && wtemplate[1] == L':') { + /* We have an absolute path missing the drive prefix */ + offset = 2; + } if (!_wmktemp(wtemplate)) return NULL; - if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0) + if (xwcstoutf(template, wtemplate + offset, strlen(template) + 1) < 0) return NULL; return template; } diff --git a/t/t5580-unc-paths.sh b/t/t5580-unc-paths.sh index 2c30f3b51d3183..e9df367d5777fd 100755 --- a/t/t5580-unc-paths.sh +++ b/t/t5580-unc-paths.sh @@ -32,7 +32,7 @@ case "$UNCPATH" in ;; esac -test_expect_failure 'clone into absolute path lacking a drive prefix' ' +test_expect_success 'clone into absolute path lacking a drive prefix' ' USINGBACKSLASHES="$(echo "$WITHOUTDRIVE"/without-drive-prefix | tr / \\\\)" && git clone . "$USINGBACKSLASHES" && From 64e8496b7e10ccee02eb916d9d724d9fea7ed893 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 11 Dec 2018 12:55:26 +0100 Subject: [PATCH 048/306] clean: remove mount points when possible Windows' equivalent to "bind mounts", NTFS junction points, can be unlinked without affecting the mount target. This is clearly what users expect to happen when they call `git clean -dfx` in a worktree that contains NTFS junction points: the junction should be removed, and the target directory of said junction should be left alone (unless it is inside the worktree). Signed-off-by: Johannes Schindelin --- builtin/clean.c | 13 +++++++++++++ compat/mingw.h | 1 + t/t7300-clean.sh | 1 + 3 files changed, 15 insertions(+) diff --git a/builtin/clean.c b/builtin/clean.c index aeb7eb2d1ed2c3..df39efcc50fbc4 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -40,8 +40,10 @@ static const char *msg_remove = N_("Removing %s\n"); static const char *msg_would_remove = N_("Would remove %s\n"); static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); +#ifndef CAN_UNLINK_MOUNT_POINTS static const char *msg_skip_mount_point = N_("Skipping mount point %s\n"); static const char *msg_would_skip_mount_point = N_("Would skip mount point %s\n"); +#endif static const char *msg_warn_remove_failed = N_("failed to remove %s"); static const char *msg_warn_lstat_failed = N_("could not lstat %s\n"); static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n"); @@ -187,6 +189,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, } if (is_mount_point(path)) { +#ifndef CAN_UNLINK_MOUNT_POINTS if (!quiet) { quote_path(path->buf, prefix, "ed, 0); printf(dry_run ? @@ -194,6 +197,16 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, _(msg_skip_mount_point), quoted.buf); } *dir_gone = 0; +#else + if (!dry_run && unlink(path->buf)) { + int saved_errno = errno; + quote_path(path->buf, prefix, "ed, 0); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = -1; + } +#endif goto out; } diff --git a/compat/mingw.h b/compat/mingw.h index 92830de32a40e3..92aa29bb8bb790 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -457,6 +457,7 @@ static inline void convert_slashes(char *path) struct strbuf; int mingw_is_mount_point(struct strbuf *path); #define is_mount_point mingw_is_mount_point +#define CAN_UNLINK_MOUNT_POINTS 1 #define PATH_SEP ';' char *mingw_query_user_email(void); #define query_user_email mingw_query_user_email diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 7c3a1ca91df534..6f16f3893191e7 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -806,6 +806,7 @@ test_expect_success MINGW 'clean does not traverse mount points' ' git init with-mountpoint && cmd //c "mklink /j with-mountpoint\\mountpoint target" && git -C with-mountpoint clean -dfx && + test_path_is_missing with-mountpoint/mountpoint && test_path_is_file target/dont-clean-me ' From a1108cf54037f46d15b4777a81235415e8e99225 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 16 Feb 2015 14:06:59 +0100 Subject: [PATCH 049/306] mingw: include the Python parts in the build While Git for Windows does not _ship_ Python (in order to save on bandwidth), MSYS2 provides very fine Python interpreters that users can easily take advantage of, by using Git for Windows within its SDK. Signed-off-by: Johannes Schindelin --- config.mak.uname | 1 + 1 file changed, 1 insertion(+) diff --git a/config.mak.uname b/config.mak.uname index b12d4e168ae119..7fba3e595c5cd5 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -739,6 +739,7 @@ ifeq ($(uname_S),MINGW) USE_GETTEXT_SCHEME = fallthrough USE_LIBPCRE = YesPlease USE_NED_ALLOCATOR = YesPlease + NO_PYTHON = ifeq (/mingw64,$(subst 32,64,$(prefix))) # Move system config into top-level /etc/ ETC_GITCONFIG = ../etc/gitconfig From 11ebfb38332e695262e3e38f7dd779e084bafbaa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 24 Jun 2019 23:45:21 +0200 Subject: [PATCH 050/306] mingw: use mimalloc Thorough benchmarking with repacking a subset of linux.git (the commit history reachable from 93a6fefe2f ([PATCH] fix the SYSCTL=n compilation, 2007-02-28), to be precise) suggest that this allocator is on par, in multi-threaded situations maybe even better than nedmalloc: `git repack -adfq` with mimalloc, 8 threads: 31.166991900 27.576763800 28.712311000 27.373859000 27.163141900 `git repack -adfq` with nedmalloc, 8 threads: 31.915032900 27.149883100 28.244933700 27.240188800 28.580849500 In a different test using GitHub Actions build agents (probably single-threaded, a core-strength of nedmalloc)): `git repack -q -d -l -A --unpack-unreachable=2.weeks.ago` with mimalloc: 943.426 978.500 939.709 959.811 954.605 `git repack -q -d -l -A --unpack-unreachable=2.weeks.ago` with nedmalloc: 995.383 952.179 943.253 963.043 980.468 While these measurements were not executed with complete scientific rigor, as no hardware was set aside specifically for these benchmarks, it shows that mimalloc and nedmalloc perform almost the same, nedmalloc with a bit higher variance and also slightly higher average (further testing suggests that nedmalloc performs worse in multi-threaded situations than in single-threaded ones). In short: mimalloc seems to be slightly better suited for our purposes than nedmalloc. Seeing that mimalloc is developed actively, while nedmalloc ceased to see any updates in eight years, let's use mimalloc on Windows instead. Signed-off-by: Johannes Schindelin --- config.mak.uname | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.mak.uname b/config.mak.uname index cdfb606f010ba4..2ebb82feb9f1b2 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -738,7 +738,7 @@ ifeq ($(uname_S),MINGW) HAVE_LIBCHARSET_H = YesPlease USE_GETTEXT_SCHEME = fallthrough USE_LIBPCRE = YesPlease - USE_NED_ALLOCATOR = YesPlease + USE_MIMALLOC = YesPlease NO_PYTHON = ifeq (/mingw64,$(subst 32,64,$(prefix))) # Move system config into top-level /etc/ From 6b608e8991303134214a7c29bd803642e19eb4a9 Mon Sep 17 00:00:00 2001 From: Thomas Braun Date: Thu, 8 May 2014 21:43:24 +0200 Subject: [PATCH 051/306] transport: optionally disable side-band-64k Since commit 0c499ea60fda (send-pack: demultiplex a sideband stream with status data, 2010-02-05) the send-pack builtin uses the side-band-64k capability if advertised by the server. Unfortunately this breaks pushing over the dump git protocol if used over a network connection. The detailed reasons for this breakage are (by courtesy of Jeff Preshing, quoted from https://groups.google.com/d/msg/msysgit/at8D7J-h7mw/eaLujILGUWoJ): MinGW wraps Windows sockets in CRT file descriptors in order to mimic the functionality of POSIX sockets. This causes msvcrt.dll to treat sockets as Installable File System (IFS) handles, calling ReadFile, WriteFile, DuplicateHandle and CloseHandle on them. This approach works well in simple cases on recent versions of Windows, but does not support all usage patterns. In particular, using this approach, any attempt to read & write concurrently on the same socket (from one or more processes) will deadlock in a scenario where the read waits for a response from the server which is only invoked after the write. This is what send_pack currently attempts to do in the use_sideband codepath. The new config option `sendpack.sideband` allows to override the side-band-64k capability of the server, and thus makes the dumb git protocol work. Other transportation methods like ssh and http/https still benefit from the sideband channel, therefore the default value of `sendpack.sideband` is still true. Signed-off-by: Thomas Braun Signed-off-by: Oliver Schneider Signed-off-by: Johannes Schindelin --- Documentation/config.txt | 2 ++ Documentation/config/sendpack.txt | 5 +++++ send-pack.c | 6 +++--- 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 Documentation/config/sendpack.txt diff --git a/Documentation/config.txt b/Documentation/config.txt index 8c0b3ed8075214..3ad4956fe48944 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -518,6 +518,8 @@ include::config/safe.txt[] include::config/sendemail.txt[] +include::config/sendpack.txt[] + include::config/sequencer.txt[] include::config/showbranch.txt[] diff --git a/Documentation/config/sendpack.txt b/Documentation/config/sendpack.txt new file mode 100644 index 00000000000000..e306f657fba7dd --- /dev/null +++ b/Documentation/config/sendpack.txt @@ -0,0 +1,5 @@ +sendpack.sideband:: + Allows to disable the side-band-64k capability for send-pack even + when it is advertised by the server. Makes it possible to work + around a limitation in the git for windows implementation together + with the dump git protocol. Defaults to true. diff --git a/send-pack.c b/send-pack.c index 7e8321368379ef..0d39ee7859d067 100644 --- a/send-pack.c +++ b/send-pack.c @@ -494,7 +494,7 @@ int send_pack(struct send_pack_args *args, int need_pack_data = 0; int allow_deleting_refs = 0; int status_report = 0; - int use_sideband = 0; + int use_sideband = 1; int quiet_supported = 0; int agent_supported = 0; int advertise_sid = 0; @@ -518,6 +518,7 @@ int send_pack(struct send_pack_args *args, goto out; } + git_config_get_bool("sendpack.sideband", &use_sideband); git_config_get_bool("push.negotiate", &push_negotiate); if (push_negotiate) { trace2_region_enter("send_pack", "push_negotiate", the_repository); @@ -539,8 +540,7 @@ int send_pack(struct send_pack_args *args, allow_deleting_refs = 1; if (server_supports("ofs-delta")) args->use_ofs_delta = 1; - if (server_supports("side-band-64k")) - use_sideband = 1; + use_sideband = use_sideband && server_supports("side-band-64k"); if (server_supports("quiet")) quiet_supported = 1; if (server_supports("agent")) From 0bc4c90b37de0ce93b5e81e107eb1976e6be1eee Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 31 Jan 2020 12:02:47 +0100 Subject: [PATCH 052/306] mingw: demonstrate a `git add` issue with NTFS junctions NTFS junctions are somewhat similar in spirit to Unix bind mounts: they point to a different directory and are resolved by the filesystem driver. As such, they appear to `lstat()` as if they are directories, not as if they are symbolic links. _Any_ user can create junctions, while symbolic links can only be created by non-administrators in Developer Mode on Windows 10. Hence NTFS junctions are much more common "in the wild" than NTFS symbolic links. It was reported in https://github.com/git-for-windows/git/issues/2481 that adding files via an absolute path that traverses an NTFS junction: since 1e64d18 (mingw: do resolve symlinks in `getcwd()`), we resolve not only symbolic links but also NTFS junctions when determining the absolute path of the current directory. The same is not true for `git add `, where symbolic links are resolved in ``, but not NTFS junctions. Signed-off-by: Johannes Schindelin --- t/t3700-add.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/t/t3700-add.sh b/t/t3700-add.sh index df580a5806b4f1..d934f3417faa45 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -548,4 +548,15 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' ' git add "$downcased" ' +test_expect_failure MINGW 'can add files via NTFS junctions' ' + test_when_finished "cmd //c rmdir junction && rm -rf target" && + test_create_repo target && + cmd //c "mklink /j junction target" && + >target/via-junction && + git -C junction add "$(pwd)/junction/via-junction" && + echo via-junction >expect && + git -C target diff --cached --name-only >actual && + test_cmp expect actual +' + test_done From 0cb219f0a47fa8565b925b0f077518f467971c0d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 21 Feb 2017 13:28:58 +0100 Subject: [PATCH 053/306] mingw: ensure valid CTYPE A change between versions 2.4.1 and 2.6.0 of the MSYS2 runtime modified how Cygwin's runtime (and hence Git for Windows' MSYS2 runtime derivative) handles locales: d16a56306d (Consolidate wctomb/mbtowc calls for POSIX-1.2008, 2016-07-20). An unintended side-effect is that "cold-calling" into the POSIX emulation will start with a locale based on the current code page, something that Git for Windows is very ill-prepared for, as it expects to be able to pass a command-line containing non-ASCII characters to the shell without having those characters munged. One symptom of this behavior: when `git clone` or `git fetch` shell out to call `git-upload-pack` with a path that contains non-ASCII characters, the shell tried to interpret the entire command-line (including command-line parameters) as executable path, which obviously must fail. This fixes https://github.com/git-for-windows/git/issues/1036 Signed-off-by: Johannes Schindelin --- compat/mingw.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..5f6b42b3301643 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2822,6 +2822,9 @@ static void setup_windows_environment(void) if (!tmp && (tmp = getenv("USERPROFILE"))) setenv("HOME", tmp, 1); } + + if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG")) + setenv("LC_CTYPE", "C.UTF-8", 1); } static PSID get_current_user_sid(void) From 13709f0783a68cfd25f0bff5f63159a9c48f27d1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 31 Jan 2020 11:44:31 +0100 Subject: [PATCH 054/306] strbuf_realpath(): use platform-dependent API if available Some platforms (e.g. Windows) provide API functions to resolve paths much quicker. Let's offer a way to short-cut `strbuf_realpath()` on those platforms. Signed-off-by: Johannes Schindelin --- abspath.c | 3 +++ git-compat-util.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/abspath.c b/abspath.c index 1202cde23dbc9b..0c17e98654e4b0 100644 --- a/abspath.c +++ b/abspath.c @@ -93,6 +93,9 @@ static char *strbuf_realpath_1(struct strbuf *resolved, const char *path, goto error_out; } + if (platform_strbuf_realpath(resolved, path)) + return resolved->buf; + strbuf_addstr(&remaining, path); get_root_part(resolved, &remaining); diff --git a/git-compat-util.h b/git-compat-util.h index e283c46c6fa06e..3e419bbe886fe2 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -630,6 +630,10 @@ static inline int git_has_dir_sep(const char *path) #define query_user_email() NULL #endif +#ifndef platform_strbuf_realpath +#define platform_strbuf_realpath(resolved, path) NULL +#endif + #ifdef __TANDEM #include #include From 345d8dd1b6f01e4460887bda372c904a014b9d02 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 1 Feb 2020 00:31:16 +0100 Subject: [PATCH 055/306] mingw: allow `git.exe` to be used instead of the "Git wrapper" Git for Windows wants to add `git.exe` to the users' `PATH`, without cluttering the latter with unnecessary executables such as `wish.exe`. To that end, it invented the concept of its "Git wrapper", i.e. a tiny executable located in `C:\Program Files\Git\cmd\git.exe` (originally a CMD script) whose sole purpose is to set up a couple of environment variables and then spawn the _actual_ `git.exe` (which nowadays lives in `C:\Program Files\Git\mingw64\bin\git.exe` for 64-bit, and the obvious equivalent for 32-bit installations). Currently, the following environment variables are set unless already initialized: - `MSYSTEM`, to make sure that the MSYS2 Bash and the MSYS2 Perl interpreter behave as expected, and - `PLINK_PROTOCOL`, to force PuTTY's `plink.exe` to use the SSH protocol instead of Telnet, - `PATH`, to make sure that the `bin` folder in the user's home directory, as well as the `/mingw64/bin` and the `/usr/bin` directories are included. The trick here is that the `/mingw64/bin/` and `/usr/bin/` directories are relative to the top-level installation directory of Git for Windows (which the included Bash interprets as `/`, i.e. as the MSYS pseudo root directory). Using the absence of `MSYSTEM` as a tell-tale, we can detect in `git.exe` whether these environment variables have been initialized properly. Therefore we can call `C:\Program Files\Git\mingw64\bin\git` in-place after this change, without having to call Git through the Git wrapper. Obviously, above-mentioned directories must be _prepended_ to the `PATH` variable, otherwise we risk picking up executables from unrelated Git installations. We do that by constructing the new `PATH` value from scratch, appending `$HOME/bin` (if `HOME` is set), then the MSYS2 system directories, and then appending the original `PATH`. Side note: this modification of the `PATH` variable is independent of the modification necessary to reach the executables and scripts in `/mingw64/libexec/git-core/`, i.e. the `GIT_EXEC_PATH`. That modification is still performed by Git, elsewhere, long after making the changes described above. While we _still_ cannot simply hard-link `mingw64\bin\git.exe` to `cmd` (because the former depends on a couple of `.dll` files that are only in `mingw64\bin`, i.e. calling `...\cmd\git.exe` would fail to load due to missing dependencies), at least we can now avoid that extra process of running the Git wrapper (which then has to wait for the spawned `git.exe` to finish) by calling `...\mingw64\bin\git.exe` directly, via its absolute path. Testing this is in Git's test suite tricky: we set up a "new" MSYS pseudo-root and copy the `git.exe` file into the appropriate location, then verify that `MSYSTEM` is set properly, and also that the `PATH` is modified so that scripts can be found in `$HOME/bin`, `/mingw64/bin/` and `/usr/bin/`. This addresses https://github.com/git-for-windows/git/issues/2283 Signed-off-by: Johannes Schindelin --- compat/mingw.c | 69 +++++++++++++++++++++++++++++++++++++++++++ config.mak.uname | 4 +-- t/t0060-path-utils.sh | 31 ++++++++++++++++++- 3 files changed, 101 insertions(+), 3 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 5f6b42b3301643..97860f1fc7eff1 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2771,6 +2771,47 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen) return -1; } +#ifdef ENSURE_MSYSTEM_IS_SET +static size_t append_system_bin_dirs(char *path, size_t size) +{ +#if !defined(RUNTIME_PREFIX) || !defined(HAVE_WPGMPTR) + return 0; +#else + char prefix[32768]; + const char *slash; + size_t len = xwcstoutf(prefix, _wpgmptr, sizeof(prefix)), off = 0; + + if (len == 0 || len >= sizeof(prefix) || + !(slash = find_last_dir_sep(prefix))) + return 0; + /* strip trailing `git.exe` */ + len = slash - prefix; + + /* strip trailing `cmd` or `mingw64\bin` or `mingw32\bin` or `bin` or `libexec\git-core` */ + if (strip_suffix_mem(prefix, &len, "\\mingw64\\libexec\\git-core") || + strip_suffix_mem(prefix, &len, "\\mingw64\\bin")) + off += xsnprintf(path + off, size - off, + "%.*s\\mingw64\\bin;", (int)len, prefix); + else if (strip_suffix_mem(prefix, &len, "\\mingw32\\libexec\\git-core") || + strip_suffix_mem(prefix, &len, "\\mingw32\\bin")) + off += xsnprintf(path + off, size - off, + "%.*s\\mingw32\\bin;", (int)len, prefix); + else if (strip_suffix_mem(prefix, &len, "\\cmd") || + strip_suffix_mem(prefix, &len, "\\bin") || + strip_suffix_mem(prefix, &len, "\\libexec\\git-core")) + off += xsnprintf(path + off, size - off, + "%.*s\\mingw%d\\bin;", (int)len, prefix, + (int)(sizeof(void *) * 8)); + else + return 0; + + off += xsnprintf(path + off, size - off, + "%.*s\\usr\\bin;", (int)len, prefix); + return off; +#endif +} +#endif + static void setup_windows_environment(void) { char *tmp = getenv("TMPDIR"); @@ -2823,6 +2864,34 @@ static void setup_windows_environment(void) setenv("HOME", tmp, 1); } + if (!getenv("PLINK_PROTOCOL")) + setenv("PLINK_PROTOCOL", "ssh", 0); + +#ifdef ENSURE_MSYSTEM_IS_SET + if (!(tmp = getenv("MSYSTEM")) || !tmp[0]) { + const char *home = getenv("HOME"), *path = getenv("PATH"); + char buf[32768]; + size_t off = 0; + + xsnprintf(buf, sizeof(buf), + "MINGW%d", (int)(sizeof(void *) * 8)); + setenv("MSYSTEM", buf, 1); + + if (home) + off += xsnprintf(buf + off, sizeof(buf) - off, + "%s\\bin;", home); + off += append_system_bin_dirs(buf + off, sizeof(buf) - off); + if (path) + off += xsnprintf(buf + off, sizeof(buf) - off, + "%s", path); + else if (off > 0) + buf[off - 1] = '\0'; + else + buf[0] = '\0'; + setenv("PATH", buf, 1); + } +#endif + if (!getenv("LC_ALL") && !getenv("LC_CTYPE") && !getenv("LANG")) setenv("LC_CTYPE", "C.UTF-8", 1); } diff --git a/config.mak.uname b/config.mak.uname index b12d4e168ae119..23748b65a8e480 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -501,7 +501,7 @@ endif compat/win32/pthread.o compat/win32/syslog.o \ compat/win32/trace2_win32_process_info.o \ compat/win32/dirent.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DENSURE_MSYSTEM_IS_SET -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -ENTRY:wmainCRTStartup -SUBSYSTEM:CONSOLE # invalidcontinue.obj allows Git's source code to close the same file # handle twice, or to access the osfhandle of an already-closed stdout @@ -730,7 +730,7 @@ ifeq ($(uname_S),MINGW) endif CC = gcc COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \ - -fstack-protector-strong + -DENSURE_MSYSTEM_IS_SET -fstack-protector-strong EXTLIBS += -lntdll EXTRA_PROGRAMS += headless-git$X INSTALL = /bin/install diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index dbb2e73bcd912a..a4edda1462da52 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -598,7 +598,8 @@ test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD 'RUNTIME_PREFIX wor cp "$GIT_EXEC_PATH"/git$X pretend/bin/ && GIT_EXEC_PATH= ./pretend/bin/git here >actual && echo HERE >expect && - test_cmp expect actual' + test_cmp expect actual +' test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' ' mkdir -p pretend/bin && @@ -609,4 +610,32 @@ test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' test_cmp expect actual ' +test_expect_success MINGW 'MSYSTEM/PATH is adjusted if necessary' ' + mkdir -p "$HOME"/bin pretend/mingw64/bin \ + pretend/mingw64/libexec/git-core pretend/usr/bin && + cp "$GIT_EXEC_PATH"/git.exe pretend/mingw64/bin/ && + cp "$GIT_EXEC_PATH"/git.exe pretend/mingw64/libexec/git-core/ && + # copy the .dll files, if any (happens when building via CMake) + case "$GIT_EXEC_PATH"/*.dll in + */"*.dll") ;; # no `.dll` files to be copied + *) + cp "$GIT_EXEC_PATH"/*.dll pretend/mingw64/bin/ && + cp "$GIT_EXEC_PATH"/*.dll pretend/mingw64/libexec/git-core/ + ;; + esac && + echo "env | grep MSYSTEM=" | write_script "$HOME"/bin/git-test-home && + echo "echo mingw64" | write_script pretend/mingw64/bin/git-test-bin && + echo "echo usr" | write_script pretend/usr/bin/git-test-bin2 && + + ( + MSYSTEM= && + GIT_EXEC_PATH= && + pretend/mingw64/libexec/git-core/git.exe test-home >actual && + pretend/mingw64/libexec/git-core/git.exe test-bin >>actual && + pretend/mingw64/bin/git.exe test-bin2 >>actual + ) && + test_write_lines MSYSTEM=$MSYSTEM mingw64 usr >expect && + test_cmp expect actual +' + test_done From d7476fc7eb9a6073e867fed9682ec8c21e9254f0 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 1 Jan 2020 21:07:22 +0100 Subject: [PATCH 056/306] mingw: do resolve symlinks in `getcwd()` As pointed out in https://github.com/git-for-windows/git/issues/1676, the `git rev-parse --is-inside-work-tree` command currently fails when the current directory's path contains symbolic links. The underlying reason for this bug is that `getcwd()` is supposed to resolve symbolic links, but our `mingw_getcwd()` implementation did not. We do have all the building blocks for that, though: the `GetFinalPathByHandleW()` function will resolve symbolic links. However, we only called that function if `GetLongPathNameW()` failed, for historical reasons: the latter function was supported for a long time, but the former API function was introduced only with Windows Vista, and we used to support also Windows XP. With that support having been dropped, we are free to call the symbolic link-resolving function right away. Signed-off-by: Johannes Schindelin --- compat/mingw.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..6263a725c8bc36 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1211,18 +1211,16 @@ char *mingw_getcwd(char *pointer, int len) { wchar_t cwd[MAX_PATH], wpointer[MAX_PATH]; DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd); + HANDLE hnd; if (!ret || ret >= ARRAY_SIZE(cwd)) { errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError()); return NULL; } - ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer)); - if (!ret && GetLastError() == ERROR_ACCESS_DENIED) { - HANDLE hnd = CreateFileW(cwd, 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (hnd == INVALID_HANDLE_VALUE) - return NULL; + hnd = CreateFileW(cwd, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hnd != INVALID_HANDLE_VALUE) { ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0); CloseHandle(hnd); if (!ret || ret >= ARRAY_SIZE(wpointer)) @@ -1231,13 +1229,11 @@ char *mingw_getcwd(char *pointer, int len) return NULL; return pointer; } - if (!ret || ret >= ARRAY_SIZE(wpointer)) - return NULL; - if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributesW(cwd) == INVALID_FILE_ATTRIBUTES) { errno = ENOENT; return NULL; } - if (xwcstoutf(pointer, wpointer, len) < 0) + if (xwcstoutf(pointer, cwd, len) < 0) return NULL; convert_slashes(pointer); return pointer; From 2b6f2917bb34c9ecee2648d6e7c2deb58e328427 Mon Sep 17 00:00:00 2001 From: Bjoern Mueller Date: Wed, 22 Jan 2020 13:49:13 +0100 Subject: [PATCH 057/306] mingw: fix fatal error working on mapped network drives on Windows In 1e64d18 (mingw: do resolve symlinks in `getcwd()`) a problem was introduced that causes git for Windows to stop working with certain mapped network drives (in particular, drives that are mapped to locations with long path names). Error message was "fatal: Unable to read current working directory: No such file or directory". Present change fixes this issue as discussed in https://github.com/git-for-windows/git/issues/2480 Signed-off-by: Bjoern Mueller --- compat/mingw.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..6b5938f62e4534 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1225,8 +1225,13 @@ char *mingw_getcwd(char *pointer, int len) return NULL; ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0); CloseHandle(hnd); - if (!ret || ret >= ARRAY_SIZE(wpointer)) - return NULL; + if (!ret || ret >= ARRAY_SIZE(wpointer)) { + ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer)); + if (!ret || ret >= ARRAY_SIZE(wpointer)) { + errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError()); + return NULL; + } + } if (xwcstoutf(pointer, normalize_ntpath(wpointer), len) < 0) return NULL; return pointer; From d9192520669ff880cda77c713ed9e33e970e1054 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Thu, 30 Jan 2020 14:22:27 -0500 Subject: [PATCH 058/306] clink.pl: fix MSVC compile script to handle libcurl-d.lib Update clink.pl to link with either libcurl.lib or libcurl-d.lib depending on whether DEBUG=1 is set. Signed-off-by: Jeff Hostetler Signed-off-by: Johannes Schindelin --- compat/vcbuild/scripts/clink.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index 3bd824154be381..c4c99d1a11f18c 100755 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -56,7 +56,8 @@ # need to use that instead? foreach my $flag (@lflags) { if ($flag =~ /^-LIBPATH:(.*)/) { - foreach my $l ("libcurl_imp.lib", "libcurl.lib") { + my $libcurl = $is_debug ? "libcurl-d.lib" : "libcurl.lib"; + foreach my $l ("libcurl_imp.lib", $libcurl) { if (-f "$1/$l") { $lib = $l; last; From 132f4314d100c84df5b6a43c8501a78f00d2fc9e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 31 Jan 2020 11:49:04 +0100 Subject: [PATCH 059/306] mingw: implement a platform-specific `strbuf_realpath()` There is a Win32 API function to resolve symbolic links, and we can use that instead of resolving them manually. Even better, this function also resolves NTFS junction points (which are somewhat similar to bind mounts). This fixes https://github.com/git-for-windows/git/issues/2481. Signed-off-by: Johannes Schindelin --- compat/mingw.c | 76 +++++++++++++++++++++++++++++++++++++++++++ compat/mingw.h | 3 ++ t/t0060-path-utils.sh | 8 +++++ t/t3700-add.sh | 2 +- t/t5601-clone.sh | 7 ++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..69b3c762f4f5d9 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1207,6 +1207,82 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) } #endif +char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path) +{ + wchar_t wpath[MAX_PATH]; + HANDLE h; + DWORD ret; + int len; + const char *last_component = NULL; + char *append = NULL; + + if (xutftowcs_path(wpath, path) < 0) + return NULL; + + h = CreateFileW(wpath, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + + /* + * strbuf_realpath() allows the last path component to not exist. If + * that is the case, now it's time to try without last component. + */ + if (h == INVALID_HANDLE_VALUE && + GetLastError() == ERROR_FILE_NOT_FOUND) { + /* cut last component off of `wpath` */ + wchar_t *p = wpath + wcslen(wpath); + + while (p != wpath) + if (*(--p) == L'/' || *p == L'\\') + break; /* found start of last component */ + + if (p != wpath && (last_component = find_last_dir_sep(path))) { + append = xstrdup(last_component + 1); /* skip directory separator */ + /* + * Do not strip the trailing slash at the drive root, otherwise + * the path would be e.g. `C:` (which resolves to the + * _current_ directory on that drive). + */ + if (p[-1] == L':') + p[1] = L'\0'; + else + *p = L'\0'; + h = CreateFileW(wpath, 0, FILE_SHARE_READ | + FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL); + } + } + + if (h == INVALID_HANDLE_VALUE) { +realpath_failed: + FREE_AND_NULL(append); + return NULL; + } + + ret = GetFinalPathNameByHandleW(h, wpath, ARRAY_SIZE(wpath), 0); + CloseHandle(h); + if (!ret || ret >= ARRAY_SIZE(wpath)) + goto realpath_failed; + + len = wcslen(wpath) * 3; + strbuf_grow(resolved, len); + len = xwcstoutf(resolved->buf, normalize_ntpath(wpath), len); + if (len < 0) + goto realpath_failed; + resolved->len = len; + + if (append) { + /* Use forward-slash, like `normalize_ntpath()` */ + strbuf_complete(resolved, '/'); + strbuf_addstr(resolved, append); + FREE_AND_NULL(append); + } + + return resolved->buf; + +} + char *mingw_getcwd(char *pointer, int len) { wchar_t cwd[MAX_PATH], wpointer[MAX_PATH]; diff --git a/compat/mingw.h b/compat/mingw.h index ebfb8ba423b754..35c427376cbc6d 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -457,6 +457,9 @@ static inline void convert_slashes(char *path) #define PATH_SEP ';' char *mingw_query_user_email(void); #define query_user_email mingw_query_user_email +struct strbuf; +char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path); +#define platform_strbuf_realpath mingw_strbuf_realpath #if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800) #define PRIuMAX "I64u" #define PRId64 "I64d" diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index dbb2e73bcd912a..14ac5c1137d0c2 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -281,6 +281,14 @@ test_expect_success SYMLINKS 'real path works on symlinks' ' test_cmp expect actual ' +test_expect_success MINGW 'real path works near drive root' ' + # we need a non-existing path at the drive root; simply skip if C:/xyz exists + if test ! -e C:/xyz + then + test C:/xyz = $(test-tool path-utils real_path C:/xyz) + fi +' + test_expect_success SYMLINKS 'prefix_path works with absolute paths to work tree symlinks' ' ln -s target symlink && echo "symlink" >expect && diff --git a/t/t3700-add.sh b/t/t3700-add.sh index d934f3417faa45..10185f31f019a9 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -548,7 +548,7 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' ' git add "$downcased" ' -test_expect_failure MINGW 'can add files via NTFS junctions' ' +test_expect_success MINGW 'can add files via NTFS junctions' ' test_when_finished "cmd //c rmdir junction && rm -rf target" && test_create_repo target && cmd //c "mklink /j junction target" && diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index d0c18660e33113..b3ecd6b8b06c33 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -78,6 +78,13 @@ test_expect_success 'clone respects GIT_WORK_TREE' ' ' +test_expect_success CASE_INSENSITIVE_FS 'core.worktree is not added due to path case' ' + + mkdir UPPERCASE && + git clone src "$(pwd)/uppercase" && + test "unset" = "$(git -C UPPERCASE config --default unset core.worktree)" +' + test_expect_success 'clone from hooks' ' test_create_repo r0 && From 3db90c39c03c9349ecbecd4b0d5123fb4ff45865 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 25 Aug 2020 12:13:26 +0200 Subject: [PATCH 060/306] mingw: ignore HOMEDRIVE/HOMEPATH if it points to Windows' system directory Internally, Git expects the environment variable `HOME` to be set, and to point to the current user's home directory. This environment variable is not set by default on Windows, and therefore Git tries its best to construct one if it finds `HOME` unset. There are actually two different approaches Git tries: first, it looks at `HOMEDRIVE`/`HOMEPATH` because this is widely used in corporate environments with roaming profiles, and a user generally wants their global Git settings to be in a roaming profile. Only when `HOMEDRIVE`/`HOMEPATH` is either unset or does not point to a valid location, Git will fall back to using `USERPROFILE` instead. However, starting with Windows Vista, for secondary logons and services, the environment variables `HOMEDRIVE`/`HOMEPATH` point to Windows' system directory (usually `C:\Windows\system32`). That is undesirable, and that location is usually write-protected anyway. So let's verify that the `HOMEDRIVE`/`HOMEPATH` combo does not point to Windows' system directory before using it, falling back to `USERPROFILE` if it does. This fixes git-for-windows#2709 Initial-Path-by: Ivan Pozdeev Signed-off-by: Johannes Schindelin --- compat/mingw.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/compat/mingw.c b/compat/mingw.c index 97860f1fc7eff1..bd415c4471348a 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2812,6 +2812,18 @@ static size_t append_system_bin_dirs(char *path, size_t size) } #endif +static int is_system32_path(const char *path) +{ + WCHAR system32[MAX_PATH], wpath[MAX_PATH]; + + if (xutftowcs_path(wpath, path) < 0 || + !GetSystemDirectoryW(system32, ARRAY_SIZE(system32)) || + _wcsicmp(system32, wpath)) + return 0; + + return 1; +} + static void setup_windows_environment(void) { char *tmp = getenv("TMPDIR"); @@ -2852,7 +2864,8 @@ static void setup_windows_environment(void) strbuf_addstr(&buf, tmp); if ((tmp = getenv("HOMEPATH"))) { strbuf_addstr(&buf, tmp); - if (is_directory(buf.buf)) + if (!is_system32_path(buf.buf) && + is_directory(buf.buf)) setenv("HOME", buf.buf, 1); else tmp = NULL; /* use $USERPROFILE */ From acc5a141275519cd19fb45293db3138653884fb9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 4 Mar 2020 21:55:28 +0100 Subject: [PATCH 061/306] http: use new "best effort" strategy for Secure Channel revoke checking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The native Windows HTTPS backend is based on Secure Channel which lets the caller decide how to handle revocation checking problems caused by missing information in the certificate or offline CRL distribution points. Unfortunately, cURL chose to handle these problems differently than OpenSSL by default: while OpenSSL happily ignores those problems (essentially saying "¯\_(ツ)_/¯"), the Secure Channel backend will error out instead. As a remedy, the "no revoke" mode was introduced, which turns off revocation checking altogether. This is a bit heavy-handed. We support this via the `http.schannelCheckRevoke` setting. In https://github.com/curl/curl/pull/4981, we contributed an opt-in "best effort" strategy that emulates what OpenSSL seems to do. In Git for Windows, we actually want this to be the default. This patch makes it so, introducing it as a new value for the `http.schannelCheckRevoke" setting, which now becmes a tristate: it accepts the values "false", "true" or "best-effort" (defaulting to the last one). Signed-off-by: Johannes Schindelin --- Documentation/config/http.txt | 12 +++++++----- http.c | 26 ++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt index a14371b5c96a51..9125d93a75b036 100644 --- a/Documentation/config/http.txt +++ b/Documentation/config/http.txt @@ -218,11 +218,13 @@ http.sslBackend:: 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. + when http.sslBackend is set to "schannel" via "true" and "false", + respectively. Another accepted value is "best-effort" (the default) + in which case revocation checks are performed, but errors due to + revocation list distribution points that are offline are silently + ignored, as well as errors due to certificates missing revocation + list distribution points. 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 diff --git a/http.c b/http.c index c8fc15aa118d3b..e057e877163b7b 100644 --- a/http.c +++ b/http.c @@ -143,7 +143,13 @@ static char *cached_accept_language; static char *http_ssl_backend; -static int http_schannel_check_revoke = 1; +static int http_schannel_check_revoke_mode = +#ifdef CURLSSLOPT_REVOKE_BEST_EFFORT + CURLSSLOPT_REVOKE_BEST_EFFORT; +#else + CURLSSLOPT_NO_REVOKE; +#endif + /* * 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 @@ -418,7 +424,19 @@ static int http_options(const char *var, const char *value, } if (!strcmp("http.schannelcheckrevoke", var)) { - http_schannel_check_revoke = git_config_bool(var, value); + if (value && !strcmp(value, "best-effort")) { + http_schannel_check_revoke_mode = +#ifdef CURLSSLOPT_REVOKE_BEST_EFFORT + CURLSSLOPT_REVOKE_BEST_EFFORT; +#else + CURLSSLOPT_NO_REVOKE; + warning(_("%s=%s unsupported by current cURL"), + var, value); +#endif + } else + http_schannel_check_revoke_mode = + (git_config_bool(var, value) ? + 0 : CURLSSLOPT_NO_REVOKE); return 0; } @@ -1045,8 +1063,8 @@ static CURL *get_curl_handle(void) #endif if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && - !http_schannel_check_revoke) { - curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NO_REVOKE); + http_schannel_check_revoke_mode) { + curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, http_schannel_check_revoke_mode); } if (http_proactive_auth != PROACTIVE_AUTH_NONE) From ccdb636f7e6366feb898c62c9b1e04282bc1d11a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 May 2020 14:08:36 +0200 Subject: [PATCH 062/306] vcxproj: unclash project directories with build outputs It already caused problems with the test suite that the directory containing `git.vcxproj` is called the same as the Git executable without its file extension: `./git` is ambiguous, it could refer both to the directory `git/` as well as to `git.exe`. Now there is one more problem: when our GitHub workflow runs on the `vs/master` branch, it fails in all but the Windows builds, as they want to write the file `git` but there is already a directory in the way. Let's just go ahead and append `.proj` to all of those directories, e.g. `git.proj/` instead of `git/`. Signed-off-by: Johannes Schindelin --- config.mak.uname | 8 ++++---- contrib/buildsystems/Generators/Vcxproj.pm | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/config.mak.uname b/config.mak.uname index b12d4e168ae119..ecca53fc0d0d8b 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -768,7 +768,7 @@ vcxproj: # Make .vcxproj files and add them perl contrib/buildsystems/generate -g Vcxproj - git add -f git.sln {*,*/lib,t/helper/*}/*.vcxproj + git add -f git.sln {*,*/lib.proj,t/helper/*}/*.vcxproj # Generate the LinkOrCopyBuiltins.targets and LinkOrCopyRemoteHttp.targets file (echo '' && \ @@ -778,7 +778,7 @@ vcxproj: echo ' '; \ done && \ echo ' ' && \ - echo '') >git/LinkOrCopyBuiltins.targets + echo '') >git.proj/LinkOrCopyBuiltins.targets (echo '' && \ echo ' ' && \ for name in $(REMOTE_CURL_ALIASES); \ @@ -786,8 +786,8 @@ vcxproj: echo ' '; \ done && \ echo ' ' && \ - echo '') >git-remote-http/LinkOrCopyRemoteHttp.targets - git add -f git/LinkOrCopyBuiltins.targets git-remote-http/LinkOrCopyRemoteHttp.targets + echo '') >git-remote-http.proj/LinkOrCopyRemoteHttp.targets + git add -f git.proj/LinkOrCopyBuiltins.targets git-remote-http.proj/LinkOrCopyRemoteHttp.targets # Add generated headers $(MAKE) MSVC=1 SKIP_VCPKG=1 prefix=/mingw64 $(GENERATED_H) diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index b2e68a16715e39..0439b82f55e243 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -58,8 +58,8 @@ sub createProject { my $uuid = generate_guid($name); $$build_structure{"$prefix${target}_GUID"} = $uuid; my $vcxproj = $target; - $vcxproj =~ s/(.*\/)?(.*)/$&\/$2.vcxproj/; - $vcxproj =~ s/([^\/]*)(\/lib)\/(lib.vcxproj)/$1$2\/$1_$3/; + $vcxproj =~ s/(.*\/)?(.*)/$&.proj\/$2.vcxproj/; + $vcxproj =~ s/([^\/]*)(\/lib\.proj)\/(lib.vcxproj)/$1$2\/$1_$3/; $$build_structure{"$prefix${target}_VCXPROJ"} = $vcxproj; my @srcs = sort(map("$rel_dir\\$_", @{$$build_structure{"$prefix${name}_SOURCES"}})); @@ -89,7 +89,9 @@ sub createProject { $defines =~ s/>/>/g; $defines =~ s/\'//g; - die "Could not create the directory $target for $label project!\n" unless (-d "$target" || mkdir "$target"); + my $dir = $vcxproj; + $dir =~ s/\/[^\/]*$//; + die "Could not create the directory $dir for $label project!\n" unless (-d "$dir" || mkdir "$dir"); open F, ">$vcxproj" or die "Could not open $vcxproj for writing!\n"; binmode F, ":crlf :utf8"; @@ -237,7 +239,7 @@ EOM print F << "EOM"; - + $uuid_libgit false @@ -252,7 +254,7 @@ EOM } if (!($name =~ 'xdiff')) { print F << "EOM"; - + $uuid_xdiff_lib false @@ -261,7 +263,7 @@ EOM if ($name =~ /(test-(line-buffer|svn-fe)|^git-remote-testsvn)\.exe$/) { my $uuid_vcs_svn_lib = $$build_structure{"LIBS_vcs-svn/lib_GUID"}; print F << "EOM"; - + $uuid_vcs_svn_lib false @@ -338,7 +340,7 @@ sub createGlueProject { my $vcxproj = $build_structure{"APPS_${appname}_VCXPROJ"}; $vcxproj =~ s/\//\\/g; $appname =~ s/.*\///; - print F "\"${appname}\", \"${vcxproj}\", \"${uuid}\""; + print F "\"${appname}.proj\", \"${vcxproj}\", \"${uuid}\""; print F "$SLN_POST"; } foreach (@libs) { @@ -348,7 +350,7 @@ sub createGlueProject { my $vcxproj = $build_structure{"LIBS_${libname}_VCXPROJ"}; $vcxproj =~ s/\//\\/g; $libname =~ s/\//_/g; - print F "\"${libname}\", \"${vcxproj}\", \"${uuid}\""; + print F "\"${libname}.proj\", \"${vcxproj}\", \"${uuid}\""; print F "$SLN_POST"; } From c532ef35c7de1ac6aebfcceb4e7debcd1eae111c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 May 2020 16:19:06 +0200 Subject: [PATCH 063/306] t5505/t5516: allow running without `.git/branches/` in the templates When we commit the template directory as part of `make vcxproj`, the `branches/` directory is not actually commited, as it is empty. Two tests were not prepared for that situation. This developer tried to get rid of the support for `.git/branches/` a long time ago, but that effort did not bear fruit, so the best we can do is work around in these here tests. Signed-off-by: Johannes Schindelin --- t/t5505-remote.sh | 4 ++-- t/t5516-fetch-push.sh | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 519f7973e31287..a64f3fb077564b 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -1145,7 +1145,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' ' ( cd six && git remote rm origin && - mkdir .git/branches && + mkdir -p .git/branches && echo "$origin_url#main" >.git/branches/origin && git remote rename origin origin && test_path_is_missing .git/branches/origin && @@ -1160,7 +1160,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)' ( cd seven && git remote rm origin && - mkdir .git/branches && + mkdir -p .git/branches && echo "quux#foom" > .git/branches/origin && git remote rename origin origin && test_path_is_missing .git/branches/origin && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 041d7d806fea57..ab6beb4b9b333f 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -979,7 +979,7 @@ test_expect_success 'fetch with branches' ' mk_empty testrepo && git branch second $the_first_commit && git checkout second && - mkdir testrepo/.git/branches && + mkdir -p testrepo/.git/branches && echo ".." > testrepo/.git/branches/branch1 && ( cd testrepo && @@ -993,7 +993,7 @@ test_expect_success 'fetch with branches' ' test_expect_success 'fetch with branches containing #' ' mk_empty testrepo && - mkdir testrepo/.git/branches && + mkdir -p testrepo/.git/branches && echo "..#second" > testrepo/.git/branches/branch2 && ( cd testrepo && @@ -1010,7 +1010,7 @@ test_expect_success 'push with branches' ' git checkout second && test_when_finished "rm -rf .git/branches" && - mkdir .git/branches && + mkdir -p .git/branches && echo "testrepo" > .git/branches/branch1 && git push branch1 && @@ -1026,7 +1026,7 @@ test_expect_success 'push with branches containing #' ' mk_empty testrepo && test_when_finished "rm -rf .git/branches" && - mkdir .git/branches && + mkdir -p .git/branches && echo "testrepo#branch3" > .git/branches/branch2 && git push branch2 && From adfc048ec39471c7e11f238ccfed9b43d53ae3be Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 9 May 2020 19:24:23 +0200 Subject: [PATCH 064/306] t5505/t5516: fix white-space around redirectors The convention in Git project's shell scripts is to have white-space _before_, but not _after_ the `>` (or `<`). Signed-off-by: Johannes Schindelin --- t/t5505-remote.sh | 6 +++--- t/t5516-fetch-push.sh | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index a64f3fb077564b..86498c62b29273 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -941,8 +941,8 @@ test_expect_success '"remote show" does not show symbolic refs' ' ( cd three && git remote show origin >output && - ! grep "^ *HEAD$" < output && - ! grep -i stale < output + ! grep "^ *HEAD$" .git/branches/origin && + echo "quux#foom" >.git/branches/origin && git remote rename origin origin && test_path_is_missing .git/branches/origin && test "$(git config remote.origin.url)" = "quux" && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index ab6beb4b9b333f..cc0fad65bfce9f 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -980,7 +980,7 @@ test_expect_success 'fetch with branches' ' git branch second $the_first_commit && git checkout second && mkdir -p testrepo/.git/branches && - echo ".." > testrepo/.git/branches/branch1 && + echo ".." >testrepo/.git/branches/branch1 && ( cd testrepo && git fetch branch1 && @@ -994,7 +994,7 @@ test_expect_success 'fetch with branches' ' test_expect_success 'fetch with branches containing #' ' mk_empty testrepo && mkdir -p testrepo/.git/branches && - echo "..#second" > testrepo/.git/branches/branch2 && + echo "..#second" >testrepo/.git/branches/branch2 && ( cd testrepo && git fetch branch2 && @@ -1011,7 +1011,7 @@ test_expect_success 'push with branches' ' test_when_finished "rm -rf .git/branches" && mkdir -p .git/branches && - echo "testrepo" > .git/branches/branch1 && + echo "testrepo" >.git/branches/branch1 && git push branch1 && ( @@ -1027,7 +1027,7 @@ test_expect_success 'push with branches containing #' ' test_when_finished "rm -rf .git/branches" && mkdir -p .git/branches && - echo "testrepo#branch3" > .git/branches/branch2 && + echo "testrepo#branch3" >.git/branches/branch2 && git push branch2 && ( @@ -1557,7 +1557,7 @@ EOF git init no-thin && git --git-dir=no-thin/.git config receive.unpacklimit 0 && git push no-thin/.git refs/heads/main:refs/heads/foo && - echo modified >> path1 && + echo modified >>path1 && git commit -am modified && git repack -adf && rcvpck="git receive-pack --reject-thin-pack-for-testing" && From ca535032ad4c19fc21a7528008e181c74365a247 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 12 Sep 2015 12:25:47 +0200 Subject: [PATCH 065/306] t3701: verify that we can add *lots* of files interactively Signed-off-by: Johannes Schindelin --- t/t3701-add-interactive.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index b8a05d95f3f130..81d0487b3b90eb 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -1166,6 +1166,27 @@ test_expect_success 'checkout -p patch editing of added file' ' ) ' +test_expect_success EXPENSIVE 'add -i with a lot of files' ' + git reset --hard && + x160=0123456789012345678901234567890123456789 && + x160=$x160$x160$x160$x160 && + y= && + i=0 && + while test $i -le 200 + do + name=$(printf "%s%03d" $x160 $i) && + echo $name >$name && + git add -N $name && + y="${y}y$LF" && + i=$(($i+1)) || + exit 1 + done && + echo "$y" | git add -p -- . && + git diff --cached >staged && + test_line_count = 1407 staged && + git reset --hard +' + test_expect_success 'show help from add--helper' ' git reset --hard && cat >expect <<-EOF && From c3d5ebcdf19e374b618249a34a80aecc1176db1e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 2 Jul 2020 16:35:05 +0200 Subject: [PATCH 066/306] git add -i: handle CR/LF line endings in the interactive input As of Git for Windows v2.27.0, there is an option to use Windows' newly-introduced Pseudo Console support. When running an interactive add operation with this support enabled, Git will receive CR/LF line endings. Therefore, let's not pretend that we are expecting Unix line endings. This fixes https://github.com/git-for-windows/git/issues/2729 Signed-off-by: Johannes Schindelin --- prompt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prompt.c b/prompt.c index f21c5bf1c7e42f..a07b135050b9c2 100644 --- a/prompt.c +++ b/prompt.c @@ -80,7 +80,7 @@ int git_read_line_interactively(struct strbuf *line) int ret; fflush(stdout); - ret = strbuf_getline_lf(line, stdin); + ret = strbuf_getline(line, stdin); if (ret != EOF) strbuf_trim_trailing_newline(line); From 201c0e830fcc208257a81f7cc52d0dc7d379d45a Mon Sep 17 00:00:00 2001 From: Luke Bonanomi Date: Wed, 24 Jun 2020 07:45:52 -0400 Subject: [PATCH 067/306] commit: accept "scissors" with CR/LF line endings This change enhances `git commit --cleanup=scissors` by detecting scissors lines ending in either LF (UNIX-style) or CR/LF (DOS-style). Regression tests are included to specifically test for trailing comments after a CR/LF-terminated scissors line. Signed-off-by: Luke Bonanomi Signed-off-by: Johannes Schindelin --- t/t7502-commit-porcelain.sh | 42 +++++++++++++++++++++++++++++++++++++ wt-status.c | 13 +++++++++--- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh index b37e2018a74a7b..c38b96b66cd20a 100755 --- a/t/t7502-commit-porcelain.sh +++ b/t/t7502-commit-porcelain.sh @@ -623,6 +623,48 @@ test_expect_success 'cleanup commit messages (scissors option,-F,-e, scissors on test_must_be_empty actual ' +test_expect_success 'helper-editor' ' + + write_script lf-to-crlf.sh <<-\EOF + sed "s/\$/Q/" <"$1" | tr Q "\\015" >"$1".new && + mv -f "$1".new "$1" + EOF +' + +test_expect_success 'cleanup commit messages (scissors option,-F,-e, CR/LF line endings)' ' + + test_config core.editor "\"$PWD/lf-to-crlf.sh\"" && + scissors="# ------------------------ >8 ------------------------" && + + test_write_lines >text \ + "# Keep this comment" "" " $scissors" \ + "# Keep this comment, too" "$scissors" \ + "# Remove this comment" "$scissors" \ + "Remove this comment, too" && + + test_write_lines >expect \ + "# Keep this comment" "" " $scissors" \ + "# Keep this comment, too" && + + git commit --cleanup=scissors -e -F text --allow-empty && + git cat-file -p HEAD >raw && + sed -e "1,/^\$/d" raw >actual && + test_cmp expect actual +' + +test_expect_success 'cleanup commit messages (scissors option,-F,-e, scissors on first line, CR/LF line endings)' ' + + scissors="# ------------------------ >8 ------------------------" && + test_write_lines >text \ + "$scissors" \ + "# Remove this comment and any following lines" && + cp text /tmp/test2-text && + git commit --cleanup=scissors -e -F text --allow-empty --allow-empty-message && + git cat-file -p HEAD >raw && + sed -e "1,/^\$/d" raw >actual && + test_must_be_empty actual +' + test_expect_success 'cleanup commit messages (strip option,-F)' ' echo >>negative && diff --git a/wt-status.c b/wt-status.c index 3ee918176422c7..9609ba260fe804 100644 --- a/wt-status.c +++ b/wt-status.c @@ -40,7 +40,7 @@ #define UF_DELAY_WARNING_IN_MS (2 * 1000) static const char cut_line[] = -"------------------------ >8 ------------------------\n"; +"------------------------ >8 ------------------------"; static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ @@ -1096,15 +1096,22 @@ static void wt_longstatus_print_other(struct wt_status *s, status_printf_ln(s, GIT_COLOR_NORMAL, "%s", ""); } +static inline int starts_with_newline(const char *p) +{ + return *p == '\n' || (*p == '\r' && p[1] == '\n'); +} + size_t wt_status_locate_end(const char *s, size_t len) { const char *p; struct strbuf pattern = STRBUF_INIT; strbuf_addf(&pattern, "\n%s %s", comment_line_str, cut_line); - if (starts_with(s, pattern.buf + 1)) + if (starts_with(s, pattern.buf + 1) && + starts_with_newline(s + pattern.len - 1)) len = 0; - else if ((p = strstr(s, pattern.buf))) { + else if ((p = strstr(s, pattern.buf)) && + starts_with_newline(p + pattern.len)) { size_t newlen = p - s + 1; if (newlen < len) len = newlen; From ae133a0ea9e3180f443995e5685165662c0be93e Mon Sep 17 00:00:00 2001 From: Jens Glathe Date: Tue, 2 Jun 2020 12:12:25 +0200 Subject: [PATCH 068/306] t0014: fix indentation For some reason, this test case was indented with 4 spaces instead of 1 horizontal tab. The other test cases in the same test script are fine. Signed-off-by: Jens Glathe Signed-off-by: Johannes Schindelin --- t/t0014-alias.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 854d59ec58c25a..30708146887d19 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh @@ -38,10 +38,10 @@ test_expect_success 'looping aliases - internal execution' ' #' test_expect_success 'run-command formats empty args properly' ' - test_must_fail env GIT_TRACE=1 git frotz a "" b " " c 2>actual.raw && - sed -ne "/run_command:/s/.*trace: run_command: //p" actual.raw >actual && - echo "git-frotz a '\'''\'' b '\'' '\'' c" >expect && - test_cmp expect actual + test_must_fail env GIT_TRACE=1 git frotz a "" b " " c 2>actual.raw && + sed -ne "/run_command:/s/.*trace: run_command: //p" actual.raw >actual && + echo "git-frotz a '\'''\'' b '\'' '\'' c" >expect && + test_cmp expect actual ' test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' ' From ac73ea47ec6ae428bdcd945bcc97766e5b6ce9f7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 12 Aug 2020 15:06:17 +0000 Subject: [PATCH 069/306] git-gui: accommodate for intent-to-add files As of Git v2.28.0, the diff for files staged via `git add -N` marks them as new files. Git GUI was ill-prepared for that, and this patch teaches Git GUI about them. Please note that this will not even fix things with v2.28.0, as the `rp/apply-cached-with-i-t-a` patches are required on Git's side, too. This fixes https://github.com/git-for-windows/git/issues/2779 Signed-off-by: Johannes Schindelin Signed-off-by: Pratyush Yadav --- git-gui/git-gui.sh | 2 ++ git-gui/lib/diff.tcl | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 887d6d596c16fc..3390dc15ccaffe 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -2078,6 +2078,7 @@ set all_icons(U$ui_index) file_merge set all_icons(T$ui_index) file_statechange set all_icons(_$ui_workdir) file_plain +set all_icons(A$ui_workdir) file_plain set all_icons(M$ui_workdir) file_mod set all_icons(D$ui_workdir) file_question set all_icons(U$ui_workdir) file_merge @@ -2104,6 +2105,7 @@ foreach i { {A_ {mc "Staged for commit"}} {AM {mc "Portions staged for commit"}} {AD {mc "Staged for commit, missing"}} + {AA {mc "Intended to be added"}} {_D {mc "Missing"}} {D_ {mc "Staged for removal"}} diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index d657bfec05b498..9a593a3f6e1547 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -568,7 +568,8 @@ proc apply_or_revert_hunk {x y revert} { if {$current_diff_side eq $ui_index} { set failed_msg [mc "Failed to unstage selected hunk."] lappend apply_cmd --reverse --cached - if {[string index $mi 0] ne {M}} { + set file_state [string index $mi 0] + if {$file_state ne {M} && $file_state ne {A}} { unlock_index return } @@ -581,7 +582,8 @@ proc apply_or_revert_hunk {x y revert} { lappend apply_cmd --cached } - if {[string index $mi 1] ne {M}} { + set file_state [string index $mi 1] + if {$file_state ne {M} && $file_state ne {A}} { unlock_index return } @@ -673,7 +675,8 @@ proc apply_or_revert_range_or_line {x y revert} { set failed_msg [mc "Failed to unstage selected line."] set to_context {+} lappend apply_cmd --reverse --cached - if {[string index $mi 0] ne {M}} { + set file_state [string index $mi 0] + if {$file_state ne {M} && $file_state ne {A}} { unlock_index return } @@ -688,7 +691,8 @@ proc apply_or_revert_range_or_line {x y revert} { lappend apply_cmd --cached } - if {[string index $mi 1] ne {M}} { + set file_state [string index $mi 1] + if {$file_state ne {M} && $file_state ne {A}} { unlock_index return } From 77283d028d3776fb25331c2f3c86e8151d0af503 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Tue, 30 Mar 2021 14:25:31 -0400 Subject: [PATCH 070/306] clink.pl: fix libexpatd.lib link error when using MSVC When building with `make MSVC=1 DEBUG=1`, link to `libexpatd.lib` rather than `libexpat.lib`. It appears that the `vcpkg` package for "libexpat" has changed and now creates `libexpatd.lib` for debug mode builds. Previously, both debug and release builds created a ".lib" with the same basename. Signed-off-by: Jeff Hostetler --- compat/vcbuild/scripts/clink.pl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index 3bd824154be381..2768ae15f1879f 100755 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -66,7 +66,11 @@ } push(@args, $lib); } elsif ("$arg" eq "-lexpat") { + if ($is_debug) { + push(@args, "libexpatd.lib"); + } else { push(@args, "libexpat.lib"); + } } elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") { $arg =~ s/^-L/-LIBPATH:/; push(@lflags, $arg); From 4bff95d54cf6dafe50a2625fd7b3dc14efe314f7 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Mon, 5 Apr 2021 15:27:38 -0400 Subject: [PATCH 071/306] Makefile: clean up .ilk files when MSVC=1 Signed-off-by: Jeff Hostetler --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 97e8385b6643b9..38da50f916aa47 100644 --- a/Makefile +++ b/Makefile @@ -3740,12 +3740,15 @@ ifdef MSVC $(RM) $(patsubst %.o,%.o.pdb,$(OBJECTS)) $(RM) headless-git.o.pdb $(RM) $(patsubst %.exe,%.pdb,$(OTHER_PROGRAMS)) + $(RM) $(patsubst %.exe,%.ilk,$(OTHER_PROGRAMS)) $(RM) $(patsubst %.exe,%.iobj,$(OTHER_PROGRAMS)) $(RM) $(patsubst %.exe,%.ipdb,$(OTHER_PROGRAMS)) $(RM) $(patsubst %.exe,%.pdb,$(PROGRAMS)) + $(RM) $(patsubst %.exe,%.ilk,$(PROGRAMS)) $(RM) $(patsubst %.exe,%.iobj,$(PROGRAMS)) $(RM) $(patsubst %.exe,%.ipdb,$(PROGRAMS)) $(RM) $(patsubst %.exe,%.pdb,$(TEST_PROGRAMS)) + $(RM) $(patsubst %.exe,%.ilk,$(TEST_PROGRAMS)) $(RM) $(patsubst %.exe,%.iobj,$(TEST_PROGRAMS)) $(RM) $(patsubst %.exe,%.ipdb,$(TEST_PROGRAMS)) $(RM) compat/vcbuild/MSVC-DEFS-GEN From db01c407b8794e6f5c0404194d552bbab6fa1735 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Mon, 5 Apr 2021 14:08:22 -0400 Subject: [PATCH 072/306] vcbuild: add support for compiling Windows resource files Create a wrapper for the Windows Resource Compiler (RC.EXE) for use by the MSVC=1 builds. This is similar to the CL.EXE and LIB.EXE wrappers used for the MSVC=1 builds. Signed-off-by: Jeff Hostetler --- compat/vcbuild/find_vs_env.bat | 7 ++++++ compat/vcbuild/scripts/rc.pl | 46 ++++++++++++++++++++++++++++++++++ config.mak.uname | 3 ++- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 compat/vcbuild/scripts/rc.pl diff --git a/compat/vcbuild/find_vs_env.bat b/compat/vcbuild/find_vs_env.bat index b35d264c0e6bed..379b16296e09c2 100644 --- a/compat/vcbuild/find_vs_env.bat +++ b/compat/vcbuild/find_vs_env.bat @@ -99,6 +99,7 @@ REM ================================================================ SET sdk_dir=%WindowsSdkDir% SET sdk_ver=%WindowsSDKVersion% + SET sdk_ver_bin_dir=%WindowsSdkVerBinPath%%tgt% SET si=%sdk_dir%Include\%sdk_ver% SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared" SET sl=%sdk_dir%lib\%sdk_ver% @@ -130,6 +131,7 @@ REM ================================================================ SET sdk_dir=%WindowsSdkDir% SET sdk_ver=%WindowsSDKVersion% + SET sdk_ver_bin_dir=%WindowsSdkVerBinPath%bin\amd64 SET si=%sdk_dir%Include\%sdk_ver% SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared" -I"%si%winrt" SET sl=%sdk_dir%lib\%sdk_ver% @@ -160,6 +162,11 @@ REM ================================================================ echo msvc_includes=%msvc_includes% echo msvc_libs=%msvc_libs% + echo sdk_ver_bin_dir=%sdk_ver_bin_dir% + SET X1=%sdk_ver_bin_dir:C:=/C% + SET X2=%X1:\=/% + echo sdk_ver_bin_dir_msys=%X2% + echo sdk_includes=%sdk_includes% echo sdk_libs=%sdk_libs% diff --git a/compat/vcbuild/scripts/rc.pl b/compat/vcbuild/scripts/rc.pl new file mode 100644 index 00000000000000..7bca4cd81c6c63 --- /dev/null +++ b/compat/vcbuild/scripts/rc.pl @@ -0,0 +1,46 @@ +#!/usr/bin/perl -w +###################################################################### +# Compile Resources on Windows +# +# This is a wrapper to facilitate the compilation of Git with MSVC +# using GNU Make as the build system. So, instead of manipulating the +# Makefile into something nasty, just to support non-space arguments +# etc, we use this wrapper to fix the command line options +# +###################################################################### +use strict; +my @args = (); +my @input = (); + +while (@ARGV) { + my $arg = shift @ARGV; + if ("$arg" =~ /^-[dD]/) { + # GIT_VERSION gets passed with too many + # layers of dquote escaping. + $arg =~ s/\\"/"/g; + + push(@args, $arg); + + } elsif ("$arg" eq "-i") { + my $arg = shift @ARGV; + # TODO complain if NULL or is dashed ?? + push(@input, $arg); + + } elsif ("$arg" eq "-o") { + my $arg = shift @ARGV; + # TODO complain if NULL or is dashed ?? + push(@args, "-fo$arg"); + + } else { + push(@args, $arg); + } +} + +push(@args, "-nologo"); +push(@args, "-v"); +push(@args, @input); + +unshift(@args, "rc.exe"); +printf("**** @args\n"); + +exit (system(@args) != 0); diff --git a/config.mak.uname b/config.mak.uname index a57432ba5ad6b6..73f6219c5d8ef7 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -440,7 +440,7 @@ ifeq ($(uname_S),Windows) # link.exe next to, and required by, cl.exe, we have to prepend this # onto the existing $PATH. # - SANE_TOOL_PATH ?= $(msvc_bin_dir_msys) + SANE_TOOL_PATH ?= $(msvc_bin_dir_msys):$(sdk_ver_bin_dir_msys) HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease @@ -508,6 +508,7 @@ endif # See https://msdn.microsoft.com/en-us/library/ms235330.aspx EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj kernel32.lib ntdll.lib PTHREAD_LIBS = + RC = compat/vcbuild/scripts/rc.pl lib = BASIC_CFLAGS += $(vcpkg_inc) $(sdk_includes) $(msvc_includes) ifndef DEBUG From 8f5ad65c777a624f821bbeef51326f87a0798e83 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Mon, 5 Apr 2021 14:12:14 -0400 Subject: [PATCH 073/306] config.mak.uname: add git.rc to MSVC builds Teach MSVC=1 builds to depend on the `git.rc` file so that the resulting executables have Windows-style resources and version number information within them. Signed-off-by: Jeff Hostetler --- config.mak.uname | 1 + 1 file changed, 1 insertion(+) diff --git a/config.mak.uname b/config.mak.uname index 73f6219c5d8ef7..f27cbfe9d0f796 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -507,6 +507,7 @@ endif # handle twice, or to access the osfhandle of an already-closed stdout # See https://msdn.microsoft.com/en-us/library/ms235330.aspx EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj kernel32.lib ntdll.lib + GITLIBS += git.res PTHREAD_LIBS = RC = compat/vcbuild/scripts/rc.pl lib = From 3e47c796ebbcf61ac94424571a331d07c8a73b73 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Mon, 5 Apr 2021 14:24:52 -0400 Subject: [PATCH 074/306] clink.pl: ignore no-stack-protector arg on MSVC=1 builds Ignore the `-fno-stack-protector` compiler argument when building with MSVC. This will be used in a later commit that needs to build a Win32 GUI app. Signed-off-by: Jeff Hostetler --- compat/vcbuild/scripts/clink.pl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index 2768ae15f1879f..73c8a2b184f38b 100755 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -122,6 +122,8 @@ push(@cflags, "-wd4996"); } elsif ("$arg" =~ /^-W[a-z]/) { # let's ignore those + } elsif ("$arg" eq "-fno-stack-protector") { + # eat this } else { push(@args, $arg); } From b59e4b93533cc9d81c781c7f9d022155ff2f17f0 Mon Sep 17 00:00:00 2001 From: Jeff Hostetler Date: Mon, 5 Apr 2021 14:39:33 -0400 Subject: [PATCH 075/306] clink.pl: move default linker options for MSVC=1 builds Move the default `-ENTRY` and `-SUBSYSTEM` arguments for MSVC=1 builds from `config.mak.uname` into `clink.pl`. These args are constant for console-mode executables. Add support to `clink.pl` for generating a Win32 GUI application using the `-mwindows` argument (to match how GCC does it). This changes the `-ENTRY` and `-SUBSYSTEM` arguments accordingly. Signed-off-by: Jeff Hostetler --- compat/vcbuild/scripts/clink.pl | 11 +++++++++++ config.mak.uname | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl index 73c8a2b184f38b..a38b360015ece9 100755 --- a/compat/vcbuild/scripts/clink.pl +++ b/compat/vcbuild/scripts/clink.pl @@ -15,6 +15,7 @@ my @lflags = (); my $is_linking = 0; my $is_debug = 0; +my $is_gui = 0; while (@ARGV) { my $arg = shift @ARGV; if ("$arg" eq "-DDEBUG") { @@ -124,11 +125,21 @@ # let's ignore those } elsif ("$arg" eq "-fno-stack-protector") { # eat this + } elsif ("$arg" eq "-mwindows") { + $is_gui = 1; } else { push(@args, $arg); } } if ($is_linking) { + if ($is_gui) { + push(@args, "-ENTRY:wWinMainCRTStartup"); + push(@args, "-SUBSYSTEM:WINDOWS"); + } else { + push(@args, "-ENTRY:wmainCRTStartup"); + push(@args, "-SUBSYSTEM:CONSOLE"); + } + push(@args, @lflags); unshift(@args, "link.exe"); } else { diff --git a/config.mak.uname b/config.mak.uname index f27cbfe9d0f796..9f4671734aec90 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -502,7 +502,7 @@ endif compat/win32/trace2_win32_process_info.o \ compat/win32/dirent.o COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DENSURE_MSYSTEM_IS_SET -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" - BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -ENTRY:wmainCRTStartup -SUBSYSTEM:CONSOLE + BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO # invalidcontinue.obj allows Git's source code to close the same file # handle twice, or to access the osfhandle of an already-closed stdout # See https://msdn.microsoft.com/en-us/library/ms235330.aspx From 49cf4b67ed15ef490b9cf04c05a86de5e547e6bd Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 7 Apr 2021 15:29:21 +0200 Subject: [PATCH 076/306] buildsystems: remove duplicate clause This seems to have been there since 259d87c35495 (Add scripts to generate projects for other buildsystems (MSVC vcproj, QMake), 2009-09-16), i.e. since the beginning of that file. Signed-off-by: Johannes Schindelin --- contrib/buildsystems/engine.pl | 1 - 1 file changed, 1 deletion(-) diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 069be7e4befcd7..4243784e8a6b6e 100755 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -265,7 +265,6 @@ sub handleCompileLine shift @parts; } elsif ("$part" eq "-c") { # ignore compile flag - } elsif ("$part" eq "-c") { } elsif ($part =~ /^.?-I/) { push(@incpaths, $part); } elsif ($part =~ /^.?-D/) { From adb78670a8b94da6c8d9a4df3423a07f67eb413b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 7 Apr 2021 15:15:08 +0200 Subject: [PATCH 077/306] vcxproj: handle resource files, too On Windows, we also compile a "resource" file, which is similar to source code, but contains metadata (such as the program version). So far, we did not compile it in `MSVC` mode, only when compiling Git for Windows with the GNU C Compiler. In preparation for including it also when compiling with MS Visual C, let's teach our `vcxproj` generator to handle those sort of files, too. Signed-off-by: Johannes Schindelin --- contrib/buildsystems/Generators/Vcxproj.pm | 17 ++++++++++++++++- contrib/buildsystems/engine.pl | 9 +++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index 0439b82f55e243..5e97f2ff99d344 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -89,6 +89,9 @@ sub createProject { $defines =~ s/>/>/g; $defines =~ s/\'//g; + my $rcdefines = $defines; + $rcdefines =~ s/(?WIN32;_DEBUG;$defines;%(PreprocessorDefinitions) MultiThreadedDebugDLL + + WIN32;_DEBUG;$rcdefines;%(PreprocessorDefinitions) + true @@ -216,6 +222,9 @@ EOM true Speed + + WIN32;NDEBUG;$rcdefines;%(PreprocessorDefinitions) + true true @@ -225,9 +234,15 @@ EOM EOM foreach(@sources) { - print F << "EOM"; + if (/\.rc$/) { + print F << "EOM"; + +EOM + } else { + print F << "EOM"; EOM + } } print F << "EOM"; diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 4243784e8a6b6e..82849812212efc 100755 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -165,7 +165,7 @@ sub parseMakeOutput next; } - if($text =~ / -c /) { + if($text =~ / -c / || $text =~ / -i \S+\.rc /) { # compilation handleCompileLine($text, $line); @@ -263,7 +263,7 @@ sub handleCompileLine if ("$part" eq "-o") { # ignore object file shift @parts; - } elsif ("$part" eq "-c") { + } elsif ("$part" eq "-c" || "$part" eq "-i") { # ignore compile flag } elsif ($part =~ /^.?-I/) { push(@incpaths, $part); @@ -271,7 +271,7 @@ sub handleCompileLine push(@defines, $part); } elsif ($part =~ /^-/) { push(@cflags, $part); - } elsif ($part =~ /\.(c|cc|cpp)$/) { + } elsif ($part =~ /\.(c|cc|cpp|rc)$/) { $sourcefile = $part; } else { die "Unhandled compiler option @ line $lineno: $part"; @@ -358,7 +358,7 @@ sub handleLinkLine push(@libs, $part); } elsif ($part eq 'invalidcontinue.obj') { # ignore - known to MSVC - } elsif ($part =~ /\.o$/) { + } elsif ($part =~ /\.(o|res)$/) { push(@objfiles, $part); } elsif ($part =~ /\.obj$/) { # do nothing, 'make' should not be producing .obj, only .o files @@ -372,6 +372,7 @@ sub handleLinkLine my $sourcefile = $_; $sourcefile =~ s/^headless-git\.o$/compat\/win32\/headless.c/; $sourcefile =~ s/\.o$/.c/; + $sourcefile =~ s/\.res$/.rc/; push(@sources, $sourcefile); push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}}); push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}}); From 79bb88cfac6555f9e558a2f8433a732d5e9edc0c Mon Sep 17 00:00:00 2001 From: Ian Bearman Date: Fri, 31 Jan 2020 15:37:27 -0800 Subject: [PATCH 078/306] vcxproj: support building Windows/ARM64 binaries Signed-off-by: Ian Bearman Signed-off-by: Dennis Ameling Signed-off-by: Johannes Schindelin --- contrib/buildsystems/Generators/Vcxproj.pm | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index b2e68a16715e39..f9db773fdbb6cb 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -114,12 +114,21 @@ sub createProject { Release x64 + + Debug + ARM64 + + + Release + ARM64 + $uuid Win32Proj x86-windows - x64-windows + x64-windows + arm64-windows $cdup\\compat\\vcbuild\\vcpkg\\installed\\\$(VCPKGArch) \$(VCPKGArchDirectory)\\debug\\bin \$(VCPKGArchDirectory)\\debug\\lib @@ -140,7 +149,7 @@ sub createProject { $config_type - v140 + v142 ..\\ @@ -355,8 +364,10 @@ sub createGlueProject { print F << "EOM"; Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 + Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection @@ -367,10 +378,14 @@ EOM foreach (@apps) { my $appname = $_; my $uuid = $build_structure{"APPS_${appname}_GUID"}; + print F "\t\t${uuid}.Debug|ARM64.ActiveCfg = Debug|ARM64\n"; + print F "\t\t${uuid}.Debug|ARM64.Build.0 = Debug|ARM64\n"; print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n"; print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n"; print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n"; print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n"; + print F "\t\t${uuid}.Release|ARM64.ActiveCfg = Release|ARM64\n"; + print F "\t\t${uuid}.Release|ARM64.Build.0 = Release|ARM64\n"; print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n"; print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n"; print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n"; @@ -379,10 +394,14 @@ EOM foreach (@libs) { my $libname = $_; my $uuid = $build_structure{"LIBS_${libname}_GUID"}; + print F "\t\t${uuid}.Debug|ARM64.ActiveCfg = Debug|ARM64\n"; + print F "\t\t${uuid}.Debug|ARM64.Build.0 = Debug|ARM64\n"; print F "\t\t${uuid}.Debug|x64.ActiveCfg = Debug|x64\n"; print F "\t\t${uuid}.Debug|x64.Build.0 = Debug|x64\n"; print F "\t\t${uuid}.Debug|x86.ActiveCfg = Debug|Win32\n"; print F "\t\t${uuid}.Debug|x86.Build.0 = Debug|Win32\n"; + print F "\t\t${uuid}.Release|ARM64.ActiveCfg = Release|ARM64\n"; + print F "\t\t${uuid}.Release|ARM64.Build.0 = Release|ARM64\n"; print F "\t\t${uuid}.Release|x64.ActiveCfg = Release|x64\n"; print F "\t\t${uuid}.Release|x64.Build.0 = Release|x64\n"; print F "\t\t${uuid}.Release|x86.ActiveCfg = Release|Win32\n"; From 69d15a382760dd17f8cb3671cfe43fb190aa9782 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 7 Apr 2021 21:57:31 +0200 Subject: [PATCH 079/306] vcxproj: ignore -fno-stack-protector and -fno-common An upcoming commit will introduce those compile options; MSVC does not understand them, so let's suppress them when generating the Visual Studio project files. Signed-off-by: Johannes Schindelin --- contrib/buildsystems/engine.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 82849812212efc..417ae71d44ccab 100755 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -263,7 +263,7 @@ sub handleCompileLine if ("$part" eq "-o") { # ignore object file shift @parts; - } elsif ("$part" eq "-c" || "$part" eq "-i") { + } elsif ("$part" eq "-c" || "$part" eq "-i" || "$part" =~ /^-fno-/) { # ignore compile flag } elsif ($part =~ /^.?-I/) { push(@incpaths, $part); From 81ee80b54f7a0685bde7e9a30678d759eb6f3fe9 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Sun, 6 Oct 2019 18:40:55 +0100 Subject: [PATCH 080/306] vcpkg_install: detect lack of Git The vcpkg_install batch file depends on the availability of a working Git on the CMD path. This may not be present if the user has selected the 'bash only' option during Git-for-Windows install. Detect and tell the user about their lack of a working Git in the CMD window. Fixes #2348. A separate PR https://github.com/git-for-windows/build-extra/pull/258 now highlights the recommended path setting during install. Signed-off-by: Philip Oakley --- compat/vcbuild/vcpkg_install.bat | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat index ebd0bad242a8ca..bcbbf536af3141 100644 --- a/compat/vcbuild/vcpkg_install.bat +++ b/compat/vcbuild/vcpkg_install.bat @@ -36,6 +36,13 @@ REM ================================================================ dir vcpkg\vcpkg.exe >nul 2>nul && GOTO :install_libraries + git.exe version 2>nul + IF ERRORLEVEL 1 ( + echo "***" + echo "Git not found. Please adjust your CMD path or Git install option." + echo "***" + EXIT /B 1 ) + echo Fetching vcpkg in %cwd%vcpkg git.exe clone https://github.com/Microsoft/vcpkg vcpkg IF ERRORLEVEL 1 ( EXIT /B 1 ) From ff822c640f3d76781df14da4a39378ececaf9d19 Mon Sep 17 00:00:00 2001 From: Ian Bearman Date: Fri, 31 Jan 2020 16:00:25 -0800 Subject: [PATCH 081/306] vcbuild: install ARM64 dependencies when building ARM64 binaries Co-authored-by: Dennis Ameling Signed-off-by: Ian Bearman Signed-off-by: Dennis Ameling Signed-off-by: Johannes Schindelin --- compat/vcbuild/README | 6 +++++- compat/vcbuild/vcpkg_copy_dlls.bat | 7 ++++++- compat/vcbuild/vcpkg_install.bat | 9 +++++++-- contrib/buildsystems/Generators/Vcxproj.pm | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/compat/vcbuild/README b/compat/vcbuild/README index 29ec1d0f104b80..1df1cabb1ebbbd 100644 --- a/compat/vcbuild/README +++ b/compat/vcbuild/README @@ -6,7 +6,11 @@ The Steps to Build Git with VS2015 or VS2017 from the command line. Prompt or from an SDK bash window: $ cd - $ ./compat/vcbuild/vcpkg_install.bat + $ ./compat/vcbuild/vcpkg_install.bat x64-windows + + or + + $ ./compat/vcbuild/vcpkg_install.bat arm64-windows The vcpkg tools and all of the third-party sources will be installed in this folder: diff --git a/compat/vcbuild/vcpkg_copy_dlls.bat b/compat/vcbuild/vcpkg_copy_dlls.bat index 13661c14f8705c..8bea0cbf83b6cf 100644 --- a/compat/vcbuild/vcpkg_copy_dlls.bat +++ b/compat/vcbuild/vcpkg_copy_dlls.bat @@ -15,7 +15,12 @@ REM ================================================================ @FOR /F "delims=" %%D IN ("%~dp0") DO @SET cwd=%%~fD cd %cwd% - SET arch=x64-windows + SET arch=%2 + IF NOT DEFINED arch ( + echo defaulting to 'x64-windows`. Invoke %0 with 'x86-windows', 'x64-windows', or 'arm64-windows' + set arch=x64-windows + ) + SET inst=%cwd%vcpkg\installed\%arch% IF [%1]==[release] ( diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat index 8330d8120fb511..cacef18c11dc79 100644 --- a/compat/vcbuild/vcpkg_install.bat +++ b/compat/vcbuild/vcpkg_install.bat @@ -31,6 +31,12 @@ REM ================================================================ SETLOCAL EnableDelayedExpansion + SET arch=%1 + IF NOT DEFINED arch ( + echo defaulting to 'x64-windows`. Invoke %0 with 'x86-windows', 'x64-windows', or 'arm64-windows' + set arch=x64-windows + ) + @FOR /F "delims=" %%D IN ("%~dp0") DO @SET cwd=%%~fD cd %cwd% @@ -55,9 +61,8 @@ REM ================================================================ echo Successfully installed %cwd%vcpkg\vcpkg.exe :install_libraries - SET arch=x64-windows - echo Installing third-party libraries... + echo Installing third-party libraries(%arch%)... FOR %%i IN (zlib expat libiconv openssl libssh2 curl) DO ( cd %cwd%vcpkg IF NOT EXIST "packages\%%i_%arch%" CALL :sub__install_one %%i diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index f9db773fdbb6cb..dc32493f91bf7f 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -193,7 +193,7 @@ EOM Initialize VCPKG del "$cdup\\compat\\vcbuild\\vcpkg" - call "$cdup\\compat\\vcbuild\\vcpkg_install.bat" + call "$cdup\\compat\\vcbuild\\vcpkg_install.bat" \$(VCPKGArch) EOM } From f1b2fd699f221f00a51a66f412a9dfb5de414054 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 7 Apr 2021 15:48:50 +0200 Subject: [PATCH 082/306] vcxproj: handle GUI programs, too So far, we only built Console programs, but we are about to introduce a program that targets the Windows subsystem (i.e. it is a so-called "GUI" program). Let's handle this preemptively in the script that generates the Visual Studio files. Signed-off-by: Johannes Schindelin --- contrib/buildsystems/Generators/Vcxproj.pm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index 5e97f2ff99d344..a6d1c6b8d05682 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -92,6 +92,13 @@ sub createProject { my $rcdefines = $defines; $rcdefines =~ s/(?\$(VCPKGLibDirectory);%(AdditionalLibraryDirectories) \$(VCPKGLibs);\$(AdditionalDependencies) invalidcontinue.obj %(AdditionalOptions) - wmainCRTStartup + $entrypoint $cdup\\compat\\win32\\git.manifest - Console + $subsystem EOM if ($target eq 'libgit') { From f45352ca45c7563700c2e246c47ecd53c449c049 Mon Sep 17 00:00:00 2001 From: Yuyi Wang Date: Sat, 11 Mar 2023 17:51:18 +0800 Subject: [PATCH 083/306] cmake: install headless-git. headless-git is a git executable without opening a console window. It is useful when other GUI executables want to call git. We should install it together with git on Windows. Signed-off-by: Yuyi Wang --- contrib/buildsystems/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 10dc54fdcb647b..4655158ae20149 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -745,6 +745,7 @@ if(WIN32) endif() add_executable(headless-git ${CMAKE_SOURCE_DIR}/compat/win32/headless.c) + list(APPEND PROGRAMS_BUILT headless-git) if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang") target_link_options(headless-git PUBLIC -municode -Wl,-subsystem,windows) elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC") @@ -945,7 +946,7 @@ list(TRANSFORM git_perl_scripts PREPEND "${CMAKE_BINARY_DIR}/") #install foreach(program ${PROGRAMS_BUILT}) -if(program MATCHES "^(git|git-shell|scalar)$") +if(program MATCHES "^(git|git-shell|headless-git|scalar)$") install(TARGETS ${program} RUNTIME DESTINATION bin) else() From e3729ad92552a69763d03251184f87b3e3157192 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Sun, 6 Oct 2019 18:43:57 +0100 Subject: [PATCH 084/306] vcpkg_install: add comment regarding slow network connections The vcpkg downloads may not succeed. Warn careful readers of the time out. A simple retry will usually resolve the issue. Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- compat/vcbuild/vcpkg_install.bat | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat index bcbbf536af3141..8330d8120fb511 100644 --- a/compat/vcbuild/vcpkg_install.bat +++ b/compat/vcbuild/vcpkg_install.bat @@ -80,6 +80,12 @@ REM ================================================================ :sub__install_one echo Installing package %1... + REM vcpkg may not be reliable on slow, intermittent or proxy + REM connections, see e.g. + REM https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/4a8f7be5-5e15-4213-a7bb-ddf424a954e6/winhttpsendrequest-ends-with-12002-errorhttptimeout-after-21-seconds-no-matter-what-timeout?forum=windowssdk + REM which explains the hidden 21 second timeout + REM (last post by Dave : Microsoft - Windows Networking team) + .\vcpkg.exe install %1:%arch% IF ERRORLEVEL 1 ( EXIT /B 1 ) From 6e695685291cda2e9b30d73f99bae68f3295b638 Mon Sep 17 00:00:00 2001 From: Ian Bearman Date: Tue, 4 Feb 2020 10:34:40 -0800 Subject: [PATCH 085/306] vcbuild: add an option to install individual 'features' In this context, a "feature" is a dependency combined with its own dependencies. Signed-off-by: Ian Bearman Signed-off-by: Johannes Schindelin --- compat/vcbuild/vcpkg_install.bat | 35 +++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat index cacef18c11dc79..8da212487ae97d 100644 --- a/compat/vcbuild/vcpkg_install.bat +++ b/compat/vcbuild/vcpkg_install.bat @@ -85,14 +85,47 @@ REM ================================================================ :sub__install_one echo Installing package %1... + call :%1_features + REM vcpkg may not be reliable on slow, intermittent or proxy REM connections, see e.g. REM https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/4a8f7be5-5e15-4213-a7bb-ddf424a954e6/winhttpsendrequest-ends-with-12002-errorhttptimeout-after-21-seconds-no-matter-what-timeout?forum=windowssdk REM which explains the hidden 21 second timeout REM (last post by Dave : Microsoft - Windows Networking team) - .\vcpkg.exe install %1:%arch% + .\vcpkg.exe install %1%features%:%arch% IF ERRORLEVEL 1 ( EXIT /B 1 ) echo Finished %1 goto :EOF + +:: +:: features for each vcpkg to install +:: there should be an entry here for each package to install +:: 'set features=' means use the default otherwise +:: 'set features=[comma-delimited-feature-set]' is the syntax +:: + +:zlib_features +set features= +goto :EOF + +:expat_features +set features= +goto :EOF + +:libiconv_features +set features= +goto :EOF + +:openssl_features +set features= +goto :EOF + +:libssh2_features +set features= +goto :EOF + +:curl_features +set features=[core,openssl] +goto :EOF From b3ab8b78d1bb8e3e7f8bf4fd05e5dac7ae0de3e9 Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Fri, 4 Dec 2020 14:11:34 +0100 Subject: [PATCH 086/306] cmake: allow building for Windows/ARM64 Signed-off-by: Dennis Ameling Signed-off-by: Johannes Schindelin --- contrib/buildsystems/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 10dc54fdcb647b..fb70820f959803 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -65,9 +65,9 @@ if(USE_VCPKG) set(VCPKG_DIR "${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg") if(NOT EXISTS ${VCPKG_DIR}) message("Initializing vcpkg and building the Git's dependencies (this will take a while...)") - execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat) + execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat ${VCPKG_ARCH}) endif() - list(APPEND CMAKE_PREFIX_PATH "${VCPKG_DIR}/installed/x64-windows") + list(APPEND CMAKE_PREFIX_PATH "${VCPKG_DIR}/installed/${VCPKG_ARCH}") # In the vcpkg edition, we need this to be able to link to libcurl set(CURL_NO_CURL_CMAKE ON) @@ -1207,7 +1207,7 @@ string(REPLACE "@USE_GETTEXT_SCHEME@" "" git_build_options "${git_build_options} string(REPLACE "@USE_LIBPCRE2@" "" git_build_options "${git_build_options}") string(REPLACE "@X@" "${EXE_EXTENSION}" git_build_options "${git_build_options}") if(USE_VCPKG) - string(APPEND git_build_options "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/x64-windows/bin\"\n") + string(APPEND git_build_options "PATH=\"$PATH:$TEST_DIRECTORY/../compat/vcbuild/vcpkg/installed/${VCPKG_ARCH}/bin\"\n") endif() file(WRITE ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS ${git_build_options}) From 3aeb23b73891788ef56269aff46a66a39a5bf22e Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Fri, 2 Jul 2021 00:30:24 +0100 Subject: [PATCH 087/306] CMake: default Visual Studio generator has changed Correct some wording and inform users regarding the Visual Studio changes (from V16.6) to the default generator. Subsequent commits ensure that Git for Windows can be directly opened in modern Visual Studio without needing special configuration of the CMakeLists settings. It appeares that internally Visual Studio creates it's own version of the .sln file (etc.) for extension tools that expect them. The large number of references below document the shifting of Visual Studio default and CMake setting options. refs: https://docs.microsoft.com/en-us/search/?scope=C%2B%2B&view=msvc-150&terms=Ninja 1. https://docs.microsoft.com/en-us/cpp/linux/cmake-linux-configure?view=msvc-160 (note the linux bit) "In Visual Studio 2019 version 16.6 or later ***, Ninja is the default generator for configurations targeting a remote system or WSL. For more information, see this post on the C++ Team Blog [https://devblogs.microsoft.com/cppblog/linux-development-with-visual-studio-first-class-support-for-gdbserver-improved-build-times-with-ninja-and-updates-to-the-connection-manager/]. For more information about these settings, see CMakeSettings.json reference [https://docs.microsoft.com/en-us/cpp/build/cmakesettings-reference?view=msvc-160]." 2. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160 "CMake supports two files that allow users to specify common configure, build, and test options and share them with others: CMakePresets.json and CMakeUserPresets.json." " Both files are supported in Visual Studio 2019 version 16.10 or later. ***" 3. https://devblogs.microsoft.com/cppblog/linux-development-with-visual-studio-first-class-support-for-gdbserver-improved-build-times-with-ninja-and-updates-to-the-connection-manager/ " Ninja has been the default generator (underlying build system) for CMake configurations targeting Windows for some time***, but in Visual Studio 2019 version 16.6 Preview 3*** we added support for Ninja on Linux." 4. https://docs.microsoft.com/en-us/cpp/build/cmakesettings-reference?view=msvc-160 " `generator`: specifies CMake generator to use for this configuration. May be one of: Visual Studio 2019 only: Visual Studio 16 2019 Visual Studio 16 2019 Win64 Visual Studio 16 2019 ARM Visual Studio 2017 and later: Visual Studio 15 2017 Visual Studio 15 2017 Win64 Visual Studio 15 2017 ARM Visual Studio 14 2015 Visual Studio 14 2015 Win64 Visual Studio 14 2015 ARM Unix Makefiles Ninja Because Ninja is designed for fast build speeds instead of flexibility and function, it is set as the default. However, some CMake projects may be unable to correctly build using Ninja. If this occurs, you can instruct CMake to generate Visual Studio projects instead. To specify a Visual Studio generator in Visual Studio 2017, open the settings editor from the main menu by choosing CMake | Change CMake Settings. Delete "Ninja" and type "V". This activates IntelliSense, which enables you to choose the generator you want." "To specify a Visual Studio generator in Visual Studio 2019, right-click on the CMakeLists.txt file in Solution Explorer and choose CMake Settings for project > Show Advanced Settings > CMake Generator. When the active configuration specifies a Visual Studio generator, by default MSBuild.exe is invoked with` -m -v:minimal` arguments." 5. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#enable-cmakepresetsjson-integration-in-visual-studio-2019 "Enable CMakePresets.json integration in Visual Studio 2019 CMakePresets.json integration isn't enabled by default in Visual Studio 2019. You can enable it for all CMake projects in Tools > Options > CMake > General: (tick a box)" ... see more. 6. https://docs.microsoft.com/en-us/cpp/build/cmakesettings-reference?view=msvc-140 (whichever v140 is..) "CMake projects are supported in Visual Studio 2017 and later." 7. https://docs.microsoft.com/en-us/cpp/overview/what-s-new-for-cpp-2017?view=msvc-150 "Support added for the CMake Ninja generator." 8. https://docs.microsoft.com/en-us/cpp/overview/what-s-new-for-cpp-2017?view=msvc-150#cmake-support-via-open-folder "CMake support via Open Folder Visual Studio 2017 introduces support for using CMake projects without converting to MSBuild project files (.vcxproj). For more information, see CMake projects in Visual Studio[https://docs.microsoft.com/en-us/cpp/build/cmake-projects-in-visual-studio?view=msvc-150]. Opening CMake projects with Open Folder automatically configures the environment for C++ editing, building, and debugging." ... +more! 9. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#supported-cmake-and-cmakepresetsjson-versions "Visual Studio reads and evaluates CMakePresets.json and CMakeUserPresets.json itself and doesn't invoke CMake directly with the --preset option. So, CMake version 3.20 or later isn't strictly required when you're building with CMakePresets.json inside Visual Studio. We recommend using CMake version 3.14 or later." 10. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#enable-cmakepresetsjson-integration-in-visual-studio-2019 "If you don't want to enable CMakePresets.json integration for all CMake projects, you can enable CMakePresets.json integration for a single CMake project by adding a CMakePresets.json file to the root of the open folder. You must close and reopen the folder in Visual Studio to activate the integration. 11. https://docs.microsoft.com/en-us/cpp/build/cmake-presets-vs?view=msvc-160#default-configure-presets ***(doesn't actually say which version..) "Default Configure Presets If no CMakePresets.json or CMakeUserPresets.json file exists, or if CMakePresets.json or CMakeUserPresets.json is invalid, Visual Studio will fall back*** on the following default Configure Presets: Windows example JSON { "name": "windows-default", "displayName": "Windows x64 Debug", "description": "Sets Ninja generator, compilers, x64 architecture, build and install directory, debug build type", "generator": "Ninja", "binaryDir": "${sourceDir}/out/build/${presetName}", "architecture": { "value": "x64", "strategy": "external" }, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}" }, "vendor": { "microsoft.com/VisualStudioSettings/CMake/1.0": { "hostOS": [ "Windows" ] } } }, " Signed-off-by: Philip Oakley --- contrib/buildsystems/CMakeLists.txt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 4951d4d350ebb4..08da5b6cfe42aa 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -14,6 +14,11 @@ Note: Visual Studio also has the option of opening `CMakeLists.txt` directly; Using this option, Visual Studio will not find the source code, though, therefore the `File>Open>Folder...` option is preferred. +Visual Studio does not produce a .sln solution file nor the .vcxproj files +that may be required by VS extension tools. + +To generate the .sln/.vcxproj files run CMake manually, as described below. + Instructions to run CMake manually: mkdir -p contrib/buildsystems/out @@ -22,7 +27,7 @@ Instructions to run CMake manually: This will build the git binaries in contrib/buildsystems/out directory (our top-level .gitignore file knows to ignore contents of -this directory). +this directory). The project .sln and .vcxproj files are also generated. Possible build configurations(-DCMAKE_BUILD_TYPE) with corresponding compiler flags @@ -35,17 +40,16 @@ empty(default) : NOTE: -DCMAKE_BUILD_TYPE is optional. For multi-config generators like Visual Studio this option is ignored -This process generates a Makefile(Linux/*BSD/MacOS) , Visual Studio solution(Windows) by default. +This process generates a Makefile(Linux/*BSD/MacOS), Visual Studio solution(Windows) by default. Run `make` to build Git on Linux/*BSD/MacOS. Open git.sln on Windows and build Git. -NOTE: By default CMake uses Makefile as the build tool on Linux and Visual Studio in Windows, -to use another tool say `ninja` add this to the command line when configuring. -`-G Ninja` - NOTE: By default CMake will install vcpkg locally to your source tree on configuration, to avoid this, add `-DNO_VCPKG=TRUE` to the command line when configuring. +The Visual Studio default generator changed in v16.6 from its Visual Studio +implemenation to `Ninja` This required changes to many CMake scripts. + ]] cmake_minimum_required(VERSION 3.14) From d7655970bb547b76faf24cddb643600f90cea6ae Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Sun, 29 Nov 2020 00:12:26 +0100 Subject: [PATCH 088/306] ci(vs-build) also build Windows/ARM64 artifacts There are no Windows/ARM64 agents in GitHub Actions yet, therefore we just skip adjusting the `vs-test` job for now. Signed-off-by: Dennis Ameling Signed-off-by: Johannes Schindelin --- .github/workflows/main.yml | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 900be9957a23fc..441b858fb77fee 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -169,8 +169,11 @@ jobs: NO_PERL: 1 GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'" runs-on: windows-latest + strategy: + matrix: + arch: [x64, arm64] concurrency: - group: vs-build-${{ github.ref }} + group: vs-build-${{ github.ref }}-${{ matrix.arch }} cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} steps: - uses: actions/checkout@v4 @@ -189,14 +192,14 @@ jobs: uses: microsoft/setup-msbuild@v2 - name: copy dlls to root shell: cmd - run: compat\vcbuild\vcpkg_copy_dlls.bat release + run: compat\vcbuild\vcpkg_copy_dlls.bat release ${{ matrix.arch }}-windows - name: generate Visual Studio solution shell: bash run: | - cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/x64-windows \ - -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON + cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/${{ matrix.arch }}-windows \ + -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=${{ matrix.arch }} -DVCPKG_ARCH=${{ matrix.arch }}-windows - name: MSBuild - run: msbuild git.sln -property:Configuration=Release -property:Platform=x64 -maxCpuCount:4 -property:PlatformToolset=v142 + run: msbuild git.sln -property:Configuration=Release -property:Platform=${{ matrix.arch }} -maxCpuCount:4 -property:PlatformToolset=v142 - name: bundle artifact tar shell: bash env: @@ -210,7 +213,7 @@ jobs: - name: upload tracked files and build artifacts uses: actions/upload-artifact@v4 with: - name: vs-artifacts + name: vs-artifacts-${{ matrix.arch }} path: artifacts vs-test: name: win+VS test @@ -228,7 +231,7 @@ jobs: - name: download tracked files and build artifacts uses: actions/download-artifact@v4 with: - name: vs-artifacts + name: vs-artifacts-x64 path: ${{github.workspace}} - name: extract tracked files and build artifacts shell: bash From c20c6eee803feb2329fa776cd53380ff1bae2dbb Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Thu, 5 Aug 2021 19:04:13 -0400 Subject: [PATCH 089/306] subtree: update `contrib/subtree` `test` target The intention of this change is to align with how the top-level git `Makefile` defines its own test target (which also internally calls `$(MAKE) -C t/ all`). This change also ensures the consistency of `make -C contrib/subtree test` with other testing in CI executions (which rely on `$DEFAULT_TEST_TARGET` being defined as `prove`). Signed-off-by: Victoria Dye --- contrib/subtree/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/subtree/Makefile b/contrib/subtree/Makefile index 6fa7496bfdb3fd..6f6e90c4cb49b6 100644 --- a/contrib/subtree/Makefile +++ b/contrib/subtree/Makefile @@ -94,7 +94,7 @@ $(GIT_SUBTREE_TEST): $(GIT_SUBTREE) cp $< $@ test: $(GIT_SUBTREE_TEST) - $(MAKE) -C t/ test + $(MAKE) -C t/ all clean: $(RM) $(GIT_SUBTREE) From 1033d6fb5e1760db02e8e84308a5f3d0dacbc7a8 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Sat, 24 Apr 2021 11:09:58 +0100 Subject: [PATCH 090/306] .gitignore: add Visual Studio CMakeSetting.json file The CMakeSettings.json file is tool generated. Developers may track it should they provide additional settings. Signed-off-by: Philip Oakley --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e82aa19df03fc8..d6cdb6ca15efe1 100644 --- a/.gitignore +++ b/.gitignore @@ -250,3 +250,4 @@ Release/ /git.VC.db *.dSYM /contrib/buildsystems/out +CMakeSettings.json From f8c6875658ef963380f33c452751aac787d8af92 Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Sun, 6 Dec 2020 18:39:26 +0100 Subject: [PATCH 091/306] Add schannel to curl installation Signed-off-by: Dennis Ameling --- compat/vcbuild/vcpkg_install.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/vcbuild/vcpkg_install.bat b/compat/vcbuild/vcpkg_install.bat index 8da212487ae97d..575c65c20ba307 100644 --- a/compat/vcbuild/vcpkg_install.bat +++ b/compat/vcbuild/vcpkg_install.bat @@ -127,5 +127,5 @@ set features= goto :EOF :curl_features -set features=[core,openssl] +set features=[core,openssl,schannel] goto :EOF From c80d4f9090639f8ebbb5ec7c7c8f0ca0cad7b438 Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Mon, 19 Jul 2021 13:02:16 +0200 Subject: [PATCH 092/306] cmake(): allow setting HOST_CPU for cross-compilation Git's regular Makefile mentions that HOST_CPU should be defined when cross-compiling Git: https://github.com/git-for-windows/git/blob/37796bca76ef4180c39ee508ca3e42c0777ba444/Makefile#L438-L439 This is then used to set the GIT_HOST_CPU variable when compiling Git: https://github.com/git-for-windows/git/blob/37796bca76ef4180c39ee508ca3e42c0777ba444/Makefile#L1337-L1341 Then, when the user runs `git version --build-options`, it returns that value: https://github.com/git-for-windows/git/blob/37796bca76ef4180c39ee508ca3e42c0777ba444/help.c#L658 This commit adds the same functionality to the CMake configuration. Users can now set -DHOST_CPU= to set the target architecture. Signed-off-by: Dennis Ameling --- .github/workflows/main.yml | 2 +- contrib/buildsystems/CMakeLists.txt | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 441b858fb77fee..b1ed3794e2b2c8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -197,7 +197,7 @@ jobs: shell: bash run: | cmake `pwd`/contrib/buildsystems/ -DCMAKE_PREFIX_PATH=`pwd`/compat/vcbuild/vcpkg/installed/${{ matrix.arch }}-windows \ - -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=${{ matrix.arch }} -DVCPKG_ARCH=${{ matrix.arch }}-windows + -DNO_GETTEXT=YesPlease -DPERL_TESTS=OFF -DPYTHON_TESTS=OFF -DCURL_NO_CURL_CMAKE=ON -DCMAKE_GENERATOR_PLATFORM=${{ matrix.arch }} -DVCPKG_ARCH=${{ matrix.arch }}-windows -DHOST_CPU=${{ matrix.arch }} - name: MSBuild run: msbuild git.sln -property:Configuration=Release -property:Platform=${{ matrix.arch }} -maxCpuCount:4 -property:PlatformToolset=v142 - name: bundle artifact tar diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index fb70820f959803..4951d4d350ebb4 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -212,7 +212,14 @@ endif() #default behaviour include_directories(${CMAKE_SOURCE_DIR}) -add_compile_definitions(GIT_HOST_CPU="${CMAKE_SYSTEM_PROCESSOR}") + +# When cross-compiling, define HOST_CPU as the canonical name of the CPU on +# which the built Git will run (for instance "x86_64"). +if(NOT HOST_CPU) + add_compile_definitions(GIT_HOST_CPU="${CMAKE_SYSTEM_PROCESSOR}") +else() + add_compile_definitions(GIT_HOST_CPU="${HOST_CPU}") +endif() add_compile_definitions(SHA256_BLK INTERNAL_QSORT RUNTIME_PREFIX) add_compile_definitions(NO_OPENSSL SHA1_DC SHA1DC_NO_STANDARD_INCLUDES SHA1DC_INIT_SAFE_HASH_DEFAULT=0 From 1e95a2f6129572452a1af2df4340a05dfbba71ad Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 2 Apr 2021 22:50:54 +0200 Subject: [PATCH 093/306] mingw: allow for longer paths in `parse_interpreter()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As reported in https://github.com/newren/git-filter-repo/pull/225, it looks like 99 bytes is not really sufficient to represent e.g. the full path to Python when installed via Windows Store (and this path is used in the hasb bang line when installing scripts via `pip`). Let's increase it to what is probably the maximum sensible path size: MAX_PATH. This makes `parse_interpreter()` in line with what `lookup_prog()` handles. Signed-off-by: Johannes Schindelin Signed-off-by: Vilius Šumskas --- compat/mingw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..cad3f5802102bc 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1339,7 +1339,7 @@ static const char *quote_arg_msys2(const char *arg) static const char *parse_interpreter(const char *cmd) { - static char buf[100]; + static char buf[MAX_PATH]; char *p, *opt; ssize_t n; /* read() can return negative values */ int fd; From 48c6336d00d7a9fdcc9bc9589c3f05cfb7bdb009 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 17 May 2021 10:46:52 +0200 Subject: [PATCH 094/306] compat/vcbuild: document preferred way to build in Visual Studio We used to have that `make vcxproj` hack, but a hack it is. In the meantime, we have a much cleaner solution: using CMake, either explicitly, or even more conveniently via Visual Studio's built-in CMake support (simply open Git's top-level directory via File>Open>Folder...). Let's let the `README` reflect this. Signed-off-by: Johannes Schindelin --- compat/vcbuild/README | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/compat/vcbuild/README b/compat/vcbuild/README index 29ec1d0f104b80..5c71ea2daa4017 100644 --- a/compat/vcbuild/README +++ b/compat/vcbuild/README @@ -37,27 +37,17 @@ The Steps to Build Git with VS2015 or VS2017 from the command line. ================================================================ -Alternatively, run `make vcxproj` and then load the generated `git.sln` in -Visual Studio. The initial build will install the vcpkg system and build the +Alternatively, just open Git's top-level directory in Visual Studio, via +`File>Open>Folder...`. This will use CMake internally to generate the +project definitions. It will also install the vcpkg system and build the dependencies automatically. This will take a while. -Instead of generating the `git.sln` file yourself (which requires a full Git -for Windows SDK), you may want to consider fetching the `vs/master` branch of -https://github.com/git-for-windows/git instead (which is updated automatically -via CI running `make vcxproj`). The `vs/master` branch does not require a Git -for Windows to build, but you can run the test scripts in a regular Git Bash. - -Note that `make vcxproj` will automatically add and commit the generated `.sln` -and `.vcxproj` files to the repo. This is necessary to allow building a -fully-testable Git in Visual Studio, where a regular Git Bash can be used to -run the test scripts (as opposed to a full Git for Windows SDK): a number of -build targets, such as Git commands implemented as Unix shell scripts (where -`@@SHELL_PATH@@` and other placeholders are interpolated) require a full-blown -Git for Windows SDK (which is about 10x the size of a regular Git for Windows -installation). - -If your plan is to open a Pull Request with Git for Windows, it is a good idea -to drop this commit before submitting. +You can also generate the Visual Studio solution manually by downloading +and running CMake explicitly rather than letting Visual Studio doing +that implicitly. + +Another, deprecated option is to run `make vcxproj`. This option is +superseded by the CMake-based build, and will be removed at some point. ================================================================ The Steps of Build Git with VS2008 From 921f35cada189ddc1cda7dde0d2c56a01d2d78f6 Mon Sep 17 00:00:00 2001 From: Pascal Muller Date: Wed, 23 Jun 2021 21:21:10 +0200 Subject: [PATCH 095/306] http: optionally send SSL client certificate This adds support for a new http.sslAutoClientCert config value. In cURL 7.77 or later the schannel backend does not automatically send client certificates from the Windows Certificate Store anymore. This config value is only used if http.sslBackend is set to "schannel", and can be used to opt in to the old behavior and force cURL to send client certificates. This fixes https://github.com/git-for-windows/git/issues/3292 Signed-off-by: Pascal Muller --- Documentation/config/http.txt | 5 +++++ git-curl-compat.h | 8 ++++++++ http.c | 24 +++++++++++++++++++++--- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Documentation/config/http.txt b/Documentation/config/http.txt index 9125d93a75b036..ddda264dbed9b7 100644 --- a/Documentation/config/http.txt +++ b/Documentation/config/http.txt @@ -234,6 +234,11 @@ http.schannelUseSSLCAInfo:: when the `schannel` backend was configured via `http.sslBackend`, unless `http.schannelUseSSLCAInfo` overrides this behavior. +http.sslAutoClientCert:: + As of cURL v7.77.0, the Secure Channel backend won't automatically + send client certificates from the Windows Certificate Store anymore. + To opt in to the old behavior, http.sslAutoClientCert can be set. + 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/git-curl-compat.h b/git-curl-compat.h index 703756ba851d5b..8291755f50454f 100644 --- a/git-curl-compat.h +++ b/git-curl-compat.h @@ -45,4 +45,12 @@ #define GIT_CURL_HAVE_CURLOPT_PROTOCOLS_STR 1 #endif +/** + * CURLSSLOPT_AUTO_CLIENT_CERT was added in 7.77.0, released in May + * 2021. + */ +#if LIBCURL_VERSION_NUM >= 0x074d00 +#define GIT_CURL_HAVE_CURLSSLOPT_AUTO_CLIENT_CERT +#endif + #endif diff --git a/http.c b/http.c index e057e877163b7b..b6a5b87a4c3d63 100644 --- a/http.c +++ b/http.c @@ -157,6 +157,8 @@ static int http_schannel_check_revoke_mode = */ static int http_schannel_use_ssl_cainfo; +static int http_auto_client_cert; + static int always_auth_proactively(void) { return http_proactive_auth != PROACTIVE_AUTH_NONE && @@ -445,6 +447,11 @@ static int http_options(const char *var, const char *value, return 0; } + if (!strcmp("http.sslautoclientcert", var)) { + http_auto_client_cert = git_config_bool(var, value); + return 0; + } + if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value, ctx->kvi); if (min_curl_sessions > 1) @@ -1062,9 +1069,20 @@ static CURL *get_curl_handle(void) } #endif - if (http_ssl_backend && !strcmp("schannel", http_ssl_backend) && - http_schannel_check_revoke_mode) { - curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, http_schannel_check_revoke_mode); + if (http_ssl_backend && !strcmp("schannel", http_ssl_backend)) { + long ssl_options = 0; + if (http_schannel_check_revoke_mode) { + ssl_options |= http_schannel_check_revoke_mode; + } + + if (http_auto_client_cert) { +#ifdef GIT_CURL_HAVE_CURLSSLOPT_AUTO_CLIENT_CERT + ssl_options |= CURLSSLOPT_AUTO_CLIENT_CERT; +#endif + } + + if (ssl_options) + curl_easy_setopt(result, CURLOPT_SSL_OPTIONS, ssl_options); } if (http_proactive_auth != PROACTIVE_AUTH_NONE) From 1fcbe5cba6c56976619330f460ea8c567ff63bba Mon Sep 17 00:00:00 2001 From: Victoria Dye Date: Thu, 5 Aug 2021 19:11:59 -0400 Subject: [PATCH 096/306] ci: run `contrib/subtree` tests in CI builds Because `git subtree` (unlike most other `contrib` modules) is included as part of the standard release of Git for Windows, its stability should be verified as consistently as it is for the rest of git. By including the `git subtree` tests in the CI workflow, these tests are as much of a gate to merging and indicator of stability as the standard test suite. Signed-off-by: Victoria Dye --- ci/run-build-and-tests.sh | 4 ++++ ci/run-test-slice.sh | 3 +++ 2 files changed, 7 insertions(+) diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 76667a1277720d..1fe8a59169a054 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -73,5 +73,9 @@ case "$jobname" in ;; esac +case " $MAKE_TARGETS " in +*" all "*) make -C contrib/subtree test;; +esac + check_unignored_build_artifacts save_good_tree diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh index e167e646f79e3d..8cb0038d07ca64 100755 --- a/ci/run-test-slice.sh +++ b/ci/run-test-slice.sh @@ -20,4 +20,7 @@ if [ "$1" == "0" ] ; then group "Run unit tests" make --quiet -C t unit-tests-test-tool fi +# Run the git subtree tests only if main tests succeeded +test 0 != "$1" || make -C contrib/subtree test + check_unignored_build_artifacts From 6183d2da274c4c6265a3f72a058791c24fe8e2b6 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Sun, 31 Oct 2021 23:15:13 +0000 Subject: [PATCH 097/306] hash-object: demonstrate a >4GB/LLP64 problem On LLP64 systems, such as Windows, the size of `long`, `int`, etc. is only 32 bits (for backward compatibility). Git's use of `unsigned long` for file memory sizes in many places, rather than size_t, limits the handling of large files on LLP64 systems (commonly given as `>4GB`). Provide a minimum test for handling a >4GB file. The `hash-object` command, with the `--literally` and without `-w` option avoids writing the object, either loose or packed. This avoids the code paths hitting the `bigFileThreshold` config test code, the zlib code, and the pack code. Subsequent patches will walk the test's call chain, converting types to `size_t` (which is larger in LLP64 data models) where appropriate. Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- t/t1007-hash-object.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index a0481139de5519..51604797e98b35 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -49,6 +49,9 @@ test_expect_success 'setup' ' example sha1:ddd3f836d3e3fbb7ae289aa9ae83536f76956399 example sha256:b44fe1fe65589848253737db859bd490453510719d7424daab03daf0767b85ae + + large5GB sha1:0be2be10a4c8764f32c4bf372a98edc731a4b204 + large5GB sha256:dc18ca621300c8d3cfa505a275641ebab00de189859e022a975056882d313e64 EOF ' @@ -265,4 +268,12 @@ test_expect_success '--stdin outside of repository (uses SHA-1)' ' test_cmp expect actual ' +test_expect_failure EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ + 'files over 4GB hash literally' ' + test-tool genzeros $((5*1024*1024*1024)) >big && + test_oid large5GB >expect && + git hash-object --stdin --literally actual && + test_cmp expect actual +' + test_done From 23fe6002516b16fff6b3515a801f6888c4f349ad Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Fri, 12 Nov 2021 21:07:03 +0000 Subject: [PATCH 098/306] write_object_file_literally(): use size_t The previous commit adds a test that demonstrates a problem in the `hash-object --literally` command, manifesting in an unnecessary file size limit on systems using the LLP64 data model (which includes Windows). Walking the affected code path is `cmd_hash_object()` >> `hash_fd()` >> `hash_literally()` >> `hash_object_file_literally()`. The function `hash_object_file_literally()` is the first with a file length parameter (via a mem buffer). This commit changes the type of that parameter to the LLP64 compatible `size_t` type. There are no other uses of the function. The `strbuf` type is already `size_t` compatible. Note: The hash-object test does not yet pass. Subsequent commits will continue to walk the call tree's lower level functions to identify further fixes. Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- object-file.c | 4 ++-- object-store-ll.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/object-file.c b/object-file.c index 5b792b3dd42cec..db2d977e8cdb71 100644 --- a/object-file.c +++ b/object-file.c @@ -1960,7 +1960,7 @@ static void write_object_file_prepare(const struct git_hash_algo *algo, } static void write_object_file_prepare_literally(const struct git_hash_algo *algo, - const void *buf, unsigned long len, + const void *buf, size_t len, const char *type, struct object_id *oid, char *hdr, int *hdrlen) { @@ -2498,7 +2498,7 @@ int write_object_file_flags(const void *buf, unsigned long len, return 0; } -int write_object_file_literally(const void *buf, unsigned long len, +int write_object_file_literally(const void *buf, size_t len, const char *type, struct object_id *oid, unsigned flags) { diff --git a/object-store-ll.h b/object-store-ll.h index cd3bd5bd99f78c..d54534742f97df 100644 --- a/object-store-ll.h +++ b/object-store-ll.h @@ -282,7 +282,7 @@ static inline int write_object_file(const void *buf, unsigned long len, return write_object_file_flags(buf, len, type, oid, NULL, 0); } -int write_object_file_literally(const void *buf, unsigned long len, +int write_object_file_literally(const void *buf, size_t len, const char *type, struct object_id *oid, unsigned flags); int stream_loose_object(struct input_stream *in_stream, size_t len, From c22d98bc72fa7a1ce46496db67cead4506dfef0a Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Fri, 12 Nov 2021 21:14:50 +0000 Subject: [PATCH 099/306] object-file.c: use size_t for header lengths Continue walking the code path for the >4GB `hash-object --literally` test. The `hash_object_file_literally()` function internally uses both `hash_object_file()` and `write_object_file_prepare()`. Both function signatures use `unsigned long` rather than `size_t` for the mem buffer sizes. Use `size_t` instead, for LLP64 compatibility. While at it, convert those function's object's header buffer length to `size_t` for consistency. The value is already upcast to `uintmax_t` for print format compatibility. Note: The hash-object test still does not pass. A subsequent commit continues to walk the call tree's lower level hash functions to identify further fixes. Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- object-file.c | 22 +++++++++++----------- object-store-ll.h | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/object-file.c b/object-file.c index db2d977e8cdb71..2960775978f0f6 100644 --- a/object-file.c +++ b/object-file.c @@ -1937,7 +1937,7 @@ void *read_object_with_reference(struct repository *r, static void hash_object_body(const struct git_hash_algo *algo, git_hash_ctx *c, const void *buf, unsigned long len, struct object_id *oid, - char *hdr, int *hdrlen) + char *hdr, size_t *hdrlen) { algo->init_fn(c); algo->update_fn(c, hdr, *hdrlen); @@ -1946,9 +1946,9 @@ static void hash_object_body(const struct git_hash_algo *algo, git_hash_ctx *c, } static void write_object_file_prepare(const struct git_hash_algo *algo, - const void *buf, unsigned long len, + const void *buf, size_t len, enum object_type type, struct object_id *oid, - char *hdr, int *hdrlen) + char *hdr, size_t *hdrlen) { git_hash_ctx c; @@ -1962,7 +1962,7 @@ static void write_object_file_prepare(const struct git_hash_algo *algo, static void write_object_file_prepare_literally(const struct git_hash_algo *algo, const void *buf, size_t len, const char *type, struct object_id *oid, - char *hdr, int *hdrlen) + char *hdr, size_t *hdrlen) { git_hash_ctx c; @@ -2082,17 +2082,17 @@ int finalize_object_file_flags(const char *tmpfile, const char *filename, } static void hash_object_file_literally(const struct git_hash_algo *algo, - const void *buf, unsigned long len, + const void *buf, size_t len, const char *type, struct object_id *oid) { char hdr[MAX_HEADER_LEN]; - int hdrlen = sizeof(hdr); + size_t hdrlen = sizeof(hdr); write_object_file_prepare_literally(algo, buf, len, type, oid, hdr, &hdrlen); } void hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, enum object_type type, + size_t len, enum object_type type, struct object_id *oid) { hash_object_file_literally(algo, buf, len, type_name(type), oid); @@ -2458,7 +2458,7 @@ int stream_loose_object(struct input_stream *in_stream, size_t len, return err; } -int write_object_file_flags(const void *buf, unsigned long len, +int write_object_file_flags(const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *compat_oid_in, unsigned flags) { @@ -2467,7 +2467,7 @@ int write_object_file_flags(const void *buf, unsigned long len, const struct git_hash_algo *compat = repo->compat_hash_algo; struct object_id compat_oid; char hdr[MAX_HEADER_LEN]; - int hdrlen = sizeof(hdr); + size_t hdrlen = sizeof(hdr); /* Generate compat_oid */ if (compat) { @@ -2507,8 +2507,8 @@ int write_object_file_literally(const void *buf, size_t len, const struct git_hash_algo *algo = repo->hash_algo; const struct git_hash_algo *compat = repo->compat_hash_algo; struct object_id compat_oid; - int hdrlen, status = 0; - int compat_type = -1; + size_t hdrlen; + int status = 0, compat_type = -1; if (compat) { compat_type = type_from_string_gently(type, -1, 1); diff --git a/object-store-ll.h b/object-store-ll.h index d54534742f97df..f9eee3433a112c 100644 --- a/object-store-ll.h +++ b/object-store-ll.h @@ -270,10 +270,10 @@ void *repo_read_object_file(struct repository *r, int oid_object_info(struct repository *r, const struct object_id *, unsigned long *); void hash_object_file(const struct git_hash_algo *algo, const void *buf, - unsigned long len, enum object_type type, + size_t len, enum object_type type, struct object_id *oid); -int write_object_file_flags(const void *buf, unsigned long len, +int write_object_file_flags(const void *buf, size_t len, enum object_type type, struct object_id *oid, struct object_id *comapt_oid_in, unsigned flags); static inline int write_object_file(const void *buf, unsigned long len, From 4f197bd47d73df6199ec2db865c48227d26f179b Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Fri, 12 Nov 2021 21:16:51 +0000 Subject: [PATCH 100/306] hash algorithms: use size_t for section lengths Continue walking the code path for the >4GB `hash-object --literally` test to the hash algorithm step for LLP64 systems. This patch lets the SHA1DC code use `size_t`, making it compatible with LLP64 data models (as used e.g. by Windows). The interested reader of this patch will note that we adjust the signature of the `git_SHA1DCUpdate()` function without updating _any_ call site. This certainly puzzled at least one reviewer already, so here is an explanation: This function is never called directly, but always via the macro `platform_SHA1_Update`, which is usually called via the macro `git_SHA1_Update`. However, we never call `git_SHA1_Update()` directly in `struct git_hash_algo`. Instead, we call `git_hash_sha1_update()`, which is defined thusly: static void git_hash_sha1_update(git_hash_ctx *ctx, const void *data, size_t len) { git_SHA1_Update(&ctx->sha1, data, len); } i.e. it contains an implicit downcast from `size_t` to `unsigned long` (before this here patch). With this patch, there is no downcast anymore. With this patch, finally, the t1007-hash-object.sh "files over 4GB hash literally" test case is fixed. Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- object-file.c | 4 ++-- sha1dc_git.c | 3 +-- sha1dc_git.h | 2 +- t/t1007-hash-object.sh | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/object-file.c b/object-file.c index 2960775978f0f6..f94254a0202c66 100644 --- a/object-file.c +++ b/object-file.c @@ -1935,7 +1935,7 @@ void *read_object_with_reference(struct repository *r, } static void hash_object_body(const struct git_hash_algo *algo, git_hash_ctx *c, - const void *buf, unsigned long len, + const void *buf, size_t len, struct object_id *oid, char *hdr, size_t *hdrlen) { @@ -1955,7 +1955,7 @@ static void write_object_file_prepare(const struct git_hash_algo *algo, /* Generate the header */ *hdrlen = format_object_header(hdr, *hdrlen, type, len); - /* Sha1.. */ + /* Hash (function pointers) computation */ hash_object_body(algo, &c, buf, len, oid, hdr, hdrlen); } diff --git a/sha1dc_git.c b/sha1dc_git.c index 9b675a046ee699..fe58d7962a30c9 100644 --- a/sha1dc_git.c +++ b/sha1dc_git.c @@ -27,10 +27,9 @@ void git_SHA1DCFinal(unsigned char hash[20], SHA1_CTX *ctx) /* * Same as SHA1DCUpdate, but adjust types to match git's usual interface. */ -void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, unsigned long len) +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *vdata, size_t len) { const char *data = vdata; - /* We expect an unsigned long, but sha1dc only takes an int */ while (len > INT_MAX) { SHA1DCUpdate(ctx, data, INT_MAX); data += INT_MAX; diff --git a/sha1dc_git.h b/sha1dc_git.h index f6f880cabea382..0bcf1aa84b7241 100644 --- a/sha1dc_git.h +++ b/sha1dc_git.h @@ -15,7 +15,7 @@ void git_SHA1DCInit(SHA1_CTX *); #endif void git_SHA1DCFinal(unsigned char [20], SHA1_CTX *); -void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, unsigned long len); +void git_SHA1DCUpdate(SHA1_CTX *ctx, const void *data, size_t len); #define platform_SHA_IS_SHA1DC /* used by "test-tool sha1-is-sha1dc" */ diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 51604797e98b35..710139837e7a91 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -268,7 +268,7 @@ test_expect_success '--stdin outside of repository (uses SHA-1)' ' test_cmp expect actual ' -test_expect_failure EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ 'files over 4GB hash literally' ' test-tool genzeros $((5*1024*1024*1024)) >big && test_oid large5GB >expect && From c4551e70dbd709597a9f96a94ea2886cd64875e1 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Mon, 6 Dec 2021 22:26:50 +0000 Subject: [PATCH 101/306] hash-object --stdin: verify that it works with >4GB/LLP64 Just like the `hash-object --literally` code path, the `--stdin` code path also needs to use `size_t` instead of `unsigned long` to represent memory sizes, otherwise it would cause problems on platforms using the LLP64 data model (such as Windows). To limit the scope of the test case, the object is explicitly not written to the object store, nor are any filters applied. The `big` file from the previous test case is reused to save setup time; To avoid relying on that side effect, it is generated if it does not exist (e.g. when running via `sh t1007-*.sh --long --run=1,41`). Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- t/t1007-hash-object.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 710139837e7a91..aa1dd51709d5cc 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -276,4 +276,12 @@ test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ test_cmp expect actual ' +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ + 'files over 4GB hash correctly via --stdin' ' + { test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } && + test_oid large5GB >expect && + git hash-object --stdin actual && + test_cmp expect actual +' + test_done From 40431da795005659257d03189d8f92cc39e8ec40 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Thu, 22 Apr 2021 11:11:38 +0100 Subject: [PATCH 102/306] CMakeLists: add default "x64-windows" arch for Visual Studio In Git-for-Windows, work on using ARM64 has progressed. The commit 2d94b77b27 (cmake: allow building for Windows/ARM64, 2020-12-04) failed to notice that /compat/vcbuild/vcpkg_install.bat will default to using the "x64-windows" architecture for the vcpkg installation if not set, but CMake is not told of this default. Commit 635b6d99b3 (vcbuild: install ARM64 dependencies when building ARM64 binaries, 2020-01-31) later updated vcpkg_install.bat to accept an arch (%1) parameter, but retained the default. This default is neccessary for the use case where the project directory is opened directly in Visual Studio, which will find and build a CMakeLists.txt file without any parameters, thus expecting use of the default setting. Also Visual studio will generate internal .sln solution and .vcxproj project files needed for some extension tools. Inform users of the additional .sln/.vcxproj generation. ** How to test: rm -rf '.vs' # remove old visual studio settings rm -rf 'compat/vcbuild/vcpkg' # remove any vcpkg downloads rm -rf 'contrib/buildsystems/out' # remove builds & CMake artifacts with a fresh Visual Studio Community Edition, File>>Open>>(git *folder*) to load the project (which will take some time!). check for successful compilation. The implicit .sln (etc.) are in the hidden .vs directory created by Visual Studio. Signed-off-by: Philip Oakley --- contrib/buildsystems/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 08da5b6cfe42aa..374e34b0cf51b3 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -71,6 +71,10 @@ if(USE_VCPKG) message("Initializing vcpkg and building the Git's dependencies (this will take a while...)") execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat ${VCPKG_ARCH}) endif() + if(NOT EXISTS ${VCPKG_ARCH}) + message("VCPKG_ARCH: unset, using 'x64-windows'") + set(VCPKG_ARCH "x64-windows") # default from vcpkg_install.bat + endif() list(APPEND CMAKE_PREFIX_PATH "${VCPKG_DIR}/installed/${VCPKG_ARCH}") # In the vcpkg edition, we need this to be able to link to libcurl From ce0d0a9baf8ae72cfd9ef65114f3cff327306399 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Mon, 6 Dec 2021 22:42:46 +0000 Subject: [PATCH 103/306] hash-object: add another >4GB/LLP64 test case To complement the `--stdin` and `--literally` test cases that verify that we can hash files larger than 4GB on 64-bit platforms using the LLP64 data model, here is a test case that exercises `hash-object` _without_ any options. Just as before, we use the `big` file from the previous test case if it exists to save on setup time, otherwise generate it. Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- t/t1007-hash-object.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index aa1dd51709d5cc..7c6b7913d4f23d 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -284,4 +284,12 @@ test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ test_cmp expect actual ' +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ + 'files over 4GB hash correctly' ' + { test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } && + test_oid large5GB >expect && + git hash-object -- big >actual && + test_cmp expect actual +' + test_done From b045dfa18e6016163552b73f94c6cd09fb623f2c Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 13 Apr 2022 14:49:17 -0400 Subject: [PATCH 104/306] setup: properly use "%(prefix)/" when in WSL Signed-off-by: Derrick Stolee --- setup.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setup.c b/setup.c index 8a488f3e7c74b1..fc4e9ff742efc9 100644 --- a/setup.c +++ b/setup.c @@ -1781,10 +1781,19 @@ const char *setup_git_directory_gently(int *nongit_ok) break; case GIT_DIR_INVALID_OWNERSHIP: if (!nongit_ok) { + struct strbuf prequoted = STRBUF_INIT; struct strbuf quoted = STRBUF_INIT; strbuf_complete(&report, '\n'); - sq_quote_buf_pretty("ed, dir.buf); + +#ifdef __MINGW32__ + if (dir.buf[0] == '/') + strbuf_addstr(&prequoted, "%(prefix)/"); +#endif + + strbuf_add(&prequoted, dir.buf, dir.len); + sq_quote_buf_pretty("ed, prequoted.buf); + die(_("detected dubious ownership in repository at '%s'\n" "%s" "To add an exception for this directory, call:\n" From 86670c89a8935ec4354e0b0433cfedf3b830d81c Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Mon, 10 May 2021 16:47:40 +0100 Subject: [PATCH 105/306] CMake: show Win32 and Generator_platform build-option values Ensure key CMake option values are part of the CMake output to facilitate user support when tool updates impact the wider CMake actions, particularly ongoing 'improvements' in Visual Studio. These CMake displays perform the same function as the build-options.txt provided in the main Git for Windows. CMake is already chatty. The setting of CMAKE_EXPORT_COMPILE_COMMANDS is also reported. Include the environment's CMAKE_EXPORT_COMPILE_COMMANDS value which may have been propogated to CMake's internal value. Testing the CMAKE_EXPORT_COMPILE_COMMANDS processing can be difficult in the Visual Studio environment, as it may be cached in many places. The 'environment' may include the OS, the user shell, CMake's own environment, along with the Visual Studio presets and caches. See previous commit for arefacts that need removing for a clean test. Signed-off-by: Philip Oakley --- contrib/buildsystems/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 374e34b0cf51b3..54c28134bd0481 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -63,10 +63,20 @@ endif() if(NOT DEFINED CMAKE_EXPORT_COMPILE_COMMANDS) set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) + message("settting CMAKE_EXPORT_COMPILE_COMMANDS: ${CMAKE_EXPORT_COMPILE_COMMANDS}") endif() if(USE_VCPKG) set(VCPKG_DIR "${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg") + message("WIN32: ${WIN32}") # show its underlying text values + message("VCPKG_DIR: ${VCPKG_DIR}") + message("VCPKG_ARCH: ${VCPKG_ARCH}") # maybe unset + message("MSVC: ${MSVC}") + message("CMAKE_GENERATOR: ${CMAKE_GENERATOR}") + message("CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") + message("CMAKE_GENERATOR_PLATFORM: ${CMAKE_GENERATOR_PLATFORM}") + message("CMAKE_EXPORT_COMPILE_COMMANDS: ${CMAKE_EXPORT_COMPILE_COMMANDS}") + message("ENV(CMAKE_EXPORT_COMPILE_COMMANDS): $ENV{CMAKE_EXPORT_COMPILE_COMMANDS}") if(NOT EXISTS ${VCPKG_DIR}) message("Initializing vcpkg and building the Git's dependencies (this will take a while...)") execute_process(COMMAND ${CMAKE_SOURCE_DIR}/compat/vcbuild/vcpkg_install.bat ${VCPKG_ARCH}) From d13b2b6f0b56e154efcc986bef8240de466487cc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 8 Sep 2021 13:05:42 +0200 Subject: [PATCH 106/306] init: do parse _all_ core.* settings early In Git for Windows, `has_symlinks` is set to 0 by default. Therefore, we need to parse the config setting `core.symlinks` to know if it has been set to `true`. In `git init`, we must do that before copying the templates because they might contain symbolic links. Even if the support for symbolic links on Windows has not made it to upstream Git yet, we really should make sure that all the `core.*` settings are parsed before proceeding, as they might very well change the behavior of `git init` in a way the user intended. This fixes https://github.com/git-for-windows/git/issues/3414 Signed-off-by: Johannes Schindelin --- config.c | 4 ++-- config.h | 2 ++ setup.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config.c b/config.c index 50f2d17b399446..95b7de0522d180 100644 --- a/config.c +++ b/config.c @@ -1392,8 +1392,8 @@ int git_config_color(char *dest, const char *var, const char *value) return 0; } -static int git_default_core_config(const char *var, const char *value, - const struct config_context *ctx, void *cb) +int git_default_core_config(const char *var, const char *value, + const struct config_context *ctx, void *cb) { /* This needs a better name */ if (!strcmp(var, "core.filemode")) { diff --git a/config.h b/config.h index 5c730c4f899160..e4199bbdc07685 100644 --- a/config.h +++ b/config.h @@ -167,6 +167,8 @@ typedef int (*config_fn_t)(const char *, const char *, int git_default_config(const char *, const char *, const struct config_context *, void *); +int git_default_core_config(const char *var, const char *value, + const struct config_context *ctx, void *cb); /** * Read a specific file in git-config format. diff --git a/setup.c b/setup.c index 8a488f3e7c74b1..9bcfa72f6e8cd2 100644 --- a/setup.c +++ b/setup.c @@ -2586,7 +2586,7 @@ int init_db(const char *git_dir, const char *real_git_dir, * have set up the repository format such that we can evaluate * includeIf conditions correctly in the case of re-initialization. */ - git_config(platform_core_config, NULL); + git_config(git_default_core_config, NULL); safe_create_dir(git_dir, 0); From 1961e700e6c045d350e9cdb626cf044815ae33d9 Mon Sep 17 00:00:00 2001 From: Philip Oakley Date: Tue, 7 Dec 2021 09:53:41 +0000 Subject: [PATCH 107/306] hash-object: add a >4GB/LLP64 test case using filtered input To verify that the `clean` side of the `clean`/`smudge` filter code is correct with regards to LLP64 (read: to ensure that `size_t` is used instead of `unsigned long`), here is a test case using a trivial filter, specifically _not_ writing anything to the object store to limit the scope of the test case. As in previous commits, the `big` file from previous test cases is reused if available, to save setup time, otherwise re-generated. Signed-off-by: Philip Oakley Signed-off-by: Johannes Schindelin --- t/t1007-hash-object.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 7c6b7913d4f23d..5850c27a9cc315 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -292,4 +292,16 @@ test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ test_cmp expect actual ' +# This clean filter does nothing, other than excercising the interface. +# We ensure that cleaning doesn't mangle large files on 64-bit Windows. +test_expect_success EXPENSIVE,SIZE_T_IS_64BIT,!LONG_IS_64BIT \ + 'hash filtered files over 4GB correctly' ' + { test -f big || test-tool genzeros $((5*1024*1024*1024)) >big; } && + test_oid large5GB >expect && + test_config filter.null-filter.clean "cat" && + echo "big filter=null-filter" >.gitattributes && + git hash-object -- big >actual && + test_cmp expect actual +' + test_done From 307bb0547e24ab46118d446f9468d4ad2f789491 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Wed, 13 Apr 2022 14:54:43 -0400 Subject: [PATCH 108/306] compat/mingw.c: do not warn when failing to get owner In the case of Git for Windows (say, in a Git Bash window) running in a Windows Subsystem for Linux (WSL) directory, the GetNamedSecurityInfoW() call in is_path_owned_By_current_side() returns an error code other than ERROR_SUCCESS. This is consistent behavior across this boundary. In these cases, the owner would always be different because the WSL owner is a different entity than the Windows user. The change here is to suppress the error message that looks like this: error: failed to get owner for '//wsl.localhost/...' (1) Before this change, this warning happens for every Git command, regardless of whether the directory is marked with safe.directory. Signed-off-by: Derrick Stolee --- compat/mingw.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..ed466660f39cc6 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2924,9 +2924,7 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report) DACL_SECURITY_INFORMATION, &sid, NULL, NULL, NULL, &descriptor); - if (err != ERROR_SUCCESS) - error(_("failed to get owner for '%s' (%ld)"), path, err); - else if (sid && IsValidSid(sid)) { + if (err == ERROR_SUCCESS && sid && IsValidSid(sid)) { /* Now, verify that the SID matches the current user's */ static PSID current_user_sid; BOOL is_member; From 7324a351e4803a182cb74cb0a4c0a23502c2f298 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 29 Mar 2022 12:05:18 +0200 Subject: [PATCH 109/306] vcxproj: allow building with `NO_PERL` again This is another fall-out of the recent refactoring flurry. Signed-off-by: Johannes Schindelin --- config.mak.uname | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config.mak.uname b/config.mak.uname index 9f4671734aec90..a2a23f0f30c07e 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -802,9 +802,11 @@ vcxproj: sed -i '/^git_broken_path_fix ".*/d' git-sh-setup git add -f $(SCRIPT_LIB) $(SCRIPTS) +ifndef NO_PERL # Add Perl module $(MAKE) $(LIB_PERL_GEN) git add -f perl/build +endif # Add bin-wrappers, for testing rm -rf bin-wrappers/ From 348fae04025413ba4201b37af415e68b486e7cc9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 28 Jun 2022 16:35:04 +0200 Subject: [PATCH 110/306] vcxproj: require C11 This fixes the build after 7bc341e21b (git-compat-util: add a test balloon for C99 support, 2021-12-01). Signed-off-by: Johannes Schindelin --- contrib/buildsystems/Generators/Vcxproj.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index a6d1c6b8d05682..1858107378396a 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -178,6 +178,7 @@ sub createProject { OnlyExplicitInline ProgramDatabase + stdc11 true From 102e426b4a4f12e57185559361b197011688a37a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 28 Jun 2022 16:38:12 +0200 Subject: [PATCH 111/306] vcxproj: ignore the `-pedantic` option This is now passed by default, ever since 6a8cbc41ba (developer: enable pedantic by default, 2021-09-03). Signed-off-by: Johannes Schindelin --- contrib/buildsystems/engine.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl index 417ae71d44ccab..ee4fca200cc506 100755 --- a/contrib/buildsystems/engine.pl +++ b/contrib/buildsystems/engine.pl @@ -263,7 +263,7 @@ sub handleCompileLine if ("$part" eq "-o") { # ignore object file shift @parts; - } elsif ("$part" eq "-c" || "$part" eq "-i" || "$part" =~ /^-fno-/) { + } elsif ("$part" eq "-c" || "$part" eq "-i" || "$part" =~ /^-fno-/ || "$part" eq '-pedantic') { # ignore compile flag } elsif ($part =~ /^.?-I/) { push(@incpaths, $part); From 32973aab639ac5d9db5564790e31423d6c3d8967 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 28 Jun 2022 17:00:59 +0200 Subject: [PATCH 112/306] vcxproj: include reftable when committing `.vcxproj` files Signed-off-by: Johannes Schindelin --- config.mak.uname | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.mak.uname b/config.mak.uname index a2a23f0f30c07e..9b7e0a519374b3 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -770,7 +770,7 @@ vcxproj: # Make .vcxproj files and add them perl contrib/buildsystems/generate -g Vcxproj - git add -f git.sln {*,*/lib.proj,t/helper/*}/*.vcxproj + git add -f git.sln {*,*/lib.proj,t/helper/*,reftable/libreftable{,_test}.proj}/*.vcxproj # Generate the LinkOrCopyBuiltins.targets and LinkOrCopyRemoteHttp.targets file (echo '' && \ From 60299ef25e4add58a602bf269cd8b7e52ac71da5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 28 Jun 2022 18:04:01 +0200 Subject: [PATCH 113/306] vcxproj: handle libreftable_test, too Since ef8a6c6268 (reftable: utility functions, 2021-10-07) we not only have a libreftable, but also a libreftable_test. Signed-off-by: Johannes Schindelin --- contrib/buildsystems/Generators/Vcxproj.pm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index 1858107378396a..20d91ea84bfd44 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -77,7 +77,7 @@ sub createProject { my $libs_release = "\n "; my $libs_debug = "\n "; if (!$static_library && $name ne 'headless-git') { - $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}})); + $libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable(_test)?\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}})); $libs_debug = $libs_release; $libs_debug =~ s/zlib\.lib/zlibd\.lib/g; $libs_debug =~ s/libexpat\.lib/libexpatd\.lib/g; @@ -258,6 +258,7 @@ EOM if ((!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') && !($name =~ /headless-git/)) { my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"}; my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"}; + my $uuid_libreftable_test = $$build_structure{"LIBS_reftable/libreftable_test_GUID"}; my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"}; print F << "EOM"; @@ -269,10 +270,14 @@ EOM EOM if (!($name =~ /xdiff|libreftable/)) { print F << "EOM"; - + $uuid_libreftable false + + $uuid_libreftable_test + false + EOM } if (!($name =~ 'xdiff')) { From a69adf6ed97f510ea88d4b41fa21c19059201982 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 28 Jun 2022 17:36:21 +0200 Subject: [PATCH 114/306] vcxproj: avoid escaping double quotes in the defines Visual Studio 2022 does not like that at all. Signed-off-by: Johannes Schindelin --- contrib/buildsystems/Generators/Vcxproj.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index 20d91ea84bfd44..bf77a44e11f463 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -88,6 +88,7 @@ sub createProject { $defines =~ s//>/g; $defines =~ s/\'//g; + $defines =~ s/\\"/"/g; my $rcdefines = $defines; $rcdefines =~ s/(? Date: Sun, 10 Jul 2022 00:39:32 +0200 Subject: [PATCH 115/306] ci: adjust Azure Pipeline for `runs_on_pool` These refactorings are really gifts that keep on giving. Signed-off-by: Johannes Schindelin --- ci/lib.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ci/lib.sh b/ci/lib.sh index 8885ee3c3f86c6..1e192b2756e4b0 100755 --- a/ci/lib.sh +++ b/ci/lib.sh @@ -225,6 +225,12 @@ then GIT_TEST_OPTS="--write-junit-xml" JOBS=10 + case "$CI_OS_NAME" in + linux) runs_on_pool=ubuntu-latest;; + macos|osx) runs_on_pool=macos-latest;; + windows_nt) runs_on_pool=windows-latest;; + *) echo "Unhandled OS: $CI_OS_NAME" >&2; exit 1;; + esac elif test true = "$GITHUB_ACTIONS" then CI_TYPE=github-actions From d4d7862dc6105cc4f933291c56e4bdb31c52766d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 10 Jul 2022 01:15:08 +0200 Subject: [PATCH 116/306] ci: stop linking the `prove` cache It is not useful because we do not have any persisted directory anymore, not since dropping our Travis CI support. Signed-off-by: Johannes Schindelin --- ci/run-build-and-tests.sh | 5 ----- ci/run-test-slice.sh | 5 ----- 2 files changed, 10 deletions(-) diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 76667a1277720d..2026aae7595a54 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -5,11 +5,6 @@ . ${0%/*}/lib.sh -case "$CI_OS_NAME" in -windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";; -*) ln -s "$cache_dir/.prove" t/.prove;; -esac - run_tests=t case "$jobname" in diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh index e167e646f79e3d..0444c79c023c82 100755 --- a/ci/run-test-slice.sh +++ b/ci/run-test-slice.sh @@ -5,11 +5,6 @@ . ${0%/*}/lib.sh -case "$CI_OS_NAME" in -windows*) cmd //c mklink //j t\\.prove "$(cygpath -aw "$cache_dir/.prove")";; -*) ln -s "$cache_dir/.prove" t/.prove;; -esac - group "Run tests" make --quiet -C t T="$(cd t && ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh | tr '\n' ' ')" || From b4b814ecc737b1867ea16f4cd20cc05066f42647 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 11 Feb 2021 15:09:57 +0100 Subject: [PATCH 117/306] ci: reinstate Azure Pipelines support ... so that we can test a MinGit backport in a private repository (with GitHub Actions, minutes and parallel jobs are limited way more than with Azure Pipelines in private repositories). In this commit, we reinstate the exact version of `azure-pipelines.yml` as 6081d3898fe (ci: retire the Azure Pipelines definition, 2020-04-11) deleted. Naturally, many adjustments are required to make it work again. Some of the changes are actually outside of that file (such as the `runs_on_pool` changes that are needed in the Azure Pipelines part of `ci/lib.sh`) and they were made in the commits leading up to this here commit. However, other adjustments are required in the `azure-pipelines.yml` file itself, and for ease of review (read: to build confidence in those changes) they will be made in subsequent, individual commits that explain the intent, context, implementation and justification like every good commit message should do. Signed-off-by: Johannes Schindelin --- azure-pipelines.yml | 558 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 558 insertions(+) create mode 100644 azure-pipelines.yml diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000000000..11413f66f89662 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,558 @@ +variables: + Agent.Source.Git.ShallowFetchDepth: 1 + +jobs: +- job: windows_build + displayName: Windows Build + condition: succeeded() + pool: + vmImage: windows-latest + timeoutInMinutes: 240 + steps: + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no + cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ + } + displayName: 'Mount test-cache' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - powershell: | + $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" + $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id + $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl + (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip") + Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force + Remove-Item git-sdk-64-minimal.zip + + # Let Git ignore the SDK and the test-cache + "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude" + displayName: 'Download git-sdk-64-minimal' + - powershell: | + & git-sdk-64-minimal\usr\bin\bash.exe -lc @" + ci/make-test-artifacts.sh artifacts + "@ + if (!$?) { exit(1) } + displayName: Build + env: + HOME: $(Build.SourcesDirectory) + MSYSTEM: MINGW64 + DEVELOPER: 1 + NO_PERL: 1 + - task: PublishPipelineArtifact@0 + displayName: 'Publish Pipeline Artifact: test artifacts' + inputs: + artifactName: 'windows-artifacts' + targetPath: '$(Build.SourcesDirectory)\artifacts' + - task: PublishPipelineArtifact@0 + displayName: 'Publish Pipeline Artifact: git-sdk-64-minimal' + inputs: + artifactName: 'git-sdk-64-minimal' + targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal' + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" + } + displayName: 'Unmount test-cache' + condition: true + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + +- job: windows_test + displayName: Windows Test + dependsOn: windows_build + condition: succeeded() + pool: + vmImage: windows-latest + timeoutInMinutes: 240 + strategy: + parallel: 10 + steps: + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no + cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ + } + displayName: 'Mount test-cache' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: DownloadPipelineArtifact@0 + displayName: 'Download Pipeline Artifact: test artifacts' + inputs: + artifactName: 'windows-artifacts' + targetPath: '$(Build.SourcesDirectory)' + - task: DownloadPipelineArtifact@0 + displayName: 'Download Pipeline Artifact: git-sdk-64-minimal' + inputs: + artifactName: 'git-sdk-64-minimal' + targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal' + - powershell: | + & git-sdk-64-minimal\usr\bin\bash.exe -lc @" + test -f artifacts.tar.gz || { + echo No test artifacts found\; skipping >&2 + exit 0 + } + tar xf artifacts.tar.gz || exit 1 + + # Let Git ignore the SDK and the test-cache + printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude + + ci/run-test-slice.sh `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE || { + ci/print-test-failures.sh + exit 1 + } + "@ + if (!$?) { exit(1) } + displayName: 'Test (parallel)' + env: + HOME: $(Build.SourcesDirectory) + MSYSTEM: MINGW64 + NO_SVN_TESTS: 1 + GIT_TEST_SKIP_REBASE_P: 1 + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" + } + displayName: 'Unmount test-cache' + condition: true + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'windows' + platform: Windows + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-test-artifacts + +- job: vs_build + displayName: Visual Studio Build + condition: succeeded() + pool: + vmImage: windows-latest + timeoutInMinutes: 240 + steps: + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no + cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ + } + displayName: 'Mount test-cache' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - powershell: | + $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" + $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id + $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl + (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip") + Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force + Remove-Item git-sdk-64-minimal.zip + + # Let Git ignore the SDK and the test-cache + "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude" + displayName: 'Download git-sdk-64-minimal' + - powershell: | + & git-sdk-64-minimal\usr\bin\bash.exe -lc @" + make NDEBUG=1 DEVELOPER=1 vcxproj + "@ + if (!$?) { exit(1) } + displayName: Generate Visual Studio Solution + env: + HOME: $(Build.SourcesDirectory) + MSYSTEM: MINGW64 + DEVELOPER: 1 + NO_PERL: 1 + GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'" + - powershell: | + $urlbase = "https://dev.azure.com/git/git/_apis/build/builds" + $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id + $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[0].resource.downloadUrl + (New-Object Net.WebClient).DownloadFile($downloadUrl, "compat.zip") + Expand-Archive compat.zip -DestinationPath . -Force + Remove-Item compat.zip + displayName: 'Download vcpkg artifacts' + - task: MSBuild@1 + inputs: + solution: git.sln + platform: x64 + configuration: Release + maximumCpuCount: 4 + msbuildArguments: /p:PlatformToolset=v142 + - powershell: | + & compat\vcbuild\vcpkg_copy_dlls.bat release + if (!$?) { exit(1) } + & git-sdk-64-minimal\usr\bin\bash.exe -lc @" + mkdir -p artifacts && + eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts | grep ^tar)\" + "@ + if (!$?) { exit(1) } + displayName: Bundle artifact tar + env: + HOME: $(Build.SourcesDirectory) + MSYSTEM: MINGW64 + DEVELOPER: 1 + NO_PERL: 1 + MSVC: 1 + VCPKG_ROOT: $(Build.SourcesDirectory)\compat\vcbuild\vcpkg + - powershell: | + $tag = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-tag.txt").content + $version = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-version.txt").content + $url = "https://github.com/git-for-windows/git/releases/download/${tag}/PortableGit-${version}-64-bit.7z.exe" + (New-Object Net.WebClient).DownloadFile($url,"PortableGit.exe") + & .\PortableGit.exe -y -oartifacts\PortableGit + # Wait until it is unpacked + while (-not @(Remove-Item -ErrorAction SilentlyContinue PortableGit.exe; $?)) { sleep 1 } + displayName: Download & extract portable Git + - task: PublishPipelineArtifact@0 + displayName: 'Publish Pipeline Artifact: MSVC test artifacts' + inputs: + artifactName: 'vs-artifacts' + targetPath: '$(Build.SourcesDirectory)\artifacts' + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" + } + displayName: 'Unmount test-cache' + condition: true + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + +- job: vs_test + displayName: Visual Studio Test + dependsOn: vs_build + condition: succeeded() + pool: + vmImage: windows-latest + timeoutInMinutes: 240 + strategy: + parallel: 10 + steps: + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no + cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ + } + displayName: 'Mount test-cache' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: DownloadPipelineArtifact@0 + displayName: 'Download Pipeline Artifact: VS test artifacts' + inputs: + artifactName: 'vs-artifacts' + targetPath: '$(Build.SourcesDirectory)' + - powershell: | + & PortableGit\git-cmd.exe --command=usr\bin\bash.exe -lc @" + test -f artifacts.tar.gz || { + echo No test artifacts found\; skipping >&2 + exit 0 + } + tar xf artifacts.tar.gz || exit 1 + + # Let Git ignore the SDK and the test-cache + printf '%s\n' /PortableGit/ /test-cache/ >>.git/info/exclude + + cd t && + PATH=\"`$PWD/helper:`$PATH\" && + test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \ + `$(test-tool.exe path-utils slice-tests \ + `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE t[0-9]*.sh) + "@ + if (!$?) { exit(1) } + displayName: 'Test (parallel)' + env: + HOME: $(Build.SourcesDirectory) + MSYSTEM: MINGW64 + NO_SVN_TESTS: 1 + GIT_TEST_SKIP_REBASE_P: 1 + - powershell: | + if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { + cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" + } + displayName: 'Unmount test-cache' + condition: true + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'vs' + platform: Windows + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-vs-test-artifacts + +- job: linux_clang + displayName: linux-clang + condition: succeeded() + pool: + vmImage: ubuntu-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2-bin && + + export CC=clang || exit 1 + + ci/install-dependencies.sh || exit 1 + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'linux-clang' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-test-artifacts + +- job: linux_gcc + displayName: linux-gcc + condition: succeeded() + pool: + vmImage: ubuntu-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo add-apt-repository ppa:ubuntu-toolchain-r/test && + sudo apt-get update && + sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2 language-pack-is git-svn gcc-8 || exit 1 + + ci/install-dependencies.sh || exit 1 + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'linux-gcc' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-test-artifacts + +- job: osx_clang + displayName: osx-clang + condition: succeeded() + pool: + vmImage: macOS-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + export CC=clang + + ci/install-dependencies.sh || exit 1 + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'osx-clang' + platform: macOS + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-test-artifacts + +- job: osx_gcc + displayName: osx-gcc + condition: succeeded() + pool: + vmImage: macOS-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + ci/install-dependencies.sh || exit 1 + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'osx-gcc' + platform: macOS + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-test-artifacts + +- job: gettext_poison + displayName: GETTEXT_POISON + condition: succeeded() + pool: + vmImage: ubuntu-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev && + + export jobname=GETTEXT_POISON || exit 1 + + ci/run-build-and-tests.sh || { + ci/print-test-failures.sh + exit 1 + } + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-build-and-tests.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'gettext-poison' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-test-artifacts + +- job: linux32 + displayName: Linux32 + condition: succeeded() + pool: + vmImage: ubuntu-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + res=0 + sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS="$MAKEFLAGS" jobname=Linux32 bash -lxc ci/run-docker.sh || res=1 + + sudo chmod a+r t/out/TEST-*.xml + test ! -d t/failed-test-artifacts || sudo chmod a+r t/failed-test-artifacts + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || res=1 + exit $res + displayName: 'jobname=Linux32 ci/run-docker.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + - task: PublishTestResults@2 + displayName: 'Publish Test Results **/TEST-*.xml' + inputs: + mergeTestResults: true + testRunTitle: 'linux32' + platform: Linux + publishRunAttachments: false + condition: succeededOrFailed() + - task: PublishBuildArtifacts@1 + displayName: 'Publish trash directories of failed tests' + condition: failed() + inputs: + PathtoPublish: t/failed-test-artifacts + ArtifactName: failed-test-artifacts + +- job: static_analysis + displayName: StaticAnalysis + condition: succeeded() + pool: + vmImage: ubuntu-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo apt-get install -y coccinelle libcurl4-openssl-dev libssl-dev libexpat-dev gettext && + + export jobname=StaticAnalysis && + + ci/run-static-analysis.sh || exit 1 + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/run-static-analysis.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) + +- job: documentation + displayName: Documentation + condition: succeeded() + pool: + vmImage: ubuntu-latest + steps: + - bash: | + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 + + sudo apt-get update && + sudo apt-get install -y asciidoc xmlto asciidoctor docbook-xsl-ns && + + export ALREADY_HAVE_ASCIIDOCTOR=yes. && + export jobname=Documentation && + + ci/test-documentation.sh || exit 1 + + test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 + displayName: 'ci/test-documentation.sh' + env: + GITFILESHAREPWD: $(gitfileshare.pwd) From c71eebaae730da4f1c75bec64f87184eb7b62bf0 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Tue, 12 Apr 2022 19:53:33 +0000 Subject: [PATCH 118/306] mingw: $env:TERM="xterm-256color" for newer OSes For Windows builds >= 15063 set $env:TERM to "xterm-256color" instead of "cygwin" because they have a more capable console system that supports this. Also set $env:COLORTERM="truecolor" if unset. $env:TERM is initialized so that ANSI colors in color.c work, see 29a3963484 (Win32: patch Windows environment on startup, 2012-01-15). See git-for-windows/git#3629 regarding problems caused by always setting $env:TERM="cygwin". This is the same heuristic used by the Cygwin runtime. Signed-off-by: Rafael Kitover Signed-off-by: Johannes Schindelin --- compat/mingw.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..a90f8daa3c66df 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2795,9 +2795,20 @@ static void setup_windows_environment(void) convert_slashes(tmp); } - /* simulate TERM to enable auto-color (see color.c) */ - if (!getenv("TERM")) - setenv("TERM", "cygwin", 1); + + /* + * Make sure TERM is set up correctly to enable auto-color + * (see color.c .) Use "cygwin" for older OS releases which + * works correctly with MSYS2 utilities on older consoles. + */ + if (!getenv("TERM")) { + if ((GetVersion() >> 16) < 15063) + setenv("TERM", "cygwin", 0); + else { + setenv("TERM", "xterm-256color", 0); + setenv("COLORTERM", "truecolor", 0); + } + } /* calculate HOME if not set */ if (!getenv("HOME")) { From 404e0aa054f939e9c7bff435cd85591c8e11e5b7 Mon Sep 17 00:00:00 2001 From: Christopher Degawa Date: Sat, 28 May 2022 14:53:54 -0500 Subject: [PATCH 119/306] winansi: check result and Buffer before using Name NtQueryObject under Wine can return a success but fill out no name. In those situations, Wine will set Buffer to NULL, and set result to the sizeof(OBJECT_NAME_INFORMATION). Running a command such as echo "$(git.exe --version 2>/dev/null)" will crash due to a NULL pointer dereference when the code attempts to null terminate the buffer, although, weirdly, removing the subshell or redirecting stdout to a file will not trigger the crash. Code has been added to also check Buffer and Length to ensure the check is as robust as possible due to the current behavior being fragile at best, and could potentially change in the future This code is based on the behavior of NtQueryObject under wine and reactos. Signed-off-by: Christopher Degawa --- compat/winansi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compat/winansi.c b/compat/winansi.c index ac2ffb78691a7d..d28137a20b0bcc 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -575,6 +575,9 @@ static void detect_msys_tty(int fd) if (!NT_SUCCESS(NtQueryObject(h, ObjectNameInformation, buffer, sizeof(buffer) - 2, &result))) return; + if (result < sizeof(*nameinfo) || !nameinfo->Name.Buffer || + !nameinfo->Name.Length) + return; name = nameinfo->Name.Buffer; name[nameinfo->Name.Length / sizeof(*name)] = 0; From cadcb6dd24f6f03b295d483931bc4538cd84c983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E5=8D=93=E8=AF=86?= Date: Sun, 16 Jan 2022 03:38:33 +0800 Subject: [PATCH 120/306] Add config option `windows.appendAtomically` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Atomic append on windows is only supported on local disk files, and it may cause errors in other situations, e.g. network file system. If that is the case, this config option should be used to turn atomic append off. Co-Authored-By: Johannes Schindelin Signed-off-by: 孙卓识 Signed-off-by: Johannes Schindelin --- Documentation/config.txt | 2 ++ Documentation/config/windows.txt | 4 ++++ compat/mingw.c | 36 +++++++++++++++++++++++++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 Documentation/config/windows.txt diff --git a/Documentation/config.txt b/Documentation/config.txt index 8c0b3ed8075214..f1510a85205487 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -554,4 +554,6 @@ include::config/versionsort.txt[] include::config/web.txt[] +include::config/windows.txt[] + include::config/worktree.txt[] diff --git a/Documentation/config/windows.txt b/Documentation/config/windows.txt new file mode 100644 index 00000000000000..fdaaf1c65504f3 --- /dev/null +++ b/Documentation/config/windows.txt @@ -0,0 +1,4 @@ +windows.appendAtomically:: + By default, append atomic API is used on windows. But it works only with + local disk files, if you're working on a network file system, you should + set it false to turn it off. diff --git a/compat/mingw.c b/compat/mingw.c index 1d5b211b548dab..599bf6cd1c6388 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -21,6 +21,7 @@ #include "gettext.h" #define SECURITY_WIN32 #include +#include "../repository.h" #define HCAST(type, handle) ((type)(intptr_t)handle) @@ -614,6 +615,7 @@ static int is_local_named_pipe_path(const char *filename) int mingw_open (const char *filename, int oflags, ...) { + static int append_atomically = -1; typedef int (*open_fn_t)(wchar_t const *wfilename, int oflags, ...); va_list args; unsigned mode; @@ -630,7 +632,16 @@ int mingw_open (const char *filename, int oflags, ...) return -1; } - if ((oflags & O_APPEND) && !is_local_named_pipe_path(filename)) + /* + * Only set append_atomically to default value(1) when repo is initialized + * and fail to get config value + */ + if (append_atomically < 0 && the_repository && the_repository->commondir && + git_config_get_bool("windows.appendatomically", &append_atomically)) + append_atomically = 1; + + if (append_atomically && (oflags & O_APPEND) && + !is_local_named_pipe_path(filename)) open_fn = mingw_open_append; else if (!(oflags & ~(O_ACCMODE | O_NOINHERIT))) open_fn = mingw_open_existing; @@ -781,9 +792,28 @@ ssize_t mingw_write(int fd, const void *buf, size_t len) /* check if fd is a pipe */ HANDLE h = (HANDLE) _get_osfhandle(fd); - if (GetFileType(h) != FILE_TYPE_PIPE) + if (GetFileType(h) != FILE_TYPE_PIPE) { + if (orig == EINVAL) { + wchar_t path[MAX_PATH]; + DWORD ret = GetFinalPathNameByHandleW(h, path, + ARRAY_SIZE(path), 0); + UINT drive_type = ret > 0 && ret < ARRAY_SIZE(path) ? + GetDriveTypeW(path) : DRIVE_UNKNOWN; + + /* + * The default atomic append causes such an error on + * network file systems, in such a case, it should be + * turned off via config. + * + * `drive_type` of UNC path: DRIVE_NO_ROOT_DIR + */ + if (DRIVE_NO_ROOT_DIR == drive_type || DRIVE_REMOTE == drive_type) + warning("invalid write operation detected; you may try:\n" + "\n\tgit config windows.appendAtomically false"); + } + errno = orig; - else if (orig == EINVAL) + } else if (orig == EINVAL) errno = EPIPE; else { DWORD buf_size; From 3f98d1317a782a67d08d52663d7def6c40ceaa7d Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 4 Sep 2017 11:59:45 +0200 Subject: [PATCH 121/306] mingw: change core.fsyncObjectFiles = 1 by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the documentation of said setting: This boolean will enable fsync() when writing object files. This is a total waste of time and effort on a filesystem that orders data writes properly, but can be useful for filesystems that do not use journalling (traditional UNIX filesystems) or that only journal metadata and not file contents (OS X’s HFS+, or Linux ext3 with "data=writeback"). The most common file system on Windows (NTFS) does not guarantee that order, therefore a sudden loss of power (or any other event causing an unclean shutdown) would cause corrupt files (i.e. files filled with NULs). Therefore we need to change the default. Note that the documentation makes it sound as if this causes really bad performance. In reality, writing loose objects is something that is done only rarely, and only a handful of files at a time. Signed-off-by: Johannes Schindelin --- compat/mingw.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compat/mingw.c b/compat/mingw.c index 599bf6cd1c6388..59b8aaa004d5f7 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -21,6 +21,7 @@ #include "gettext.h" #define SECURITY_WIN32 #include +#include "../write-or-die.h" #include "../repository.h" #define HCAST(type, handle) ((type)(intptr_t)handle) @@ -3283,6 +3284,7 @@ int wmain(int argc, const wchar_t **wargv) #endif maybe_redirect_std_handles(); + fsync_object_files = 1; /* determine size of argv and environ conversion buffer */ maxlen = wcslen(wargv[0]); From ebc3f7bc3716b10b428f49c479964c8e04dc4953 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 29 Mar 2022 13:42:19 +0200 Subject: [PATCH 122/306] azure-pipeline: drop the `GETTEXT_POISON` job This is a follow-up to 6c280b4142 (ci: remove GETTEXT_POISON jobs, 2021-01-20) after reinstating the Azure Pipeline. Signed-off-by: Johannes Schindelin --- azure-pipelines.yml | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 11413f66f89662..7b20ad2667fdc9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -441,44 +441,6 @@ jobs: PathtoPublish: t/failed-test-artifacts ArtifactName: failed-test-artifacts -- job: gettext_poison - displayName: GETTEXT_POISON - condition: succeeded() - pool: - vmImage: ubuntu-latest - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - sudo apt-get update && - sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev && - - export jobname=GETTEXT_POISON || exit 1 - - ci/run-build-and-tests.sh || { - ci/print-test-failures.sh - exit 1 - } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 - displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'gettext-poison' - platform: Linux - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - - job: linux32 displayName: Linux32 condition: succeeded() From 3ec9280c324f38acc1f47fd3009f24a468dde115 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 29 Mar 2022 12:28:12 +0200 Subject: [PATCH 123/306] azure-pipeline: stop hard-coding `apt-get` calls We have `ci/install-dependencies.sh` for that. Incidentally, this avoids the following error in the linux-* jobs: The following packages have unmet dependencies: git-email : Depends: git (< 1:2.25.1-.) but 1:2.35.1-0ppa1~ubuntu20.04.1 is to be installed Recommends: libemail-valid-perl but it is not going to be installed Signed-off-by: Johannes Schindelin --- azure-pipelines.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7b20ad2667fdc9..e311d3055e5eca 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -303,9 +303,6 @@ jobs: - bash: | test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - sudo apt-get update && - sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2-bin && - export CC=clang || exit 1 ci/install-dependencies.sh || exit 1 @@ -342,10 +339,6 @@ jobs: - bash: | test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - sudo add-apt-repository ppa:ubuntu-toolchain-r/test && - sudo apt-get update && - sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2 language-pack-is git-svn gcc-8 || exit 1 - ci/install-dependencies.sh || exit 1 ci/run-build-and-tests.sh || { ci/print-test-failures.sh From 209f633b293cb5405e7b2b500ffe3c01fd76fbef Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 10 Jul 2022 00:02:30 +0200 Subject: [PATCH 124/306] azure-pipeline: drop the code to write to/read from a file share We haven't used this feature in ages, we don't actually need to. Signed-off-by: Johannes Schindelin --- azure-pipelines.yml | 105 -------------------------------------------- 1 file changed, 105 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e311d3055e5eca..94bc7ea1f51b47 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,14 +9,6 @@ jobs: vmImage: windows-latest timeoutInMinutes: 240 steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - powershell: | $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id @@ -49,14 +41,6 @@ jobs: inputs: artifactName: 'git-sdk-64-minimal' targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal' - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - job: windows_test displayName: Windows Test @@ -68,14 +52,6 @@ jobs: strategy: parallel: 10 steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: DownloadPipelineArtifact@0 displayName: 'Download Pipeline Artifact: test artifacts' inputs: @@ -109,14 +85,6 @@ jobs: MSYSTEM: MINGW64 NO_SVN_TESTS: 1 GIT_TEST_SKIP_REBASE_P: 1 - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -139,14 +107,6 @@ jobs: vmImage: windows-latest timeoutInMinutes: 240 steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - powershell: | $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id @@ -215,14 +175,6 @@ jobs: inputs: artifactName: 'vs-artifacts' targetPath: '$(Build.SourcesDirectory)\artifacts' - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - job: vs_test displayName: Visual Studio Test @@ -234,14 +186,6 @@ jobs: strategy: parallel: 10 steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: DownloadPipelineArtifact@0 displayName: 'Download Pipeline Artifact: VS test artifacts' inputs: @@ -271,14 +215,6 @@ jobs: MSYSTEM: MINGW64 NO_SVN_TESTS: 1 GIT_TEST_SKIP_REBASE_P: 1 - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -301,8 +237,6 @@ jobs: vmImage: ubuntu-latest steps: - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - export CC=clang || exit 1 ci/install-dependencies.sh || exit 1 @@ -310,11 +244,7 @@ jobs: ci/print-test-failures.sh exit 1 } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -337,18 +267,12 @@ jobs: vmImage: ubuntu-latest steps: - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - ci/install-dependencies.sh || exit 1 ci/run-build-and-tests.sh || { ci/print-test-failures.sh exit 1 } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -371,8 +295,6 @@ jobs: vmImage: macOS-latest steps: - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - export CC=clang ci/install-dependencies.sh || exit 1 @@ -380,11 +302,7 @@ jobs: ci/print-test-failures.sh exit 1 } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -407,18 +325,12 @@ jobs: vmImage: macOS-latest steps: - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - ci/install-dependencies.sh || exit 1 ci/run-build-and-tests.sh || { ci/print-test-failures.sh exit 1 } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -441,19 +353,14 @@ jobs: vmImage: ubuntu-latest steps: - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - res=0 sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS="$MAKEFLAGS" jobname=Linux32 bash -lxc ci/run-docker.sh || res=1 sudo chmod a+r t/out/TEST-*.xml test ! -d t/failed-test-artifacts || sudo chmod a+r t/failed-test-artifacts - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || res=1 exit $res displayName: 'jobname=Linux32 ci/run-docker.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -476,19 +383,13 @@ jobs: vmImage: ubuntu-latest steps: - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - sudo apt-get update && sudo apt-get install -y coccinelle libcurl4-openssl-dev libssl-dev libexpat-dev gettext && export jobname=StaticAnalysis && ci/run-static-analysis.sh || exit 1 - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 displayName: 'ci/run-static-analysis.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - job: documentation displayName: Documentation @@ -497,8 +398,6 @@ jobs: vmImage: ubuntu-latest steps: - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - sudo apt-get update && sudo apt-get install -y asciidoc xmlto asciidoctor docbook-xsl-ns && @@ -506,8 +405,4 @@ jobs: export jobname=Documentation && ci/test-documentation.sh || exit 1 - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 displayName: 'ci/test-documentation.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) From ac9ce7cd5c4a44bfc373aa9cd15417896e5d6e71 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 10 Jul 2022 00:14:53 +0200 Subject: [PATCH 125/306] azure-pipeline: use partial clone/parallel checkout to initialize minimal-sdk The Azure Pipeline `git-sdk-64-minimal` was retired... Signed-off-by: Johannes Schindelin --- azure-pipelines.yml | 125 +++++++++++++++++++------------------------- 1 file changed, 55 insertions(+), 70 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 94bc7ea1f51b47..f11f1342cd080b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,5 +1,6 @@ variables: Agent.Source.Git.ShallowFetchDepth: 1 + GIT_CONFIG_PARAMETERS: "'checkout.workers=56' 'user.name=CI' 'user.email=ci@git'" jobs: - job: windows_build @@ -9,28 +10,24 @@ jobs: vmImage: windows-latest timeoutInMinutes: 240 steps: - - powershell: | - $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" - $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id - $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl - (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip") - Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force - Remove-Item git-sdk-64-minimal.zip - + - bash: git clone --bare --depth=1 --filter=blob:none --single-branch -b main https://github.com/git-for-windows/git-sdk-64 + displayName: 'clone git-sdk-64' + - bash: git clone --depth=1 --single-branch -b main https://github.com/git-for-windows/build-extra + displayName: 'clone build-extra' + - bash: sh -x ./build-extra/please.sh create-sdk-artifact --sdk=git-sdk-64.git --out=git-sdk-64-minimal minimal-sdk + displayName: 'build git-sdk-64-minimal-sdk' + - bash: | # Let Git ignore the SDK and the test-cache - "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude" - displayName: 'Download git-sdk-64-minimal' - - powershell: | - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - ci/make-test-artifacts.sh artifacts - "@ - if (!$?) { exit(1) } + printf "%s\n" /git-sdk-64.git/ /build-extra/ /git-sdk-64-minimal/ /test-cache/ >>'.git/info/exclude' + displayName: 'Ignore untracked directories' + - bash: ci/make-test-artifacts.sh artifacts displayName: Build env: HOME: $(Build.SourcesDirectory) MSYSTEM: MINGW64 DEVELOPER: 1 NO_PERL: 1 + PATH: "$(Build.SourcesDirectory)\\git-sdk-64-minimal\\mingw64\\bin;$(Build.SourcesDirectory)\\git-sdk-64-minimal\\usr\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\system32\\wbem" - task: PublishPipelineArtifact@0 displayName: 'Publish Pipeline Artifact: test artifacts' inputs: @@ -62,29 +59,27 @@ jobs: inputs: artifactName: 'git-sdk-64-minimal' targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal' - - powershell: | - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - test -f artifacts.tar.gz || { - echo No test artifacts found\; skipping >&2 - exit 0 - } - tar xf artifacts.tar.gz || exit 1 + - bash: | + test -f artifacts.tar.gz || { + echo No test artifacts found\; skipping >&2 + exit 0 + } + tar xf artifacts.tar.gz || exit 1 - # Let Git ignore the SDK and the test-cache - printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude + # Let Git ignore the SDK and the test-cache + printf '%s\n' /git-sdk-64.git/ /build-extra/ /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude - ci/run-test-slice.sh `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE || { - ci/print-test-failures.sh - exit 1 - } - "@ - if (!$?) { exit(1) } + ci/run-test-slice.sh $SYSTEM_JOBPOSITIONINPHASE $SYSTEM_TOTALJOBSINPHASE || { + ci/print-test-failures.sh + exit 1 + } displayName: 'Test (parallel)' env: HOME: $(Build.SourcesDirectory) MSYSTEM: MINGW64 NO_SVN_TESTS: 1 GIT_TEST_SKIP_REBASE_P: 1 + PATH: "$(Build.SourcesDirectory)\\git-sdk-64-minimal\\mingw64\\bin;$(Build.SourcesDirectory)\\git-sdk-64-minimal\\usr\\bin\\core_perl;$(Build.SourcesDirectory)\\git-sdk-64-minimal\\usr\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\system32\\wbem" - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: @@ -107,29 +102,24 @@ jobs: vmImage: windows-latest timeoutInMinutes: 240 steps: - - powershell: | - $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" - $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id - $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl - (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip") - Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force - Remove-Item git-sdk-64-minimal.zip - + - bash: git clone --bare --depth=1 --filter=blob:none --single-branch -b main https://github.com/git-for-windows/git-sdk-64 + displayName: 'clone git-sdk-64' + - bash: git clone --depth=1 --single-branch -b main https://github.com/git-for-windows/build-extra + displayName: 'clone build-extra' + - bash: sh -x ./build-extra/please.sh create-sdk-artifact --sdk=git-sdk-64.git --out=git-sdk-64-minimal minimal-sdk + displayName: 'build git-sdk-64-minimal-sdk' + - bash: | # Let Git ignore the SDK and the test-cache - "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude" - displayName: 'Download git-sdk-64-minimal' - - powershell: | - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - make NDEBUG=1 DEVELOPER=1 vcxproj - "@ - if (!$?) { exit(1) } + printf "%s\n" /git-sdk-64-minimal/ /test-cache/ >>'.git/info/exclude' + displayName: 'Ignore untracked directories' + - bash: make NDEBUG=1 DEVELOPER=1 vcxproj displayName: Generate Visual Studio Solution env: HOME: $(Build.SourcesDirectory) MSYSTEM: MINGW64 DEVELOPER: 1 NO_PERL: 1 - GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'" + PATH: "$(Build.SourcesDirectory)\\git-sdk-64-minimal\\mingw64\\bin;$(Build.SourcesDirectory)\\git-sdk-64-minimal\\usr\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\system32\\wbem" - powershell: | $urlbase = "https://dev.azure.com/git/git/_apis/build/builds" $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id @@ -145,14 +135,10 @@ jobs: configuration: Release maximumCpuCount: 4 msbuildArguments: /p:PlatformToolset=v142 - - powershell: | - & compat\vcbuild\vcpkg_copy_dlls.bat release - if (!$?) { exit(1) } - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - mkdir -p artifacts && - eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts | grep ^tar)\" - "@ - if (!$?) { exit(1) } + - bash: | + ./compat/vcbuild/vcpkg_copy_dlls.bat release && + mkdir -p artifacts && + eval "$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts | grep ^tar)" displayName: Bundle artifact tar env: HOME: $(Build.SourcesDirectory) @@ -161,6 +147,7 @@ jobs: NO_PERL: 1 MSVC: 1 VCPKG_ROOT: $(Build.SourcesDirectory)\compat\vcbuild\vcpkg + PATH: "$(Build.SourcesDirectory)\\git-sdk-64-minimal\\mingw64\\bin;$(Build.SourcesDirectory)\\git-sdk-64-minimal\\usr\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\system32\\wbem" - powershell: | $tag = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-tag.txt").content $version = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-version.txt").content @@ -191,30 +178,28 @@ jobs: inputs: artifactName: 'vs-artifacts' targetPath: '$(Build.SourcesDirectory)' - - powershell: | - & PortableGit\git-cmd.exe --command=usr\bin\bash.exe -lc @" - test -f artifacts.tar.gz || { - echo No test artifacts found\; skipping >&2 - exit 0 - } - tar xf artifacts.tar.gz || exit 1 + - bash: | + test -f artifacts.tar.gz || { + echo No test artifacts found\; skipping >&2 + exit 0 + } + tar xf artifacts.tar.gz || exit 1 - # Let Git ignore the SDK and the test-cache - printf '%s\n' /PortableGit/ /test-cache/ >>.git/info/exclude + # Let Git ignore the SDK and the test-cache + printf '%s\n' /PortableGit/ /test-cache/ >>.git/info/exclude - cd t && - PATH=\"`$PWD/helper:`$PATH\" && - test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \ - `$(test-tool.exe path-utils slice-tests \ - `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE t[0-9]*.sh) - "@ - if (!$?) { exit(1) } + cd t && + PATH="$PWD/helper:$PATH" && + test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \ + $(test-tool.exe path-utils slice-tests \ + $SYSTEM_JOBPOSITIONINPHASE $SYSTEM_TOTALJOBSINPHASE t[0-9]*.sh) displayName: 'Test (parallel)' env: HOME: $(Build.SourcesDirectory) MSYSTEM: MINGW64 NO_SVN_TESTS: 1 GIT_TEST_SKIP_REBASE_P: 1 + PATH: "$(Build.SourcesDirectory)\\PortableGit\\mingw64\\bin;$(Build.SourcesDirectory)\\PortableGit\\usr\\bin;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\system32\\wbem" - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: From a671d4136f6a6221f4bd7cb96a1be59ac970e651 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 10 Jul 2022 00:52:40 +0200 Subject: [PATCH 126/306] azure-pipeline: downcase the job name of the `Linux32` job These many refactorings in Git sure are gifts that keep on giving. Signed-off-by: Johannes Schindelin --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f11f1342cd080b..21ee5a463380d6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -339,13 +339,13 @@ jobs: steps: - bash: | res=0 - sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS="$MAKEFLAGS" jobname=Linux32 bash -lxc ci/run-docker.sh || res=1 + sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS="$MAKEFLAGS" jobname=linux32 bash -lxc ci/run-docker.sh || res=1 sudo chmod a+r t/out/TEST-*.xml test ! -d t/failed-test-artifacts || sudo chmod a+r t/failed-test-artifacts exit $res - displayName: 'jobname=Linux32 ci/run-docker.sh' + displayName: 'jobname=linux32 ci/run-docker.sh' - task: PublishTestResults@2 displayName: 'Publish Test Results **/TEST-*.xml' inputs: From 7e228155987174a8907bf5dfa7372d0dbc3a4444 Mon Sep 17 00:00:00 2001 From: Taylor Blau Date: Mon, 8 Feb 2021 16:22:34 -0500 Subject: [PATCH 127/306] azure-pipeline: run static-analysis on jammy This is inspired by d051ed77ee6 (.github/workflows/main.yml: run static-analysis on bionic, 2021-02-08) and by ef46584831 (ci: update 'static-analysis' to Ubuntu 22.04, 2022-08-23), adapted to the Azure Pipeline. When Azure Pipelines' build agents transitioned 'ubuntu-latest' from 18.04 to 20.04, it broke our `static-analysis` job, since Coccinelle was not madeavailable on Ubuntu focal (it is only available in the universe suite). This is not an issue with Ubuntu 22.04, but we will only know whether it is an issue with 24.04 when _that_ comes out. So let's play it safe and pin the `static_analysis` job to the latest Ubuntu version that we know to offer a working Coccinelle package. Signed-off-by: Taylor Blau Signed-off-by: Junio C Hamano Signed-off-by: Johannes Schindelin --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 21ee5a463380d6..fed26341d49a4a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -365,7 +365,7 @@ jobs: displayName: StaticAnalysis condition: succeeded() pool: - vmImage: ubuntu-latest + vmImage: ubuntu-22.04 steps: - bash: | sudo apt-get update && From a4cc607692deb5ccaa91779c1056a56cde400e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20A=C3=9Fhauer?= Date: Sun, 10 Jul 2022 11:27:25 +0200 Subject: [PATCH 128/306] MinGW: link as terminal server aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Whith Windows 2000, Microsoft introduced a flag to the PE header to mark executables as "terminal server aware". Windows terminal servers provide a redirected Windows directory and redirected registry hives when launching legacy applications without this flag set. Since we do not use any INI files in the Windows directory and don't write to the registry, we don't need this additional preparation. Telling the OS that we don't need this should provide slightly improved startup times in terminal server environments. When building for supported Windows Versions with MSVC the /TSAWARE linker flag is automatically set, but MinGW requires us to set the --tsaware flag manually. This partially addresses https://github.com/git-for-windows/git/issues/3935. Signed-off-by: Matthias Aßhauer --- config.mak.uname | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.mak.uname b/config.mak.uname index b12d4e168ae119..1736c4f0116e75 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -691,7 +691,7 @@ ifeq ($(uname_S),MINGW) DEFAULT_HELP_FORMAT = html HAVE_PLATFORM_PROCINFO = YesPlease CSPRNG_METHOD = rtlgenrandom - BASIC_LDFLAGS += -municode + BASIC_LDFLAGS += -municode -Wl,--tsaware COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/winansi.o \ From 4c4a0d5e63efb442b35fd9cc86633a9d9ebc7f55 Mon Sep 17 00:00:00 2001 From: Kiel Hurley Date: Wed, 2 Nov 2022 22:56:16 +1300 Subject: [PATCH 129/306] Fix Windows version resources Add FileVersion, which is a required field As not all required fields were present, none were being included Fixes #4090 Signed-off-by: Kiel Hurley --- git.rc.in | 1 + 1 file changed, 1 insertion(+) diff --git a/git.rc.in b/git.rc.in index e69444eef3f0c5..460ea39561b87f 100644 --- a/git.rc.in +++ b/git.rc.in @@ -12,6 +12,7 @@ BEGIN VALUE "OriginalFilename", "git.exe\0" VALUE "ProductName", "Git\0" VALUE "ProductVersion", "@GIT_VERSION@\0" + VALUE "FileVersion", "@GIT_VERSION@\0" END END From 231a570c1766a2d3188b60134eb17169ea48a248 Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Tue, 4 Oct 2022 09:58:10 +0200 Subject: [PATCH 130/306] bswap.h: add support for built-in bswap functions Newer compiler versions, like GCC 10 and Clang 12, have built-in functions for bswap32 and bswap64. This comes in handy, for example, when targeting CLANGARM64 on Windows, which would not be supported without this logic. Signed-off-by: Dennis Ameling --- compat/bswap.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/compat/bswap.h b/compat/bswap.h index 512f6f4b9937c8..a443e99eef2f1c 100644 --- a/compat/bswap.h +++ b/compat/bswap.h @@ -35,7 +35,19 @@ static inline uint64_t default_bswap64(uint64_t val) #undef bswap32 #undef bswap64 -#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +/** + * __has_builtin is available since Clang 10 and GCC 10. + * Below is a fallback for older compilers. + */ +#ifndef __has_builtin + #define __has_builtin(x) 0 +#endif + +#if __has_builtin(__builtin_bswap32) && __has_builtin(__builtin_bswap64) +#define bswap32(x) __builtin_bswap32((x)) +#define bswap64(x) __builtin_bswap64((x)) + +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) #define bswap32 git_bswap32 static inline uint32_t git_bswap32(uint32_t x) From e80721de6fa06892ae4cff0f02708211aa9ad5b9 Mon Sep 17 00:00:00 2001 From: Dennis Ameling Date: Tue, 4 Oct 2022 09:59:32 +0200 Subject: [PATCH 131/306] config.mak.uname: add support for clangarm64 CLANGARM64 is a relatively new MSYSTEM added by the MSYS2 team. In order to have Git build correctly for this platform, let's add some configuration for it to config.mak.uname. Signed-off-by: Dennis Ameling --- config.mak.uname | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config.mak.uname b/config.mak.uname index b12d4e168ae119..1e5d89f1aa4a73 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -724,6 +724,10 @@ ifeq ($(uname_S),MINGW) prefix = /mingw64 HOST_CPU = x86_64 BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup + else ifeq (CLANGARM64,$(MSYSTEM)) + prefix = /clangarm64 + HOST_CPU = aarch64 + BASIC_LDFLAGS += -Wl,--pic-executable,-e,mainCRTStartup else COMPAT_CFLAGS += -D_USE_32BIT_TIME_T BASIC_LDFLAGS += -Wl,--large-address-aware From 12dfa6893cb71cb003e167425ac4c2aa39cda2d5 Mon Sep 17 00:00:00 2001 From: Andrey Zabavnikov Date: Fri, 28 Oct 2022 17:12:06 +0300 Subject: [PATCH 132/306] status: fix for old-style submodules with commondir In f9b7573f6b00 (repository: free fields before overwriting them, 2017-09-05), Git was taught to release memory before overwriting it, but 357a03ebe9e0 (repository.c: move env-related setup code back to environment.c, 2018-03-03) changed the code so that it would not _always_ be overwritten. As a consequence, the `commondir` attribute would point to already-free()d memory. This seems not to cause problems in core Git, but there are add-on patches in Git for Windows where the `commondir` attribute is subsequently used and causing invalid memory accesses e.g. in setups containing old-style submodules (i.e. the ones with a `.git` directory within theirs worktrees) that have `commondir` configured. This fixes https://github.com/git-for-windows/git/pull/4083. Signed-off-by: Andrey Zabavnikov Signed-off-by: Johannes Schindelin --- repository.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repository.c b/repository.c index 1a6a62bbd03a5d..468fe580a5d6b6 100644 --- a/repository.c +++ b/repository.c @@ -136,7 +136,7 @@ static void repo_set_commondir(struct repository *repo, { struct strbuf sb = STRBUF_INIT; - free(repo->commondir); + FREE_AND_NULL(repo->commondir); if (commondir) { repo->different_commondir = 1; From d9aa4c7614c0f91fac2a6427ae18e54b53fdfb75 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 27 Jan 2023 08:55:21 +0100 Subject: [PATCH 133/306] windows: skip linking `git-` for built-ins It is merely a historical wart that, say, `git-commit` exists in the `libexec/git-core/` directory, a tribute to the original idea to let Git be essentially a bunch of Unix shell scripts revolving around very few "plumbing" (AKA low-level) commands. Git has evolved a lot from there. These days, most of Git's functionality is contained within the `git` executable, in the form of "built-in" commands. To accommodate for scripts that use the "dashed" form of Git commands, even today, Git provides hard-links that make the `git` executable available as, say, `git-commit`, just in case that an old script has not been updated to invoke `git commit`. Those hard-links do not come cheap: they take about half a minute for every build of Git on Windows, they are mistaken for taking up huge amounts of space by some Windows Explorer versions that do not understand hard-links, and therefore many a "bug" report had to be addressed. The "dashed form" has been officially deprecated in Git version 1.5.4, which was released on February 2nd, 2008, i.e. a very long time ago. This deprecation was never finalized by skipping these hard-links, but we can start the process now, in Git for Windows. Signed-off-by: Johannes Schindelin --- config.mak.uname | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config.mak.uname b/config.mak.uname index b12d4e168ae119..05260d0f1885de 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -485,6 +485,7 @@ ifeq ($(uname_S),Windows) NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease DEFAULT_HELP_FORMAT = html + SKIP_DASHED_BUILT_INS = YabbaDabbaDoo ifeq (/mingw64,$(subst 32,64,$(prefix))) # Move system config into top-level /etc/ ETC_GITCONFIG = ../etc/gitconfig @@ -677,6 +678,7 @@ ifeq ($(uname_S),MINGW) FSMONITOR_DAEMON_BACKEND = win32 FSMONITOR_OS_SETTINGS = win32 + SKIP_DASHED_BUILT_INS = YabbaDabbaDoo RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease From 58f6351bfc5bc7b9f43ff36767190990f46a8813 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 6 May 2023 22:26:15 +0200 Subject: [PATCH 134/306] http: optionally load libcurl lazily This compile-time option allows to ask Git to load libcurl dynamically at runtime. Together with a follow-up patch that optionally overrides the file name depending on the `http.sslBackend` setting, this kicks open the door for installing multiple libcurl flavors side by side, and load the one corresponding to the (runtime-)configured SSL/TLS backend. Signed-off-by: Johannes Schindelin --- Makefile | 28 +++- compat/lazyload-curl.c | 354 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 375 insertions(+), 7 deletions(-) create mode 100644 compat/lazyload-curl.c diff --git a/Makefile b/Makefile index 97e8385b6643b9..4e96a2ee9b85db 100644 --- a/Makefile +++ b/Makefile @@ -471,6 +471,11 @@ include shared.mak # # CURL_LDFLAGS=-lcurl # +# Define LAZYLOAD_LIBCURL to dynamically load the libcurl; This can be useful +# if Multiple libcurl versions exist (with different file names) that link to +# various SSL/TLS backends, to support the `http.sslBackend` runtime switch in +# such a scenario. +# # === Optional library: libpcre2 === # # Define USE_LIBPCRE if you have and want to use libpcre. Various @@ -1647,10 +1652,19 @@ else CURL_LIBCURL = endif - ifndef CURL_LDFLAGS - CURL_LDFLAGS = $(eval CURL_LDFLAGS := $$(shell $$(CURL_CONFIG) --libs))$(CURL_LDFLAGS) + ifdef LAZYLOAD_LIBCURL + LAZYLOAD_LIBCURL_OBJ = compat/lazyload-curl.o + OBJECTS += $(LAZYLOAD_LIBCURL_OBJ) + # The `CURL_STATICLIB` constant must be defined to avoid seeing the functions + # declared as DLL imports + CURL_CFLAGS = -DCURL_STATICLIB + CURL_LIBCURL = -ldl + else + ifndef CURL_LDFLAGS + CURL_LDFLAGS = $(eval CURL_LDFLAGS := $$(shell $$(CURL_CONFIG) --libs))$(CURL_LDFLAGS) + endif + CURL_LIBCURL += $(CURL_LDFLAGS) endif - CURL_LIBCURL += $(CURL_LDFLAGS) ifndef CURL_CFLAGS CURL_CFLAGS = $(eval CURL_CFLAGS := $$(shell $$(CURL_CONFIG) --cflags))$(CURL_CFLAGS) @@ -1671,7 +1685,7 @@ else endif ifdef USE_CURL_FOR_IMAP_SEND BASIC_CFLAGS += -DUSE_CURL_FOR_IMAP_SEND - IMAP_SEND_BUILDDEPS = http.o + IMAP_SEND_BUILDDEPS = http.o $(LAZYLOAD_LIBCURL_OBJ) IMAP_SEND_LDFLAGS += $(CURL_LIBCURL) endif ifndef NO_EXPAT @@ -2849,10 +2863,10 @@ git-imap-send$X: imap-send.o $(IMAP_SEND_BUILDDEPS) GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(IMAP_SEND_LDFLAGS) $(LIBS) -git-http-fetch$X: http.o http-walker.o http-fetch.o GIT-LDFLAGS $(GITLIBS) +git-http-fetch$X: http.o http-walker.o http-fetch.o $(LAZYLOAD_LIBCURL_OBJ) GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(LIBS) -git-http-push$X: http.o http-push.o GIT-LDFLAGS $(GITLIBS) +git-http-push$X: http.o http-push.o $(LAZYLOAD_LIBCURL_OBJ) GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) @@ -2862,7 +2876,7 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY) ln -s $< $@ 2>/dev/null || \ cp $< $@ -$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS) +$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(LAZYLOAD_LIBCURL_OBJ) GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) $(LIBS) diff --git a/compat/lazyload-curl.c b/compat/lazyload-curl.c new file mode 100644 index 00000000000000..19aa2b6d4b6942 --- /dev/null +++ b/compat/lazyload-curl.c @@ -0,0 +1,354 @@ +#include "../git-compat-util.h" +#include "../git-curl-compat.h" +#include + +/* + * The ABI version of libcurl is encoded in its shared libraries' file names. + * This ABI version has not changed since October 2006 and is unlikely to be + * changed in the future. See https://curl.se/libcurl/abi.html for details. + */ +#define LIBCURL_ABI_VERSION "4" + +typedef void (*func_t)(void); + +#ifdef __APPLE__ +#define LIBCURL_FILE_NAME(base) base "." LIBCURL_ABI_VERSION ".dylib" +#else +#define LIBCURL_FILE_NAME(base) base ".so." LIBCURL_ABI_VERSION +#endif + +static void *load_library(const char *name) +{ + return dlopen(name, RTLD_LAZY); +} + +static func_t load_function(void *handle, const char *name) +{ + /* + * Casting the return value of `dlsym()` to a function pointer is + * explicitly allowed in recent POSIX standards, but GCC complains + * about this in pedantic mode nevertheless. For more about this issue, + * see https://stackoverflow.com/q/31526876/1860823 and + * http://stackoverflow.com/a/36385690/1905491. + */ + func_t f; + *(void **)&f = dlsym(handle, name); + return f; +} + +typedef struct curl_version_info_data *(*curl_version_info_type)(CURLversion version); +static curl_version_info_type curl_version_info_func; + +typedef char *(*curl_easy_escape_type)(CURL *handle, const char *string, int length); +static curl_easy_escape_type curl_easy_escape_func; + +typedef void (*curl_free_type)(void *p); +static curl_free_type curl_free_func; + +typedef CURLcode (*curl_global_init_type)(long flags); +static curl_global_init_type curl_global_init_func; + +typedef CURLsslset (*curl_global_sslset_type)(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail); +static curl_global_sslset_type curl_global_sslset_func; + +typedef void (*curl_global_cleanup_type)(void); +static curl_global_cleanup_type curl_global_cleanup_func; + +typedef struct curl_slist *(*curl_slist_append_type)(struct curl_slist *list, const char *data); +static curl_slist_append_type curl_slist_append_func; + +typedef void (*curl_slist_free_all_type)(struct curl_slist *list); +static curl_slist_free_all_type curl_slist_free_all_func; + +typedef const char *(*curl_easy_strerror_type)(CURLcode error); +static curl_easy_strerror_type curl_easy_strerror_func; + +typedef CURLM *(*curl_multi_init_type)(void); +static curl_multi_init_type curl_multi_init_func; + +typedef CURLMcode (*curl_multi_add_handle_type)(CURLM *multi_handle, CURL *curl_handle); +static curl_multi_add_handle_type curl_multi_add_handle_func; + +typedef CURLMcode (*curl_multi_remove_handle_type)(CURLM *multi_handle, CURL *curl_handle); +static curl_multi_remove_handle_type curl_multi_remove_handle_func; + +typedef CURLMcode (*curl_multi_fdset_type)(CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd); +static curl_multi_fdset_type curl_multi_fdset_func; + +typedef CURLMcode (*curl_multi_perform_type)(CURLM *multi_handle, int *running_handles); +static curl_multi_perform_type curl_multi_perform_func; + +typedef CURLMcode (*curl_multi_cleanup_type)(CURLM *multi_handle); +static curl_multi_cleanup_type curl_multi_cleanup_func; + +typedef CURLMsg *(*curl_multi_info_read_type)(CURLM *multi_handle, int *msgs_in_queue); +static curl_multi_info_read_type curl_multi_info_read_func; + +typedef const char *(*curl_multi_strerror_type)(CURLMcode error); +static curl_multi_strerror_type curl_multi_strerror_func; + +typedef CURLMcode (*curl_multi_timeout_type)(CURLM *multi_handle, long *milliseconds); +static curl_multi_timeout_type curl_multi_timeout_func; + +typedef CURL *(*curl_easy_init_type)(void); +static curl_easy_init_type curl_easy_init_func; + +typedef CURLcode (*curl_easy_perform_type)(CURL *curl); +static curl_easy_perform_type curl_easy_perform_func; + +typedef void (*curl_easy_cleanup_type)(CURL *curl); +static curl_easy_cleanup_type curl_easy_cleanup_func; + +typedef CURL *(*curl_easy_duphandle_type)(CURL *curl); +static curl_easy_duphandle_type curl_easy_duphandle_func; + +typedef CURLcode (*curl_easy_getinfo_long_type)(CURL *curl, CURLINFO info, long *value); +static curl_easy_getinfo_long_type curl_easy_getinfo_long_func; + +typedef CURLcode (*curl_easy_getinfo_pointer_type)(CURL *curl, CURLINFO info, void **value); +static curl_easy_getinfo_pointer_type curl_easy_getinfo_pointer_func; + +typedef CURLcode (*curl_easy_getinfo_off_t_type)(CURL *curl, CURLINFO info, curl_off_t *value); +static curl_easy_getinfo_off_t_type curl_easy_getinfo_off_t_func; + +typedef CURLcode (*curl_easy_setopt_long_type)(CURL *curl, CURLoption opt, long value); +static curl_easy_setopt_long_type curl_easy_setopt_long_func; + +typedef CURLcode (*curl_easy_setopt_pointer_type)(CURL *curl, CURLoption opt, void *value); +static curl_easy_setopt_pointer_type curl_easy_setopt_pointer_func; + +typedef CURLcode (*curl_easy_setopt_off_t_type)(CURL *curl, CURLoption opt, curl_off_t value); +static curl_easy_setopt_off_t_type curl_easy_setopt_off_t_func; + +static void lazy_load_curl(void) +{ + static int initialized; + void *libcurl; + func_t curl_easy_getinfo_func, curl_easy_setopt_func; + + if (initialized) + return; + + initialized = 1; + libcurl = load_library(LIBCURL_FILE_NAME("libcurl")); + if (!libcurl) + die("failed to load library '%s'", LIBCURL_FILE_NAME("libcurl")); + + curl_version_info_func = (curl_version_info_type)load_function(libcurl, "curl_version_info"); + curl_easy_escape_func = (curl_easy_escape_type)load_function(libcurl, "curl_easy_escape"); + curl_free_func = (curl_free_type)load_function(libcurl, "curl_free"); + curl_global_init_func = (curl_global_init_type)load_function(libcurl, "curl_global_init"); + curl_global_sslset_func = (curl_global_sslset_type)load_function(libcurl, "curl_global_sslset"); + curl_global_cleanup_func = (curl_global_cleanup_type)load_function(libcurl, "curl_global_cleanup"); + curl_slist_append_func = (curl_slist_append_type)load_function(libcurl, "curl_slist_append"); + curl_slist_free_all_func = (curl_slist_free_all_type)load_function(libcurl, "curl_slist_free_all"); + curl_easy_strerror_func = (curl_easy_strerror_type)load_function(libcurl, "curl_easy_strerror"); + curl_multi_init_func = (curl_multi_init_type)load_function(libcurl, "curl_multi_init"); + curl_multi_add_handle_func = (curl_multi_add_handle_type)load_function(libcurl, "curl_multi_add_handle"); + curl_multi_remove_handle_func = (curl_multi_remove_handle_type)load_function(libcurl, "curl_multi_remove_handle"); + curl_multi_fdset_func = (curl_multi_fdset_type)load_function(libcurl, "curl_multi_fdset"); + curl_multi_perform_func = (curl_multi_perform_type)load_function(libcurl, "curl_multi_perform"); + curl_multi_cleanup_func = (curl_multi_cleanup_type)load_function(libcurl, "curl_multi_cleanup"); + curl_multi_info_read_func = (curl_multi_info_read_type)load_function(libcurl, "curl_multi_info_read"); + curl_multi_strerror_func = (curl_multi_strerror_type)load_function(libcurl, "curl_multi_strerror"); + curl_multi_timeout_func = (curl_multi_timeout_type)load_function(libcurl, "curl_multi_timeout"); + curl_easy_init_func = (curl_easy_init_type)load_function(libcurl, "curl_easy_init"); + curl_easy_perform_func = (curl_easy_perform_type)load_function(libcurl, "curl_easy_perform"); + curl_easy_cleanup_func = (curl_easy_cleanup_type)load_function(libcurl, "curl_easy_cleanup"); + curl_easy_duphandle_func = (curl_easy_duphandle_type)load_function(libcurl, "curl_easy_duphandle"); + + curl_easy_getinfo_func = load_function(libcurl, "curl_easy_getinfo"); + curl_easy_getinfo_long_func = (curl_easy_getinfo_long_type)curl_easy_getinfo_func; + curl_easy_getinfo_pointer_func = (curl_easy_getinfo_pointer_type)curl_easy_getinfo_func; + curl_easy_getinfo_off_t_func = (curl_easy_getinfo_off_t_type)curl_easy_getinfo_func; + + curl_easy_setopt_func = load_function(libcurl, "curl_easy_setopt"); + curl_easy_setopt_long_func = (curl_easy_setopt_long_type)curl_easy_setopt_func; + curl_easy_setopt_pointer_func = (curl_easy_setopt_pointer_type)curl_easy_setopt_func; + curl_easy_setopt_off_t_func = (curl_easy_setopt_off_t_type)curl_easy_setopt_func; +} + +struct curl_version_info_data *curl_version_info(CURLversion version) +{ + lazy_load_curl(); + return curl_version_info_func(version); +} + +char *curl_easy_escape(CURL *handle, const char *string, int length) +{ + lazy_load_curl(); + return curl_easy_escape_func(handle, string, length); +} + +void curl_free(void *p) +{ + lazy_load_curl(); + curl_free_func(p); +} + +CURLcode curl_global_init(long flags) +{ + lazy_load_curl(); + return curl_global_init_func(flags); +} + +CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail) +{ + lazy_load_curl(); + return curl_global_sslset_func(id, name, avail); +} + +void curl_global_cleanup(void) +{ + lazy_load_curl(); + curl_global_cleanup_func(); +} + +struct curl_slist *curl_slist_append(struct curl_slist *list, const char *data) +{ + lazy_load_curl(); + return curl_slist_append_func(list, data); +} + +void curl_slist_free_all(struct curl_slist *list) +{ + lazy_load_curl(); + curl_slist_free_all_func(list); +} + +const char *curl_easy_strerror(CURLcode error) +{ + lazy_load_curl(); + return curl_easy_strerror_func(error); +} + +CURLM *curl_multi_init(void) +{ + lazy_load_curl(); + return curl_multi_init_func(); +} + +CURLMcode curl_multi_add_handle(CURLM *multi_handle, CURL *curl_handle) +{ + lazy_load_curl(); + return curl_multi_add_handle_func(multi_handle, curl_handle); +} + +CURLMcode curl_multi_remove_handle(CURLM *multi_handle, CURL *curl_handle) +{ + lazy_load_curl(); + return curl_multi_remove_handle_func(multi_handle, curl_handle); +} + +CURLMcode curl_multi_fdset(CURLM *multi_handle, fd_set *read_fd_set, fd_set *write_fd_set, fd_set *exc_fd_set, int *max_fd) +{ + lazy_load_curl(); + return curl_multi_fdset_func(multi_handle, read_fd_set, write_fd_set, exc_fd_set, max_fd); +} + +CURLMcode curl_multi_perform(CURLM *multi_handle, int *running_handles) +{ + lazy_load_curl(); + return curl_multi_perform_func(multi_handle, running_handles); +} + +CURLMcode curl_multi_cleanup(CURLM *multi_handle) +{ + lazy_load_curl(); + return curl_multi_cleanup_func(multi_handle); +} + +CURLMsg *curl_multi_info_read(CURLM *multi_handle, int *msgs_in_queue) +{ + lazy_load_curl(); + return curl_multi_info_read_func(multi_handle, msgs_in_queue); +} + +const char *curl_multi_strerror(CURLMcode error) +{ + lazy_load_curl(); + return curl_multi_strerror_func(error); +} + +CURLMcode curl_multi_timeout(CURLM *multi_handle, long *milliseconds) +{ + lazy_load_curl(); + return curl_multi_timeout_func(multi_handle, milliseconds); +} + +CURL *curl_easy_init(void) +{ + lazy_load_curl(); + return curl_easy_init_func(); +} + +CURLcode curl_easy_perform(CURL *curl) +{ + lazy_load_curl(); + return curl_easy_perform_func(curl); +} + +void curl_easy_cleanup(CURL *curl) +{ + lazy_load_curl(); + curl_easy_cleanup_func(curl); +} + +CURL *curl_easy_duphandle(CURL *curl) +{ + lazy_load_curl(); + return curl_easy_duphandle_func(curl); +} + +#ifndef CURL_IGNORE_DEPRECATION +#define CURL_IGNORE_DEPRECATION(x) x +#endif + +#ifndef CURLOPTTYPE_BLOB +#define CURLOPTTYPE_BLOB 40000 +#endif + +#undef curl_easy_getinfo +CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ...) +{ + va_list ap; + CURLcode res; + + va_start(ap, info); + lazy_load_curl(); + CURL_IGNORE_DEPRECATION( + if (info >= CURLINFO_LONG && info < CURLINFO_DOUBLE) + res = curl_easy_getinfo_long_func(curl, info, va_arg(ap, long *)); + else if ((info >= CURLINFO_STRING && info < CURLINFO_LONG) || + (info >= CURLINFO_SLIST && info < CURLINFO_SOCKET)) + res = curl_easy_getinfo_pointer_func(curl, info, va_arg(ap, void **)); + else if (info >= CURLINFO_OFF_T) + res = curl_easy_getinfo_off_t_func(curl, info, va_arg(ap, curl_off_t *)); + else + die("%s:%d: TODO (info: %d)!", __FILE__, __LINE__, info); + ) + va_end(ap); + return res; +} + +#undef curl_easy_setopt +CURLcode curl_easy_setopt(CURL *curl, CURLoption opt, ...) +{ + va_list ap; + CURLcode res; + + va_start(ap, opt); + lazy_load_curl(); + CURL_IGNORE_DEPRECATION( + if (opt >= CURLOPTTYPE_LONG && opt < CURLOPTTYPE_OBJECTPOINT) + res = curl_easy_setopt_long_func(curl, opt, va_arg(ap, long)); + else if (opt >= CURLOPTTYPE_OBJECTPOINT && opt < CURLOPTTYPE_OFF_T) + res = curl_easy_setopt_pointer_func(curl, opt, va_arg(ap, void *)); + else if (opt >= CURLOPTTYPE_OFF_T && opt < CURLOPTTYPE_BLOB) + res = curl_easy_setopt_off_t_func(curl, opt, va_arg(ap, curl_off_t)); + else + die("%s:%d: TODO (opt: %d)!", __FILE__, __LINE__, opt); + ) + va_end(ap); + return res; +} From 6f3580c9492827276947617856ca7f9b493710d6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 7 May 2023 22:51:52 +0200 Subject: [PATCH 135/306] http: support lazy-loading libcurl also on Windows This implements the Windows-specific support code, because everything is slightly different on Windows, even loading shared libraries. Note: I specifically do _not_ use the code from `compat/win32/lazyload.h` here because that code is optimized for loading individual functions from various system DLLs, while we specifically want to load _many_ functions from _one_ DLL here, and distinctly not a system DLL (we expect libcurl to be located outside `C:\Windows\system32`, something `INIT_PROC_ADDR` refuses to work with). Also, the `curl_easy_getinfo()`/`curl_easy_setopt()` functions are declared as vararg functions, which `lazyload.h` cannot handle. Finally, we are about to optionally override the exact file name that is to be loaded, which is a goal contrary to `lazyload.h`'s design. Signed-off-by: Johannes Schindelin --- Makefile | 4 ++++ compat/lazyload-curl.c | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/Makefile b/Makefile index 4e96a2ee9b85db..1b5fd1de6768bb 100644 --- a/Makefile +++ b/Makefile @@ -1658,7 +1658,11 @@ else # The `CURL_STATICLIB` constant must be defined to avoid seeing the functions # declared as DLL imports CURL_CFLAGS = -DCURL_STATICLIB +ifneq ($(uname_S),MINGW) +ifneq ($(uname_S),Windows) CURL_LIBCURL = -ldl +endif +endif else ifndef CURL_LDFLAGS CURL_LDFLAGS = $(eval CURL_LDFLAGS := $$(shell $$(CURL_CONFIG) --libs))$(CURL_LDFLAGS) diff --git a/compat/lazyload-curl.c b/compat/lazyload-curl.c index 19aa2b6d4b6942..98d73fb0f2a66f 100644 --- a/compat/lazyload-curl.c +++ b/compat/lazyload-curl.c @@ -1,6 +1,8 @@ #include "../git-compat-util.h" #include "../git-curl-compat.h" +#ifndef WIN32 #include +#endif /* * The ABI version of libcurl is encoded in its shared libraries' file names. @@ -11,6 +13,7 @@ typedef void (*func_t)(void); +#ifndef WIN32 #ifdef __APPLE__ #define LIBCURL_FILE_NAME(base) base "." LIBCURL_ABI_VERSION ".dylib" #else @@ -35,6 +38,55 @@ static func_t load_function(void *handle, const char *name) *(void **)&f = dlsym(handle, name); return f; } +#else +#define LIBCURL_FILE_NAME(base) base "-" LIBCURL_ABI_VERSION ".dll" + +static void *load_library(const char *name) +{ + size_t name_size = strlen(name) + 1; + const char *path = getenv("PATH"); + char dll_path[MAX_PATH]; + + while (path && *path) { + const char *sep = strchrnul(path, ';'); + size_t len = sep - path; + + if (len && len + name_size < sizeof(dll_path)) { + memcpy(dll_path, path, len); + dll_path[len] = '/'; + memcpy(dll_path + len + 1, name, name_size); + + if (!access(dll_path, R_OK)) { + wchar_t wpath[MAX_PATH]; + int wlen = MultiByteToWideChar(CP_UTF8, 0, dll_path, -1, wpath, ARRAY_SIZE(wpath)); + void *res = wlen ? (void *)LoadLibraryExW(wpath, NULL, 0) : NULL; + if (!res) { + DWORD err = GetLastError(); + char buf[1024]; + + if (!FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, err, LANG_NEUTRAL, + buf, sizeof(buf) - 1, NULL)) + xsnprintf(buf, sizeof(buf), "last error: %ld", err); + error("LoadLibraryExW() failed with: %s", buf); + } + return res; + } + } + + path = *sep ? sep + 1 : NULL; + } + + return NULL; +} + +static func_t load_function(void *handle, const char *name) +{ + return (func_t)GetProcAddress((HANDLE)handle, name); +} +#endif typedef struct curl_version_info_data *(*curl_version_info_type)(CURLversion version); static curl_version_info_type curl_version_info_func; From 5c064c2ddaa154ecc208b2b69410c143b9077fd7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 7 May 2023 22:05:33 +0200 Subject: [PATCH 136/306] http: when loading libcurl lazily, allow for multiple SSL backends The previous commits introduced a compile-time option to load libcurl lazily, but it uses the hard-coded name "libcurl-4.dll" (or equivalent on platforms other than Windows). To allow for installing multiple libcurl flavors side by side, where each supports one specific SSL/TLS backend, let's first look whether `libcurl--4.dll` exists, and only use `libcurl-4.dll` as a fall back. That will allow us to ship with a libcurl by default that only supports the Secure Channel backend for the `https://` protocol. This libcurl won't suffer from any dependency problem when upgrading OpenSSL to a new major version (which will change the DLL name, and hence break every program and library that depends on it). This is crucial because Git for Windows relies on libcurl to keep working when building and deploying a new OpenSSL package because that library is used by `git fetch` and `git clone`. Note that this feature is by no means specific to Windows. On Ubuntu, for example, a `git` built using `LAZY_LOAD_LIBCURL` will use `libcurl.so.4` for `http.sslbackend=openssl` and `libcurl-gnutls.so.4` for `http.sslbackend=gnutls`. Signed-off-by: Johannes Schindelin --- compat/lazyload-curl.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/compat/lazyload-curl.c b/compat/lazyload-curl.c index 98d73fb0f2a66f..2f2b2ebc27993e 100644 --- a/compat/lazyload-curl.c +++ b/compat/lazyload-curl.c @@ -172,17 +172,26 @@ static curl_easy_setopt_pointer_type curl_easy_setopt_pointer_func; typedef CURLcode (*curl_easy_setopt_off_t_type)(CURL *curl, CURLoption opt, curl_off_t value); static curl_easy_setopt_off_t_type curl_easy_setopt_off_t_func; +static char ssl_backend[64]; + static void lazy_load_curl(void) { static int initialized; - void *libcurl; + void *libcurl = NULL; func_t curl_easy_getinfo_func, curl_easy_setopt_func; if (initialized) return; initialized = 1; - libcurl = load_library(LIBCURL_FILE_NAME("libcurl")); + if (ssl_backend[0]) { + char dll_name[64 + 16]; + snprintf(dll_name, sizeof(dll_name) - 1, + LIBCURL_FILE_NAME("libcurl-%s"), ssl_backend); + libcurl = load_library(dll_name); + } + if (!libcurl) + libcurl = load_library(LIBCURL_FILE_NAME("libcurl")); if (!libcurl) die("failed to load library '%s'", LIBCURL_FILE_NAME("libcurl")); @@ -246,6 +255,9 @@ CURLcode curl_global_init(long flags) CURLsslset curl_global_sslset(curl_sslbackend id, const char *name, const curl_ssl_backend ***avail) { + if (name && strlen(name) < sizeof(ssl_backend)) + strlcpy(ssl_backend, name, sizeof(ssl_backend)); + lazy_load_curl(); return curl_global_sslset_func(id, name, avail); } From f3cd3ed4039890493f98176d05d3bfb45cd8d3fb Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 21 Mar 2023 16:14:44 +0100 Subject: [PATCH 137/306] windows: fix Repository>Explore Working Copy Since Git v2.39.1, we are a bit more stringent in searching the PATH. In particular, we specifically require the `.exe` suffix. However, the `Repository>Explore Working Copy` command asks for `explorer.exe` to be found on the `PATH`, which _already_ has that suffix. Let's unstartle the PATH-finding logic about this scenario. This fixes https://github.com/git-for-windows/git/issues/4356 Signed-off-by: Johannes Schindelin --- git-gui/git-gui.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index 887d6d596c16fc..c913a08566542e 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -101,6 +101,9 @@ proc _which {what args} { if {[is_Windows] && [lsearch -exact $args -script] >= 0} { set suffix {} + } elseif {[string match *$_search_exe $what]} { + # The search string already has the file extension + set suffix {} } else { set suffix $_search_exe } From a8bd077c4b24c84ab2cc512ef5ac7b5565758296 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 7 May 2023 22:43:37 +0200 Subject: [PATCH 138/306] mingw: do load libcurl dynamically by default This will help with Git for Windows' maintenance going forward: It allows Git for Windows to switch its primary libcurl to a variant without the OpenSSL backend, while still loading an alternate when setting `http.sslBackend = openssl`. This is necessary to avoid maintenance headaches with upgrading OpenSSL: its major version name is encoded in the shared library's file name and hence major version updates (temporarily) break libraries that are linked against the OpenSSL library. Signed-off-by: Johannes Schindelin --- config.mak.uname | 1 + 1 file changed, 1 insertion(+) diff --git a/config.mak.uname b/config.mak.uname index 1736c4f0116e75..76b60c2fba8d57 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -692,6 +692,7 @@ ifeq ($(uname_S),MINGW) HAVE_PLATFORM_PROCINFO = YesPlease CSPRNG_METHOD = rtlgenrandom BASIC_LDFLAGS += -municode -Wl,--tsaware + LAZYLOAD_LIBCURL = YesDoThatPlease COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/winansi.o \ From 0959164055441e0705c37952ad35e8573f58d8db Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 2 Nov 2022 16:23:58 +0100 Subject: [PATCH 139/306] Add a GitHub workflow to verify that Git/Scalar work in Nano Server In Git for Windows v2.39.0, we fixed a regression where `git.exe` would no longer work in Windows Nano Server (frequently used in Docker containers). This GitHub workflow can be used to verify manually that the Git/Scalar executables work in Nano Server. Signed-off-by: Johannes Schindelin --- .github/workflows/nano-server.yml | 76 +++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .github/workflows/nano-server.yml diff --git a/.github/workflows/nano-server.yml b/.github/workflows/nano-server.yml new file mode 100644 index 00000000000000..3d943c23d2616d --- /dev/null +++ b/.github/workflows/nano-server.yml @@ -0,0 +1,76 @@ +name: Windows Nano Server tests + +on: + workflow_dispatch: + +env: + DEVELOPER: 1 + +jobs: + test-nano-server: + runs-on: windows-2022 + env: + WINDBG_DIR: "C:/Program Files (x86)/Windows Kits/10/Debuggers/x64" + IMAGE: mcr.microsoft.com/powershell:nanoserver-ltsc2022 + + steps: + - uses: actions/checkout@v4 + - uses: git-for-windows/setup-git-for-windows-sdk@v1 + - name: build Git + shell: bash + run: make -j15 + - name: pull nanoserver image + shell: bash + run: docker pull $IMAGE + - name: run nano-server test + shell: bash + run: | + docker run \ + --user "ContainerAdministrator" \ + -v "$WINDBG_DIR:C:/dbg" \ + -v "$(cygpath -aw /mingw64/bin):C:/mingw64-bin" \ + -v "$(cygpath -aw .):C:/test" \ + $IMAGE pwsh.exe -Command ' + # Extend the PATH to include the `.dll` files in /mingw64/bin/ + $env:PATH += ";C:\mingw64-bin" + + # For each executable to test pick some no-operation set of + # flags/subcommands or something that should quickly result in an + # error with known exit code that is not a negative 32-bit + # number, and set the expected return code appropriately. + # + # Only test executables that could be expected to run in a UI + # less environment. + # + # ( Executable path, arguments, expected return code ) + # also note space is required before close parenthesis (a + # powershell quirk when defining nested arrays like this) + + $executables_to_test = @( + ("C:\test\git.exe", "", 1 ), + ("C:\test\scalar.exe", "version", 0 ) + ) + + foreach ($executable in $executables_to_test) + { + Write-Output "Now testing $($executable[0])" + &$executable[0] $executable[1] + if ($LASTEXITCODE -ne $executable[2]) { + # if we failed, run the debugger to find out what function + # or DLL could not be found and then exit the script with + # failure The missing DLL or EXE will be referenced near + # the end of the output + + # Set a flag to have the debugger show loader stub + # diagnostics. This requires running as administrator, + # otherwise the flag will be ignored. + C:\dbg\gflags -i $executable[0] +SLS + + C:\dbg\cdb.exe -c "g" -c "q" $executable[0] $executable[1] + + exit 1 + } + } + + exit 0 + ' From 171878e1b313dce598c9f5e6bc7d0ea853a29c99 Mon Sep 17 00:00:00 2001 From: David Lomas Date: Fri, 28 Jul 2023 15:31:25 +0100 Subject: [PATCH 140/306] mingw: suggest `windows.appendAtomically` in more cases When running Git for Windows on a remote APFS filesystem, it would appear that the `mingw_open_append()`/`write()` combination would fail almost exactly like on some CIFS-mounted shares as had been reported in https://github.com/git-for-windows/git/issues/2753, albeit with a different `errno` value. Let's handle that `errno` value just the same, by suggesting to set `windows.appendAtomically=false`. Signed-off-by: David Lomas Signed-off-by: Johannes Schindelin --- compat/mingw.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compat/mingw.c b/compat/mingw.c index 599bf6cd1c6388..afb0f8eb3beeff 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -787,7 +787,7 @@ ssize_t mingw_write(int fd, const void *buf, size_t len) { ssize_t result = write(fd, buf, len); - if (result < 0 && (errno == EINVAL || errno == ENOSPC) && buf) { + if (result < 0 && (errno == EINVAL || errno == EBADF || errno == ENOSPC) && buf) { int orig = errno; /* check if fd is a pipe */ @@ -813,7 +813,7 @@ ssize_t mingw_write(int fd, const void *buf, size_t len) } errno = orig; - } else if (orig == EINVAL) + } else if (orig == EINVAL || errno == EBADF) errno = EPIPE; else { DWORD buf_size; From a3742f4833957f030c0cab91247949857120cfa4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 22 Nov 2023 22:57:38 +0100 Subject: [PATCH 141/306] win32: use native ANSI sequence processing, if possible Windows 10 version 1511 (also known as Anniversary Update), according to https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences introduced native support for ANSI sequence processing. This allows using colors from the entire 24-bit color range. All we need to do is test whether the console's "virtual processing support" can be enabled. If it can, we do not even need to start the `console_thread` to handle ANSI sequences. Or, almost all we need to do: When `console_thread()` does its work, it uses the Unicode-aware `write_console()` function to write to the Win32 Console, which supports Git for Windows' implicit convention that all text that is written is encoded in UTF-8. The same is not necessarily true if native ANSI sequence processing is used, as the output is then subject to the current code page. Let's ensure that the code page is set to `CP_UTF8` as long as Git writes to it. Signed-off-by: Johannes Schindelin --- compat/winansi.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/compat/winansi.c b/compat/winansi.c index ac2ffb78691a7d..a83a7f47ada9b2 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -593,6 +593,49 @@ static void detect_msys_tty(int fd) #endif +static HANDLE std_console_handle; +static DWORD std_console_mode = ENABLE_VIRTUAL_TERMINAL_PROCESSING; +static UINT std_console_code_page = CP_UTF8; + +static void reset_std_console(void) +{ + if (std_console_mode != ENABLE_VIRTUAL_TERMINAL_PROCESSING) + SetConsoleMode(std_console_handle, std_console_mode); + if (std_console_code_page != CP_UTF8) + SetConsoleOutputCP(std_console_code_page); +} + +static int enable_virtual_processing(void) +{ + std_console_handle = GetStdHandle(STD_OUTPUT_HANDLE); + if (std_console_handle == INVALID_HANDLE_VALUE || + !GetConsoleMode(std_console_handle, &std_console_mode)) { + std_console_handle = GetStdHandle(STD_ERROR_HANDLE); + if (std_console_handle == INVALID_HANDLE_VALUE || + !GetConsoleMode(std_console_handle, &std_console_mode)) + return 0; + } + + std_console_code_page = GetConsoleOutputCP(); + if (std_console_code_page != CP_UTF8) + SetConsoleOutputCP(CP_UTF8); + if (!std_console_code_page) + std_console_code_page = CP_UTF8; + + atexit(reset_std_console); + + if (std_console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) + return 1; + + if (!SetConsoleMode(std_console_handle, + std_console_mode | + ENABLE_PROCESSED_OUTPUT | + ENABLE_VIRTUAL_TERMINAL_PROCESSING)) + return 0; + + return 1; +} + /* * Wrapper for isatty(). Most calls in the main git code * call isatty(1 or 2) to see if the instance is interactive @@ -631,6 +674,9 @@ void winansi_init(void) return; } + if (enable_virtual_processing()) + return; + /* create a named pipe to communicate with the console thread */ if (swprintf(name, ARRAY_SIZE(name) - 1, L"\\\\.\\pipe\\winansi%lu", GetCurrentProcessId()) < 0) From 52640776d3296100306aa909c82c597238752e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20A=C3=9Fhauer?= Date: Sat, 2 Dec 2023 12:10:00 +0100 Subject: [PATCH 142/306] git.rc: include winuser.h MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit winuser.h contains the definition of RT_MANIFEST that our LLVM based toolchain needs to understand that we want to embed compat/win32/git.manifest as an application manifest. It currently just embeds it as additional data that Windows doesn't understand. This also helps our GCC based toolchain understand that we only want one copy embedded. It currently embeds one working assembly manifest and one nearly identical, but useless copy as additional data. This also teaches our Visual Studio based buildsystems to pick up the manifest file from git.rc. This means we don't have to explicitly specify it in contrib/buildsystems/Generators/Vcxproj.pm anymore. Slightly counter-intuitively this also means we have to explicitly tell Cmake not to embed a default manifest. This fixes https://github.com/git-for-windows/git/issues/4707 Signed-off-by: Matthias Aßhauer Signed-off-by: Johannes Schindelin --- contrib/buildsystems/CMakeLists.txt | 1 + contrib/buildsystems/Generators/Vcxproj.pm | 1 - git.rc.in | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 10dc54fdcb647b..4bb616a7dc9321 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -208,6 +208,7 @@ if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) add_compile_options(/MP /std:c11) + add_link_options(/MANIFEST:NO) endif() #default behaviour diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm index a6d1c6b8d05682..7a62542eed152b 100644 --- a/contrib/buildsystems/Generators/Vcxproj.pm +++ b/contrib/buildsystems/Generators/Vcxproj.pm @@ -187,7 +187,6 @@ sub createProject { \$(VCPKGLibs);\$(AdditionalDependencies) invalidcontinue.obj %(AdditionalOptions) $entrypoint - $cdup\\compat\\win32\\git.manifest $subsystem EOM diff --git a/git.rc.in b/git.rc.in index e69444eef3f0c5..1d5b627b610549 100644 --- a/git.rc.in +++ b/git.rc.in @@ -1,3 +1,4 @@ +#include 1 VERSIONINFO FILEVERSION @GIT_MAJOR_VERSION@,@GIT_MINOR_VERSION@,@GIT_MICRO_VERSION@,@GIT_PATCH_LEVEL@ PRODUCTVERSION @GIT_MAJOR_VERSION@,@GIT_MINOR_VERSION@,@GIT_MICRO_VERSION@,@GIT_PATCH_LEVEL@ From ef1206c7ae3a0d96178514bd63c559e5f4b6aeb0 Mon Sep 17 00:00:00 2001 From: Derrick Stolee Date: Sat, 7 Sep 2024 19:43:45 -0400 Subject: [PATCH 143/306] pack-objects: add --full-name-hash option The pack_name_hash() method has not been materially changed since it was introduced in ce0bd64299a (pack-objects: improve path grouping heuristics., 2006-06-05). The intention here is to group objects by path name, but also attempt to group similar file types together by making the most-significant digits of the hash be focused on the final characters. Here's the crux of the implementation: /* * This effectively just creates a sortable number from the * last sixteen non-whitespace characters. Last characters * count "most", so things that end in ".c" sort together. */ while ((c = *name++) != 0) { if (isspace(c)) continue; hash = (hash >> 2) + (c << 24); } As the comment mentions, this only cares about the last sixteen non-whitespace characters. This cause some filenames to collide more than others. Here are some examples that I've seen while investigating repositories that are growing more than they should be: * "/CHANGELOG.json" is 15 characters, and is created by the beachball [1] tool. Only the final character of the parent directory can differntiate different versions of this file, but also only the two most-significant digits. If that character is a letter, then this is always a collision. Similar issues occur with the similar "/CHANGELOG.md" path, though there is more opportunity for differences in the parent directory. * Localization files frequently have common filenames but differentiate via parent directories. In C#, the name "/strings.resx.lcl" is used for these localization files and they will all collide in name-hash. [1] https://github.com/microsoft/beachball I've come across many other examples where some internal tool uses a common name across multiple directories and is causing Git to repack poorly due to name-hash collisions. It is clear that the existing name-hash algorithm is optimized for repositories with short path names, but also is optimized for packing a single snapshot of a repository, not a repository with many versions of the same file. In my testing, this has proven out where the name-hash algorithm does a good job of finding peer files as delta bases when unable to use a historical version of that exact file. However, for repositories that have many versions of most files and directories, it is more important that the objects that appear at the same path are grouped together. Create a new pack_full_name_hash() method and a new --full-name-hash option for 'git pack-objects' to call that method instead. Add a simple pass-through for 'git repack --full-name-hash' for additional testing in the context of a full repack, where I expect this will be most effective. The hash algorithm is as simple as possible to be reasonably effective: for each character of the path string, add a multiple of that character and a large prime number (chosen arbitrarily, but intended to be large relative to the size of a uint32_t). Then, shift the current hash value to the right by 5, with overlap. The addition and shift parameters are standard mechanisms for creating hard-to-predict behaviors in the bits of the resulting hash. This is not meant to be cryptographic at all, but uniformly distributed across the possible hash values. This creates a hash that appears pseudorandom. There is no ability to consider similar file types as being close to each other. In a later change, a test-tool will be added so the effectiveness of this hash can be demonstrated directly. For now, let's consider how effective this mechanism is when repacking a repository with and without the --full-name-hash option. Specifically, let's use 'git repack -adf [--full-name-hash]' as our test. On the Git repository, we do not expect much difference. All path names are short. This is backed by our results: | Stage | Pack Size | Repack Time | |-----------------------|-----------|-------------| | After clone | 260 MB | N/A | | Standard Repack | 127MB | 106s | | With --full-name-hash | 126 MB | 99s | This example demonstrates how there is some natural overhead coming from the cloned copy because the server is hosting many forks and has not optimized for exactly this set of reachable objects. But the full repack has similar characteristics with and without --full-name-hash. However, we can test this in a repository that uses one of the problematic naming conventions above. The fluentui [2] repo uses beachball to generate CHANGELOG.json and CHANGELOG.md files, and these files have very poor delta characteristics when comparing against versions across parent directories. | Stage | Pack Size | Repack Time | |-----------------------|-----------|-------------| | After clone | 694 MB | N/A | | Standard Repack | 438 MB | 728s | | With --full-name-hash | 168 MB | 142s | [2] https://github.com/microsoft/fluentui In this example, we see significant gains in the compressed packfile size as well as the time taken to compute the packfile. Using a collection of repositories that use the beachball tool, I was able to make similar comparisions with dramatic results. While the fluentui repo is public, the others are private so cannot be shared for reproduction. The results are so significant that I find it important to share here: | Repo | Standard Repack | With --full-name-hash | |----------|-----------------|-----------------------| | fluentui | 438 MB | 168 MB | | Repo B | 6,255 MB | 829 MB | | Repo C | 37,737 MB | 7,125 MB | | Repo D | 130,049 MB | 6,190 MB | Future changes could include making --full-name-hash implied by a config value or even implied by default during a full repack. Signed-off-by: Derrick Stolee --- Documentation/git-pack-objects.txt | 3 ++- builtin/pack-objects.c | 23 ++++++++++++++++++----- builtin/repack.c | 5 +++++ pack-objects.h | 21 +++++++++++++++++++++ t/t5300-pack-object.sh | 15 +++++++++++++++ 5 files changed, 61 insertions(+), 6 deletions(-) diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index e32404c6aaee30..93861d9f85b3b1 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -15,7 +15,8 @@ SYNOPSIS [--revs [--unpacked | --all]] [--keep-pack=] [--cruft] [--cruft-expiration=