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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0