From: Frantisek Hrbata Date: Fri, 13 May 2022 15:37:43 +0000 (+0200) Subject: initial commit X-Git-Url: http://www.hrbata.com/gitweb/?a=commitdiff_plain;h=18d9ff6caaf7f7cc985922037ddb2be882d63757;p=gimrr_c.git initial commit Signed-off-by: Frantisek Hrbata --- 18d9ff6caaf7f7cc985922037ddb2be882d63757 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..08321a5 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +all: gimrr$(X) + +GIT_DIR = git +GIT_URL = https://github.com/git/git.git +GIT_LNK = $(GIT_DIR)/$(GIMRR_GIT_DIR) +GIT_MAK = $(GIT_DIR)/makefile +GIMRR_MAK = gimrr.mak +GIMRR_GIT_DIR = contrib/gimrr +GIMRR_SRC_DIR = src + +GIT_DIR_EXISTS := $(shell git -C $(GIT_DIR)/.git rev-parse --is-inside-git-dir 2>/dev/null) + +# make sure git repo is always available +ifneq ($(GIT_DIR_EXISTS),true) +foo := $(shell git clone --depth 1 $(GIT_URL) $(GIT_DIR)) +endif + +CSCOPE_SOURCES := $(shell find . $(GIT_DIR) \ + \( -name .git -type d -prune \) -o \ + \( -name '*.[ch]' -type f -print \)) + +$(GIT_LNK): + @ln -s $(CURDIR)/$(GIMRR_SRC_DIR) $@ && \ + sed -i '/^.PHONY: objects/i include $(GIMRR_GIT_DIR)/$(GIMRR_MAK)\n' \ + $(GIT_DIR)/Makefile + +gimrr$(X): FORCE | $(GIT_LNK) + $(MAKE) -C $(GIT_DIR) $(GIMRR_GIT_DIR)/$@ + +clean: | $(GIT_LNK) + $(RM) $(GIMRR_SRC_DIR)/gimrr$(X) cscope.out + $(MAKE) -C $(GIT_DIR) clean + +cscope.out: $(CSCOPE_SOURCES) + echo $? | xargs cscope -b + +cscope: cscope.out + +FORCE: +.PHONY: FORCE clean + +test: + $(MAKE) -C $(GIT_DIR) XXX diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..941302d --- /dev/null +++ b/src/common.c @@ -0,0 +1,142 @@ +#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 diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..578f395 --- /dev/null +++ b/src/common.h @@ -0,0 +1,8 @@ +#ifndef COMMON_H +#define COMMON_H + +#include "gimrr.h" + +const char *lookup_mrid_by_name(const char *name); + +#endif diff --git a/src/create.c b/src/create.c new file mode 100644 index 0000000..56f851b --- /dev/null +++ b/src/create.c @@ -0,0 +1,640 @@ +#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 []"), + 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; +} diff --git a/src/gimrr.c b/src/gimrr.c new file mode 100644 index 0000000..1e6fc15 --- /dev/null +++ b/src/gimrr.c @@ -0,0 +1,32 @@ +#include +#include "gimrr.h" + +static const char * const gimrr_usage[] = { + N_("gimrr mr list"), + N_("gimrr mr create [ []]"), + NULL +}; + +int cmd_main(int argc, const char *argv[]) +{ + const char *prefix; + struct option options[] = { + OPT_END() + }; + + prefix = setup_git_directory(); + + argc = parse_options(argc, argv, prefix, options, gimrr_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc < 1 || !strcmp(argv[0], "list")) + return cmd_list(argc, argv, prefix); + else if (!strcmp(argv[0], "create")) + return cmd_create(argc, argv, prefix); + else + error(_("unknown subcommand: %s"), argv[0]); + + usage_with_options(gimrr_usage, options); + + return 0; +} diff --git a/src/gimrr.h b/src/gimrr.h new file mode 100644 index 0000000..082d09b --- /dev/null +++ b/src/gimrr.h @@ -0,0 +1,18 @@ +#ifndef MR_H +#define MR_H + +#include "remote.h" +#include "refs.h" +#include "revision.h" +#include "object-store.h" +#include "config.h" +#include "commit-reach.h" +#include "tag.h" +#include "parse-options.h" +#include "common.h" +#include "strvec.h" + +int cmd_create(int argc, const char **argv, const char *prefix); +int cmd_list(int argc, const char **argv, const char *prefix); + +#endif diff --git a/src/gimrr.mak b/src/gimrr.mak new file mode 100644 index 0000000..3805ca1 --- /dev/null +++ b/src/gimrr.mak @@ -0,0 +1,10 @@ +GIMRR_GIT_DIR = contrib/gimrr +GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/gimrr.o +GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/create.o +GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/list.o +GIMRR_OBJECTS += $(GIMRR_GIT_DIR)/common.o +OBJECTS += $(GIMRR_OBJECTS) + +$(GIMRR_GIT_DIR)/gimrr$X: $(GIMRR_OBJECTS) GIT-LDFLAGS $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIBS) diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..0b4edf5 --- /dev/null +++ b/src/list.c @@ -0,0 +1,6 @@ +#include "gimrr.h" + +int cmd_list(int argc, const char **argv, const char *prefix) +{ + return 0; +}