/[CvsGraph]/cvsgraph/cvsgraph.c
ViewVC logotype

Annotate of /cvsgraph/cvsgraph.c

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.46 - (show annotations)
Sun Aug 29 12:07:05 2004 UTC (13 years, 3 months ago) by bertho
Branch: MAIN
Changes since 1.45: +24 -10 lines
File MIME type: text/plain
- Add patch from Henrik Carlqvist <henca {at} users.SourceForge.net>
	* add option rev_hidenumber for disabling revision number display.
	* add option tag_ignore_merge for disabling merge_from/merge_to display
	  if matched with tag_ignore.
- Add option merge_findall so that merge_from/merge_to tags can have multiple
  matches.
- Fix some whitespace to tab.
1 /*
2 * CvsGraph graphical representation generator of brances and revisions
3 * of a file in cvs/rcs.
4 *
5 * Copyright (C) 2001,2002,2003 B. Stultiens
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22 #include "config.h"
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <unistd.h>
28 #include <string.h>
29 #include <assert.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #ifdef HAVE_SYS_WAIT_H
33 # include <sys/wait.h>
34 #endif
35 #include <fcntl.h>
36 #include <regex.h>
37 #include <errno.h>
38 #include <ctype.h>
39 #include <time.h>
40 #include <limits.h>
41 #include <regex.h>
42 #include <math.h>
43
44 #ifdef HAVE_GETOPT_H
45 # include <getopt.h>
46 #endif
47
48 #include <gd.h>
49 #include <gdfontt.h>
50
51 #include "cvsgraph.h"
52 #include "utils.h"
53 #include "readconf.h"
54 #include "rcs.h"
55
56 #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
57 # error No image output format available. Check libgd
58 #endif
59
60
61 /*#define DEBUG 1*/
62 /*#define NOGDFILL 1*/
63 /*#define DEBUG_IMAGEMAP 1*/
64
65 #define LOOPSAFEGUARD 10000 /* Max itterations in possible infinite loops */
66
67 #ifndef MAX
68 # define MAX(a,b) ((a) > (b) ? (a) : (b))
69 #endif
70
71 #ifndef MIN
72 # define MIN(a,b) ((a) < (b) ? (a) : (b))
73 #endif
74
75 #define ALIGN_HL 0x00
76 #define ALIGN_HC 0x01
77 #define ALIGN_HR 0x02
78 #define ALIGN_HX 0x0f
79 #define ALIGN_VT 0x00
80 #define ALIGN_VC 0x10
81 #define ALIGN_VB 0x20
82 #define ALIGN_VX 0xf0
83
84 #ifndef M_PI /* math.h should have defined this */
85 # define M_PI 3.14159265358979323846
86 #endif
87 #define ROUND(f) ((f >= 0.0)?((int)(f + 0.5)):((int)(f - 0.5)))
88
89 #define ARROW_LENGTH 12 /* Default arrow dimensions */
90 #define ARROW_WIDTH 3
91
92 /*
93 **************************************************************************
94 * Globals
95 **************************************************************************
96 */
97
98 config_t conf;
99 int debuglevel;
100
101 static color_t white_color = {255, 255, 255, 0};
102 static color_t black_color = {0, 0, 0, 0};
103
104 static branch_t *subtree_branch = NULL; /* Set to the (first) subtree branch that we want to show */
105 static revision_t *subtree_rev = NULL; /* Set to the subtree revision which branches we want to show */
106
107 static msg_stack_t *msg_stack = NULL; /* Messages that would otherwise be sent to stderr goto the image */
108 static int nmsg_stack = 0;
109
110 /*
111 **************************************************************************
112 * Forwards
113 **************************************************************************
114 */
115 static void zap_string(void);
116 static char *dup_string(void);
117 static void add_string_str(const char *s);
118 static void add_string_ch(int ch);
119 static void add_string_date(const char *d);
120 static void add_string_str_html(const char *s, int maxlen);
121 static void add_string_str_len(const char *s, int maxlen);
122
123 static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h);
124
125 /*
126 **************************************************************************
127 * Debug routines
128 **************************************************************************
129 */
130 static void dump_rev(char *p, rev_t *r)
131 {
132 printf("%s", p);
133 if(r)
134 printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
135 else
136 printf("<null>\n");
137 }
138
139 static void dump_id(char *p, char *d)
140 {
141 printf("%s", p);
142 if(d)
143 printf("'%s'\n", d);
144 else
145 printf("<null>\n");
146 }
147
148 static void dump_idrev(char *p, idrev_t *t)
149 {
150 printf("%s", p);
151 if(t)
152 {
153 printf("'%s' -> ", t->id);
154 dump_rev("", t->rev);
155 }
156 else
157 printf("<null>\n");
158 }
159
160 static void dump_tag(char *p, tag_t *t)
161 {
162 printf("%s", p);
163 if(t)
164 {
165 printf("'%s' -> ", t->tag);
166 dump_rev("", t->rev);
167 }
168 else
169 printf("<null>\n");
170 }
171
172 static void dump_delta(char *p, delta_t *d)
173 {
174 int i;
175 printf("%sdelta.rev : ", p);
176 dump_rev("", d->rev);
177 printf("%sdelta.date : %s\n", p, d->date);
178 printf("%sdelta.author: %s\n", p, d->author);
179 printf("%sdelta.state : %s\n", p, d->state);
180 for(i = 0; d->branches && i < d->branches->nrevs; i++)
181 {
182 printf("%sdelta.branch: ", p);
183 dump_rev("", d->branches->revs[i]);
184 }
185 printf("%sdelta.next : ", p);
186 dump_rev("", d->next);
187 printf("\n");
188 }
189
190 static void dump_dtext(char *p, dtext_t *d)
191 {
192 printf("%sdtext.rev : ", p);
193 dump_rev("", d->rev);
194 printf("%sdtext.log : %d bytes\n", p, d->log ? strlen(d->log) : -1);
195 printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
196 printf("\n");
197 }
198
199 static void dump_rcsfile(rcsfile_t *rcs)
200 {
201 int i;
202 printf("root : '%s'\n", rcs->root);
203 printf("module : '%s'\n", rcs->module);
204 printf("file : '%s'\n", rcs->file);
205 dump_rev("head : ", rcs->head);
206 dump_rev("branch : ", rcs->branch);
207 printf("access :\n");
208 for(i = 0; rcs->access && i < rcs->access->nids; i++)
209 dump_id("\t", rcs->access->ids[i]);
210 printf("tags :\n");
211 for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
212 dump_tag("\t", rcs->tags->tags[i]);
213 printf("locks :%s\n", rcs->strict ? " (strict)" : "");
214 for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
215 dump_idrev("\t", rcs->locks->idrevs[i]);
216 printf("comment: '%s'\n", rcs->comment);
217 printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
218 printf("deltas :\n");
219 for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
220 dump_delta("\t", rcs->deltas->deltas[i]);
221 printf("desc : '%s'\n", rcs->desc);
222 printf("dtexts :\n");
223 for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
224 dump_dtext("\t", rcs->dtexts->dtexts[i]);
225
226 fflush(stdout);
227 }
228
229 /*
230 **************************************************************************
231 * Error/Warning Message helpers
232 **************************************************************************
233 */
234 #define MSGBUFSIZE 256
235 void stack_msg(int severity, const char *fmt, ...)
236 {
237 va_list va;
238 int i;
239 char *buf = xmalloc(MSGBUFSIZE);
240 switch(severity)
241 {
242 case MSG_WARN: sprintf(buf, "Warning: "); break;
243 case MSG_ERR: sprintf(buf, "Error: "); break;
244 default: sprintf(buf, "Unqualified error: "); break;
245 }
246 i = strlen(buf);
247 assert(i < MSGBUFSIZE);
248 va_start(va, fmt);
249 vsnprintf(buf+i, MSGBUFSIZE-i, fmt, va);
250 va_end(va);
251 if(!msg_stack)
252 msg_stack = xmalloc(sizeof(*msg_stack));
253 else
254 {
255 msg_stack = xrealloc(msg_stack, (nmsg_stack+1)*sizeof(*msg_stack));
256 }
257 msg_stack[nmsg_stack].msg = buf;
258 msg_stack[nmsg_stack].severity = severity;
259 nmsg_stack++;
260 }
261
262 /*
263 **************************************************************************
264 * Read the rcs file
265 **************************************************************************
266 */
267 static rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
268 {
269 char *cmd = NULL;
270 int rv;
271
272 if(file)
273 {
274 cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
275 sprintf(cmd, "%s%s%s", cvsroot, module, file);
276 if(!(rcsin = fopen(cmd, "rb")))
277 {
278 perror(cmd);
279 return NULL;
280 }
281 input_file = cmd;
282 }
283 else
284 {
285 rcsin = stdin;
286 input_file = "<stdin>";
287 }
288 line_number = 1;
289 rv = rcsparse();
290 if(file)
291 {
292 fclose(rcsin);
293 xfree(cmd);
294 }
295 if(rv)
296 return NULL;
297 input_file = NULL;
298 if(file)
299 {
300 rcsfile->root = xstrdup(cvsroot);
301 rcsfile->module = xstrdup(module);
302 rcsfile->file = xstrdup(file);
303 }
304 else
305 {
306 rcsfile->root = xstrdup("");
307 rcsfile->module = xstrdup("");
308 rcsfile->file = xstrdup("<stdin>");
309 }
310 return rcsfile;
311 }
312
313 /*
314 **************************************************************************
315 * Sort and find helpers
316 **************************************************************************
317 */
318 static int count_dots(const char *s)
319 {
320 int i;
321 for(i = 0; *s; s++)
322 {
323 if(*s == '.')
324 i++;
325 }
326 return i;
327 }
328
329 static int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
330 {
331 int d1, d2;
332 char *c1, *c2;
333 char *v1, *v2;
334 char *s1, *s2;
335 int retval = 0;
336 assert(r1 != NULL);
337 assert(r2 != NULL);
338 if(bcmp)
339 {
340 assert(r1->branch != NULL);
341 assert(r2->branch != NULL);
342 c1 = r1->branch;
343 c2 = r2->branch;
344 }
345 else
346 {
347 assert(r1->rev != NULL);
348 assert(r2->rev != NULL);
349 c1 = r1->rev;
350 c2 = r2->rev;
351 }
352
353 d1 = count_dots(c1);
354 d2 = count_dots(c2);
355 if(d1 != d2)
356 {
357 return d1 - d2;
358 }
359
360 s1 = v1 = xstrdup(c1);
361 s2 = v2 = xstrdup(c2);
362 while(1)
363 {
364 char *vc1 = strchr(s1, '.');
365 char *vc2 = strchr(s2, '.');
366 if(vc1 && vc2)
367 *vc1 = *vc2 = '\0';
368 if(*s1 && *s2)
369 {
370 d1 = atoi(s1);
371 d2 = atoi(s2);
372 if(d1 != d2)
373 {
374 retval = d1 - d2;
375 break;
376 }
377 }
378 if(!vc1 || !vc2)
379 break;
380 s1 = vc1 + 1;
381 s2 = vc2 + 1;
382 }
383 xfree(v1);
384 xfree(v2);
385 return retval;
386 }
387
388 /*
389 **************************************************************************
390 * Reorganise the rcsfile for the branches
391 *
392 * Basically, we have a list of deltas (i.e. administrative info on
393 * revisions) and a list of delta text (the actual logs and diffs).
394 * The deltas are linked through the 'next' and the 'branches' fields
395 * which describe the tree-structure of revisions.
396 * The reorganisation means that we put each delta and corresponding
397 * delta text in a revision structure and assign it to a specific
398 * branch. This is required because we want to be able to calculate
399 * the bounding boxes of each branch. The revisions expand vertically
400 * and the branches expand horizontally.
401 * The reorganisation is performed in these steps:
402 * 1 - sort deltas and delta text on revision number for quick lookup
403 * 2 - start at the denoted head revision:
404 * * create a branch structure and add this revision
405 * * for each 'branches' in the delta do:
406 * - walk all 'branches' of the delta and recursively goto 2
407 * with the denoted branch delta as new head
408 * - backlink the newly create sub-branch to the head revision
409 * so that we can draw them recursively
410 * * set head to the 'next' field and goto 2 until no next is
411 * available
412 * 3 - update the administration
413 **************************************************************************
414 */
415 static int sort_delta(const void *d1, const void *d2)
416 {
417 return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
418 }
419
420 static int search_delta(const void *r, const void *d)
421 {
422 return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
423 }
424
425 static delta_t *find_delta(delta_t **dl, int n, rev_t *r)
426 {
427 delta_t **d;
428 if(!n)
429 return NULL;
430 d = bsearch(r, dl, n, sizeof(*dl), search_delta);
431 if(!d)
432 return NULL;
433 return *d;
434 }
435
436 static int sort_dtext(const void *d1, const void *d2)
437 {
438 return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
439 }
440
441 static int search_dtext(const void *r, const void *d)
442 {
443 return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
444 }
445
446 static dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
447 {
448 dtext_t **d;
449 if(!n)
450 return NULL;
451 d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
452 if(!d)
453 return NULL;
454 return *d;
455 }
456
457 static rev_t *dup_rev(const rev_t *r)
458 {
459 rev_t *t = xmalloc(sizeof(*t));
460 t->rev = xstrdup(r->rev);
461 t->branch = xstrdup(r->branch);
462 t->isbranch = r->isbranch;
463 return t;
464 }
465
466 static branch_t *new_branch(delta_t *d, dtext_t *t)
467 {
468 branch_t *b = xmalloc(sizeof(*b));
469 revision_t *r = xmalloc(sizeof(*r));
470 r->delta = d;
471 r->dtext = t;
472 r->rev = d->rev;
473 r->branch = b;
474 b->branch = dup_rev(d->rev);
475 b->branch->isbranch = 1;
476 b->nrevs = 1;
477 b->revs = xmalloc(sizeof(b->revs[0]));
478 b->revs[0] = r;
479 return b;
480 }
481
482 static revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
483 {
484 revision_t *r = xmalloc(sizeof(*r));
485 r->delta = d;
486 r->dtext = t;
487 r->rev = d->rev;
488 r->branch = b;
489 b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
490 b->revs[b->nrevs] = r;
491 b->nrevs++;
492 return r;
493 }
494
495 static void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
496 {
497 branch_t *b;
498 dtext_t *text;
499 revision_t *currev;
500
501 assert(head != NULL);
502
503 if(head->flag)
504 {
505 stack_msg(MSG_ERR, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
506 return;
507 }
508 head->flag++;
509 text = find_dtext(sdt, nsdt, head->rev);
510
511 /* Create a new branch for this head */
512 b = new_branch(head, text);
513 *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
514 (*bl)[*nbl] = b;
515 (*nbl)++;
516 currev = b->revs[0];
517 while(1)
518 {
519 /* Process all sub-branches */
520 if(head->branches)
521 {
522 int i;
523 for(i = 0; i < head->branches->nrevs; i++)
524 {
525 delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
526 int btag = *nbl;
527 if(!d)
528 continue;
529 build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
530
531 /* Set the new branch's origin */
532 (*bl)[btag]->branchpoint = currev;
533
534 /* Add branch to this revision */
535 currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
536 currev->branches[currev->nbranches] = (*bl)[btag];
537 currev->nbranches++;
538 }
539 }
540
541 /* Walk through the next list */
542 if(!head->next)
543 return;
544
545 head = find_delta(sdl, nsdl, head->next);
546 if(!head)
547 {
548 stack_msg(MSG_ERR, "Next revision (%s) not found in deltalist\n", head->next->rev);
549 return;
550 }
551 if(head->flag)
552 {
553 stack_msg(MSG_ERR, "Circular reference on '%s'\n", head->rev->rev);
554 return;
555 }
556 head->flag++;
557 text = find_dtext(sdt, nsdt, head->rev);
558 currev = add_to_branch(b, head, text);
559 }
560 }
561
562 int reorganise_branches(rcsfile_t *rcs)
563 {
564 delta_t **sdelta;
565 int nsdelta;
566 dtext_t **sdtext;
567 int nsdtext;
568 delta_t *head;
569 branch_t **bl;
570 int nbl;
571 int i;
572
573 assert(rcs->deltas != NULL);
574 assert(rcs->head != NULL);
575
576 /* Make a new list for quick lookup */
577 nsdelta = rcs->deltas->ndeltas;
578 sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
579 memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
580 qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
581
582 /* Do the same for the delta text */
583 if(rcs->dtexts)
584 {
585 nsdtext = rcs->dtexts->ndtexts;
586 sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
587 memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
588 qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
589 }
590 else
591 {
592 nsdtext = 0;
593 sdtext = NULL;
594 }
595
596 /* Start from the head of the trunk */
597 head = find_delta(sdelta, nsdelta, rcs->head);
598 if(!head)
599 {
600 stack_msg(MSG_ERR, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
601 return 0;
602 }
603 bl = NULL;
604 nbl = 0;
605 build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
606
607 /* Reverse the head branch */
608 for(i = 0; i < bl[0]->nrevs/2; i++)
609 {
610 revision_t *r;
611 r = bl[0]->revs[i];
612 bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
613 bl[0]->revs[bl[0]->nrevs-i-1] = r;
614 }
615
616 /* Update the branch-number of the head because it was reversed */
617 xfree(bl[0]->branch->branch);
618 bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
619
620 /* Keep the admin */
621 rcs->branches = bl;
622 rcs->nbranches = nbl;
623 rcs->sdelta = sdelta;
624 rcs->nsdelta = nsdelta;
625 rcs->sdtext = sdtext;
626 rcs->nsdtext = nsdtext;
627 rcs->active = bl[0];
628 return 1;
629 }
630
631 /*
632 **************************************************************************
633 * Assign the symbolic tags to the revisions and branches
634 *
635 * The tags point to revision numbers somewhere in the tree structure
636 * of branches and revisions. First we make a sorted list of all
637 * revisions and then we assign each tag to the proper revision.
638 **************************************************************************
639 */
640 static int sort_revision(const void *r1, const void *r2)
641 {
642 return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
643 }
644
645 static int search_revision(const void *t, const void *r)
646 {
647 return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
648 }
649
650 static int sort_branch(const void *b1, const void *b2)
651 {
652 return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
653 }
654
655 static int search_branch(const void *t, const void *b)
656 {
657 return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
658 }
659
660 static char *previous_rev(const char *c)
661 {
662 int dots = count_dots(c);
663 char *cptr;
664 char *r;
665 if(!dots)
666 {
667 stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
668 return xstrdup("1.0"); /* FIXME: don't know what the parent is */
669 }
670 if(dots & 1)
671 {
672 /* Is is a revision we want the parent of */
673 r = xstrdup(c);
674 cptr = strrchr(r, '.');
675 assert(cptr != NULL);
676 if(dots == 1)
677 {
678 stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
679 /* FIXME: What is the parent of 1.1? */
680 cptr[1] = '\0';
681 strcat(r, "0");
682 return r;
683 }
684 /* Here we have a "x.x[.x.x]+" case */
685 *cptr = '\0';
686 cptr = strrchr(r, '.');
687 assert(cptr != NULL);
688 *cptr = '\0';
689 return r;
690 }
691 /* It is a branch we want the parent of */
692 r = xstrdup(c);
693 cptr = strrchr(r, '.');
694 assert(cptr != NULL);
695 *cptr = '\0';
696 return r;
697 }
698
699 static char *build_regex(size_t n, regmatch_t *m, const char *ms)
700 {
701 char *cptr;
702 int i;
703
704 if(!conf.merge_to || !conf.merge_to[0])
705 return NULL;
706
707 zap_string();
708 for(cptr = conf.merge_to; *cptr; cptr++)
709 {
710 if(*cptr == '%')
711 {
712 if(cptr[1] >= '1' && cptr[1] <= '9')
713 {
714 int idx = cptr[1] - '0';
715 regmatch_t *p = &m[idx];
716 if(idx < n && !(p->rm_so == -1 || p->rm_so >= p->rm_eo))
717 {
718 for(i = p->rm_so; i < p->rm_eo; i++)
719 {
720 if(strchr("^$.*+\\[{()", ms[i]))
721 add_string_ch('\\');
722 add_string_ch(ms[i]);
723 }
724 }
725 cptr++;
726 }
727 else
728 add_string_ch('%');
729 }
730 else
731 add_string_ch(*cptr);
732 }
733 return dup_string();
734 }
735
736 static void find_merges(rcsfile_t *rcs)
737 {
738 int i;
739 int err;
740 int rcflags = REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0);
741 regex_t *refrom = NULL;
742 regex_t *reto = NULL;
743 regmatch_t *matchfrom = NULL;
744
745 if(!conf.merge_from || !conf.merge_from[0] || !conf.merge_to || !conf.merge_to[0])
746 return;
747
748 refrom = xmalloc(sizeof(*refrom));
749 reto = xmalloc(sizeof(*reto));
750
751 /* Compile the 'from' regex match for merge identification */
752 err = regcomp(refrom, conf.merge_from, rcflags);
753 if(err)
754 {
755 char *msg;
756 i = regerror(err, refrom, NULL, 0);
757 msg = xmalloc(i+1);
758 regerror(err, refrom, msg, i+1);
759 stack_msg(MSG_WARN, "%s", msg);
760 xfree(msg);
761 xfree(refrom);
762 xfree(reto);
763 return;
764 }
765 else
766 matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
767
768 for(i = 0; i < rcs->tags->ntags; i++)
769 {
770 tag_t *t = rcs->tags->tags[i];
771
772 /* Must be revision tags and not detached */
773 if(t->rev->isbranch || !t->logrev)
774 continue;
775
776 /* Try to find merge tag matches */
777 if(!regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
778 {
779 int n;
780 char *to;
781
782 to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);
783 if(to)
784 {
785 err = regcomp(reto, to, rcflags);
786 if(err)
787 {
788 char *msg;
789 i = regerror(err, reto, NULL, 0);
790 msg = xmalloc(i+1);
791 regerror(err, reto, msg, i+1);
792 stack_msg(MSG_WARN, "%s", msg);
793 xfree(msg);
794 }
795 else if(!err)
796 {
797 for(n = 0; n < rcs->tags->ntags; n++)
798 {
799 tag_t *nt = rcs->tags->tags[n];
800 /* From and To never should match the same tag or belong to a branch */
801 if(n == i || nt->rev->isbranch || !nt->logrev)
802 continue;
803
804 if(!regexec(reto, nt->tag, 0, NULL, 0))
805 {
806 /* Tag matches */
807 rcs->merges = xrealloc(rcs->merges,
808 sizeof(rcs->merges[0]) * (rcs->nmerges+1));
809 rcs->merges[rcs->nmerges].to = nt;
810 rcs->merges[rcs->nmerges].from = t;
811 rcs->nmerges++;
812 if(!conf.tag_ignore_merge)
813 {
814 nt->ignore = 0;
815 t->ignore = 0;
816 }
817 /* We cannot (should not) match multiple times */
818 if(!conf.merge_findall)
819 break;
820 }
821 }
822 regfree(reto);
823 }
824 xfree(to);
825 }
826 }
827 }
828 if(matchfrom) xfree(matchfrom);
829 if(refrom) { regfree(refrom); xfree(refrom); }
830 if(reto) xfree(reto);
831 }
832
833 static void assign_tags(rcsfile_t *rcs)
834 {
835 int i;
836 int nr;
837 regex_t *regextag = NULL;
838
839 if(conf.tag_ignore && conf.tag_ignore[0])
840 {
841 int err;
842 regextag = xmalloc(sizeof(*regextag));
843 err = regcomp(regextag, conf.tag_ignore, REG_EXTENDED | REG_NOSUB | (conf.tag_nocase ? REG_ICASE : 0));
844 if(err)
845 {
846 char *msg;
847 i = regerror(err, regextag, NULL, 0);
848 msg = xmalloc(i+1);
849 regerror(err, regextag, msg, i+1);
850 stack_msg(MSG_WARN, "%s", msg);
851 xfree(msg);
852 xfree(regextag);
853 regextag = NULL;
854 }
855 }
856
857 for(i = nr = 0; i < rcs->nbranches; i++)
858 nr += rcs->branches[i]->nrevs;
859
860 rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
861 rcs->nsrev = nr;
862 for(i = nr = 0; i < rcs->nbranches; i++)
863 {
864 memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
865 nr += rcs->branches[i]->nrevs;
866 }
867
868 qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
869 qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
870
871 if(!rcs->branch)
872 {
873 /* The main trunk is the active trunk */
874 rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
875 rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
876 rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
877 rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
878 rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
879 rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
880 rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
881 rcs->tags->ntags++;
882 }
883
884 /* We should have at least two tags (HEAD and MAIN) */
885 assert(rcs->tags != NULL);
886
887 for(i = 0; i < rcs->tags->ntags; i++)
888 {
889 tag_t *t = rcs->tags->tags[i];
890 if(t->rev->isbranch)
891 {
892 branch_t **b;
893 add_btag:
894 b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
895 if(!b)
896 {
897 rev_t rev;
898 revision_t **r;
899 /* This happens for the magic branch numbers if there are
900 * no commits within the new branch yet. So, we add the
901 * branch and try to continue.
902 */
903 rev.rev = previous_rev(t->rev->branch);
904 rev.branch = NULL;
905 rev.isbranch = 0;
906 r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
907 xfree(rev.rev);
908 if(!r)
909 {
910 stack_msg(MSG_WARN, "No branch found for tag '%s:%s'", t->tag, t->rev->branch);
911 }
912 else
913 {
914 rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
915 rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
916 rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
917 rcs->branches[rcs->nbranches]->branchpoint = *r;
918 (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
919 (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
920 (*r)->nbranches++;
921 rcs->nbranches++;
922 /* Resort the branches */
923 qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
924 goto add_btag;
925 }
926 }
927 else
928 {
929 branch_t *bb = *b;
930 bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
931 bb->tags[bb->ntags] = t;
932 bb->ntags++;
933 }
934 }
935 else
936 {
937 revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
938 if(!r)
939 {
940 stack_msg(MSG_WARN, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
941 }
942 else
943 {
944 revision_t *rr = *r;
945 t->logrev = rr;
946 if(!conf.rev_maxtags || rr->ntags <= conf.rev_maxtags)
947 {
948 rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
949 if(conf.rev_maxtags && rr->ntags == conf.rev_maxtags)
950 {
951 rr->tags[rr->ntags] = xmalloc(sizeof(tag_t));
952 rr->tags[rr->ntags]->tag = xstrdup("...");
953 rr->tags[rr->ntags]->rev = t->rev;
954 }
955 else
956 rr->tags[rr->ntags] = t;
957 rr->ntags++;
958 }
959 }
960
961 if(conf.tag_negate)
962 t->ignore++;
963 /* Mark the tag ignored if it matches the configuration */
964 if(regextag && !regexec(regextag, t->tag, 0, NULL, 0))
965 {
966 if(conf.tag_negate)
967 t->ignore--;
968 else
969 t->ignore++;
970 }
971 }
972 }
973
974 /* We need to reset the first in the list of branches to the
975 * active branch to ensure the drawing of all
976 */
977 if(rcs->active != rcs->branches[0])
978 {
979 branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
980 branch_t *t;
981 assert(b != NULL);
982 t = *b;
983 *b = rcs->branches[0];
984 rcs->branches[0] = t;
985 }
986
987 if(regextag)
988 {
989 regfree(regextag);
990 xfree(regextag);
991 }
992 }
993
994 /*
995 **************************************************************************
996 * String expansion
997 **************************************************************************
998 */
999 static char *_string;
1000 static int _nstring;
1001 static int _nastring;
1002
1003 static void zap_string(void)
1004 {
1005 _nstring = 0;
1006 if(_string)
1007 _string[0] = '\0';
1008 }
1009
1010 static char *dup_string(void)
1011 {
1012 if(_string)
1013 return xstrdup(_string);
1014 else
1015 return "";
1016 }
1017
1018 static void add_string_str(const char *s)
1019 {
1020 int l = strlen(s) + 1;
1021 if(_nstring + l > _nastring)
1022 {
1023 _nastring += MAX(128, l);
1024 _string = xrealloc(_string, _nastring * sizeof(_string[0]));
1025 }
1026 memcpy(_string+_nstring, s, l);
1027 _nstring += l-1;
1028 }
1029
1030 static void add_string_ch(int ch)
1031 {
1032 char buf[2];
1033 buf[0] = ch;
1034 buf[1] = '\0';
1035 add_string_str(buf);
1036 }
1037
1038 static void add_string_date(const char *d)
1039 {
1040 struct tm tm, *tmp;
1041 int n;
1042 time_t t;
1043 char *buf;
1044 int nbuf;
1045
1046 memset(&tm, 0, sizeof(tm));
1047 n = sscanf(d, "%d.%d.%d.%d.%d.%d",
1048 &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
1049 &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
1050 tm.tm_mon--;
1051 if(tm.tm_year > 1900)
1052 tm.tm_year -= 1900;
1053 t = mktime(&tm) - timezone;
1054 if(n != 6 || t == (time_t)(-1))
1055 {
1056 add_string_str("<invalid date>");
1057 return;
1058 }
1059
1060 tmp = localtime(&t);
1061 nbuf = strlen(conf.date_format) * 16; /* Should be enough to hold all types of expansions */
1062 buf = xmalloc(nbuf);
1063 strftime(buf, nbuf, conf.date_format, tmp);
1064 add_string_str(buf);
1065 xfree(buf);
1066 }
1067
1068 static void add_string_str_html(const char *s, int maxlen)
1069 {
1070 int l = 0;
1071 char *str = xmalloc(6 * strlen(s) + 1); /* Should hold all char entity-expand */
1072 char *cptr = str;
1073 for(; *s; s++)
1074 {
1075 if(maxlen && l > abs(maxlen))
1076 {
1077 cptr += sprintf(cptr, "...");
1078 break;
1079 }
1080 if(*s < 0x20)
1081 {
1082 if(*s == '\n')
1083 {
1084 if(maxlen < 0)
1085 *cptr++ = ' ';
1086 else
1087 cptr += sprintf(cptr, "<br%s>", conf.html_level == HTMLLEVEL_X ? " /" : "");
1088 }
1089 }
1090 else if(*s >= 0x7f || *s == '"')
1091 cptr += sprintf(cptr, "&#%d;", (int)(unsigned char)*s);
1092 else if(*s == '<')
1093 cptr += sprintf(cptr, "&lt;");
1094 else if(*s == '>')
1095 cptr += sprintf(cptr, "&gt;");
1096 else if(*s == '&')
1097 cptr += sprintf(cptr, "&amp;");
1098 else if(*s == '"')
1099 cptr += sprintf(cptr, "&quot;");
1100 else
1101 *cptr++ = *s;
1102 l++;
1103 }
1104 *cptr = '\0';
1105 add_string_str(str);
1106 xfree(str);
1107 }
1108
1109 static void add_string_str_len(const char *s, int maxlen)
1110 {
1111 int l = strlen(s);
1112 char *str = xmalloc(l + 1 + 3);
1113 strcpy(str, s);
1114 if(maxlen < l)
1115 sprintf(&str[maxlen], "...");
1116 add_string_str(str);
1117 xfree(str);
1118 }
1119
1120 static char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1121 {
1122 char nb[32];
1123 char nr[32];
1124 char *base;
1125 char *exp;
1126 int l;
1127 char ch;
1128
1129 if(!s)
1130 return xstrdup("");
1131
1132 zap_string();
1133
1134 sprintf(nb, "%d", rcs->nbranches);
1135 sprintf(nr, "%d", rcs->nsrev);
1136 for(; *s; s++)
1137 {
1138 if(*s == '%')
1139 {
1140 switch(*++s)
1141 {
1142 case 'c':
1143 case 'C':
1144 add_string_str(conf.cvsroot);
1145 if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
1146 {
1147 /* Strip the trailing '/' */
1148 _nstring--;
1149 _string[_nstring] = '\0';
1150 }
1151 break;
1152 case 'f':
1153 case 'F':
1154 base = strrchr(rcs->file, '/');
1155 if(!base)
1156 add_string_str(rcs->file);
1157 else
1158 add_string_str(base+1);
1159 if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
1160 {
1161 _nstring -= 2;
1162 _string[_nstring] = '\0';
1163 }
1164 break;
1165 case 'p':
1166 base = strrchr(rcs->file, '/');
1167 if(base)
1168 {
1169 char *c = xstrdup(rcs->file);
1170 base = strrchr(c, '/');
1171 assert(base != NULL);
1172 base[1] = '\0';
1173 add_string_str(c);
1174 xfree(c);
1175 }
1176 /*
1177 * We should not add anything here because we can encounter
1178 * a completely empty path, in which case we do not want
1179 * to add any slash. This prevents an inadvertent root redirect.
1180 *
1181 * else
1182 * add_string_str("/");
1183 */
1184 break;
1185 case 'm':
1186 case 'M':
1187 add_string_str(conf.cvsmodule);
1188 if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
1189 {
1190 /* Strip the trailing '/' */
1191 _nstring--;
1192 _string[_nstring] = '\0';
1193 }
1194 break;
1195 case 'r': add_string_str(nr); break;
1196 case 'b': add_string_str(nb); break;
1197 case '%': add_string_ch('%'); break;
1198 case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
1199 case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
1200 case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
1201 case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
1202 case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
1203 case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
1204 case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
1205 case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
1206 case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
1207 case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
1208 case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
1209 case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
1210 case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
1211 case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
1212 case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
1213 case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
1214 case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
1215 case 'L':
1216 case 'l':
1217 ch = *s;
1218 l = 0;
1219 if(s[1] == '[')
1220 {
1221 char *cptr = strchr(s, ']');
1222 char *eptr;
1223 if(cptr)
1224 {
1225 l = strtol(&s[2], &eptr, 10);
1226 if(eptr != cptr)
1227 l = 0;
1228 else
1229 s = cptr;
1230 }
1231 }
1232 if(!conf.parse_logs)
1233 add_string_str("N/A");
1234 else if(r && r->dtext && r->dtext->log)
1235 {
1236 if(ch == 'l')
1237 add_string_str_html(r->dtext->log, l);
1238 else
1239 add_string_str_len(r->dtext->log, abs(l));
1240 }
1241 break;
1242 case '(':
1243 base = dup_string();
1244 exp = expand_string(s+1, rcs, r, rev, prev, tag);
1245 zap_string();
1246 add_string_str(base);
1247 add_string_str_html(exp, 0);
1248 xfree(base);
1249 xfree(exp);
1250 /* Find the %) in this recursion level */
1251 for(; *s; s++)
1252 {
1253 if(*s == '%' && s[1] == ')')
1254 {
1255 s++;
1256 break;
1257 }
1258 }
1259 if(!*s)
1260 {
1261 s--; /* To end outer loop */
1262 stack_msg(MSG_WARN, "string expand: Missing %%) in expansion");
1263 }
1264 break;
1265 case ')':
1266 return dup_string();
1267 default:
1268 add_string_ch('%');
1269 add_string_ch(*s);
1270 break;
1271 }
1272 }
1273 else
1274 add_string_ch(*s);
1275 }
1276 return dup_string();
1277 }
1278
1279 /*
1280 **************************************************************************
1281 * Drawing routines
1282 **************************************************************************
1283 */
1284 static int get_swidth(const char *s, font_t *f)
1285 {
1286 int n;
1287 int m;
1288 if(!s || !*s)
1289 return 0;
1290
1291 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1292 if(conf.use_ttf && f->ttfont)
1293 {
1294 int bb[8];
1295 char *e;
1296 #ifdef HAVE_GDIMAGESTRINGFT
1297 e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1298 #else
1299 e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1300 #endif
1301 if(!e)
1302 return bb[2] - bb[6];
1303 }
1304 #endif
1305 for(n = m = 0; *s; n++, s++)
1306 {
1307 if(*s == '\n')
1308 {
1309 if(n > m)
1310 m = n;
1311 n = 0;
1312 }
1313 }
1314 if(n > m)
1315 m = n;
1316 return f->gdfont ? m * f->gdfont->w : m;
1317 }
1318
1319 static int get_sheight(const char *s, font_t *f)
1320 {
1321 int nl;
1322 if(!s || !*s)
1323 return 0;
1324
1325 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1326 if(conf.use_ttf && f->ttfont)
1327 {
1328 int bb[8];
1329 char *e;
1330 #ifdef HAVE_GDIMAGESTRINGFT
1331 e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1332 #else
1333 e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1334 #endif
1335 if(!e)
1336 return bb[3] - bb[7] + 4;
1337 }
1338 #endif
1339 for(nl = 1; *s; s++)
1340 {
1341 if(*s == '\n' && s[1])
1342 nl++;
1343 }
1344 return nl * f->gdfont->h;
1345 }
1346
1347 static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
1348 {
1349 int r2 = 2*r;
1350 if(!r)
1351 gdImageFilledRectangle(im, x1, y1, x2, y2, bgcolor->id);
1352 #ifdef HAVE_GDIMAGEFILLEDARC
1353 else
1354 {
1355 gdImageFilledArc(im, x1+r, y1+r, r2, r2, 180, 270, bgcolor->id, gdArc);
1356 gdImageFilledArc(im, x2-r, y1+r, r2, r2, 270, 360, bgcolor->id, gdArc);
1357 gdImageFilledArc(im, x1+r, y2-r, r2, r2, 90, 180, bgcolor->id, gdArc);
1358 gdImageFilledArc(im, x2-r, y2-r, r2, r2, 0, 90, bgcolor->id, gdArc);
1359 gdImageFilledRectangle(im, x1+r, y1, x2-r, y1+r, bgcolor->id);
1360 gdImageFilledRectangle(im, x1, y1+r, x2, y2-r, bgcolor->id);
1361 gdImageFilledRectangle(im, x1+r, y2-r, x2-r, y2, bgcolor->id);
1362 }
1363 #endif
1364 gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1365 gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1366 gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
1367 gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
1368 if(conf.box_shadow)
1369 {
1370 gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, black_color.id);
1371 gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, black_color.id);
1372 }
1373 if(r)
1374 {
1375 /* FIXME: Pixelization is not perfect */
1376 gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
1377 gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
1378 gdImageArc(im, x1+r, y2-r, r2, r2, 90, 180, color->id);
1379 if(conf.box_shadow)
1380 {
1381 gdImageArc(im, x2-r+1, y2-r+1, r2, r2, 0, 90, black_color.id);
1382 gdImageArc(im, x2-r+1, y2-r, r2, r2, 0, 90, black_color.id);
1383 gdImageArc(im, x2-r, y2-r+1, r2, r2, 0, 90, black_color.id);
1384 }
1385 gdImageArc(im, x2-r, y2-r, r2, r2, 0, 90, color->id);
1386 #if !defined(NOGDFILL) && !defined(HAVE_GDIMAGEFILLEDARC)
1387 /* BUG: We clip manually because libgd segfaults on out of bound values */
1388 if((x1+x2)/2 >= 0 && (x1+x2)/2 < gdImageSX(im) && (y1+y2)/2 >= 0 && (y1+y2)/2 < gdImageSY(im))
1389 gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1390 #endif
1391 }
1392 }
1393
1394 static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1395 {
1396 int h = get_sheight(s, f);
1397 int xx, yy;
1398 switch(align & ALIGN_HX)
1399 {
1400 default:
1401 case ALIGN_HL: xx = 0; break;
1402 case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
1403 case ALIGN_HR: xx = -get_swidth(s, f); break;
1404 }
1405 switch(align & ALIGN_VX)
1406 {
1407 default:
1408 case ALIGN_VT: yy = 0; break;
1409 case ALIGN_VC: yy = h/2; break;
1410 case ALIGN_VB: yy = h; break;
1411 }
1412 #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1413 if(conf.use_ttf && f->ttfont)
1414 {
1415 int bb[8];
1416 char *e;
1417 int cid = conf.anti_alias ? c->id : -c->id;
1418 #ifdef HAVE_GDIMAGESTRINGFT
1419 e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1420 #else
1421 e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1422 #endif
1423 if(!e)
1424 return;
1425 }
1426 #endif
1427 yy = -yy;
1428 gdImageString(im, f->gdfont, x+xx+1, y+yy, s, c->id);
1429 }
1430
1431 static void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1432 {
1433 char *t;
1434 char *d;
1435 d = s = xstrdup(s);
1436 do
1437 {
1438 t = strchr(s, '\n');
1439 if(t)
1440 *t = '\0';
1441 draw_string(im, s, f, x, y, align, c);
1442 y += get_sheight(s, f);
1443 s = t+1;
1444 } while(t);
1445 xfree(d);
1446 }
1447
1448 static void draw_rev(gdImagePtr im, revision_t *r)
1449 {
1450 int lx;
1451 int rx;
1452 int x2;
1453 int i;
1454 int ty;
1455
1456 if(conf.left_right)
1457 {
1458 lx = r->cx;
1459 rx = r->cx + r->w;
1460 ty = r->y - r->h/2;
1461 x2 = r->cx + r->w/2;
1462 }
1463 else
1464 {
1465 lx = r->cx - r->w/2;
1466 rx = lx + r->w;
1467 ty = r->y;
1468 x2 = r->cx;
1469 }
1470 draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
1471 ty += conf.rev_tspace;
1472 if(!conf.rev_hidenumber)
1473 {
1474 draw_string(im, r->rev->rev, &conf.rev_font, x2, ty, ALIGN_HC, &conf.rev_color);
1475 ty += get_sheight(r->rev->rev, &conf.rev_font);
1476 }
1477 draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, &conf.rev_text_color);
1478 ty += get_sheight(r->revtext, &conf.rev_text_font);
1479 for(i = 0; i < r->ntags; i++)
1480 {
1481 draw_string(im, r->tags[i]->tag, &conf.tag_font, x2, ty, ALIGN_HC, &conf.tag_color);
1482 ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1483 }
1484 }
1485
1486 static void draw_branch_box(gdImagePtr im, branch_t *b, int xp, int yp)
1487 {
1488 int lx;
1489 int rx;
1490 int i;
1491 int yy;
1492 int x2;
1493
1494 if(conf.left_right)
1495 {
1496 lx = b->cx;
1497 rx = lx + b->w;
1498 x2 = b->cx + b->w/2;
1499 }
1500 else
1501 {
1502 lx = b->cx - b->w/2;
1503 rx = lx + b->w;
1504 x2 = b->cx;
1505 }
1506 draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1507 yy = conf.branch_tspace;
1508 if(!b->nfolds)
1509 {
1510 if(!conf.rev_hidenumber)
1511 {
1512 draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_color);
1513 yy += get_sheight(b->branch->branch, &conf.branch_font);
1514 }
1515 for(i = 0; i < b->ntags; i++)
1516 {
1517 draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_tag_color);
1518 yy += get_sheight(b->tags[i]->tag, &conf.branch_tag_font);
1519 }
1520 }
1521 else
1522 {
1523 int y1, y2;
1524 int tx = lx + b->fw + conf.branch_lspace;
1525 int nx = tx - get_swidth(" ", &conf.branch_font);
1526 draw_string(im, b->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1527 y1 = get_sheight(b->branch->branch, &conf.branch_font);
1528 draw_string(im, b->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1529 y2 = get_sheight(b->tags[0]->tag, &conf.branch_font);
1530 yy += MAX(y1, y2);
1531 for(i = 0; i < b->nfolds; i++)
1532 {
1533 draw_string(im, b->folds[i]->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1534 y1 = get_sheight(b->folds[i]->branch->branch, &conf.branch_font);
1535 draw_string(im, b->folds[i]->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1536 y2 = get_sheight(b->folds[i]->tags[0]->tag, &conf.branch_tag_font);
1537 yy += MAX(y1, y2);
1538 }
1539 }
1540 }
1541
1542 static void draw_branch(gdImagePtr im, branch_t *b)
1543 {
1544 int yy, xx;
1545 int i;
1546 int line[4];
1547 int l;
1548 int sign;
1549
1550 line[0] = conf.rev_color.id;
1551 line[1] = gdTransparent;
1552 line[1] = gdTransparent;
1553 line[3] = conf.rev_color.id;
1554
1555 /* Trivial clip the branch */
1556 if(conf.left_right)
1557 {
1558 if(b->cx > gdImageSX(im) || b->cx+b->tw < 0 || b->y-b->th/2 > gdImageSY(im) || b->y+b->th/2 < 0)
1559 return;
1560 }
1561 else
1562 {
1563 if(b->cx-b->tw/2 > gdImageSX(im) || b->cx+b->tw/2 < 0 || b->y > gdImageSY(im) || b->y+b->th < 0)
1564 return;
1565 }
1566
1567 draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1568
1569 if(conf.left_right)
1570 {
1571 if(conf.upside_down)
1572 {
1573 xx = b->cx;
1574 for(i = 0; i < b->nrevs; i++)
1575 {
1576 revision_t *r = b->revs[i];
1577 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1578 gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1579 for(sign = l = 1; l < conf.thick_lines; l++)
1580 {
1581 int pp = (l+1)/2*sign;
1582 gdImageLine(im, xx, r->y+pp, r->cx+r->w, r->y+pp, gdStyled);
1583 sign *= -1;
1584 }
1585 draw_rev(im, r);
1586 xx = r->cx;
1587 }
1588 if(conf.branch_dupbox && b->nrevs)
1589 {
1590 i = b->cx - b->tw + b->w;
1591 gdImageLine(im, xx, b->y, i+b->w, b->y, conf.rev_color.id);
1592 for(sign = l = 1; l < conf.thick_lines; l++)
1593 {
1594 int pp = (l+1)/2*sign;
1595 gdImageLine(im, xx, b->y+pp, i+b->w, b->y+pp, conf.rev_color.id);
1596 sign *= -1;
1597 }
1598 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1599 }
1600 }
1601 else
1602 {
1603 xx = b->cx + b->w;
1604 for(i = 0; i < b->nrevs; i++)
1605 {
1606 revision_t *r = b->revs[i];
1607 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1608 gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1609 for(sign = l = 1; l < conf.thick_lines; l++)
1610 {
1611 int pp = (l+1)/2*sign;
1612 gdImageLine(im, xx, r->y+pp, r->cx, r->y+pp, gdStyled);
1613 sign *= -1;
1614 }
1615 draw_rev(im, r);
1616 xx = r->cx + r->w;
1617 }
1618 if(conf.branch_dupbox && b->nrevs)
1619 {
1620 i = b->cx + b->tw - b->w;
1621 gdImageLine(im, xx, b->y, i, b->y, conf.rev_color.id);
1622 for(sign = l = 1; l < conf.thick_lines; l++)
1623 {
1624 int pp = (l+1)/2*sign;
1625 gdImageLine(im, xx, b->y+pp, i, b->y+pp, conf.rev_color.id);
1626 sign *= -1;
1627 }
1628 draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1629 }
1630 }
1631 }
1632 else
1633 {
1634 if(conf.upside_down)
1635 {
1636 yy = b->y;
1637 for(i = 0; i < b->nrevs; i++)
1638 {
1639 revision_t *r = b->revs[i];
1640 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1641 gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1642 for(sign = l = 1; l < conf.thick_lines; l++)
1643 {
1644 int pp = (l+1)/2*sign;
1645 gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1646 sign *= -1;
1647 }
1648 draw_rev(im, r);
1649 yy = r->y;
1650 }
1651 if(conf.branch_dupbox && b->nrevs)
1652 {
1653 i = b->y - b->th + b->h;
1654 gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1655 for(sign = l = 1; l < conf.thick_lines; l++)
1656 {
1657 int pp = (l+1)/2*sign;
1658 gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1659 sign *= -1;
1660 }
1661 draw_branch_box(im, b, 0, i);
1662 }
1663 }
1664 else
1665 {
1666 yy = b->y + b->h;
1667 for(i = 0; i < b->nrevs; i++)
1668 {
1669 revision_t *r = b->revs[i];
1670 gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1671 gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1672 for(sign = l = 1; l < conf.thick_lines; l++)
1673 {
1674 int pp = (l+1)/2*sign;
1675 gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1676 sign *= -1;
1677 }
1678 draw_rev(im, r);
1679 yy = r->y + r->h;
1680 }
1681 if(conf.branch_dupbox && b->nrevs)
1682 {
1683 i = b->y + b->th - b->h;
1684 gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1685 for(sign = l = 1; l < conf.thick_lines; l++)
1686 {
1687 int pp = (l+1)/2*sign;
1688 gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1689 sign *= -1;
1690 }
1691 draw_branch_box(im, b, 0, i);
1692 }
1693 }
1694 }
1695 }
1696
1697 static void draw_connector(gdImagePtr im, branch_t *b)
1698 {
1699 int l;
1700 int sign;
1701 revision_t *r = b->branchpoint;
1702 int x1 = r->cx + r->w/2 + 2;
1703 int y1 = r->y + r->h/2;
1704 int x2 = b->cx;
1705 int y2 = b->y;
1706
1707 if(conf.left_right)
1708 {
1709 x2 = r->cx + r->w/2;
1710 y2 = r->y + r->h/2 + 3;
1711 x1 = b->cx;
1712 y1 = b->y;
1713 if(conf.upside_down)
1714 x1 += b->w;
1715 }
1716 else
1717 {
1718 x1 = r->cx + r->w/2 + 2;
1719 y1 = r->y + r->h/2;
1720 x2 = b->cx;
1721 y2 = b->y;
1722 if(conf.upside_down)
1723 y2 += b->h;
1724 }
1725 gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1726 gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1727 for(sign = l = 1; l < conf.thick_lines; l++)
1728 {
1729 int pp = (l+1)/2*sign;
1730 gdImageLine(im, x1, y1+pp, x2, y1+pp, conf.branch_color.id);
1731 gdImageLine(im, x2+pp, y1, x2+pp, y2, conf.branch_color.id);
1732 sign *= -1;
1733 }
1734 }
1735
1736 static void draw_merges(gdImagePtr im, rcsfile_t *rcs, int dot)
1737 {
1738 int i;
1739 for(i = 0; i < rcs->nmerges; i++)
1740 {
1741 revision_t *fr = rcs->merges[i].from->logrev;
1742 revision_t *tr = rcs->merges[i].to->logrev;
1743 int x1, x2, y1, y2;
1744 if(!fr || !tr || fr == tr)
1745 continue; /* This can happen with detached tags and self-references */
1746 if(conf.left_right)
1747 {
1748 if(fr->branch == tr->branch)
1749 {
1750 y1 = fr->y - fr->h/2;
1751 y2 = tr->y - tr->h/2;
1752 }
1753 else
1754 {
1755 if(fr->y < tr->y)
1756 {
1757 y1 = fr->y + fr->h/2;
1758 y2 = tr->y - tr->h/2;
1759 }
1760 else
1761 {
1762 y1 = fr->y - fr->h/2;
1763 y2 = tr->y + tr->h/2;
1764 }
1765 }
1766 x1 = fr->cx + fr->w/2;
1767 x2 = tr->cx + tr->w/2;
1768 }
1769 else
1770 {
1771 if(fr->branch == tr->branch)
1772 {
1773 x1 = fr->cx - fr->w/2;
1774 x2 = tr->cx - tr->w/2;
1775 }
1776 else
1777 {
1778 if(fr->cx < tr->cx)
1779 {
1780 x1 = fr->cx + fr->w/2;
1781 x2 = tr->cx - tr->w/2;
1782 }
1783 else
1784 {
1785 x1 = fr->cx - fr->w/2;
1786 x2 = tr->cx + tr->w/2;
1787 }
1788 }
1789 y1 = fr->y + rcs->merges[i].from->yofs;
1790 y2 = tr->y + rcs->merges[i].to->yofs;
1791 }
1792 if(dot && !conf.merge_arrows)
1793 {
1794 int o = conf.left_right ? 1 : 0;
1795 gdImageArc(im, x2, y2+o, 8, 8, 0, 360, conf.merge_color.id);
1796 /* BUG: We clip manually because libgd segfaults on out of bound values */
1797 if(x2+1 >= 0 && x2+1 < gdImageSX(im) && y2+o+1 >= 0 && y2+o+1 < gdImageSY(im))
1798 gdImageFillToBorder(im, x2+1, y2+o+1, conf.merge_color.id, conf.merge_color.id);
1799 }
1800 else if(dot && conf.merge_arrows)
1801 {
1802 /*
1803 * Arrow patch from Haroon Rafique <haroon.rafique@utoronto.ca>
1804 * Slightly adapted to be more configurable.
1805 */
1806 int sx, sy; /* start point coordinates */
1807 int ex, ey; /* end point coordinates */
1808 double theta;
1809 double u1, v1, u2, v2;
1810 gdPoint p[3];
1811
1812 sx = x1; sy = y1;
1813 ex = x2; ey = y2;
1814 if(conf.left_right)
1815 {
1816 if(fr->branch == tr->branch)
1817 {
1818 int yy = (y1 < y2 ? y1 : y2) - 5;
1819 /* line from (x1,yy) to (x2,yy) */
1820 sy = ey = yy;
1821 }
1822 else
1823 {
1824 if(y1 > y2)
1825 {
1826 /* line from (x1,y1-3) to (x2,y2+3+1) */
1827 sy = y1-3;
1828 ey = y2+3+1;
1829 }
1830 else
1831 {
1832 /* line from (x1,y1+3+1) to (x2,y2-3) */
1833 sy = y1+3+1;
1834 ey = y2-3;
1835 }
1836 }
1837 }
1838 else
1839 {
1840 if(fr->branch == tr->branch)
1841 {
1842 int xx = (x1 < x2 ? x1 : x2) - 5;
1843 /* line from (xx,y1) to (xx,y2) */
1844 sx = ex = xx;
1845 }
1846 else
1847 {
1848 if(x1 > x2)
1849 {
1850 /* line from (x1-3,y1) to (x2+3,y2) */
1851 sx = x1-3;
1852 ex = x2+3;
1853 }
1854 else
1855 {
1856 /* line from (x1+3,y1) to (x2-3,y2) */
1857 sx = x1+3;
1858 ex = x2-3;
1859 }
1860 }
1861 }
1862 /*
1863 * inspiration for arrow code comes from arrows.c in the
1864 * graphviz package. Thank you, AT&T
1865 */
1866 /* theta in radians */
1867 theta = atan2((double)(sy-ey), (double)(sx-ex));
1868 u1 = (double)conf.arrow_length * cos(theta);
1869 v1 = (double)conf.arrow_length * sin(theta);
1870 u2 = (double)conf.arrow_width * cos(theta + M_PI/2.0);
1871 v2 = (double)conf.arrow_width * sin(theta + M_PI/2.0);
1872 /* points of polygon (triangle) */
1873 p[0].x = ROUND(ex + u1 - u2);
1874 p[0].y = ROUND(ey + v1 - v2);
1875 p[1].x = ex;
1876 p[1].y = ey;
1877 p[2].x = ROUND(ex + u1 + u2);
1878 p[2].y = ROUND(ey + v1 + v2);
1879 /* draw the polygon (triangle) */
1880 gdImageFilledPolygon(im, p, 3, conf.merge_color.id);
1881 }
1882 else
1883 {
1884 if(conf.left_right)
1885 {
1886 if(fr->branch == tr->branch)
1887 {
1888 int yy = (y1 < y2 ? y1 : y2) - 5;
1889 gdImageLine(im, x1, y1, x1, yy, conf.merge_color.id);
1890 gdImageLine(im, x2, y2, x2, yy, conf.merge_color.id);
1891 gdImageLine(im, x1, yy, x2, yy, conf.merge_color.id);
1892 }
1893 else
1894 {
1895 if(y1 > y2)
1896 {
1897 gdImageLine(im, x1, y1, x1, y1-3, conf.merge_color.id);
1898 gdImageLine(im, x2, y2+1, x2, y2+3+1, conf.merge_color.id);
1899 gdImageLine(im, x1, y1-3, x2, y2+3+1, conf.merge_color.id);
1900 }
1901 else
1902 {
1903 gdImageLine(im, x1, y1+1, x1, y1+3+1, conf.merge_color.id);
1904 gdImageLine(im, x2, y2, x2, y2-3, conf.merge_color.id);
1905 gdImageLine(im, x1, y1+3+1, x2, y2-3, conf.merge_color.id);
1906 }
1907 }
1908 }
1909 else
1910 {
1911 if(fr->branch == tr->branch)
1912 {
1913 int xx = (x1 < x2 ? x1 : x2) - 5;
1914 gdImageLine(im, xx, y1, x1, y1, conf.merge_color.id);
1915 gdImageLine(im, xx, y2, x2, y2, conf.merge_color.id);
1916 gdImageLine(im, xx, y1, xx, y2, conf.merge_color.id);
1917 }
1918 else
1919 {
1920 if(x1 > x2)
1921 {
1922 gdImageLine(im, x1, y1, x1-3, y1, conf.merge_color.id);
1923 gdImageLine(im, x2, y2, x2+3, y2, conf.merge_color.id);
1924 gdImageLine(im, x1-3, y1, x2+3, y2, conf.merge_color.id);
1925 }
1926 else
1927 {
1928 gdImageLine(im, x1, y1, x1+3, y1, conf.merge_color.id);
1929 gdImageLine(im, x2, y2, x2-3, y2, conf.merge_color.id);
1930 gdImageLine(im, x1+3, y1, x2-3, y2, conf.merge_color.id);
1931 }
1932 }
1933 }
1934 }
1935 }
1936 }
1937
1938 static void draw_messages(gdImagePtr im, int offset)
1939 {
1940 int i;
1941
1942 for(i = 0; i < nmsg_stack; i++)
1943 {
1944 draw_stringnl(im, msg_stack[i].msg, &conf.msg_font, conf.margin_left, offset, ALIGN_HL|ALIGN_VT, &conf.msg_color);
1945 offset += msg_stack[i].h;
1946 }
1947 }
1948
1949 static void alloc_color(gdImagePtr im, color_t *c)
1950 {
1951 c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1952 }
1953
1954 static gdImagePtr make_image(rcsfile_t *rcs)
1955 {
1956 gdImagePtr im;
1957 int i;
1958 char *cptr;
1959 int w, h;
1960 int subx = 0, suby = 0;
1961 int subw, subh;
1962 int msgh = 0;
1963
1964 if(subtree_branch)
1965 {
1966 subw = 0;
1967 subh = 0;
1968 if(subtree_rev)
1969 {
1970 for(i = 0; i < subtree_rev->nbranches; i++)
1971 calc_subtree_size(subtree_rev->branches[i], &subx, &suby, &subw, &subh);
1972 }
1973 else
1974 calc_subtree_size(subtree_branch, &subx, &suby, &subw, &subh);
1975 }
1976 else
1977 {
1978 subw = rcs->tw;
1979 subh = rcs->th;
1980 }
1981
1982 cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1983 w = subw + conf.margin_left + conf.margin_right;
1984 h = subh + conf.margin_top + conf.margin_bottom;
1985 i = get_swidth(cptr, &conf.title_font);
1986 if(i > w)
1987 w = i;
1988
1989 if(!quiet && nmsg_stack)
1990 {
1991 int msgw = 0;
1992 for(i = 0; i < nmsg_stack; i++)
1993 {
1994 int ww = msg_stack[i].w = get_swidth(msg_stack[i].msg, &conf.msg_font);
1995 int hh = msg_stack[i].h = get_sheight(msg_stack[i].msg, &conf.msg_font);
1996 msgh += hh;
1997 h += hh;
1998 if(ww > msgw)
1999 msgw = ww;
2000 }
2001 if(msgw > w)
2002 w = msgw;
2003 }
2004
2005 im = gdImageCreate(w, h);
2006 alloc_color(im, &conf.color_bg);
2007 alloc_color(im, &conf.tag_color);
2008 alloc_color(im, &conf.rev_color);
2009 alloc_color(im, &conf.rev_bgcolor);
2010 alloc_color(im, &conf.rev_text_color);
2011 alloc_color(im, &conf.branch_color);
2012 alloc_color(im, &conf.branch_tag_color);
2013 alloc_color(im, &conf.branch_bgcolor);
2014 alloc_color(im, &conf.title_color);
2015 alloc_color(im, &conf.merge_color);
2016 alloc_color(im, &conf.msg_color);
2017 alloc_color(im, &black_color);
2018 alloc_color(im, &white_color);
2019
2020 if(conf.transparent_bg)
2021 gdImageColorTransparent(im, conf.color_bg.id);
2022
2023 if(!conf.merge_front)
2024 draw_merges(im, rcs, 0);
2025
2026 for(i = 0; i < rcs->nbranches; i++)
2027 {
2028 if(!rcs->branches[i]->folded && !(subtree_branch && !rcs->branches[i]->subtree_draw))
2029 draw_branch(im, rcs->branches[i]);
2030 }
2031
2032 draw_merges(im, rcs, 1); /* The dots of the merge dest */
2033
2034 for(i = 0; i < rcs->nbranches; i++)
2035 {
2036 if(rcs->branches[i]->branchpoint)
2037 draw_connector(im, rcs->branches[i]);
2038 }
2039
2040 /* Clear the margins if we have a partial tree */
2041 if(subtree_branch)
2042 {
2043 gdImageFilledRectangle(im, 0, 0, w-1, conf.margin_top-1, conf.color_bg.id);
2044 gdImageFilledRectangle(im, 0, 0, conf.margin_left-1, h-1, conf.color_bg.id);
2045 gdImageFilledRectangle(im, 0, h-conf.margin_bottom, w-1, h-1, conf.color_bg.id);
2046 gdImageFilledRectangle(im, w-conf.margin_right, 0, w-1, h-1, conf.color_bg.id);
2047 }
2048
2049 draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
2050 xfree(cptr);
2051
2052 if(conf.merge_front)
2053 draw_merges(im, rcs, 0);
2054
2055 if(!quiet)
2056 draw_messages(im, h - conf.margin_bottom/2 - msgh);
2057
2058 return im;
2059 }
2060
2061 /*
2062 **************************************************************************
2063 * Layout routines
2064 *
2065 * Branch BBox:
2066 * left = center_x - total_width / 2 (cx-tw)/2
2067 * right = center_x + total_width / 2 (cx+tw)/2
2068 * top = y_pos (y)
2069 * bottom = y_pos + total_height (y+th)
2070 *
2071 * Margins of branches:
2072 *
2073 * . .
2074 * . .
2075 * +--------------+
2076 * ^
2077 * | branch_margin .
2078 * v .
2079 * ----------------+ .
2080 * | ^ |
2081 * | | branch_connect |
2082 * | v |
2083 *..-+ +t-----+------+ +------+------+
2084 * | l | | |
2085 * | <--> | branch bbox | <--> | branch bbox |
2086 * | | | r | | |
2087 *..-+ | +------------b+ | +-------------+
2088 * | ^ branch_margin
2089 * | | branch_margin
2090 * | v
2091 * | +-------------+
2092 * | . .
2093 * | . .
2094 * |
2095 * branch_margin
2096 *
2097 * FIXME: There are probable som +/-1 errors in the code...
2098 * (notably shadows are not calculated in the margins)
2099 **************************************************************************
2100 */
2101 static void move_branch(branch_t *b, int x, int y)
2102 {
2103 int i;
2104 b->cx += x;
2105 b->y += y;
2106 for(i = 0; i < b->nrevs; i++)
2107 {
2108 b->revs[i]->cx += x;
2109 b->revs[i]->y += y;
2110 }
2111 }
2112
2113 static void initial_reposition_branch(revision_t *r, int *x, int *w)
2114 {
2115 int i, j;
2116 for(j = 0; j < r->nbranches; j++)
2117 {
2118 branch_t *b = r->branches[j];
2119 *x += *w + conf.rev_minline + b->tw/2 - b->cx;
2120 *w = b->tw/2;
2121 move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
2122 *x = b->cx;
2123 /* Recurse to move branches of branched revisions */
2124 for(i = b->nrevs-1; i >= 0; i--)
2125 {
2126 initial_reposition_branch(b->revs[i], x, w);
2127 }
2128 }
2129 }
2130
2131 static void initial_reposition_branch_lr(revision_t *r, int *y, int *h)
2132 {
2133 int i, j;
2134 for(j = 0; j < r->nbranches; j++)
2135 {
2136 branch_t *b = r->branches[j];
2137 *y += *h + conf.rev_minline + b->th/2 - b->y;
2138 *h = b->th/2;
2139 move_branch(b, r->cx + r->w/2 + conf.branch_connect, *y);
2140 *y = b->y;
2141 /* Recurse to move branches of branched revisions */
2142 for(i = b->nrevs-1; i >= 0; i--)
2143 {
2144 initial_reposition_branch_lr(b->revs[i], y, h);
2145 }
2146 }
2147 }
2148
2149 static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
2150 {
2151 int x1 = *x;
2152 int x2 = x1 + *w;
2153 int y1 = *y;
2154 int y2 = y1 + *h;
2155 int xx1;
2156 int xx2;
2157 int yy1;
2158 int yy2;
2159
2160 if(conf.left_right)
2161 {
2162 xx1 = b->cx;
2163 yy1 = b->y - b->th/2;
2164 }
2165 else
2166 {
2167 xx1 = b->cx - b->tw/2;
2168 yy1 = b->y;
2169 }
2170 xx2 = xx1 + b->tw;
2171 yy2 = yy1 + b->th;
2172
2173 x1 = MIN(x1, xx1);
2174 x2 = MAX(x2, xx2);
2175 y1 = MIN(y1, yy1);
2176 y2 = MAX(y2, yy2);
2177 *x = x1;
2178 *y = y1;
2179 *w = x2 - x1;
2180 *h = y2 - y1;
2181 }
2182
2183 static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h)
2184 {
2185 int i, j;
2186
2187 rect_union(x, y, w, h, b);
2188
2189 for(i = 0; i < b->nrevs; i++)
2190 {
2191 for(j = 0; j < b->revs[i]->nbranches; j++)
2192 calc_subtree_size(b->revs[i]->branches[j], x, y, w, h);
2193 }
2194 }
2195
2196 static int branch_intersects(int top, int bottom, int left, branch_t *b)
2197 {
2198 int br = b->cx + b->tw/2;
2199 int bt = b->y - conf.branch_connect - conf.branch_margin/2;
2200 int bb = b->y + b->th + conf.branch_margin/2;
2201 return !(bt > bottom || bb < top || br >= left);
2202 }
2203
2204 static int branch_intersects_lr(int left, int right, int top, branch_t *b)
2205 {
2206 int bt = b->y + b->th/2;
2207 int bl = b->cx - conf.branch_connect - conf.branch_margin/2;
2208 int br = b->cx + b->tw + conf.branch_margin/2;
2209 return !(bl > right || br < left || bt >= top);
2210 }
2211
2212 static int kern_branch(rcsfile_t *rcs, branch_t *b)
2213 {
2214 int left = b->cx - b->tw/2;
2215 int top = b->y - conf.branch_connect - conf.branch_margin/2;
2216 int bottom = b->y + b->th + conf.branch_margin/2;
2217 int i;
2218 int xpos = 0;
2219
2220 for(i = 0; i < rcs->nbranches; i++)
2221 {
2222 branch_t *bp = rcs->branches[i];
2223 if(bp == b)
2224 continue;
2225 if(branch_intersects(top, bottom, left, bp))
2226 {
2227 int m = bp->cx + bp->tw/2 + conf.branch_margin;
2228 if(m > xpos)
2229 xpos = m;
2230 }
2231 }
2232 if(xpos && (b->cx - b->tw/2) - xpos > 0)
2233 {
2234 move_branch(b, xpos - (b->cx - b->tw/2), 0);
2235 return 1;
2236 }
2237 return 0;
2238 }
2239
2240 static int kern_branch_lr(rcsfile_t *rcs, branch_t *b)
2241 {
2242 int top = b->y - b->th/2;
2243 int left = b->cx - conf.branch_connect - conf.branch_margin/2;
2244 int right = b->cx + b->tw + conf.branch_margin/2;
2245 int i;
2246 int ypos = 0;
2247
2248 for(i = 0; i < rcs->nbranches; i++)
2249 {
2250 branch_t *bp = rcs->branches[i];
2251 if(bp == b)
2252 continue;
2253 if(branch_intersects_lr(left, right, top, bp))
2254 {
2255 int m = bp->y + bp->th/2 + conf.branch_margin;
2256 if(m > ypos)
2257 ypos = m;
2258 }
2259 }
2260 if(ypos && (b->y - b->th/2) - ypos > 0)
2261 {
2262 move_branch(b, 0, ypos - (b->y - b->th/2));
2263 return 1;
2264 }
2265 return 0;
2266 }
2267
2268 static int kern_tree(rcsfile_t *rcs)
2269 {
2270 int i;
2271 int moved;
2272 int safeguard;
2273 int totalmoved = 0;
2274 for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
2275 {
2276 moved = 0;
2277 for(i = 1; i < rcs->nbranches; i++)
2278 {
2279 if(conf.left_right)
2280 moved += kern_branch_lr(rcs, rcs->branches[i]);
2281 else
2282 moved += kern_branch(rcs, rcs->branches[i]);
2283 }
2284 totalmoved += moved;
2285 #ifdef DEBUG
2286 fprintf(stderr, "kern_tree: moved=%d\n", moved);
2287 #endif
2288 }
2289 if(!safeguard)
2290 stack_msg(MSG_WARN, "kern_tree: safeguard terminated possible infinite loop; please report.");
2291 return totalmoved;
2292 }
2293
2294 static int index_of_revision(revision_t *r)
2295 {
2296 branch_t *b = r->branch;
2297 int i;
2298 for(i = 0; i < b->nrevs; i++)
2299 {
2300 if(r == b->revs[i])
2301 return i;
2302 }
2303 stack_msg(MSG_ERR, "index_of_revision: Cannot find revision in branch\n");
2304 return 0;
2305 }
2306
2307 static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2308 {
2309 if(l) *l = br->cx - br->tw/2;
2310 if(r) *r = br->cx + br->tw/2;
2311 if(t) *t = br->y;
2312 if(b) *b = br->y + br->th + ((conf.branch_dupbox && br->nrevs) ? conf.rev_minline + br->h : 0);
2313 }
2314
2315 static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2316 {
2317 int extra = conf.branch_margin & 1; /* Correct +/-1 error on div 2 */
2318 branch_bbox(br, l, r, t, b);
2319 if(l) *l -= conf.branch_margin/2;
2320 if(r) *r += conf.branch_margin/2 + extra;
2321 if(t) *t -= conf.branch_connect + conf.branch_margin/2;
2322 if(b) *b += conf.branch_margin/2 + extra;
2323 }
2324
2325 static int branch_distance(branch_t *br1, branch_t *br2)
2326 {
2327 int l1, r1, t1, b1;
2328 int l2, r2, t2, b2;
2329 assert(br1 != NULL);
2330 assert(br2 != NULL);
2331 branch_bbox(br1, &l1, &r1, NULL, NULL);
2332 branch_bbox(br2, &l2, &r2, NULL, NULL);
2333 branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2334 branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2335 /* Return:
2336 * - 0 if branches have no horizontal overlap
2337 * - positive if b1 is left of b2
2338 * - negative if b2 is left of b1
2339 */
2340 if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
2341 return l1 < l2 ? l2 - r1 : -(l1 - r2);
2342 else
2343 return 0;
2344 }
2345
2346 static int space_needed(branch_t *br1, branch_t *br2)
2347 {
2348 int t1, b1;
2349 int t2, b2;
2350 assert(br1 != NULL);
2351 assert(br2 != NULL);
2352 assert(br1->cx < br2->cx); /* br1 must be left of br2 */
2353 branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2354 branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2355 /* Return:
2356 * - positive if top br1 is located lower than br2
2357 * - negatve is top br2 is located lower than br1
2358 */
2359 if(t1 > t2)
2360 return -(t1 - b2);
2361 else
2362 return t2 - b1;
2363 }
2364
2365 static void move_yr_branch(branch_t *b, int dy)
2366 {
2367 int i, j;
2368 #ifdef DEBUG
2369 /* fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);*/
2370 #endif
2371 b->y += dy;
2372 for(i = 0; i < b->nrevs; i++)
2373 {
2374 b->revs[i]->y += dy;
2375 for(j = 0; j < b->revs[i]->nbranches; j++)
2376 {
2377 #ifdef DEBUG
2378 /* fprintf(stderr, ".");*/
2379 #endif
2380 move_yr_branch(b->revs[i]->branches[j], dy);
2381 }
2382 }
2383 }
2384
2385 static void move_trunk(revision_t *r, int dy)
2386 {
2387 int i, j;
2388 branch_t *b = r->branch;
2389 b->th += dy;
2390 for(i = index_of_revision(r); i < b->nrevs; i++)
2391 {
2392 #ifdef DEBUG
2393 fprintf(stderr, "move_trunk: start %s, moving %s by %d (b's %d)\n", r->rev->rev, b->revs[i]->rev->rev, dy, b->revs[i]->nbranches);
2394 #endif
2395 b->revs[i]->y += dy;
2396 for(j = 0; j < b->revs[i]->nbranches; j++)
2397 {
2398 move_yr_branch(b->revs[i]->branches[j], dy);
2399 }
2400 }
2401 }
2402
2403 static int space_below(rcsfile_t *rcs, revision_t *r)
2404 {
2405 int i, j;
2406 int bl, br, bb;
2407 int space = INT_MAX;
2408 branch_t *b = r->branch;
2409 branch_t *minb = NULL;
2410
2411 branch_ext_bbox(b, &bl, &br, NULL, &bb);
2412 for(i = 0; i < rcs->nbranches; i++)
2413 {
2414 int tbl, tbr, tbt;
2415 branch_t *tb = rcs->branches[i];
2416 branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
2417 if(tb == b)
2418 continue;
2419 if(tbt > bb) /* Must be below our branch */
2420 {
2421 if(tb->branchpoint) /* Take account for the horiz connector */
2422 tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
2423 if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
2424 {
2425 int s = tbt - bb - conf.branch_connect;
2426 if(s < space)
2427 {
2428 space = s;
2429 minb = tb;
2430 }
2431 }
2432 }
2433 }
2434 if(b->branchpoint)
2435 {
2436 for(i = index_of_revision(r); i < b->nrevs; i++)
2437 {
2438 for(j = 0; j < b->revs[i]->nbranches; j++)
2439 {
2440 int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
2441 if(s < space)
2442 space = s;
2443 }
2444 }
2445 }
2446 #ifdef DEBUG
2447 fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
2448 #endif
2449 return space;
2450 }
2451
2452 static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
2453 {
2454 int i;
2455 int space = 0;
2456 int nlinks = 0;
2457 revision_t *r;
2458 branch_t *b;
2459 branch_t *ancestor;
2460 revision_t *branchpoint;
2461
2462 if(!tagbr->branchpoint || !colbr->branchpoint)
2463 {
2464 stack_msg(MSG_WARN, "space_available: Trying to stretch the top?");
2465 return 0;
2466 }
2467
2468 r = colbr->branchpoint;
2469 b = r->branch;
2470 branchpoint = tagbr->branchpoint;
2471 ancestor = branchpoint->branch;
2472 assert(b != NULL);
2473 assert(ancestor != NULL);
2474
2475 while(1)
2476 {
2477 int s;
2478 int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
2479 for(i = index_of_revision(r); i >= rtag; i--)
2480 {
2481 if(i > 0)
2482 s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2483 else
2484 s = b->revs[i]->y - (b->y + b->h);
2485 if(s < conf.rev_maxline)
2486 {
2487 space += conf.rev_maxline - s;
2488 nlinks++;
2489 }
2490 }
2491 s = space_below(rcs, r);
2492 if(s < space)
2493 space = s;
2494 #ifdef DEBUG
2495 if(space < 0)
2496 return -1;
2497 #endif
2498 if(b == ancestor)
2499 break;
2500 r = b->branchpoint;
2501 if(!r)
2502 {
2503 /* Not a common ancestor */
2504 r = colbr->branchpoint;
2505 b = r->branch;
2506 branchpoint = ancestor->branchpoint;
2507 if(!branchpoint)
2508 {
2509 stack_msg(MSG_WARN, "space_available: No common ancestor?");
2510 return 0;
2511 }
2512 ancestor = branchpoint->branch;
2513 assert(ancestor != NULL);
2514 nlinks = 0;
2515 space = 0;
2516 continue; /* Restart with a new ancestor */
2517 }
2518 b = r->branch;
2519 }
2520 if(nl)
2521 *nl = nlinks; /* Return the number of links that can stretch */
2522 if(bpcommon)
2523 *bpcommon = branchpoint; /* Return the ancestral branchpoint on the common branch */
2524 return space;
2525 }
2526
2527 static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
2528 {
2529 revision_t *r;
2530 revision_t *bpcommon = NULL;
2531 branch_t *ancestor = NULL;
2532 branch_t *b;
2533 int i;
2534 int space;
2535 int nlinks;
2536 int dy;
2537 int rest;
2538
2539 space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
2540 if(bpcommon)
2541 ancestor = bpcommon->branch;
2542
2543 #ifdef DEBUG
2544 if(space == -1)
2545 return 0;
2546 fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
2547 #endif
2548 if(space < totalstretch)
2549 return 0;
2550
2551 dy = totalstretch / nlinks;
2552 rest = totalstretch - dy * nlinks;
2553
2554 r = br1->branchpoint;
2555 b = r->branch;
2556 while(1)
2557 {
2558 int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
2559 for(i = index_of_revision(r); i >= rtag; i--)
2560 {
2561 int s, q;
2562 if(i > 0)
2563 s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2564 else
2565 s = b->revs[i]->y - (b->y + b->h);
2566 q = conf.rev_maxline - s;
2567 if(q > 0)
2568 {
2569 int d = rest ? rest/nlinks+1 : 0;
2570 if(q >= dy+d)
2571 {
2572 move_trunk(b->revs[i], dy+d);
2573 }
2574 else
2575 {
2576 move_trunk(b->revs[i], q);
2577 rest += dy+d - q;
2578 }
2579 rest -= d;
2580 nlinks--;
2581 }
2582 }
2583 if(b == ancestor)
2584 break;
2585 r = b->branchpoint;
2586 assert(r != NULL); /* else 'space_available' wouldn't have returned positively */
2587 b = r->branch;
2588 }
2589 return 1;
2590 }
2591
2592 static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
2593 {
2594 int i;
2595 int dist = INT_MAX;
2596 branch_t *col = NULL;
2597
2598 for(i = 0; i < rcs->nbranches; i++)
2599 {
2600 int t = branch_distance(rcs->branches[i], b);
2601 if(t > 0 && t < dist)
2602 {
2603 dist = t;
2604 col = rcs->branches[i];
2605 }
2606 }
2607 return col;
2608 }
2609
2610 static void auto_stretch(rcsfile_t *rcs)
2611 {
2612 int i;
2613 int safeguard;
2614
2615 for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
2616 {
2617 int bl, pr;
2618 branch_t *b = rcs->branches[i];
2619 if(!b->branchpoint)
2620 continue;
2621 branch_bbox(b, &bl, NULL, NULL, NULL);
2622 branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
2623 if(bl - conf.branch_margin - pr > 0)
2624 {
2625 branch_t *col;
2626 int spaceneeded;
2627 /* There is a potential to move branch b further left.
2628 * All branches obstructing this one from moving further
2629 * left must be originating from revisions below
2630 * b->branchpoint until a common ancester.
2631 * So, we search all branches for a branch that lies left
2632 * of b and is closest to b. This is then the collission
2633 * branch that needs to be moved.
2634 */
2635 col = find_collision_branch(rcs, b);
2636 if(!col)
2637 continue;
2638 spaceneeded = space_needed(col, b);
2639 if(spaceneeded < 0)
2640 continue;
2641 #ifdef DEBUG
2642 fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
2643 #endif
2644 /* Trace the collision branch back to find the common ancester
2645 * of both col and b. All revisions encountered while traversing
2646 * backwards must be stretched, including all revisions on the
2647 * common ancester from where the branches sprout.
2648 */
2649 if(stretch_branches(rcs, col, b, spaceneeded))
2650 {
2651 if(kern_tree(rcs))
2652 {
2653 /* Restart the process because movement can
2654 * cause more movement.
2655 */
2656 i = 0 - 1; /* -1 for the i++ of the loop */
2657 safeguard--; /* Prevent infinite loop, just in case */
2658 }
2659 /*return;*/
2660 }
2661 }
2662 }
2663 if(!safeguard)
2664 stack_msg(MSG_ERR, "auto_stretch: safeguard terminated possible infinite loop; please report.");
2665 }
2666
2667 static void fold_branch(rcsfile_t *rcs, revision_t *r)
2668 {
2669 int i, j;
2670 branch_t *btag = NULL;
2671
2672 for(i = 0; i < r->nbranches; i++)
2673 {
2674 branch_t *b = r->branches[i];
2675 if(!b->nrevs && b->ntags < 2)
2676 {
2677 /* No commits in this branch and no duplicate tags */
2678 if(!btag)
2679 btag = b;
2680 else
2681 {
2682 /* We have consecutive empty branches, fold */
2683 b->folded = 1;
2684 b->folded_to = btag;
2685 for(j = 0; j < rcs->nbranches; j++)
2686 {
2687 if(b == rcs->branches[j])
2688 {
2689 /* Zap the branch from the admin */
2690 memmove(&rcs->branches[j],
2691 &rcs->branches[j+1],
2692 (rcs->nbranches - j - 1)*sizeof(rcs->branches[0]));
2693 rcs->nbranches--;
2694 break;
2695 }
2696
2697 }
2698 memmove(&r->branches[i], &r->branches[i+1], (r->nbranches - i - 1)*sizeof(r->branches[0]));
2699 r->nbranches--;
2700 i--; /* We have one less now */
2701
2702 /* Add to the fold-list */
2703 btag->folds = xrealloc(btag->folds, (btag->nfolds+1) * sizeof(btag->folds[0]));
2704 btag->folds[btag->nfolds] = b;
2705 btag->nfolds++;
2706 }
2707 }
2708 else
2709 {
2710 if(!conf.branch_foldall)
2711 btag = NULL; /* Start a new box */
2712 /* Recursively fold sub-branches */
2713 for(j = 0; j < b->nrevs; j++)
2714 fold_branch(rcs, b->revs[j]);
2715 }
2716 }
2717 }
2718
2719 static void mark_subtree(branch_t *b)
2720 {
2721 int i, j;
2722 b->subtree_draw = 1;
2723 for(i = 0; i < b->nrevs; i++)
2724 {
2725 for(j = 0; j < b->revs[i]->nbranches; j++)
2726 mark_subtree(b->revs[i]->branches[j]);
2727 }
2728 }
2729
2730 static void make_layout(rcsfile_t *rcs)
2731 {
2732 int i, j;
2733 int x, y;
2734 int w, h;
2735 int w2;
2736
2737 /* Remove all unwanted revisions */
2738 if(conf.strip_untagged)
2739 {
2740 int fr = conf.strip_first_rev ? 0 : 1;
2741 for(i = 0; i < rcs->nbranches; i++)
2742 {
2743 branch_t *bp = rcs->branches[i];
2744 for(j = fr; j < bp->nrevs-1; j++)
2745 {
2746 if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)
2747 {
2748 memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
2749 bp->nrevs--;
2750 bp->revs[j]->stripped = 1;
2751 j--;
2752 }
2753 }
2754 }
2755 }
2756
2757 /* Find the sub-tree(s) we want to see */
2758 if(conf.branch_subtree && conf.branch_subtree[0])
2759 {
2760 branch_t **b;
2761 revision_t **r;
2762 rev_t rev;
2763 int k;
2764 char *tag = conf.branch_subtree;
2765
2766 /* First translate any symbolic tag to a real branch/revision number */
2767 if(rcs->tags)
2768 {
2769 for(k = 0; k < rcs->tags->ntags; k++)
2770 {
2771 if(!strcmp(conf.branch_subtree, rcs->tags->tags[k]->tag))
2772 {
2773 if(rcs->tags->tags[k]->rev->isbranch)
2774 tag = rcs->tags->tags[k]->rev->branch;
2775 else
2776 tag = rcs->tags->tags[k]->rev->rev;
2777 break;
2778 }
2779 }
2780 }
2781
2782 /* Find the corresponding branch */
2783 rev.branch = tag;
2784 rev.rev = NULL;
2785 rev.isbranch = 1;
2786 b = bsearch(&rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
2787 if(b)
2788 {
2789 if((*b)->branchpoint)
2790 {
2791 subtree_branch = *b;
2792 for(k = 0; k < (*b)->branchpoint->nbranches; k++)
2793 mark_subtree((*b)->branchpoint->branches[k]);
2794 }
2795 /*
2796 * else -> we want everything.
2797 * This happens for the top level branch because it has no
2798 * branchpoint. We do not set the subtree_branch, which then
2799 * results in drawing the whole tree as if we did not select a
2800 * particular branch.
2801 */
2802 }
2803 else
2804 {
2805 /* Maybe it is a revision we want all subtrees from */
2806 rev.rev = tag;
2807 rev.branch = NULL;
2808 rev.isbranch = 0;
2809 r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
2810 if(r)
2811 {
2812 if((*r)->nbranches)
2813 {
2814 subtree_branch = (*r)->branches[0];
2815 subtree_rev = *r;
2816 for(k = 0; k < (*r)->nbranches; k++)
2817 mark_subtree((*r)->branches[k]);
2818 }
2819 /*
2820 * else -> we select everything.
2821 * This happens for the any revision that has no branches.
2822 * We do not set the subtree_branch, which then results in
2823 * drawing the whole tree as if we did not select a
2824 * particular revision's branches.
2825 */
2826 }
2827 }
2828 }
2829
2830 /* Fold all empty branches in one box on the same branchpoint */
2831 if(conf.branch_fold)
2832 {
2833 for(i = 0; i < rcs->branches[0]->nrevs; i++)
2834 {
2835 if(rcs->branches[0]->revs[i]->nbranches > 0)
2836 fold_branch(rcs, rcs->branches[0]->revs[i]);
2837 }
2838 }
2839
2840 /* Remove all unwanted tags */
2841 for(i = 0; i < rcs->nbranches; i++)
2842 {
2843 branch_t *bp = rcs->branches[i];
2844 for(j = 0; j < bp->nrevs; j++)
2845 {
2846 revision_t *r = bp->revs[j];
2847 int k;
2848 for(k = 0; k < r->ntags; k++)
2849 {
2850 if(r->tags[k]->ignore > 0)
2851 {
2852 memmove(&r->tags[k], &r->tags[k+1], (r->ntags-k-1) * sizeof(r->tags[0]));
2853 r->ntags--;
2854 k--;
2855 }
2856 }
2857 }
2858 }
2859
2860 /* Calculate the box-sizes of the revisions */
2861 for(i = 0; i < rcs->nsrev; i++)
2862 {
2863 revision_t *rp;
2864 int w;
2865 int h;
2866 rp = rcs->srev[i];
2867 rp->revtext = expand_string(conf.rev_text, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
2868 w = get_swidth(rp->revtext, &conf.rev_text_font);
2869 j = get_swidth(rp->rev->rev, &conf.rev_font);
2870 if(j > w)
2871 w = j;
2872 h = get_sheight(rp->revtext, &conf.rev_text_font);
2873 if(!conf.rev_hidenumber)
2874 h += get_sheight(rp->rev->rev, &conf.rev_font);
2875 for(j = 0; j < rp->ntags; j++)
2876 {
2877 int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
2878 int th;
2879 if(ww > w) w = ww;
2880 th = get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
2881 rp->tags[j]->yofs = h + th/2 + conf.rev_tspace;
2882 h += th;
2883 }
2884 rp->w = w + conf.rev_lspace + conf.rev_rspace;
2885 rp->h = h + conf.rev_tspace + conf.rev_bspace;
2886 }
2887
2888 /* Calculate the box-sizes of the branches */
2889 for(i = 0; i < rcs->nbranches; i++)
2890 {
2891 branch_t *bp = rcs->branches[i];
2892 int w;
2893 int h;
2894 if(!bp->nfolds)
2895 {
2896 w = get_swidth(bp->branch->branch, &conf.branch_font);
2897 if(conf.rev_hidenumber)
2898 h = 0;
2899 else
2900 h = get_sheight(bp->branch->branch, &conf.branch_font);
2901 for(j = 0; j < bp->ntags; j++)
2902 {
2903 int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
2904 if(ww > w) w = ww;
2905 h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
2906 }
2907 }
2908 else
2909 {
2910 int h1, h2;
2911 int w1, w2;
2912 int fw;
2913 w1 = get_swidth(bp->branch->branch, &conf.branch_font);
2914 w1 += get_swidth(" ", &conf.branch_font);
2915 w2 = get_swidth(bp->tags[0]->tag, &conf.branch_tag_font);
2916 fw = w1;
2917 w = w1 + w2;
2918 h1 = get_sheight(bp->branch->branch, &conf.branch_font);
2919 h2 = get_sheight(bp->tags[0]->tag, &conf.branch_tag_font);
2920 h = MAX(h1, h2);
2921 for(j = 0; j < bp->nfolds; j++)
2922 {
2923 w1 = get_swidth(bp->folds[j]->branch->branch, &conf.branch_font);
2924 w1 += get_swidth(" ", &conf.branch_font);
2925 w2 = get_swidth(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2926 if(w1 > fw)
2927 fw = w1;
2928 if(w1 + w2 > w)
2929 w = w1 + w2;
2930 h1 = get_sheight(bp->folds[j]->branch->branch, &conf.branch_font);
2931 h2 = get_sheight(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2932 h += MAX(h1, h2);
2933 }
2934 bp->fw = fw;
2935 }
2936 w += conf.branch_lspace + conf.branch_rspace;
2937 h += conf.branch_tspace + conf.branch_bspace;
2938 bp->w = w;
2939 bp->h = h;
2940 if(conf.left_right)
2941 {
2942 for(j = 0; j < bp->nrevs; j++)
2943 {
2944 if(bp->revs[j]->h > h)
2945 h = bp->revs[j]->h;
2946 w += bp->revs[j]->w + conf.rev_minline;
2947 }
2948 if(conf.branch_dupbox && bp->nrevs)
2949 w += bp->w + conf.rev_minline;
2950 }
2951 else
2952 {
2953 for(j = 0; j < bp->nrevs; j++)
2954 {
2955 if(bp->revs[j]->w > w)
2956 w = bp->revs[j]->w;
2957 h += bp->revs[j]->h + conf.rev_minline;
2958 }
2959 if(conf.branch_dupbox && bp->nrevs)
2960 h += bp->h + conf.rev_minline;
2961 }
2962 bp->th = h;
2963 bp->tw = w;
2964 }
2965
2966 /* Calculate the relative positions of revs in a branch */
2967 if(conf.left_right)
2968 {
2969 for(i = 0; i < rcs->nbranches; i++)
2970 {
2971 branch_t *b = rcs->branches[i];
2972 y = b->th/2;
2973 x = b->w;
2974 b->y = y;
2975 b->cx = 0;
2976 for(j = 0; j < b->nrevs; j++)
2977 {
2978 x += conf.rev_minline;
2979 b->revs[j]->y = y;
2980 b->revs[j]->cx = x;
2981 x += b->revs[j]->w;
2982 }
2983 }
2984 }
2985 else
2986 {
2987 for(i = 0; i < rcs->nbranches; i++)
2988 {
2989 branch_t *b = rcs->branches[i];
2990 x = b->tw/2;
2991 y = b->h;
2992 b->cx = x;
2993 b->y = 0;
2994 for(j = 0; j < b->nrevs; j++)
2995 {
2996 y += conf.rev_minline;
2997 b->revs[j]->cx = x;
2998 b->revs[j]->y = y;
2999 y += b->revs[j]->h;
3000 }
3001 }
3002 }
3003
3004 /* Initially reposition the branches from bottom to top progressively right */
3005 if(conf.left_right)
3006 {
3007 x = rcs->branches[0]->y;
3008 w2 = rcs->branches[0]->th / 2;
3009 for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3010 {
3011 initial_reposition_branch_lr(rcs->branches[0]->revs[i], &x, &w2);
3012 }
3013 }
3014 else
3015 {
3016 x = rcs->branches[0]->cx;
3017 w2 = rcs->branches[0]->tw / 2;
3018 for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
3019 {
3020 initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
3021 }
3022 }
3023
3024 /* Initially move branches left if there is room */
3025 kern_tree(rcs);
3026
3027 /* Try to kern the branches more by expanding the inter-revision spacing */
3028 if(conf.auto_stretch && !conf.left_right)
3029 auto_stretch(rcs);
3030
3031 /* Calculate overall image size */
3032 if(conf.left_right)
3033 {
3034 x = rcs->branches[0]->cx;
3035 y = rcs->branches[0]->y - rcs->branches[0]->th/2;
3036 }
3037 else
3038 {
3039 x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
3040 y = rcs->branches[0]->y;
3041 }
3042 w = rcs->branches[0]->tw;
3043 h = rcs->branches[0]->th;
3044 for(i = 1; i < rcs->nbranches; i++)
3045 rect_union(&x, &y, &w, &h, rcs->branches[i]);
3046 rcs->tw = w;
3047 rcs->th = h;
3048
3049 /* Flip the entire tree */
3050 if(conf.upside_down)
3051 {
3052 if(conf.left_right)
3053 {
3054 x += rcs->tw;
3055 for(i = 0; i < rcs->nbranches; i++)
3056 {
3057 branch_t *b = rcs->branches[i];
3058 for(j = 0; j < b->nrevs; j++)
3059 {
3060 revision_t *r = b->revs[j];
3061 r->cx = x - r->cx - r->w;
3062 }
3063 b->cx = x - b->cx - b->w;
3064 }
3065 }
3066 else
3067 {
3068 y += rcs->th;
3069 for(i = 0; i < rcs->nbranches; i++)
3070 {
3071 branch_t *b = rcs->branches[i];
3072 for(j = 0; j < b->nrevs; j++)
3073 {
3074 revision_t *r = b->revs[j];
3075 r->y = y - r->y - r->h;
3076 }
3077 b->y = y - b->y - b->h;
3078 }
3079 }
3080 }
3081
3082 /* Relocate the lot if we only draw a sub-tree */
3083 if(subtree_branch)
3084 {
3085 int xx, yy;
3086
3087 if(subtree_branch->folded) /* Fix the reference if the branch got folded */
3088 subtree_branch = subtree_branch->folded_to;
3089
3090 xx = conf.left_right ? subtree_branch->cx : subtree_branch->cx - subtree_branch->tw/2;
3091 yy = conf.left_right ? subtree_branch->y - subtree_branch->th/2 : subtree_branch->y;
3092 if(subtree_branch != rcs->branches[0])
3093 {
3094 if(conf.left_right)
3095 xx -= conf.branch_connect;
3096 else
3097 yy -= conf.branch_connect;
3098 }
3099 for(i = 0; i < rcs->nbranches; i++)
3100 move_branch(rcs->branches[i], -xx, -yy);
3101 }
3102
3103 /* Move everything w.r.t. the top-left margin */
3104 for(i = 0; i < rcs->nbranches; i++)
3105 move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
3106 }
3107
3108 /*
3109 **************************************************************************
3110 * Imagemap functions
3111 **************************************************************************
3112 */
3113 static void map_merge_box(rcsfile_t *rcs, FILE *fp, revision_t *fr, revision_t *tr, gdImagePtr im, int x1, int y1, int x2, int y2)
3114 {
3115 char *href = expand_string(conf.map_merge_href, rcs, tr, tr->rev, fr->rev, NULL);
3116 char *alt = expand_string(conf.map_merge_alt, rcs, tr, tr->rev, fr->rev, NULL);
3117 const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3118
3119 if(x1 > 0 && x2 > 0 && y1 > 0 && y2 > 0)
3120 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3121 href, x1, y1, x2, y2, alt, htp);
3122 xfree(alt);
3123 xfree(href);
3124
3125 if(im)
3126 {
3127 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3128 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3129 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3130 }
3131 }
3132
3133 static void map_merges(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3134 {
3135 int i;
3136 int tagh2 = get_sheight("Hg", &conf.tag_font) / 2;
3137 int bm = conf.branch_margin / 2;
3138
3139 for(i = 0; i < rcs->nmerges; i++)
3140 {
3141 revision_t *fr = rcs->merges[i].from->logrev;
3142 revision_t *tr = rcs->merges[i].to->logrev;
3143 int x1, x2, y1, y2;
3144 if(!fr || !tr || fr == tr)
3145 continue; /* This can happen with detached tags and self-references */
3146 if(conf.left_right)
3147 {
3148 if(fr->branch == tr->branch)
3149 {
3150 y1 = fr->y - fr->h/2;
3151 y2 = tr->y - tr->h/2;
3152 }
3153 else
3154 {
3155 if(fr->y < tr->y)
3156 {
3157 y1 = fr->y + fr->h/2;
3158 y2 = tr->y - tr->h/2;
3159 }
3160 else
3161 {
3162 y1 = fr->y - fr->h/2;
3163 y2 = tr->y + tr->h/2;
3164 }
3165 }
3166 x1 = fr->cx + fr->w/2;
3167 x2 = tr->cx + tr->w/2;
3168 }
3169 else
3170 {
3171 if(fr->branch == tr->branch)
3172 {
3173 x1 = fr->cx - fr->w/2;
3174 x2 = tr->cx - tr->w/2;
3175 }
3176 else
3177 {
3178 if(fr->cx < tr->cx)
3179 {
3180 x1 = fr->cx + fr->w/2;
3181 x2 = tr->cx - tr->w/2;
3182 }
3183 else
3184 {
3185 x1 = fr->cx - fr->w/2;
3186 x2 = tr->cx + tr->w/2;
3187 }
3188 }
3189 y1 = fr->y + rcs->merges[i].from->yofs;
3190 y2 = tr->y + rcs->merges[i].to->yofs;
3191 }
3192
3193 if(conf.left_right)
3194 {
3195 if(fr->branch == tr->branch)
3196 {
3197 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3198 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3199 }
3200 else
3201 {
3202 if(y1 > y2)
3203 {
3204 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3205 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2, x2+bm, y2+bm);
3206 }
3207 else
3208 {
3209 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1, x1+bm, y1+bm);
3210 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3211 }
3212 }
3213 }
3214 else
3215 {
3216 if(fr->branch == tr->branch)
3217 {
3218 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3219 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3220 }
3221 else
3222 {
3223 if(x1 > x2)
3224 {
3225 map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3226 map_merge_box(rcs, fp, fr, tr, im, x2, y2-tagh2, x2+bm, y2+tagh2);
3227 }
3228 else
3229 {
3230 map_merge_box(rcs, fp, fr, tr, im, x1, y1-tagh2, x1+bm, y1+tagh2);
3231 map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3232 }
3233 }
3234 }
3235 }
3236 }
3237
3238 static void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3239 {
3240 int i, j;
3241 const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3242
3243 switch(conf.html_level)
3244 {
3245 case HTMLLEVEL_4:
3246 fprintf(fp, "<map name=\"%s\" id=\"%s\">\n", conf.map_name, conf.map_name);
3247 break;
3248 case HTMLLEVEL_X:
3249 fprintf(fp, "<map id=\"%s\">\n", conf.map_name);
3250 break;
3251 default:
3252 fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
3253 }
3254
3255 for(i = 0; i < rcs->nbranches; i++)
3256 {
3257 branch_t *b = rcs->branches[i];
3258 tag_t *tag = b->ntags ? b->tags[0] : NULL;
3259 char *bhref;
3260 char *balt;
3261 int x1;
3262 int x2;
3263 int y1;
3264 int y2;
3265
3266 if(subtree_branch && !b->subtree_draw)
3267 continue;
3268
3269 bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
3270 balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
3271
3272 if(!b->nfolds)
3273 {
3274 if(conf.left_right)
3275 {
3276 x1 = b->cx;
3277 y1 = b->y - b->h/2;
3278 x2 = b->cx + b->w;
3279 y2 = b->y + b->h/2;
3280 }
3281 else
3282 {
3283 x1 = b->cx - b->w/2;
3284 y1 = b->y;
3285 x2 = b->cx + b->w/2;
3286 y2 = b->y + b->h;
3287 }
3288 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3289 bhref, x1, y1, x2, y2, balt, htp);
3290 if(im)
3291 {
3292 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3293 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3294 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3295 }
3296 }
3297 else
3298 {
3299 int yy1, yy2, yy;
3300 if(conf.left_right)
3301 {
3302 x1 = b->cx + conf.branch_lspace;
3303 y1 = b->y - b->h/2 + conf.branch_tspace;
3304 }
3305 else
3306 {
3307 x1 = b->cx - b->w/2 + conf.branch_lspace;
3308 y1 = b->y + conf.branch_tspace;
3309 }
3310 x2 = x1 + b->w - conf.branch_rspace;
3311
3312 yy1 = get_sheight(b->branch->branch, &conf.branch_font);
3313 yy2 = get_sheight(b->tags[0]->tag, &conf.branch_tag_font);
3314 yy = MAX(yy1, yy2);
3315 y2 = y1 + yy;
3316 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3317 bhref, x1, y1, x2, y2, balt, htp);
3318
3319 y1 += yy;
3320 y2 += yy;
3321 for(j = 0; j < b->nfolds; j++)
3322 {
3323 branch_t *fb = b->folds[j];
3324 tag_t *t = fb->tags[0];
3325 xfree(bhref);
3326 xfree(balt);
3327 bhref = expand_string(conf.map_branch_href, rcs, NULL, fb->branch, NULL, t);
3328 balt = expand_string(conf.map_branch_alt, rcs, NULL, fb->branch, NULL, t);
3329 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3330 bhref, x1, y1, x2, y2, balt, htp);
3331 yy1 = get_sheight(fb->branch->branch, &conf.branch_font);
3332 yy2 = get_sheight(fb->tags[0]->tag, &conf.branch_tag_font);
3333 yy = MAX(yy1, yy2);
3334 y1 += yy;
3335 y2 += yy;
3336 }
3337 }
3338
3339 for(j = 0; j < b->nrevs; j++)
3340 {
3341 revision_t *r = b->revs[j];
3342 revision_t* r1;
3343 int xoff = 1;
3344 int yoff = 1;
3345 char *href;
3346 char *alt;
3347
3348 tag = r->ntags ? r->tags[0] : NULL;
3349 href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
3350 alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
3351 if(conf.left_right)
3352 {
3353 x1 = r->cx;
3354 y1 = r->y - r->h/2;
3355 x2 = r->cx + r->w;
3356 y2 = r->y + r->h/2;
3357 }
3358 else
3359 {
3360 x1 = r->cx - r->w/2;
3361 y1 = r->y;
3362 x2 = r->cx + r->w/2;
3363 y2 = r->y + r->h;
3364 }
3365 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3366 href, x1, y1, x2, y2, alt, htp);
3367 if(im)
3368 {
3369 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3370 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3371 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3372 }
3373 xfree(href);
3374 xfree(alt);
3375 if(j > 0 || b->branchpoint)
3376 {
3377 if(j > 0)
3378 {
3379 r1 = b->revs[j-1];
3380 if(conf.left_right)
3381 {
3382 yoff = MIN(r->h, r1->h)/4;
3383 x1 = conf.upside_down ? r1->cx : r1->cx + r1->w;
3384 }
3385 else
3386 {
3387 xoff = MIN(r->w, r1->w)/4;
3388 y1 = conf.upside_down ? r1->y : r1->y + r1->h;
3389 }
3390 }
3391 else
3392 {
3393 r1 = b->branchpoint;
3394 if(conf.left_right)
3395 {
3396 yoff = MIN(r->h, b->h)/4;
3397 x1 = conf.upside_down ? b->cx : b->cx + b->w;
3398 }
3399 else
3400 {
3401 xoff = MIN(r->w, b->w)/4;
3402 y1 = conf.upside_down ? b->y : b->y + b->h;
3403 }
3404 }
3405 if(conf.left_right)
3406 {
3407 y1 = r->y - yoff;
3408 y2 = r->y + yoff;
3409 x2 = conf.upside_down ? r->cx + r->w : r->cx;
3410 yoff = 0;
3411 }
3412 else
3413 {
3414 x1 = r->cx - xoff;
3415 x2 = r->cx + xoff;
3416 y2 = conf.upside_down ? r->y + r->h : r->y;
3417 xoff = 0;
3418 }
3419 if(x1 > x2)
3420 {
3421 int tt = x1;
3422 x1 = x2;
3423 x2 = tt;
3424 }
3425 if(y1 > y2)
3426 {
3427 int tt = y1;
3428 y1 = y2;
3429 y2 = tt;
3430 }
3431 href = expand_string(conf.map_diff_href, rcs, r, r->rev, r1->rev, tag);
3432 alt = expand_string(conf.map_diff_alt, rcs, r, r->rev, r1->rev, tag);
3433 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3434 href,
3435 x1+xoff, y1+yoff, x2-xoff, y2-yoff,
3436 alt, htp);
3437 if(im)
3438 {
3439 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3440 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3441 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3442 }
3443 xfree(href);
3444 xfree(alt);
3445 }
3446 }
3447 if(conf.branch_dupbox && b->nrevs)
3448 {
3449 if(conf.left_right)
3450 {
3451 x1 = conf.upside_down ? b->cx + b->w - b->tw : b->cx - b->w + b->tw;
3452 y1 = b->y - b->h/2;
3453 x2 = x1 + b->w;
3454 y2 = b->y + b->h/2;
3455 }
3456 else
3457 {
3458 x1 = b->cx - b->w/2;
3459 y1 = conf.upside_down ? b->y + b->h - b->th : b->y - b->h + b->th;
3460 x2 = b->cx + b->w/2;
3461 y2 = y1 + b->h;
3462 }
3463 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3464 bhref, x1, y1, x2, y2, balt, htp);
3465 if(im)
3466 {
3467 gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3468 gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3469 gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3470 }
3471 }
3472 xfree(bhref);
3473 xfree(balt);
3474 }
3475
3476 map_merges(rcs, fp, im);
3477
3478 fprintf(fp, "</map>\n");
3479 }
3480
3481 /*
3482 **************************************************************************
3483 * Program entry
3484 **************************************************************************
3485 */
3486 static const char usage_str[] =
3487 "Usage: cvsgraph [options] <file>\n"
3488 " -b Add a branch box at both sides of the trunk (config value is negated)\n"
3489 " -c <file> Read alternative config from <file>\n"
3490 " -d <level> Enable debug mode at <level>\n"
3491 " -h This message\n"
3492 " -i Generate an imagemap instead of image\n"
3493 " -I <file> Also write the imagemap to <file>\n"
3494 " -k Auto stretch the tree (config value is negated)\n"
3495 " -M <name> Use <name> as imagemap name\n"
3496 " -m <mod> Use <mod> as cvs module\n"
3497 " -o <file> Output to <file>\n"
3498 " -O <opt=val> Set option opt to value val\n"
3499 " -q Be quiet (i.e. no warnings)\n"
3500 " -r <path> Use <path> as cvsroot path\n"
3501 " -s Strip untagged revisions (config value is negated)\n"
3502 " -S Also strip the first revision (config value is negated)\n"
3503 " -u Upside down image (mirror vertically; config value is negated)\n"
3504 " -V Print version and exit\n"
3505 " -x [34x] Specify level of HTML 3.2 (default), 4.0 or XHTML\n"
3506 " -[0-9] <txt> Use <txt> for expansion\n"
3507 ;
3508
3509 #define VERSION_STR "1.5.0"
3510 #define NOTICE_STR "Copyright (c) 2001,2002,2003,2004 B.Stultiens"
3511
3512 static void append_slash(char **path)
3513 {
3514 int l;
3515 assert(path != NULL);
3516 assert(*path != NULL);
3517 l = strlen(*path);
3518 if(!l || (*path)[l-1] == '/')
3519 return;
3520 *path = xrealloc(*path, l+2);
3521 strcat(*path, "/");
3522 }
3523
3524 int main(int argc, char *argv[])
3525 {
3526 extern int rcs_flex_debug;
3527 extern int rcsdebug;
3528 int optc;
3529 char *confpath = NULL;
3530 char *outfile = NULL;
3531 char *cvsroot = NULL;
3532 char *cvsmodule = NULL;
3533 int imagemap = 0;
3534 int upsidedown = 0;
3535 int bdupbox = 0;
3536 int stripuntag = 0;
3537 int stripfirst = 0;
3538 int autostretch = 0;
3539 int htmllevel = 0;
3540 char *imgmapname = NULL;
3541 char *imgmapfile = NULL;
3542 int lose = 0;
3543 FILE *fp;
3544 char *rcsfilename;
3545 rcsfile_t *rcs;
3546 gdImagePtr im;
3547
3548 while((optc = getopt(argc, argv, "0:1:2:3:4:5:6:7:8:9:bc:d:hI:ikM:m:O:o:qr:SsuVx:")) != EOF)
3549 {
3550 switch(optc)
3551 {
3552 case 'b':
3553 bdupbox = 1;
3554 break;
3555 case 'c':
3556 confpath = xstrdup(optarg);
3557 break;
3558 case 'd':
3559 debuglevel = strtol(optarg, NULL, 0);
3560 break;
3561 case 'I':
3562 imgmapfile = xstrdup(optarg);
3563 break;
3564 case 'i':
3565 imagemap = 1;
3566 break;
3567 case 'k':
3568 autostretch = 1;
3569 break;
3570 case 'M':
3571 imgmapname = xstrdup(optarg);
3572 break;
3573 case 'm':
3574 cvsmodule = xstrdup(optarg);
3575 break;
3576 case 'O':
3577 stack_option(optarg);
3578 break;
3579 case 'o':
3580 outfile = xstrdup(optarg);
3581 break;
3582 case 'q':
3583 quiet = 1;
3584 break;
3585 case 'r':
3586 cvsroot = xstrdup(optarg);
3587 break;
3588 case 'S':
3589 stripfirst = 1;
3590 break;
3591 case 's':
3592 stripuntag = 1;
3593 break;
3594 case 'u':
3595 upsidedown = 1;
3596 break;
3597 case 'V':
3598 fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
3599 return 0;
3600 case 'x':
3601 switch(optarg[0])
3602 {
3603 case '3':
3604 htmllevel = HTMLLEVEL_3;
3605 break;
3606 case '4':
3607 htmllevel = HTMLLEVEL_4;
3608 break;
3609 case 'x':
3610 htmllevel = HTMLLEVEL_X;
3611 break;
3612 default:
3613 fprintf(stderr, "Invalid HTML level in -x\n");
3614 lose++;
3615 }
3616 break;
3617 case 'h':
3618 fprintf(stdout, "%s", usage_str);
3619 return 0;
3620 default:
3621 if(isdigit(optc))
3622 {
3623 conf.expand[optc-'0'] = xstrdup(optarg);
3624 }
3625 else
3626 lose++;
3627 }
3628 }
3629
3630 if(lose)
3631 {
3632 fprintf(stderr, "%s", usage_str);
3633 return 1;
3634 }
3635
3636 if(debuglevel)
3637 {
3638 setvbuf(stdout, NULL, 0, _IONBF);
3639 setvbuf(stderr, NULL, 0, _IONBF);
3640 }
3641 rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
3642 rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
3643
3644 /* Set defaults */
3645 conf.tag_font.gdfont = gdFontTiny;
3646 conf.rev_font.gdfont = gdFontTiny;
3647 conf.branch_font.gdfont = gdFontTiny;
3648 conf.branch_tag_font.gdfont = gdFontTiny;
3649 conf.title_font.gdfont = gdFontTiny;
3650 conf.rev_text_font.gdfont = gdFontTiny;
3651 conf.msg_font.gdfont = gdFontTiny;
3652
3653 conf.anti_alias = 1;
3654 conf.thick_lines = 1;
3655 conf.branch_fold = 1;
3656
3657 conf.cvsroot = xstrdup("");
3658 conf.cvsmodule = xstrdup("");
3659 conf.date_format = xstrdup("%d-%b-%Y %H:%M:%S");
3660 conf.title = xstrdup("");
3661 conf.map_name = xstrdup("CvsGraphImageMap");
3662 conf.map_branch_href = xstrdup("href=\"unset: conf.map_branch_href\"");
3663 conf.map_branch_alt = xstrdup("alt=\"%B\"");
3664 conf.map_rev_href = xstrdup("href=\"unset: conf.map_rev_href\"");
3665 conf.map_rev_alt = xstrdup("alt=\"%R\"");
3666 conf.map_diff_href = xstrdup("href=\"unset: conf.map_diff_href\"");
3667 conf.map_diff_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3668 conf.map_merge_href = xstrdup("href=\"unset: conf.map_merge_href\"");
3669 conf.map_merge_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3670 conf.rev_text = xstrdup("%d");
3671 conf.branch_subtree = xstrdup("");
3672 conf.tag_ignore = xstrdup("");
3673 conf.merge_from = xstrdup("");
3674 conf.merge_to = xstrdup("");
3675 conf.merge_arrows = 1;
3676 conf.arrow_width = ARROW_WIDTH;
3677 conf.arrow_length = ARROW_LENGTH;
3678
3679 conf.color_bg = white_color;
3680 conf.branch_bgcolor = white_color;
3681 conf.branch_color = black_color;
3682 conf.branch_tag_color = black_color;
3683 conf.rev_color = black_color;
3684 conf.rev_bgcolor = white_color;
3685 conf.merge_color = black_color;
3686 conf.tag_color = black_color;
3687 conf.title_color = black_color;
3688 conf.rev_text_color = black_color;
3689 conf.msg_color = black_color;
3690
3691 conf.image_quality = 100;
3692 conf.rev_maxline = -1; /* Checked later to set to default */
3693
3694 read_config(confpath);
3695
3696 if(conf.rev_maxline == -1) conf.rev_maxline = 5 * conf.rev_minline;
3697
3698 /* Set overrides */
3699 if(cvsroot) conf.cvsroot = cvsroot;
3700 if(cvsmodule) conf.cvsmodule = cvsmodule;
3701 if(imgmapname) conf.map_name = imgmapname;
3702 if(upsidedown) conf.upside_down = !conf.upside_down;
3703 if(bdupbox) conf.branch_dupbox = !conf.branch_dupbox;
3704 if(stripuntag) conf.strip_untagged = !conf.strip_untagged;
3705 if(stripfirst) conf.strip_first_rev = !conf.strip_first_rev;
3706 if(autostretch) conf.auto_stretch = !conf.auto_stretch;
3707 if(htmllevel) conf.html_level = htmllevel;
3708
3709 if(conf.rev_minline >= conf.rev_maxline)
3710 {
3711 if(conf.auto_stretch)
3712 stack_msg(MSG_WARN, "Auto stretch is only possible if rev_minline < rev_maxline");
3713 conf.auto_stretch = 0;
3714 }
3715
3716 if(conf.thick_lines < 1)
3717 conf.thick_lines = 1;
3718 if(conf.thick_lines > 11)
3719 conf.thick_lines = 11;
3720
3721 append_slash(&conf.cvsroot);
3722 append_slash(&conf.cvsmodule);
3723
3724 if(optind >= argc)
3725 {
3726 #ifdef __WIN32__
3727 /* Bad hack for DOS/Windows */
3728 if(setmode(fileno(stdin), O_BINARY) == -1)
3729 {
3730 perror("Set binary mode for stdin");
3731 return 1;
3732 }
3733 #endif
3734 rcsfilename = NULL;
3735 }
3736 else
3737 rcsfilename = argv[optind];
3738
3739 rcs = get_rcsfile(conf.cvsroot, conf.cvsmodule, rcsfilename);
3740 if(!rcs)
3741 return 1;
3742
3743 if(debuglevel & DEBUG_RCS_FILE)
3744 dump_rcsfile(rcs);
3745
3746 if(!reorganise_branches(rcs))
3747 return 1;
3748
3749 assign_tags(rcs);
3750 find_merges(rcs);
3751
3752 if(outfile)
3753 {
3754 if((fp = fopen(outfile, "wb")) == NULL)
3755 {
3756 perror(outfile);
3757 return 1;
3758 }
3759 }
3760 else
3761 {
3762 fp = stdout;
3763 #ifdef __WIN32__
3764 /* Bad hack for DOS/Windows */
3765 if(setmode(fileno(fp), O_BINARY) == -1)
3766 {
3767 perror("Set binary mode for stdout");
3768 return 1;
3769 }
3770 #endif
3771 }
3772
3773 make_layout(rcs);
3774
3775 if(!imagemap)
3776 {
3777 /* Create an image */
3778 im = make_image(rcs);
3779 #ifdef DEBUG_IMAGEMAP
3780 {
3781 FILE *nulfile = fopen("/dev/null", "w");
3782 make_imagemap(rcs, nulfile, im);
3783 fclose(nulfile);
3784 }
3785 #endif
3786 switch(conf.image_type)
3787 {
3788 #ifdef HAVE_IMAGE_GIF
3789 # ifndef HAVE_IMAGE_PNG
3790 default:
3791 # endif
3792 case IMAGE_GIF:
3793 gdImageGif(im, fp);
3794 break;
3795 #endif
3796 #ifdef HAVE_IMAGE_PNG
3797 default:
3798 case IMAGE_PNG:
3799 gdImagePng(im, fp);
3800 break;
3801 #endif
3802 #ifdef HAVE_IMAGE_JPEG
3803 # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
3804 default:
3805 # endif
3806 case IMAGE_JPEG:
3807 gdImageJpeg(im, fp, conf.image_quality);
3808 break;
3809 #endif
3810 }
3811
3812 gdImageDestroy(im);
3813 }
3814 else
3815 {
3816 /* Create an imagemap */
3817 make_imagemap(rcs, fp, NULL);
3818 }
3819
3820 /* Also create imagemap to file if requested */
3821 if(imgmapfile)
3822 {
3823 FILE *ifp = fopen(imgmapfile, "wb");
3824 if(!ifp)
3825 {
3826 perror(imgmapfile);
3827 return 1;
3828 }
3829 make_imagemap(rcs, ifp, NULL);
3830 fclose(ifp);
3831 }
3832
3833 if(outfile)
3834 fclose(fp);
3835
3836 return 0;
3837 }

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0