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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0