diff options
43 files changed, 674 insertions, 296 deletions
diff --git a/Documentation/RelNotes/2.25.1.txt b/Documentation/RelNotes/2.25.1.txt new file mode 100644 index 0000000000..cd869b02bb --- /dev/null +++ b/Documentation/RelNotes/2.25.1.txt @@ -0,0 +1,55 @@ +Git 2.25.1 Release Notes +======================== + +Fixes since v2.25 +----------------- + + * "git commit" gives output similar to "git status" when there is + nothing to commit, but without honoring the advise.statusHints + configuration variable, which has been corrected. + + * has_object_file() said "no" given an object registered to the + system via pretend_object_file(), making it inconsistent with + read_object_file(), causing lazy fetch to attempt fetching an + empty tree from promisor remotes. + + * The code that tries to skip over the entries for the paths in a + single directory using the cache-tree was not careful enough + against corrupt index file. + + * Complete an update to tutorial that encourages "git switch" over + "git checkout" that was done only half-way. + + * Reduce unnecessary round-trip when running "ls-remote" over the + stateless RPC mechanism. + + * "git restore --staged" did not correctly update the cache-tree + structure, resulting in bogus trees to be written afterwards, which + has been corrected. + + * The code recently added to move to the entry beyond the ones in the + same directory in the index in the sparse-cone mode did not count + the number of entries to skip over incorrectly, which has been + corrected. + + * Work around test breakages caused by custom regex engine used in + libasan, when address sanitizer is used with more recent versions + of gcc and clang. + + * "git fetch --refmap=" option has got a better documentation. + + * Corner case bugs in "git clean" that stems from a (necessarily for + performance reasons) awkward calling convention in the directory + enumeration API has been corrected. + + * "git grep --no-index" should not get affected by the contents of + the .gitmodules file but when "--recurse-submodules" is given or + the "submodule.recurse" variable is set, it did. Now these + settings are ignored in the "--no-index" mode. + + * Technical details of the bundle format has been documented. + + * Unhelpful warning messages during documentation build have been + squelched. + +Also contains various documentation updates, code clean-ups and minor fixups. diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt index 0a0e000569..dffd4d5f7b 100644 --- a/Documentation/config/push.txt +++ b/Documentation/config/push.txt @@ -79,7 +79,7 @@ higher priority configuration file (e.g. `.git/config` in a repository) to clear the values inherited from a lower priority configuration files (e.g. `$HOME/.gitconfig`). + --- +---- Example: @@ -96,7 +96,7 @@ repo/.git/config This will result in only b (a and c are cleared). --- +---- push.recurseSubmodules:: Make sure all submodule commits used by the revisions to be pushed diff --git a/Documentation/doc-diff b/Documentation/doc-diff index 88a9b20168..1694300e50 100755 --- a/Documentation/doc-diff +++ b/Documentation/doc-diff @@ -127,7 +127,7 @@ generate_render_makefile () { while read src do dst=$2/${src#$1/} - printf 'all:: %s\n' "$dst" + printf 'all: %s\n' "$dst" printf '%s: %s\n' "$dst" "$src" printf '\t@echo >&2 " RENDER $(notdir $@)" && \\\n' printf '\tmkdir -p $(dir $@) && \\\n' diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt index 8b2d49c79e..0c3924a63d 100644 --- a/Documentation/git-check-ignore.txt +++ b/Documentation/git-check-ignore.txt @@ -30,9 +30,15 @@ OPTIONS valid with a single pathname. -v, --verbose:: - Also output details about the matching pattern (if any) - for each given pathname. For precedence rules within and - between exclude sources, see linkgit:gitignore[5]. + Instead of printing the paths that are excluded, for each path + that matches an exclude pattern, print the exclude pattern + together with the path. (Matching an exclude pattern usually + means the path is excluded, but if the pattern begins with '!' + then it is a negated pattern and matching it means the path is + NOT excluded.) ++ +For precedence rules within and between exclude sources, see +linkgit:gitignore[5]. --stdin:: Read pathnames from the standard input, one per line, diff --git a/Documentation/git-ls-remote.txt b/Documentation/git-ls-remote.txt index a2ea1fd687..0a5c8b7d49 100644 --- a/Documentation/git-ls-remote.txt +++ b/Documentation/git-ls-remote.txt @@ -28,7 +28,9 @@ OPTIONS Limit to only refs/heads and refs/tags, respectively. These options are _not_ mutually exclusive; when given both, references stored in refs/heads and refs/tags are - displayed. + displayed. Note that `git ls-remote -h` used without + anything else on the command line gives help, consistent + with other git subcommands. --refs:: Do not show peeled tags or pseudorefs like `HEAD` in the output. diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index 373cfa2264..92e4ba6a2f 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -126,6 +126,11 @@ usage: git describe [<options>] <commit-ish>* --long always use long format --abbrev[=<n>] use <n> digits to display SHA-1s --------------------------------------------- ++ +Note that some subcommand (e.g. `git grep`) may behave differently +when there are things on the command line other than `-h`, but `git +subcmd -h` without anything else on the command line is meant to +consistently give the usage. --help-all:: Some Git commands take options that are only used for plumbing or that diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 2b7f62b8bf..2a8e709916 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.25.0 +DEF_VER=v2.25.1 LF=' ' @@ -1 +1 @@ -Documentation/RelNotes/2.25.0.txt
\ No newline at end of file +Documentation/RelNotes/2.25.1.txt
\ No newline at end of file diff --git a/add-interactive.c b/add-interactive.c index 6a5048c83e..143e694724 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -326,7 +326,10 @@ static ssize_t list_and_choose(struct add_i_state *s, if (endp == p + sep) to = from + 1; else if (*endp == '-') { - to = strtoul(++endp, &endp, 10); + if (isdigit(*(++endp))) + to = strtoul(endp, &endp, 10); + else + to = items->items.nr; /* extra characters after the range? */ if (endp != p + sep) from = -1; @@ -913,7 +916,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps, opts->prompt = N_("Patch update"); count = list_and_choose(s, files, opts); - if (count >= 0) { + if (count > 0) { struct argv_array args = ARGV_ARRAY_INIT; struct pathspec ps_selected = { 0 }; @@ -954,7 +957,7 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps, opts->flags = IMMEDIATE; count = list_and_choose(s, files, opts); opts->flags = 0; - if (count >= 0) { + if (count > 0) { struct argv_array args = ARGV_ARRAY_INIT; argv_array_pushl(&args, "git", "diff", "-p", "--cached", diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 5a4f92395f..ea5d0ae3a6 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -108,6 +108,9 @@ static int check_ignore(struct dir_struct *dir, int dtype = DT_UNKNOWN; pattern = last_matching_pattern(dir, &the_index, full_path, &dtype); + if (!verbose && pattern && + pattern->flags & PATTERN_FLAG_NEGATIVE) + pattern = NULL; } if (!quiet && (pattern || show_non_matching)) output_pattern(pathspec.items[i].original, pattern); diff --git a/builtin/fetch.c b/builtin/fetch.c index b4c6d921d0..fd69c4f69d 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -335,6 +335,7 @@ static void find_non_local_tags(const struct ref *refs, struct string_list_item *remote_ref_item; const struct ref *ref; struct refname_hash_entry *item = NULL; + const int quick_flags = OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT; refname_hash_init(&existing_refs); refname_hash_init(&remote_refs); @@ -353,10 +354,9 @@ static void find_non_local_tags(const struct ref *refs, */ if (ends_with(ref->name, "^{}")) { if (item && - !has_object_file_with_flags(&ref->old_oid, - OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&ref->old_oid, quick_flags) && !oidset_contains(&fetch_oids, &ref->old_oid) && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); item = NULL; @@ -370,7 +370,7 @@ static void find_non_local_tags(const struct ref *refs, * fetch. */ if (item && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); @@ -391,7 +391,7 @@ static void find_non_local_tags(const struct ref *refs, * checked to see if it needs fetching. */ if (item && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 05a92c59d8..736f666f64 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -494,7 +494,6 @@ static void fmt_merge_msg_sigs(struct strbuf *out) enum object_type type; unsigned long size, len; char *buf = read_object_file(oid, &type, &size); - struct signature_check sigc = { 0 }; struct strbuf sig = STRBUF_INIT; if (!buf || type != OBJ_TAG) @@ -503,12 +502,10 @@ static void fmt_merge_msg_sigs(struct strbuf *out) if (size == len) ; /* merely annotated */ - else if (!check_signature(buf, len, buf + len, size - len, - &sigc)) { - strbuf_addstr(&sig, sigc.gpg_output); - signature_check_clear(&sigc); - } else - strbuf_addstr(&sig, "gpg verification failed.\n"); + else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) { + if (!sig.len) + strbuf_addstr(&sig, "gpg verification failed.\n"); + } if (!tag_number++) { fmt_tag_signature(&tagbuf, &sig, buf, len); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 60a5591039..41a7c11c8e 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1003,7 +1003,9 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, base->obj->real_type)) - BUG("child->real_type != OBJ_REF_DELTA"); + die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)", + (uintmax_t)child->idx.offset, + oid_to_hex(&base->obj->idx.oid)); resolve_delta(child, base, result); if (base->ref_first == base->ref_last && base->ofs_last == -1) diff --git a/compat/mingw.c b/compat/mingw.c index 402c1ad91c..b5230149db 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -13,6 +13,19 @@ 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; + + argv_array_pushl(&cp.args, "mintty", "gdb", NULL); + argv_array_pushf(&cp.args, "--pid=%d", getpid()); + cp.clean_on_exit = 1; + if (start_command(&cp) < 0) + die_errno("Could not start gdb"); + sleep(1); +} + int err_win_to_posix(DWORD winerr) { int error = ENOSYS; diff --git a/compat/mingw.h b/compat/mingw.h index 714bc1d591..e6fe810ba9 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -599,6 +599,16 @@ int wmain(int argc, const wchar_t **w_argv); int main(int argc, const char **argv); /* + * For debugging: if a problem occurs, say, in a Git process that is spawned + * from another Git process which in turn is spawned from yet another Git + * process, it can be quite daunting to figure out what is going on. + * + * Call this function to open a new MinTTY (this assumes you are in Git for + * Windows' SDK) with a GDB that attaches to the current process right away. + */ +extern void open_in_gdb(void); + +/* * Used by Pthread API implementation for Windows */ int err_win_to_posix(DWORD winerr); diff --git a/compat/obstack.h b/compat/obstack.h index 01e7c81840..f90a46d9b9 100644 --- a/compat/obstack.h +++ b/compat/obstack.h @@ -135,8 +135,10 @@ extern "C" { alignment relative to 0. */ #define __PTR_ALIGN(B, P, A) \ - __BPTR_ALIGN (sizeof (PTR_INT_TYPE) < sizeof (void *) ? (B) : (char *) 0, \ - P, A) + (sizeof (PTR_INT_TYPE) < sizeof(void *) ? \ + __BPTR_ALIGN((B), (P), (A)) : \ + (void *)__BPTR_ALIGN((PTR_INT_TYPE)(void *)0, (PTR_INT_TYPE)(P), (A)) \ + ) #include <string.h> diff --git a/compat/poll/poll.c b/compat/poll/poll.c index 0e95dd493c..afa6d24584 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c @@ -139,22 +139,10 @@ win32_compute_revents (HANDLE h, int *p_sought) INPUT_RECORD *irbuffer; DWORD avail, nbuffer; BOOL bRet; - IO_STATUS_BLOCK iosb; - FILE_PIPE_LOCAL_INFORMATION fpli; - static PNtQueryInformationFile NtQueryInformationFile; - static BOOL once_only; switch (GetFileType (h)) { case FILE_TYPE_PIPE: - if (!once_only) - { - NtQueryInformationFile = (PNtQueryInformationFile)(void (*)(void)) - GetProcAddress (GetModuleHandleW (L"ntdll.dll"), - "NtQueryInformationFile"); - once_only = TRUE; - } - happened = 0; if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0) { @@ -166,22 +154,9 @@ win32_compute_revents (HANDLE h, int *p_sought) else { - /* It was the write-end of the pipe. Check if it is writable. - If NtQueryInformationFile fails, optimistically assume the pipe is - writable. This could happen on Win9x, where NtQueryInformationFile - is not available, or if we inherit a pipe that doesn't permit - FILE_READ_ATTRIBUTES access on the write end (I think this should - not happen since WinXP SP2; WINE seems fine too). Otherwise, - ensure that enough space is available for atomic writes. */ - memset (&iosb, 0, sizeof (iosb)); - memset (&fpli, 0, sizeof (fpli)); - - if (!NtQueryInformationFile - || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli), - FilePipeLocalInformation) - || fpli.WriteQuotaAvailable >= PIPE_BUF - || (fpli.OutboundQuota < PIPE_BUF && - fpli.WriteQuotaAvailable == fpli.OutboundQuota)) + /* It was the write-end of the pipe. Unfortunately there is no + reliable way of knowing if it can be written without blocking. + Just say that it's all good. */ happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND); } return happened; diff --git a/gpg-interface.c b/gpg-interface.c index 5134ce2780..131e7d529e 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -207,55 +207,6 @@ found_duplicate_status: FREE_AND_NULL(sigc->key); } -static int verify_signed_buffer(const char *payload, size_t payload_size, - const char *signature, size_t signature_size, - struct strbuf *gpg_output, - struct strbuf *gpg_status) -{ - struct child_process gpg = CHILD_PROCESS_INIT; - struct gpg_format *fmt; - struct tempfile *temp; - int ret; - struct strbuf buf = STRBUF_INIT; - - temp = mks_tempfile_t(".git_vtag_tmpXXXXXX"); - if (!temp) - return error_errno(_("could not create temporary file")); - if (write_in_full(temp->fd, signature, signature_size) < 0 || - close_tempfile_gently(temp) < 0) { - error_errno(_("failed writing detached signature to '%s'"), - temp->filename.buf); - delete_tempfile(&temp); - return -1; - } - - fmt = get_format_by_sig(signature); - if (!fmt) - BUG("bad signature '%s'", signature); - - argv_array_push(&gpg.args, fmt->program); - argv_array_pushv(&gpg.args, fmt->verify_args); - argv_array_pushl(&gpg.args, - "--status-fd=1", - "--verify", temp->filename.buf, "-", - NULL); - - if (!gpg_status) - gpg_status = &buf; - - sigchain_push(SIGPIPE, SIG_IGN); - ret = pipe_command(&gpg, payload, payload_size, - gpg_status, 0, gpg_output, 0); - sigchain_pop(SIGPIPE); - - delete_tempfile(&temp); - - ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG "); - strbuf_release(&buf); /* no matter it was used or not */ - - return ret; -} - int check_signature(const char *payload, size_t plen, const char *signature, size_t slen, struct signature_check *sigc) { @@ -400,3 +351,51 @@ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *sig return 0; } + +int verify_signed_buffer(const char *payload, size_t payload_size, + const char *signature, size_t signature_size, + struct strbuf *gpg_output, struct strbuf *gpg_status) +{ + struct child_process gpg = CHILD_PROCESS_INIT; + struct gpg_format *fmt; + struct tempfile *temp; + int ret; + struct strbuf buf = STRBUF_INIT; + + temp = mks_tempfile_t(".git_vtag_tmpXXXXXX"); + if (!temp) + return error_errno(_("could not create temporary file")); + if (write_in_full(temp->fd, signature, signature_size) < 0 || + close_tempfile_gently(temp) < 0) { + error_errno(_("failed writing detached signature to '%s'"), + temp->filename.buf); + delete_tempfile(&temp); + return -1; + } + + fmt = get_format_by_sig(signature); + if (!fmt) + BUG("bad signature '%s'", signature); + + argv_array_push(&gpg.args, fmt->program); + argv_array_pushv(&gpg.args, fmt->verify_args); + argv_array_pushl(&gpg.args, + "--status-fd=1", + "--verify", temp->filename.buf, "-", + NULL); + + if (!gpg_status) + gpg_status = &buf; + + sigchain_push(SIGPIPE, SIG_IGN); + ret = pipe_command(&gpg, payload, payload_size, + gpg_status, 0, gpg_output, 0); + sigchain_pop(SIGPIPE); + + delete_tempfile(&temp); + + ret |= !strstr(gpg_status->buf, "\n[GNUPG:] GOODSIG "); + strbuf_release(&buf); /* no matter it was used or not */ + + return ret; +} diff --git a/gpg-interface.h b/gpg-interface.h index 93cc3aff5c..3e624ec289 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -46,6 +46,15 @@ size_t parse_signature(const char *buf, size_t size); int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); +/* + * Run "gpg" to see if the payload matches the detached signature. + * gpg_output, when set, receives the diagnostic output from GPG. + * gpg_status, when set, receives the status output from GPG. + */ +int verify_signed_buffer(const char *payload, size_t payload_size, + const char *signature, size_t signature_size, + struct strbuf *gpg_output, struct strbuf *gpg_status); + int git_gpg_config(const char *, const char *, void *); void set_signing_key(const char *); const char *get_signing_key(void); diff --git a/log-tree.c b/log-tree.c index 4e32638de8..32d17c941b 100644 --- a/log-tree.c +++ b/log-tree.c @@ -449,22 +449,22 @@ static void show_signature(struct rev_info *opt, struct commit *commit) { struct strbuf payload = STRBUF_INIT; struct strbuf signature = STRBUF_INIT; - struct signature_check sigc = { 0 }; + struct strbuf gpg_output = STRBUF_INIT; int status; if (parse_signed_commit(commit, &payload, &signature) <= 0) goto out; - status = check_signature(payload.buf, payload.len, signature.buf, - signature.len, &sigc); - if (status && sigc.result == 'N') - show_sig_lines(opt, status, "No signature\n"); - else { - show_sig_lines(opt, status, sigc.gpg_output); - signature_check_clear(&sigc); - } + status = verify_signed_buffer(payload.buf, payload.len, + signature.buf, signature.len, + &gpg_output, NULL); + if (status && !gpg_output.len) + strbuf_addstr(&gpg_output, "No signature\n"); + + show_sig_lines(opt, status, gpg_output.buf); out: + strbuf_release(&gpg_output); strbuf_release(&payload); strbuf_release(&signature); } @@ -497,7 +497,6 @@ static int show_one_mergetag(struct commit *commit, struct object_id oid; struct tag *tag; struct strbuf verify_message; - struct signature_check sigc = { 0 }; int status, nth; size_t payload_size, gpg_message_offset; @@ -516,7 +515,7 @@ static int show_one_mergetag(struct commit *commit, "merged tag '%s'\n", tag->tag); else if ((nth = which_parent(&tag->tagged->oid, commit)) < 0) strbuf_addf(&verify_message, "tag %s names a non-parent %s\n", - tag->tag, tag->tagged->oid.hash); + tag->tag, oid_to_hex(&tag->tagged->oid)); else strbuf_addf(&verify_message, "parent #%d, tagged '%s'\n", nth + 1, tag->tag); @@ -526,13 +525,12 @@ static int show_one_mergetag(struct commit *commit, status = -1; if (extra->len > payload_size) { /* could have a good signature */ - if (!check_signature(extra->value, payload_size, - extra->value + payload_size, - extra->len - payload_size, &sigc)) { - strbuf_addstr(&verify_message, sigc.gpg_output); - signature_check_clear(&sigc); + if (!verify_signed_buffer(extra->value, payload_size, + extra->value + payload_size, + extra->len - payload_size, + &verify_message, NULL)) status = 0; /* good */ - } else if (verify_message.len <= gpg_message_offset) + else if (verify_message.len <= gpg_message_offset) strbuf_addstr(&verify_message, "No signature\n"); /* otherwise we couldn't verify, which is shown as bad */ } diff --git a/merge-recursive.c b/merge-recursive.c index 10dca5644b..e6f943c5cc 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -998,10 +998,13 @@ static int update_file_flags(struct merge_options *opt, free(buf); } update_index: - if (!ret && update_cache) - if (add_cacheinfo(opt, contents, path, 0, update_wd, + if (!ret && update_cache) { + int refresh = (!opt->priv->call_depth && + contents->mode != S_IFGITLINK); + if (add_cacheinfo(opt, contents, path, 0, refresh, ADD_CACHE_OK_TO_ADD)) return -1; + } return ret; } @@ -1712,6 +1715,14 @@ static char *find_path_for_conflict(struct merge_options *opt, return new_path; } +/* + * Toggle the stage number between "ours" and "theirs" (2 and 3). + */ +static inline int flip_stage(int stage) +{ + return (2 + 3) - stage; +} + static int handle_rename_rename_1to2(struct merge_options *opt, struct rename_conflict_info *ci) { @@ -1756,14 +1767,14 @@ static int handle_rename_rename_1to2(struct merge_options *opt, * such cases, we should keep the added file around, * resolving the conflict at that path in its favor. */ - add = &ci->ren1->dst_entry->stages[2 ^ 1]; + add = &ci->ren1->dst_entry->stages[flip_stage(2)]; if (is_valid(add)) { if (update_file(opt, 0, add, a->path)) return -1; } else remove_file_from_index(opt->repo->index, a->path); - add = &ci->ren2->dst_entry->stages[3 ^ 1]; + add = &ci->ren2->dst_entry->stages[flip_stage(3)]; if (is_valid(add)) { if (update_file(opt, 0, add, b->path)) return -1; @@ -1776,7 +1787,7 @@ static int handle_rename_rename_1to2(struct merge_options *opt, * rename/add collision. If not, we can write the file out * to the specified location. */ - add = &ci->ren1->dst_entry->stages[2 ^ 1]; + add = &ci->ren1->dst_entry->stages[flip_stage(2)]; if (is_valid(add)) { add->path = mfi.blob.path = a->path; if (handle_file_collision(opt, a->path, @@ -1797,7 +1808,7 @@ static int handle_rename_rename_1to2(struct merge_options *opt, return -1; } - add = &ci->ren2->dst_entry->stages[3 ^ 1]; + add = &ci->ren2->dst_entry->stages[flip_stage(3)]; if (is_valid(add)) { add->path = mfi.blob.path = b->path; if (handle_file_collision(opt, b->path, @@ -1846,7 +1857,7 @@ static int handle_rename_rename_2to1(struct merge_options *opt, path_side_1_desc = xstrfmt("version of %s from %s", path, a->path); path_side_2_desc = xstrfmt("version of %s from %s", path, b->path); ostage1 = ci->ren1->branch == opt->branch1 ? 3 : 2; - ostage2 = ostage1 ^ 1; + ostage2 = flip_stage(ostage1); ci->ren1->src_entry->stages[ostage1].path = a->path; ci->ren2->src_entry->stages[ostage2].path = b->path; if (merge_mode_and_contents(opt, a, c1, @@ -576,16 +576,16 @@ redo: * the note tree that have not yet been explored. There * is a direct relationship between subtree entries at * level 'n' in the tree, and the 'fanout' variable: - * Subtree entries at level 'n <= 2 * fanout' should be + * Subtree entries at level 'n < 2 * fanout' should be * preserved, since they correspond exactly to a fanout * directory in the on-disk structure. However, subtree - * entries at level 'n > 2 * fanout' should NOT be + * entries at level 'n >= 2 * fanout' should NOT be * preserved, but rather consolidated into the above * notes tree level. We achieve this by unconditionally * unpacking subtree entries that exist below the * threshold level at 'n = 2 * fanout'. */ - if (n <= 2 * fanout && + if (n < 2 * fanout && flags & FOR_EACH_NOTE_YIELD_SUBTREES) { /* invoke callback with subtree */ unsigned int path_len = @@ -602,7 +602,7 @@ redo: path, cb_data); } - if (n > fanout * 2 || + if (n >= 2 * fanout || !(flags & FOR_EACH_NOTE_DONT_UNPACK_SUBTREES)) { /* unpack subtree and resume traversal */ tree->a[i] = NULL; @@ -723,13 +723,15 @@ static int write_each_note_helper(struct tree_write_stack *tws, struct write_each_note_data { struct tree_write_stack *root; - struct non_note *next_non_note; + struct non_note **nn_list; + struct non_note *nn_prev; }; static int write_each_non_note_until(const char *note_path, struct write_each_note_data *d) { - struct non_note *n = d->next_non_note; + struct non_note *p = d->nn_prev; + struct non_note *n = p ? p->next : *d->nn_list; int cmp = 0, ret; while (n && (!note_path || (cmp = strcmp(n->path, note_path)) <= 0)) { if (note_path && cmp == 0) @@ -740,9 +742,10 @@ static int write_each_non_note_until(const char *note_path, if (ret) return ret; } + p = n; n = n->next; } - d->next_non_note = n; + d->nn_prev = p; return 0; } @@ -1177,7 +1180,8 @@ int write_notes_tree(struct notes_tree *t, struct object_id *result) strbuf_init(&root.buf, 256 * (32 + the_hash_algo->hexsz)); /* assume 256 entries */ root.path[0] = root.path[1] = '\0'; cb_data.root = &root; - cb_data.next_non_note = t->first_non_note; + cb_data.nn_list = &(t->first_non_note); + cb_data.nn_prev = NULL; /* Write tree objects representing current notes tree */ flags = FOR_EACH_NOTE_DONT_UNPACK_SUBTREES | diff --git a/pathspec.c b/pathspec.c index 128f27fcb7..8243e06eab 100644 --- a/pathspec.c +++ b/pathspec.c @@ -438,8 +438,13 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags, } else { match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom); - if (!match) - die(_("%s: '%s' is outside repository"), elt, copyfrom); + if (!match) { + const char *hint_path = get_git_work_tree(); + if (!hint_path) + hint_path = get_git_dir(); + die(_("%s: '%s' is outside repository at '%s'"), elt, + copyfrom, absolute_path(hint_path)); + } } item->match = match; diff --git a/rebase-interactive.c b/rebase-interactive.c index aa18ae82b7..1259adc8ea 100644 --- a/rebase-interactive.c +++ b/rebase-interactive.c @@ -104,9 +104,11 @@ int edit_todo_list(struct repository *r, struct todo_list *todo_list, -1, flags | TODO_LIST_SHORTEN_IDS | TODO_LIST_APPEND_TODO_HELP)) return error_errno(_("could not write '%s'"), todo_file); - if (initial && copy_file(rebase_path_todo_backup(), todo_file, 0666)) - return error(_("could not copy '%s' to '%s'."), todo_file, - rebase_path_todo_backup()); + if (initial && + todo_list_write_to_file(r, todo_list, rebase_path_todo_backup(), + shortrevisions, shortonto, -1, + (flags | TODO_LIST_APPEND_TODO_HELP) & ~TODO_LIST_SHORTEN_IDS) < 0) + return error(_("could not write '%s'."), rebase_path_todo_backup()); if (launch_sequence_editor(todo_file, &new_todo->buf, NULL)) return -2; diff --git a/sequencer.c b/sequencer.c index b9dbf1adb0..df6d18f9ab 100644 --- a/sequencer.c +++ b/sequencer.c @@ -588,7 +588,7 @@ static int do_recursive_merge(struct repository *r, struct merge_options o; struct tree *next_tree, *base_tree, *head_tree; int clean; - char **xopt; + int i; struct lock_file index_lock = LOCK_INIT; if (repo_hold_locked_index(r, &index_lock, LOCK_REPORT_ON_ERROR) < 0) @@ -608,8 +608,8 @@ static int do_recursive_merge(struct repository *r, next_tree = next ? get_commit_tree(next) : empty_tree(r); base_tree = base ? get_commit_tree(base) : empty_tree(r); - for (xopt = opts->xopts; xopt != opts->xopts + opts->xopts_nr; xopt++) - parse_merge_opt(&o, *xopt); + for (i = 0; i < opts->xopts_nr; i++) + parse_merge_opt(&o, opts->xopts[i]); clean = merge_trees(&o, head_tree, @@ -2118,6 +2118,8 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, saved = *end_of_object_name; *end_of_object_name = '\0'; status = get_oid(bol, &commit_oid); + if (status < 0) + error(_("could not parse '%s'"), bol); /* return later */ *end_of_object_name = saved; bol = end_of_object_name + strspn(end_of_object_name, " \t"); @@ -2125,11 +2127,10 @@ static int parse_insn_line(struct repository *r, struct todo_item *item, item->arg_len = (int)(eol - bol); if (status < 0) - return error(_("could not parse '%.*s'"), - (int)(end_of_object_name - bol), bol); + return status; item->commit = lookup_commit_reference(r, &commit_oid); - return !item->commit; + return item->commit ? 0 : -1; } int sequencer_get_last_command(struct repository *r, enum replay_action *action) @@ -5075,7 +5076,7 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla { const char *shortonto, *todo_file = rebase_path_todo(); struct todo_list new_todo = TODO_LIST_INIT; - struct strbuf *buf = &todo_list->buf; + struct strbuf *buf = &todo_list->buf, buf2 = STRBUF_INIT; struct object_id oid = onto->object.oid; int res; @@ -5127,6 +5128,15 @@ int complete_action(struct repository *r, struct replay_opts *opts, unsigned fla return -1; } + /* Expand the commit IDs */ + todo_list_to_strbuf(r, &new_todo, &buf2, -1, 0); + strbuf_swap(&new_todo.buf, &buf2); + strbuf_release(&buf2); + new_todo.total_nr -= new_todo.nr; + if (todo_list_parse_insn_buffer(r, new_todo.buf.buf, &new_todo) < 0) + BUG("invalid todo list after expanding IDs:\n%s", + new_todo.buf.buf); + if (opts->allow_ff && skip_unnecessary_picks(r, &new_todo, &oid)) { todo_list_release(&new_todo); return error(_("could not skip unnecessary pick commands")); @@ -120,8 +120,13 @@ char *prefix_path_gently(const char *prefix, int len, char *prefix_path(const char *prefix, int len, const char *path) { char *r = prefix_path_gently(prefix, len, NULL, path); - if (!r) - die(_("'%s' is outside repository"), path); + if (!r) { + const char *hint_path = get_git_work_tree(); + if (!hint_path) + hint_path = get_git_dir(); + die(_("'%s' is outside repository at '%s'"), path, + absolute_path(hint_path)); + } return r; } diff --git a/submodule.c b/submodule.c index 6b867445f0..9f4c4c08d1 100644 --- a/submodule.c +++ b/submodule.c @@ -82,7 +82,7 @@ int is_staging_gitmodules_ok(struct index_state *istate) if ((pos >= 0) && (pos < istate->cache_nr)) { struct stat st; if (lstat(GITMODULES_FILE, &st) == 0 && - ie_match_stat(istate, istate->cache[pos], &st, 0) & DATA_CHANGED) + ie_modified(istate, istate->cache[pos], &st, 0) & DATA_CHANGED) return 0; } @@ -1811,7 +1811,7 @@ out: void submodule_unset_core_worktree(const struct submodule *sub) { char *config_path = xstrfmt("%s/modules/%s/config", - get_git_common_dir(), sub->name); + get_git_dir(), sub->name); if (git_config_set_in_file_gently(config_path, "core.worktree", NULL)) warning(_("Could not unset core.worktree setting in submodule '%s'"), @@ -1914,7 +1914,7 @@ int submodule_move_head(const char *path, ABSORB_GITDIR_RECURSE_SUBMODULES); } else { char *gitdir = xstrfmt("%s/modules/%s", - get_git_common_dir(), sub->name); + get_git_dir(), sub->name); connect_work_tree_and_git_dir(path, gitdir, 0); free(gitdir); @@ -1924,7 +1924,7 @@ int submodule_move_head(const char *path, if (old_head && (flags & SUBMODULE_MOVE_HEAD_FORCE)) { char *gitdir = xstrfmt("%s/modules/%s", - get_git_common_dir(), sub->name); + get_git_dir(), sub->name); connect_work_tree_and_git_dir(path, gitdir, 1); free(gitdir); } diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index 1744cee5e9..370a389e5c 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -424,9 +424,24 @@ test_expect_success 'local ignore inside a sub-directory with --verbose' ' ) ' -test_expect_success_multi 'nested include' \ - 'a/b/.gitignore:8:!on* a/b/one' ' - test_check_ignore "a/b/one" +test_expect_success 'nested include of negated pattern' ' + expect "" && + test_check_ignore "a/b/one" 1 +' + +test_expect_success 'nested include of negated pattern with -q' ' + expect "" && + test_check_ignore "-q a/b/one" 1 +' + +test_expect_success 'nested include of negated pattern with -v' ' + expect "a/b/.gitignore:8:!on* a/b/one" && + test_check_ignore "-v a/b/one" 0 +' + +test_expect_success 'nested include of negated pattern with -v -n' ' + expect "a/b/.gitignore:8:!on* a/b/one" && + test_check_ignore "-v -n a/b/one" 0 ' ############################################################################ @@ -460,7 +475,6 @@ test_expect_success 'cd to ignored sub-directory' ' expect_from_stdin <<-\EOF && foo twoooo - ../one seven ../../one EOF @@ -543,7 +557,6 @@ test_expect_success 'global ignore' ' globalthree a/globalthree a/per-repo - globaltwo EOF test_check_ignore "globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo" ' @@ -586,17 +599,7 @@ EOF cat <<-\EOF >expected-default one a/one - a/b/on - a/b/one - a/b/one one - a/b/one two - "a/b/one\"three" - a/b/two a/b/twooo - globaltwo - a/globaltwo - a/b/globaltwo - b/globaltwo EOF cat <<-EOF >expected-verbose .gitignore:1:one one @@ -696,8 +699,12 @@ cat <<-EOF >expected-all $global_excludes:2:!globaltwo ../b/globaltwo :: c/not-ignored EOF +cat <<-EOF >expected-default +../one +one +b/twooo +EOF grep -v '^:: ' expected-all >expected-verbose -sed -e 's/.* //' expected-verbose >expected-default broken_c_unquote stdin >stdin0 diff --git a/t/t2405-worktree-submodule.sh b/t/t2405-worktree-submodule.sh new file mode 100755 index 0000000000..e1b2bfd87e --- /dev/null +++ b/t/t2405-worktree-submodule.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +test_description='Combination of submodules and multiple worktrees' + +. ./test-lib.sh + +base_path=$(pwd -P) + +test_expect_success 'setup: create origin repos' ' + git init origin/sub && + test_commit -C origin/sub file1 && + git init origin/main && + test_commit -C origin/main first && + git -C origin/main submodule add ../sub && + git -C origin/main commit -m "add sub" && + test_commit -C origin/sub "file1 updated" file1 file1updated file1updated && + git -C origin/main/sub pull && + git -C origin/main add sub && + git -C origin/main commit -m "sub updated" +' + +test_expect_success 'setup: clone superproject to create main worktree' ' + git clone --recursive "$base_path/origin/main" main +' + +rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1") +rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1") + +test_expect_success 'add superproject worktree' ' + git -C main worktree add "$base_path/worktree" "$rev1_hash_main" +' + +test_expect_failure 'submodule is checked out just after worktree add' ' + git -C worktree diff --submodule master"^!" >out && + grep "file1 updated" out +' + +test_expect_success 'add superproject worktree and initialize submodules' ' + git -C main worktree add "$base_path/worktree-submodule-update" "$rev1_hash_main" && + git -C worktree-submodule-update submodule update +' + +test_expect_success 'submodule is checked out just after submodule update in linked worktree' ' + git -C worktree-submodule-update diff --submodule master"^!" >out && + grep "file1 updated" out +' + +test_expect_success 'add superproject worktree and manually add submodule worktree' ' + git -C main worktree add "$base_path/linked_submodule" "$rev1_hash_main" && + git -C main/sub worktree add "$base_path/linked_submodule/sub" "$rev1_hash_sub" +' + +test_expect_success 'submodule is checked out after manually adding submodule worktree' ' + git -C linked_submodule diff --submodule master"^!" >out && + grep "file1 updated" out +' + +test_expect_success 'checkout --recurse-submodules uses $GIT_DIR for submodules in a linked worktree' ' + git -C main worktree add "$base_path/checkout-recurse" --detach && + git -C checkout-recurse submodule update --init && + echo "gitdir: ../../main/.git/worktrees/checkout-recurse/modules/sub" >expect-gitfile && + cat checkout-recurse/sub/.git >actual-gitfile && + test_cmp expect-gitfile actual-gitfile && + git -C main/sub rev-parse HEAD >expect-head-main && + git -C checkout-recurse checkout --recurse-submodules HEAD~1 && + cat checkout-recurse/sub/.git >actual-gitfile && + git -C main/sub rev-parse HEAD >actual-head-main && + test_cmp expect-gitfile actual-gitfile && + test_cmp expect-head-main actual-head-main +' + +test_expect_success 'core.worktree is removed in $GIT_DIR/modules/<name>/config, not in $GIT_COMMON_DIR/modules/<name>/config' ' + echo "../../../sub" >expect-main && + git -C main/sub config --get core.worktree >actual-main && + test_cmp expect-main actual-main && + echo "../../../../../../checkout-recurse/sub" >expect-linked && + git -C checkout-recurse/sub config --get core.worktree >actual-linked && + test_cmp expect-linked actual-linked && + git -C checkout-recurse checkout --recurse-submodules first && + test_expect_code 1 git -C main/.git/worktrees/checkout-recurse/modules/sub config --get core.worktree >linked-config && + test_must_be_empty linked-config && + git -C main/sub config --get core.worktree >actual-main && + test_cmp expect-main actual-main +' + +test_expect_success 'unsetting core.worktree does not prevent running commands directly against the submodule repository' ' + git -C main/.git/worktrees/checkout-recurse/modules/sub log +' + +test_done diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh index 831f83d211..3b4753e1b4 100755 --- a/t/t3305-notes-fanout.sh +++ b/t/t3305-notes-fanout.sh @@ -4,6 +4,38 @@ test_description='Test that adding/removing many notes triggers automatic fanout . ./test-lib.sh +path_has_fanout() { + path=$1 && + fanout=$2 && + after_last_slash=$((40 - $fanout * 2)) && + echo $path | grep -q "^\([0-9a-f]\{2\}/\)\{$fanout\}[0-9a-f]\{$after_last_slash\}$" +} + +touched_one_note_with_fanout() { + notes_commit=$1 && + modification=$2 && # 'A' for addition, 'D' for deletion + fanout=$3 && + diff=$(git diff-tree --no-commit-id --name-status --root -r $notes_commit) && + path=$(echo $diff | sed -e "s/^$modification[\t ]//") && + path_has_fanout "$path" $fanout; +} + +all_notes_have_fanout() { + notes_commit=$1 && + fanout=$2 && + git ls-tree -r --name-only $notes_commit 2>/dev/null | + while read path + do + path_has_fanout $path $fanout || return 1 + done +} + +test_expect_success 'tweak test environment' ' + git checkout -b nondeterminism && + test_commit A && + git checkout --orphan with_notes; +' + test_expect_success 'creating many notes with git-notes' ' num_notes=300 && i=0 && @@ -20,7 +52,7 @@ test_expect_success 'creating many notes with git-notes' ' test_expect_success 'many notes created correctly with git-notes' ' git log | grep "^ " > output && - i=300 && + i=$num_notes && while test $i -gt 0 do echo " commit #$i" && @@ -30,34 +62,46 @@ test_expect_success 'many notes created correctly with git-notes' ' test_cmp expect output ' -test_expect_success 'many notes created with git-notes triggers fanout' ' - # Expect entire notes tree to have a fanout == 1 - git ls-tree -r --name-only refs/notes/commits | - while read path +test_expect_success 'stable fanout 0 is followed by stable fanout 1' ' + i=$num_notes && + fanout=0 && + while test $i -gt 0 do - echo $path | grep "^../[0-9a-f]*$" || { - echo "Invalid path \"$path\"" && - return 1; - } - done + i=$(($i - 1)) && + if touched_one_note_with_fanout refs/notes/commits~$i A $fanout + then + continue + elif test $fanout -eq 0 + then + fanout=1 && + if all_notes_have_fanout refs/notes/commits~$i $fanout + then + echo "Fanout 0 -> 1 at refs/notes/commits~$i" && + continue + fi + fi && + echo "Failed fanout=$fanout check at refs/notes/commits~$i" && + git ls-tree -r --name-only refs/notes/commits~$i && + return 1 + done && + all_notes_have_fanout refs/notes/commits 1 ' test_expect_success 'deleting most notes with git-notes' ' - num_notes=250 && + remove_notes=285 && i=0 && git rev-list HEAD | - while test $i -lt $num_notes && read sha1 + while test $i -lt $remove_notes && read sha1 do i=$(($i + 1)) && test_tick && - git notes remove "$sha1" || - exit 1 + git notes remove "$sha1" 2>/dev/null || return 1 done ' test_expect_success 'most notes deleted correctly with git-notes' ' - git log HEAD~250 | grep "^ " > output && - i=50 && + git log HEAD~$remove_notes | grep "^ " > output && + i=$(($num_notes - $remove_notes)) && while test $i -gt 0 do echo " commit #$i" && @@ -67,16 +111,29 @@ test_expect_success 'most notes deleted correctly with git-notes' ' test_cmp expect output ' -test_expect_success 'deleting most notes triggers fanout consolidation' ' - # Expect entire notes tree to have a fanout == 0 - git ls-tree -r --name-only refs/notes/commits | - while read path +test_expect_success 'stable fanout 1 is followed by stable fanout 0' ' + i=$remove_notes && + fanout=1 && + while test $i -gt 0 do - echo $path | grep -v "^../.*" || { - echo "Invalid path \"$path\"" && - return 1; - } - done + i=$(($i - 1)) && + if touched_one_note_with_fanout refs/notes/commits~$i D $fanout + then + continue + elif test $fanout -eq 1 + then + fanout=0 && + if all_notes_have_fanout refs/notes/commits~$i $fanout + then + echo "Fanout 1 -> 0 at refs/notes/commits~$i" && + continue + fi + fi && + echo "Failed fanout=$fanout check at refs/notes/commits~$i" && + git ls-tree -r --name-only refs/notes/commits~$i && + return 1 + done && + all_notes_have_fanout refs/notes/commits 0 ' test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index ae6e55ce79..b90ea0fe44 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1264,13 +1264,26 @@ test_expect_success SHA1 'short SHA-1 setup' ' test_expect_success SHA1 'short SHA-1 collide' ' test_when_finished "reset_rebase && git checkout master" && git checkout collide && + colliding_sha1=6bcda37 && + test $colliding_sha1 = "$(git rev-parse HEAD | cut -c 1-7)" && ( unset test_tick && test_tick && set_fake_editor && FAKE_COMMIT_MESSAGE="collide2 ac4f2ee" \ - FAKE_LINES="reword 1 2" git rebase -i HEAD~2 - ) + FAKE_LINES="reword 1 break 2" git rebase -i HEAD~2 && + test $colliding_sha1 = "$(git rev-parse HEAD | cut -c 1-7)" && + grep "^pick $colliding_sha1 " \ + .git/rebase-merge/git-rebase-todo.tmp && + grep "^pick [0-9a-f]\{40\}" \ + .git/rebase-merge/git-rebase-todo && + grep "^pick [0-9a-f]\{40\}" \ + .git/rebase-merge/git-rebase-todo.backup && + git rebase --continue + ) && + collide2="$(git rev-parse HEAD~1 | cut -c 1-4)" && + collide3="$(git rev-parse collide3 | cut -c 1-4)" && + test "$collide2" = "$collide3" ' test_expect_success 'respect core.abbrev' ' diff --git a/t/t3433-rebase-across-mode-change.sh b/t/t3433-rebase-across-mode-change.sh new file mode 100755 index 0000000000..05df964670 --- /dev/null +++ b/t/t3433-rebase-across-mode-change.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='git rebase across mode change' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir DS && + >DS/whatever && + git add DS && + git commit -m base && + + git branch side1 && + git branch side2 && + + git checkout side1 && + git rm -rf DS && + test_ln_s_add unrelated DS && + git commit -m side1 && + + git checkout side2 && + >unrelated && + git add unrelated && + git commit -m commit1 && + + echo >>unrelated && + git commit -am commit2 +' + +test_expect_success 'rebase changes with the apply backend' ' + test_when_finished "git rebase --abort || true" && + git checkout -b apply-backend side2 && + git rebase side1 +' + +test_expect_success 'rebase changes with the merge backend' ' + test_when_finished "git rebase --abort || true" && + git checkout -b merge-backend side2 && + git rebase -m side1 +' + +test_expect_success 'rebase changes with the merge backend with a delay' ' + test_when_finished "git rebase --abort || true" && + git checkout -b merge-delay-backend side2 && + git rebase -m --exec "sleep 1" side1 +' + +test_done diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 0ea858d652..f2c0168941 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -425,6 +425,13 @@ test_expect_success 'rm will error out on a modified .gitmodules file unless sta git status -s -uno >actual && test_cmp expect actual ' +test_expect_success 'rm will not error out on .gitmodules file with zero stat data' ' + git reset --hard && + git submodule update && + git read-tree HEAD && + git rm submod && + test_path_is_missing submod +' test_expect_success 'rm issues a warning when section is not found in .gitmodules' ' git reset --hard && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 12ee321707..2182b1c030 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -68,6 +68,15 @@ test_expect_success 'revert works (initial)' ' ! grep . output ' +test_expect_success 'add untracked (multiple)' ' + test_when_finished "git reset && rm [1-9]" && + touch $(test_seq 9) && + test_write_lines a "2-5 8-" | git add -i -- [1-9] && + test_write_lines 2 3 4 5 8 9 >expected && + git ls-files [1-9] >output && + test_cmp expected output +' + test_expect_success 'setup (commit)' ' echo baseline >file && git add file && diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 2c9489484a..c9c10ca537 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -1631,6 +1631,26 @@ test_expect_success GPG 'log --graph --show-signature for merged tag' ' grep "^| | gpg: Good signature" actual ' +test_expect_success GPG 'log --graph --show-signature for merged tag in shallow clone' ' + test_when_finished "git reset --hard && git checkout master" && + git checkout -b plain-shallow master && + echo aaa >bar && + git add bar && + git commit -m bar_commit && + git checkout --detach master && + echo bbb >baz && + git add baz && + git commit -m baz_commit && + git tag -s -m signed_tag_msg signed_tag_shallow && + hash=$(git rev-parse HEAD) && + git checkout plain-shallow && + git merge --no-ff -m msg signed_tag_shallow && + git clone --depth 1 --no-local . shallow && + test_when_finished "rm -rf shallow" && + git -C shallow log --graph --show-signature -n1 plain-shallow >actual && + grep "tag signed_tag_shallow names a non-parent $hash" actual +' + test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' ' test_when_finished "git reset --hard && git checkout master" && test_config gpg.format x509 && diff --git a/t/t5309-pack-delta-cycles.sh b/t/t5309-pack-delta-cycles.sh index 491556dad9..6c209ad45c 100755 --- a/t/t5309-pack-delta-cycles.sh +++ b/t/t5309-pack-delta-cycles.sh @@ -62,13 +62,13 @@ test_expect_success 'index-pack detects REF_DELTA cycles' ' test_must_fail git index-pack --fix-thin --stdin <cycle.pack ' -test_expect_failure 'failover to an object in another pack' ' +test_expect_success 'failover to an object in another pack' ' clear_packs && git index-pack --stdin <ab.pack && - git index-pack --stdin --fix-thin <cycle.pack + test_must_fail git index-pack --stdin --fix-thin <cycle.pack ' -test_expect_failure 'failover to a duplicate object in the same pack' ' +test_expect_success 'failover to a duplicate object in the same pack' ' clear_packs && { pack_header 3 && @@ -77,7 +77,7 @@ test_expect_failure 'failover to a duplicate object in the same pack' ' pack_obj $A } >recoverable.pack && pack_trailer recoverable.pack && - git index-pack --fix-thin --stdin <recoverable.pack + test_must_fail git index-pack --fix-thin --stdin <recoverable.pack ' test_done diff --git a/t/t5580-clone-push-unc.sh b/t/t5580-unc-paths.sh index 01b52c195a..cf768b3a27 100755 --- a/t/t5580-clone-push-unc.sh +++ b/t/t5580-unc-paths.sh @@ -40,11 +40,23 @@ test_expect_success clone ' git clone "file://$UNCPATH" clone ' +test_expect_success 'clone without file://' ' + git clone "$UNCPATH" clone-without-file +' + test_expect_success 'clone with backslashed path' ' BACKSLASHED="$(echo "$UNCPATH" | tr / \\\\)" && git clone "$BACKSLASHED" backslashed ' +test_expect_success fetch ' + git init to-fetch && + ( + cd to-fetch && + git fetch "$UNCPATH" master + ) +' + test_expect_success push ' ( cd clone && diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 9a9178fd28..3988a9cea2 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -384,6 +384,37 @@ test_expect_success 'fetch lazy-fetches only to resolve deltas, protocol v2' ' grep "want $(cat hash)" trace ' +# The following two tests must be in this order, or else +# the first will not fail. It is important that the srv.bare +# repository did not have tags during clone, but has tags +# in the fetch. + +test_expect_failure 'verify fetch succeeds when asking for new tags' ' + git clone --filter=blob:none "file://$(pwd)/srv.bare" tag-test && + for i in I J K + do + test_commit -C src $i && + git -C src branch $i || return 1 + done && + git -C srv.bare fetch --tags origin +refs/heads/*:refs/heads/* && + git -C tag-test -c protocol.version=2 fetch --tags origin +' + +test_expect_success 'verify fetch downloads only one pack when updating refs' ' + git clone --filter=blob:none "file://$(pwd)/srv.bare" pack-test && + ls pack-test/.git/objects/pack/*pack >pack-list && + test_line_count = 2 pack-list && + for i in A B C + do + test_commit -C src $i && + git -C src branch $i || return 1 + done && + git -C srv.bare fetch origin +refs/heads/*:refs/heads/* && + git -C pack-test fetch origin && + ls pack-test/.git/objects/pack/*pack >pack-list && + test_line_count = 3 pack-list +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t6136-pathspec-in-bare.sh b/t/t6136-pathspec-in-bare.sh new file mode 100755 index 0000000000..b117251366 --- /dev/null +++ b/t/t6136-pathspec-in-bare.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='diagnosing out-of-scope pathspec' + +. ./test-lib.sh + +test_expect_success 'setup a bare and non-bare repository' ' + test_commit file1 && + git clone --bare . bare +' + +test_expect_success 'log and ls-files in a bare repository' ' + ( + cd bare && + test_must_fail git log -- .. >out 2>err && + test_must_be_empty out && + test_i18ngrep "outside repository" err && + + test_must_fail git ls-files -- .. >out 2>err && + test_must_be_empty out && + test_i18ngrep "outside repository" err + ) +' + +test_expect_success 'log and ls-files in .git directory' ' + ( + cd .git && + test_must_fail git log -- .. >out 2>err && + test_must_be_empty out && + test_i18ngrep "outside repository" err && + + test_must_fail git ls-files -- .. >out 2>err && + test_must_be_empty out && + test_i18ngrep "outside repository" err + ) +' + +test_done diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh deleted file mode 100755 index f1b492ebc4..0000000000 --- a/t/t7410-submodule-checkout-to.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/sh - -test_description='Combination of submodules and multiple workdirs' - -. ./test-lib.sh - -base_path=$(pwd -P) - -test_expect_success 'setup: make origin' ' - mkdir -p origin/sub && - ( - cd origin/sub && git init && - echo file1 >file1 && - git add file1 && - git commit -m file1 - ) && - mkdir -p origin/main && - ( - cd origin/main && git init && - git submodule add ../sub && - git commit -m "add sub" - ) && - ( - cd origin/sub && - echo file1updated >file1 && - git add file1 && - git commit -m "file1 updated" - ) && - git -C origin/main/sub pull && - ( - cd origin/main && - git add sub && - git commit -m "sub updated" - ) -' - -test_expect_success 'setup: clone' ' - mkdir clone && - git -C clone clone --recursive "$base_path/origin/main" -' - -rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1") -rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1") - -test_expect_success 'checkout main' ' - mkdir default_checkout && - git -C clone/main worktree add "$base_path/default_checkout/main" "$rev1_hash_main" -' - -test_expect_failure 'can see submodule diffs just after checkout' ' - git -C default_checkout/main diff --submodule master"^!" >out && - grep "file1 updated" out -' - -test_expect_success 'checkout main and initialize independent clones' ' - mkdir fully_cloned_submodule && - git -C clone/main worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main" && - git -C fully_cloned_submodule/main submodule update -' - -test_expect_success 'can see submodule diffs after independent cloning' ' - git -C fully_cloned_submodule/main diff --submodule master"^!" >out && - grep "file1 updated" out -' - -test_expect_success 'checkout sub manually' ' - mkdir linked_submodule && - git -C clone/main worktree add "$base_path/linked_submodule/main" "$rev1_hash_main" && - git -C clone/main/sub worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub" -' - -test_expect_success 'can see submodule diffs after manual checkout of linked submodule' ' - git -C linked_submodule/main diff --submodule master"^!" >out && - grep "file1 updated" out -' - -test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 44df51be8f..0ea1e5a05e 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1083,7 +1083,8 @@ finalize_junit_xml () { # adjust the overall time junit_time=$(test-tool date getnanos $junit_suite_start) - sed "s/<testsuite [^>]*/& time=\"$junit_time\"/" \ + sed -e "s/\(<testsuite.*\) time=\"[^\"]*\"/\1/" \ + -e "s/<testsuite [^>]*/& time=\"$junit_time\"/" \ <"$junit_xml_path" >"$junit_xml_path.new" mv "$junit_xml_path.new" "$junit_xml_path" diff --git a/unpack-trees.c b/unpack-trees.c index 191e738143..da4d6d4ec0 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -372,15 +372,20 @@ static int check_updates(struct unpack_trees_options *o) state.refresh_cache = 1; state.istate = index; + if (!o->update || o->dry_run) { + remove_marked_cache_entries(index, 0); + trace_performance_leave("check_updates"); + return 0; + } + if (o->clone) setup_collided_checkout_detection(&state, index); progress = get_progress(o); - if (o->update) - git_attr_set_direction(GIT_ATTR_CHECKOUT); + git_attr_set_direction(GIT_ATTR_CHECKOUT); - if (should_update_submodules() && o->update && !o->dry_run) + if (should_update_submodules()) load_gitmodules_file(index, NULL); for (i = 0; i < index->cache_nr; i++) { @@ -388,18 +393,18 @@ static int check_updates(struct unpack_trees_options *o) if (ce->ce_flags & CE_WT_REMOVE) { display_progress(progress, ++cnt); - if (o->update && !o->dry_run) - unlink_entry(ce); + unlink_entry(ce); } } + remove_marked_cache_entries(index, 0); remove_scheduled_dirs(); - if (should_update_submodules() && o->update && !o->dry_run) + if (should_update_submodules()) load_gitmodules_file(index, &state); enable_delayed_checkout(&state); - if (has_promisor_remote() && o->update && !o->dry_run) { + if (has_promisor_remote()) { /* * Prefetch the objects that are to be checked out in the loop * below. @@ -431,15 +436,12 @@ static int check_updates(struct unpack_trees_options *o) ce->name); display_progress(progress, ++cnt); ce->ce_flags &= ~CE_UPDATE; - if (o->update && !o->dry_run) { - errs |= checkout_entry(ce, &state, NULL, NULL); - } + errs |= checkout_entry(ce, &state, NULL, NULL); } } stop_progress(&progress); errs |= finish_delayed_checkout(&state, NULL); - if (o->update) - git_attr_set_direction(GIT_ATTR_CHECKIN); + git_attr_set_direction(GIT_ATTR_CHECKIN); if (o->clone) report_collided_checkout(index); @@ -1350,7 +1352,7 @@ static int clear_ce_flags_1(struct index_state *istate, enum pattern_match_result default_match, int progress_nr) { - struct cache_entry **cache_end = cache + nr; + struct cache_entry **cache_end = nr ? cache + nr : cache; /* * Process all entries that have the given prefix and meet diff --git a/xdiff-interface.c b/xdiff-interface.c index 8509f9ea22..4d20069302 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -84,8 +84,8 @@ static void trim_common_tail(mmfile_t *a, mmfile_t *b) { const int blk = 1024; long trimmed = 0, recovered = 0; - char *ap = a->ptr + a->size; - char *bp = b->ptr + b->size; + char *ap = a->size ? a->ptr + a->size : a->ptr; + char *bp = b->size ? b->ptr + b->size : b->ptr; long smaller = (a->size < b->size) ? a->size : b->size; while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) { @@ -250,9 +250,13 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value, int cflags) ALLOC_ARRAY(regs->array, regs->nr); for (i = 0; i < regs->nr; i++) { struct ff_reg *reg = regs->array + i; - const char *ep = strchr(value, '\n'), *expression; + const char *ep, *expression; char *buffer = NULL; + if (!value) + BUG("mismatch between line count and parsing"); + ep = strchr(value, '\n'); + reg->negate = (*value == '!'); if (reg->negate && i == regs->nr - 1) die("Last expression must not be negated: %s", value); @@ -265,7 +269,7 @@ void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value, int cflags) if (regcomp(®->re, expression, cflags)) die("Invalid regexp to look for hunk header: %s", expression); free(buffer); - value = ep + 1; + value = ep ? ep + 1 : NULL; } } |