1 
2 /*
3  * vpatch - visual front end for wiggle
4  *
5  * "files" Display, lists all files with statistics
6  *    - can hide various lines including subdirectories
7  *      and files without wiggles or conflicts
8  * "diff" display shows merged file with different parts
9  *      in different colours
10  *    - untouched are pale  A_DIM
11  *    - matched/remaining are regular A_NORMAL
12  *    - matched/removed are red/underlined A_UNDERLINE
13  *    - unmatched in file are A_STANDOUT
14  *    - unmatched in patch are A_STANDOUT|A_UNDERLINE ???
15  *    - inserted are inverse/green ?? A_REVERSE
16  *
17  *  The window can be split horizontally or vertically and
18  *  two different views displayed.  They will have different
19  *  parts missing
20  *
21  *  So a display of NORMAL, underline, standout|underline reverse
22  *   should show a normal patch.
23  *
24  */
25 
26 #include "wiggle.h"
27 #include <malloc.h>
28 #include <string.h>
29 #include <curses.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <signal.h>
33 
34 #define assert(x) do { if (!(x)) abort(); } while (0)
35 
36 struct plist {
37 	char *file;
38 	unsigned int start, end;
39 	int parent;
40 	int next, prev, last;
41 	int open;
42 	int chunks, wiggles, conflicts;
43 };
44 
patch_add_file(struct plist * pl,int * np,char * file,unsigned int start,unsigned int end)45 struct plist *patch_add_file(struct plist *pl, int *np, char *file,
46 	       unsigned int start, unsigned int end)
47 {
48 	/* size of pl is 0, 16, n^2 */
49 	int n = *np;
50 	int asize;
51 
52 /*	printf("adding %s at %d: %u %u\n", file, n, start, end); */
53 	if (n==0) asize = 0;
54 	else if (n<=16) asize = 16;
55 	else if ((n&(n-1))==0) asize = n;
56 	else asize = n+1; /* not accurate, but not too large */
57 	if (asize <= n) {
58 		/* need to extend array */
59 		struct plist *npl;
60 		if (asize < 16) asize = 16;
61 		else asize += asize;
62 		npl = realloc(pl, asize * sizeof(struct plist));
63 		if (!npl) {
64 			fprintf(stderr, "malloc failed - skipping %s\n", file);
65 			return pl;
66 		}
67 		pl = npl;
68 	}
69 	pl[n].file = file;
70 	pl[n].start = start;
71 	pl[n].end = end;
72 	pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
73 	pl[n].chunks = pl[n].wiggles = pl[n].conflicts = 0;
74 	pl[n].open = 1;
75 	*np = n+1;
76 	return pl;
77 }
78 
79 
80 
parse_patch(FILE * f,FILE * of,int * np)81 struct plist *parse_patch(FILE *f, FILE *of, int *np)
82 {
83 	/* read a multi-file patch from 'f' and record relevant
84 	 * details in a plist.
85 	 * if 'of' >= 0, fd might not be seekable so we write
86 	 * to 'of' and use lseek on 'of' to determine position
87 	 */
88 	struct plist *plist = NULL;
89 
90 	while (!feof(f)) {
91 		/* first, find the start of a patch: "\n+++ "
92 		 * grab the file name and scan to the end of a line
93 		 */
94 		char *target="\n+++ ";
95 		char *target2="\n--- ";
96 		char *pos = target;
97 		int c;
98 		char name[1024];
99 		unsigned start, end;
100 
101 		while (*pos && (c=fgetc(f)) != EOF ) {
102 			if (of) fputc(c, of);
103 			if (c == *pos)
104 				pos++;
105 			else pos = target;
106 		}
107 		if (c == EOF)
108 			break;
109 		assert(c == ' ');
110 		/* now read a file name */
111 		pos = name;
112 		while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' &&
113 		       pos - name < 1023) {
114 			*pos++ = c;
115 			if (of) fputc(c, of);
116 		}
117 		*pos = 0;
118 		if (c == EOF)
119 			break;
120 		if (of) fputc(c, of);
121 		while (c != '\n' && (c=fgetc(f)) != EOF) {
122 			if (of) fputc(c, of);
123 		}
124 		start = of ? ftell(of) : ftell(f);
125 
126 		if (c == EOF) break;
127 
128 		/* now skip to end - "\n--- " */
129 		pos = target2+1;
130 
131 		while (*pos && (c=fgetc(f)) != EOF) {
132 			if (of) fputc(c, of);
133 			if (c == *pos)
134 				pos++;
135 			else pos = target2;
136 		}
137 		if (pos > target2) {
138 			end = of ? ftell(of) : ftell(f);
139 			end -= (pos - target2) - 1;
140 			plist = patch_add_file(plist, np,
141 					       strdup(name), start, end);
142 		}
143 	}
144 	return plist;
145 }
die()146 void die()
147 {
148 	fprintf(stderr,"vpatch: fatal error\n");
149 	abort();
150 	exit(3);
151 }
152 
153 
load_segment(FILE * f,unsigned int start,unsigned int end)154 static struct stream load_segment(FILE *f,
155 				  unsigned int start, unsigned int end)
156 {
157 	struct stream s;
158 	s.len = end - start;
159 	s.body = malloc(s.len);
160 	if (s.body) {
161 		fseek(f, start, 0);
162 		if (fread(s.body, 1, s.len, f) != s.len) {
163 			free(s.body);
164 			s.body = NULL;
165 		}
166 	} else
167 		die();
168 	return s;
169 }
170 
171 
catch(int sig)172 void catch(int sig)
173 {
174 	if (sig == SIGINT) {
175 		signal(sig, catch);
176 		return;
177 	}
178 	nocbreak();nl();endwin();
179 	printf("Died on signal %d\n", sig);
180 	exit(2);
181 }
182 
pl_cmp(const void * av,const void * bv)183 int pl_cmp(const void *av, const void *bv)
184 {
185 	const struct plist *a = av;
186 	const struct plist *b = bv;
187 	return strcmp(a->file, b->file);
188 }
189 
common_depth(char * a,char * b)190 int common_depth(char *a, char *b)
191 {
192 	/* find number of patch segments that these two have
193 	 * in common
194 	 */
195 	int depth = 0;
196 	while(1) {
197 		char *c;
198 		int al, bl;
199 		c = strchr(a, '/');
200 		if (c) al = c-a; else al = strlen(a);
201 		c = strchr(b, '/');
202 		if (c) bl = c-b; else bl = strlen(b);
203 		if (al == 0 || al != bl || strncmp(a,b,al) != 0)
204 			return depth;
205 		a+= al;
206 		while (*a=='/') a++;
207 		b+= bl;
208 		while(*b=='/') b++;
209 
210 		depth++;
211 	}
212 }
213 
add_dir(struct plist * pl,int * np,char * file,char * curr)214 struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
215 {
216 	/* any parent of file that is not a parent of curr
217 	 * needs to be added to pl
218 	 */
219 	int d = common_depth(file, curr);
220 	char *buf = curr;
221 	while (d) {
222 		char *c = strchr(file, '/');
223 		int l;
224 		if (c) l = c-file; else l = strlen(file);
225 		file += l;
226 		curr += l;
227 		while (*file == '/') file++;
228 		while (*curr == '/') curr++;
229 		d--;
230 	}
231 	while (*file) {
232 		if (curr > buf && curr[-1] != '/')
233 			*curr++ = '/';
234 		while (*file && *file != '/')
235 			*curr++ = *file++;
236 		while (*file == '/') *file++;
237 		*curr = '\0';
238 		if (*file)
239 			pl = patch_add_file(pl, np, strdup(buf),
240 					    0, 0);
241 	}
242 	return pl;
243 }
244 
sort_patches(struct plist * pl,int * np)245 struct plist *sort_patches(struct plist *pl, int *np)
246 {
247 	/* sort the patches, add directory names, and re-sort */
248 	char curr[1024];
249 	char *prev;
250 	int parents[100];
251 	int prevnode[100];
252 	int i, n;
253 	qsort(pl, *np, sizeof(struct plist), pl_cmp);
254 	curr[0] = 0;
255 	n = *np;
256 	for (i=0; i<n; i++)
257 		pl = add_dir(pl, np, pl[i].file, curr);
258 
259 	qsort(pl, *np, sizeof(struct plist), pl_cmp);
260 
261 	/* array is now stable, so set up parent pointers */
262 	n = *np;
263 	curr[0] = 0;
264 	prevnode[0] = -1;
265 	prev = "";
266 	for (i=0; i<n; i++) {
267 		int d = common_depth(prev, pl[i].file);
268 		if (d == 0)
269 			pl[i].parent = -1;
270 		else {
271 			pl[i].parent = parents[d-1];
272 			pl[pl[i].parent].last = i;
273 		}
274 		pl[i].prev = prevnode[d];
275 		if (pl[i].prev > -1)
276 			pl[pl[i].prev].next = i;
277 		prev = pl[i].file;
278 		parents[d] = i;
279 		prevnode[d] = i;
280 		prevnode[d+1] = -1;
281 	}
282 	return pl;
283 }
284 
get_prev(int pos,struct plist * pl,int n)285 int get_prev(int pos, struct plist *pl, int n)
286 {
287 	if (pos == -1) return pos;
288 	if (pl[pos].prev == -1)
289 		return pl[pos].parent;
290 	pos = pl[pos].prev;
291 	while (pl[pos].open &&
292 	       pl[pos].last >= 0)
293 		pos = pl[pos].last;
294 	return pos;
295 }
296 
get_next(int pos,struct plist * pl,int n)297 int get_next(int pos, struct plist *pl, int n)
298 {
299 	if (pos == -1) return pos;
300 	if (pl[pos].open) {
301 		if (pos +1 < n)
302 			return pos+1;
303 		else
304 			return -1;
305 	}
306 	while (pos >= 0 && pl[pos].next == -1)
307 		pos = pl[pos].parent;
308 	if (pos >= 0)
309 		pos = pl[pos].next;
310 	return pos;
311 }
312 
draw_one(int row,struct plist * pl)313 void draw_one(int row, struct plist *pl)
314 {
315 	char hdr[10];
316 	hdr[0] = 0;
317 
318 	if (pl == NULL) {
319 		move(row,0);
320 		clrtoeol();
321 		return;
322 	}
323 	if (pl->chunks > 99)
324 		strcpy(hdr, "XX");
325 	else sprintf(hdr, "%02d", pl->chunks);
326 	if (pl->wiggles > 99)
327 		strcpy(hdr, " XX");
328 	else sprintf(hdr+2, " %02d", pl->wiggles);
329 	if (pl->conflicts > 99)
330 		strcpy(hdr, " XX");
331 	else sprintf(hdr+5, " %02d ", pl->conflicts);
332 	if (pl->end)
333 		strcpy(hdr+9, "= ");
334 	else if (pl->open)
335 		strcpy(hdr+9, "+ ");
336 	else strcpy(hdr+9, "- ");
337 
338 	mvaddstr(row, 0, hdr);
339 	mvaddstr(row, 11, pl->file);
340 	clrtoeol();
341 }
342 
addword(struct elmnt e)343 void addword(struct elmnt e)
344 {
345 	addnstr(e.start, e.len);
346 }
347 
diff_window(struct plist * p,FILE * f)348 void diff_window(struct plist *p, FILE *f)
349 {
350 	/*
351 	 * I wonder what to display here ....
352 	 */
353 	struct stream s;
354 	struct stream  s1, s2;
355 	struct file f1, f2;
356 	struct csl *csl;
357 	char buf[100];
358 	int ch;
359 	s = load_segment(f, p->start, p->end);
360 	ch = split_patch(s, &s1, &s2);
361 
362 	clear();
363 	sprintf(buf, "Chunk count: %d\n", ch);
364 	mvaddstr(1,1,buf); clrtoeol();
365 
366 
367 	f1 = split_stream(s1, ByWord, 0);
368 	f2 = split_stream(s2, ByWord, 0);
369 
370 	csl = diff(f1, f2);
371 
372 	/* now try to display the diff highlighted */
373 	int sol = 1;
374 	int a=0, b=0;
375 
376 	while(a<f1.elcnt || b < f2.elcnt) {
377 		if (a < csl->a) {
378 			if (sol) {
379 				int a1;
380 				/* if we remove a whole line, output +line,
381 				 * else clear sol and retry
382 				 */
383 				sol = 0;
384 				for (a1=a; a1<csl->a; a1++)
385 					if (f1.list[a1].start[0] == '\n') {
386 						sol = 1;
387 						break;
388 					}
389 				if (sol) {
390 					addch('-');
391 					attron(A_UNDERLINE);
392 					for (; a<csl->a; a++) {
393 						addword(f1.list[a]);
394 						if (f1.list[a].start[0] == '\n') {
395 							a++;
396 							break;
397 						}
398 					}
399 					attroff(A_UNDERLINE);
400 				} else addch('|');
401 			}
402 			if (!sol) {
403 				attron(A_UNDERLINE);
404 				do {
405 					if (sol) {
406 						attroff(A_UNDERLINE);
407 						addch('|');
408 						attron(A_UNDERLINE);
409 					}
410 					addword(f1.list[a]);
411 					sol = (f1.list[a].start[0] == '\n');
412 					a++;
413 				} while (a < csl->a);
414 				attroff(A_UNDERLINE);
415 				if (sol) addch('|');
416 				sol = 0;
417 			}
418 		} else if (b < csl->b) {
419 			if (sol) {
420 				int b1;
421 				sol = 0;
422 				for (b1=b; b1<csl->b; b1++)
423 					if (f2.list[b1].start[0] == '\n') {
424 						sol = 1;
425 						break;
426 					}
427 				if (sol) {
428 					addch('+');
429 					attron(A_BOLD);
430 					for (; b<csl->b; b++) {
431 						addword(f2.list[b]);
432 						if (f2.list[b].start[0] == '\n') {
433 							b++;
434 							break;
435 						}
436 					}
437 					attroff(A_BOLD);
438 				} else addch('|');
439 			}
440 			if (!sol) {
441 				attron(A_BOLD);
442 				do {
443 					if (sol) {
444 						attroff(A_BOLD);
445 						addch('|');
446 						attron(A_BOLD);
447 					}
448 					addword(f2.list[b]);
449 					sol = (f2.list[b].start[0] == '\n');
450 					b++;
451 				} while (b < csl->b);
452 				attroff(A_BOLD);
453 				if (sol) addch('|');
454 				sol = 0;
455 			}
456 		} else {
457 			if (sol) {
458 				int a1;
459 				sol = 0;
460 				for (a1=a; a1<csl->a+csl->len; a1++)
461 					if (f1.list[a1].start[0] == '\n')
462 						sol = 1;
463 				if (sol) {
464 					if (f1.list[a].start[0]) {
465 						addch(' ');
466 						for (; a< csl->a+csl->len; a++,b++) {
467 							addword(f1.list[a]);
468 							if (f1.list[a].start[0]=='\n') {
469 								a++,b++;
470 								break;
471 							}
472 						}
473 					} else {
474 						addstr("SEP\n");
475 						a++; b++;
476 					}
477 				} else addch('|');
478 			}
479 			if (!sol) {
480 				addword(f1.list[a]);
481 				if (f1.list[a].start[0] == '\n')
482 					sol = 1;
483 				a++;
484 				b++;
485 			}
486 			if (a >= csl->a+csl->len)
487 				csl++;
488 		}
489 	}
490 
491 
492 	getch();
493 
494 	free(s1.body);
495 	free(s2.body);
496 	free(f1.list);
497 	free(f2.list);
498 }
499 
main_window(struct plist * pl,int n,FILE * f)500 void main_window(struct plist *pl, int n, FILE *f)
501 {
502 	/* The main window lists all files together with summary information:
503 	 * number of chunks, number of wiggles, number of conflicts.
504 	 * The list is scrollable
505 	 * When a entry is 'selected', we switch to the 'file' window
506 	 * The list can be condensed by removing files with no conflict
507 	 * or no wiggles, or removing subdirectories
508 	 *
509 	 * We record which file in the list is 'current', and which
510 	 * screen line it is on.  We try to keep things stable while
511 	 * moving.
512 	 *
513 	 * Counts are printed before the name using at most 2 digits.
514 	 * Numbers greater than 99 are XX
515 	 * Ch Wi Co File
516 	 * 27 5   1 drivers/md/md.c
517 	 *
518 	 * A directory show the sum in all children.
519 	 *
520 	 * Commands:
521 	 *  select:  enter, space, mouseclick
522 	 *      on file, go to file window
523 	 *      on directory, toggle open
524 	 *  up:  k, p, control-p uparrow
525 	 *      Move to previous open object
526 	 *  down: j, n, control-n, downarrow
527 	 *      Move to next open object
528 	 *
529 	 */
530 	int pos=0; /* position in file */
531 	int row=1; /* position on screen */
532 	int rows; /* size of screen in rows */
533 	int cols;
534 	int tpos, i;
535 	int refresh = 2;
536 	int c;
537 
538 	while(1) {
539 		if (refresh == 2) {
540 			clear();
541 			attron(A_BOLD);
542 			mvaddstr(0,0,"Ch Wi Co Patched Files");
543 			move(2,0);
544 			attroff(A_BOLD);
545 			refresh = 1;
546 		}
547 		if (row <1  || row >= rows)
548 			refresh = 1;
549 		if (refresh) {
550 			refresh = 0;
551 			getmaxyx(stdscr, rows, cols);
552 			if (row >= rows +3)
553 				row = (rows+1)/2;
554 			if (row >= rows)
555 				row = rows-1;
556 			tpos = pos;
557 			for (i=row; i>1; i--) {
558 				tpos = get_prev(tpos, pl, n);
559 				if (tpos == -1) {
560 					row = row - i + 1;
561 					break;
562 				}
563 			}
564 			/* Ok, row and pos could be trustworthy now */
565 			tpos = pos;
566 			for (i=row; i>=1; i--) {
567 				draw_one(i, &pl[tpos]);
568 				tpos = get_prev(tpos, pl, n);
569 			}
570 			tpos = pos;
571 			for (i=row+1; i<rows; i++) {
572 				tpos = get_next(tpos, pl, n);
573 				if (tpos >= 0)
574 					draw_one(i, &pl[tpos]);
575 				else
576 					draw_one(i, NULL);
577 			}
578 		}
579 		move(row, 9);
580 		c = getch();
581 		switch(c) {
582 		case 'j':
583 		case 'n':
584 		case 'N':
585 		case 'N'-64:
586 		case KEY_DOWN:
587 			tpos = get_next(pos, pl, n);
588 			if (tpos >= 0) {
589 				pos = tpos;
590 				row++;
591 			}
592 			break;
593 		case 'k':
594 		case 'p':
595 		case 'P':
596 		case 'P'-64:
597 		case KEY_UP:
598 			tpos = get_prev(pos, pl, n);
599 			if (tpos >= 0) {
600 				pos = tpos;
601 				row--;
602 			}
603 			break;
604 
605 		case ' ':
606 		case 13:
607 			if (pl[pos].end == 0) {
608 				pl[pos].open = ! pl[pos].open;
609 				refresh = 1;
610 			} else {
611 				diff_window(&pl[pos], f);
612 				refresh = 2;
613 			}
614 			break;
615 		case 27: /* escape */
616 		case 'q':
617 			return;
618 		}
619 	}
620 }
621 
622 
main(int argc,char * argv[])623 int main(int argc, char *argv[])
624 {
625 	int n = 0;
626 	FILE *f = NULL;
627 	FILE *in = stdin;
628 	struct plist *pl;
629 
630 	if (argc == 3)
631 		f = fopen(argv[argc-1], "w+");
632 	if (argc >=2)
633 		in = fopen(argv[1], "r");
634 	else {
635 		printf("no arg...\n");
636 		exit(2);
637 	}
638 
639 	pl = parse_patch(in, f, &n);
640 	pl = sort_patches(pl, &n);
641 
642 	if (f) {
643 		fclose(in);
644 		in = f;
645 	}
646 #if 0
647 	int i;
648 	for (i=0; i<n ; i++) {
649 		printf("%3d: %3d %2d/%2d %s\n", i, pl[i].parent, pl[i].prev, pl[i].next, pl[i].file);
650 	}
651 	exit(0);
652 #endif
653 	signal(SIGINT, catch);
654 	signal(SIGQUIT, catch);
655 	signal(SIGBUS, catch);
656 	signal(SIGTERM, catch);
657 	signal(SIGSEGV, catch);
658 
659 	initscr(); cbreak(); noecho();
660 	nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
661 	mousemask(ALL_MOUSE_EVENTS, NULL);
662 
663 	main_window(pl, n, in);
664 
665 	nocbreak();nl();endwin();
666 	return 0;
667 }
668