--- /dev/null
+#include "gimrr.h"
+
+static int check_base58(const char *str)
+{
+ const signed char map[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, '1','2','3','4','5','6','7',
+ '8','9',-1, -1, -1, -1, -1, -1,
+ -1, 'A','B','C','D','E','F','G',
+ 'H',-1, 'J','K','L','M','N',-1,
+ 'P','Q','R','S','T','U','V','W',
+ 'X','Y','Z',-1, -1, -1, -1, -1,
+ -1, 'a','b','c','d','e','f','g',
+ 'h','i','j','k',-1, 'm','n','o',
+ 'p','q','r','s','t','u','v','w',
+ 'x','y','z',-1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ };
+
+ while (*str && map[(int)*str++] != -1)
+ ;
+ return *str;
+}
+
+struct expand_ref_data {
+ const char *str;
+ struct strvec prefixes;
+ struct object_id oid;
+ int refs_found;
+};
+
+static int expand_ref_cb(const char *refname, const struct object_id *oid,
+ int flag, void *cb_data)
+{
+ struct expand_ref_data *data = (struct expand_ref_data *)cb_data;
+
+ if (!ends_with(refname, "/head"))
+ return 0;
+
+ if (!data->refs_found++)
+ oidcpy(&data->oid, oid);
+
+ return 0;
+}
+
+static int remote_ref_cb(struct remote *remote, void *cb_data)
+{
+ struct expand_ref_data *data = (struct expand_ref_data *)cb_data;
+
+ if (!remote_is_configured(remote, 1))
+ return 0;
+
+ strvec_pushf(&data->prefixes, "refs/mrs/local/%s/%s",
+ remote->name, data->str);
+ strvec_pushf(&data->prefixes, "refs/mrs/remote/%s/%s",
+ remote->name, data->str);
+ return 0;
+}
+
+static struct commit *lookup_commit_reference_by_short_name(const char *str)
+{
+ struct expand_ref_data data = { str, STRVEC_INIT };
+
+ /*
+ * Sanity check that 'str' looks like mrid and has at least some
+ * reasonable length.
+ */
+ if (strlen(str) < MINIMUM_ABBREV || check_base58(str))
+ return NULL;
+
+ for_each_remote(remote_ref_cb, &data);
+ for_each_fullref_in_prefixes(NULL, data.prefixes.v, expand_ref_cb,
+ &data);
+ strvec_clear(&data.prefixes);
+
+ if (!data.refs_found)
+ return NULL;
+
+ if (data.refs_found > 1) {
+ warning(_("ambiguous mrid '%s'"), str);
+ return NULL;
+ }
+
+ return lookup_commit_reference(the_repository, &data.oid);
+}
+
+/*
+ * Base58 output buffer size requires to be approximately 1.4 greater.
+ * log58(256^x) / log256(256^x) = 1.36
+ */
+#define BASE58_SIZE (GIT_MAX_RAWSZ * 140 / 100 + 1)
+
+const char *lookup_mrid_by_name(const char *str)
+{
+ static char id[BASE58_SIZE + 1]; /* '\0' terminated */
+ struct commit *commit;
+ const char *buf, *hdr;
+ size_t buf_size, hdr_size;
+
+ commit = lookup_commit_reference_by_name(str);
+ if (!commit)
+ commit = lookup_commit_reference_by_short_name(str);
+ if (!commit)
+ return NULL;
+
+ buf = get_commit_buffer(commit, &buf_size);
+ hdr = find_header_mem(buf, buf_size, "merge-request", &hdr_size);
+ if (!hdr || hdr_size < 3 || !starts_with(hdr, "id ")) {
+ unuse_commit_buffer(commit, buf);
+ return NULL;
+ }
+
+ hdr += 3;
+ hdr_size -= 3;
+ memcpy(id, hdr, hdr_size);
+ id[hdr_size] = 0;
+ unuse_commit_buffer(commit, buf);
+
+ return id;
+}
+
+#undef BASE58_SIZE
--- /dev/null
+#include "gimrr.h"
+
+struct create_ctx {
+ struct commit_list *commits;
+ struct commit_list *deps;
+ struct commit *base;
+ struct commit *tail;
+ unsigned int commits_nr;
+ const char *upstream;
+ const char *start;
+ const char *end;
+ const char *remote;
+ const char *branch;
+ const char *user;
+ 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
+ */
+#define BASE58_SIZE (GIT_MAX_RAWSZ * 140 / 100 + 1)
+
+static char *oid_to_b58(const struct object_id *oid)
+{
+ static unsigned char b58[BASE58_SIZE + 1]; /* '\0' terminated */
+ static char *b58_alph = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+ unsigned char *cur, *beg, *end;
+ unsigned int carry = 0, zeroes = 0;
+ const unsigned char *hash = oid->hash;
+ size_t hash_size = hash_algos[oid->algo].rawsz;
+
+ cur = beg = end = b58 + BASE58_SIZE - 1;
+ memset(b58, 0, BASE58_SIZE);
+
+ /* count leading zeroes */
+ for (zeroes = 0; !*hash && hash_size - zeroes; hash++, zeroes++)
+ ;
+
+ /* calculate base58 */
+ while (hash_size--) {
+ carry = *hash++;
+ cur = beg;
+ /*
+ * Multiply b58 by 256 and add current hash byte.
+ * b58 is constructed in BE, so 'beg' points to the
+ * last entry in b58 and 'cur' is decremented.*/
+ while (carry || end <= cur) {
+ carry += *cur << 8;
+ *cur-- = carry % 58;
+ carry /= 58;
+ }
+ /* 'end' points to the first calculated b58 entry */
+ end = ++cur;
+ }
+
+ /* add leading zeroes to b58 */
+ end -= zeroes;
+
+ /* map to b58 alphabet */
+ for (cur = end; cur <= beg; cur++)
+ *cur = b58_alph[*cur];
+
+ return (char *)end;
+}
+
+static const char *get_header(const char *msg, const char *key)
+{
+ size_t len;
+ const char *v = find_commit_header(msg, key, &len);
+ return v ? xmemdupz(v, len) : NULL;
+}
+
+static void append_extra_header(struct commit_extra_header ***tail,
+ const char *key, const char *value)
+{
+ struct commit_extra_header *h = CALLOC_ARRAY(h, 1);
+
+ h->key = xmemdupz(key, strlen(key));
+ h->value = xmemdupz(value, strlen(value));
+ h->len = strlen(value);
+
+ while (**tail)
+ *tail = &(**tail)->next;
+
+ **tail = h;
+ *tail = &h->next;
+}
+
+static const char *exclude_hdrs[] = {
+ "gpgsig",
+ "gpgsig-sha256",
+ "merge-request",
+ NULL};
+
+static struct commit *create_mr_commit(struct commit *commit,
+ struct commit *parent, const char *hdr)
+{
+ struct commit_extra_header *extra, **eptr = &extra;
+ struct commit_list *parents = NULL, **pptr = &parents;
+ struct object_id *tree, *oid = &commit->object.oid, new_oid;
+ struct commit *new_commit;
+ const char *buf, *msg, *author;
+
+ parse_commit_or_die(commit);
+
+ tree = get_commit_tree_oid(commit);
+ if (!tree)
+ die(_("commit '%s' lacks tree"), oid_to_hex(oid));
+
+ buf = logmsg_reencode(commit, NULL, get_commit_output_encoding());
+ if (!buf)
+ die(_("failed to read commit '%s' message"), oid_to_hex(oid));
+
+ msg = strstr(buf, "\n\n");
+ if (!msg)
+ die(_("commit '%s' lacks headers"), oid_to_hex(oid));
+ msg += 2;
+
+ author = get_header(buf, "author");
+ if (!author)
+ die(_("commit '%s' lacks author header"), oid_to_hex(oid));
+
+ if (parent)
+ pptr = commit_list_append(parent, pptr);
+ else
+ parents = copy_commit_list(commit->parents);
+
+ extra = read_commit_extra_headers(commit, exclude_hdrs);
+ append_extra_header(&eptr, "merge-request", hdr);
+
+ if (commit_tree_extended(msg, strlen(msg), tree, parents, &new_oid,
+ author, NULL, NULL, extra))
+ die(_("failed to write commit object"));
+
+ free_commit_extra_headers(extra);
+ unuse_commit_buffer(commit, buf);
+ free((void *)author);
+
+ new_commit = lookup_commit(the_repository, &new_oid);
+ if (!new_commit)
+ die(_("unable to access commit '%s'"), oid_to_hex(&new_oid));
+
+ return new_commit;
+}
+
+static struct commit *create_mr_head(struct commit_list *commits, const char *hdr)
+{
+ struct commit *old, *new = NULL;
+
+ while (commits) {
+ old = commits->item;
+ new = create_mr_commit(old, new, hdr);
+ commits = commits->next;
+ }
+
+ return new;
+}
+
+static struct tag *create_mr_tag(struct strbuf *msg, struct commit *head,
+ const char *mrid)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct object_id oid;
+
+ strbuf_addf(&buf,
+ "object %s\n"
+ "type %s\n"
+ "tag %s\n"
+ "tagger %s\n\n"
+ "%s",
+ oid_to_hex(&head->object.oid),
+ type_name(OBJ_COMMIT),
+ mrid,
+ git_committer_info(IDENT_STRICT),
+ msg->buf);
+
+ if (write_object_file(buf.buf, buf.len, OBJ_TAG, &oid) < 0)
+ die(_("unable to write tag file"));
+
+ strbuf_release(&buf);
+
+ return lookup_tag(the_repository, &oid);
+}
+
+struct upstream_to_remote_cb_data {
+ struct refspec_item spec;
+ struct remote *remote;
+ int matches;
+};
+
+static int upstream_to_remote_cb(struct remote *remote, void *priv)
+{
+ struct upstream_to_remote_cb_data *cb_data = priv;
+
+ if (!remote_is_configured(remote, 1))
+ return 0;
+
+ if (remote_find_tracking(remote, &cb_data->spec))
+ return 0;
+
+ FREE_AND_NULL(cb_data->spec.src);
+
+ if (cb_data->matches++)
+ return 0;
+
+ cb_data->remote = remote;
+
+ return 0;
+}
+
+static struct remote *upstream_to_remote(char *ref)
+{
+ struct upstream_to_remote_cb_data cb_data;
+
+ memset(&cb_data, 0, sizeof(cb_data));
+ cb_data.spec.dst = ref;
+ for_each_remote(upstream_to_remote_cb, &cb_data);
+
+ if (!cb_data.matches)
+ die(_("no remote found for upstream '%s'"), ref);
+ else if (cb_data.matches > 1)
+ die(_("ambiguous remote for upstream '%s'"), ref);
+
+ return cb_data.remote;
+}
+
+static void init_create_ctx_from_branch(struct create_ctx *ctx,
+ struct branch *branch)
+{
+ if (!branch)
+ die(_("no current branch"));
+
+ if (!branch->merge_nr)
+ die(_("no upstream configured for branch: '%s'"),
+ branch->name);
+ else if (branch->merge_nr > 1)
+ die(_("ambiguous upstream configuration for branch: '%s'"),
+ branch->name);
+
+ ctx->upstream = xstrdup(branch->merge[0]->dst);
+
+ if (!ctx->remote) {
+ const char *remote;
+
+ remote = pushremote_for_branch(branch, NULL);
+
+ if (!remote_is_configured(remote_get(remote), 1))
+ die(_("no such remote: '%s'"), remote);
+
+ ctx->remote = xstrdup(remote);
+ }
+
+ if (!ctx->end)
+ ctx->end = xstrdup(branch->refname);
+
+ if (!ctx->branch)
+ ctx->branch = xstrdup(branch->merge[0]->src);
+
+ if (!ctx->start)
+ ctx->start = xstrdup(branch->merge[0]->dst);
+}
+
+static void init_create_ctx_from_upstream(struct create_ctx *ctx,
+ const char *upstream)
+{
+ struct object_id up_oid;
+ struct remote *remote;
+ char *up_ref;
+ int rv;
+
+ rv = dwim_ref(upstream, strlen(upstream), &up_oid, &up_ref, 0);
+ if (!rv)
+ die(_("non-existing remote-tracking reference '%s'"),
+ upstream);
+ else if (rv != 1)
+ die(_("ambiguous upstream reference: '%s'"), upstream);
+ else if (!starts_with(up_ref, "refs/remotes/"))
+ die(_("upstream reference '%s' is not remote-tracking branch"),
+ upstream);
+
+ ctx->upstream = up_ref;
+
+ remote = upstream_to_remote(up_ref);
+
+ if (!ctx->remote)
+ ctx->remote = xstrdup(remote->name);
+
+ if (!ctx->branch) {
+ struct refspec_item refspec;
+
+ memset(&refspec, 0, sizeof(refspec));
+ refspec.dst = up_ref;
+
+ if (remote_find_tracking(remote, &refspec))
+ die(_("no upstream branch for remote-tracking branch '%s'"),
+ up_ref);
+
+ ctx->branch = refspec.src;
+ }
+
+ if (!ctx->start)
+ ctx->start = xstrdup(up_ref);
+
+ if (!ctx->end) {
+ struct branch *branch = branch_get(NULL);
+ if (!branch)
+ die(_("no current branch"));
+
+ ctx->end = xstrdup(branch->refname);
+ }
+}
+
+static void init_mr_commits(struct create_ctx *ctx)
+{
+ struct rev_info revs;
+ struct commit *commit, *start, *end, *upstream;
+ struct commit_list *bases = NULL, *parents = NULL, *deps = NULL;
+
+ start = lookup_commit_reference_by_name(ctx->start);
+ if (!start)
+ die(_("could not lookup commit %s"), ctx->start);
+ end = lookup_commit_reference_by_name(ctx->end);
+ if (!end)
+ die(_("could not lookup commit %s"), ctx->end);
+
+ upstream = lookup_commit_reference_by_name(ctx->upstream);
+ if (!upstream)
+ die(_("could not lookup commit %s"), ctx->upstream);
+
+ bases = get_merge_bases(start, end);
+ if (!bases || !oideq(&bases->item->object.oid, &start->object.oid))
+ die(_("'%s' is not acestor of '%s'"), ctx->start, ctx->end);
+
+ free_commit_list(bases);
+
+ bases = get_merge_bases(upstream, start);
+ if (!bases || !oideq(&bases->item->object.oid, &upstream->object.oid))
+ die(_("'%s' is not acestor of '%s'"), ctx->upstream,
+ ctx->start);
+ free_commit_list(bases);
+
+ init_revisions(&revs, NULL);
+ revs.first_parent_only = 1;
+
+ start->object.flags ^= UNINTERESTING;
+ add_pending_object(&revs, &start->object, ctx->start);
+ add_pending_object(&revs, &end->object, ctx->end);
+
+ if (prepare_revision_walk(&revs))
+ die(_("revision walk setup failed"));
+
+ while ((commit = get_revision(&revs))) {
+ parents = commit->parents->next;
+ /* This is merge commit. */
+ if (parents) {
+ if (!ctx->base)
+ ctx->base = commit;
+ while (parents) {
+ deps = commit_list_insert(parents->item,
+ &deps);
+ parents = parents->next;
+ }
+ continue;
+ }
+ /* Normal commit after at least one merge commit. */
+ if (ctx->base)
+ break;
+
+ ctx->commits = commit_list_insert(commit, &ctx->commits);
+ ctx->commits_nr++;
+ }
+
+ clear_commit_marks(start, UNINTERESTING);
+
+ if (!ctx->commits_nr)
+ die(_("no mr suitable commits in refspec '%s:%s'"), ctx->start,
+ ctx->end);
+ if (!ctx->base)
+ ctx->base = start;
+
+ if (!oideq(&ctx->base->object.oid, &upstream->object.oid)) {
+ parents = ctx->base->parents;
+ if (parents && parents->next &&
+ !oideq(&parents->item->object.oid, &upstream->object.oid))
+ deps = commit_list_insert(parents->item, &deps);
+ else
+ deps = commit_list_insert(ctx->base, &deps);
+ }
+
+ ctx->tail = upstream;
+ ctx->deps = reduce_heads(deps);
+ free_commit_list(deps);
+}
+
+static char *generate_mr_mrid(struct commit_list *commits)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct object_id oid;
+
+ while (commits) {
+ strbuf_addstr(&buf, oid_to_hex(&commits->item->object.oid));
+ commits = commits->next;
+ }
+
+ hash_object_file(the_hash_algo, buf.buf, buf.len, OBJ_BLOB, &oid);
+ strbuf_reset(&buf);
+ strbuf_addstr(&buf, oid_to_b58(&oid));
+ return strbuf_detach(&buf, NULL);
+}
+
+static char *generate_mr_refname(const char *remote, const char *mrid,
+ const char *user)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "refs/mrs/local/%s/%s/%s/head", remote, mrid, user);
+ return strbuf_detach(&buf, NULL);
+}
+
+static char *generate_mr_hdr(const char *mrid, struct create_ctx *ctx)
+{
+ struct commit_list *deps = ctx->deps;
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf,
+ "id %s\n"
+ "branch %s\n"
+ "user %s\n"
+ "base %s\n"
+ "tail %s\n"
+ "ccnt %u\n",
+ mrid,
+ ctx->branch,
+ ctx->user,
+ oid_to_hex(&ctx->base->object.oid),
+ oid_to_hex(&ctx->tail->object.oid),
+ ctx->commits_nr);
+
+ while (deps) {
+ strbuf_addf(&buf, "dep %s\n",
+ oid_to_hex(&deps->item->object.oid));
+ deps = deps->next;
+ }
+
+ return strbuf_detach(&buf, NULL);
+}
+
+static void create_mr_ref(const char *ref, struct tag *tag)
+{
+ struct object_id *oid = &tag->object.oid;
+ struct object_id old_oid;
+
+ oidclr(&old_oid);
+ update_ref(NULL, ref, oid, &old_oid, REF_NO_DEREF,
+ UPDATE_REFS_DIE_ON_ERR);
+}
+
+static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ if (buf->len)
+ strbuf_addch(buf, '\n');
+ strbuf_addstr(buf, arg);
+ strbuf_complete_line(buf);
+
+ return 0;
+}
+
+static int parse_file_arg(const struct option *opt, const char *arg, int unset)
+{
+ struct strbuf *buf = opt->value;
+
+ BUG_ON_OPT_NEG(unset);
+
+ if (buf->len)
+ strbuf_addch(buf, '\n');
+
+ if (!strcmp(arg, "-")) {
+ if (strbuf_read(buf, 0, 0) < 0)
+ die_errno(_("cannot read '%s'"), arg);
+ } else if (strbuf_read_file(buf, arg, 0) < 0)
+ die_errno(_("could not open or read '%s'"), arg);
+
+ strbuf_complete_line(buf);
+
+ return 0;
+}
+
+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")),
+ OPT_STRING('e', "end", &end, N_("end"), N_("end")),
+ OPT_STRING('b', "branch", &branch, N_("branch"), N_("branch")),
+ OPT_CALLBACK_F('m', "message", &ctx->msg, N_("message"),
+ N_("message"), PARSE_OPT_NONEG, parse_msg_arg),
+ OPT_CALLBACK_F('F', "file", &ctx->msg, N_("file"),
+ N_("read message from file"), PARSE_OPT_NONEG,
+ parse_file_arg),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, create_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (remote) {
+ if (!remote_is_configured(remote_get(remote), 1))
+ die(_("no such remote: '%s'"), remote);
+ ctx->remote = xstrdup(remote);
+ }
+
+ if (start)
+ ctx->start = xstrdup(start);
+
+ if (end)
+ ctx->end = xstrdup(end);
+
+ if (branch) {
+ if (!starts_with(branch, "refs/heads/"))
+ die(_("branch '%s' does not start with 'refs/heads/'"),
+ branch);
+ ctx->branch = xstrdup(branch);
+ }
+
+ switch (argc) {
+ case 0:
+ init_create_ctx_from_branch(ctx, branch_get(NULL));
+ break;
+ case 1:
+ init_create_ctx_from_upstream(ctx, argv[0]);
+ break;
+ default:
+ 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))
+ die(_("no mr user defined"));
+
+ strbuf_release(&buf);
+
+ init_mr_commits(ctx);
+}
+
+static void release_create_ctx(struct create_ctx *ctx)
+{
+ free((void *)ctx->upstream);
+ free((void *)ctx->start);
+ free((void *)ctx->end);
+ free((void *)ctx->remote);
+ free((void *)ctx->branch);
+ free((void *)ctx->user);
+ free_commit_list(ctx->commits);
+ free_commit_list(ctx->deps);
+ strbuf_release(&ctx->msg);
+}
+
+static int create_config(const char *k, const char *v, void *cb)
+{
+ /*
+ struct create_ctx *ctx = cb;
+ */
+
+ return git_default_config(k, v, cb);
+}
+
+static void generate_mr_msg(struct strbuf *msg, int edit)
+{
+ const char *path;
+ int fd;
+
+ if (msg->len && !edit)
+ return;
+
+ path = git_path("MR_EDITMSG");
+
+ fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+ write_or_die(fd, msg->buf, msg->len);
+ close(fd);
+
+ strbuf_reset(msg);
+
+ if (launch_editor(path, msg, NULL)) {
+ fprintf(stderr,
+ _("Please supply the message using either -m or -F option.\n"));
+ exit(1);
+ }
+
+ if (!msg->len)
+ die(_("no mr message?"));
+}
+
+int cmd_create(int argc, const char **argv, const char *prefix)
+{
+ struct commit *head;
+ struct tag *tag;
+ char *mrid = NULL, *refname = NULL, *hdr = NULL;
+ struct create_ctx ctx = { .msg = STRBUF_INIT };
+
+ git_config(create_config, NULL);
+ init_create_ctx(argc, argv, prefix, &ctx);
+
+ mrid = generate_mr_mrid(ctx.commits);
+
+ refname = generate_mr_refname(ctx.remote, mrid, ctx.user);
+ if (ref_exists(refname))
+ die(_("merge request '%s' already exists"), refname);
+
+ hdr = generate_mr_hdr(mrid, &ctx);
+
+ generate_mr_msg(&ctx.msg, 0);
+
+ head = create_mr_head(ctx.commits, hdr);
+
+ tag = create_mr_tag(&ctx.msg, head, mrid);
+
+ create_mr_ref(refname, tag);
+
+ free((void *)mrid);
+ free((void *)refname);
+ free((void *)hdr);
+ release_create_ctx(&ctx);
+
+ return 0;
+}