initial update command
authorFrantisek Hrbata <frantisek@hrbata.com>
Wed, 18 May 2022 15:39:54 +0000 (17:39 +0200)
committerFrantisek Hrbata <frantisek@hrbata.com>
Wed, 18 May 2022 15:39:54 +0000 (17:39 +0200)
Signed-off-by: Frantisek Hrbata <frantisek@hrbata.com>
Makefile
src/common.c
src/common.h
src/create.c
src/gimrr.c
src/gimrr.h
src/gimrr.mak
src/upload.c [new file with mode: 0644]

index 08321a5dbbb460ed1f7a2bbdf07bab0297613904..cf1650d0bd589cd3cd6a2821390420672b005a27 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,7 @@
 all: gimrr$(X)
 
+CFLAGS = -g -O0 -Wall
+
 GIT_DIR = git
 GIT_URL = https://github.com/git/git.git
 GIT_LNK = $(GIT_DIR)/$(GIMRR_GIT_DIR)
@@ -25,7 +27,7 @@ $(GIT_LNK):
                $(GIT_DIR)/Makefile     
 
 gimrr$(X): FORCE | $(GIT_LNK)
-       $(MAKE) -C $(GIT_DIR) $(GIMRR_GIT_DIR)/$@
+       $(MAKE) -C $(GIT_DIR) $(GIMRR_GIT_DIR)/$@ CFLAGS="$(CFLAGS)"
 
 clean: | $(GIT_LNK)
        $(RM) $(GIMRR_SRC_DIR)/gimrr$(X) cscope.out
index 941302d09f86f482de752564521de8a565ac5f71..21ddd7dd76cea712e4ec4f5478b53903a46ff2b5 100644 (file)
@@ -139,4 +139,29 @@ const char *lookup_mrid_by_name(const char *str)
        return id;
 }
 
-#undef BASE58_SIZE
+const char *get_remote_user(const char *remote) {
+       struct strbuf key = STRBUF_INIT;
+       char *user;
+
+       strbuf_addf(&key, "remote.%s.mruser", remote);
+       if (git_config_get_string(key.buf, &user) &&
+           git_config_get_string("user.mruser", &user))
+               user = NULL;
+       strbuf_release(&key);
+       return user;
+}
+
+const char *find_nth_component(const char *s, int nth, int *len)
+{
+       while (*s && nth)
+               if (*s++ == '/')
+                       --nth;
+       if (len && *s) {
+               const char *end;
+               for (end = s; *end && *end != '/'; end++)
+                       ;
+               *len = end - s;
+       }
+
+       return nth ? NULL : s;
+}
index 578f395979d3badd3bea07229d29ff09927e84f8..5d7e6b330d80796f753e8b69e97715d0b27b41c2 100644 (file)
@@ -4,5 +4,10 @@
 #include "gimrr.h"
 
 const char *lookup_mrid_by_name(const char *name);
+const char *get_remote_user(const char *remote);
+const char *find_nth_component(const char *s, int nth, int *len);
+
+#define local_ref_suffix(s) find_nth_component((s), 4, NULL)
+#define remote_ref_suffix(s) find_nth_component((s), 2, NULL)
 
 #endif
index 56f851b369ec003f4e13223aa792d8bd5ca3010a..8d20710aa26aba349ca71919a5157ce90e46ef7a 100644 (file)
@@ -1,5 +1,10 @@
 #include "gimrr.h"
 
