/[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.44 - (show annotations)
Sun Aug 15 16:58:22 2004 UTC (13 years, 2 months ago) by bertho
Branch: MAIN
CVS Tags: REL_1_5_0
Changes since 1.43: +464 -79 lines
File MIME type: text/plain
- Added drawing of partial trees. You now can select a subtree based on a
  revision number, branch number or symbolic tag to show only that part of the
  tree. New config option branch_subtree selects which part is shown. If the
  subtree is empty, then the whole tree is shown.

- Most error and warning messages are now displayed in the graph, instead of
  being written to stderr. This enables you to see an image eventhough errors
  might be present. The old method would generate a corrupt image.
  New configuration options include msg_color and msg_font to control the looks
  of it. Messages are always printed at the bottom of the image.

- Fixed a bug in the folding code where subtrees would not fold correctly.

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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0