]> gitweb.ps.run Git - ps-cgit/blob - ui-diff.c
Add support for 'noheader' option
[ps-cgit] / ui-diff.c
1 /* ui-diff.c: show diff between two blobs
2  *
3  * Copyright (C) 2006 Lars Hjemli
4  *
5  * Licensed under GNU General Public License v2
6  *   (see COPYING for full license text)
7  */
8
9 #include "cgit.h"
10 #include "html.h"
11 #include "ui-shared.h"
12
13 unsigned char old_rev_sha1[20];
14 unsigned char new_rev_sha1[20];
15
16 static int files, slots;
17 static int total_adds, total_rems, max_changes;
18 static int lines_added, lines_removed;
19
20 static struct fileinfo {
21         char status;
22         unsigned char old_sha1[20];
23         unsigned char new_sha1[20];
24         unsigned short old_mode;
25         unsigned short new_mode;
26         char *old_path;
27         char *new_path;
28         unsigned int added;
29         unsigned int removed;
30         unsigned long old_size;
31         unsigned long new_size;
32         int binary:1;
33 } *items;
34
35
36 static void print_fileinfo(struct fileinfo *info)
37 {
38         char *class;
39
40         switch (info->status) {
41         case DIFF_STATUS_ADDED:
42                 class = "add";
43                 break;
44         case DIFF_STATUS_COPIED:
45                 class = "cpy";
46                 break;
47         case DIFF_STATUS_DELETED:
48                 class = "del";
49                 break;
50         case DIFF_STATUS_MODIFIED:
51                 class = "upd";
52                 break;
53         case DIFF_STATUS_RENAMED:
54                 class = "mov";
55                 break;
56         case DIFF_STATUS_TYPE_CHANGED:
57                 class = "typ";
58                 break;
59         case DIFF_STATUS_UNKNOWN:
60                 class = "unk";
61                 break;
62         case DIFF_STATUS_UNMERGED:
63                 class = "stg";
64                 break;
65         default:
66                 die("bug: unhandled diff status %c", info->status);
67         }
68
69         html("<tr>");
70         htmlf("<td class='mode'>");
71         if (is_null_sha1(info->new_sha1)) {
72                 cgit_print_filemode(info->old_mode);
73         } else {
74                 cgit_print_filemode(info->new_mode);
75         }
76
77         if (info->old_mode != info->new_mode &&
78             !is_null_sha1(info->old_sha1) &&
79             !is_null_sha1(info->new_sha1)) {
80                 html("<span class='modechange'>[");
81                 cgit_print_filemode(info->old_mode);
82                 html("]</span>");
83         }
84         htmlf("</td><td class='%s'>", class);
85         cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86                        ctx.qry.sha2, info->new_path);
87         if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88                 htmlf(" (%s from %s)",
89                       info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
90                       info->old_path);
91         html("</td><td class='right'>");
92         if (info->binary) {
93                 htmlf("bin</td><td class='graph'>%d -> %d bytes",
94                       info->old_size, info->new_size);
95                 return;
96         }
97         htmlf("%d", info->added + info->removed);
98         html("</td><td class='graph'>");
99         htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
100         htmlf("<td class='add' style='width: %.1f%%;'/>",
101               info->added * 100.0 / max_changes);
102         htmlf("<td class='rem' style='width: %.1f%%;'/>",
103               info->removed * 100.0 / max_changes);
104         htmlf("<td class='none' style='width: %.1f%%;'/>",
105               (max_changes - info->removed - info->added) * 100.0 / max_changes);
106         html("</tr></table></td></tr>\n");
107 }
108
109 static void count_diff_lines(char *line, int len)
110 {
111         if (line && (len > 0)) {
112                 if (line[0] == '+')
113                         lines_added++;
114                 else if (line[0] == '-')
115                         lines_removed++;
116         }
117 }
118
119 static void inspect_filepair(struct diff_filepair *pair)
120 {
121         int binary = 0;
122         unsigned long old_size = 0;
123         unsigned long new_size = 0;
124         files++;
125         lines_added = 0;
126         lines_removed = 0;
127         cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
128                         &binary, count_diff_lines);
129         if (files >= slots) {
130                 if (slots == 0)
131                         slots = 4;
132                 else
133                         slots = slots * 2;
134                 items = xrealloc(items, slots * sizeof(struct fileinfo));
135         }
136         items[files-1].status = pair->status;
137         hashcpy(items[files-1].old_sha1, pair->one->sha1);
138         hashcpy(items[files-1].new_sha1, pair->two->sha1);
139         items[files-1].old_mode = pair->one->mode;
140         items[files-1].new_mode = pair->two->mode;
141         items[files-1].old_path = xstrdup(pair->one->path);
142         items[files-1].new_path = xstrdup(pair->two->path);
143         items[files-1].added = lines_added;
144         items[files-1].removed = lines_removed;
145         items[files-1].old_size = old_size;
146         items[files-1].new_size = new_size;
147         items[files-1].binary = binary;
148         if (lines_added + lines_removed > max_changes)
149                 max_changes = lines_added + lines_removed;
150         total_adds += lines_added;
151         total_rems += lines_removed;
152 }
153
154 void cgit_print_diffstat(const unsigned char *old_sha1,
155                          const unsigned char *new_sha1)
156 {
157         int i;
158
159         html("<div class='diffstat-header'>");
160         cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
161                        ctx.qry.sha2, NULL);
162         html("</div>");
163         html("<table summary='diffstat' class='diffstat'>");
164         max_changes = 0;
165         cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
166         for(i = 0; i<files; i++)
167                 print_fileinfo(&items[i]);
168         html("</table>");
169         html("<div class='diffstat-summary'>");
170         htmlf("%d files changed, %d insertions, %d deletions",
171               files, total_adds, total_rems);
172         html("</div>");
173 }
174
175
176 /*
177  * print a single line returned from xdiff
178  */
179 static void print_line(char *line, int len)
180 {
181         char *class = "ctx";
182         char c = line[len-1];
183
184         if (line[0] == '+')
185                 class = "add";
186         else if (line[0] == '-')
187                 class = "del";
188         else if (line[0] == '@')
189                 class = "hunk";
190
191         htmlf("<div class='%s'>", class);
192         line[len-1] = '\0';
193         html_txt(line);
194         html("</div>");
195         line[len-1] = c;
196 }
197
198 static void header(unsigned char *sha1, char *path1, int mode1,
199                    unsigned char *sha2, char *path2, int mode2)
200 {
201         char *abbrev1, *abbrev2;
202         int subproject;
203
204         subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
205         html("<div class='head'>");
206         html("diff --git a/");
207         html_txt(path1);
208         html(" b/");
209         html_txt(path2);
210
211         if (is_null_sha1(sha1))
212                 path1 = "dev/null";
213         if (is_null_sha1(sha2))
214                 path2 = "dev/null";
215
216         if (mode1 == 0)
217                 htmlf("<br/>new file mode %.6o", mode2);
218
219         if (mode2 == 0)
220                 htmlf("<br/>deleted file mode %.6o", mode1);
221
222         if (!subproject) {
223                 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
224                 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
225                 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
226                 free(abbrev1);
227                 free(abbrev2);
228                 if (mode1 != 0 && mode2 != 0) {
229                         htmlf(" %.6o", mode1);
230                         if (mode2 != mode1)
231                                 htmlf("..%.6o", mode2);
232                 }
233                 html("<br/>--- a/");
234                 if (mode1 != 0)
235                         cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
236                                        sha1_to_hex(old_rev_sha1), path1);
237                 else
238                         html_txt(path1);
239                 html("<br/>+++ b/");
240                 if (mode2 != 0)
241                         cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
242                                        sha1_to_hex(new_rev_sha1), path2);
243                 else
244                         html_txt(path2);
245         }
246         html("</div>");
247 }
248
249 static void filepair_cb(struct diff_filepair *pair)
250 {
251         unsigned long old_size = 0;
252         unsigned long new_size = 0;
253         int binary = 0;
254
255         header(pair->one->sha1, pair->one->path, pair->one->mode,
256                pair->two->sha1, pair->two->path, pair->two->mode);
257         if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258                 if (S_ISGITLINK(pair->one->mode))
259                         print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260                 if (S_ISGITLINK(pair->two->mode))
261                         print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
262                 return;
263         }
264         if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 
265                             &new_size, &binary, print_line))
266                 cgit_print_error("Error running diff");
267         if (binary)
268                 html("Binary files differ");
269 }
270
271 void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
272 {
273         enum object_type type;
274         unsigned long size;
275         struct commit *commit, *commit2;
276
277         if (!new_rev)
278                 new_rev = ctx.qry.head;
279         get_sha1(new_rev, new_rev_sha1);
280         type = sha1_object_info(new_rev_sha1, &size);
281         if (type == OBJ_BAD) {
282                 cgit_print_error(fmt("Bad object name: %s", new_rev));
283                 return;
284         }
285         commit = lookup_commit_reference(new_rev_sha1);
286         if (!commit || parse_commit(commit))
287                 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
288
289         if (old_rev)
290                 get_sha1(old_rev, old_rev_sha1);
291         else if (commit->parents && commit->parents->item)
292                 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
293         else
294                 hashclr(old_rev_sha1);
295
296         if (!is_null_sha1(old_rev_sha1)) {
297                 type = sha1_object_info(old_rev_sha1, &size);
298                 if (type == OBJ_BAD) {
299                         cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
300                         return;
301                 }
302                 commit2 = lookup_commit_reference(old_rev_sha1);
303                 if (!commit2 || parse_commit(commit2))
304                         cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305         }
306         cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307
308         html("<table summary='diff' class='diff'>");
309         html("<tr><td>");
310         cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
311         html("</td></tr>");
312         html("</table>");
313 }