+static const char * const create_usage[] = {
+       N_("gimrr create [<upstream>]"),
+       NULL
+};
+
 struct create_ctx {
        struct commit_list *commits;
        struct commit_list *deps;
@@ -15,11 +20,6 @@ struct create_ctx {
        struct strbuf msg;
 };
 
-static const char * const create_usage[] = {
-       N_("git mr create [<upstream>]"),
-       NULL
-};
-
 /* 
  * Base58 output buffer size requires to be approximately 1.4 greater.
  * log58(256^x) / log256(256^x) = 1.36
@@ -499,7 +499,6 @@ static void init_create_ctx(int argc, const char **argv, const char *prefix,
                struct create_ctx *ctx)
 {
        const char *remote = NULL, *start = NULL, *end = NULL, *branch = NULL;
-       struct strbuf buf = STRBUF_INIT;
        struct option options[] = {
                OPT_STRING('r', "remote", &remote, N_("remote"), N_("remote")),
                OPT_STRING('s', "start", &start, N_("start"), N_("start")),
@@ -513,6 +512,7 @@ static void init_create_ctx(int argc, const char **argv, const char *prefix,
                OPT_END()
        };
 
+
        argc = parse_options(argc, argv, prefix, options, create_usage,
                PARSE_OPT_STOP_AT_NON_OPTION);
 
@@ -546,14 +546,9 @@ static void init_create_ctx(int argc, const char **argv, const char *prefix,
                usage_with_options(create_usage, options);
        }
 
-       strbuf_addf(&buf, "remote.%s.mruser", ctx->remote);
-
-       if (git_config_get_string(buf.buf, (char **)&ctx->user) &&
-           git_config_get_string("user.mruser", (char **)&ctx->user))
+       if (!(ctx->user = get_remote_user(ctx->remote)))
                die(_("no mr user defined"));
 
-       strbuf_release(&buf);
-
        init_mr_commits(ctx);
 }
 
index 1e6fc15b474203479fda2a2672d546e3a2f9ec0b..6aef9bbc41f011a084e8de1c1e0f514a78ac32d9 100644 (file)
@@ -2,8 +2,9 @@
 #include "gimrr.h"
 
 static const char * const gimrr_usage[] = {
-       N_("gimrr mr list"),
-       N_("gimrr mr create [<upstream> [<head>]]"),
+       N_("gimrr list"),
+       N_("gimrr create [<upstream> [<head>]]"),
+       N_("gimrr upload [<mrid>...]"),
        NULL
 };
 
@@ -23,6 +24,8 @@ int cmd_main(int argc, const char *argv[])
                return cmd_list(argc, argv, prefix);
        else if (!strcmp(argv[0], "create"))
                return cmd_create(argc, argv, prefix);
+       else if (!strcmp(argv[0], "upload"))
+               return cmd_upload(argc, argv, prefix);
        else
                error(_("unknown subcommand: %s"), argv[0]);
 
index 082d09bce3a8f6cdd2f770916f5bd15de63d2d73..ae88d9979e6eb67c40d0c3bd41290e0bb46a20d8 100644 (file)
 #include "parse-options.h"
 #include "common.h"
 #include "strvec.h"
+#include "transport.h"
+#include "transport-internal.h"
 
 int cmd_create(int argc, const char **argv, const char *prefix);
 int cmd_list(int argc, const char **argv, const char *prefix);
+int cmd_push(int argc, const char **argv, const char *prefix);
 
 #endif
index 3805ca157a846cd15c52e80242eaeb22e5b28a0c..7252e0f787cdf8680780192d09f623fb2e79f2c4 100644 (file)
@@ -1,8 +1,9 @@
 GIMRR_GIT_DIR = contrib/gimrr
 GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/gimrr.o
+GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/common.o
 GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/create.o
 GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/list.o
-GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/common.o
+GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/upload.o
 OBJECTS += $(GIMRR_OBJECTS)
 
 $(GIMRR_GIT_DIR)/gimrr$X: $(GIMRR_OBJECTS) GIT-LDFLAGS $(GITLIBS)
diff --git a/src/upload.c b/src/upload.c
new file mode 100644 (file)
index 0000000..ec2b339
--- /dev/null
@@ -0,0 +1,414 @@
+#include "gimrr.h"
+
+#define HAS_LOCAL_REFS 1
+
+#define UPLOAD_HEAD  (1<<1)
+#define UPLOAD_TAGS  (1<<2)
+#define UPLOAD_MBOX  (1<<3)
+#define UPLOAD_ALL   (UPLOAD_HEAD | UPLOAD_TAGS | UPLOAD_MBOX)
+
+static const char * const upload_usage[] = {
+       N_("gimrr upload [<mrid>...]"),
+       NULL
+};
+
+/* local mr refs for one remote */
+struct local_refs {
+       struct remote *remote;
+       struct ref *ref;
+};
+
+static struct string_list mrids = STRING_LIST_INIT_DUP;
+static struct local_refs *local_refs;
+static size_t local_refs_nr;
+static size_t local_refs_alloc;
+static int upload_flags;
+static int verbosity;
+static int verbose;
+static int quiet;
+static int progress = -1;
+
+static int get_mr_refs(const char *refname, const struct object_id *oid,
+                        int flag, void *cb_data)
+{
+       struct ref ***ref_tail = cb_data;
+       struct ref *ref;
+       const char *last;
+       int type = 0;
+
+       last = strrchr(refname, '/');
+       last++;
+       if (!strcmp("head", last))
+               type = UPLOAD_HEAD;
+       else if (!strcmp("tags", last))
+               type = UPLOAD_TAGS;
+       else if (!strcmp("mbox", last))
+               type = UPLOAD_MBOX;
+
+       if (!type) {
+               warning(_("unknown mr reference '%s'"), refname);
+               return 0;
+       }
+
+       if (!(type & upload_flags))
+               return 0;
+
+       ref = alloc_ref(refname);
+       oidcpy(&ref->new_oid, oid);
+       **ref_tail = ref;
+       *ref_tail = &ref->next;
+
+       return 0;
+}
+
+static int get_local_refs(struct remote *remote, void *cb_data)
+{
+       struct string_list_item *item;
+       struct strbuf ref_prefix = STRBUF_INIT;
+       struct local_refs *local_ref;
+       struct ref **ref_tail;
+
+       if (!remote_is_configured(remote, 1))
+               return 0;
+
+       ALLOC_GROW(local_refs, local_refs_nr + 1, local_refs_alloc);
+       local_ref = &local_refs[local_refs_nr++];
+       local_ref->remote = remote;
+       local_ref->ref = NULL;
+       ref_tail = &local_ref->ref;
+
+       for_each_string_list_item(item, &mrids) {
+               struct ref **ref_tail_orig = ref_tail;
+               strbuf_reset(&ref_prefix);
+               strbuf_addf(&ref_prefix, "refs/mrs/local/%s/%s/",
+                               remote->name, item->string);
+               for_each_fullref_in(ref_prefix.buf, get_mr_refs, &ref_tail);
+               /* no new refs added for remote */
+               if (ref_tail_orig == ref_tail)
+                       continue;
+               if (item->util)
+                       warning(_("mr '%s' in different remotes"), item->string);
+               item->util = (void *)HAS_LOCAL_REFS;
+       }
+
+       strbuf_release(&ref_prefix);
+
+       return 0;
+}
+
+static void set_remote_prefixes(struct ref *local_refs, struct strvec *names)
+{
+       struct ref *ref = local_refs;
+
+       while (ref) {
+               strvec_pushf(names, "refs/mrs/%s", local_ref_suffix(ref->name));
+               ref = ref->next;
+       }
+}
+
+static struct ref *get_remote_peer(struct ref *src, const char *local_suffix,
+               struct ref *dst)
+{
+       const char *remote_suffix;
+
+       while (dst) {
+               remote_suffix = remote_ref_suffix(dst->name);
+               if (!strcmp(local_suffix, remote_suffix))
+                       return dst;
+               dst = dst->next;
+       }
+
+       return NULL;
+}
+
+static void set_remote_peer(struct ref *src, struct ref *dst,
+               struct ref ***dst_tail)
+{
+       struct strbuf dst_refname = STRBUF_INIT;
+       const char *local_suffix = local_ref_suffix(src->name);
+       struct ref *peer = get_remote_peer(src, local_suffix, dst);
+
+       if (peer) {
+               peer->peer_ref = copy_ref(src);
+               return;
+       }
+
+       strbuf_addf(&dst_refname, "refs/mrs/%s", local_suffix);
+       peer = alloc_ref(dst_refname.buf);
+       strbuf_release(&dst_refname);
+       **dst_tail = peer;
+       *dst_tail = &peer->next;
+       peer->peer_ref = copy_ref(src);
+}
+
+static void set_remote_ref_status(struct ref *ref)
+{
+       if (!ref->peer_ref)
+               return;
+
+       oidcpy(&ref->new_oid, &ref->peer_ref->new_oid);
+
+       if (is_null_oid(&ref->old_oid))
+               return;
+
+       /*
+       if (ends_with(ref->name, "head"))
+               ref->status = REF_STATUS_REJECT_ALREADY_EXISTS;
+       */
+       if (!has_object_file(&ref->old_oid))
+               ref->status = REF_STATUS_REJECT_FETCH_FIRST;
+       else if (!lookup_commit_reference_gently(the_repository, &ref->old_oid, 1))
+               ref->status = REF_STATUS_REJECT_NEEDS_FORCE;
+       else if (!lookup_commit_reference_gently(the_repository, &ref->new_oid, 1))
+               ref->status = REF_STATUS_REJECT_NEEDS_FORCE;
+       else if (!ref_newer(&ref->new_oid, &ref->old_oid))
+               ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+       else if (oideq(&ref->old_oid, &ref->new_oid))
+               ref->status = REF_STATUS_UPTODATE;
+}
+static void set_remote_peers(struct ref *dst, struct ref *src)
+{
+       struct ref **dst_tail = &dst;
+       struct ref *ref;
+
+       for (ref = dst; ref; ref = ref->next)
+               dst_tail = &ref->next;
+
+       for (ref = src; ref; ref = ref->next)
+               set_remote_peer(ref, dst, &dst_tail);
+
+       for (ref = dst; ref; ref = ref->next)
+               set_remote_ref_status(ref);
+}
+
+static const char *get_ref_status_msg(struct ref *r)
+{
+       switch(r->status) {
+       case REF_STATUS_NONE:
+               return "[no match]";
+       case REF_STATUS_REJECT_NODELETE:
+               return "remote does not support deleting refs";
+       case REF_STATUS_UPTODATE:
+               return "up to date";
+       case REF_STATUS_REJECT_NONFASTFORWARD:
+               return "non-fast-forward";
+       case REF_STATUS_REJECT_ALREADY_EXISTS:
+               return "already exists";
+       case REF_STATUS_REJECT_FETCH_FIRST:
+               return "fetch first";
+       case REF_STATUS_REJECT_NEEDS_FORCE:
+               return "needs force";
+       case REF_STATUS_REJECT_STALE:
+               return "stale info";
+       case REF_STATUS_REJECT_REMOTE_UPDATED:
+               return "remote ref updated since checkout";
+       case REF_STATUS_REJECT_SHALLOW:
+               return "new shallow roots not allowed";
+       case REF_STATUS_REMOTE_REJECT:
+               return "remote rejected";
+       case REF_STATUS_EXPECTING_REPORT:
+               return "remote failed to report status";
+       case REF_STATUS_ATOMIC_PUSH_FAILED:
+               return "atomic push failed";
+       case REF_STATUS_OK:
+               return "ok";
+       }
+
+       return "unknown";
+}
+
+static void print_ref_status(struct ref *r, const char *flag, const char *msg,
+               const char *err_msg)
+{
+       int type_len, id_len, ns_len;
+       const char *type, *id, *ns;
+
+       type = find_nth_component(r->name, 4, &type_len);
+       id = find_nth_component(r->name, 2, &id_len);
+       ns = find_nth_component(r->name, 1, &ns_len);
+       ns--;
+
+       if (err_msg)
+               fprintf(stderr, " %s [%s %.*s %.*s] %.*s (%s)\n", flag, msg, ns_len,
+                               ns, type_len, type, id_len, id, err_msg);
+       else
+               fprintf(stderr, " %s [%s %.*s %.*s] %.*s\n", flag, msg, ns_len,
+                               ns, type_len, type, id_len, id);
+}
+static void print_upload_status(struct ref *r, int err)
+{
+       int s;
+
+       if (!err && quiet)
+               return;
+
+       for (; r; r = r->next) {
+               s = r->status;
+
+               if (s == REF_STATUS_UPTODATE) {
+                       if (verbose)
+                               print_ref_status(r, "=", "up to date", NULL);
+
+               } else if (s == REF_STATUS_OK) {
+                       if (is_null_oid(&r->old_oid))
+                               print_ref_status(r, "*", "new", NULL);
+                       else
+                               print_ref_status(r, ">", "updated", NULL);
+
+               } else if (s != REF_STATUS_NONE)
+                       print_ref_status(r, "!", "rejected", get_ref_status_msg(r));
+       }
+}
+
+static void update_tracking_refs(struct ref *refs)
+{
+       struct ref *r;
+       int type_len, remote_len, id_len, ns_len;
+       const char *type, *remote, *id, *ns;
+       struct strbuf buf = STRBUF_INIT;
+
+       for (r = refs; r; r = r->next) {
+               if (r->status != REF_STATUS_OK &&
+                   r->status != REF_STATUS_UPTODATE)
+                       continue;
+
+               type = find_nth_component(r->name, 4, &type_len);
+               remote = find_nth_component(r->peer_ref->name, 3, &remote_len);
+               id = find_nth_component(r->name, 2, &id_len);
+               ns = find_nth_component(r->name, 1, &ns_len);
+               ns_len--;
+
+               if (verbose) {
+                       fprintf(stderr, "updating remote tracking ref for "
+                                       "%.*s %.*s '%.*s'\n",
+                                       ns_len, ns, type_len, type, id_len,
+                                       id);
+               }
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "refs/mrs/remote/%s", remote);
+               update_ref("update by gimrr", buf.buf, &r->new_oid, NULL, 0, 0);
+
+               if (verbose)
+                       fprintf(stderr, "deleting local ref for "
+                                       "%.*s %.*s '%.*s'\n",
+                                       ns_len, ns, type_len, type, id_len,
+                                       id);
+               delete_ref(NULL, r->peer_ref->name, NULL, 0);
+       }
+
+       strbuf_release(&buf);
+}
+
+static int upload_local_refs_url(struct remote *remote, const char *url,
+               struct ref *local_refs)
+{
+       struct transport *t = transport_get(remote, url);
+       struct transport_ls_refs_options opts = TRANSPORT_LS_REFS_OPTIONS_INIT;
+       char *anon_url = transport_anonymize_url(url);
+       struct ref *remote_refs;
+       int err = 0;
+
+       if (verbose > 0)
+               fprintf(stderr, _("uploading to remote '%s' with url '%s'\n"),
+                               remote->name, anon_url);
+
+       if (!t->vtable->push_refs)
+               goto err;
+
+       transport_set_verbosity(t, verbosity, progress);
+       set_remote_prefixes(local_refs, &opts.ref_prefixes);
+       remote_refs = t->vtable->get_refs_list(t, 1, &opts);
+       transport_ls_refs_options_release(&opts);
+       set_remote_peers(remote_refs, local_refs);
+       err = t->vtable->push_refs(t, remote_refs, 0);
+       print_upload_status(remote_refs, err);
+       update_tracking_refs(remote_refs);
+       err += transport_disconnect(t);
+       if (err)
+               goto err;
+
+       if (!quiet)
+               fprintf(stderr, "everything already uploaded for "
+                               "remote '%s' with url '%s'\n",
+                               remote->name, anon_url);
+
+       free(anon_url);
+       return 0;
+err:
+       error(_("failed to upload some refs to remote '%s' "
+               "with url '%s'"), remote->name, anon_url);
+       free(anon_url);
+       return 1;
+}
+
+static int upload_local_refs(struct remote *remote, struct ref *local_refs)
+{
+       const char **url = remote->pushurl_nr ? remote->pushurl : remote->url;
+       int url_nr = remote->pushurl_nr ? remote->pushurl_nr : remote->url_nr;
+       int i, rv = 0;
+
+       for (i = 0; i < url_nr; ++i)
+               rv += upload_local_refs_url(remote, url[i], local_refs);
+
+       return !!rv;
+}
+
+static int for_each_cmdl_remote(struct string_list *remotes, each_remote_fn fn,
+               void *priv)
+{
+       struct string_list_item *item;
+       struct remote *remote;
+       int result;
+
+       for_each_string_list_item(item, remotes) {
+               remote = remote_get(item->string);
+               if (!remote_is_configured(remote, 1))
+                       die(_("no such remote: '%s'"), item->string);
+               if ((result = fn(remote, priv)))
+                       break;
+       }
+       return result;
+}
+
+int cmd_upload(int argc, const char **argv, const char *prefix)
+{
+       struct string_list remotes = STRING_LIST_INIT_NODUP;
+       int i, rv = 0;
+
+       struct option options[] = {
+               OPT__VERBOSITY(&verbosity),
+               OPT_BIT('h' , "head", &upload_flags, N_("upload only heads"), UPLOAD_HEAD),
+               OPT_BIT('t' , "tags", &upload_flags, N_("upload only tags"), UPLOAD_TAGS),
+               OPT_BIT('m' , "mbox", &upload_flags, N_("upload only mboxes"), UPLOAD_MBOX),
+               OPT_STRING_LIST('r', "remote", &remotes, N_("remote"), N_("upload only to <remote>")),
+               OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, prefix, options, upload_usage,
+               PARSE_OPT_STOP_AT_NON_OPTION);
+
+       verbose = (verbosity > 0);
+       quiet = (verbosity < 0);
+
+       if (!upload_flags)
+               upload_flags = UPLOAD_ALL;
+
+       for (i = 0; i < argc; ++i) {
+               const char *mrid = lookup_mrid_by_name(argv[i]);
+               if (!mrid)
+                       die(_("no mr found for '%s'"), argv[i]);
+               string_list_insert(&mrids, mrid);
+       }
+
+       if (remotes.nr)
+               for_each_cmdl_remote(&remotes, get_local_refs, NULL);
+       else
+               for_each_remote(get_local_refs, NULL);
+
+       for (i = 0; i < local_refs_nr; ++i) {
+               rv += upload_local_refs(local_refs[i].remote, local_refs[i].ref);
+       }
+
+       return rv;
+}