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

Annotate of /cvsgraph/cvsgraph.c

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


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

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

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

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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0