Signed-off-by: Felipe Contreras <felipe.contreras@xxxxxxxxx> --- Makefile | 3 +- builtin.h | 1 + builtin/request-pull.c | 275 +++++++++++++++++++++++++++++++++++++++++++++++++ git-request-pull.rb | 199 ----------------------------------- git.c | 1 + 5 files changed, 278 insertions(+), 201 deletions(-) create mode 100644 builtin/request-pull.c delete mode 100755 git-request-pull.rb diff --git a/Makefile b/Makefile index 709c087..edbceea 100644 --- a/Makefile +++ b/Makefile @@ -492,9 +492,7 @@ SCRIPT_PYTHON += git-remote-testpy.py SCRIPT_PYTHON += git-p4.py SCRIPT_RUBY += git-rb-setup.rb -SCRIPT_RUBY += git-request-pull.rb -RUBY_PROGRAMS += git-request-pull$X PROGRAMS += $(RUBY_PROGRAMS) NO_INSTALL += git-remote-testgit @@ -983,6 +981,7 @@ BUILTIN_OBJS += builtin/remote.o BUILTIN_OBJS += builtin/remote-ext.o BUILTIN_OBJS += builtin/remote-fd.o BUILTIN_OBJS += builtin/replace.o +BUILTIN_OBJS += builtin/request-pull.o BUILTIN_OBJS += builtin/rerere.o BUILTIN_OBJS += builtin/reset.o BUILTIN_OBJS += builtin/rev-list.o diff --git a/builtin.h b/builtin.h index 8afa2de..7db356e 100644 --- a/builtin.h +++ b/builtin.h @@ -103,6 +103,7 @@ extern int cmd_remote(int argc, const char **argv, const char *prefix); extern int cmd_remote_ext(int argc, const char **argv, const char *prefix); extern int cmd_remote_fd(int argc, const char **argv, const char *prefix); extern int cmd_repo_config(int argc, const char **argv, const char *prefix); +extern int cmd_request_pull(int argc, const char **argv, const char *prefix); extern int cmd_rerere(int argc, const char **argv, const char *prefix); extern int cmd_reset(int argc, const char **argv, const char *prefix); extern int cmd_rev_list(int argc, const char **argv, const char *prefix); diff --git a/builtin/request-pull.c b/builtin/request-pull.c new file mode 100644 index 0000000..a8f7f04 --- /dev/null +++ b/builtin/request-pull.c @@ -0,0 +1,275 @@ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" +#include "revision.h" +#include "refs.h" +#include "remote.h" +#include "transport.h" +#include "branch.h" +#include "shortlog.h" +#include "diff.h" +#include "log-tree.h" + +static const char *tag_name; + +static const char *const pull_request_usage[] = { + N_("git request-pull [options] start url [end]"), + NULL +}; + +static int describe_cb(const char *name, const unsigned char *sha1, int flags, void *cb_data) +{ + unsigned char peel_sha1[20]; + if (prefixcmp(name, "refs/tags/")) + return 0; + peel_ref(name, peel_sha1); + if (hashcmp(peel_sha1, cb_data)) + return 0; + tag_name = skip_prefix(name, "refs/tags/"); + return 1; +} + +const char *describe(const char *head) +{ + unsigned char sha1[20]; + get_sha1(head, sha1); + for_each_ref(describe_cb, sha1); + return tag_name; +} + +const char *abbr(const char *name) +{ + return name + ( + !prefixcmp(name, "refs/heads/") ? 11 : + !prefixcmp(name, "refs/tags/") ? 5 : + 0); +} + +const char *get_ref(struct transport *transport, const char *head_ref, const unsigned char *head_id) +{ + const struct ref *refs, *e; + const char *found = NULL; + int deref; + + refs = transport_get_remote_refs(transport); + + for (e = refs; e; e = e->next) { + if (hashcmp(e->old_sha1, head_id)) + continue; + + deref = !suffixcmp(e->name, "^{}"); + found = abbr(e->name); + if (deref && tag_name && !prefixcmp(e->name + 10, tag_name)) + break; + if (head_ref && !strcmp(e->name, head_ref)) + break; + } + if (!found) + return NULL; + return xstrndup(found, strlen(found) - (deref ? 3 : 0)); +} + +static const char *show_ident_date(const struct ident_split *ident, + enum date_mode mode) +{ + unsigned long date = 0; + int tz = 0; + + if (ident->date_begin && ident->date_end) + date = strtoul(ident->date_begin, NULL, 10); + if (ident->tz_begin && ident->tz_end) + tz = strtol(ident->tz_begin, NULL, 10); + return show_date(date, tz, mode); +} + +static void parse_buffer(const char *buffer, const char **summary, const char **date) +{ + struct strbuf subject = STRBUF_INIT; + struct ident_split s; + const char *c, *e; + + c = strstr(buffer, "\ncommitter ") + 11; + e = strchr(c, '\n'); + if (!split_ident_line(&s, c, e - c)) + *date = show_ident_date(&s, DATE_ISO8601); + + c = strstr(c, "\n\n") + 2; + format_subject(&subject, c, " "); + *summary = strbuf_detach(&subject, NULL); +} + +static void show_shortlog(const char *base, const char *head) +{ + struct commit *commit; + struct rev_info revs; + struct shortlog log; + const char *args[3]; + struct strbuf tmp = STRBUF_INIT; + + strbuf_addf(&tmp, "^%s", base); + + args[1] = tmp.buf; + args[2] = head; + + init_revisions(&revs, NULL); + setup_revisions(3, args, &revs, NULL); + + strbuf_release(&tmp); + + shortlog_init(&log); + prepare_revision_walk(&revs); + while ((commit = get_revision(&revs))) + shortlog_add_commit(&log, commit); + shortlog_output(&log); +} + +static void show_diff(int patch, const unsigned char *base, const unsigned char *head) +{ + struct rev_info revs; + const char *args[3]; + struct strbuf tmp = STRBUF_INIT; + + strbuf_addf(&tmp, "^%s", sha1_to_hex(base)); + + args[1] = tmp.buf; + args[2] = sha1_to_hex(head); + + init_revisions(&revs, NULL); + setup_revisions(3, args, &revs, NULL); + revs.diffopt.stat_width = -1; + revs.diffopt.stat_graph_width = -1; + revs.diffopt.output_format = patch ? DIFF_FORMAT_PATCH : DIFF_FORMAT_DIFFSTAT; + revs.diffopt.output_format |= DIFF_FORMAT_SUMMARY; + revs.diffopt.detect_rename = DIFF_DETECT_RENAME; + revs.diffopt.flags |= DIFF_OPT_RECURSIVE; + + strbuf_release(&tmp); + diff_tree_sha1(base, head, "", &revs.diffopt); + log_tree_diff_flush(&revs); +} + +int cmd_request_pull(int argc, const char **argv, const char *prefix) +{ + int patch; + const char *base, *url, *head; + char *head_ref; + char *branch_name = NULL; + struct strbuf branch_desc = STRBUF_INIT; + const char *ref; + int status = 0; + unsigned char head_id[20], base_id[20]; + unsigned char *merge_base_id; + struct commit *base_commit, *head_commit, *merge_base_commit; + struct commit_list *merge_bases; + const char *merge_base_summary, *merge_base_date; + const char *head_summary, *head_date; + struct remote *remote; + struct transport *transport; + + const struct option options[] = { + OPT_BOOL('p', NULL, &patch, N_("show patch text as well")), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, pull_request_usage, 0); + + base = argv[0]; + url = argv[1]; + head = argv[2] ? argv[2] : "HEAD"; + status = 0; + + if (!base || !url) + usage_with_options(pull_request_usage, options); + + if (dwim_ref(head, strlen(head), head_id, &head_ref) == 1) { + if (!prefixcmp(head_ref, "refs/heads/")) { + branch_name = head_ref + 11; + if (read_branch_desc(&branch_desc, branch_name) || !branch_desc.len) + branch_name = NULL; + } + } + + describe(head); + + get_sha1(base, base_id); + base_commit = lookup_commit_reference(base_id); + if (!base_commit) + die("Not a valid revision: %s", base); + + head_commit = lookup_commit_reference(head_id); + if (!head_commit) + die("Not a valid revision: %s", head); + + merge_bases = get_merge_bases(base_commit, head_commit, 0); + if (!merge_bases) + die("No commits in common between %s and %s", base, head); + + merge_base_commit = merge_bases->item; + merge_base_id = merge_base_commit->object.sha1; + + remote = remote_get(url); + url = remote->url[0]; + transport = transport_get(remote, url); + ref = get_ref(transport, strcmp(head_ref, "HEAD") ? head_ref : NULL, head_id); + transport_disconnect(transport); + + parse_buffer(merge_base_commit->buffer, &merge_base_summary, &merge_base_date); + parse_buffer(head_commit->buffer, &head_summary, &head_date); + + printf("The following changes since commit %s:\n" + "\n" + " %s (%s)\n" + "\n" + "are available in the git repository at:\n" + "\n", + sha1_to_hex(merge_base_id), merge_base_summary, merge_base_date); + printf(" %s", url); + if (ref) + printf(" %s", ref); + printf("\n"); + printf("\n" + "for you to fetch changes up to %s:\n" + "\n" + " %s (%s)\n" + "\n" + "----------------------------------------------------------------\n", + sha1_to_hex(head_id), head_summary, head_date); + + if (branch_name) + printf("(from the branch description for %s local branch)\n\n%s\n", branch_name, branch_desc.buf); + + if (tag_name) { + void *buffer; + enum object_type type; + unsigned long size; + unsigned char sha1[20]; + const char *begin, *end; + + if (!ref || prefixcmp(ref, "tags/") || strcmp(ref + 5, tag_name)) { + fprintf(stderr, "warn: You locally have %s but it does not (yet)\n", tag_name); + fprintf(stderr, "warn: appear to be at %s\n", url); + fprintf(stderr, "warn: Do you want to push it there, perhaps?\n"); + } + get_sha1(tag_name, sha1); + buffer = read_sha1_file(sha1, &type, &size); + begin = strstr(buffer, "\n\n") + 2; + end = strstr(begin, "-----BEGIN PGP "); + if (!end) + end = begin + strlen(begin); + printf("%.*s\n", (int)(end - begin), begin); + } + + if (branch_name || tag_name) + puts("----------------------------------------------------------------"); + + show_shortlog(base, head); + show_diff(patch, merge_base_id, head_id); + + if (!ref) { + fprintf(stderr, "warn: No branch of %s is at:\n", url); + fprintf(stderr, "warn: %s: %s\n", find_unique_abbrev(head_id, DEFAULT_ABBREV), head_summary); + fprintf(stderr, "warn: Are you sure you pushed '%s' there?\n", head); + status = 1; + } + return status; +} diff --git a/git-request-pull.rb b/git-request-pull.rb deleted file mode 100755 index 941ff72..0000000 --- a/git-request-pull.rb +++ /dev/null @@ -1,199 +0,0 @@ -#!git ruby - -require 'date' - -patch = nil - -def usage - puts <<EOF -usage: git request-pull [options] start url [end] - - -p show patch text as well - -EOF - exit 1 -end - -def read_branch_desc(name) - git_config() do |key, value| - return value if key == "branch.#{name}.description" - end - return nil -end - -def describe(rev) - for_each_ref() do |name, sha1, flags| - next unless name.start_with?('refs/tags/') - next unless peel_ref(name) == get_sha1(rev) - return name.skip_prefix('refs/tags/') - end - return nil -end - -def abbr(ref) - if (ref =~ %r{^refs/heads/(.*)} || ref =~ %r{^refs/(tags/.*)}) - return $1 - end - return ref -end - -# $head is the token given from the command line, and $tag_name, if -# exists, is the tag we are going to show the commit information for. -# If that tag exists at the remote and it points at the commit, use it. -# Otherwise, if a branch with the same name as $head exists at the remote -# and their values match, use that instead. -# -# Otherwise find a random ref that matches $head_id. - -def get_ref(transport, head_ref, head_id, tag_name) - found = nil - transport.get_remote_refs().each do |e| - sha1 = e.old_sha1 - next unless sha1 == head_id - ref, deref = e.name.scan(/^(\S+?)(\^\{\})?$/).first - found = abbr(ref) - break if (deref && ref == "refs/tags/#{tag_name}") - break if ref == head_ref - end - return found -end - -def parse_buffer(buffer) - summary = [] - date = msg = nil - header, body = buffer.split("\n\n", 2) - header.each_line do |line| - case line - when /^committer ([^<>]+) <(\S+)> (.+)$/ - date = DateTime.strptime($3, '%s %z') - end - end - body.each_line do |l| - break if (l.strip.empty?) - summary << l.chomp - end - summary = summary.join(' ') - date = date.strftime('%F %T %z') - return [summary, date] -end - -def show_shortlog(base, head) - rev = Git::RevInfo.setup(nil, ['^' + base, head], nil) - shortlog(rev.to_a) -end - -def show_diff(patch, base, head) - rev = Git::RevInfo.setup(nil, ['^' + sha1_to_hex(base), sha1_to_hex(head)], nil) - rev.diffopt.stat_width = -1 - rev.diffopt.stat_graph_width = -1 - rev.diffopt.output_format = patch ? DIFF_FORMAT_PATCH : DIFF_FORMAT_DIFFSTAT - rev.diffopt.output_format |= DIFF_FORMAT_SUMMARY - rev.diffopt.detect_rename = DIFF_DETECT_RENAME - rev.diffopt.flags |= DIFF_OPT_RECURSIVE - - diff_tree_sha1(base, head, "", rev.diffopt) - log_tree_diff_flush(rev) -end - -until ARGV.empty? - case ARGV.first - when '-p' - patch = '-p' - when '--' - ARGV.shift - break - when /^-/ - usage - else - break - end - ARGV.shift -end - -base = ARGV[0] -url = ARGV[1] -head = ARGV[2] || 'HEAD' -branch_name = branch_desc = nil - -usage unless base or url - -_, _, head_ref = dwim_ref(head) - -if head_ref.start_with?('refs/heads') - branch_name = head_ref[11..-1] - branch_desc = read_branch_desc(branch_name) - branch_name = nil if not branch_desc -end - -tag_name = describe(head) - -base_id = get_sha1("#{base}^0") -die "Not a valid revision: #{base}" unless base_id - -head_id = get_sha1("#{head}^0") -die "Not a valid revision: #{head}" unless head_id - -base_commit = Git::Commit.get(base_id) -head_commit = Git::Commit.get(head_id) - -merge_bases = get_merge_bases([base_commit, head_commit], 0); -die "No commits in common between #{base} and #{head}" unless merge_bases - -merge_base_id = merge_bases.first.sha1 -merge_base_commit = Git::Commit.get(merge_base_id) - -remote = remote_get(url) -transport = transport_get(remote, nil) - -ref = get_ref(transport, head_ref != "HEAD" ? head_ref : nil, head_id, tag_name) -url = remote.url.first - -merge_base_summary, merge_base_date = parse_buffer(merge_base_commit.buffer) -head_summary, head_date = parse_buffer(head_commit.buffer) - -puts "The following changes since commit %s: - - %s (%s) - -are available in the git repository at: - -" % [merge_base_commit, merge_base_summary, merge_base_date] -puts " #{url}" + (ref ? " #{ref}" : "") -puts " -for you to fetch changes up to %s: - - %s (%s) - ----------------------------------------------------------------- -" % [head_commit, head_summary, head_date] - -if branch_name - puts "(from the branch description for #{branch_name} local branch)" - puts - puts branch_desc -end - -if tag_name - if ref != "tags/#{tag_name}" - $stderr.puts "warn: You locally have #{tag_name} but it does not (yet)" - $stderr.puts "warn: appear to be at #{url}" - $stderr.puts "warn: Do you want to push it there, perhaps?" - end - buffer, _ = read_sha1_file(get_sha1(tag_name)) - puts buffer.scan(/(?:\n\n)(.+)(?:-----BEGIN PGP )?/m).first - puts -end - -if branch_name || tag_name - puts "----------------------------------------------------------------" -end - -show_shortlog(base, head) -show_diff(patch, merge_base_id, head_id) - -if ! ref - $stderr.puts "warn: No branch of #{url} is at:" - $stderr.puts "warn: %s: %s'" % [find_unique_abbrev(head_id, DEFAULT_ABBREV), head_summary] - $stderr.puts "warn: Are you sure you pushed '#{abbr(head_ref)}' there?" - exit 1 -end diff --git a/git.c b/git.c index 2025f77..d67d3fb 100644 --- a/git.c +++ b/git.c @@ -398,6 +398,7 @@ static void handle_internal_command(int argc, const char **argv) { "remote-fd", cmd_remote_fd }, { "replace", cmd_replace, RUN_SETUP }, { "repo-config", cmd_repo_config, RUN_SETUP_GENTLY }, + { "request-pull", cmd_request_pull, RUN_SETUP_GENTLY }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, { "rev-list", cmd_rev_list, RUN_SETUP }, -- 1.8.4-fc -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html