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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0