diff options
-rw-r--r-- | Documentation/Makefile | 1 | ||||
-rw-r--r-- | Documentation/RelNotes/2.38.0.txt | 9 | ||||
-rw-r--r-- | Documentation/diff-format.txt | 6 | ||||
-rw-r--r-- | Documentation/git-archive.txt | 21 | ||||
-rw-r--r-- | Documentation/git-diff-index.txt | 6 | ||||
-rw-r--r-- | Documentation/revisions.txt | 2 | ||||
-rw-r--r-- | Documentation/technical/bitmap-format.txt | 203 | ||||
-rwxr-xr-x | GIT-VERSION-GEN | 2 | ||||
l--------- | RelNotes | 2 | ||||
-rw-r--r-- | add-interactive.c | 12 | ||||
-rw-r--r-- | archive-tar.c | 77 | ||||
-rw-r--r-- | archive.h | 2 | ||||
-rw-r--r-- | branch.c | 76 | ||||
-rw-r--r-- | branch.h | 7 | ||||
-rw-r--r-- | builtin/branch.c | 11 | ||||
-rw-r--r-- | builtin/fetch.c | 46 | ||||
-rw-r--r-- | combine-diff.c | 7 | ||||
-rw-r--r-- | packfile.c | 3 | ||||
-rw-r--r-- | setup.c | 2 | ||||
-rwxr-xr-x | t/t2407-worktree-heads.sh | 129 | ||||
-rwxr-xr-x | t/t3701-add-interactive.sh | 9 | ||||
-rwxr-xr-x | t/t5000-tar-tree.sh | 28 |
22 files changed, 479 insertions, 182 deletions
diff --git a/Documentation/Makefile b/Documentation/Makefile index f2e7fc1daa..4f801f4e4c 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -94,6 +94,7 @@ TECH_DOCS += MyFirstContribution TECH_DOCS += MyFirstObjectWalk TECH_DOCS += SubmittingPatches TECH_DOCS += ToolsForGit +TECH_DOCS += technical/bitmap-format TECH_DOCS += technical/bundle-format TECH_DOCS += technical/cruft-packs TECH_DOCS += technical/hash-function-transition diff --git a/Documentation/RelNotes/2.38.0.txt b/Documentation/RelNotes/2.38.0.txt new file mode 100644 index 0000000000..a96fcabd85 --- /dev/null +++ b/Documentation/RelNotes/2.38.0.txt @@ -0,0 +1,9 @@ +Git v2.38 Release Notes +======================= + +Fixes since v2.37 +----------------- + + * Rewrite of "git add -i" in C that appeared in Git 2.25 didn't + correctly record a removed file to the index, which was fixed. + (merge 4788e8b256 js/add-i-delete later to maint). diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 7a9c3b6ff4..a3ae8747a2 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -43,7 +43,7 @@ That is, from the left to the right: . a space. . sha1 for "src"; 0\{40\} if creation or unmerged. . a space. -. sha1 for "dst"; 0\{40\} if creation, unmerged or "look at work tree". +. sha1 for "dst"; 0\{40\} if deletion, unmerged or "work tree out of sync with the index". . a space. . status, followed by optional "score" number. . a tab or a NUL when `-z` option is used. @@ -69,8 +69,8 @@ percentage of similarity between the source and target of the move or copy). Status letter M may be followed by a score (denoting the percentage of dissimilarity) for file rewrites. -<sha1> is shown as all 0's if a file is new on the filesystem -and it is out of sync with the index. +The sha1 for "dst" is shown as all 0's if a file on the filesystem +is out of sync with the index. Example: diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 56989a2f34..60c040988b 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -34,10 +34,12 @@ OPTIONS ------- --format=<fmt>:: - Format of the resulting archive: 'tar' or 'zip'. If this option + Format of the resulting archive. Possible values are `tar`, + `zip`, `tar.gz`, `tgz`, and any format defined using the + configuration option `tar.<format>.command`. If `--format` is not given, and the output file is specified, the format is - inferred from the filename if possible (e.g. writing to "foo.zip" - makes the output to be in the zip format). Otherwise the output + inferred from the filename if possible (e.g. writing to `foo.zip` + makes the output to be in the `zip` format). Otherwise the output format is `tar`. -l:: @@ -143,17 +145,16 @@ tar.<format>.command:: is executed using the shell with the generated tar file on its standard input, and should produce the final output on its standard output. Any compression-level options will be passed - to the command (e.g., "-9"). An output file with the same - extension as `<format>` will be use this format if no other - format is given. + to the command (e.g., `-9`). + -The "tar.gz" and "tgz" formats are defined automatically and default to -`gzip -cn`. You may override them with custom commands. +The `tar.gz` and `tgz` formats are defined automatically and use the +magic command `git archive gzip` by default, which invokes an internal +implementation of gzip. tar.<format>.remote:: - If true, enable `<format>` for use by remote clients via + If true, enable the format for use by remote clients via linkgit:git-upload-archive[1]. Defaults to false for - user-defined formats, but true for the "tar.gz" and "tgz" + user-defined formats, but true for the `tar.gz` and `tgz` formats. [[ATTRIBUTES]] diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index 679cae27d9..c30d8f0da8 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -69,8 +69,8 @@ done an `update-index` to make that effective in the index file. matches my working directory. But doing a 'git diff-index' does: torvalds@ppc970:~/git> git diff-index --cached HEAD - -100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 commit.c - +100644 blob 4161aecc6700a2eb579e842af0b7f22b98443f74 git-commit.c + :100644 000000 4161aecc6700a2eb579e842af0b7f22b98443f74 0000000000000000000000000000000000000000 D commit.c + :000000 100644 0000000000000000000000000000000000000000 4161aecc6700a2eb579e842af0b7f22b98443f74 A git-commit.c You can see easily that the above is a rename. @@ -103,7 +103,7 @@ have not actually done a 'git update-index' on it yet - there is no "object" associated with the new state, and you get: torvalds@ppc970:~/v2.6/linux> git diff-index --abbrev HEAD - :100644 100664 7476bb... 000000... kernel/sched.c + :100644 100644 7476bb5ba 000000000 M kernel/sched.c i.e., it shows that the tree has changed, and that `kernel/sched.c` is not up to date and may contain new stuff. The all-zero sha1 means that to diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index f5f17b65a1..cef8c3c66f 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -283,7 +283,7 @@ The '..' (two-dot) Range Notation:: for commits that are reachable from r2 excluding those that are reachable from r1 by '{caret}r1 r2' and it can be written as 'r1..r2'. -The '...' (three-dot) Symmetric Difference Notation:: +The '\...' (three-dot) Symmetric Difference Notation:: A similar notation 'r1\...r2' is called symmetric difference of 'r1' and 'r2' and is defined as 'r1 r2 --not $(git merge-base --all r1 r2)'. diff --git a/Documentation/technical/bitmap-format.txt b/Documentation/technical/bitmap-format.txt index 04b3ec2178..a85f58f515 100644 --- a/Documentation/technical/bitmap-format.txt +++ b/Documentation/technical/bitmap-format.txt @@ -25,9 +25,9 @@ An object is uniquely described by its bit position within a bitmap: is defined as follows: o1 <= o2 <==> pack(o1) <= pack(o2) /\ offset(o1) <= offset(o2) - - The ordering between packs is done according to the MIDX's .rev file. - Notably, the preferred pack sorts ahead of all other packs. ++ +The ordering between packs is done according to the MIDX's .rev file. +Notably, the preferred pack sorts ahead of all other packs. The on-disk representation (described below) of a bitmap is the same regardless of whether or not that bitmap belongs to a packfile or a MIDX. The only @@ -39,97 +39,108 @@ MIDXs, both the bit-cache and rev-cache extensions are required. == On-disk format - - A header appears at the beginning: - - 4-byte signature: {'B', 'I', 'T', 'M'} - - 2-byte version number (network byte order) - The current implementation only supports version 1 - of the bitmap index (the same one as JGit). - - 2-byte flags (network byte order) - - The following flags are supported: - - - BITMAP_OPT_FULL_DAG (0x1) REQUIRED - This flag must always be present. It implies that the - bitmap index has been generated for a packfile or - multi-pack index (MIDX) with full closure (i.e. where - every single object in the packfile/MIDX can find its - parent links inside the same packfile/MIDX). This is a - requirement for the bitmap index format, also present in - JGit, that greatly reduces the complexity of the - implementation. - - - BITMAP_OPT_HASH_CACHE (0x4) - If present, the end of the bitmap file contains - `N` 32-bit name-hash values, one per object in the - pack/MIDX. The format and meaning of the name-hash is - described below. - - 4-byte entry count (network byte order) - - The total count of entries (bitmapped commits) in this bitmap index. - - 20-byte checksum - - The SHA1 checksum of the pack/MIDX this bitmap index - belongs to. - - - 4 EWAH bitmaps that act as type indexes - - Type indexes are serialized after the hash cache in the shape - of four EWAH bitmaps stored consecutively (see Appendix A for - the serialization format of an EWAH bitmap). - - There is a bitmap for each Git object type, stored in the following - order: - - - Commits - - Trees - - Blobs - - Tags - - In each bitmap, the `n`th bit is set to true if the `n`th object - in the packfile or multi-pack index is of that type. - - The obvious consequence is that the OR of all 4 bitmaps will result - in a full set (all bits set), and the AND of all 4 bitmaps will - result in an empty bitmap (no bits set). - - - N entries with compressed bitmaps, one for each indexed commit - - Where `N` is the total amount of entries in this bitmap index. - Each entry contains the following: - - - 4-byte object position (network byte order) - The position **in the index for the packfile or - multi-pack index** where the bitmap for this commit is - found. - - - 1-byte XOR-offset - The xor offset used to compress this bitmap. For an entry - in position `x`, a XOR offset of `y` means that the actual - bitmap representing this commit is composed by XORing the - bitmap for this entry with the bitmap in entry `x-y` (i.e. - the bitmap `y` entries before this one). - - Note that this compression can be recursive. In order to - XOR this entry with a previous one, the previous entry needs - to be decompressed first, and so on. - - The hard-limit for this offset is 160 (an entry can only be - xor'ed against one of the 160 entries preceding it). This - number is always positive, and hence entries are always xor'ed - with **previous** bitmaps, not bitmaps that will come afterwards - in the index. - - - 1-byte flags for this bitmap - At the moment the only available flag is `0x1`, which hints - that this bitmap can be re-used when rebuilding bitmap indexes - for the repository. - - - The compressed bitmap itself, see Appendix A. + * A header appears at the beginning: + + 4-byte signature: :: {'B', 'I', 'T', 'M'} + + 2-byte version number (network byte order): :: + + The current implementation only supports version 1 + of the bitmap index (the same one as JGit). + + 2-byte flags (network byte order): :: + + The following flags are supported: + + ** {empty} + BITMAP_OPT_FULL_DAG (0x1) REQUIRED: ::: + + This flag must always be present. It implies that the + bitmap index has been generated for a packfile or + multi-pack index (MIDX) with full closure (i.e. where + every single object in the packfile/MIDX can find its + parent links inside the same packfile/MIDX). This is a + requirement for the bitmap index format, also present in + JGit, that greatly reduces the complexity of the + implementation. + + ** {empty} + BITMAP_OPT_HASH_CACHE (0x4): ::: + + If present, the end of the bitmap file contains + `N` 32-bit name-hash values, one per object in the + pack/MIDX. The format and meaning of the name-hash is + described below. + + 4-byte entry count (network byte order): :: + The total count of entries (bitmapped commits) in this bitmap index. + + 20-byte checksum: :: + The SHA1 checksum of the pack/MIDX this bitmap index + belongs to. + + * 4 EWAH bitmaps that act as type indexes ++ +Type indexes are serialized after the hash cache in the shape +of four EWAH bitmaps stored consecutively (see Appendix A for +the serialization format of an EWAH bitmap). ++ +There is a bitmap for each Git object type, stored in the following +order: ++ + - Commits + - Trees + - Blobs + - Tags + ++ +In each bitmap, the `n`th bit is set to true if the `n`th object +in the packfile or multi-pack index is of that type. ++ +The obvious consequence is that the OR of all 4 bitmaps will result +in a full set (all bits set), and the AND of all 4 bitmaps will +result in an empty bitmap (no bits set). + + * N entries with compressed bitmaps, one for each indexed commit ++ +Where `N` is the total amount of entries in this bitmap index. +Each entry contains the following: + + ** {empty} + 4-byte object position (network byte order): :: + The position **in the index for the packfile or + multi-pack index** where the bitmap for this commit is + found. + + ** {empty} + 1-byte XOR-offset: :: + The xor offset used to compress this bitmap. For an entry + in position `x`, a XOR offset of `y` means that the actual + bitmap representing this commit is composed by XORing the + bitmap for this entry with the bitmap in entry `x-y` (i.e. + the bitmap `y` entries before this one). ++ +NOTE: This compression can be recursive. In order to +XOR this entry with a previous one, the previous entry needs +to be decompressed first, and so on. ++ +The hard-limit for this offset is 160 (an entry can only be +xor'ed against one of the 160 entries preceding it). This +number is always positive, and hence entries are always xor'ed +with **previous** bitmaps, not bitmaps that will come afterwards +in the index. + + ** {empty} + 1-byte flags for this bitmap: :: + At the moment the only available flag is `0x1`, which hints + that this bitmap can be re-used when rebuilding bitmap indexes + for the repository. + + ** The compressed bitmap itself, see Appendix A. + + * {empty} + TRAILER: :: + Trailing checksum of the preceding contents. == Appendix A: Serialization format for an EWAH bitmap @@ -142,8 +153,8 @@ implementation: - 4-byte number of words of the COMPRESSED bitmap, when stored - N x 8-byte words, as specified by the previous field - - This is the actual content of the compressed bitmap. ++ +This is the actual content of the compressed bitmap. - 4-byte position of the current RLW for the compressed bitmap diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index b210b306b7..6ec9e34282 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.37.0 +DEF_VER=v2.37.GIT LF=' ' @@ -1 +1 @@ -Documentation/RelNotes/2.37.0.txt
\ No newline at end of file +Documentation/RelNotes/2.38.0.txt
\ No newline at end of file diff --git a/add-interactive.c b/add-interactive.c index 6047e8f648..22fcd3412c 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -697,8 +697,16 @@ static int run_update(struct add_i_state *s, const struct pathspec *ps, for (i = 0; i < files->items.nr; i++) { const char *name = files->items.items[i].string; - if (files->selected[i] && - add_file_to_index(s->r->index, name, 0) < 0) { + struct stat st; + + if (!files->selected[i]) + continue; + if (lstat(name, &st) && is_missing_file_error(errno)) { + if (remove_file_from_index(s->r->index, name) < 0) { + res = error(_("could not stage '%s'"), name); + break; + } + } else if (add_file_to_index(s->r->index, name, 0) < 0) { res = error(_("could not stage '%s'"), name); break; } diff --git a/archive-tar.c b/archive-tar.c index 042feb66d2..3d77e0f750 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -38,11 +38,18 @@ static int write_tar_filter_archive(const struct archiver *ar, #define USTAR_MAX_MTIME 077777777777ULL #endif +static void tar_write_block(const void *buf) +{ + write_or_die(1, buf, BLOCKSIZE); +} + +static void (*write_block)(const void *) = tar_write_block; + /* writes out the whole block, but only if it is full */ static void write_if_needed(void) { if (offset == BLOCKSIZE) { - write_or_die(1, block, BLOCKSIZE); + write_block(block); offset = 0; } } @@ -66,7 +73,7 @@ static void do_write_blocked(const void *data, unsigned long size) write_if_needed(); } while (size >= BLOCKSIZE) { - write_or_die(1, buf, BLOCKSIZE); + write_block(buf); size -= BLOCKSIZE; buf += BLOCKSIZE; } @@ -101,10 +108,10 @@ static void write_trailer(void) { int tail = BLOCKSIZE - offset; memset(block + offset, 0, tail); - write_or_die(1, block, BLOCKSIZE); + write_block(block); if (tail < 2 * RECORDSIZE) { memset(block, 0, offset); - write_or_die(1, block, BLOCKSIZE); + write_block(block); } } @@ -383,8 +390,8 @@ static int tar_filter_config(const char *var, const char *value, void *data) if (!strcmp(type, "command")) { if (!value) return config_error_nonbool(var); - free(ar->data); - ar->data = xstrdup(value); + free(ar->filter_command); + ar->filter_command = xstrdup(value); return 0; } if (!strcmp(type, "remote")) { @@ -425,17 +432,65 @@ static int write_tar_archive(const struct archiver *ar, return err; } +static git_zstream gzstream; +static unsigned char outbuf[16384]; + +static void tgz_deflate(int flush) +{ + while (gzstream.avail_in || flush == Z_FINISH) { + int status = git_deflate(&gzstream, flush); + if (!gzstream.avail_out || status == Z_STREAM_END) { + write_or_die(1, outbuf, gzstream.next_out - outbuf); + gzstream.next_out = outbuf; + gzstream.avail_out = sizeof(outbuf); + if (status == Z_STREAM_END) + break; + } + if (status != Z_OK && status != Z_BUF_ERROR) + die(_("deflate error (%d)"), status); + } +} + +static void tgz_write_block(const void *data) +{ + gzstream.next_in = (void *)data; + gzstream.avail_in = BLOCKSIZE; + tgz_deflate(Z_NO_FLUSH); +} + +static const char internal_gzip_command[] = "git archive gzip"; + static int write_tar_filter_archive(const struct archiver *ar, struct archiver_args *args) { +#if ZLIB_VERNUM >= 0x1221 + struct gz_header_s gzhead = { .os = 3 }; /* Unix, for reproducibility */ +#endif struct strbuf cmd = STRBUF_INIT; struct child_process filter = CHILD_PROCESS_INIT; int r; - if (!ar->data) + if (!ar->filter_command) BUG("tar-filter archiver called with no filter defined"); - strbuf_addstr(&cmd, ar->data); + if (!strcmp(ar->filter_command, internal_gzip_command)) { + write_block = tgz_write_block; + git_deflate_init_gzip(&gzstream, args->compression_level); +#if ZLIB_VERNUM >= 0x1221 + if (deflateSetHeader(&gzstream.z, &gzhead) != Z_OK) + BUG("deflateSetHeader() called too late"); +#endif + gzstream.next_out = outbuf; + gzstream.avail_out = sizeof(outbuf); + + r = write_tar_archive(ar, args); + + tgz_deflate(Z_FINISH); + git_deflate_end(&gzstream); + return r; + } + + strbuf_addstr(&cmd, ar->filter_command); if (args->compression_level >= 0) strbuf_addf(&cmd, " -%d", args->compression_level); @@ -471,14 +526,14 @@ void init_tar_archiver(void) int i; register_archiver(&tar_archiver); - tar_filter_config("tar.tgz.command", "gzip -cn", NULL); + tar_filter_config("tar.tgz.command", internal_gzip_command, NULL); tar_filter_config("tar.tgz.remote", "true", NULL); - tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL); + tar_filter_config("tar.tar.gz.command", internal_gzip_command, NULL); tar_filter_config("tar.tar.gz.remote", "true", NULL); git_config(git_tar_config, NULL); for (i = 0; i < nr_tar_filters; i++) { /* omit any filters that never had a command configured */ - if (tar_filters[i]->data) + if (tar_filters[i]->filter_command) register_archiver(tar_filters[i]); } } @@ -43,7 +43,7 @@ struct archiver { const char *name; int (*write_archive)(const struct archiver *, struct archiver_args *); unsigned flags; - void *data; + char *filter_command; }; void register_archiver(struct archiver *); @@ -10,6 +10,7 @@ #include "worktree.h" #include "submodule-config.h" #include "run-command.h" +#include "strmap.h" struct tracking { struct refspec_item spec; @@ -369,6 +370,70 @@ int validate_branchname(const char *name, struct strbuf *ref) return ref_exists(ref->buf); } +static int initialized_checked_out_branches; +static struct strmap current_checked_out_branches = STRMAP_INIT; + +static void prepare_checked_out_branches(void) +{ + int i = 0; + struct worktree **worktrees; + + if (initialized_checked_out_branches) + return; + initialized_checked_out_branches = 1; + + worktrees = get_worktrees(); + + while (worktrees[i]) { + char *old; + struct wt_status_state state = { 0 }; + struct worktree *wt = worktrees[i++]; + + if (wt->is_bare) + continue; + + if (wt->head_ref) { + old = strmap_put(¤t_checked_out_branches, + wt->head_ref, + xstrdup(wt->path)); + free(old); + } + + if (wt_status_check_rebase(wt, &state) && + (state.rebase_in_progress || state.rebase_interactive_in_progress) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); + + if (wt_status_check_bisect(wt, &state) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); + } + + free_worktrees(worktrees); +} + +const char *branch_checked_out(const char *refname) +{ + prepare_checked_out_branches(); + return strmap_get(¤t_checked_out_branches, refname); +} + /* * Check if a branch 'name' can be created as a new branch; die otherwise. * 'force' can be used when it is OK for the named branch already exists. @@ -377,9 +442,7 @@ int validate_branchname(const char *name, struct strbuf *ref) */ int validate_new_branchname(const char *name, struct strbuf *ref, int force) { - struct worktree **worktrees; - const struct worktree *wt; - + const char *path; if (!validate_branchname(name, ref)) return 0; @@ -387,13 +450,10 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force) die(_("a branch named '%s' already exists"), ref->buf + strlen("refs/heads/")); - worktrees = get_worktrees(); - wt = find_shared_symref(worktrees, "HEAD", ref->buf); - if (wt && !wt->is_bare) + if ((path = branch_checked_out(ref->buf))) die(_("cannot force update the branch '%s' " "checked out at '%s'"), - ref->buf + strlen("refs/heads/"), wt->path); - free_worktrees(worktrees); + ref->buf + strlen("refs/heads/"), path); return 1; } @@ -101,6 +101,13 @@ void create_branches_recursively(struct repository *r, const char *name, const char *tracking_name, int force, int reflog, int quiet, enum branch_track track, int dry_run); + +/* + * If the branch at 'refname' is currently checked out in a worktree, + * then return the path to that worktree. + */ +const char *branch_checked_out(const char *refname); + /* * Check if 'name' can be a valid name for a branch; die otherwise. * Return 1 if the named branch already exists; return 0 otherwise. diff --git a/builtin/branch.c b/builtin/branch.c index 5d00d0b8d3..55cd9a6e99 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -204,7 +204,6 @@ static void delete_branch_config(const char *branchname) static int delete_branches(int argc, const char **argv, int force, int kinds, int quiet) { - struct worktree **worktrees; struct commit *head_rev = NULL; struct object_id oid; char *name = NULL; @@ -242,8 +241,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, die(_("Couldn't look up commit object for HEAD")); } - worktrees = get_worktrees(); - for (i = 0; i < argc; i++, strbuf_reset(&bname)) { char *target = NULL; int flags = 0; @@ -253,12 +250,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name = mkpathdup(fmt, bname.buf); if (kinds == FILTER_REFS_BRANCHES) { - const struct worktree *wt = - find_shared_symref(worktrees, "HEAD", name); - if (wt) { + const char *path; + if ((path = branch_checked_out(name))) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), - bname.buf, wt->path); + bname.buf, path); ret = 1; continue; } @@ -315,7 +311,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); strbuf_release(&bname); - free_worktrees(worktrees); return ret; } diff --git a/builtin/fetch.c b/builtin/fetch.c index ac29c2b1ae..b019004c1b 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -881,11 +881,9 @@ static void format_display(struct strbuf *display, char code, static int update_local_ref(struct ref *ref, struct ref_transaction *transaction, const char *remote, const struct ref *remote_ref, - struct strbuf *display, int summary_width, - struct worktree **worktrees) + struct strbuf *display, int summary_width) { struct commit *current = NULL, *updated; - const struct worktree *wt; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -900,16 +898,14 @@ static int update_local_ref(struct ref *ref, } if (!update_head_ok && - (wt = find_shared_symref(worktrees, "HEAD", ref->name)) && - !wt->is_bare && !is_null_oid(&ref->old_oid)) { + !is_null_oid(&ref->old_oid) && + branch_checked_out(ref->name)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ format_display(display, '!', _("[rejected]"), - wt->is_current ? - _("can't fetch in current branch") : - _("checked out in another worktree"), + _("can't fetch into checked-out branch"), remote, pretty_ref, summary_width); return 1; } @@ -1110,7 +1106,7 @@ N_("it took %.2f seconds to check forced updates; you can use\n" static int store_updated_refs(const char *raw_url, const char *remote_name, int connectivity_checked, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, struct worktree **worktrees) + struct fetch_head *fetch_head) { int url_len, i, rc = 0; struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; @@ -1240,8 +1236,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_reset(¬e); if (ref) { rc |= update_local_ref(ref, transaction, what, - rm, ¬e, summary_width, - worktrees); + rm, ¬e, summary_width); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1332,8 +1327,7 @@ static int check_exist_and_connected(struct ref *ref_map) static int fetch_and_consume_refs(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int connectivity_checked = 1; int ret; @@ -1356,7 +1350,7 @@ static int fetch_and_consume_refs(struct transport *transport, trace2_region_enter("fetch", "consume_refs", the_repository); ret = store_updated_refs(transport->url, transport->remote->name, connectivity_checked, transaction, ref_map, - fetch_head, worktrees); + fetch_head); trace2_region_leave("fetch", "consume_refs", the_repository); out: @@ -1434,19 +1428,16 @@ cleanup: return result; } -static void check_not_current_branch(struct ref *ref_map, - struct worktree **worktrees) +static void check_not_current_branch(struct ref *ref_map) { - const struct worktree *wt; + const char *path; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && starts_with(ref_map->peer_ref->name, "refs/heads/") && - (wt = find_shared_symref(worktrees, "HEAD", - ref_map->peer_ref->name)) && - !wt->is_bare) + (path = branch_checked_out(ref_map->peer_ref->name))) die(_("refusing to fetch into branch '%s' " "checked out at '%s'"), - ref_map->peer_ref->name, wt->path); + ref_map->peer_ref->name, path); } static int truncate_fetch_head(void) @@ -1549,8 +1540,7 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) static int backfill_tags(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int retcode, cannot_reuse; @@ -1571,7 +1561,7 @@ static int backfill_tags(struct transport *transport, transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head, worktrees); + retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head); if (gsecondary) { transport_disconnect(gsecondary); @@ -1592,7 +1582,6 @@ static int do_fetch(struct transport *transport, struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; - struct worktree **worktrees = get_worktrees(); struct fetch_head fetch_head = { 0 }; struct strbuf err = STRBUF_INIT; @@ -1650,7 +1639,7 @@ static int do_fetch(struct transport *transport, ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); if (!update_head_ok) - check_not_current_branch(ref_map, worktrees); + check_not_current_branch(ref_map); retcode = open_fetch_head(&fetch_head); if (retcode) @@ -1683,7 +1672,7 @@ static int do_fetch(struct transport *transport, retcode = 1; } - if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head, worktrees)) { + if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head)) { retcode = 1; goto cleanup; } @@ -1706,7 +1695,7 @@ static int do_fetch(struct transport *transport, * the transaction and don't commit anything. */ if (backfill_tags(transport, transaction, tags_ref_map, - &fetch_head, worktrees)) + &fetch_head)) retcode = 1; } @@ -1791,7 +1780,6 @@ cleanup: close_fetch_head(&fetch_head); strbuf_release(&err); free_refs(ref_map); - free_worktrees(worktrees); return retcode; } diff --git a/combine-diff.c b/combine-diff.c index b724f02123..b0ece95480 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1498,6 +1498,13 @@ void diff_tree_combined(const struct object_id *oid, int i, num_paths, needsep, show_log_first, num_parent = parents->nr; int need_generic_pathscan; + if (opt->ignore_regex_nr) + die("combined diff and '%s' cannot be used together", + "--ignore-matching-lines"); + if (opt->close_file) + die("combined diff and '%s' cannot be used together", + "--output"); + /* nothing to do, if no parents */ if (!num_parent) return; diff --git a/packfile.c b/packfile.c index 8e812a84a3..ed69fe457b 100644 --- a/packfile.c +++ b/packfile.c @@ -2275,7 +2275,8 @@ int is_promisor_object(const struct object_id *oid) if (has_promisor_remote()) { for_each_packed_object(add_promisor_object, &promisor_objects, - FOR_EACH_OBJECT_PROMISOR_ONLY); + FOR_EACH_OBJECT_PROMISOR_ONLY | + FOR_EACH_OBJECT_PACK_ORDER); } promisor_objects_prepared = 1; } @@ -1402,7 +1402,7 @@ const char *setup_git_directory_gently(int *nongit_ok) * find a repository. */ default: - BUG("unhandled setup_git_directory_1() result"); + BUG("unhandled setup_git_directory_gently_1() result"); } /* diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh new file mode 100755 index 0000000000..b6be42f74a --- /dev/null +++ b/t/t2407-worktree-heads.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='test operations trying to overwrite refs at worktree HEAD' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + git branch -f fake-1 && + git branch -f fake-2 && + + for i in 1 2 3 4 + do + test_commit $i && + git branch wt-$i && + git worktree add wt-$i wt-$i || return 1 + done && + + # Create a server that updates each branch by one commit + git init server && + test_commit -C server initial && + git remote add server ./server && + for i in 1 2 3 4 + do + git -C server checkout -b wt-$i && + test_commit -C server A-$i || return 1 + done && + for i in 1 2 + do + git -C server checkout -b fake-$i && + test_commit -C server f-$i || return 1 + done +' + +test_expect_success 'refuse to overwrite: checked out in worktree' ' + for i in 1 2 3 4 + do + test_must_fail git branch -f wt-$i HEAD 2>err + grep "cannot force update the branch" err && + + test_must_fail git branch -D wt-$i 2>err + grep "Cannot delete branch" err || return 1 + done +' + +test_expect_success 'refuse to overwrite: worktree in bisect' ' + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + + test_must_fail git branch -f fake-2 HEAD 2>err && + grep "cannot force update the branch '\''fake-2'\'' checked out at.*wt-4" err +' + +test_expect_success 'refuse to overwrite: worktree in rebase' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + + mkdir -p .git/worktrees/wt-3/rebase-merge && + touch .git/worktrees/wt-3/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto && + + test_must_fail git branch -f fake-1 HEAD 2>err && + grep "cannot force update the branch '\''fake-1'\'' checked out at.*wt-3" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' ' + test_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err && + grep "refusing to fetch into branch '\''refs/heads/wt-3'\''" err && + + # General fetch into refs/heads/ will fail on first ref, + # so use a generic error message check. + test_must_fail git fetch server +refs/heads/*:refs/heads/* 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in bisect' ' + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + + test_must_fail git fetch server +refs/heads/fake-2:refs/heads/fake-2 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + + mkdir -p .git/worktrees/wt-4/rebase-merge && + touch .git/worktrees/wt-4/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/onto && + + test_must_fail git fetch server +refs/heads/fake-1:refs/heads/fake-1 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success 'refuse to overwrite when in error states' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + # Both branches are currently under rebase. + mkdir -p .git/worktrees/wt-3/rebase-merge && + touch .git/worktrees/wt-3/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto && + mkdir -p .git/worktrees/wt-4/rebase-merge && + touch .git/worktrees/wt-4/rebase-merge/interactive && + echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/head-name && + echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/onto && + + # Both branches are currently under bisect. + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + touch .git/worktrees/wt-1/BISECT_LOG && + echo refs/heads/fake-1 >.git/worktrees/wt-1/BISECT_START && + + for i in 1 2 + do + test_must_fail git branch -f fake-$i HEAD 2>err && + grep "cannot force update the branch '\''fake-$i'\'' checked out at" err || + return 1 + done +' + +test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index fc26cb8bae..b354fb39de 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -103,6 +103,15 @@ test_expect_success 'status works (commit)' ' grep "+1/-0 *+2/-0 file" output ' +test_expect_success 'update can stage deletions' ' + >to-delete && + git add to-delete && + rm to-delete && + test_write_lines u t "" | git add -i && + git ls-files to-delete >output && + test_must_be_empty output +' + test_expect_success 'setup expected' ' cat >expected <<-\EOF index 180b47c..b6f2c08 100644 diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 7f8d2ab0a7..1a68e89a55 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -339,21 +339,21 @@ test_expect_success 'only enabled filters are available remotely' ' test_cmp_bin remote.bar config.bar ' -test_expect_success GZIP 'git archive --format=tgz' ' +test_expect_success 'git archive --format=tgz' ' git archive --format=tgz HEAD >j.tgz ' -test_expect_success GZIP 'git archive --format=tar.gz' ' +test_expect_success 'git archive --format=tar.gz' ' git archive --format=tar.gz HEAD >j1.tar.gz && test_cmp_bin j.tgz j1.tar.gz ' -test_expect_success GZIP 'infer tgz from .tgz filename' ' +test_expect_success 'infer tgz from .tgz filename' ' git archive --output=j2.tgz HEAD && test_cmp_bin j.tgz j2.tgz ' -test_expect_success GZIP 'infer tgz from .tar.gz filename' ' +test_expect_success 'infer tgz from .tar.gz filename' ' git archive --output=j3.tar.gz HEAD && test_cmp_bin j.tgz j3.tar.gz ' @@ -363,17 +363,33 @@ test_expect_success GZIP 'extract tgz file' ' test_cmp_bin b.tar j.tar ' -test_expect_success GZIP 'remote tar.gz is allowed by default' ' +test_expect_success 'remote tar.gz is allowed by default' ' git archive --remote=. --format=tar.gz HEAD >remote.tar.gz && test_cmp_bin j.tgz remote.tar.gz ' -test_expect_success GZIP 'remote tar.gz can be disabled' ' +test_expect_success 'remote tar.gz can be disabled' ' git config tar.tar.gz.remote false && test_must_fail git archive --remote=. --format=tar.gz HEAD \ >remote.tar.gz ' +test_expect_success GZIP 'git archive --format=tgz (external gzip)' ' + test_config tar.tgz.command "gzip -cn" && + git archive --format=tgz HEAD >external_gzip.tgz +' + +test_expect_success GZIP 'git archive --format=tar.gz (external gzip)' ' + test_config tar.tar.gz.command "gzip -cn" && + git archive --format=tar.gz HEAD >external_gzip.tar.gz && + test_cmp_bin external_gzip.tgz external_gzip.tar.gz +' + +test_expect_success GZIP 'extract tgz file (external gzip)' ' + gzip -d -c <external_gzip.tgz >external_gzip.tar && + test_cmp_bin b.tar external_gzip.tar +' + test_expect_success 'archive and :(glob)' ' git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual && cat >expect <<EOF && |