request-pull: rewrite to C
[git] / builtin / request-pull.c
1 #include "cache.h"
2 #include "builtin.h"
3 #include "parse-options.h"
4 #include "revision.h"
5 #include "refs.h"
6 #include "remote.h"
7 #include "transport.h"
8 #include "branch.h"
9 #include "shortlog.h"
10 #include "diff.h"
11 #include "log-tree.h"
12
13 static const char *tag_name;
14
15 static const char *const pull_request_usage[] = {
16         N_("git request-pull [options] start url [end]"),
17         NULL
18 };
19
20 static int describe_cb(const char *name, const unsigned char *sha1, int flags, void *cb_data)
21 {
22         unsigned char peel_sha1[20];
23         if (prefixcmp(name, "refs/tags/"))
24                 return 0;
25         peel_ref(name, peel_sha1);
26         if (hashcmp(peel_sha1, cb_data))
27                 return 0;
28         tag_name = skip_prefix(name, "refs/tags/");
29         return 1;
30 }
31
32 const char *describe(const char *head)
33 {
34         unsigned char sha1[20];
35         get_sha1(head, sha1);
36         for_each_ref(describe_cb, sha1);
37         return tag_name;
38 }
39
40 const char *abbr(const char *name)
41 {
42         return name + (
43                         !prefixcmp(name, "refs/heads/") ? 11 :
44                         !prefixcmp(name, "refs/tags/") ? 5 :
45                         0);
46 }
47
48 const char *get_ref(struct transport *transport, const char *head_ref, const unsigned char *head_id)
49 {
50         const struct ref *refs, *e;
51         const char *found = NULL;
52         int deref;
53
54         refs = transport_get_remote_refs(transport);
55
56         for (e = refs; e; e = e->next) {
57                 if (hashcmp(e->old_sha1, head_id))
58                         continue;
59
60                 deref = !suffixcmp(e->name, "^{}");
61                 found = abbr(e->name);
62                 if (deref && tag_name && !prefixcmp(e->name + 10, tag_name))
63                         break;
64                 if (head_ref && !strcmp(e->name, head_ref))
65                         break;
66         }
67         if (!found)
68                 return NULL;
69         return xstrndup(found, strlen(found) - (deref ? 3 : 0));
70 }
71
72 static const char *show_ident_date(const struct ident_split *ident,
73                                    enum date_mode mode)
74 {
75         unsigned long date = 0;
76         int tz = 0;
77
78         if (ident->date_begin && ident->date_end)
79                 date = strtoul(ident->date_begin, NULL, 10);
80         if (ident->tz_begin && ident->tz_end)
81                 tz = strtol(ident->tz_begin, NULL, 10);
82         return show_date(date, tz, mode);
83 }
84
85 static void parse_buffer(const char *buffer, const char **summary, const char **date)
86 {
87         struct strbuf subject = STRBUF_INIT;
88         struct ident_split s;
89         const char *c, *e;
90
91         c = strstr(buffer, "\ncommitter ") + 11;
92         e = strchr(c, '\n');
93         if (!split_ident_line(&s, c, e - c))
94                 *date = show_ident_date(&s, DATE_ISO8601);
95
96         c = strstr(c, "\n\n") + 2;
97         format_subject(&subject, c, " ");
98         *summary = strbuf_detach(&subject, NULL);
99 }
100
101 static void show_shortlog(const char *base, const char *head)
102 {
103         struct commit *commit;
104         struct rev_info revs;
105         struct shortlog log;
106         const char *args[3];
107         struct strbuf tmp = STRBUF_INIT;
108
109         strbuf_addf(&tmp, "^%s", base);
110
111         args[1] = tmp.buf;
112         args[2] = head;
113
114         init_revisions(&revs, NULL);
115         setup_revisions(3, args, &revs, NULL);
116
117         strbuf_release(&tmp);
118
119         shortlog_init(&log);
120         prepare_revision_walk(&revs);
121         while ((commit = get_revision(&revs)))
122                 shortlog_add_commit(&log, commit);
123         shortlog_output(&log);
124 }
125
126 static void show_diff(int patch, const unsigned char *base, const unsigned char *head)
127 {
128         struct rev_info revs;
129         const char *args[3];
130         struct strbuf tmp = STRBUF_INIT;
131
132         strbuf_addf(&tmp, "^%s", sha1_to_hex(base));
133
134         args[1] = tmp.buf;
135         args[2] = sha1_to_hex(head);
136
137         init_revisions(&revs, NULL);
138         setup_revisions(3, args, &revs, NULL);
139         revs.diffopt.stat_width = -1;
140         revs.diffopt.stat_graph_width = -1;
141         revs.diffopt.output_format = patch ? DIFF_FORMAT_PATCH : DIFF_FORMAT_DIFFSTAT;
142         revs.diffopt.output_format |= DIFF_FORMAT_SUMMARY;
143         revs.diffopt.detect_rename = DIFF_DETECT_RENAME;
144         revs.diffopt.flags |= DIFF_OPT_RECURSIVE;
145
146         strbuf_release(&tmp);
147         diff_tree_sha1(base, head, "", &revs.diffopt);
148         log_tree_diff_flush(&revs);
149 }
150
151 int cmd_request_pull(int argc, const char **argv, const char *prefix)
152 {
153         int patch;
154         const char *base, *url, *head;
155         char *head_ref;
156         char *branch_name = NULL;
157         struct strbuf branch_desc = STRBUF_INIT;
158         const char *ref;
159         int status = 0;
160         unsigned char head_id[20], base_id[20];
161         unsigned char *merge_base_id;
162         struct commit *base_commit, *head_commit, *merge_base_commit;
163         struct commit_list *merge_bases;
164         const char *merge_base_summary, *merge_base_date;
165         const char *head_summary, *head_date;
166         struct remote *remote;
167         struct transport *transport;
168
169         const struct option options[] = {
170                 OPT_BOOL('p', NULL, &patch, N_("show patch text as well")),
171                 OPT_END(),
172         };
173
174         argc = parse_options(argc, argv, prefix, options, pull_request_usage, 0);
175
176         base = argv[0];
177         url = argv[1];
178         head = argv[2] ? argv[2] : "HEAD";
179         status = 0;
180
181         if (!base || !url)
182                 usage_with_options(pull_request_usage, options);
183
184         if (dwim_ref(head, strlen(head), head_id, &head_ref) == 1) {
185                 if (!prefixcmp(head_ref, "refs/heads/")) {
186                         branch_name = head_ref + 11;
187                         if (read_branch_desc(&branch_desc, branch_name) || !branch_desc.len)
188                                 branch_name = NULL;
189                 }
190         }
191
192         describe(head);
193
194         get_sha1(base, base_id);
195         base_commit = lookup_commit_reference(base_id);
196         if (!base_commit)
197                 die("Not a valid revision: %s", base);
198
199         head_commit = lookup_commit_reference(head_id);
200         if (!head_commit)
201                 die("Not a valid revision: %s", head);
202
203         merge_bases = get_merge_bases(base_commit, head_commit, 0);
204         if (!merge_bases)
205                 die("No commits in common between %s and %s", base, head);
206
207         merge_base_commit = merge_bases->item;
208         merge_base_id = merge_base_commit->object.sha1;
209
210         remote = remote_get(url);
211         url = remote->url[0];
212         transport = transport_get(remote, url);
213         ref = get_ref(transport, strcmp(head_ref, "HEAD") ? head_ref : NULL, head_id);
214         transport_disconnect(transport);
215
216         parse_buffer(merge_base_commit->buffer, &merge_base_summary, &merge_base_date);
217         parse_buffer(head_commit->buffer, &head_summary, &head_date);
218
219         printf("The following changes since commit %s:\n"
220                         "\n"
221                         "  %s (%s)\n"
222                         "\n"
223                         "are available in the git repository at:\n"
224                         "\n",
225                         sha1_to_hex(merge_base_id), merge_base_summary, merge_base_date);
226         printf("  %s", url);
227         if (ref)
228                 printf(" %s", ref);
229         printf("\n");
230         printf("\n"
231                         "for you to fetch changes up to %s:\n"
232                         "\n"
233                         "  %s (%s)\n"
234                         "\n"
235                         "----------------------------------------------------------------\n",
236                         sha1_to_hex(head_id), head_summary, head_date);
237
238         if (branch_name)
239                 printf("(from the branch description for %s local branch)\n\n%s\n", branch_name, branch_desc.buf);
240
241         if (tag_name) {
242                 void *buffer;
243                 enum object_type type;
244                 unsigned long size;
245                 unsigned char sha1[20];
246                 const char *begin, *end;
247
248                 if (!ref || prefixcmp(ref, "tags/") || strcmp(ref + 5, tag_name)) {
249                         fprintf(stderr, "warn: You locally have %s but it does not (yet)\n", tag_name);
250                         fprintf(stderr, "warn: appear to be at %s\n", url);
251                         fprintf(stderr, "warn: Do you want to push it there, perhaps?\n");
252                 }
253                 get_sha1(tag_name, sha1);
254                 buffer = read_sha1_file(sha1, &type, &size);
255                 begin = strstr(buffer, "\n\n") + 2;
256                 end = strstr(begin, "-----BEGIN PGP ");
257                 if (!end)
258                         end = begin + strlen(begin);
259                 printf("%.*s\n", (int)(end - begin), begin);
260         }
261
262         if (branch_name || tag_name)
263                 puts("----------------------------------------------------------------");
264
265         show_shortlog(base, head);
266         show_diff(patch, merge_base_id, head_id);
267
268         if (!ref) {
269                 fprintf(stderr, "warn: No branch of %s is at:\n", url);
270                 fprintf(stderr, "warn:   %s: %s\n", find_unique_abbrev(head_id, DEFAULT_ABBREV), head_summary);
271                 fprintf(stderr, "warn: Are you sure you pushed '%s' there?\n", head);
272                 status = 1;
273         }
274         return status;
275 }