From patchwork Mon Sep 30 22:19:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bence Ferdinandy X-Patchwork-Id: 13817159 Received: from aib29agh123.zrh1.oracleemaildelivery.com (aib29agh123.zrh1.oracleemaildelivery.com [192.29.178.123]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5F2A218859F for ; Mon, 30 Sep 2024 22:22:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.29.178.123 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727734932; cv=none; b=PyZloyMWWv3/DniB5MLzwUZptrfrAtqrhtrRXKCNvkk5xC4PIuh+QldNB2oi70ZlDY7ELTKLd0HLjsRAtYAE/uLBB8DC4uyPdoIZkHOFjZ/uwl81VB/NkaUvw6dXg2vveErzm32g1SKIBiZYDt0MxGEQFGI5sXsUuPmsjLnvhoA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727734932; c=relaxed/simple; bh=CNkB9ycQ2wCb2lWTRDC5JvnfIKbyfwbQYYDl1HIxGVo=; h=From:To:Cc:Subject:Date:Message-id:In-reply-to:References: MIME-version; b=N7/n4MvpOVVDaUgHqcEsYB1blEkQKwy7sa2TElk+p/1uaBLKkI5rezoRDZrlUjZxJe1De2hU4Ku8y02J+E/QfgkYazbYKwS8YwbR4TD5MU1gqn6Dn+s1H2RJUOJTFTbaNDXvfWPGpXxtF6fSyho0i99+K/CSUE2o6Hc8empvX40= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b=WeEzeL7u; arc=none smtp.client-ip=192.29.178.123 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b="WeEzeL7u" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=prod-zrh-20200406; d=zrh1.rp.oracleemaildelivery.com; h=Date:To:From:Subject:Message-Id:MIME-Version:Sender:List-Unsubscribe:List-Unsubscribe-Post; bh=07gNpbR203+J9+4bMSBX1XsHsaLmTpppzI+SBVJKGlE=; b=WeEzeL7upfsidDM5/WtRqIK2M4XCnDcuS9BVuO440BjW0u3gp5lg6sHs+Ji0lcNFVNk3ypob8UNX Z6tplibtV0oL8bMuIM4ngJgiEX+gdqoe8MEgEmnA8l+lfPllQiDRONvgFZ4KadwI0BGSwf6HidRx w3m2OPxQwei96RweSf0racmw4Co5KnRyJnt04Mxsvkxn/T1Yd2r2y/QB5HeaIAmgPdwscRQGNUoF caLf5UQVfsz0pH8trUtb2ODeXUGEG/+AsaAHNs6iR3eFmDkdDr0uLhTwK/p1BlPMdxa/ua30RjM9 9YSyG1Nby3ib9NCPCqVwxfzvzIovN501SEDURw== Received: by omta-ad1-fd1-402-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com (Oracle Communications Messaging Server 8.1.0.1.20240911 64bit (built Sep 11 2024)) with ESMTPS id <0SKN00BGWCSUSK80@omta-ad1-fd1-402-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com> for git@vger.kernel.org; Mon, 30 Sep 2024 22:22:06 +0000 (GMT) List-Unsubscribe-Post: List-Unsubscribe=One-Click From: Bence Ferdinandy To: git@vger.kernel.org Cc: phillip.wood@dunelm.org.uk, Junio C Hamano , Taylor Blau , =?utf-8?q?Ren=C3=A9_Scharfe?= , Johannes Schindelin , Bence Ferdinandy Subject: [PATCH v4 1/5] refs_update_symref: atomically record overwritten ref Date: Tue, 1 Oct 2024 00:19:51 +0200 Message-id: <20240930222025.2349008-2-bence@ferdinandy.com> In-reply-to: <20240930222025.2349008-1-bence@ferdinandy.com> References: <20240930222025.2349008-1-bence@ferdinandy.com> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-version: 1.0 Content-transfer-encoding: 8bit Reporting-Meta: AAHyeI1GC1WEq4rT6PdihqO8P/mfN0dkRFH643wIUILRJE9M1SidZS4CDd3XE2nQ kt2nhmTIBMHS8zWb9GMcjObAD0bgyHTbzQ4i+jIyYBWd+TDJ022liVpoVuipCL3n Z50NxhFCD6TgD7oR18I425iJcZwN1Oi8dD6xN4Visk1riARNPlld3yFenFxUvJt/ pj/1efGl18byo4Jg5QwG9HW3QNI9wCLyvEXYECNnYMCjQgZqp5NUnEWuuVmTVLKF HogVvm5oyzopoyUHw0+KKaaufpkouyiPvrvZVxYmnf6sxVtr/0dAFDaHOW+MkCXj 1pT+2hskSTD8ohW2xR6/Zb44UskIebS43mXpYUJ4FzVOubG5ztXcoQ0gf/SehiZl wDEHD5DoQzOtxNK8Hx7I2krjw+BXiaxEMUpNkqd8o5Bj6DrI1ij6Zu0VYFPts4RX gGOcMZe1ZijHRja2km1aO3uBIBVNc96JVu+N3MXUjcjMDhXqso/tuRBJ When updating a symref it's currently not possible to know for sure what was the previous value that was overwritten. Make use of ref_transaction's atomicity and record the previous value there. Add a new variable to refs_update_symref's signature to be able to pass this information back up to callers. Signed-off-by: Bence Ferdinandy --- Notes: v4: new patch builtin/branch.c | 2 +- builtin/checkout.c | 4 ++-- builtin/clone.c | 6 +++--- builtin/notes.c | 2 +- builtin/remote.c | 6 +++--- builtin/symbolic-ref.c | 2 +- builtin/worktree.c | 2 +- refs.c | 8 ++++++-- refs.h | 3 ++- refs/files-backend.c | 1 + refs/refs-internal.h | 8 ++++++++ reset.c | 2 +- sequencer.c | 2 +- setup.c | 2 +- t/helper/test-ref-store.c | 2 +- 15 files changed, 33 insertions(+), 19 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index fd1611ebf5..6c87690b58 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees, continue; refs = get_worktree_ref_store(worktrees[i]); - if (refs_update_symref(refs, "HEAD", newref, logmsg)) + if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL)) ret = error(_("HEAD of working tree %s is not updated"), worktrees[i]->path); } diff --git a/builtin/checkout.c b/builtin/checkout.c index 9c30000d3a..356ee9bcde 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1015,7 +1015,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, describe_detached_head(_("HEAD is now at"), new_branch_info->commit); } } else if (new_branch_info->path) { /* Switch branches. */ - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf, NULL) < 0) die(_("unable to update HEAD")); if (!opts->quiet) { if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) { @@ -1479,7 +1479,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts) die(_("You are on a branch yet to be born")); strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch); status = refs_update_symref(get_main_ref_store(the_repository), - "HEAD", branch_ref.buf, "checkout -b"); + "HEAD", branch_ref.buf, "checkout -b", NULL); strbuf_release(&branch_ref); if (!opts->quiet) fprintf(stderr, _("Switched to a new branch '%s'\n"), diff --git a/builtin/clone.c b/builtin/clone.c index e77339c847..ead2af20ea 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs, strbuf_addstr(&head_ref, "HEAD"); if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf, remote_head_points_at->peer_ref->name, - msg) < 0) + msg, NULL) < 0) die(_("unable to update %s"), head_ref.buf); strbuf_release(&head_ref); } @@ -673,7 +673,7 @@ static void update_head(const struct ref *our, const struct ref *remote, const char *head; if (our && skip_prefix(our->name, "refs/heads/", &head)) { /* Local default branch link */ - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL, NULL) < 0) die(_("unable to update HEAD")); if (!option_bare) { refs_update_ref(get_main_ref_store(the_repository), @@ -702,7 +702,7 @@ static void update_head(const struct ref *our, const struct ref *remote, * Unborn head from remote; same as "our" case above except * that we have no ref to update. */ - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL, NULL) < 0) die(_("unable to update HEAD")); if (!option_bare) install_branch_config(0, head, remote_name, unborn); diff --git a/builtin/notes.c b/builtin/notes.c index 8c26e45526..ba646f06ff 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -980,7 +980,7 @@ static int merge(int argc, const char **argv, const char *prefix) die(_("a notes merge into %s is already in-progress at %s"), notes_ref, wt->path); free_worktrees(worktrees); - if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL)) + if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL, NULL)) die(_("failed to store link to current notes ref (%s)"), notes_ref); fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s " diff --git a/builtin/remote.c b/builtin/remote.c index 76670ddd8b..d8ff440027 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -244,7 +244,7 @@ static int add(int argc, const char **argv, const char *prefix) strbuf_reset(&buf2); strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master); - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add")) + if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add", NULL)) result = error(_("Could not setup master '%s'"), master); } @@ -864,7 +864,7 @@ static int mv(int argc, const char **argv, const char *prefix) strbuf_reset(&buf3); strbuf_addf(&buf3, "remote: renamed %s to %s", item->string, buf.buf); - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf)) + if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL)) die(_("creating '%s' failed"), buf.buf); display_progress(progress, ++refs_renamed_nr); } @@ -1444,7 +1444,7 @@ static int set_head(int argc, const char **argv, const char *prefix) /* make sure it's valid */ if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf)) result |= error(_("Not a valid ref: %s"), buf2.buf); - else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head")) + else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head", NULL)) result |= error(_("Could not setup %s"), buf.buf); else if (opt_a) printf("%s/HEAD set to %s\n", argv[0], head_name); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 299d23d76a..7728fbc3c1 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc, if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0) die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]); ret = !!refs_update_symref(get_main_ref_store(the_repository), - argv[0], argv[1], msg); + argv[0], argv[1], msg, NULL); break; default: usage_with_options(git_symbolic_ref_usage, options); diff --git a/builtin/worktree.c b/builtin/worktree.c index fc31d072a6..a7ab4193c1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname, ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); else - ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL); + ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL); if (ret) goto done; diff --git a/refs.c b/refs.c index 5f729ed412..301db0dcdc 100644 --- a/refs.c +++ b/refs.c @@ -2114,7 +2114,8 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct } int refs_update_symref(struct ref_store *refs, const char *ref, - const char *target, const char *logmsg) + const char *target, const char *logmsg, + struct strbuf *before_target) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; @@ -2130,6 +2131,10 @@ int refs_update_symref(struct ref_store *refs, const char *ref, } strbuf_release(&err); + + if (before_target && transaction->updates[0]->before_target) + strbuf_addstr(before_target, transaction->updates[0]->before_target); + if (transaction) ref_transaction_free(transaction); @@ -2948,4 +2953,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update) return (update->flags & REF_HAVE_OLD) && (!is_null_oid(&update->old_oid) || update->old_target); } - diff --git a/refs.h b/refs.h index 108dfc93b3..f38616db84 100644 --- a/refs.h +++ b/refs.h @@ -571,7 +571,8 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref, const char *newref, const char *logmsg); int refs_update_symref(struct ref_store *refs, const char *refname, - const char *target, const char *logmsg); + const char *target, const char *logmsg, + struct strbuf *before_target); enum action_on_err { UPDATE_REFS_MSG_ON_ERR, diff --git a/refs/files-backend.c b/refs/files-backend.c index 0824c0b8a9..8415f2d020 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2577,6 +2577,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, } update->backend_data = lock; + update->before_target = xstrdup_or_null(referent.buf); if (update->type & REF_ISSYMREF) { if (update->flags & REF_NO_DEREF) { diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 2313c830d8..7df3e6271e 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -104,6 +104,14 @@ struct ref_update { */ const char *old_target; + /* + * The previous target before applying new_target will be + * written here, to be used by callers when they do not want to + * check old_target during the transaction, but do want to know + * what it was. + */ + const char *before_target; + /* * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG, * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags. diff --git a/reset.c b/reset.c index b22b1be792..cc36a9ed56 100644 --- a/reset.c +++ b/reset.c @@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts, if (!ret) ret = refs_update_symref(get_main_ref_store(the_repository), "HEAD", switch_to_branch, - reflog_head); + reflog_head, NULL); } if (!ret && run_hook) run_hooks_l(the_repository, "post-checkout", diff --git a/sequencer.c b/sequencer.c index 8d01cd50ac..23b162924c 100644 --- a/sequencer.c +++ b/sequencer.c @@ -5107,7 +5107,7 @@ static int pick_commits(struct repository *r, } msg = reflog_message(opts, "finish", "returning to %s", head_ref.buf); - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg)) { + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg, NULL)) { res = error(_("could not update HEAD to %s"), head_ref.buf); goto cleanup_head_ref; diff --git a/setup.c b/setup.c index 94e79b2e48..d95f051465 100644 --- a/setup.c +++ b/setup.c @@ -2275,7 +2275,7 @@ void create_reference_database(enum ref_storage_format ref_storage_format, die(_("invalid initial branch name: '%s'"), initial_branch); - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL, NULL) < 0) exit(1); free(ref); } diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 65346dee55..a911302bea 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv) const char *target = notnull(*argv++, "target"); const char *logmsg = *argv++; - return refs_update_symref(refs, refname, target, logmsg); + return refs_update_symref(refs, refname, target, logmsg, NULL); } static struct flag_definition transaction_flags[] = { From patchwork Mon Sep 30 22:19:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bence Ferdinandy X-Patchwork-Id: 13817160 Received: from aib29agh126.zrh1.oracleemaildelivery.com (aib29agh126.zrh1.oracleemaildelivery.com [192.29.178.126]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5EE81185B74 for ; Mon, 30 Sep 2024 22:23:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.29.178.126 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727734999; cv=none; b=Bs6j33lonCPzgA7yCpxErlUBTMo4sDx6aF6lS0xNivprDZ2MNDQ4iJlgshKN7FC7Y9HNriT+SFupX9xEOAmrT2G9ECEyUBNs9Farb+Fhu/mIIOZktlWPB2V5N7OvKDavCHwC/YSYOP0IFZPNvCnAn+G2OjeL/s8liMDz97ugucI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727734999; c=relaxed/simple; bh=fAFVBuv10LA02QRc8/E7tmw7rHSiaADhYIKP9tvIgdM=; h=From:To:Cc:Subject:Date:Message-id:In-reply-to:References: MIME-version; b=q7sZQnh11aam5YMh2TQseiLTsC2wFuIVoDAi2OjxpteJ7hbLjOL6bGn8lRI+uT9iRtbYSZIR+Ut6MNbbmzw4yQCmvsNxmYNhD06BiGif72/ZDM/mH3Js3Cyfv6EOkm9VuB35ZqBcVAfhSI0x5aCl1ACDPsIpc5U8kAhb/MqicPo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b=A19hmGZS; arc=none smtp.client-ip=192.29.178.126 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b="A19hmGZS" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=prod-zrh-20200406; d=zrh1.rp.oracleemaildelivery.com; h=Date:To:From:Subject:Message-Id:MIME-Version:Sender:List-Unsubscribe:List-Unsubscribe-Post; bh=nmcIT2k0Fg8lH2I5P85N2ZcnNRMFEltQEQg7YHQ2kzY=; b=A19hmGZSvBxyK7rKp5lyOkJxz7Px/ZzRFvSDl+80SeYAgIxzEaqh/JjaDiLgPMqXUeCDOFl1lfj1 sbcKKeVAbRRDL4GtzWG+BU8keS8K9Z3Or1Woep9fS0FrTChgScD2m3mJsMD8nDVXnF1QoF2EFeCA HlVMSGHnJmGIWhk6RZRbcNhloKt1LmCCxnkI3d7dwRFO+U29jQ2U+8CkGSVwohamNV20pcMd2Y1z wYlKsycMGmvj7DA/whzs6e1yw8uqjlprF+bFifGPwVjYrW87cXc654/eFmOjQdFMqBDf4/VLV09O SmMcZD/T8Nhj94+kh8g1yv2jsiHdV4GdOphYUg== Received: by omta-ad1-fd3-401-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com (Oracle Communications Messaging Server 8.1.0.1.20240911 64bit (built Sep 11 2024)) with ESMTPS id <0SKN00N09CURQF60@omta-ad1-fd3-401-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com> for git@vger.kernel.org; Mon, 30 Sep 2024 22:23:15 +0000 (GMT) List-Unsubscribe-Post: List-Unsubscribe=One-Click From: Bence Ferdinandy To: git@vger.kernel.org Cc: phillip.wood@dunelm.org.uk, Junio C Hamano , Taylor Blau , =?utf-8?q?Ren=C3=A9_Scharfe?= , Johannes Schindelin , Bence Ferdinandy Subject: [PATCH v4 2/5] set-head: better output for --auto Date: Tue, 1 Oct 2024 00:19:52 +0200 Message-id: <20240930222025.2349008-3-bence@ferdinandy.com> In-reply-to: <20240930222025.2349008-1-bence@ferdinandy.com> References: <20240930222025.2349008-1-bence@ferdinandy.com> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-version: 1.0 Content-transfer-encoding: 8bit Reporting-Meta: AAHyeI1GC1WEq4rT6PdihqO8P/mfN0dkRFH643wIUILRJE9M1SidZS4CDd3XE2nQ kt2nhmTIBMHS8zWb9GN7jObAD0bgyHTbzQ59S1s5cSJU+D384JLWUDb1mSIc1rDN AfelJMSSDOnfSfuRf9pjKYp7stqk3N8/aWsqvTUAj/SBKH8uWizFAA2+9VLY8thV demoUEjHCD0f0L8SJX2gXyc8IB15TkhYuW0LO+uFm84ZirJPK8/Drqu3Iyv+f2EV oyxtQ14pU12FstYh+BSXEXn/i64ussEH+IOtqFNW6/jIipZHVqwvGEAY86s3pFco TVb4klRei7RfEcsFqZSnV3C23IHYyRhbfaPcZX+QEkoU0zbP+Zq0009Ie6JPcRb4 pp2udLczMR0fiA+e7xZH8oe9E9wxFDSwDqw59eMQzuheHFNlf4w6i4Nk3prR4c5e JxSkT7Zhs2alN+Nml7aSEwnHqvyeX19J6Ww46LALJogbL6rdZ8lZe0Y= Currently, set-head --auto will print a message saying "remote/HEAD set to branch", which implies something was changed. Change the output of --auto, so the output actually reflects what was done: a) set a previously unset HEAD, b) change HEAD because remote changed or c) no updates. Signed-off-by: Bence Ferdinandy --- Notes: v1-v2: was RFC in https://lore.kernel.org/git/20240910203835.2288291-1-bence@ferdinandy.com/ v3: This patch was originally sent along when I thought set-head was going to be invoked by fetch, but the discussion on the RFC concluded that it should be not. This opened the possibility to make it more explicit. Note: although I feel both things the patch does are really just cosmetic, an argument could be made for breaking it into two, one for the no-op part and one for the --auto print update. Was sent in: https://lore.kernel.org/git/20240915221055.904107-1-bence@ferdinandy.com/ v4: - changes are now handled atomically via the ref update transaction - outputs have changed along the lines of Junio's suggestion - minor refactor to set_head for improved legibility builtin/remote.c | 36 ++++++++++++++++++++++++++++++------ t/t5505-remote.sh | 13 ++++++++++++- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/builtin/remote.c b/builtin/remote.c index d8ff440027..c61a7800ff 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1399,12 +1399,34 @@ static int show(int argc, const char **argv, const char *prefix) return result; } +static void report_auto(const char *remote, const char *head_name, + struct strbuf *buf_prev) { + struct strbuf buf_prefix = STRBUF_INIT; + const char *prev_head; + + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote); + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head); + + if (prev_head && !strcmp(prev_head, head_name)) + printf("'%s/HEAD' is unchanged and points to '%s'\n", + remote, head_name); + else if (prev_head) + printf("'%s/HEAD' has changed from '%s' and now points to '%s'\n", + remote, prev_head, head_name); + else + printf("'%s/HEAD' is now created and points to '%s'\n", + remote, head_name); +} + static int set_head(int argc, const char **argv, const char *prefix) { int i, opt_a = 0, opt_d = 0, result = 0; - struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, + buf_prev = STRBUF_INIT; char *head_name = NULL; + struct ref_store *refs = get_main_ref_store(the_repository); + struct option options[] = { OPT_BOOL('a', "auto", &opt_a, N_("set refs/remotes//HEAD according to remote")), @@ -1434,7 +1456,7 @@ static int set_head(int argc, const char **argv, const char *prefix) head_name = xstrdup(states.heads.items[0].string); free_remote_ref_states(&states); } else if (opt_d && !opt_a && argc == 1) { - if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF)) + if (refs_delete_ref(refs, NULL, buf.buf, NULL, REF_NO_DEREF)) result |= error(_("Could not delete %s"), buf.buf); } else usage_with_options(builtin_remote_sethead_usage, options); @@ -1442,17 +1464,19 @@ static int set_head(int argc, const char **argv, const char *prefix) if (head_name) { strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name); /* make sure it's valid */ - if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf)) + if (!refs_ref_exists(refs, buf2.buf)) result |= error(_("Not a valid ref: %s"), buf2.buf); - else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head", NULL)) + else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", &buf_prev)) result |= error(_("Could not setup %s"), buf.buf); - else if (opt_a) - printf("%s/HEAD set to %s\n", argv[0], head_name); + else if (opt_a) { + report_auto(argv[0], head_name, &buf_prev); + } free(head_name); } strbuf_release(&buf); strbuf_release(&buf2); + strbuf_release(&buf_prev); return result; } diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 532035933f..711d6e5598 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -434,7 +434,7 @@ test_expect_success 'set-head --auto has no problem w/multiple HEADs' ' cd test && git fetch two "refs/heads/*:refs/remotes/two/*" && git remote set-head --auto two >output 2>&1 && - echo "two/HEAD set to main" >expect && + echo "'\''two/HEAD'\'' is now created and points to '\''main'\''" >expect && test_cmp expect output ) ' @@ -453,6 +453,17 @@ test_expect_success 'set-head explicit' ' ) ' + +test_expect_success 'set-head --auto reports change' ' + ( + cd test && + git remote set-head origin side2 && + git remote set-head --auto origin >output 2>&1 && + echo "'\''origin/HEAD'\'' has changed from '\''side2'\'' and now points to '\''main'\''" >expect && + test_cmp expect output + ) +' + cat >test/expect < X-Patchwork-Id: 13817165 Received: from aib29agh124.zrh1.oracleemaildelivery.com (aib29agh124.zrh1.oracleemaildelivery.com [192.29.178.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8C38B18873D for ; Mon, 30 Sep 2024 22:28:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.29.178.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727735305; cv=none; b=f46FM4nzeoqiUPno43oH/qhb2S22k9AXHeXvvW+ZzAyqKALC10my7Hn95FXvg5wQQUm2j9+j0EgUpvnFguJeJSGyKQWIXHNIEUx5//BAp5tOAB3aP6fppjOpXp7eRAD7VWzX8eIk+YhwFphg2AxoL0xLB1Q6hOJm4ogU5NWZCN4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727735305; c=relaxed/simple; bh=bO7Ym1HJQChGt9kASgp5o774YCXD1/X0iIVzEGKYGmo=; h=From:To:Cc:Subject:Date:Message-id:In-reply-to:References: MIME-version; b=puaEHb/MjXh8fMJL9Kg02Z+x66RWpQs9CBuGWO5yewNzYjGo4oC3wfjGE7fVDGDSZ0WRJHlDSbJitZ2kcY0+InQvDZ/4urnS+CO+UdLOiAUupsWLBXsC3qEDq0Mla45hioiBbVN4gMhfkXZy1H5nyg6xQC5+UCMIyTUQllslCtI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b=IEdR/oWD; arc=none smtp.client-ip=192.29.178.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b="IEdR/oWD" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=prod-zrh-20200406; d=zrh1.rp.oracleemaildelivery.com; h=Date:To:From:Subject:Message-Id:MIME-Version:Sender:List-Unsubscribe:List-Unsubscribe-Post; bh=A1MJxE9+f6ZF3lBQXzDDsPuU8z4LDlPG/IOmXg+5QbM=; b=IEdR/oWD6/LgGTMiLLlpsUnFo9aeiReBF3K20j8ruk3VZ+SqfmeK9EM37QEyFXNzaUC7dVmjYmRD 4E7zkLY+7F/JxdREy++CVYEdNHCQYl2gXIMiZGct8Y4trPUJponEs0hG6sX2KMP7dp2vn60bTf9p dnYCUElHgM/5cyT7OiljBSrjVP9l6EPWGhFNkhFOkJOtgodNjDs7p3xq2SnLR8LZr8Eqw95d+tvx /XKUgNBUqtBNbNraBlTqpIEl1tdwnubK1QOMiYTW4kl89L+y5wHCFiw5lZ8ADVXFUPKSEw9mJyMW NRR85YnMkk67TAkcsKZptd0zC480SVVgmYqWsQ== Received: by omta-ad1-fd2-401-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com (Oracle Communications Messaging Server 8.1.0.1.20240911 64bit (built Sep 11 2024)) with ESMTPS id <0SKN003BRCURCN50@omta-ad1-fd2-401-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com> for git@vger.kernel.org; Mon, 30 Sep 2024 22:23:15 +0000 (GMT) List-Unsubscribe-Post: List-Unsubscribe=One-Click From: Bence Ferdinandy To: git@vger.kernel.org Cc: phillip.wood@dunelm.org.uk, Junio C Hamano , Taylor Blau , =?utf-8?q?Ren=C3=A9_Scharfe?= , Johannes Schindelin , Bence Ferdinandy Subject: [PATCH v4 3/5] transaction: add TRANSACTION_CREATE_EXISTS error Date: Tue, 1 Oct 2024 00:19:53 +0200 Message-id: <20240930222025.2349008-4-bence@ferdinandy.com> In-reply-to: <20240930222025.2349008-1-bence@ferdinandy.com> References: <20240930222025.2349008-1-bence@ferdinandy.com> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-version: 1.0 Content-transfer-encoding: 8bit Reporting-Meta: AAHyeI1GC1WEq4rT6PdihqO8P/mfN0dkRFH643wIUILRJE9M1SidZS4CDd3XE2nQ kt2nhmTIBMHS8zWb9GN4jObAD0bgyHTbzQ4aYhml5q35dkSKm6SrYGgxcTmFF2IW x4WkqbbKiYSQg/brXhzGW/MEYAq+R1OkqEV2Cwx9lp1ZCMGAgBfz62qVmP/Wwhn/ ghVprFRx5mYnisWNXtorXVX1zED1CfjU0Np867X0+YlwsM4yXTGPB5Z0XfnMY3wc fx+GThzGH9L0k+exJLNQZZOkYYVkbzbbMMn3LKkx9GP+hPNbTfoR7mJdpCCi718X 26MItoSx54B+wdce/7ExJrfGXMZfaTj9mlmH8f+RLwqvUJjJXAO7EMW3+2hrOIqP 7NwXZJ/6iHPfNpyDMMid4+uWrAC2XY4nwSTBqK80TPSevra0SFpvctUWl+vkyPJH O3hvCr56fMAAzGnqYw4mNqRFjb+Bzn9Tg47ZebQEehUMp0UA7zDUINc= Currently there is only one special error for transaction, for when there is a naming conflict, all other errors are dumped under a generic error. Add a new special error case for when the caller requests the reference to be updated only when it does not yet exist and the reference actually does exist. Signed-off-by: Bence Ferdinandy --- Notes: v4: new patch refs.h | 4 +++- refs/files-backend.c | 28 ++++++++++++++++++++-------- refs/reftable-backend.c | 6 ++++-- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/refs.h b/refs.h index f38616db84..166affbc89 100644 --- a/refs.h +++ b/refs.h @@ -759,8 +759,10 @@ int ref_transaction_verify(struct ref_transaction *transaction, /* Naming conflict (for example, the ref names A and A/B conflict). */ #define TRANSACTION_NAME_CONFLICT -1 +/* When only creation was requested, but the ref already exists. */ +#define TRANSACTION_CREATE_EXISTS -2 /* All other errors. */ -#define TRANSACTION_GENERIC_ERROR -2 +#define TRANSACTION_GENERIC_ERROR -3 /* * Perform the preparatory stages of committing `transaction`. Acquire diff --git a/refs/files-backend.c b/refs/files-backend.c index 8415f2d020..272ad81315 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -2502,14 +2502,18 @@ static int split_symref_update(struct ref_update *update, static int check_old_oid(struct ref_update *update, struct object_id *oid, struct strbuf *err) { + int ret = TRANSACTION_GENERIC_ERROR; + if (!(update->flags & REF_HAVE_OLD) || oideq(oid, &update->old_oid)) return 0; - if (is_null_oid(&update->old_oid)) + if (is_null_oid(&update->old_oid)) { strbuf_addf(err, "cannot lock ref '%s': " "reference already exists", ref_update_original_update_refname(update)); + ret = TRANSACTION_CREATE_EXISTS; + } else if (is_null_oid(oid)) strbuf_addf(err, "cannot lock ref '%s': " "reference is missing but expected %s", @@ -2522,7 +2526,7 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid, oid_to_hex(oid), oid_to_hex(&update->old_oid)); - return -1; + return ret; } /* @@ -2603,9 +2607,13 @@ static int lock_ref_for_update(struct files_ref_store *refs, ret = TRANSACTION_GENERIC_ERROR; goto out; } - } else if (check_old_oid(update, &lock->old_oid, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto out; + } else { + int checkret; + checkret = check_old_oid(update, &lock->old_oid, err); + if (checkret) { + ret = checkret; + goto out; + } } } else { /* @@ -2636,9 +2644,13 @@ static int lock_ref_for_update(struct files_ref_store *refs, update->old_target); ret = TRANSACTION_GENERIC_ERROR; goto out; - } else if (check_old_oid(update, &lock->old_oid, err)) { - ret = TRANSACTION_GENERIC_ERROR; - goto out; + } else { + int checkret; + checkret = check_old_oid(update, &lock->old_oid, err); + if (checkret) { + ret = checkret; + goto out; + } } /* diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index f5f957e6de..f66bbf5d49 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1197,10 +1197,13 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, goto done; } } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) { - if (is_null_oid(&u->old_oid)) + ret = TRANSACTION_NAME_CONFLICT; + if (is_null_oid(&u->old_oid)) { strbuf_addf(err, _("cannot lock ref '%s': " "reference already exists"), ref_update_original_update_refname(u)); + ret = TRANSACTION_CREATE_EXISTS; + } else if (is_null_oid(¤t_oid)) strbuf_addf(err, _("cannot lock ref '%s': " "reference is missing but expected %s"), @@ -1212,7 +1215,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, ref_update_original_update_refname(u), oid_to_hex(¤t_oid), oid_to_hex(&u->old_oid)); - ret = -1; goto done; } From patchwork Mon Sep 30 22:19:54 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bence Ferdinandy X-Patchwork-Id: 13817166 Received: from aib29agh125.zrh1.oracleemaildelivery.com (aib29agh125.zrh1.oracleemaildelivery.com [192.29.178.125]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 97842188CB6 for ; Mon, 30 Sep 2024 22:29:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.29.178.125 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727735356; cv=none; b=u69lIENFRUnVdUWyREqVVcN3w/KGZWKhNjMRlDOYFLQMs6iJB2Dmzn38E33QhBdAAdGaMPA5w5zvuZOMGerwXjyvCYf9fCIqHwPVN917pNZ9uz7ZAfQhYaT5oraiKH/nrpv3XhbKGV7qxbEfb/1kFeLsTZQfvWci9X6qQEoVroA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727735356; c=relaxed/simple; bh=e3R1Yh88GAoA1f6ftytNrQ9B+Zoi776UPXAq5ph/B9s=; h=From:To:Cc:Subject:Date:Message-id:In-reply-to:References: MIME-version; b=OwBYF+P94YO67nKTM6cvxx3JRGLWJakGgx5sIMZl2up3rtB9jmMNSMtj8DfhO3heKVhXkN0P0XQSLm0Rxya4jERig/bWU03hmUJzCx62RyjoK4vobEW2y+po0pjVvNK5P7PG5Tbm9mUdfRrWWcwXnA0sPg4qdHZvMYbKKFZEgAI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b=GIby9Z0+; arc=none smtp.client-ip=192.29.178.125 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b="GIby9Z0+" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=prod-zrh-20200406; d=zrh1.rp.oracleemaildelivery.com; h=Date:To:From:Subject:Message-Id:MIME-Version:Sender:List-Unsubscribe:List-Unsubscribe-Post; bh=9XrG/PadRZOoo0NJ0qyaQ/54WC+SwA8KJ/ZJalR7UJI=; b=GIby9Z0+dTaoJrQVHpVfOU7FMD+9EzFxZ8Go2Z7y/kieq3+WtqHKdkikpFKLenTMNMBWIpenjFDe K10JgT6r1UE4J/M14abGrVwl+KaU6PPNgc+Atj9auE5ECpikIzWUM5W+mEJszJjFymIK4fltUg6d Ml6koguEle7pHS4o4CQ+dQyUKWXweGHXAbEsuQjWzrVWBSj8a1Hlb3/Bn+0hETt+wN0FkPmILjR1 KexVXB6FYT3OhXqqVRkGv+caivZKjM/TsojP5U/li27Zh4O8MY4EM04uBJzMXWaxCy4jugWi4z4D 0l6JyxhmzdTVqgoGTaHzl7xZ6U3qkfq97gTNLA== Received: by omta-ad1-fd2-402-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com (Oracle Communications Messaging Server 8.1.0.1.20240911 64bit (built Sep 11 2024)) with ESMTPS id <0SKN00399CW6RU60@omta-ad1-fd2-402-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com> for git@vger.kernel.org; Mon, 30 Sep 2024 22:24:06 +0000 (GMT) List-Unsubscribe-Post: List-Unsubscribe=One-Click From: Bence Ferdinandy To: git@vger.kernel.org Cc: phillip.wood@dunelm.org.uk, Junio C Hamano , Taylor Blau , =?utf-8?q?Ren=C3=A9_Scharfe?= , Johannes Schindelin , Bence Ferdinandy Subject: [PATCH v4 4/5] refs_update_symref: add create_only option Date: Tue, 1 Oct 2024 00:19:54 +0200 Message-id: <20240930222025.2349008-5-bence@ferdinandy.com> In-reply-to: <20240930222025.2349008-1-bence@ferdinandy.com> References: <20240930222025.2349008-1-bence@ferdinandy.com> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-version: 1.0 Content-transfer-encoding: 8bit Reporting-Meta: AAHZwkTWDLgHwA9ZRNNKp49+rH3LojH12GvMbCKPwfa+i3+1bnoxK0HQ5V6xyCdg AlEKEauLTZVPUx7oZRpdQwB4L2jL1QZcxGIBL+E4lK8B58DLCxLuHrWYVw8Ep57E Uo4pxDZwVGQmVvd1A4mHEIPg+FIKPgXWlHhoYuCqyBSbOO0zg+hIXczOLtDrMqXl 4tVzBiDndDafyPEgHXLl17k0JLh7dTJFTBaE89rx0PS1wTNglZU6bwhF2ZklJL7Q RN6Q2aYall62wBc1o76ooKpRo4LsjqErokQvb97KRdTn0uBZmGHJHndrJMePQ8+b fgYHiFtkbB1A+OYBXuQOyKJYATCYvmBQ6XTTgyQAGtFEVDstZi96DqYxu1Mq5m8V 9DyXRAbdljFUZd6AT4+RzylSOqF9eOyTaB1hy5Re9SLATtdFoLaZ9RSjcZatfTzI +fydmekwKkGu0EvyKVtrUTL/ONKtRRLH8YWifeWu4+9p9R2bUzQK3Do= Allow the caller to specify that it only wants to update the symref if it does not already exist. Silently ignore the error from the transaction API if the symref already exists. Signed-off-by: Bence Ferdinandy --- Notes: v4: new patch builtin/branch.c | 2 +- builtin/checkout.c | 5 +++-- builtin/clone.c | 8 +++++--- builtin/notes.c | 3 ++- builtin/remote.c | 9 ++++++--- builtin/symbolic-ref.c | 2 +- builtin/worktree.c | 2 +- refs.c | 29 +++++++++++++++++++++-------- refs.h | 2 +- reset.c | 2 +- sequencer.c | 3 ++- setup.c | 3 ++- t/helper/test-ref-store.c | 2 +- 13 files changed, 47 insertions(+), 25 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 6c87690b58..3c9bc39800 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -559,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees, continue; refs = get_worktree_ref_store(worktrees[i]); - if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL)) + if (refs_update_symref(refs, "HEAD", newref, logmsg, NULL, false)) ret = error(_("HEAD of working tree %s is not updated"), worktrees[i]->path); } diff --git a/builtin/checkout.c b/builtin/checkout.c index 356ee9bcde..d9514e9b7a 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1015,7 +1015,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, describe_detached_head(_("HEAD is now at"), new_branch_info->commit); } } else if (new_branch_info->path) { /* Switch branches. */ - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, + msg.buf, NULL, false) < 0) die(_("unable to update HEAD")); if (!opts->quiet) { if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) { @@ -1479,7 +1480,7 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts) die(_("You are on a branch yet to be born")); strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch); status = refs_update_symref(get_main_ref_store(the_repository), - "HEAD", branch_ref.buf, "checkout -b", NULL); + "HEAD", branch_ref.buf, "checkout -b", NULL, false); strbuf_release(&branch_ref); if (!opts->quiet) fprintf(stderr, _("Switched to a new branch '%s'\n"), diff --git a/builtin/clone.c b/builtin/clone.c index ead2af20ea..25d0bcf3aa 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -661,7 +661,7 @@ static void update_remote_refs(const struct ref *refs, strbuf_addstr(&head_ref, "HEAD"); if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf, remote_head_points_at->peer_ref->name, - msg, NULL) < 0) + msg, NULL, false) < 0) die(_("unable to update %s"), head_ref.buf); strbuf_release(&head_ref); } @@ -673,7 +673,8 @@ static void update_head(const struct ref *our, const struct ref *remote, const char *head; if (our && skip_prefix(our->name, "refs/heads/", &head)) { /* Local default branch link */ - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, + NULL, NULL, false) < 0) die(_("unable to update HEAD")); if (!option_bare) { refs_update_ref(get_main_ref_store(the_repository), @@ -702,7 +703,8 @@ static void update_head(const struct ref *our, const struct ref *remote, * Unborn head from remote; same as "our" case above except * that we have no ref to update. */ - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, + NULL, NULL, false) < 0) die(_("unable to update HEAD")); if (!option_bare) install_branch_config(0, head, remote_name, unborn); diff --git a/builtin/notes.c b/builtin/notes.c index ba646f06ff..fb49e491c3 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -980,7 +980,8 @@ static int merge(int argc, const char **argv, const char *prefix) die(_("a notes merge into %s is already in-progress at %s"), notes_ref, wt->path); free_worktrees(worktrees); - if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, NULL, NULL)) + if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", notes_ref, + NULL, NULL, false)) die(_("failed to store link to current notes ref (%s)"), notes_ref); fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s " diff --git a/builtin/remote.c b/builtin/remote.c index c61a7800ff..a1cec6276d 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -244,7 +244,8 @@ static int add(int argc, const char **argv, const char *prefix) strbuf_reset(&buf2); strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master); - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add", NULL)) + if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, + "remote add", NULL, false)) result = error(_("Could not setup master '%s'"), master); } @@ -864,7 +865,8 @@ static int mv(int argc, const char **argv, const char *prefix) strbuf_reset(&buf3); strbuf_addf(&buf3, "remote: renamed %s to %s", item->string, buf.buf); - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf, NULL)) + if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, + buf3.buf, NULL, false)) die(_("creating '%s' failed"), buf.buf); display_progress(progress, ++refs_renamed_nr); } @@ -1466,7 +1468,8 @@ static int set_head(int argc, const char **argv, const char *prefix) /* make sure it's valid */ if (!refs_ref_exists(refs, buf2.buf)) result |= error(_("Not a valid ref: %s"), buf2.buf); - else if (refs_update_symref(refs, buf.buf, buf2.buf, "remote set-head", &buf_prev)) + else if (refs_update_symref(refs, buf.buf, buf2.buf, + "remote set-head", &buf_prev, false)) result |= error(_("Could not setup %s"), buf.buf); else if (opt_a) { report_auto(argv[0], head_name, &buf_prev); diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 7728fbc3c1..169f5f5340 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -88,7 +88,7 @@ int cmd_symbolic_ref(int argc, if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0) die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]); ret = !!refs_update_symref(get_main_ref_store(the_repository), - argv[0], argv[1], msg, NULL); + argv[0], argv[1], msg, NULL, false); break; default: usage_with_options(git_symbolic_ref_usage, options); diff --git a/builtin/worktree.c b/builtin/worktree.c index a7ab4193c1..46b515a243 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname, ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR); else - ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL); + ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL, NULL, false); if (ret) goto done; diff --git a/refs.c b/refs.c index 301db0dcdc..e012b89ee9 100644 --- a/refs.c +++ b/refs.c @@ -2115,19 +2115,32 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct int refs_update_symref(struct ref_store *refs, const char *ref, const char *target, const char *logmsg, - struct strbuf *before_target) + struct strbuf *before_target, bool create_only) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - int ret = 0; + int ret = 0, create_ret = 0; transaction = ref_store_transaction_begin(refs, &err); - if (!transaction || - ref_transaction_update(transaction, ref, NULL, NULL, - target, NULL, REF_NO_DEREF, - logmsg, &err) || - ref_transaction_commit(transaction, &err)) { - ret = error("%s", err.buf); + if (create_only) { + if (!transaction || + ref_transaction_create(transaction, ref, NULL, target, + REF_NO_DEREF, logmsg, &err)) { + ret = error("%s", err.buf); + } + else { + create_ret = ref_transaction_commit(transaction, &err); + if (create_ret && create_ret != TRANSACTION_CREATE_EXISTS) + ret = error("%s", err.buf); + } + } + else + if (!transaction || + ref_transaction_update(transaction, ref, NULL, NULL, + target, NULL, REF_NO_DEREF, + logmsg, &err) || + ref_transaction_commit(transaction, &err)) { + ret = error("%s", err.buf); } strbuf_release(&err); diff --git a/refs.h b/refs.h index 166affbc89..b64fd2318b 100644 --- a/refs.h +++ b/refs.h @@ -572,7 +572,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref, int refs_update_symref(struct ref_store *refs, const char *refname, const char *target, const char *logmsg, - struct strbuf *before_target); + struct strbuf *before_target, bool create_only); enum action_on_err { UPDATE_REFS_MSG_ON_ERR, diff --git a/reset.c b/reset.c index cc36a9ed56..674896fb61 100644 --- a/reset.c +++ b/reset.c @@ -76,7 +76,7 @@ static int update_refs(const struct reset_head_opts *opts, if (!ret) ret = refs_update_symref(get_main_ref_store(the_repository), "HEAD", switch_to_branch, - reflog_head, NULL); + reflog_head, NULL, false); } if (!ret && run_hook) run_hooks_l(the_repository, "post-checkout", diff --git a/sequencer.c b/sequencer.c index 23b162924c..1a46ef56ba 100644 --- a/sequencer.c +++ b/sequencer.c @@ -5107,7 +5107,8 @@ static int pick_commits(struct repository *r, } msg = reflog_message(opts, "finish", "returning to %s", head_ref.buf); - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg, NULL)) { + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, + msg, NULL, false)) { res = error(_("could not update HEAD to %s"), head_ref.buf); goto cleanup_head_ref; diff --git a/setup.c b/setup.c index d95f051465..67abbfaf3c 100644 --- a/setup.c +++ b/setup.c @@ -2275,7 +2275,8 @@ void create_reference_database(enum ref_storage_format ref_storage_format, die(_("invalid initial branch name: '%s'"), initial_branch); - if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL, NULL) < 0) + if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, + NULL, NULL, false) < 0) exit(1); free(ref); } diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index a911302bea..b6b06bb2e9 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -120,7 +120,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv) const char *target = notnull(*argv++, "target"); const char *logmsg = *argv++; - return refs_update_symref(refs, refname, target, logmsg, NULL); + return refs_update_symref(refs, refname, target, logmsg, NULL, false); } static struct flag_definition transaction_flags[] = { From patchwork Mon Sep 30 22:19:55 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Bence Ferdinandy X-Patchwork-Id: 13817161 Received: from aib29agh124.zrh1.oracleemaildelivery.com (aib29agh124.zrh1.oracleemaildelivery.com [192.29.178.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9F447185B74 for ; Mon, 30 Sep 2024 22:24:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=192.29.178.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727735047; cv=none; b=dgGM6KtCG42drYEF0GlyA8l+phTHAToBIvda/vpNsOhM/kkZc8TsAbQuMmGCqZrvg3GqxAxudqpaH4mNmrlasr1etuBliSQtuJc3/mkhWAn2XqDn1+AXdWB+a9zwBu+/w0fdd85UVDpVJo9k6PA7HupWJkA6coAvIcqFiVBtA0w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727735047; c=relaxed/simple; bh=wHzc577Rhf0HaNdAF+tZkN2qqaTzVqgo+VadjMDfxnU=; h=From:To:Cc:Subject:Date:Message-id:In-reply-to:References: MIME-version; b=nu5FYyRHPsOUM029JBZc+M+65Ovp/CGpESLjZ3pq/acdf9H+WRT/IlQ0jSGconxMCvWlA1VFrg4Up9hFvVgriWOyjOLxK3ug9oFJBtprzovNmX/b8SdomalmnyQT2Ge3Sqle6JTvkFzt8rM8h4pAz4Nly3ohrz90zmj8vbW8Uic= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b=V+wrGhGT; arc=none smtp.client-ip=192.29.178.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=fail (p=none dis=none) header.from=ferdinandy.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=zrh1.rp.oracleemaildelivery.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=zrh1.rp.oracleemaildelivery.com header.i=@zrh1.rp.oracleemaildelivery.com header.b="V+wrGhGT" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; s=prod-zrh-20200406; d=zrh1.rp.oracleemaildelivery.com; h=Date:To:From:Subject:Message-Id:MIME-Version:Sender:List-Unsubscribe:List-Unsubscribe-Post; bh=DMl/MXN+52ljcRsXWcpgZZ1JRwXdwgm/eWpEyvA7YP0=; b=V+wrGhGTTqLQKZESSEhuBO/77eMJRLDgO4BDqwum0JoCTpgeRsHVbzR1gthMZ4yW2qlm3QXIVVFW yDrNrfzMrYbgUjQDnhzhtd27M2gqlaVeGv0S12qGETXL8roMUYcydh0FHQRPXBWEmhLBOpw+G2FK vB5mulbcsjxtFLxc3PoQKs6Rg84zKX+DK6EAMsY0zVbplJKJOkEF6+3NvCLbyDAu/npubAjXifRr zwvUcI92jXjF1EW6SVBG1DOicVx7fwJ0pKb9jl9eDTppMG6WZVIaZ/bDQ4CkBy/g5FFHkBbDXbuP Pf9fsrC16V1GazYFuHz7eYkNBndGG6sP0oCFfw== Received: by omta-ad1-fd2-401-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com (Oracle Communications Messaging Server 8.1.0.1.20240911 64bit (built Sep 11 2024)) with ESMTPS id <0SKN003EOCW3CN50@omta-ad1-fd2-401-eu-zurich-1.omtaad1.vcndpzrh.oraclevcn.com> for git@vger.kernel.org; Mon, 30 Sep 2024 22:24:03 +0000 (GMT) List-Unsubscribe-Post: List-Unsubscribe=One-Click From: Bence Ferdinandy To: git@vger.kernel.org Cc: phillip.wood@dunelm.org.uk, Junio C Hamano , Taylor Blau , =?utf-8?q?Ren=C3=A9_Scharfe?= , Johannes Schindelin , Bence Ferdinandy Subject: [PATCH v4 5/5] fetch: set remote/HEAD if it does not exist Date: Tue, 1 Oct 2024 00:19:55 +0200 Message-id: <20240930222025.2349008-6-bence@ferdinandy.com> In-reply-to: <20240930222025.2349008-1-bence@ferdinandy.com> References: <20240930222025.2349008-1-bence@ferdinandy.com> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-version: 1.0 Content-transfer-encoding: 8bit Reporting-Meta: AAHyeI1GC1WEq4rT6PdihqO8P/mfN0dkRFH643wIUILRJE9M1SidZS4CDd3XE2nQ kt2nhmTIBMHS8zWb9GOqjObAD0bgyHTbzQ64cJ4He+VWVge9fsMzeX+Y+64vm3y2 WptheT54L/rb7Py5Bv2TqSuD0TMVbhWqf4WQHEHLPsOKgTpsFkbSLql5JmfEwwxQ xf+u9Qfv7rQjSCTDAMhUNfqs3vUhIM5Wm/FwdbLX14EkxOoqt+e2fyXftVgxx2E9 W1lUNkC8So8xo+5MhtleOlCKtkDcMx+y+t1FEpgV1Fj9gm5XhydY236/i3TGVF9E YAjcnq7f7XUgQs6ZRU9kbtJHs0KukEVShbvHOuZFKcwGpDDEENCeSppwJA99GGy4 e2KXnY7iG9DWE11TUzJNjZjNxKR6s5HPXxA7bhvn9hisRykbi1X7EOtWrvAtef35 ReqYXn7BGYUvo1ZHgcum+CUymoXmnw19p1UFWDWhEd2J3+Kz9U44VHE3 If the user has remote/HEAD set already and it looks like it has changed on the server, then print a message, otherwise set it if we can. Silently pass if the user already has the same remote/HEAD set as reported by the server. Signed-off-by: Bence Ferdinandy --- Notes: v3: - does not rely on remote set-head anymore so it only authenticates once - uses the new REF_CREATE_ONLY to atomically check if the ref exists and only write it if it doesn't - in all other cases the maximum it does is print a warning v4: - instead of the discarded REF_CREATE_ONLY, it uses the existing, but updated transaction api to request a silent create only - it now uses the atomic before_target to determine reporting - refactored for legibility builtin/fetch.c | 83 +++++++++++++++++++++++++++++++++++++++ t/t5514-fetch-multiple.sh | 9 +++++ 2 files changed, 92 insertions(+) diff --git a/builtin/fetch.c b/builtin/fetch.c index c900f57721..f10a9d5371 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1577,6 +1577,82 @@ static int backfill_tags(struct display_state *display_state, return retcode; } +static void report_set_head(const char *remote, const char *head_name, + struct strbuf *buf_prev) { + struct strbuf buf_prefix = STRBUF_INIT; + const char *prev_head; + + strbuf_addf(&buf_prefix, "refs/remotes/%s/", remote); + skip_prefix(buf_prev->buf, buf_prefix.buf, &prev_head); + + if (prev_head && !strcmp(prev_head, head_name)) + ; + else if (prev_head) { + printf("'HEAD' at '%s' has changed from '%s' to '%s'\n", + remote, prev_head, head_name); + printf("Run 'git remote set-head %s %s' to follow the change.\n", + remote, head_name); + } +} + +static const char *strip_refshead(const char *name){ + skip_prefix(name, "refs/heads/", &name); + return name; +} + +static int set_head(const struct ref *remote_refs) +{ + int result = 0; + struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, + b_local_head = STRBUF_INIT; + const char *remote = gtransport->remote->name; + char *head_name = NULL; + struct ref *ref, *matches; + struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map; + struct refspec_item refspec = { + .force = 0, + .pattern = 1, + .src = (char *) "refs/heads/*", + .dst = (char *) "refs/heads/*", + }; + struct string_list heads = STRING_LIST_INIT_DUP; + struct ref_store *refs = get_main_ref_store(the_repository); + + get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); + matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), + fetch_map, 1); + for (ref = matches; ref; ref = ref->next) { + string_list_append(&heads, strip_refshead(ref->name)); + } + + + if (!heads.nr) + result = 1; + else if (heads.nr > 1) { + result = 1; + } else + head_name = xstrdup(heads.items[0].string); + if (head_name) { + strbuf_addf(&b_head, "refs/remotes/%s/HEAD", remote); + strbuf_addf(&b_remote_head, "refs/remotes/%s/%s", remote, head_name); + /* make sure it's valid */ + if (!refs_ref_exists(refs, b_remote_head.buf)) + result |= error(_("Not a valid ref: %s"), b_remote_head.buf); + else if (refs_update_symref(refs, b_head.buf, b_remote_head.buf, + "remote set-head", &b_local_head, true)) + result |= error(_("Could not setup %s"), b_head.buf); + else { + report_set_head(remote, head_name, &b_local_head); + } + free(head_name); + } + + strbuf_release(&b_head); + strbuf_release(&b_local_head); + strbuf_release(&b_remote_head); + return result; +} + static int do_fetch(struct transport *transport, struct refspec *rs, const struct fetch_config *config) @@ -1646,6 +1722,8 @@ static int do_fetch(struct transport *transport, "refs/tags/"); } + strvec_push(&transport_ls_refs_options.ref_prefixes,"HEAD"); + if (must_list_refs) { trace2_region_enter("fetch", "remote_refs", the_repository); remote_refs = transport_get_remote_refs(transport, @@ -1790,6 +1868,10 @@ static int do_fetch(struct transport *transport, "you need to specify exactly one branch with the --set-upstream option")); } } + if (set_head(remote_refs)) + printf("Ran into issues with creating \'%s/HEAD\',\n" + "use \'git remote set-head -a %s\' to investigate.\n", + gtransport->remote->name,gtransport->remote->name); cleanup: if (retcode) { @@ -2020,6 +2102,7 @@ static int fetch_multiple(struct string_list *list, int max_children, return !!result; } + /* * Fetching from the promisor remote should use the given filter-spec * or inherit the default filter-spec from the config. diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 579872c258..ef5874b7b3 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -45,14 +45,17 @@ test_expect_success setup ' ' cat > test/expect << EOF + one/HEAD -> one/main one/main one/side origin/HEAD -> origin/main origin/main origin/side + three/HEAD -> three/main three/another three/main three/side + two/HEAD -> two/main two/another two/main two/side @@ -97,6 +100,7 @@ cat > expect << EOF origin/HEAD -> origin/main origin/main origin/side + three/HEAD -> three/main three/another three/main three/side @@ -112,8 +116,10 @@ test_expect_success 'git fetch --multiple (but only one remote)' ' ' cat > expect << EOF + one/HEAD -> one/main one/main one/side + two/HEAD -> two/main two/another two/main two/side @@ -221,14 +227,17 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' ' create_fetch_all_expect () { cat >expect <<-\EOF + one/HEAD -> one/main one/main one/side origin/HEAD -> origin/main origin/main origin/side + three/HEAD -> three/main three/another three/main three/side + two/HEAD -> two/main two/another two/main two/side