From: Srinidhi Kaushik Date: Sun, 13 Sep 2020 14:54:07 GMT Subject: [PATCH v3 1/7] remote: add reflog check for "force-if-includes" Message-ID: <20200913145413.18351-2-shrinidhi.kaushik@gmail.com> In-Reply-To: <20200913145413.18351-1-shrinidhi.kaushik@gmail.com> Add a check to verify if the remote-tracking ref of the local branch is reachable from one of its "reflog" entries; `set_ref_status_for_push` updated to add a reject reason and disallow the forced push if the check fails. When a local branch that is based on a remote ref, has been rewound and is to be force pushed on the remote, "apply_push_force_if_includes()" runs a check that ensure any updates to remote-tracking refs that may have happened (by push from another repository) in-between the time of the last checkout, and right before the time of push by rejecting the forced update. The struct "ref" has three new bit-fields: * if_includes: Set when we have to run the new check on the ref. * is_tracking: Set when the remote ref was marked as "use_tracking" or "use_tracking_for_rest" by compare-and-swap. * unreachable: Set if the ref is unreachable from any of the "reflog" entries of its local counterpart. When "--force-with-includes" is used along with "--force-with-lease", the check is run only for refs marked as "is_tracking". The enum "status" updated to include "REF_STATUS_REJECT_REMOTE_UPDATED" to imply that the ref failed the check. Signed-off-by: Srinidhi Kaushik --- remote.c | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- remote.h | 14 +++++- 2 files changed, 141 insertions(+), 8 deletions(-) diff --git a/remote.c b/remote.c index 420150837b..e4b2d85a6f 100644 --- a/remote.c +++ b/remote.c @@ -1484,6 +1484,36 @@ void set_ref_status_for_push(struct ref *remote_refs, int send_mirror, force_ref_update = 1; } + /* + * If the tip of the remote-tracking ref is unreachable + * from any reflog entry of its local ref indicating a + * possible update since checkout; reject the push. + * + * There is no need to check for reachability, if the + * ref is marked for deletion. + */ + if (ref->if_includes && !ref->deletion) { + /* + * If `force_ref_update' was previously set by + * "compare-and-swap", and we have to run this + * check, reset it back to the original value + * and update it depending on the status of this + * check. + */ + force_ref_update = ref->force || force_update; + + if (ref->unreachable) + reject_reason = + REF_STATUS_REJECT_REMOTE_UPDATED; + else + /* + * If updates from the remote-tracking ref + * have been integrated locally; force the + * update. + */ + force_ref_update = 1; + } + /* * If the update isn't already rejected then check * the usual "must fast-forward" rules. @@ -2272,11 +2302,74 @@ static int remote_tracking(struct remote *remote, const char *refname, return 0; } +static int ref_reachable(struct object_id *o_oid, struct object_id *n_oid, + const char *ident, timestamp_t timestamp, int tz, + const char *message, void *cb_data) +{ + int ret = 0; + struct object_id *r_oid = cb_data; + + ret = oideq(n_oid, r_oid); + if (!ret) { + struct commit *loc = lookup_commit_reference(the_repository, + n_oid); + struct commit *rem = lookup_commit_reference(the_repository, + r_oid); + ret = (loc && rem) ? in_merge_bases(rem, loc) : 0; + } + + return ret; +} + +/* + * Iterate through the reflog of a local branch and check + * if the tip of the remote-tracking branch is reachable + * from one of the entries. + */ +static int ref_reachable_from_reflog(const struct object_id *r_oid, + const struct object_id *l_oid, + const char *local_ref_name) +{ + int ret = 0; + struct commit *r_commit, *l_commit; + + l_commit = lookup_commit_reference(the_repository, l_oid); + r_commit = lookup_commit_reference(the_repository, r_oid); + + /* + * If the remote-tracking ref is an ancestor of the local + * ref (a merge, for instance) there is no need to iterate + * through the reflog entries to ensure reachability; it + * can be skipped to return early instead. + */ + ret = (r_commit && l_commit) ? in_merge_bases(r_commit, l_commit) : 0; + if (!ret) + ret = for_each_reflog_ent_reverse(local_ref_name, ref_reachable, + (struct object_id *)r_oid); + + return ret; +} + +/* + * Check for reachability of a remote-tracking + * ref in the reflog entries of its local ref. + */ +void check_reflog_for_ref(struct ref *r_ref) +{ + struct object_id r_oid; + struct ref *l_ref = get_local_ref(r_ref->name); + + if (r_ref->if_includes && l_ref && !read_ref(l_ref->name, &r_oid)) + r_ref->unreachable = !ref_reachable_from_reflog(&r_ref->old_oid, + &r_oid, + l_ref->name); +} + static void apply_cas(struct push_cas_option *cas, struct remote *remote, struct ref *ref) { - int i; + int i, is_tracking = 0; /* Find an explicit --