--- /dev/null
+#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;
+}