diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc index c6c34d1a1d60eb..b3bfdf29c955f7 100644 --- a/Documentation/RelNotes/2.50.0.adoc +++ b/Documentation/RelNotes/2.50.0.adoc @@ -89,6 +89,8 @@ UI, Workflows & Features check, which can be useful when the repository arranges to ensure connectivity by some other means. + * "git notes --help" documentation updates. + Performance, Internal Implementation, Development Support etc. -------------------------------------------------------------- @@ -193,6 +195,10 @@ Performance, Internal Implementation, Development Support etc. do not pass the leak checker tests, as they should no longer be needed. + * When a stale .midx file refers to .pack files that no longer exist, + we ended up checking for these non-existent files repeatedly, which + has been optimized by memoizing the non-existence. + Fixes since v2.49 ----------------- @@ -355,6 +361,22 @@ Fixes since v2.49 expand sparse-index while working. (merge ecf9ba20e3 ds/sparse-apply-add-p later to maint). + * Avoid adding directory path to a sparse-index tree entries to the + name-hash, since they would bloat the hashtable without anybody + querying for them. This was done already for a single threaded + part of the code, but now the multi-threaded code also does the + same. + (merge 2e60aabc75 am/sparse-index-name-hash-fix later to maint). + + * Recent versions of Perl started warning against "! A =~ /pattern/" + which does not negate the result of the matching. As it turns out + that the problematic function is not even called, it was removed. + (merge 67cae845d2 op/cvsserver-perl-warning later to maint). + + * "git apply --index/--cached" when applying a deletion patch in + reverse failed to give the mode bits of the path "removed" by the + patch to the file it creates, which has been corrected. + * Other code cleanup, docfix, build fix, etc. (merge 227c4f33a0 ja/doc-block-delimiter-markup-fix later to maint). (merge 2bfd3b3685 ab/decorate-code-cleanup later to maint). diff --git a/Documentation/config/commit.adoc b/Documentation/config/commit.adoc index d3f4624fd27811..208ae76c816f3b 100644 --- a/Documentation/config/commit.adoc +++ b/Documentation/config/commit.adoc @@ -8,10 +8,11 @@ endif::git-commit[] This setting overrides the default of the `--cleanup` option in `git commit`. {see-git-commit} Changing the default can be useful when you always want to keep lines that begin - with the comment character `#` in your log message, in which case you + with the comment character (`core.commentChar`, default `#`) + in your log message, in which case you would do `git config commit.cleanup whitespace` (note that you will - have to remove the help lines that begin with `#` in the commit log - template yourself, if you do this). + have to remove the help lines that begin with the comment character + in the commit log template yourself, if you do this). `commit.gpgSign`:: A boolean to specify whether all commits should be GPG signed. diff --git a/Documentation/git-multi-pack-index.adoc b/Documentation/git-multi-pack-index.adoc index 631d5c7d15c0de..b6cd0d7f855d5f 100644 --- a/Documentation/git-multi-pack-index.adoc +++ b/Documentation/git-multi-pack-index.adoc @@ -38,10 +38,13 @@ write:: + -- --preferred-pack=:: - Optionally specify the tie-breaking pack used when - multiple packs contain the same object. `` must - contain at least one object. If not given, ties are - broken in favor of the pack with the lowest mtime. + When specified, break ties in favor of this pack when + there are additional copies of its objects in other + packs. Ties for objects not found in the preferred + pack are always resolved in favor of the copy in the + pack with the highest mtime. If unspecified, the pack + with the lowest mtime is used by default. The + preferred pack must have at least one object. --[no-]bitmap:: Control whether or not a multi-pack bitmap is written. diff --git a/Documentation/git-notes.adoc b/Documentation/git-notes.adoc index bcfe3dacd3f0de..46a232ca7185ad 100644 --- a/Documentation/git-notes.adoc +++ b/Documentation/git-notes.adoc @@ -87,6 +87,9 @@ In `--stdin` mode, take lines in the format on standard input, and copy the notes from each __ to its corresponding __. (The optional __ is ignored so that the command can read the input given to the `post-rewrite` hook.) ++ +`--stdin` cannot be combined with object names given on the command +line. `append`:: Append new message(s) given by `-m` or `-F` options to an @@ -124,6 +127,10 @@ When done, the user can either finalize the merge with giving zero or one object from the command line, this is equivalent to specifying an empty note message to the `edit` subcommand. ++ +In `--stdin` mode, also remove the object names given on standard +input. In other words, `--stdin` can be combined with object names from +the command line. `prune`:: Remove all notes for non-existing/unreachable objects. @@ -144,26 +151,18 @@ OPTIONS Use the given note message (instead of prompting). If multiple `-m` options are given, their values are concatenated as separate paragraphs. - Lines starting with `#` and empty lines other than a - single line between paragraphs will be stripped out. - If you wish to keep them verbatim, use `--no-stripspace`. `-F `:: `--file=`:: Take the note message from the given file. Use `-` to read the note message from the standard input. - Lines starting with `#` and empty lines other than a - single line between paragraphs will be stripped out. - If you wish to keep them verbatim, use `--no-stripspace`. `-C `:: `--reuse-message=`:: Take the given blob object (for example, another note) as the note message. (Use `git notes copy ` instead to - copy notes between objects.). By default, message will be - copied verbatim, but if you wish to strip out the lines - starting with `#` and empty lines other than a single line - between paragraphs, use with `--stripspace` option. + copy notes between objects.) Implies `--no-stripspace` since + the default behavior is to copy the message verbatim. `-c `:: `--reedit-message=`:: @@ -174,21 +173,34 @@ OPTIONS Allow an empty note object to be stored. The default behavior is to automatically remove empty notes. -`--[no-]separator`:: `--separator=`:: +`--separator`:: +`--no-separator`:: Specify a string used as a custom inter-paragraph separator (a newline is added at the end as needed). If `--no-separator`, no separators will be added between paragraphs. Defaults to a blank line. -`--[no-]stripspace`:: - Strip leading and trailing whitespace from the note message. - Also strip out empty lines other than a single line between - paragraphs. Lines starting with `#` will be stripped out - in non-editor cases like `-m`, `-F` and `-C`, but not in - editor case like `git notes edit`, `-c`, etc. - -`--ref `:: +`--stripspace`:: +`--no-stripspace`:: + Clean up whitespace. Specifically (see + linkgit:git-stripspace[1]): ++ +-- +- remove trailing whitespace from all lines +- collapse multiple consecutive empty lines into one empty line +- remove empty lines from the beginning and end of the input +- add a missing `\n` to the last line if necessary. +-- ++ +`--stripspace` is the default except for +`-C`/`--reuse-message`. However, keep in mind that this depends on the +order of similar options. For example, for `-C -m`, +`--stripspace` will be used because the default for `-m` overrides the +previous `-C`. This is a known limitation that may be fixed in the +future. + +`--ref=`:: Manipulate the notes tree in __. This overrides `GIT_NOTES_REF` and the `core.notesRef` configuration. The ref specifies the full refname when it begins with `refs/notes/`; when it @@ -200,9 +212,7 @@ OPTIONS object that does not have notes attached to it. `--stdin`:: - Also read the object names to remove notes from the standard - input (there is no reason you cannot combine this with object - names from the command line). + Only valid for `remove` and `copy`. See the respective subcommands. `-n`:: `--dry-run`:: diff --git a/Documentation/git-stripspace.adoc b/Documentation/git-stripspace.adoc index a293327581aa54..37287f211f062c 100644 --- a/Documentation/git-stripspace.adoc +++ b/Documentation/git-stripspace.adoc @@ -37,7 +37,8 @@ OPTIONS ------- -s:: --strip-comments:: - Skip and remove all lines starting with a comment character (default '#'). + Skip and remove all lines starting with a comment character + (`core.commentChar`, default `#`). -c:: --comment-lines:: diff --git a/apply.c b/apply.c index 381d2e3652f4e0..8bbe6ed224032e 100644 --- a/apply.c +++ b/apply.c @@ -2219,7 +2219,7 @@ static void reverse_patches(struct patch *p) struct fragment *frag = p->fragments; SWAP(p->new_name, p->old_name); - if (p->new_mode) + if (p->new_mode || p->is_delete) SWAP(p->new_mode, p->old_mode); SWAP(p->is_new, p->is_delete); SWAP(p->lines_added, p->lines_deleted); diff --git a/git-compat-util.h b/git-compat-util.h index 36b9577c8d4b3b..4678e21c4cb80b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -668,6 +668,22 @@ static inline int cast_size_t_to_int(size_t a) return (int)a; } +static inline uint64_t u64_mult(uint64_t a, uint64_t b) +{ + if (unsigned_mult_overflows(a, b)) + die("uint64_t overflow: %"PRIuMAX" * %"PRIuMAX, + (uintmax_t)a, (uintmax_t)b); + return a * b; +} + +static inline uint64_t u64_add(uint64_t a, uint64_t b) +{ + if (unsigned_add_overflows(a, b)) + die("uint64_t overflow: %"PRIuMAX" + %"PRIuMAX, + (uintmax_t)a, (uintmax_t)b); + return a + b; +} + /* * Limit size of IO chunks, because huge chunks only cause pain. OS X * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in diff --git a/git-cvsserver.perl b/git-cvsserver.perl index a4e1bad33ca2a3..d8d5422cbca90b 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -4986,13 +4986,13 @@ sub gethistorydense return $result; } -=head2 escapeRefName +=head2 unescapeRefName -Apply an escape mechanism to compensate for characters that +Undo an escape mechanism to compensate for characters that git ref names can have that CVS tags can not. =cut -sub escapeRefName +sub unescapeRefName { my($self,$refName)=@_; @@ -5009,27 +5009,6 @@ sub escapeRefName # = "_-xx-" Where "xx" is the hexadecimal representation of the # desired ASCII character byte. (for anything else) - if(! $refName=~/^[1-9][0-9]*(\.[1-9][0-9]*)*$/) - { - $refName=~s/_-/_-u--/g; - $refName=~s/\./_-p-/g; - $refName=~s%/%_-s-%g; - $refName=~s/[^-_a-zA-Z0-9]/sprintf("_-%02x-",$1)/eg; - } -} - -=head2 unescapeRefName - -Undo an escape mechanism to compensate for characters that -git ref names can have that CVS tags can not. - -=cut -sub unescapeRefName -{ - my($self,$refName)=@_; - - # see escapeRefName() for description of escape mechanism. - $refName=~s/_-([spu]|[0-9a-f][0-9a-f])-/unescapeRefNameChar($1)/eg; # allowed tag names diff --git a/midx-write.c b/midx-write.c index dd3b3070e55dfc..ba4a94950a8314 100644 --- a/midx-write.c +++ b/midx-write.c @@ -1566,7 +1566,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla _("Counting referenced objects"), m->num_objects); for (i = 0; i < m->num_objects; i++) { - int pack_int_id = nth_midxed_pack_int_id(m, i); + uint32_t pack_int_id = nth_midxed_pack_int_id(m, i); count[pack_int_id]++; display_progress(progress, i + 1); } @@ -1697,21 +1697,31 @@ static void fill_included_packs_batch(struct repository *r, total_size = 0; for (i = 0; total_size < batch_size && i < m->num_packs; i++) { - int pack_int_id = pack_info[i].pack_int_id; + uint32_t pack_int_id = pack_info[i].pack_int_id; struct packed_git *p = m->packs[pack_int_id]; - size_t expected_size; + uint64_t expected_size; if (!want_included_pack(r, m, pack_kept_objects, pack_int_id)) continue; - expected_size = st_mult(p->pack_size, - pack_info[i].referenced_objects); + /* + * Use shifted integer arithmetic to calculate the + * expected pack size to ~4 significant digits without + * overflow for packsizes less that 1PB. + */ + expected_size = (uint64_t)pack_info[i].referenced_objects << 14; expected_size /= p->num_objects; + expected_size = u64_mult(expected_size, p->pack_size); + expected_size = u64_add(expected_size, 1u << 13) >> 14; if (expected_size >= batch_size) continue; - total_size += expected_size; + if (unsigned_add_overflows(total_size, (size_t)expected_size)) + total_size = SIZE_MAX; + else + total_size += expected_size; + include_pack[pack_int_id] = 1; } diff --git a/midx.c b/midx.c index 3d0015f782818c..cd6e766ce2b158 100644 --- a/midx.c +++ b/midx.c @@ -13,6 +13,8 @@ #include "pack-bitmap.h" #include "pack-revindex.h" +#define MIDX_PACK_ERROR ((void *)(intptr_t)-1) + int midx_checksum_valid(struct multi_pack_index *m); void clear_midx_files_ext(const char *object_dir, const char *ext, const char *keep_hash); @@ -405,7 +407,7 @@ void close_midx(struct multi_pack_index *m) munmap((unsigned char *)m->data, m->data_len); for (i = 0; i < m->num_packs; i++) { - if (m->packs[i]) + if (m->packs[i] && m->packs[i] != MIDX_PACK_ERROR) m->packs[i]->multi_pack_index = 0; } FREE_AND_NULL(m->packs); @@ -458,6 +460,8 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, pack_int_id = midx_for_pack(&m, pack_int_id); + if (m->packs[pack_int_id] == MIDX_PACK_ERROR) + return 1; if (m->packs[pack_int_id]) return 0; @@ -482,8 +486,10 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, strbuf_release(&pack_name); strbuf_release(&key); - if (!p) + if (!p) { + m->packs[pack_int_id] = MIDX_PACK_ERROR; return 1; + } p->multi_pack_index = 1; m->packs[pack_int_id] = p; @@ -495,6 +501,8 @@ struct packed_git *nth_midxed_pack(struct multi_pack_index *m, uint32_t pack_int_id) { uint32_t local_pack_int_id = midx_for_pack(&m, pack_int_id); + if (m->packs[local_pack_int_id] == MIDX_PACK_ERROR) + return NULL; return m->packs[local_pack_int_id]; } diff --git a/name-hash.c b/name-hash.c index d66de1cdfd5633..b91e276267891e 100644 --- a/name-hash.c +++ b/name-hash.c @@ -492,8 +492,10 @@ static void *lazy_name_thread_proc(void *_data) for (k = 0; k < d->istate->cache_nr; k++) { struct cache_entry *ce_k = d->istate->cache[k]; ce_k->ce_flags |= CE_HASHED; - hashmap_entry_init(&ce_k->ent, d->lazy_entries[k].hash_name); - hashmap_add(&d->istate->name_hash, &ce_k->ent); + if (!S_ISSPARSEDIR(ce_k->ce_mode)) { + hashmap_entry_init(&ce_k->ent, d->lazy_entries[k].hash_name); + hashmap_add(&d->istate->name_hash, &ce_k->ent); + } } return NULL; diff --git a/packfile.c b/packfile.c index 80e35f1032d332..70c7208f027b52 100644 --- a/packfile.c +++ b/packfile.c @@ -737,6 +737,17 @@ struct packed_git *add_packed_git(struct repository *r, const char *path, p = alloc_packed_git(r, alloc); memcpy(p->pack_name, path, path_len); + /* + * Note that we have to check auxiliary data structures before we check + * for the ".pack" file to exist to avoid races with a packfile that is + * in the process of being deleted. The ".pack" file is unlinked before + * its auxiliary data structures, so we know that we either get a + * consistent snapshot of all data structures or that we'll fail to + * stat(3p) the packfile itself and thus return `NULL`. + * + * As such, we cannot bail out before the access(3p) calls in case the + * packfile doesn't exist without doing two stat(3p) calls for it. + */ xsnprintf(p->pack_name + path_len, alloc - path_len, ".keep"); if (!access(p->pack_name, F_OK)) p->pack_keep = 1; diff --git a/reftable/basics.h b/reftable/basics.h index d8888c126290cf..7d22f96261072a 100644 --- a/reftable/basics.h +++ b/reftable/basics.h @@ -16,7 +16,11 @@ #include "system.h" #include "reftable-basics.h" +#ifdef __GNUC__ #define REFTABLE_UNUSED __attribute__((__unused__)) +#else +#define REFTABLE_UNUSED +#endif /* * Initialize the buffer such that it is ready for use. This is equivalent to diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh index 2149ad5da44cde..1d6317bd7141e0 100755 --- a/t/t4129-apply-samemode.sh +++ b/t/t4129-apply-samemode.sh @@ -102,15 +102,32 @@ test_expect_success POSIXPERM 'do not use core.sharedRepository for working tree ) ' +test_file_mode_common () { + if test "$1" = "000000" + then + test_must_be_empty "$2" + else + test_grep "^$1 " "$2" + fi +} + +test_file_mode_staged () { + git ls-files --stage -- "$2" >ls-files-output && + test_file_mode_common "$1" ls-files-output +} + +test_file_mode_HEAD () { + git ls-tree HEAD -- "$2" >ls-tree-output && + test_file_mode_common "$1" ls-tree-output +} + test_expect_success 'git apply respects core.fileMode' ' test_config core.fileMode false && echo true >script.sh && git add --chmod=+x script.sh && - git ls-files -s script.sh >ls-files-output && - test_grep "^100755" ls-files-output && + test_file_mode_staged 100755 script.sh && test_tick && git commit -m "Add script" && - git ls-tree -r HEAD script.sh >ls-tree-output && - test_grep "^100755" ls-tree-output && + test_file_mode_HEAD 100755 script.sh && echo true >>script.sh && test_tick && git commit -m "Modify script" script.sh && @@ -126,7 +143,211 @@ test_expect_success 'git apply respects core.fileMode' ' test_grep ! "has type 100644, expected 100755" err && git apply --cached patch 2>err && - test_grep ! "has type 100644, expected 100755" err + test_grep ! "has type 100644, expected 100755" err && + git reset --hard +' + +test_expect_success 'setup: git apply [--reverse] warns about incorrect file modes' ' + test_config core.fileMode false && + + >mode_test && + git add --chmod=-x mode_test && + test_file_mode_staged 100644 mode_test && + test_tick && git commit -m "add mode_test" && + test_file_mode_HEAD 100644 mode_test && + git tag mode_test_forward_initial && + + echo content >>mode_test && + test_tick && git commit -m "append to mode_test" mode_test && + test_file_mode_HEAD 100644 mode_test && + git tag mode_test_reverse_initial && + + git format-patch -1 --stdout >patch && + test_grep "^index .* 100644$" patch +' + +test_expect_success 'git apply warns about incorrect file modes' ' + test_config core.fileMode false && + git reset --hard mode_test_forward_initial && + + git add --chmod=+x mode_test && + test_file_mode_staged 100755 mode_test && + test_tick && git commit -m "make mode_test executable" && + test_file_mode_HEAD 100755 mode_test && + + git apply --index patch 2>err && + test_grep "has type 100755, expected 100644" err && + test_file_mode_staged 100755 mode_test && + test_tick && git commit -m "redo: append to mode_test" && + test_file_mode_HEAD 100755 mode_test +' + +test_expect_success 'git apply --reverse warns about incorrect file modes' ' + test_config core.fileMode false && + git reset --hard mode_test_reverse_initial && + + git add --chmod=+x mode_test && + test_file_mode_staged 100755 mode_test && + test_tick && git commit -m "make mode_test executable" && + test_file_mode_HEAD 100755 mode_test && + + git apply --index --reverse patch 2>err && + test_grep "has type 100755, expected 100644" err && + test_file_mode_staged 100755 mode_test && + test_tick && git commit -m "undo: append to mode_test" && + test_file_mode_HEAD 100755 mode_test +' + +test_expect_success 'setup: git apply [--reverse] restores file modes (change_x_to_notx)' ' + test_config core.fileMode false && + + touch change_x_to_notx && + git add --chmod=+x change_x_to_notx && + test_file_mode_staged 100755 change_x_to_notx && + test_tick && git commit -m "add change_x_to_notx as executable" && + test_file_mode_HEAD 100755 change_x_to_notx && + + git add --chmod=-x change_x_to_notx && + test_file_mode_staged 100644 change_x_to_notx && + test_tick && git commit -m "make change_x_to_notx not executable" && + test_file_mode_HEAD 100644 change_x_to_notx && + + git rm change_x_to_notx && + test_file_mode_staged 000000 change_x_to_notx && + test_tick && git commit -m "remove change_x_to_notx" && + test_file_mode_HEAD 000000 change_x_to_notx && + + git format-patch -o patches -3 && + mv patches/0001-* change_x_to_notx-0001-create-0755.patch && + mv patches/0002-* change_x_to_notx-0002-chmod-0644.patch && + mv patches/0003-* change_x_to_notx-0003-delete.patch && + + test_grep "^new file mode 100755$" change_x_to_notx-0001-create-0755.patch && + test_grep "^old mode 100755$" change_x_to_notx-0002-chmod-0644.patch && + test_grep "^new mode 100644$" change_x_to_notx-0002-chmod-0644.patch && + test_grep "^deleted file mode 100644$" change_x_to_notx-0003-delete.patch && + + git tag change_x_to_notx_initial +' + +test_expect_success 'git apply restores file modes (change_x_to_notx)' ' + test_config core.fileMode false && + git reset --hard change_x_to_notx_initial && + + git apply --index change_x_to_notx-0001-create-0755.patch && + test_file_mode_staged 100755 change_x_to_notx && + test_tick && git commit -m "redo: add change_x_to_notx as executable" && + test_file_mode_HEAD 100755 change_x_to_notx && + + git apply --index change_x_to_notx-0002-chmod-0644.patch 2>err && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 100644 change_x_to_notx && + test_tick && git commit -m "redo: make change_x_to_notx not executable" && + test_file_mode_HEAD 100644 change_x_to_notx && + + git apply --index change_x_to_notx-0003-delete.patch 2>err && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 000000 change_x_to_notx && + test_tick && git commit -m "redo: remove change_notx_to_x" && + test_file_mode_HEAD 000000 change_x_to_notx +' + +test_expect_success 'git apply --reverse restores file modes (change_x_to_notx)' ' + test_config core.fileMode false && + git reset --hard change_x_to_notx_initial && + + git apply --index --reverse change_x_to_notx-0003-delete.patch && + test_file_mode_staged 100644 change_x_to_notx && + test_tick && git commit -m "undo: remove change_x_to_notx" && + test_file_mode_HEAD 100644 change_x_to_notx && + + git apply --index --reverse change_x_to_notx-0002-chmod-0644.patch 2>err && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 100755 change_x_to_notx && + test_tick && git commit -m "undo: make change_x_to_notx not executable" && + test_file_mode_HEAD 100755 change_x_to_notx && + + git apply --index --reverse change_x_to_notx-0001-create-0755.patch 2>err && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 000000 change_x_to_notx && + test_tick && git commit -m "undo: add change_x_to_notx as executable" && + test_file_mode_HEAD 000000 change_x_to_notx +' + +test_expect_success 'setup: git apply [--reverse] restores file modes (change_notx_to_x)' ' + test_config core.fileMode false && + + touch change_notx_to_x && + git add --chmod=-x change_notx_to_x && + test_file_mode_staged 100644 change_notx_to_x && + test_tick && git commit -m "add change_notx_to_x as not executable" && + test_file_mode_HEAD 100644 change_notx_to_x && + + git add --chmod=+x change_notx_to_x && + test_file_mode_staged 100755 change_notx_to_x && + test_tick && git commit -m "make change_notx_to_x executable" && + test_file_mode_HEAD 100755 change_notx_to_x && + + git rm change_notx_to_x && + test_file_mode_staged 000000 change_notx_to_x && + test_tick && git commit -m "remove change_notx_to_x" && + test_file_mode_HEAD 000000 change_notx_to_x && + + git format-patch -o patches -3 && + mv patches/0001-* change_notx_to_x-0001-create-0644.patch && + mv patches/0002-* change_notx_to_x-0002-chmod-0755.patch && + mv patches/0003-* change_notx_to_x-0003-delete.patch && + + test_grep "^new file mode 100644$" change_notx_to_x-0001-create-0644.patch && + test_grep "^old mode 100644$" change_notx_to_x-0002-chmod-0755.patch && + test_grep "^new mode 100755$" change_notx_to_x-0002-chmod-0755.patch && + test_grep "^deleted file mode 100755$" change_notx_to_x-0003-delete.patch && + + git tag change_notx_to_x_initial +' + +test_expect_success 'git apply restores file modes (change_notx_to_x)' ' + test_config core.fileMode false && + git reset --hard change_notx_to_x_initial && + + git apply --index change_notx_to_x-0001-create-0644.patch && + test_file_mode_staged 100644 change_notx_to_x && + test_tick && git commit -m "redo: add change_notx_to_x as not executable" && + test_file_mode_HEAD 100644 change_notx_to_x && + + git apply --index change_notx_to_x-0002-chmod-0755.patch 2>err && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 100755 change_notx_to_x && + test_tick && git commit -m "redo: make change_notx_to_x executable" && + test_file_mode_HEAD 100755 change_notx_to_x && + + git apply --index change_notx_to_x-0003-delete.patch && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 000000 change_notx_to_x && + test_tick && git commit -m "undo: remove change_notx_to_x" && + test_file_mode_HEAD 000000 change_notx_to_x +' + +test_expect_success 'git apply --reverse restores file modes (change_notx_to_x)' ' + test_config core.fileMode false && + git reset --hard change_notx_to_x_initial && + + git apply --index --reverse change_notx_to_x-0003-delete.patch && + test_file_mode_staged 100755 change_notx_to_x && + test_tick && git commit -m "undo: remove change_notx_to_x" && + test_file_mode_HEAD 100755 change_notx_to_x && + + git apply --index --reverse change_notx_to_x-0002-chmod-0755.patch 2>err && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 100644 change_notx_to_x && + test_tick && git commit -m "undo: make change_notx_to_x executable" && + test_file_mode_HEAD 100644 change_notx_to_x && + + git apply --index --reverse change_notx_to_x-0001-create-0644.patch 2>err && + test_grep ! "has type 100.*, expected 100.*" err && + test_file_mode_staged 000000 change_notx_to_x && + test_tick && git commit -m "undo: add change_notx_to_x as not executable" && + test_file_mode_HEAD 000000 change_notx_to_x ' test_expect_success POSIXPERM 'patch mode for new file is canonicalized' '