summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/Makefile1
-rw-r--r--Documentation/RelNotes/2.38.0.txt9
-rw-r--r--Documentation/diff-format.txt6
-rw-r--r--Documentation/git-archive.txt21
-rw-r--r--Documentation/git-diff-index.txt6
-rw-r--r--Documentation/revisions.txt2
-rw-r--r--Documentation/technical/bitmap-format.txt203
-rwxr-xr-xGIT-VERSION-GEN2
l---------RelNotes2
-rw-r--r--add-interactive.c12
-rw-r--r--archive-tar.c77
-rw-r--r--archive.h2
-rw-r--r--branch.c76
-rw-r--r--branch.h7
-rw-r--r--builtin/branch.c11
-rw-r--r--builtin/fetch.c46
-rw-r--r--combine-diff.c7
-rw-r--r--packfile.c3
-rw-r--r--setup.c2
-rwxr-xr-xt/t2407-worktree-heads.sh129
-rwxr-xr-xt/t3701-add-interactive.sh9
-rwxr-xr-xt/t5000-tar-tree.sh28
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='
'
diff --git a/RelNotes b/RelNotes
index 51144b6e83..d505db645b 120000
--- a/RelNotes
+++ b/RelNotes
@@ -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]);
}
}
diff --git a/archive.h b/archive.h
index 49fab71aaf..08bed3ed3a 100644
--- a/archive.h
+++ b/archive.h
@@ -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 *);
diff --git a/branch.c b/branch.c
index 4c8523c66a..6dbd933288 100644
--- a/branch.c
+++ b/branch.c
@@ -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(&current_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(&current_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(&current_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(&current_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;
}
diff --git a/branch.h b/branch.h
index 560b6b96a8..ef56103c05 100644
--- a/branch.h
+++ b/branch.h
@@ -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(&note);
if (ref) {
rc |= update_local_ref(ref, transaction, what,
- rm, &note, summary_width,
- worktrees);
+ rm, &note, 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;
}
diff --git a/setup.c b/setup.c
index faf5095e44..b5a537258d 100644
--- a/setup.c
+++ b/setup.c
@@ -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 &&