summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/MyFirstContribution.adoc9
-rw-r--r--Documentation/RelNotes/2.52.0.adoc40
-rw-r--r--Documentation/gitk.adoc8
-rw-r--r--builtin/describe.c41
-rw-r--r--refs/files-backend.c34
-rw-r--r--refs/reftable-backend.c53
-rw-r--r--reftable/reftable-stack.h9
-rw-r--r--reftable/reftable-writer.h4
-rw-r--r--reftable/stack.c439
-rw-r--r--reftable/system.c2
-rw-r--r--reftable/system.h4
-rw-r--r--reftable/writer.c23
-rwxr-xr-xt/t1400-update-ref.sh21
-rwxr-xr-xt/t1517-outside-repo.sh5
-rwxr-xr-xt/t5510-fetch.sh543
-rwxr-xr-xt/t6120-describe.sh30
-rw-r--r--t/unit-tests/u-reftable-stack.c59
17 files changed, 711 insertions, 613 deletions
diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc
index aca7212cfe..d786176bba 100644
--- a/Documentation/MyFirstContribution.adoc
+++ b/Documentation/MyFirstContribution.adoc
@@ -52,6 +52,15 @@ respond to you. It's better to ask your questions in the channel so that you
can be answered if you disconnect and so that others can learn from the
conversation.
+==== https://discord.gg/GRFVkzgxRd[#discord] on Discord
+This is an unofficial Git Discord server for everyone, from people just
+starting out with Git to those who develop it. It's a great place to ask
+questions, share tips, and connect with the broader Git community in real time.
+
+The server has channels for general discussions and specific channels for those
+who use Git and those who develop it. The server's search functionality also
+allows you to find previous conversations and answers to common questions.
+
[[getting-started]]
== Getting Started
diff --git a/Documentation/RelNotes/2.52.0.adoc b/Documentation/RelNotes/2.52.0.adoc
index 9f8607a752..4e8dbd0fc2 100644
--- a/Documentation/RelNotes/2.52.0.adoc
+++ b/Documentation/RelNotes/2.52.0.adoc
@@ -37,6 +37,9 @@ Performance, Internal Implementation, Development Support etc.
* Remove dependency on the_repository and other globals from the
commit-graph code, and other changes unrelated to de-globaling.
+ * Discord has been added to the first contribution documentation as
+ another way to ask for help.
+
Fixes since v2.51
-----------------
@@ -86,6 +89,42 @@ including security updates, are included in this release.
ignored") did not work well with "--name-only" and friends.
(merge b55e6d36eb ly/diff-name-only-with-diff-from-content later to maint).
+ * Documentation for "git rebase" has been updated.
+ (merge 3f7f2b0359 je/doc-rebase later to maint).
+
+ * The start_delayed_progress() function in the progress eye-candy API
+ did not clear its internal state, making an initial delay value
+ larger than 1 second ineffective, which has been corrected.
+ (merge 457534d041 js/progress-delay-fix later to maint).
+
+ * The compatObjectFormat extension is used to hide an incomplete
+ feature that is not yet usable for any purpose other than
+ developing the feature further. Document it as such to discourage
+ its use by mere mortals.
+ (merge 716d905792 bc/doc-compat-object-format-not-working later to maint).
+
+ * "git log -L..." compared trees of multiple parents with the tree of the
+ merge result in an unnecessarily inefficient way.
+ (merge 0a15bb634c sg/line-log-merge-optim later to maint).
+
+ * Under a race against another process that is repacking the
+ repository, especially a partially cloned one, "git fetch" may
+ mistakenly think some objects we do have are missing, which has
+ been corrected.
+ (merge 8f32a5a6c0 jk/fetch-check-graph-objects-fix later to maint).
+
+ * "git fetch" can clobber a symref that is dangling when the
+ remote-tracking HEAD is set to auto update, which has been
+ corrected.
+
+ * "git describe <blob>" misbehaves and/or crashes in some corner
+ cases, which has been taught to exit with failure gracefully.
+ (merge 7c10e48e81 jk/describe-blob later to maint).
+
+ * Manual page for "gitk" is updated with the current maintainer's
+ name.
+ (merge bcb20dda83 js/doc-gitk-history later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 823d537fa7 kh/doc-git-log-markup-fix later to maint).
(merge cf7efa4f33 rj/t6137-cygwin-fix later to maint).
@@ -94,3 +133,4 @@ including security updates, are included in this release.
(merge 741f36c7d9 kr/clone-synopsis-fix later to maint).
(merge a60203a015 dk/t7005-editor-updates later to maint).
(merge 7d4a5fef7d ds/doc-count-objects-fix later to maint).
+ (merge 16684b6fae ps/reftable-libgit2-cleanup later to maint).
diff --git a/Documentation/gitk.adoc b/Documentation/gitk.adoc
index 58ce40ddb1..5b34dcd077 100644
--- a/Documentation/gitk.adoc
+++ b/Documentation/gitk.adoc
@@ -163,16 +163,16 @@ used by default. If '$XDG_CONFIG_HOME' is not set it defaults to
History
-------
-Gitk was the first graphical repository browser. It's written in
-tcl/tk.
+Gitk was the first graphical repository browser, written by
+Paul Mackerras in Tcl/Tk.
'gitk' is actually maintained as an independent project, but stable
versions are distributed as part of the Git suite for the convenience
of end users.
-gitk-git/ comes from Paul Mackerras's gitk project:
+`gitk-git/` comes from Johannes Sixt's gitk project:
- git://ozlabs.org/~paulus/gitk
+ https://github.com/j6t/gitk
SEE ALSO
--------
diff --git a/builtin/describe.c b/builtin/describe.c
index 0540976210..fbe78ace66 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -352,9 +352,9 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf
repo_find_unique_abbrev(the_repository, oid, abbrev));
}
-static void describe_commit(struct object_id *oid, struct strbuf *dst)
+static void describe_commit(struct commit *cmit, struct strbuf *dst)
{
- struct commit *cmit, *gave_up_on = NULL;
+ struct commit *gave_up_on = NULL;
struct lazy_queue queue = LAZY_QUEUE_INIT;
struct commit_name *n;
struct possible_tag all_matches[MAX_TAGS];
@@ -362,8 +362,6 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
unsigned long seen_commits = 0;
unsigned int unannotated_cnt = 0;
- cmit = lookup_commit_reference(the_repository, oid);
-
n = find_commit_name(&cmit->object.oid);
if (n && (tags || all || n->prio == 2)) {
/*
@@ -371,7 +369,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
*/
append_name(n, dst);
if (n->misnamed || longformat)
- append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst);
+ append_suffix(0, n->tag ? get_tagged_oid(n->tag) : &cmit->object.oid, dst);
if (suffix)
strbuf_addstr(dst, suffix);
return;
@@ -528,8 +526,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst)
}
struct process_commit_data {
- struct object_id current_commit;
- struct object_id looking_for;
+ struct commit *current_commit;
+ const struct object_id *looking_for;
struct strbuf *dst;
struct rev_info *revs;
};
@@ -537,30 +535,38 @@ struct process_commit_data {
static void process_commit(struct commit *commit, void *data)
{
struct process_commit_data *pcd = data;
- pcd->current_commit = commit->object.oid;
+ pcd->current_commit = commit;
}
static void process_object(struct object *obj, const char *path, void *data)
{
struct process_commit_data *pcd = data;
- if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) {
+ if (oideq(pcd->looking_for, &obj->oid) && !pcd->dst->len) {
reset_revision_walk();
- describe_commit(&pcd->current_commit, pcd->dst);
- strbuf_addf(pcd->dst, ":%s", path);
+ if (pcd->current_commit) {
+ describe_commit(pcd->current_commit, pcd->dst);
+ strbuf_addf(pcd->dst, ":%s", path);
+ }
free_commit_list(pcd->revs->commits);
pcd->revs->commits = NULL;
}
}
-static void describe_blob(struct object_id oid, struct strbuf *dst)
+static void describe_blob(const struct object_id *oid, struct strbuf *dst)
{
struct rev_info revs;
struct strvec args = STRVEC_INIT;
- struct process_commit_data pcd = { *null_oid(the_hash_algo), oid, dst, &revs};
+ struct object_id head_oid;
+ struct process_commit_data pcd = { NULL, oid, dst, &revs};
+
+ if (repo_get_oid(the_repository, "HEAD", &head_oid))
+ die(_("cannot search for blob '%s' on an unborn branch"),
+ oid_to_hex(oid));
strvec_pushl(&args, "internal: The first arg is not parsed",
- "--objects", "--in-commit-order", "--reverse", "HEAD",
+ "--objects", "--in-commit-order", "--reverse",
+ oid_to_hex(&head_oid),
NULL);
repo_init_revisions(the_repository, &revs, NULL);
@@ -574,6 +580,9 @@ static void describe_blob(struct object_id oid, struct strbuf *dst)
reset_revision_walk();
release_revisions(&revs);
strvec_clear(&args);
+
+ if (!dst->len)
+ die(_("blob '%s' not reachable from HEAD"), oid_to_hex(oid));
}
static void describe(const char *arg, int last_one)
@@ -590,10 +599,10 @@ static void describe(const char *arg, int last_one)
cmit = lookup_commit_reference_gently(the_repository, &oid, 1);
if (cmit)
- describe_commit(&oid, &sb);
+ describe_commit(cmit, &sb);
else if (odb_read_object_info(the_repository->objects,
&oid, NULL) == OBJ_BLOB)
- describe_blob(oid, &sb);
+ describe_blob(&oid, &sb);
else
die(_("%s is neither a commit nor blob"), arg);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index dfc8e9bc50..1b3bf26add 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2515,13 +2515,37 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update,
*/
static enum ref_transaction_error check_old_oid(struct ref_update *update,
struct object_id *oid,
+ struct strbuf *referent,
struct strbuf *err)
{
if (update->flags & REF_LOG_ONLY ||
- !(update->flags & REF_HAVE_OLD) ||
- oideq(oid, &update->old_oid))
+ !(update->flags & REF_HAVE_OLD))
return 0;
+ if (oideq(oid, &update->old_oid)) {
+ /*
+ * Normally matching the expected old oid is enough. Either we
+ * found the ref at the expected state, or we are creating and
+ * expect the null oid (and likewise found nothing).
+ *
+ * But there is one exception for the null oid: if we found a
+ * symref pointing to nothing we'll also get the null oid. In
+ * regular recursive mode, that's good (we'll write to what the
+ * symref points to, which doesn't exist). But in no-deref
+ * mode, it means we'll clobber the symref, even though the
+ * caller asked for this to be a creation event. So flag
+ * that case to preserve the dangling symref.
+ */
+ if ((update->flags & REF_NO_DEREF) && referent->len &&
+ is_null_oid(oid)) {
+ strbuf_addf(err, "cannot lock ref '%s': "
+ "dangling symref already exists",
+ ref_update_original_update_refname(update));
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ }
+ return 0;
+ }
+
if (is_null_oid(&update->old_oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
@@ -2661,7 +2685,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
if (update->old_target)
ret = ref_update_check_old_target(referent.buf, update, err);
else
- ret = check_old_oid(update, &lock->old_oid, err);
+ ret = check_old_oid(update, &lock->old_oid,
+ &referent, err);
if (ret)
goto out;
} else {
@@ -2693,7 +2718,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re
ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
goto out;
} else {
- ret = check_old_oid(update, &lock->old_oid, err);
+ ret = check_old_oid(update, &lock->old_oid,
+ &referent, err);
if (ret) {
goto out;
}
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 570463da41..9e889da2ff 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1012,10 +1012,6 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out,
if (!arg) {
struct reftable_addition *addition;
- ret = reftable_stack_reload(be->stack);
- if (ret)
- return ret;
-
ret = reftable_stack_new_addition(&addition, be->stack,
REFTABLE_STACK_NEW_ADDITION_RELOAD);
if (ret) {
@@ -1278,9 +1274,33 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor
ret = ref_update_check_old_target(referent->buf, u, err);
if (ret)
return ret;
- } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD &&
- !oideq(&current_oid, &u->old_oid)) {
- if (is_null_oid(&u->old_oid)) {
+ } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD) {
+ if (oideq(&current_oid, &u->old_oid)) {
+ /*
+ * Normally matching the expected old oid is enough. Either we
+ * found the ref at the expected state, or we are creating and
+ * expect the null oid (and likewise found nothing).
+ *
+ * But there is one exception for the null oid: if we found a
+ * symref pointing to nothing we'll also get the null oid. In
+ * regular recursive mode, that's good (we'll write to what the
+ * symref points to, which doesn't exist). But in no-deref
+ * mode, it means we'll clobber the symref, even though the
+ * caller asked for this to be a creation event. So flag
+ * that case to preserve the dangling symref.
+ *
+ * Everything else is OK and we can fall through to the
+ * end of the conditional chain.
+ */
+ if ((u->flags & REF_NO_DEREF) &&
+ referent->len &&
+ is_null_oid(&u->old_oid)) {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "dangling symref already exists"),
+ ref_update_original_update_refname(u));
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ }
+ } else if (is_null_oid(&u->old_oid)) {
strbuf_addf(err, _("cannot lock ref '%s': "
"reference already exists"),
ref_update_original_update_refname(u));
@@ -1974,7 +1994,8 @@ static int reftable_be_rename_ref(struct ref_store *ref_store,
ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1);
if (ret)
goto done;
- ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg);
+ ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
done:
assert(ret != REFTABLE_API_ERROR);
@@ -2003,7 +2024,8 @@ static int reftable_be_copy_ref(struct ref_store *ref_store,
ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1);
if (ret)
goto done;
- ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg);
+ ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
done:
assert(ret != REFTABLE_API_ERROR);
@@ -2375,7 +2397,8 @@ static int reftable_be_create_reflog(struct ref_store *ref_store,
goto done;
arg.stack = be->stack;
- ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg);
+ ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
done:
return ret;
@@ -2446,7 +2469,8 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store,
return ret;
arg.stack = be->stack;
- ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg);
+ ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
assert(ret != REFTABLE_API_ERROR);
return ret;
@@ -2567,15 +2591,16 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store,
if (ret < 0)
goto done;
- ret = reftable_stack_init_log_iterator(be->stack, &it);
+ ret = reftable_stack_new_addition(&add, be->stack,
+ REFTABLE_STACK_NEW_ADDITION_RELOAD);
if (ret < 0)
goto done;
- ret = reftable_iterator_seek_log(&it, refname);
+ ret = reftable_stack_init_log_iterator(be->stack, &it);
if (ret < 0)
goto done;
- ret = reftable_stack_new_addition(&add, be->stack, 0);
+ ret = reftable_iterator_seek_log(&it, refname);
if (ret < 0)
goto done;
diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h
index 910ec6ef3a..d70fcb705d 100644
--- a/reftable/reftable-stack.h
+++ b/reftable/reftable-stack.h
@@ -68,12 +68,15 @@ int reftable_addition_commit(struct reftable_addition *add);
* transaction. Releases the lock if held. */
void reftable_addition_destroy(struct reftable_addition *add);
-/* add a new table to the stack. The write_table function must call
- * reftable_writer_set_limits, add refs and return an error value. */
+/*
+ * Add a new table to the stack. The write_table function must call
+ * reftable_writer_set_limits, add refs and return an error value.
+ * The flags are passed through to `reftable_stack_new_addition()`.
+ */
int reftable_stack_add(struct reftable_stack *st,
int (*write_table)(struct reftable_writer *wr,
void *write_arg),
- void *write_arg);
+ void *write_arg, unsigned flags);
struct reftable_iterator;
diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h
index 0fbeff17f4..1e7003cd69 100644
--- a/reftable/reftable-writer.h
+++ b/reftable/reftable-writer.h
@@ -156,7 +156,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
the records before adding them, reordering the records array passed in.
*/
int reftable_writer_add_refs(struct reftable_writer *w,
- struct reftable_ref_record *refs, int n);
+ struct reftable_ref_record *refs, size_t n);
/*
adds reftable_log_records. Log records are keyed by (refname, decreasing
@@ -171,7 +171,7 @@ int reftable_writer_add_log(struct reftable_writer *w,
the records before adding them, reordering records array passed in.
*/
int reftable_writer_add_logs(struct reftable_writer *w,
- struct reftable_log_record *logs, int n);
+ struct reftable_log_record *logs, size_t n);
/* reftable_writer_close finalizes the reftable. The writer is retained so
* statistics can be inspected. */
diff --git a/reftable/stack.c b/reftable/stack.c
index 4caf96aa1d..f91ce50bcd 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -17,18 +17,6 @@
#include "table.h"
#include "writer.h"
-static int stack_try_add(struct reftable_stack *st,
- int (*write_table)(struct reftable_writer *wr,
- void *arg),
- void *arg);
-static int stack_write_compact(struct reftable_stack *st,
- struct reftable_writer *wr,
- size_t first, size_t last,
- struct reftable_log_expiry_config *config);
-static void reftable_addition_close(struct reftable_addition *add);
-static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
- int reuse_open);
-
static int stack_filename(struct reftable_buf *dest, struct reftable_stack *st,
const char *name)
{
@@ -84,54 +72,6 @@ static int fd_writer_flush(void *arg)
return stack_fsync(writer->opts, writer->fd);
}
-int reftable_new_stack(struct reftable_stack **dest, const char *dir,
- const struct reftable_write_options *_opts)
-{
- struct reftable_buf list_file_name = REFTABLE_BUF_INIT;
- struct reftable_write_options opts = { 0 };
- struct reftable_stack *p;
- int err;
-
- p = reftable_calloc(1, sizeof(*p));
- if (!p) {
- err = REFTABLE_OUT_OF_MEMORY_ERROR;
- goto out;
- }
-
- if (_opts)
- opts = *_opts;
- if (opts.hash_id == 0)
- opts.hash_id = REFTABLE_HASH_SHA1;
-
- *dest = NULL;
-
- reftable_buf_reset(&list_file_name);
- if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 ||
- (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0)
- goto out;
-
- p->list_file = reftable_buf_detach(&list_file_name);
- p->list_fd = -1;
- p->opts = opts;
- p->reftable_dir = reftable_strdup(dir);
- if (!p->reftable_dir) {
- err = REFTABLE_OUT_OF_MEMORY_ERROR;
- goto out;
- }
-
- err = reftable_stack_reload_maybe_reuse(p, 1);
- if (err < 0)
- goto out;
-
- *dest = p;
- err = 0;
-
-out:
- if (err < 0)
- reftable_stack_destroy(p);
- return err;
-}
-
static int fd_read_lines(int fd, char ***namesp)
{
char *buf = NULL;
@@ -591,9 +531,59 @@ out:
return err;
}
-/* -1 = error
- 0 = up to date
- 1 = changed. */
+int reftable_new_stack(struct reftable_stack **dest, const char *dir,
+ const struct reftable_write_options *_opts)
+{
+ struct reftable_buf list_file_name = REFTABLE_BUF_INIT;
+ struct reftable_write_options opts = { 0 };
+ struct reftable_stack *p;
+ int err;
+
+ p = reftable_calloc(1, sizeof(*p));
+ if (!p) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+
+ if (_opts)
+ opts = *_opts;
+ if (opts.hash_id == 0)
+ opts.hash_id = REFTABLE_HASH_SHA1;
+
+ *dest = NULL;
+
+ reftable_buf_reset(&list_file_name);
+ if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 ||
+ (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0)
+ goto out;
+
+ p->list_file = reftable_buf_detach(&list_file_name);
+ p->list_fd = -1;
+ p->opts = opts;
+ p->reftable_dir = reftable_strdup(dir);
+ if (!p->reftable_dir) {
+ err = REFTABLE_OUT_OF_MEMORY_ERROR;
+ goto out;
+ }
+
+ err = reftable_stack_reload_maybe_reuse(p, 1);
+ if (err < 0)
+ goto out;
+
+ *dest = p;
+ err = 0;
+
+out:
+ if (err < 0)
+ reftable_stack_destroy(p);
+ return err;
+}
+
+/*
+ * Check whether the given stack is up-to-date with what we have in memory.
+ * Returns 0 if so, 1 if the stack is out-of-date or a negative error code
+ * otherwise.
+ */
static int stack_uptodate(struct reftable_stack *st)
{
char **names = NULL;
@@ -667,34 +657,6 @@ int reftable_stack_reload(struct reftable_stack *st)
return err;
}
-int reftable_stack_add(struct reftable_stack *st,
- int (*write)(struct reftable_writer *wr, void *arg),
- void *arg)
-{
- int err = stack_try_add(st, write, arg);
- if (err < 0) {
- if (err == REFTABLE_OUTDATED_ERROR) {
- /* Ignore error return, we want to propagate
- REFTABLE_OUTDATED_ERROR.
- */
- reftable_stack_reload(st);
- }
- return err;
- }
-
- return 0;
-}
-
-static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max)
-{
- char buf[100];
- uint32_t rnd = reftable_rand();
- snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x",
- min, max, rnd);
- reftable_buf_reset(dest);
- return reftable_buf_addstr(dest, buf);
-}
-
struct reftable_addition {
struct reftable_flock tables_list_lock;
struct reftable_stack *stack;
@@ -704,7 +666,25 @@ struct reftable_addition {
uint64_t next_update_index;
};
-#define REFTABLE_ADDITION_INIT {0}
+static void reftable_addition_close(struct reftable_addition *add)
+{
+ struct reftable_buf nm = REFTABLE_BUF_INIT;
+ size_t i;
+
+ for (i = 0; i < add->new_tables_len; i++) {
+ if (!stack_filename(&nm, add->stack, add->new_tables[i]))
+ unlink(nm.buf);
+ reftable_free(add->new_tables[i]);
+ add->new_tables[i] = NULL;
+ }
+ reftable_free(add->new_tables);
+ add->new_tables = NULL;
+ add->new_tables_len = 0;
+ add->new_tables_cap = 0;
+
+ flock_release(&add->tables_list_lock);
+ reftable_buf_release(&nm);
+}
static int reftable_stack_init_addition(struct reftable_addition *add,
struct reftable_stack *st,
@@ -713,18 +693,14 @@ static int reftable_stack_init_addition(struct reftable_addition *add,
struct reftable_buf lock_file_name = REFTABLE_BUF_INIT;
int err;
+ memset(add, 0, sizeof(*add));
add->stack = st;
err = flock_acquire(&add->tables_list_lock, st->list_file,
st->opts.lock_timeout_ms);
- if (err < 0) {
- if (errno == EEXIST) {
- err = REFTABLE_LOCK_ERROR;
- } else {
- err = REFTABLE_IO_ERROR;
- }
+ if (err < 0)
goto done;
- }
+
if (st->opts.default_permissions) {
if (chmod(add->tables_list_lock.path,
st->opts.default_permissions) < 0) {
@@ -754,24 +730,54 @@ done:
return err;
}
-static void reftable_addition_close(struct reftable_addition *add)
+static int stack_try_add(struct reftable_stack *st,
+ int (*write_table)(struct reftable_writer *wr,
+ void *arg),
+ void *arg, unsigned flags)
{
- struct reftable_buf nm = REFTABLE_BUF_INIT;
- size_t i;
+ struct reftable_addition add;
+ int err;
- for (i = 0; i < add->new_tables_len; i++) {
- if (!stack_filename(&nm, add->stack, add->new_tables[i]))
- unlink(nm.buf);
- reftable_free(add->new_tables[i]);
- add->new_tables[i] = NULL;
+ err = reftable_stack_init_addition(&add, st, flags);
+ if (err < 0)
+ goto done;
+
+ err = reftable_addition_add(&add, write_table, arg);
+ if (err < 0)
+ goto done;
+
+ err = reftable_addition_commit(&add);
+done:
+ reftable_addition_close(&add);
+ return err;
+}
+
+int reftable_stack_add(struct reftable_stack *st,
+ int (*write)(struct reftable_writer *wr, void *arg),
+ void *arg, unsigned flags)
+{
+ int err = stack_try_add(st, write, arg, flags);
+ if (err < 0) {
+ if (err == REFTABLE_OUTDATED_ERROR) {
+ /* Ignore error return, we want to propagate
+ REFTABLE_OUTDATED_ERROR.
+ */
+ reftable_stack_reload(st);
+ }
+ return err;
}
- reftable_free(add->new_tables);
- add->new_tables = NULL;
- add->new_tables_len = 0;
- add->new_tables_cap = 0;
- flock_release(&add->tables_list_lock);
- reftable_buf_release(&nm);
+ return 0;
+}
+
+static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max)
+{
+ char buf[100];
+ uint32_t rnd = reftable_rand();
+ snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x",
+ min, max, rnd);
+ reftable_buf_reset(dest);
+ return reftable_buf_addstr(dest, buf);
}
void reftable_addition_destroy(struct reftable_addition *add)
@@ -841,10 +847,13 @@ int reftable_addition_commit(struct reftable_addition *add)
* control. It is possible that a concurrent writer is already
* trying to compact parts of the stack, which would lead to a
* `REFTABLE_LOCK_ERROR` because parts of the stack are locked
- * already. This is a benign error though, so we ignore it.
+ * already. Similarly, the stack may have been rewritten by a
+ * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`.
+ * Both of these errors are benign, so we simply ignore them.
*/
err = reftable_stack_auto_compact(add->stack);
- if (err < 0 && err != REFTABLE_LOCK_ERROR)
+ if (err < 0 && err != REFTABLE_LOCK_ERROR &&
+ err != REFTABLE_OUTDATED_ERROR)
goto done;
err = 0;
}
@@ -858,39 +867,18 @@ int reftable_stack_new_addition(struct reftable_addition **dest,
struct reftable_stack *st,
unsigned int flags)
{
- int err = 0;
- struct reftable_addition empty = REFTABLE_ADDITION_INIT;
+ int err;
REFTABLE_CALLOC_ARRAY(*dest, 1);
if (!*dest)
return REFTABLE_OUT_OF_MEMORY_ERROR;
- **dest = empty;
err = reftable_stack_init_addition(*dest, st, flags);
if (err) {
reftable_free(*dest);
*dest = NULL;
}
- return err;
-}
-static int stack_try_add(struct reftable_stack *st,
- int (*write_table)(struct reftable_writer *wr,
- void *arg),
- void *arg)
-{
- struct reftable_addition add = REFTABLE_ADDITION_INIT;
- int err = reftable_stack_init_addition(&add, st, 0);
- if (err < 0)
- goto done;
-
- err = reftable_addition_add(&add, write_table, arg);
- if (err < 0)
- goto done;
-
- err = reftable_addition_commit(&add);
-done:
- reftable_addition_close(&add);
return err;
}
@@ -1007,72 +995,6 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st)
return 1;
}
-static int stack_compact_locked(struct reftable_stack *st,
- size_t first, size_t last,
- struct reftable_log_expiry_config *config,
- struct reftable_tmpfile *tab_file_out)
-{
- struct reftable_buf next_name = REFTABLE_BUF_INIT;
- struct reftable_buf tab_file_path = REFTABLE_BUF_INIT;
- struct reftable_writer *wr = NULL;
- struct fd_writer writer= {
- .opts = &st->opts,
- };
- struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;
- int err = 0;
-
- err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]),
- reftable_table_max_update_index(st->tables[last]));
- if (err < 0)
- goto done;
-
- err = stack_filename(&tab_file_path, st, next_name.buf);
- if (err < 0)
- goto done;
-
- err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX");
- if (err < 0)
- goto done;
-
- err = tmpfile_from_pattern(&tab_file, tab_file_path.buf);
- if (err < 0)
- goto done;
-
- if (st->opts.default_permissions &&
- chmod(tab_file.path, st->opts.default_permissions) < 0) {
- err = REFTABLE_IO_ERROR;
- goto done;
- }
-
- writer.fd = tab_file.fd;
- err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,
- &writer, &st->opts);
- if (err < 0)
- goto done;
-
- err = stack_write_compact(st, wr, first, last, config);
- if (err < 0)
- goto done;
-
- err = reftable_writer_close(wr);
- if (err < 0)
- goto done;
-
- err = tmpfile_close(&tab_file);
- if (err < 0)
- goto done;
-
- *tab_file_out = tab_file;
- tab_file = REFTABLE_TMPFILE_INIT;
-
-done:
- tmpfile_delete(&tab_file);
- reftable_writer_free(wr);
- reftable_buf_release(&next_name);
- reftable_buf_release(&tab_file_path);
- return err;
-}
-
static int stack_write_compact(struct reftable_stack *st,
struct reftable_writer *wr,
size_t first, size_t last,
@@ -1172,6 +1094,72 @@ done:
return err;
}
+static int stack_compact_locked(struct reftable_stack *st,
+ size_t first, size_t last,
+ struct reftable_log_expiry_config *config,
+ struct reftable_tmpfile *tab_file_out)
+{
+ struct reftable_buf next_name = REFTABLE_BUF_INIT;
+ struct reftable_buf tab_file_path = REFTABLE_BUF_INIT;
+ struct reftable_writer *wr = NULL;
+ struct fd_writer writer= {
+ .opts = &st->opts,
+ };
+ struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT;
+ int err = 0;
+
+ err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]),
+ reftable_table_max_update_index(st->tables[last]));
+ if (err < 0)
+ goto done;
+
+ err = stack_filename(&tab_file_path, st, next_name.buf);
+ if (err < 0)
+ goto done;
+
+ err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX");
+ if (err < 0)
+ goto done;
+
+ err = tmpfile_from_pattern(&tab_file, tab_file_path.buf);
+ if (err < 0)
+ goto done;
+
+ if (st->opts.default_permissions &&
+ chmod(tab_file.path, st->opts.default_permissions) < 0) {
+ err = REFTABLE_IO_ERROR;
+ goto done;
+ }
+
+ writer.fd = tab_file.fd;
+ err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush,
+ &writer, &st->opts);
+ if (err < 0)
+ goto done;
+
+ err = stack_write_compact(st, wr, first, last, config);
+ if (err < 0)
+ goto done;
+
+ err = reftable_writer_close(wr);
+ if (err < 0)
+ goto done;
+
+ err = tmpfile_close(&tab_file);
+ if (err < 0)
+ goto done;
+
+ *tab_file_out = tab_file;
+ tab_file = REFTABLE_TMPFILE_INIT;
+
+done:
+ tmpfile_delete(&tab_file);
+ reftable_writer_free(wr);
+ reftable_buf_release(&next_name);
+ reftable_buf_release(&tab_file_path);
+ return err;
+}
+
enum stack_compact_range_flags {
/*
* Perform a best-effort compaction. That is, even if we cannot lock
@@ -1219,17 +1207,27 @@ static int stack_compact_range(struct reftable_stack *st,
* which are part of the user-specified range.
*/
err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);
- if (err < 0) {
- if (errno == EEXIST)
- err = REFTABLE_LOCK_ERROR;
- else
- err = REFTABLE_IO_ERROR;
+ if (err < 0)
goto done;
- }
+ /*
+ * Check whether the stack is up-to-date. We unfortunately cannot
+ * handle the situation gracefully in case it's _not_ up-to-date
+ * because the range of tables that the user has requested us to
+ * compact may have been changed. So instead we abort.
+ *
+ * We could in theory improve the situation by having the caller not
+ * pass in a range, but instead the list of tables to compact. If so,
+ * we could check that relevant tables still exist. But for now it's
+ * good enough to just abort.
+ */
err = stack_uptodate(st);
- if (err)
+ if (err < 0)
+ goto done;
+ if (err > 0) {
+ err = REFTABLE_OUTDATED_ERROR;
goto done;
+ }
/*
* Lock all tables in the user-provided range. This is the slice of our
@@ -1264,7 +1262,7 @@ static int stack_compact_range(struct reftable_stack *st,
* tables, otherwise there would be nothing to compact.
* In that case, we return a lock error to our caller.
*/
- if (errno == EEXIST && last - (i - 1) >= 2 &&
+ if (err == REFTABLE_LOCK_ERROR && last - (i - 1) >= 2 &&
flags & STACK_COMPACT_RANGE_BEST_EFFORT) {
err = 0;
/*
@@ -1276,13 +1274,9 @@ static int stack_compact_range(struct reftable_stack *st,
*/
first = (i - 1) + 1;
break;
- } else if (errno == EEXIST) {
- err = REFTABLE_LOCK_ERROR;
- goto done;
- } else {
- err = REFTABLE_IO_ERROR;
- goto done;
}
+
+ goto done;
}
/*
@@ -1291,10 +1285,8 @@ static int stack_compact_range(struct reftable_stack *st,
* of tables.
*/
err = flock_close(&table_locks[nlocks++]);
- if (err < 0) {
- err = REFTABLE_IO_ERROR;
+ if (err < 0)
goto done;
- }
}
/*
@@ -1326,13 +1318,8 @@ static int stack_compact_range(struct reftable_stack *st,
* the new table.
*/
err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms);
- if (err < 0) {
- if (errno == EEXIST)
- err = REFTABLE_LOCK_ERROR;
- else
- err = REFTABLE_IO_ERROR;
+ if (err < 0)
goto done;
- }
if (st->opts.default_permissions) {
if (chmod(tables_list_lock.path,
diff --git a/reftable/system.c b/reftable/system.c
index 1ee268b125..725a25844e 100644
--- a/reftable/system.c
+++ b/reftable/system.c
@@ -72,7 +72,7 @@ int flock_acquire(struct reftable_flock *l, const char *target_path,
reftable_free(lockfile);
if (errno == EEXIST)
return REFTABLE_LOCK_ERROR;
- return -1;
+ return REFTABLE_IO_ERROR;
}
l->fd = get_lock_file_fd(lockfile);
diff --git a/reftable/system.h b/reftable/system.h
index beb9d2431f..c54ed4cad6 100644
--- a/reftable/system.h
+++ b/reftable/system.h
@@ -81,7 +81,9 @@ struct reftable_flock {
* to acquire the lock. If `timeout_ms` is 0 we don't wait, if it is negative
* we block indefinitely.
*
- * Retrun 0 on success, a reftable error code on error.
+ * Retrun 0 on success, a reftable error code on error. Specifically,
+ * `REFTABLE_LOCK_ERROR` should be returned in case the target path is already
+ * locked.
*/
int flock_acquire(struct reftable_flock *l, const char *target_path,
long timeout_ms);
diff --git a/reftable/writer.c b/reftable/writer.c
index 3b4ebdd6dc..0133b64975 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -395,14 +395,16 @@ out:
}
int reftable_writer_add_refs(struct reftable_writer *w,
- struct reftable_ref_record *refs, int n)
+ struct reftable_ref_record *refs, size_t n)
{
int err = 0;
- int i = 0;
- QSORT(refs, n, reftable_ref_record_compare_name);
- for (i = 0; err == 0 && i < n; i++) {
+
+ if (n)
+ qsort(refs, n, sizeof(*refs), reftable_ref_record_compare_name);
+
+ for (size_t i = 0; err == 0 && i < n; i++)
err = reftable_writer_add_ref(w, &refs[i]);
- }
+
return err;
}
@@ -486,15 +488,16 @@ done:
}
int reftable_writer_add_logs(struct reftable_writer *w,
- struct reftable_log_record *logs, int n)
+ struct reftable_log_record *logs, size_t n)
{
int err = 0;
- int i = 0;
- QSORT(logs, n, reftable_log_record_compare_key);
- for (i = 0; err == 0 && i < n; i++) {
+ if (n)
+ qsort(logs, n, sizeof(*logs), reftable_log_record_compare_key);
+
+ for (size_t i = 0; err == 0 && i < n; i++)
err = reftable_writer_add_log(w, &logs[i]);
- }
+
return err;
}
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index 96648a6e5d..b7415ec9d5 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -2368,4 +2368,25 @@ test_expect_success REFFILES 'empty directories are pruned when not committing'
test_path_is_missing .git/refs/heads/nested
'
+test_expect_success 'dangling symref not overwritten by creation' '
+ test_when_finished "git update-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ test_must_fail git update-ref --no-deref --stdin 2>err <<-\EOF &&
+ create refs/heads/dangling HEAD
+ EOF
+ test_grep "cannot lock.*dangling symref already exists" err &&
+ test_must_fail git rev-parse --verify refs/heads/dangling &&
+ test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
+test_expect_success 'dangling symref overwritten without old oid' '
+ test_when_finished "git update-ref -d refs/heads/dangling" &&
+ git symbolic-ref refs/heads/dangling refs/heads/does-not-exist &&
+ git update-ref --no-deref --stdin <<-\EOF &&
+ update refs/heads/dangling HEAD
+ EOF
+ git rev-parse --verify refs/heads/dangling &&
+ test_must_fail git rev-parse --verify refs/heads/does-not-exist
+'
+
test_done
diff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh
index 1c69d52c76..c824c1a25c 100755
--- a/t/t1517-outside-repo.sh
+++ b/t/t1517-outside-repo.sh
@@ -111,8 +111,11 @@ for cmd in $(git --list-cmds=main)
do
cmd=${cmd%.*} # strip .sh, .perl, etc.
case "$cmd" in
- archimport | cvsexportcommit | cvsimport | cvsserver | daemon | \
+ archimport | citool | credential-netrc | credential-libsecret | \
+ credential-osxkeychain | cvsexportcommit | cvsimport | cvsserver | \
+ daemon | \
difftool--helper | filter-branch | fsck-objects | get-tar-commit-id | \
+ gui | gui--askpass | \
http-backend | http-fetch | http-push | init-db | \
merge-octopus | merge-one-file | merge-resolve | mergetool | \
mktag | p4 | p4.py | pickaxe | remote-ftp | remote-ftps | \
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index ebc696546b..83d1aadf9f 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -14,8 +14,6 @@ then
test_done
fi
-D=$(pwd)
-
test_expect_success setup '
echo >file original &&
git add file &&
@@ -51,46 +49,50 @@ test_expect_success "clone and setup child repos" '
'
test_expect_success "fetch test" '
- cd "$D" &&
echo >file updated by origin &&
git commit -a -m "updated by origin" &&
- cd two &&
- git fetch &&
- git rev-parse --verify refs/heads/one &&
- mine=$(git rev-parse refs/heads/one) &&
- his=$(cd ../one && git rev-parse refs/heads/main) &&
- test "z$mine" = "z$his"
+ (
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/heads/one &&
+ mine=$(git rev-parse refs/heads/one) &&
+ his=$(cd ../one && git rev-parse refs/heads/main) &&
+ test "z$mine" = "z$his"
+ )
'
test_expect_success "fetch test for-merge" '
- cd "$D" &&
- cd three &&
- git fetch &&
- git rev-parse --verify refs/heads/two &&
- git rev-parse --verify refs/heads/one &&
- main_in_two=$(cd ../two && git rev-parse main) &&
- one_in_two=$(cd ../two && git rev-parse one) &&
- {
- echo "$one_in_two " &&
- echo "$main_in_two not-for-merge"
- } >expected &&
- cut -f -2 .git/FETCH_HEAD >actual &&
- test_cmp expected actual'
+ (
+ cd three &&
+ git fetch &&
+ git rev-parse --verify refs/heads/two &&
+ git rev-parse --verify refs/heads/one &&
+ main_in_two=$(cd ../two && git rev-parse main) &&
+ one_in_two=$(cd ../two && git rev-parse one) &&
+ {
+ echo "$one_in_two " &&
+ echo "$main_in_two not-for-merge"
+ } >expected &&
+ cut -f -2 .git/FETCH_HEAD >actual &&
+ test_cmp expected actual
+ )
+'
test_expect_success "fetch test remote HEAD" '
- cd "$D" &&
- cd two &&
- git fetch &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/main) &&
- test "z$head" = "z$branch"'
+ (
+ cd two &&
+ git fetch &&
+ git rev-parse --verify refs/remotes/origin/HEAD &&
+ git rev-parse --verify refs/remotes/origin/main &&
+ head=$(git rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
+ )
+'
test_expect_success "fetch test remote HEAD in bare repository" '
test_when_finished rm -rf barerepo &&
(
- cd "$D" &&
git init --bare barerepo &&
cd barerepo &&
git remote add upstream ../two &&
@@ -105,262 +107,235 @@ test_expect_success "fetch test remote HEAD in bare repository" '
test_expect_success "fetch test remote HEAD change" '
- cd "$D" &&
- cd two &&
- git switch -c other &&
- git push -u origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git fetch &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"'
-
-test_expect_success "fetch test followRemoteHEAD never" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git update-ref --no-deref -d refs/remotes/origin/HEAD &&
- git config set remote.origin.followRemoteHEAD "never" &&
- GIT_TRACE_PACKET=$PWD/trace.out git fetch &&
- # Confirm that we do not even ask for HEAD when we are
- # not going to act on it.
- test_grep ! "ref-prefix HEAD" trace.out &&
- test_must_fail git rev-parse --verify refs/remotes/origin/HEAD
- )
-'
-
-test_expect_success "fetch test followRemoteHEAD warn no change" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
(
- cd "$D" &&
cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
+ git switch -c other &&
+ git push -u origin other &&
git rev-parse --verify refs/remotes/origin/HEAD &&
git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- git fetch >output &&
- echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
- "but we have ${SQ}other${SQ} locally." >expect &&
- test_cmp expect output &&
+ git rev-parse --verify refs/remotes/origin/other &&
+ git remote set-head origin other &&
+ git fetch &&
head=$(git rev-parse refs/remotes/origin/HEAD) &&
branch=$(git rev-parse refs/remotes/origin/other) &&
test "z$head" = "z$branch"
)
'
+test_expect_success "fetch test followRemoteHEAD never" '
+ git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ test_config -C two remote.origin.followRemoteHEAD "never" &&
+ GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch &&
+ # Confirm that we do not even ask for HEAD when we are
+ # not going to act on it.
+ test_grep ! "ref-prefix HEAD" trace.out &&
+ test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD
+'
+
+test_expect_success "fetch test followRemoteHEAD warn no change" '
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ git -C two fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ test_cmp expect output &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
+'
+
test_expect_success "fetch test followRemoteHEAD warn create" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git update-ref --no-deref -d refs/remotes/origin/HEAD &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- git rev-parse --verify refs/remotes/origin/main &&
- output=$(git fetch) &&
- test "z" = "z$output" &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/main) &&
- test "z$head" = "z$branch"
- )
+ git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ output=$(git -C two fetch) &&
+ test "z" = "z$output" &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD warn detached" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git update-ref --no-deref -d refs/remotes/origin/HEAD &&
- git update-ref refs/remotes/origin/HEAD HEAD &&
- HEAD=$(git log --pretty="%H") &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- git fetch >output &&
- echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
- "but we have a detached HEAD pointing to" \
- "${SQ}${HEAD}${SQ} locally." >expect &&
- test_cmp expect output
- )
+ git -C two update-ref --no-deref -d refs/remotes/origin/HEAD &&
+ git -C two update-ref refs/remotes/origin/HEAD HEAD &&
+ HEAD=$(git -C two log --pretty="%H") &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ git -C two fetch >output &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have a detached HEAD pointing to" \
+ "${SQ}${HEAD}${SQ} locally." >expect &&
+ test_cmp expect output
'
test_expect_success "fetch test followRemoteHEAD warn quiet" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn" &&
- output=$(git fetch --quiet) &&
- test "z" = "z$output" &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn" &&
+ output=$(git -C two fetch --quiet) &&
+ test "z" = "z$output" &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn-if-not-main" &&
- actual=$(git fetch) &&
- test "z" = "z$actual" &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn-if-not-main" &&
+ actual=$(git -C two fetch) &&
+ test "z" = "z$actual" &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
- git fetch >actual &&
- echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
- "but we have ${SQ}other${SQ} locally." >expect &&
- test_cmp expect actual &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/other) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" &&
+ git -C two fetch >actual &&
+ echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \
+ "but we have ${SQ}other${SQ} locally." >expect &&
+ test_cmp expect actual &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/other) &&
+ test "z$head" = "z$branch"
'
test_expect_success "fetch test followRemoteHEAD always" '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git rev-parse --verify refs/remotes/origin/other &&
- git remote set-head origin other &&
- git rev-parse --verify refs/remotes/origin/HEAD &&
- git rev-parse --verify refs/remotes/origin/main &&
- git config set remote.origin.followRemoteHEAD "always" &&
- git fetch &&
- head=$(git rev-parse refs/remotes/origin/HEAD) &&
- branch=$(git rev-parse refs/remotes/origin/main) &&
- test "z$head" = "z$branch"
- )
+ git -C two rev-parse --verify refs/remotes/origin/other &&
+ git -C two remote set-head origin other &&
+ git -C two rev-parse --verify refs/remotes/origin/HEAD &&
+ git -C two rev-parse --verify refs/remotes/origin/main &&
+ test_config -C two remote.origin.followRemoteHEAD "always" &&
+ git -C two fetch &&
+ head=$(git -C two rev-parse refs/remotes/origin/HEAD) &&
+ branch=$(git -C two rev-parse refs/remotes/origin/main) &&
+ test "z$head" = "z$branch"
'
test_expect_success 'followRemoteHEAD does not kick in with refspecs' '
- test_when_finished "git config unset remote.origin.followRemoteHEAD" &&
- (
- cd "$D" &&
- cd two &&
- git remote set-head origin other &&
- git config set remote.origin.followRemoteHEAD always &&
- git fetch origin refs/heads/main:refs/remotes/origin/main &&
- echo refs/remotes/origin/other >expect &&
- git symbolic-ref refs/remotes/origin/HEAD >actual &&
- test_cmp expect actual
- )
+ git -C two remote set-head origin other &&
+ test_config -C two remote.origin.followRemoteHEAD always &&
+ git -C two fetch origin refs/heads/main:refs/remotes/origin/main &&
+ echo refs/remotes/origin/other >expect &&
+ git -C two symbolic-ref refs/remotes/origin/HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' '
+ git -C two remote add -m does-not-exist custom-head ../one &&
+ test_config -C two remote.custom-head.followRemoteHEAD create &&
+ git -C two fetch custom-head &&
+ echo refs/remotes/custom-head/does-not-exist >expect &&
+ git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual &&
+ test_cmp expect actual
'
test_expect_success 'fetch --prune on its own works as expected' '
- cd "$D" &&
git clone . prune &&
- cd prune &&
- git update-ref refs/remotes/origin/extrabranch main &&
+ (
+ cd prune &&
+ git update-ref refs/remotes/origin/extrabranch main &&
- git fetch --prune origin &&
- test_must_fail git rev-parse origin/extrabranch
+ git fetch --prune origin &&
+ test_must_fail git rev-parse origin/extrabranch
+ )
'
test_expect_success 'fetch --prune with a branch name keeps branches' '
- cd "$D" &&
git clone . prune-branch &&
- cd prune-branch &&
- git update-ref refs/remotes/origin/extrabranch main &&
+ (
+ cd prune-branch &&
+ git update-ref refs/remotes/origin/extrabranch main &&
- git fetch --prune origin main &&
- git rev-parse origin/extrabranch
+ git fetch --prune origin main &&
+ git rev-parse origin/extrabranch
+ )
'
test_expect_success 'fetch --prune with a namespace keeps other namespaces' '
- cd "$D" &&
git clone . prune-namespace &&
- cd prune-namespace &&
+ (
+ cd prune-namespace &&
- git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
- git rev-parse origin/main
+ git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
+ git rev-parse origin/main
+ )
'
test_expect_success 'fetch --prune handles overlapping refspecs' '
- cd "$D" &&
git update-ref refs/pull/42/head main &&
git clone . prune-overlapping &&
- cd prune-overlapping &&
- git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
+ (
+ cd prune-overlapping &&
+ git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
- git fetch --prune origin &&
- git rev-parse origin/main &&
- git rev-parse origin/pr/42 &&
+ git fetch --prune origin &&
+ git rev-parse origin/main &&
+ git rev-parse origin/pr/42 &&
- git config --unset-all remote.origin.fetch &&
- git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
- git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* &&
+ git config --unset-all remote.origin.fetch &&
+ git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* &&
+ git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* &&
- git fetch --prune origin &&
- git rev-parse origin/main &&
- git rev-parse origin/pr/42
+ git fetch --prune origin &&
+ git rev-parse origin/main &&
+ git rev-parse origin/pr/42
+ )
'
test_expect_success 'fetch --prune --tags prunes branches but not tags' '
- cd "$D" &&
git clone . prune-tags &&
- cd prune-tags &&
- git tag sometag main &&
- # Create what looks like a remote-tracking branch from an earlier
- # fetch that has since been deleted from the remote:
- git update-ref refs/remotes/origin/fake-remote main &&
-
- git fetch --prune --tags origin &&
- git rev-parse origin/main &&
- test_must_fail git rev-parse origin/fake-remote &&
- git rev-parse sometag
+ (
+ cd prune-tags &&
+ git tag sometag main &&
+ # Create what looks like a remote-tracking branch from an earlier
+ # fetch that has since been deleted from the remote:
+ git update-ref refs/remotes/origin/fake-remote main &&
+
+ git fetch --prune --tags origin &&
+ git rev-parse origin/main &&
+ test_must_fail git rev-parse origin/fake-remote &&
+ git rev-parse sometag
+ )
'
test_expect_success 'fetch --prune --tags with branch does not prune other things' '
- cd "$D" &&
git clone . prune-tags-branch &&
- cd prune-tags-branch &&
- git tag sometag main &&
- git update-ref refs/remotes/origin/extrabranch main &&
+ (
+ cd prune-tags-branch &&
+ git tag sometag main &&
+ git update-ref refs/remotes/origin/extrabranch main &&
- git fetch --prune --tags origin main &&
- git rev-parse origin/extrabranch &&
- git rev-parse sometag
+ git fetch --prune --tags origin main &&
+ git rev-parse origin/extrabranch &&
+ git rev-parse sometag
+ )
'
test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' '
- cd "$D" &&
git clone . prune-tags-refspec &&
- cd prune-tags-refspec &&
- git tag sometag main &&
- git update-ref refs/remotes/origin/foo/otherbranch main &&
- git update-ref refs/remotes/origin/extrabranch main &&
-
- git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
- test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
- git rev-parse origin/extrabranch &&
- git rev-parse sometag
+ (
+ cd prune-tags-refspec &&
+ git tag sometag main &&
+ git update-ref refs/remotes/origin/foo/otherbranch main &&
+ git update-ref refs/remotes/origin/extrabranch main &&
+
+ git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* &&
+ test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch &&
+ git rev-parse origin/extrabranch &&
+ git rev-parse sometag
+ )
'
test_expect_success 'fetch --tags gets tags even without a configured remote' '
@@ -381,21 +356,21 @@ test_expect_success 'fetch --tags gets tags even without a configured remote' '
'
test_expect_success REFFILES 'fetch --prune fails to delete branches' '
- cd "$D" &&
git clone . prune-fail &&
- cd prune-fail &&
- git update-ref refs/remotes/origin/extrabranch main &&
- git pack-refs --all &&
- : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds &&
- >.git/packed-refs.new &&
+ (
+ cd prune-fail &&
+ git update-ref refs/remotes/origin/extrabranch main &&
+ git pack-refs --all &&
+ : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds &&
+ >.git/packed-refs.new &&
- test_must_fail git fetch --prune origin
+ test_must_fail git fetch --prune origin
+ )
'
test_expect_success 'fetch --atomic works with a single branch' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-branch &&
oid=$(git rev-parse atomic-branch) &&
@@ -408,9 +383,8 @@ test_expect_success 'fetch --atomic works with a single branch' '
'
test_expect_success 'fetch --atomic works with multiple branches' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-branch-1 &&
git branch atomic-branch-2 &&
@@ -423,9 +397,8 @@ test_expect_success 'fetch --atomic works with multiple branches' '
'
test_expect_success 'fetch --atomic works with mixed branches and tags' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-mixed-branch &&
git tag atomic-mixed-tag &&
@@ -437,9 +410,8 @@ test_expect_success 'fetch --atomic works with mixed branches and tags' '
'
test_expect_success 'fetch --atomic prunes references' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git branch atomic-prune-delete &&
git clone . atomic &&
git branch --delete atomic-prune-delete &&
@@ -453,9 +425,8 @@ test_expect_success 'fetch --atomic prunes references' '
'
test_expect_success 'fetch --atomic aborts with non-fast-forward update' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git branch atomic-non-ff &&
git clone . atomic &&
git rev-parse HEAD >actual &&
@@ -472,9 +443,8 @@ test_expect_success 'fetch --atomic aborts with non-fast-forward update' '
'
test_expect_success 'fetch --atomic executes a single reference transaction only' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-hooks-1 &&
git branch atomic-hooks-2 &&
@@ -499,9 +469,8 @@ test_expect_success 'fetch --atomic executes a single reference transaction only
'
test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
git branch atomic-hooks-abort-1 &&
git branch atomic-hooks-abort-2 &&
@@ -536,9 +505,8 @@ test_expect_success 'fetch --atomic aborts all reference updates if hook aborts'
'
test_expect_success 'fetch --atomic --append appends to FETCH_HEAD' '
- test_when_finished "rm -rf \"$D\"/atomic" &&
+ test_when_finished "rm -rf atomic" &&
- cd "$D" &&
git clone . atomic &&
oid=$(git rev-parse HEAD) &&
@@ -574,8 +542,7 @@ test_expect_success REFFILES 'fetch --atomic fails transaction if reference lock
'
test_expect_success '--refmap="" ignores configured refspec' '
- cd "$TRASH_DIRECTORY" &&
- git clone "$D" remote-refs &&
+ git clone . remote-refs &&
git -C remote-refs rev-parse remotes/origin/main >old &&
git -C remote-refs update-ref refs/remotes/origin/main main~1 &&
git -C remote-refs rev-parse remotes/origin/main >new &&
@@ -599,34 +566,26 @@ test_expect_success '--refmap="" and --prune' '
test_expect_success 'fetch tags when there is no tags' '
- cd "$D" &&
-
- mkdir notags &&
- cd notags &&
- git init &&
-
- git fetch -t ..
+ git init notags &&
+ git -C notags fetch -t ..
'
test_expect_success 'fetch following tags' '
- cd "$D" &&
git tag -a -m "annotated" anno HEAD &&
git tag light HEAD &&
- mkdir four &&
- cd four &&
- git init &&
-
- git fetch .. :track &&
- git show-ref --verify refs/tags/anno &&
- git show-ref --verify refs/tags/light
-
+ git init four &&
+ (
+ cd four &&
+ git fetch .. :track &&
+ git show-ref --verify refs/tags/anno &&
+ git show-ref --verify refs/tags/light
+ )
'
test_expect_success 'fetch uses remote ref names to describe new refs' '
- cd "$D" &&
git init descriptive &&
(
cd descriptive &&
@@ -654,30 +613,20 @@ test_expect_success 'fetch uses remote ref names to describe new refs' '
test_expect_success 'fetch must not resolve short tag name' '
- cd "$D" &&
-
- mkdir five &&
- cd five &&
- git init &&
-
- test_must_fail git fetch .. anno:five
+ git init five &&
+ test_must_fail git -C five fetch .. anno:five
'
test_expect_success 'fetch can now resolve short remote name' '
- cd "$D" &&
git update-ref refs/remotes/six/HEAD HEAD &&
- mkdir six &&
- cd six &&
- git init &&
-
- git fetch .. six:six
+ git init six &&
+ git -C six fetch .. six:six
'
test_expect_success 'create bundle 1' '
- cd "$D" &&
echo >file updated again by origin &&
git commit -a -m "tip" &&
git bundle create --version=3 bundle1 main^..main
@@ -691,35 +640,36 @@ test_expect_success 'header of bundle looks right' '
OID refs/heads/main
EOF
- sed -e "s/$OID_REGEX/OID/g" -e "5q" "$D"/bundle1 >actual &&
+ sed -e "s/$OID_REGEX/OID/g" -e "5q" bundle1 >actual &&
test_cmp expect actual
'
test_expect_success 'create bundle 2' '
- cd "$D" &&
git bundle create bundle2 main~2..main
'
test_expect_success 'unbundle 1' '
- cd "$D/bundle" &&
- git checkout -b some-branch &&
- test_must_fail git fetch "$D/bundle1" main:main
+ (
+ cd bundle &&
+ git checkout -b some-branch &&
+ test_must_fail git fetch bundle1 main:main
+ )
'
test_expect_success 'bundle 1 has only 3 files ' '
- cd "$D" &&
test_bundle_object_count bundle1 3
'
test_expect_success 'unbundle 2' '
- cd "$D/bundle" &&
- git fetch ../bundle2 main:main &&
- test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)"
+ (
+ cd bundle &&
+ git fetch ../bundle2 main:main &&
+ test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)"
+ )
'
test_expect_success 'bundle does not prerequisite objects' '
- cd "$D" &&
touch file2 &&
git add file2 &&
git commit -m add.file2 file2 &&
@@ -729,7 +679,6 @@ test_expect_success 'bundle does not prerequisite objects' '
test_expect_success 'bundle should be able to create a full history' '
- cd "$D" &&
git tag -a -m "1.0" v1.0 main &&
git bundle create bundle4 v1.0
@@ -783,7 +732,6 @@ test_expect_success 'quoting of a strangely named repo' '
test_expect_success 'bundle should record HEAD correctly' '
- cd "$D" &&
git bundle create bundle5 HEAD main &&
git bundle list-heads bundle5 >actual &&
for h in HEAD refs/heads/main
@@ -803,7 +751,6 @@ test_expect_success 'mark initial state of origin/main' '
test_expect_success 'explicit fetch should update tracking' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -818,7 +765,6 @@ test_expect_success 'explicit fetch should update tracking' '
test_expect_success 'explicit pull should update tracking' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -832,7 +778,6 @@ test_expect_success 'explicit pull should update tracking' '
'
test_expect_success 'explicit --refmap is allowed only with command-line refspec' '
- cd "$D" &&
(
cd three &&
test_must_fail git fetch --refmap="*:refs/remotes/none/*"
@@ -840,7 +785,6 @@ test_expect_success 'explicit --refmap is allowed only with command-line refspec
'
test_expect_success 'explicit --refmap option overrides remote.*.fetch' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -855,7 +799,6 @@ test_expect_success 'explicit --refmap option overrides remote.*.fetch' '
'
test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -870,7 +813,6 @@ test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' '
test_expect_success 'configured fetch updates tracking' '
- cd "$D" &&
git branch -f side &&
(
cd three &&
@@ -884,7 +826,6 @@ test_expect_success 'configured fetch updates tracking' '
'
test_expect_success 'non-matching refspecs do not confuse tracking update' '
- cd "$D" &&
git update-ref refs/odd/location HEAD &&
(
cd three &&
@@ -901,14 +842,12 @@ test_expect_success 'non-matching refspecs do not confuse tracking update' '
test_expect_success 'pushing nonexistent branch by mistake should not segv' '
- cd "$D" &&
test_must_fail git push seven no:no
'
test_expect_success 'auto tag following fetches minimum' '
- cd "$D" &&
git clone .git follow &&
git checkout HEAD^0 &&
(
@@ -1307,7 +1246,7 @@ test_expect_success 'fetch --prune prints the remotes url' '
cd only-prunes &&
git fetch --prune origin 2>&1 | head -n1 >../actual
) &&
- echo "From ${D}/." >expect &&
+ echo "From $(pwd)/." >expect &&
test_cmp expect actual
'
@@ -1357,14 +1296,14 @@ test_expect_success 'fetching with auto-gc does not lock up' '
echo "$*" &&
false
EOF
- git clone "file://$D" auto-gc &&
+ git clone "file://$PWD" auto-gc &&
test_commit test2 &&
(
cd auto-gc &&
git config fetch.unpackLimit 1 &&
git config gc.autoPackLimit 1 &&
git config gc.autoDetach false &&
- GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 &&
+ GIT_ASK_YESNO="$TRASH_DIRECTORY/askyesno" git fetch --verbose >fetch.out 2>&1 &&
test_grep "Auto packing the repository" fetch.out &&
! grep "Should I try again" fetch.out
)
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 256ccaefb7..2c70cc561a 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -409,6 +409,36 @@ test_expect_success 'describe tag object' '
test_grep "fatal: test-blob-1 is neither a commit nor blob" actual
'
+test_expect_success 'describe an unreachable blob' '
+ blob=$(echo not-found-anywhere | git hash-object -w --stdin) &&
+ test_must_fail git describe $blob 2>actual &&
+ test_grep "blob .$blob. not reachable from HEAD" actual
+'
+
+test_expect_success 'describe blob on an unborn branch' '
+ oldbranch=$(git symbolic-ref HEAD) &&
+ test_when_finished "git symbolic-ref HEAD $oldbranch" &&
+ git symbolic-ref HEAD refs/heads/does-not-exist &&
+ test_must_fail git describe test-blob 2>actual &&
+ test_grep "cannot search .* on an unborn branch" actual
+'
+
+# This test creates a repository state that we generally try to disallow: HEAD
+# is pointing to an object that is not a commit. The ref update code forbids
+# non-commit writes directly to HEAD or to any branch in refs/heads/. But we
+# can use the loophole of pointing HEAD to another non-branch ref (something we
+# should forbid, but don't for historical reasons).
+#
+# Do not take this test as an endorsement of the loophole! If we ever tighten
+# it, it is reasonable to just drop this test entirely.
+test_expect_success 'describe blob on a non-commit HEAD' '
+ oldbranch=$(git symbolic-ref HEAD) &&
+ test_when_finished "git symbolic-ref HEAD $oldbranch" &&
+ git symbolic-ref HEAD refs/tags/test-blob &&
+ test_must_fail git describe test-blob 2>actual &&
+ test_grep "blob .* not reachable from HEAD" actual
+'
+
test_expect_success ULIMIT_STACK_SIZE 'name-rev works in a deep repo' '
i=1 &&
while test $i -lt 8000
diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c
index e4ea57138e..a8b91812e8 100644
--- a/t/unit-tests/u-reftable-stack.c
+++ b/t/unit-tests/u-reftable-stack.c
@@ -128,7 +128,7 @@ static void write_n_ref_tables(struct reftable_stack *st,
cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1);
cl_assert_equal_i(reftable_stack_add(st,
- &write_test_ref, &ref), 0);
+ &write_test_ref, &ref, 0), 0);
}
st->opts.disable_auto_compact = disable_auto_compact;
@@ -171,7 +171,7 @@ void test_reftable_stack__add_one(void)
err = reftable_new_stack(&st, dir, &opts);
cl_assert(!err);
- err = reftable_stack_add(st, write_test_ref, &ref);
+ err = reftable_stack_add(st, write_test_ref, &ref, 0);
cl_assert(!err);
err = reftable_stack_read_ref(st, ref.refname, &dest);
@@ -235,12 +235,12 @@ void test_reftable_stack__uptodate(void)
cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0);
cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st1, write_test_ref,
- &ref1), 0);
+ &ref1, 0), 0);
cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
- &ref2), REFTABLE_OUTDATED_ERROR);
+ &ref2, 0), REFTABLE_OUTDATED_ERROR);
cl_assert_equal_i(reftable_stack_reload(st2), 0);
cl_assert_equal_i(reftable_stack_add(st2, write_test_ref,
- &ref2), 0);
+ &ref2, 0), 0);
reftable_stack_destroy(st1);
reftable_stack_destroy(st2);
clear_dir(dir);
@@ -406,7 +406,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref), 0);
+ &ref, 0), 0);
cl_assert_equal_i(st->merged->tables_len, 1);
cl_assert_equal_i(st->stats.attempts, 0);
cl_assert_equal_i(st->stats.failures, 0);
@@ -424,7 +424,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void)
write_file_buf(table_path.buf, "", 0);
ref.update_index = 2;
- err = reftable_stack_add(st, write_test_ref, &ref);
+ err = reftable_stack_add(st, write_test_ref, &ref, 0);
cl_assert(!err);
cl_assert_equal_i(st->merged->tables_len, 2);
cl_assert_equal_i(st->stats.attempts, 1);
@@ -460,9 +460,9 @@ void test_reftable_stack__update_index_check(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref1), 0);
+ &ref1, 0), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref2), REFTABLE_API_ERROR);
+ &ref2, 0), REFTABLE_API_ERROR);
reftable_stack_destroy(st);
clear_dir(dir);
}
@@ -477,7 +477,7 @@ void test_reftable_stack__lock_failure(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--)
cl_assert_equal_i(reftable_stack_add(st, write_error,
- &i), i);
+ &i, 0), i);
reftable_stack_destroy(st);
clear_dir(dir);
@@ -521,7 +521,7 @@ void test_reftable_stack__add(void)
for (i = 0; i < N; i++)
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &refs[i]), 0);
+ &refs[i], 0), 0);
for (i = 0; i < N; i++) {
struct write_log_arg arg = {
@@ -529,7 +529,7 @@ void test_reftable_stack__add(void)
.update_index = reftable_stack_next_update_index(st),
};
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
}
cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);
@@ -612,8 +612,8 @@ void test_reftable_stack__iterator(void)
}
for (i = 0; i < N; i++)
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_ref, &refs[i]), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+ &refs[i], 0), 0);
for (i = 0; i < N; i++) {
struct write_log_arg arg = {
@@ -621,8 +621,8 @@ void test_reftable_stack__iterator(void)
.update_index = reftable_stack_next_update_index(st),
};
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_log, &arg), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_log,
+ &arg, 0), 0);
}
reftable_stack_init_ref_iterator(st, &it);
@@ -697,11 +697,11 @@ void test_reftable_stack__log_normalize(void)
input.value.update.message = (char *) "one\ntwo";
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), REFTABLE_API_ERROR);
+ &arg, 0), REFTABLE_API_ERROR);
input.value.update.message = (char *) "one";
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0);
cl_assert_equal_s(dest.value.update.message, "one\n");
@@ -709,7 +709,7 @@ void test_reftable_stack__log_normalize(void)
input.value.update.message = (char *) "two\n";
arg.update_index = 2;
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
cl_assert_equal_i(reftable_stack_read_log(st, input.refname,
&dest), 0);
cl_assert_equal_s(dest.value.update.message, "two\n");
@@ -759,15 +759,16 @@ void test_reftable_stack__tombstone(void)
}
}
for (i = 0; i < N; i++)
- cl_assert_equal_i(reftable_stack_add(st, write_test_ref, &refs[i]), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+ &refs[i], 0), 0);
for (i = 0; i < N; i++) {
struct write_log_arg arg = {
.log = &logs[i],
.update_index = reftable_stack_next_update_index(st),
};
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_log, &arg), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_log,
+ &arg, 0), 0);
}
cl_assert_equal_i(reftable_stack_read_ref(st, "branch",
@@ -815,7 +816,7 @@ void test_reftable_stack__hash_id(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
- &ref), 0);
+ &ref, 0), 0);
/* can't read it with the wrong hash ID. */
cl_assert_equal_i(reftable_new_stack(&st32, dir,
@@ -884,7 +885,7 @@ void test_reftable_stack__reflog_expire(void)
.update_index = reftable_stack_next_update_index(st),
};
cl_assert_equal_i(reftable_stack_add(st, write_test_log,
- &arg), 0);
+ &arg, 0), 0);
}
cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0);
@@ -924,7 +925,7 @@ void test_reftable_stack__empty_add(void)
cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0);
cl_assert_equal_i(reftable_stack_add(st, write_nothing,
- NULL), 0);
+ NULL, 0), 0);
cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0);
clear_dir(dir);
reftable_stack_destroy(st);
@@ -963,7 +964,7 @@ void test_reftable_stack__auto_compaction(void)
};
snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
- err = reftable_stack_add(st, write_test_ref, &ref);
+ err = reftable_stack_add(st, write_test_ref, &ref, 0);
cl_assert(!err);
err = reftable_stack_auto_compact(st);
@@ -999,7 +1000,7 @@ void test_reftable_stack__auto_compaction_factor(void)
};
xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i);
- err = reftable_stack_add(st, &write_test_ref, &ref);
+ err = reftable_stack_add(st, &write_test_ref, &ref, 0);
cl_assert(!err);
cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5));
@@ -1078,8 +1079,8 @@ void test_reftable_stack__add_performs_auto_compaction(void)
snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i);
ref.refname = buf;
- cl_assert_equal_i(reftable_stack_add(st,
- write_test_ref, &ref), 0);
+ cl_assert_equal_i(reftable_stack_add(st, write_test_ref,
+ &ref, 0), 0);
/*
* The stack length should grow continuously for all runs where