/[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.16 - (hide annotations)
Tue Dec 18 22:50:12 2001 UTC (15 years, 10 months ago) by bertho
Branch: MAIN
Changes since 1.15: +154 -52 lines
File MIME type: text/plain
- Added upside-down imaging of the tree
- Added extra branch-box at the bottom/top of the trunk
- Added stripping of untagged revisions
1 bertho 1.1 /*
2     * CvsGraph graphical representation generator of brances and revisions
3     * of a file in cvs/rcs.
4     *
5     * Copyright (C) 2001 B. Stultiens
6     *
7     * This program is free software; you can redistribute it and/or modify
8     * it under the terms of the GNU General Public License as published by
9     * the Free Software Foundation; either version 2 of the License, or
10     * (at your option) any later version.
11     *
12     * This program is distributed in the hope that it will be useful,
13     * but WITHOUT ANY WARRANTY; without even the implied warranty of
14     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15     * GNU General Public License for more details.
16     *
17     * You should have received a copy of the GNU General Public License
18     * along with this program; if not, write to the Free Software
19     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20     */
21    
22 bertho 1.14 #include "config.h"
23    
24 bertho 1.1 #include <stdio.h>
25     #include <stdlib.h>
26     #include <unistd.h>
27     #include <string.h>
28     #include <assert.h>
29     #include <sys/types.h>
30     #include <sys/stat.h>
31     #include <sys/wait.h>
32     #include <fcntl.h>
33     #include <regex.h>
34     #include <errno.h>
35 bertho 1.8 #include <ctype.h>
36 bertho 1.9 #include <time.h>
37    
38 bertho 1.14 #ifdef HAVE_GETOPT_H
39     # include <getopt.h>
40     #endif
41    
42 bertho 1.1 #include <gd.h>
43     #include <gdfontt.h>
44    
45     #include "cvsgraph.h"
46     #include "utils.h"
47     #include "readconf.h"
48 bertho 1.7 #include "rcs.h"
49 bertho 1.1
50 bertho 1.5 #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
51     # error No image output format available. Check libgd
52     #endif
53    
54    
55 bertho 1.1 /*#define DEBUG 1*/
56    
57     #define CONFFILENAME "cvsgraph.conf"
58    
59     #ifndef ETCDIR
60     # define ETCDIR "/usr/local/etc"
61     #endif
62    
63     #ifndef MAX
64     # define MAX(a,b) ((a) > (b) ? (a) : (b))
65     #endif
66    
67     #ifndef MIN
68     # define MIN(a,b) ((a) < (b) ? (a) : (b))
69     #endif
70    
71     #define ALIGN_HL 0x00
72     #define ALIGN_HC 0x01
73     #define ALIGN_HR 0x02
74 bertho 1.9 #define ALIGN_HX 0x0f
75 bertho 1.1 #define ALIGN_VT 0x00
76     #define ALIGN_VC 0x10
77     #define ALIGN_VB 0x20
78     #define ALIGN_VX 0xf0
79 bertho 1.9
80 bertho 1.1 /*
81     **************************************************************************
82     * Globals
83     **************************************************************************
84     */
85    
86     config_t conf;
87 bertho 1.7 int debuglevel;
88 bertho 1.9 color_t white_color = {255, 255, 255, 0};
89     color_t black_color = {0, 0, 0, 0};
90    
91 bertho 1.1
92     /*
93     **************************************************************************
94 bertho 1.7 * Dubug routines
95 bertho 1.1 **************************************************************************
96     */
97 bertho 1.7 void dump_rev(char *p, rev_t *r)
98 bertho 1.1 {
99 bertho 1.7 printf("%s", p);
100     if(r)
101     printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
102     else
103     printf("<null>\n");
104 bertho 1.1 }
105    
106 bertho 1.7 void dump_id(char *p, char *d)
107 bertho 1.1 {
108 bertho 1.7 printf("%s", p);
109     if(d)
110     printf("'%s'\n", d);
111     else
112     printf("<null>\n");
113 bertho 1.1 }
114    
115 bertho 1.7 void dump_idrev(char *p, idrev_t *t)
116 bertho 1.1 {
117 bertho 1.7 printf("%s", p);
118     if(t)
119 bertho 1.1 {
120 bertho 1.7 printf("'%s' -> ", t->id);
121     dump_rev("", t->rev);
122 bertho 1.1 }
123     else
124 bertho 1.7 printf("<null>\n");
125 bertho 1.1 }
126    
127 bertho 1.7 void dump_tag(char *p, tag_t *t)
128 bertho 1.1 {
129 bertho 1.7 printf("%s", p);
130     if(t)
131 bertho 1.1 {
132 bertho 1.7 printf("'%s' -> ", t->tag);
133     dump_rev("", t->rev);
134 bertho 1.1 }
135 bertho 1.7 else
136     printf("<null>\n");
137 bertho 1.1 }
138    
139 bertho 1.7 void dump_delta(char *p, delta_t *d)
140 bertho 1.1 {
141 bertho 1.7 int i;
142     printf("%sdelta.rev : ", p);
143     dump_rev("", d->rev);
144     printf("%sdelta.date : %s\n", p, d->date);
145     printf("%sdelta.author: %s\n", p, d->author);
146     printf("%sdelta.state : %s\n", p, d->state);
147     for(i = 0; d->branches && i < d->branches->nrevs; i++)
148 bertho 1.1 {
149 bertho 1.7 printf("%sdelta.branch: ", p);
150     dump_rev("", d->branches->revs[i]);
151 bertho 1.1 }
152 bertho 1.7 printf("%sdelta.next : ", p);
153     dump_rev("", d->next);
154     printf("\n");
155 bertho 1.1 }
156    
157 bertho 1.7 void dump_dtext(char *p, dtext_t *d)
158 bertho 1.1 {
159 bertho 1.7 printf("%sdtext.rev : ", p);
160     dump_rev("", d->rev);
161     printf("%sdtext.log : %d bytes\n", p, d->log ? strlen(d->log) : -1);
162     printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
163     printf("\n");
164 bertho 1.1 }
165    
166 bertho 1.7 void dump_rcsfile(rcsfile_t *rcs)
167 bertho 1.1 {
168 bertho 1.7 int i;
169     printf("root : '%s'\n", rcs->root);
170     printf("module : '%s'\n", rcs->module);
171     printf("file : '%s'\n", rcs->file);
172     dump_rev("head : ", rcs->head);
173     dump_rev("branch : ", rcs->branch);
174     printf("access :\n");
175     for(i = 0; rcs->access && i < rcs->access->nids; i++)
176     dump_id("\t", rcs->access->ids[i]);
177     printf("tags :\n");
178     for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
179     dump_tag("\t", rcs->tags->tags[i]);
180     printf("locks :%s\n", rcs->strict ? " (strict)" : "");
181     for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
182     dump_idrev("\t", rcs->locks->idrevs[i]);
183     printf("comment: '%s'\n", rcs->comment);
184     printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
185     printf("deltas :\n");
186     for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
187     dump_delta("\t", rcs->deltas->deltas[i]);
188     printf("desc : '%s'\n", rcs->desc);
189     printf("dtexts :\n");
190     for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
191     dump_dtext("\t", rcs->dtexts->dtexts[i]);
192    
193     fflush(stdout);
194 bertho 1.1 }
195    
196 bertho 1.7 /*
197     **************************************************************************
198     * Read the rcs file
199     **************************************************************************
200     */
201     rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
202 bertho 1.1 {
203 bertho 1.7 char *cmd = NULL;
204     int rv;
205 bertho 1.1
206 bertho 1.9 cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
207     sprintf(cmd, "%s%s%s", cvsroot, module, file);
208 bertho 1.15 if(!(rcsin = fopen(cmd, "rb")))
209 bertho 1.9 {
210     perror(cmd);
211 bertho 1.1 return NULL;
212 bertho 1.9 }
213 bertho 1.7 input_file = cmd;
214     line_number = 1;
215     rv = rcsparse();
216     fclose(rcsin);
217     if(rv)
218 bertho 1.1 return NULL;
219 bertho 1.7 xfree(cmd);
220     input_file = NULL;
221     rcsfile->root = xstrdup(cvsroot);
222     rcsfile->module = xstrdup(module);
223     rcsfile->file = xstrdup(file);
224     return rcsfile;
225 bertho 1.1 }
226    
227     /*
228     **************************************************************************
229     * Sort and find helpers
230     **************************************************************************
231     */
232 bertho 1.5 int count_dots(const char *s)
233     {
234     int i;
235     for(i = 0; *s; s++)
236     {
237     if(*s == '.')
238     i++;
239     }
240     return i;
241     }
242    
243 bertho 1.7 int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
244 bertho 1.5 {
245     int d1, d2;
246 bertho 1.7 char *c1, *c2;
247 bertho 1.5 char *v1, *v2;
248     char *s1, *s2;
249     int retval = 0;
250     assert(r1 != NULL);
251     assert(r2 != NULL);
252 bertho 1.7 if(bcmp)
253     {
254     assert(r1->branch != NULL);
255     assert(r2->branch != NULL);
256     c1 = r1->branch;
257     c2 = r2->branch;
258     }
259     else
260     {
261     assert(r1->rev != NULL);
262     assert(r2->rev != NULL);
263     c1 = r1->rev;
264     c2 = r2->rev;
265     }
266 bertho 1.5
267 bertho 1.7 d1 = count_dots(c1);
268     d2 = count_dots(c2);
269 bertho 1.5 if(d1 != d2)
270     {
271     return d1 - d2;
272     }
273    
274 bertho 1.7 s1 = v1 = xstrdup(c1);
275     s2 = v2 = xstrdup(c2);
276 bertho 1.5 while(1)
277     {
278     char *vc1 = strchr(s1, '.');
279     char *vc2 = strchr(s2, '.');
280     if(vc1 && vc2)
281     *vc1 = *vc2 = '\0';
282     if(*s1 && *s2)
283     {
284     d1 = atoi(s1);
285     d2 = atoi(s2);
286     if(d1 != d2)
287     {
288     retval = d1 - d2;
289     break;
290     }
291     }
292     if(!vc1 || !vc2)
293     break;
294     s1 = vc1 + 1;
295     s2 = vc2 + 1;
296     }
297     xfree(v1);
298     xfree(v2);
299     return retval;
300     }
301    
302 bertho 1.7 /*
303     **************************************************************************
304     * Reorganise the rcsfile for the branches
305     *
306     * Basically, we have a list of deltas (i.e. administrative info on
307     * revisions) and a list of delta text (the actual logs and diffs).
308     * The deltas are linked through the 'next' and the 'branches' fields
309     * which describe the tree-structure of revisions.
310     * The reorganisation means that we put each delta and corresponding
311     * delta text in a revision structure and assign it to a specific
312     * branch. This is required because we want to be able to calculate
313     * the bounding boxes of each branch. The revisions expand vertically
314     * and the branches expand horizontally.
315     * The reorganisation is performed in these steps:
316     * 1 - sort deltas and detla text on revision number for quick lookup
317     * 2 - start at the denoted head revision:
318     * * create a branch structure and add this revision
319     * * for each 'branches' in the delta do:
320     * - walk all 'branches' of the delta and recursively goto 2
321     * with the denoted branch delta as new head
322     * - backlink the newly create sub-branch to the head revision
323     * so that we can draw them recursively
324     * * set head to the 'next' field and goto 2 until no next is
325     * available
326     * 3 - update the administration
327     **************************************************************************
328     */
329     int sort_delta(const void *d1, const void *d2)
330     {
331     return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
332     }
333    
334     int search_delta(const void *r, const void *d)
335 bertho 1.1 {
336 bertho 1.7 return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
337 bertho 1.1 }
338    
339 bertho 1.7 delta_t *find_delta(delta_t **dl, int n, rev_t *r)
340 bertho 1.1 {
341 bertho 1.7 delta_t **d;
342     d = bsearch(r, dl, n, sizeof(*dl), search_delta);
343     if(!d)
344     return NULL;
345     return *d;
346 bertho 1.1 }
347    
348 bertho 1.7 int sort_dtext(const void *d1, const void *d2)
349 bertho 1.1 {
350 bertho 1.7 return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
351 bertho 1.1 }
352    
353 bertho 1.7 int search_dtext(const void *r, const void *d)
354 bertho 1.1 {
355 bertho 1.7 return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
356 bertho 1.1 }
357    
358 bertho 1.7 dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
359 bertho 1.1 {
360 bertho 1.7 dtext_t **d;
361     d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
362     if(!d)
363 bertho 1.1 return NULL;
364 bertho 1.7 return *d;
365     }
366    
367     rev_t *dup_rev(const rev_t *r)
368     {
369     rev_t *t = xmalloc(sizeof(*t));
370     t->rev = xstrdup(r->rev);
371     t->branch = xstrdup(r->branch);
372     t->isbranch = r->isbranch;
373     return t;
374     }
375    
376     branch_t *new_branch(delta_t *d, dtext_t *t)
377     {
378     branch_t *b = xmalloc(sizeof(*b));
379     revision_t *r = xmalloc(sizeof(*r));
380     r->delta = d;
381     r->dtext = t;
382     r->rev = d->rev;
383     r->branch = b;
384     b->branch = dup_rev(d->rev);
385     b->branch->isbranch = 1;
386     b->nrevs = 1;
387     b->revs = xmalloc(sizeof(b->revs[0]));
388     b->revs[0] = r;
389     return b;
390     }
391    
392     revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
393     {
394     revision_t *r = xmalloc(sizeof(*r));
395     r->delta = d;
396     r->dtext = t;
397     r->rev = d->rev;
398     r->branch = b;
399     b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
400     b->revs[b->nrevs] = r;
401     b->nrevs++;
402     return r;
403     }
404    
405     void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
406     {
407     branch_t *b;
408     dtext_t *text;
409     revision_t *currev;
410    
411     assert(head != NULL);
412    
413     if(head->flag)
414     {
415     fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
416     return;
417     }
418     head->flag++;
419     text = find_dtext(sdt, nsdt, head->rev);
420    
421     /* Create a new branch for this head */
422     b = new_branch(head, text);
423     *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
424     (*bl)[*nbl] = b;
425     (*nbl)++;
426     currev = b->revs[0];
427     while(1)
428     {
429     /* Process all sub-branches */
430     if(head->branches)
431     {
432     int i;
433     for(i = 0; i < head->branches->nrevs; i++)
434     {
435     delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
436     int btag = *nbl;
437     if(!d)
438     continue;
439     build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
440    
441     /* Set the new branch's origin */
442     (*bl)[btag]->branchpoint = currev;
443    
444     /* Add branch to this revision */
445     currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
446     currev->branches[currev->nbranches] = (*bl)[btag];
447     currev->nbranches++;
448     }
449     }
450    
451     /* Walk through the next list */
452     if(!head->next)
453     return;
454 bertho 1.9
455 bertho 1.7 head = find_delta(sdl, nsdl, head->next);
456     if(!head)
457     {
458     fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);
459     return;
460     }
461     if(head->flag)
462     {
463     fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);
464     return;
465     }
466     head->flag++;
467     text = find_dtext(sdt, nsdt, head->rev);
468     currev = add_to_branch(b, head, text);
469     }
470     }
471    
472     int reorganise_branches(rcsfile_t *rcs)
473     {
474     delta_t **sdelta;
475     int nsdelta;
476     dtext_t **sdtext;
477     int nsdtext;
478     delta_t *head;
479     branch_t **bl;
480     int nbl;
481     int i;
482    
483     assert(rcs->deltas != NULL);
484     assert(rcs->head != NULL);
485    
486     /* Make a new list for quick lookup */
487     nsdelta = rcs->deltas->ndeltas;
488     sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
489     memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
490     qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
491    
492     /* Do the same for the delta text */
493     nsdtext = rcs->dtexts->ndtexts;
494     sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
495     memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
496     qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
497    
498     /* Start from the head of the trunk */
499     head = find_delta(sdelta, nsdelta, rcs->head);
500     if(!head)
501     {
502     fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
503     return 0;
504     }
505     bl = NULL;
506     nbl = 0;
507     build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
508    
509     /* Reverse the head branch */
510     for(i = 0; i < bl[0]->nrevs/2; i++)
511     {
512     revision_t *r;
513     r = bl[0]->revs[i];
514     bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
515     bl[0]->revs[bl[0]->nrevs-i-1] = r;
516     }
517    
518     /* Update the branch-number of the head because it was reversed */
519     xfree(bl[0]->branch->branch);
520     bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
521    
522     /* Keep the admin */
523     rcs->branches = bl;
524     rcs->nbranches = nbl;
525     rcs->sdelta = sdelta;
526     rcs->nsdelta = nsdelta;
527     rcs->sdtext = sdtext;
528     rcs->nsdtext = nsdtext;
529     rcs->active = bl[0];
530     return 1;
531     }
532    
533     /*
534     **************************************************************************
535     * Assign the symbolic tags to the revisions and branches
536     *
537     * The tags point to revision numbers somewhere in the tree structure
538     * of branches and revisions. First we make a sorted list of all
539     * revisions and then we assign each tag to the proper revision.
540     **************************************************************************
541     */
542     int sort_revision(const void *r1, const void *r2)
543     {
544     return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
545     }
546    
547     int search_revision(const void *t, const void *r)
548     {
549     return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
550     }
551    
552     int sort_branch(const void *b1, const void *b2)
553     {
554     return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
555 bertho 1.1 }
556    
557 bertho 1.7 int search_branch(const void *t, const void *b)
558 bertho 1.1 {
559 bertho 1.7 return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
560 bertho 1.1 }
561    
562 bertho 1.7 char *previous_rev(const char *c)
563 bertho 1.1 {
564 bertho 1.7 int dots = count_dots(c);
565     char *cptr;
566     char *r;
567     if(!dots)
568 bertho 1.9 {
569     fprintf(stderr, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
570 bertho 1.7 return xstrdup("1.0"); /* FIXME: don't know what the parent is */
571 bertho 1.9 }
572 bertho 1.7 if(dots & 1)
573     {
574     /* Is is a revision we want the parent of */
575     r = xstrdup(c);
576     cptr = strrchr(r, '.');
577     assert(cptr != NULL);
578     if(dots == 1)
579     {
580 bertho 1.9 fprintf(stderr, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
581 bertho 1.7 /* FIXME: What is the parent of 1.1? */
582     cptr[1] = '\0';
583     strcat(r, "0");
584     return r;
585     }
586     /* Here we have a "x.x[.x.x]+" case */
587     *cptr = '\0';
588     cptr = strrchr(r, '.');
589     assert(cptr != NULL);
590     *cptr = '\0';
591     return r;
592     }
593     /* It is a branch we want the parent of */
594     r = xstrdup(c);
595     cptr = strrchr(r, '.');
596     assert(cptr != NULL);
597     *cptr = '\0';
598     return r;
599 bertho 1.1 }
600    
601 bertho 1.7 int assign_tags(rcsfile_t *rcs)
602 bertho 1.1 {
603     int i;
604 bertho 1.7 int nr;
605    
606     for(i = nr = 0; i < rcs->nbranches; i++)
607     nr += rcs->branches[i]->nrevs;
608    
609     rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
610     rcs->nsrev = nr;
611     for(i = nr = 0; i < rcs->nbranches; i++)
612     {
613     memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
614     nr += rcs->branches[i]->nrevs;
615     }
616    
617     qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
618     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
619    
620     if(!rcs->branch)
621     {
622     /* The main trunk is the active trunk */
623     rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
624     rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
625     rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
626     rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
627     rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
628     rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
629     rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
630     rcs->tags->ntags++;
631     }
632    
633     /* We should have at least two tags (HEAD and MAIN) */
634     assert(rcs->tags != 0);
635    
636     for(i = 0; i < rcs->tags->ntags; i++)
637     {
638     tag_t *t = rcs->tags->tags[i];
639     if(t->rev->isbranch)
640     {
641     branch_t **b;
642     add_btag:
643     b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
644     if(!b)
645     {
646     rev_t rev;
647     revision_t **r;
648     /* This happens for the magic branch numbers if there are
649 bertho 1.12 * no commits within the new branch yet. So, we add the
650     * branch and try to continue.
651 bertho 1.7 */
652     rev.rev = previous_rev(t->rev->branch);
653     rev.branch = NULL;
654     rev.isbranch = 0;
655     r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
656     xfree(rev.rev);
657     if(!r)
658     {
659 bertho 1.12 if(!quiet)
660     fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);
661 bertho 1.7 }
662     else
663     {
664     rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
665     rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
666     rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
667     rcs->branches[rcs->nbranches]->branchpoint = *r;
668     (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
669     (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
670     (*r)->nbranches++;
671     rcs->nbranches++;
672     /* Resort the branches */
673     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
674     goto add_btag;
675     }
676     }
677     else
678     {
679     branch_t *bb = *b;
680     bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
681     bb->tags[bb->ntags] = t;
682     bb->ntags++;
683     }
684     }
685     else
686     {
687     revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
688     if(!r)
689 bertho 1.12 {
690     if(!quiet)
691     fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
692     }
693 bertho 1.7 else
694     {
695     revision_t *rr = *r;
696     rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
697     rr->tags[rr->ntags] = t;
698     rr->ntags++;
699     }
700     }
701     }
702    
703     /* We need to reset the first in the list of branches to the
704     * active branch to ensure the drawing of all
705     */
706     if(rcs->active != rcs->branches[0])
707 bertho 1.1 {
708 bertho 1.7 branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
709     branch_t *t;
710     assert(b != NULL);
711     t = *b;
712     *b = rcs->branches[0];
713     rcs->branches[0] = t;
714 bertho 1.1 }
715 bertho 1.7 return 1;
716 bertho 1.1 }
717    
718     /*
719     **************************************************************************
720 bertho 1.8 * String expansion
721     **************************************************************************
722     */
723     static char *_string;
724     static int _nstring;
725     static int _nastring;
726    
727     void add_string_str(const char *s)
728     {
729     int l = strlen(s) + 1;
730     if(_nstring + l > _nastring)
731     {
732     _nastring += 128;
733     _string = xrealloc(_string, _nastring * sizeof(_string[0]));
734     }
735     memcpy(_string+_nstring, s, l);
736     _nstring += l-1;
737     }
738    
739     void add_string_ch(int ch)
740     {
741     char buf[2];
742     buf[0] = ch;
743     buf[1] = '\0';
744     add_string_str(buf);
745     }
746    
747 bertho 1.9 void add_string_date(const char *d)
748     {
749     struct tm tm, *tmp;
750     int n;
751     time_t t;
752     char *buf;
753     int nbuf;
754    
755     memset(&tm, 0, sizeof(tm));
756     n = sscanf(d, "%d.%d.%d.%d.%d.%d",
757     &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
758     &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
759     tm.tm_mon--;
760     if(tm.tm_year > 1900)
761     tm.tm_year -= 1900;
762     t = mktime(&tm);
763     if(n != 6 || t == (time_t)(-1))
764     {
765     add_string_str("<invalid date>");
766     return;
767     }
768    
769     tmp = localtime(&t);
770     nbuf = strlen(conf.date_format) * 16; /* Should be enough to hold all types of expansions */
771     buf = xmalloc(nbuf);
772     strftime(buf, nbuf, conf.date_format, tmp);
773     add_string_str(buf);
774     xfree(buf);
775     }
776    
777     char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
778 bertho 1.8 {
779     char nb[32];
780     char nr[32];
781     char *base;
782    
783     if(!s)
784     return xstrdup("");
785    
786     _nstring = 0;
787     if(_string)
788     _string[0] = '\0';
789    
790     sprintf(nb, "%d", rcs->nbranches);
791     sprintf(nr, "%d", rcs->nsrev);
792     for(; *s; s++)
793     {
794     if(*s == '%')
795     {
796     switch(*++s)
797     {
798 bertho 1.12 case 'c':
799     case 'C':
800     add_string_str(conf.cvsroot);
801     if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
802     {
803     /* Strip the trailing '/' */
804     _nstring--;
805     _string[_nstring] = '\0';
806     }
807     break;
808 bertho 1.8 case 'f':
809     case 'F':
810     base = strrchr(rcs->file, '/');
811     if(!base)
812     add_string_str(rcs->file);
813     else
814     add_string_str(base+1);
815     if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
816     {
817     _nstring -= 2;
818     _string[_nstring] = '\0';
819     }
820     break;
821     case 'p':
822     base = strrchr(rcs->file, '/');
823     if(base)
824     {
825     char *c = xstrdup(rcs->file);
826     base = strrchr(c, '/');
827     assert(base != NULL);
828     base[1] = '\0';
829     add_string_str(c);
830     xfree(c);
831     }
832 bertho 1.9 /*
833     * We should not add anything here because we can encounter
834     * a completely empty path, in which case we do not want
835     * to add any slash. This prevents a inadvertent root redirect.
836     *
837     * else
838     * add_string_str("/");
839     */
840 bertho 1.8 break;
841 bertho 1.12 case 'm':
842     case 'M':
843     add_string_str(conf.cvsmodule);
844     if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
845     {
846     /* Strip the trailing '/' */
847     _nstring--;
848     _string[_nstring] = '\0';
849     }
850     break;
851 bertho 1.8 case 'r': add_string_str(nr); break;
852     case 'b': add_string_str(nb); break;
853     case '%': add_string_ch('%'); break;
854     case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
855     case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
856     case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
857     case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
858     case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
859     case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
860     case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
861     case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
862     case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
863     case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
864     case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
865 bertho 1.9 case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
866 bertho 1.8 case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
867     case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
868 bertho 1.9 case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
869     case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
870     case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
871 bertho 1.8 default:
872     add_string_ch('%');
873     add_string_ch(*s);
874     break;
875     }
876     }
877     else
878     add_string_ch(*s);
879     }
880     return xstrdup(_string);
881     }
882    
883     /*
884     **************************************************************************
885 bertho 1.1 * Drawing routines
886     **************************************************************************
887     */
888     int get_swidth(const char *s, font_t *f)
889     {
890 bertho 1.9 int n;
891     int m;
892     if(!s || !*s)
893 bertho 1.1 return 0;
894 bertho 1.9 for(n = m = 0; *s; n++, s++)
895     {
896     if(*s == '\n')
897     {
898     if(n > m)
899     m = n;
900     n = 0;
901     }
902     }
903     if(n > m)
904     m = n;
905     return m * (*f)->w;
906 bertho 1.1 }
907    
908     int get_sheight(const char *s, font_t *f)
909     {
910     int nl;
911 bertho 1.9 if(!s || !*s)
912 bertho 1.1 return 0;
913     for(nl = 1; *s; s++)
914     {
915     if(*s == '\n' && s[1])
916     nl++;
917     }
918     return nl * (*f)->h;
919     }
920    
921 bertho 1.9 void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
922 bertho 1.1 {
923     int r2 = 2*r;
924     gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
925     gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
926     gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
927     gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
928 bertho 1.9 if(conf.box_shadow)
929     {
930     gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, black_color.id);
931     gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, black_color.id);
932     }
933 bertho 1.1 if(r)
934     {
935     gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
936     gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
937     gdImageArc(im, x1+r, y2-r, r2, r2, 90, 180, color->id);
938     gdImageArc(im, x2-r, y2-r, r2, r2, 0, 90, color->id);
939 bertho 1.9 if(conf.box_shadow)
940     {
941     /* FIXME: Pixelization is not correct here */
942     gdImageArc(im, x2-r+1, y2-r+1, r2, r2, 0, 90, black_color.id);
943     }
944 bertho 1.1 }
945 bertho 1.9 gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
946 bertho 1.1 }
947    
948     void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
949     {
950     int xx, yy;
951     switch(align & ALIGN_HX)
952     {
953     default:
954     case ALIGN_HL: xx = 0; break;
955     case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
956     case ALIGN_HR: xx = -get_swidth(s, f); break;
957     }
958     switch(align & ALIGN_VX)
959     {
960     default:
961     case ALIGN_VT: yy = 0; break;
962     case ALIGN_VC: yy = -get_sheight(s, f)/2; break;
963     case ALIGN_VB: yy = -get_sheight(s, f); break;
964     }
965     gdImageString(im, *f, x+xx+1, y+yy, s, c->id);
966     }
967    
968 bertho 1.9 void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
969     {
970     char *t;
971     char *d;
972     d = s = xstrdup(s);
973     do
974     {
975     t = strchr(s, '\n');
976     if(t)
977     *t = '\0';
978     draw_string(im, s, f, x, y, align, c);
979     y += get_sheight(s, f);
980     s = t+1;
981     } while(t);
982     xfree(d);
983     }
984    
985 bertho 1.16 void draw_rev(gdImagePtr im, revision_t *r)
986 bertho 1.1 {
987 bertho 1.16 int lx = r->cx - r->w/2;
988 bertho 1.1 int rx = lx + r->w;
989     int i;
990 bertho 1.16 int ty = r->y;
991 bertho 1.9 draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
992 bertho 1.1 ty += conf.rev_tspace;
993 bertho 1.16 draw_string(im, r->rev->rev, &conf.rev_font, r->cx, ty, ALIGN_HC, &conf.rev_color);
994 bertho 1.1 ty += get_sheight(r->rev->rev, &conf.rev_font);
995 bertho 1.16 draw_stringnl(im, r->revtext, &conf.rev_text_font, r->cx, ty, ALIGN_HC, &conf.rev_text_color);
996 bertho 1.9 ty += get_sheight(r->revtext, &conf.rev_text_font);
997 bertho 1.1 for(i = 0; i < r->ntags; i++)
998     {
999 bertho 1.16 draw_string(im, r->tags[i]->tag, &conf.tag_font, r->cx, ty, ALIGN_HC, &conf.tag_color);
1000 bertho 1.1 ty += get_sheight(r->tags[i]->tag, &conf.tag_font);
1001     }
1002     }
1003    
1004 bertho 1.16 void draw_branch_box(gdImagePtr im, branch_t *b, int offset)
1005 bertho 1.1 {
1006 bertho 1.16 int lx = b->cx - b->w/2;
1007 bertho 1.1 int rx = lx + b->w;
1008 bertho 1.16 int i;
1009 bertho 1.1 int yy;
1010 bertho 1.16
1011     draw_rbox(im, lx, b->y+offset, rx, b->y+b->h+offset, 5, &conf.branch_color, &conf.branch_bgcolor);
1012 bertho 1.1 yy = conf.branch_tspace;
1013 bertho 1.16 draw_string(im, b->branch->branch, &conf.branch_font, b->cx, b->y+yy+offset, ALIGN_HC, &conf.branch_color);
1014 bertho 1.7 yy += get_sheight(b->branch->branch, &conf.branch_font);
1015     for(i = 0; i < b->ntags; i++)
1016 bertho 1.1 {
1017 bertho 1.16 draw_string(im, b->tags[i]->tag, &conf.branch_font, b->cx, b->y+yy+offset, ALIGN_HC, &conf.branch_color);
1018 bertho 1.7 yy += get_sheight(b->tags[i]->tag, &conf.branch_font);
1019 bertho 1.1 }
1020 bertho 1.16 }
1021    
1022     void draw_branch(gdImagePtr im, branch_t *b)
1023     {
1024     int yy;
1025     int i;
1026     /*draw_rbox(im, cx-b->tw/2-1, ty-1, cx+b->tw/2+1, ty+b->th+1, 0, &conf.title_color);*/
1027     draw_branch_box(im, b, 0);
1028 bertho 1.1
1029 bertho 1.16 if(conf.upside_down)
1030     {
1031     yy = b->y;
1032     for(i = 0; i < b->nrevs; i++)
1033     {
1034     revision_t *r = b->revs[i];
1035     gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, conf.rev_color.id);
1036     draw_rev(im, r);
1037     yy = r->y;
1038     }
1039     if(conf.branch_mirror)
1040     {
1041     gdImageLine(im, b->cx, yy, b->cx, yy-conf.rev_minline, conf.rev_color.id);
1042     draw_branch_box(im, b, yy-b->y-b->h-conf.rev_minline);
1043     }
1044     }
1045     else
1046 bertho 1.1 {
1047 bertho 1.16 yy = b->y + b->h;
1048     for(i = 0; i < b->nrevs; i++)
1049     {
1050     revision_t *r = b->revs[i];
1051     gdImageLine(im, r->cx, yy, r->cx, r->y, conf.rev_color.id);
1052     draw_rev(im, r);
1053     yy = r->y + r->h;
1054     }
1055     if(conf.branch_mirror)
1056     {
1057     gdImageLine(im, b->cx, yy, b->cx, yy+conf.rev_minline, conf.rev_color.id);
1058     draw_branch_box(im, b, yy-b->y+conf.rev_minline);
1059     }
1060 bertho 1.1 }
1061     }
1062    
1063     void draw_connector(gdImagePtr im, branch_t *b)
1064     {
1065     revision_t *r = b->branchpoint;
1066 bertho 1.7 int x1 = r->cx + r->w/2 + 2;
1067 bertho 1.1 int y1 = r->y + r->h/2;
1068 bertho 1.7 int x2 = b->cx;
1069 bertho 1.1 int y2 = b->y;
1070 bertho 1.16 if(conf.upside_down)
1071     y2 += b->h;
1072 bertho 1.1 gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1073     gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1074     }
1075    
1076 bertho 1.9 void alloc_color(gdImagePtr im, color_t *c)
1077     {
1078     c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1079     }
1080    
1081 bertho 1.7 gdImagePtr make_image(rcsfile_t *rcs)
1082 bertho 1.1 {
1083     gdImagePtr im;
1084     int i;
1085 bertho 1.8 char *cptr;
1086 bertho 1.1
1087 bertho 1.15 cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1088     i = get_swidth(cptr, &conf.title_font);
1089     if(rcs->tw+conf.margin_left+conf.margin_right > i)
1090     i = rcs->tw+conf.margin_left+conf.margin_right;
1091     im = gdImageCreate(i, rcs->th+conf.margin_top+conf.margin_bottom);
1092 bertho 1.9 alloc_color(im, &conf.color_bg);
1093     alloc_color(im, &conf.tag_color);
1094     alloc_color(im, &conf.rev_color);
1095     alloc_color(im, &conf.rev_bgcolor);
1096     alloc_color(im, &conf.rev_text_color);
1097     alloc_color(im, &conf.branch_color);
1098     alloc_color(im, &conf.branch_bgcolor);
1099     alloc_color(im, &conf.title_color);
1100     alloc_color(im, &black_color);
1101     alloc_color(im, &white_color);
1102 bertho 1.1
1103     for(i = 0; i < rcs->nbranches; i++)
1104 bertho 1.16 draw_branch(im, rcs->branches[i]);
1105 bertho 1.1 for(i = 0; i < rcs->nbranches; i++)
1106     {
1107     if(rcs->branches[i]->branchpoint)
1108     draw_connector(im, rcs->branches[i]);
1109     }
1110 bertho 1.9 draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
1111 bertho 1.8 xfree(cptr);
1112 bertho 1.1
1113     return im;
1114     }
1115    
1116 bertho 1.7 /*
1117     **************************************************************************
1118     * Layout routines
1119     **************************************************************************
1120     */
1121 bertho 1.1 void move_branch(branch_t *b, int x, int y)
1122     {
1123     int i;
1124 bertho 1.7 b->cx += x;
1125 bertho 1.1 b->y += y;
1126     for(i = 0; i < b->nrevs; i++)
1127     {
1128 bertho 1.7 b->revs[i]->cx += x;
1129 bertho 1.1 b->revs[i]->y += y;
1130     }
1131     }
1132    
1133 bertho 1.6 void reposition_branch(revision_t *r, int *x, int *w)
1134     {
1135     int i, j;
1136     for(j = 0; j < r->nbranches; j++)
1137     {
1138     branch_t *b = r->branches[j];
1139 bertho 1.7 *x += *w + conf.rev_minline + b->tw/2 - b->cx;
1140 bertho 1.6 *w = b->tw/2;
1141 bertho 1.7 move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
1142     *x = b->cx;
1143 bertho 1.6 /* Recurse to move branches of branched revisions */
1144     for(i = b->nrevs-1; i >= 0; i--)
1145     {
1146     reposition_branch(b->revs[i], x, w);
1147     }
1148     }
1149     }
1150    
1151 bertho 1.1 void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
1152     {
1153     int x1 = *x;
1154     int x2 = x1 + *w;
1155     int y1 = *y;
1156     int y2 = y1 + *h;
1157 bertho 1.7 int xx1 = b->cx - b->tw/2;
1158 bertho 1.1 int xx2 = xx1 + b->tw;
1159     int yy1 = b->y;
1160     int yy2 = yy1 + b->th;
1161     x1 = MIN(x1, xx1);
1162     x2 = MAX(x2, xx2);
1163     y1 = MIN(y1, yy1);
1164     y2 = MAX(y2, yy2);
1165     *x = x1;
1166     *y = y1;
1167     *w = x2 - x1;
1168     *h = y2 - y1;
1169     }
1170    
1171 bertho 1.7 int branch_intersects(int top, int bottom, int left, branch_t *b)
1172     {
1173     int br = b->cx + b->tw/2;
1174     int bt = b->y - conf.branch_connect - conf.branch_margin/2;
1175     int bb = b->y + b->th + conf.branch_margin/2;
1176     return !(bt > bottom || bb < top || br >= left);
1177     }
1178    
1179     int kern_branch(rcsfile_t *rcs, branch_t *b)
1180     {
1181     int left = b->cx - b->tw/2;
1182     int top = b->y - conf.branch_connect - conf.branch_margin/2;
1183     int bottom = b->y + b->th + conf.branch_margin/2;
1184     int i;
1185     int xpos = 0;
1186    
1187     for(i = 0; i < rcs->nbranches; i++)
1188     {
1189     branch_t *bp = rcs->branches[i];
1190     if(bp == b)
1191     continue;
1192     if(branch_intersects(top, bottom, left, bp))
1193     {
1194     int m = bp->cx + bp->tw/2 + conf.branch_margin;
1195     if(m > xpos)
1196     xpos = m;
1197     }
1198     }
1199     if(xpos && (b->cx - b->tw/2) - xpos > 0)
1200     {
1201     move_branch(b, xpos - (b->cx - b->tw/2), 0);
1202     return 1;
1203     }
1204     return 0;
1205     }
1206    
1207     void make_layout(rcsfile_t *rcs)
1208 bertho 1.1 {
1209     int i, j;
1210     int x, y;
1211     int w, h;
1212     int w2;
1213 bertho 1.7 int moved;
1214 bertho 1.1
1215 bertho 1.16 /* Remove all unwanted revisions */
1216     if(conf.strip_untagged)
1217     {
1218     for(i = 0; i < rcs->nbranches; i++)
1219     {
1220     branch_t *bp = rcs->branches[i];
1221     for(j = 0; j < bp->nrevs-1; j++)
1222     {
1223     if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)
1224     {
1225     memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
1226     bp->nrevs--;
1227     j--;
1228     }
1229     }
1230     }
1231     }
1232    
1233 bertho 1.1 /* Calculate the box-sizes of the revisions */
1234 bertho 1.7 for(i = 0; i < rcs->nsrev; i++)
1235 bertho 1.1 {
1236     revision_t *rp;
1237     int w;
1238     int h;
1239 bertho 1.7 rp = rcs->srev[i];
1240 bertho 1.9 rp->revtext = expand_string(conf.rev_text, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
1241     w = get_swidth(rp->revtext, &conf.rev_text_font);
1242     j = get_swidth(rp->rev->rev, &conf.rev_font);
1243     if(j > w)
1244     w = j;
1245     h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);
1246 bertho 1.1 for(j = 0; j < rp->ntags; j++)
1247     {
1248     int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
1249     if(ww > w) w = ww;
1250     h += get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
1251     }
1252     rp->w = w + conf.rev_lspace + conf.rev_rspace;
1253     rp->h = h + conf.rev_tspace + conf.rev_bspace;
1254     }
1255    
1256     /* Calculate the box-sizes of the branches */
1257     for(i = 0; i < rcs->nbranches; i++)
1258     {
1259     branch_t *bp = rcs->branches[i];
1260     int w;
1261     int h;
1262 bertho 1.7 w = get_swidth(bp->branch->branch, &conf.branch_font);
1263     h = get_sheight(bp->branch->branch, &conf.branch_font);
1264     for(j = 0; j < bp->ntags; j++)
1265 bertho 1.1 {
1266 bertho 1.7 int ww = get_swidth(bp->tags[j]->tag, &conf.branch_font);
1267 bertho 1.1 if(ww > w) w = ww;
1268 bertho 1.7 h += get_sheight(bp->tags[j]->tag, &conf.branch_font);
1269 bertho 1.1 }
1270     w += conf.branch_lspace + conf.branch_rspace;
1271     h += conf.branch_tspace + conf.branch_bspace;
1272     bp->w = w;
1273     bp->h = h;
1274     for(j = 0; j < bp->nrevs; j++)
1275     {
1276     if(bp->revs[j]->w > w)
1277     w = bp->revs[j]->w;
1278     h += bp->revs[j]->h + conf.rev_minline;
1279     }
1280 bertho 1.16 if(conf.branch_mirror)
1281     h += bp->h + conf.rev_minline;
1282 bertho 1.1 bp->th = h;
1283     bp->tw = w;
1284     }
1285    
1286     /* Calculate the relative positions of revs in a branch */
1287     for(i = 0; i < rcs->nbranches; i++)
1288     {
1289     branch_t *b = rcs->branches[i];
1290     x = b->tw/2;
1291     y = b->h;
1292 bertho 1.7 b->cx = x;
1293 bertho 1.1 b->y = 0;
1294     for(j = 0; j < b->nrevs; j++)
1295     {
1296     y += conf.rev_minline;
1297 bertho 1.7 b->revs[j]->cx = x;
1298 bertho 1.1 b->revs[j]->y = y;
1299     y += b->revs[j]->h;
1300     }
1301     }
1302    
1303 bertho 1.6 /* Reposition the branches */
1304 bertho 1.7 x = rcs->branches[0]->cx;
1305 bertho 1.1 w2 = rcs->branches[0]->tw / 2;
1306     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
1307     {
1308 bertho 1.6 reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
1309 bertho 1.1 }
1310    
1311 bertho 1.7 /* Try to move branches left if there is room (kerning) */
1312     for(moved = 1; moved; )
1313     {
1314     moved = 0;
1315     for(i = 1; i < rcs->nbranches; i++)
1316     {
1317     moved += kern_branch(rcs, rcs->branches[i]);
1318     }
1319     }
1320    
1321     /* Move everything w.r.t. the top-left margin */
1322 bertho 1.1 for(i = 0; i < rcs->nbranches; i++)
1323     move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
1324    
1325     /* Calculate overall image size */
1326 bertho 1.7 x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
1327 bertho 1.1 y = rcs->branches[0]->y;
1328     w = rcs->branches[0]->tw;
1329     h = rcs->branches[0]->th;
1330     for(i = 1; i < rcs->nbranches; i++)
1331     rect_union(&x, &y, &w, &h, rcs->branches[i]);
1332     rcs->tw = w;
1333     rcs->th = h;
1334 bertho 1.16
1335     /* Flip the entire tree */
1336     if(conf.upside_down)
1337     {
1338     y += rcs->th;
1339     for(i = 0; i < rcs->nbranches; i++)
1340     {
1341     branch_t *b = rcs->branches[i];
1342     for(j = 0; j < b->nrevs; j++)
1343     {
1344     revision_t *r = b->revs[j];
1345     r->y = y - r->y - r->h + conf.margin_top;
1346     }
1347     b->y = y - b->y - b->h + conf.margin_top;
1348     }
1349     }
1350 bertho 1.1 }
1351    
1352     /*
1353     **************************************************************************
1354 bertho 1.6 * Imagemap functions
1355     **************************************************************************
1356     */
1357 bertho 1.7 void make_imagemap(rcsfile_t *rcs, FILE *fp)
1358 bertho 1.6 {
1359     int i, j;
1360     fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
1361     for(i = 0; i < rcs->nbranches; i++)
1362     {
1363     branch_t *b = rcs->branches[i];
1364 bertho 1.8 tag_t *tag = b->ntags ? b->tags[0] : NULL;
1365 bertho 1.16 char *bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
1366     char *balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
1367 bertho 1.8 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1368 bertho 1.16 bhref,
1369 bertho 1.7 b->cx - b->w/2, b->y, b->cx + b->w/2, b->y + b->h,
1370 bertho 1.16 balt);
1371 bertho 1.6 for(j = 0; j < b->nrevs; j++)
1372     {
1373     revision_t *r = b->revs[j];
1374 bertho 1.13 revision_t* r1;
1375     int xoff;
1376     int x1;
1377     int x2;
1378     int y1;
1379 bertho 1.16 int y2;
1380     char *href;
1381     char *alt;
1382 bertho 1.13
1383 bertho 1.8 tag = r->ntags ? r->tags[0] : NULL;
1384 bertho 1.9 href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
1385     alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
1386 bertho 1.8 fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1387     href,
1388 bertho 1.7 r->cx - r->w/2, r->y, r->cx + r->w/2, r->y + r->h,
1389 bertho 1.8 alt);
1390     xfree(href);
1391     xfree(alt);
1392 bertho 1.16 if(j > 0 || b->branchpoint)
1393 bertho 1.9 {
1394 bertho 1.16 if(j > 0)
1395     {
1396     r1 = b->revs[j-1];
1397     xoff = MIN(r->w, r1->w)/4;
1398     y1 = conf.upside_down ? r1->y : r1->y + r1->h;
1399     }
1400     else
1401     {
1402     r1 = b->branchpoint;
1403     xoff = MIN(r->w, b->w)/4;
1404     y1 = conf.upside_down ? b->y : b->y + b->h;
1405     }
1406     x1 = r->cx - xoff;
1407     x2 = r->cx + xoff;
1408     y2 = conf.upside_down ? r->y + r->h : r->y;
1409     href = expand_string(conf.map_diff_href, rcs, r, r->rev, r1->rev, tag);
1410     alt = expand_string(conf.map_diff_alt, rcs, r, r->rev, r1->rev, tag);
1411     fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1412     href,
1413     x1, y1 + 1, x2, y2 - 1,
1414     alt);
1415     xfree(href);
1416     xfree(alt);
1417 bertho 1.9 }
1418 bertho 1.6 }
1419 bertho 1.16 if(conf.branch_mirror)
1420     {
1421     int y;
1422     if(conf.upside_down)
1423     y = b->y + b->h - b->th;
1424     else
1425     y = b->y - b->h + b->th;
1426     fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1427     bhref,
1428     b->cx - b->w/2, y, b->cx + b->w/2, y + b->h,
1429     balt);
1430     }
1431     xfree(bhref);
1432     xfree(balt);
1433 bertho 1.6 }
1434     fprintf(fp, "</map>\n");
1435     }
1436    
1437     /*
1438     **************************************************************************
1439 bertho 1.1 * Configuration
1440     **************************************************************************
1441     */
1442     int read_config(const char *path)
1443     {
1444     FILE *fp;
1445     int r;
1446 bertho 1.7
1447 bertho 1.1 if(path)
1448     {
1449     if((fp = fopen(path, "r")) == NULL)
1450     {
1451     return 0;
1452     }
1453 bertho 1.7 else
1454     input_file = path;
1455 bertho 1.1 }
1456     else
1457     {
1458     if((fp = fopen("./" CONFFILENAME, "r")) == NULL)
1459     {
1460     if((fp = fopen(ETCDIR "/" CONFFILENAME, "r")) == NULL)
1461     {
1462     return 0;
1463     }
1464 bertho 1.7 else
1465     input_file = ETCDIR "/" CONFFILENAME;
1466 bertho 1.1 }
1467 bertho 1.7 else
1468     input_file = "./" CONFFILENAME;
1469 bertho 1.1 }
1470    
1471     yyin = fp;
1472     r = yyparse();
1473     fclose(fp);
1474 bertho 1.7 input_file = NULL;
1475 bertho 1.1 return r == 0;
1476     }
1477    
1478     /*
1479     **************************************************************************
1480     * Program entry
1481     **************************************************************************
1482     */
1483     static const char usage_str[] =
1484     "Usage: cvsgraph [options] <file>\n"
1485 bertho 1.16 " -b Add a branch box at both sides of the trunk (config value is negated)\n"
1486 bertho 1.8 " -c <file> Read alternative config from <file>\n"
1487     " -d <level> Enable debug mode at <level>\n"
1488     " -h This message\n"
1489     " -i Generate an imagemap instead of image\n"
1490     " -M <name> Use <name> as imagemap name\n"
1491     " -m <mod> Use <mod> as cvs module\n"
1492     " -o <file> Output to <file>\n"
1493     " -q Be quiet (i.e. no warnings)\n"
1494     " -r <path> Use <path> as cvsroot path\n"
1495 bertho 1.16 " -s Strip untagged revisions (config value is negated)\n"
1496     " -u Upside down image (mirror vertically; config value is negated)\n"
1497 bertho 1.8 " -V Print version and exit\n"
1498     " -[0-9] <txt> Use <txt> for expansion\n"
1499 bertho 1.1 ;
1500    
1501 bertho 1.15 #define VERSION_STR "1.1.4"
1502 bertho 1.1 #define NOTICE_STR "Copyright (c) 2001 B.Stultiens"
1503    
1504 bertho 1.9 void append_slash(char **path)
1505     {
1506     int l;
1507     assert(path != NULL);
1508     assert(*path != NULL);
1509     l = strlen(*path);
1510     if(!l || (*path)[l-1] == '/')
1511     return;
1512     *path = xrealloc(*path, l+2);
1513     strcat(*path, "/");
1514     }
1515    
1516 bertho 1.1 int main(int argc, char *argv[])
1517     {
1518 bertho 1.7 extern int yy_flex_debug;
1519     extern int rcs_flex_debug;
1520     extern int yydebug;
1521     extern int rcsdebug;
1522 bertho 1.1 int optc;
1523     char *confpath = NULL;
1524     char *outfile = NULL;
1525     char *cvsroot = NULL;
1526     char *cvsmodule = NULL;
1527 bertho 1.7 int imagemap = 0;
1528 bertho 1.16 int upsidedown = 0;
1529     int bmirror = 0;
1530     int stripuntag = 0;
1531 bertho 1.7 char *imgmapname = NULL;
1532 bertho 1.1 int lose = 0;
1533     FILE *fp;
1534 bertho 1.7 rcsfile_t *rcs;
1535 bertho 1.1 gdImagePtr im;
1536    
1537 bertho 1.16 while((optc = getopt(argc, argv, "0:1:2:3:4:5:6:7:8:9:bc:d:hiM:m:o:qr:suV")) != EOF)
1538 bertho 1.1 {
1539     switch(optc)
1540     {
1541 bertho 1.16 case 'b':
1542     bmirror = 1;
1543     break;
1544 bertho 1.1 case 'c':
1545     confpath = xstrdup(optarg);
1546     break;
1547 bertho 1.7 case 'd':
1548     debuglevel = strtol(optarg, NULL, 0);
1549     break;
1550 bertho 1.6 case 'i':
1551 bertho 1.7 imagemap = 1;
1552     break;
1553     case 'M':
1554     imgmapname = xstrdup(optarg);
1555 bertho 1.6 break;
1556 bertho 1.1 case 'm':
1557     cvsmodule = xstrdup(optarg);
1558     break;
1559     case 'o':
1560     outfile = xstrdup(optarg);
1561     break;
1562 bertho 1.7 case 'q':
1563     quiet = 1;
1564     break;
1565 bertho 1.1 case 'r':
1566     cvsroot = xstrdup(optarg);
1567     break;
1568 bertho 1.16 case 's':
1569     stripuntag = 1;
1570     break;
1571     case 'u':
1572     upsidedown = 1;
1573     break;
1574 bertho 1.1 case 'V':
1575     fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
1576     return 0;
1577     case 'h':
1578     fprintf(stdout, "%s", usage_str);
1579     return 0;
1580     default:
1581 bertho 1.8 if(isdigit(optc))
1582     {
1583     conf.expand[optc-'0'] = xstrdup(optarg);
1584     }
1585     else
1586     lose++;
1587 bertho 1.1 }
1588     }
1589    
1590     if(optind >= argc)
1591     {
1592     fprintf(stderr, "Missing inputfile\n");
1593     lose++;
1594     }
1595    
1596     if(lose)
1597     {
1598     fprintf(stderr, "%s", usage_str);
1599     return 1;
1600     }
1601    
1602 bertho 1.7 if(debuglevel)
1603     {
1604     setvbuf(stdout, NULL, 0, _IONBF);
1605     setvbuf(stderr, NULL, 0, _IONBF);
1606     }
1607     yy_flex_debug = (debuglevel & DEBUG_CONF_LEX) != 0;
1608     rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
1609     yydebug = (debuglevel & DEBUG_CONF_YACC) != 0;
1610     rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
1611    
1612 bertho 1.1 /* Set defaults */
1613 bertho 1.9 conf.tag_font = gdFontTiny;
1614     conf.rev_font = gdFontTiny;
1615     conf.branch_font = gdFontTiny;
1616     conf.title_font = gdFontTiny;
1617     conf.rev_text_font = gdFontTiny;
1618    
1619     conf.cvsroot = xstrdup("");
1620     conf.cvsmodule = xstrdup("");
1621     conf.date_format = xstrdup("%d-%b-%Y %H:%M:%S");
1622     conf.title = xstrdup("");
1623     conf.map_name = xstrdup("CvsGraphImageMap");
1624     conf.map_branch_href = xstrdup("href=\"unset: conf.map_branch_href\"");
1625     conf.map_branch_alt = xstrdup("alt=\"%B\"");
1626     conf.map_rev_href = xstrdup("href=\"unset: conf.map_rev_href\"");
1627     conf.map_rev_alt = xstrdup("alt=\"%R\"");
1628     conf.map_diff_href = xstrdup("href=\"unset: conf.map_diff_href\"");
1629 bertho 1.12 conf.map_diff_alt = xstrdup("alt=\"%P &lt;-&gt; %R\"");
1630 bertho 1.10 conf.rev_text = xstrdup("%d");
1631 bertho 1.9
1632     conf.color_bg = white_color;
1633     conf.branch_bgcolor = white_color;
1634     conf.branch_color = black_color;
1635     conf.rev_color = black_color;
1636     conf.rev_bgcolor = white_color;
1637     conf.tag_color = black_color;
1638     conf.title_color = black_color;
1639     conf.rev_text_color = black_color;
1640 bertho 1.10
1641     conf.image_quality = 100;
1642 bertho 1.1
1643     if(!read_config(confpath))
1644     {
1645     fprintf(stderr, "Error reading config file\n");
1646     return 1;
1647     }
1648    
1649     /* Set overrides */
1650 bertho 1.7 if(cvsroot) conf.cvsroot = cvsroot;
1651     if(cvsmodule) conf.cvsmodule = cvsmodule;
1652     if(imgmapname) conf.map_name = imgmapname;
1653 bertho 1.16 if(upsidedown) conf.upside_down = !conf.upside_down;
1654     if(bmirror) conf.branch_mirror = !conf.branch_mirror;
1655     if(stripuntag) conf.strip_untagged = !conf.strip_untagged;
1656 bertho 1.1
1657 bertho 1.9 append_slash(&conf.cvsroot);
1658     append_slash(&conf.cvsmodule);
1659    
1660 bertho 1.7 rcs = get_rcsfile(conf.cvsroot, conf.cvsmodule, argv[optind]);
1661 bertho 1.1 if(!rcs)
1662     return 1;
1663    
1664 bertho 1.7 if(debuglevel & DEBUG_RCS_FILE)
1665     dump_rcsfile(rcs);
1666 bertho 1.1
1667 bertho 1.7 if(!reorganise_branches(rcs))
1668     return 1;
1669 bertho 1.1
1670 bertho 1.7 if(!assign_tags(rcs))
1671     return 1;
1672 bertho 1.1
1673     if(outfile)
1674     {
1675 bertho 1.15 if((fp = fopen(outfile, "wb")) == NULL)
1676 bertho 1.1 {
1677     perror(outfile);
1678     return 1;
1679     }
1680     }
1681     else
1682 bertho 1.15 {
1683 bertho 1.1 fp = stdout;
1684 bertho 1.15 #ifdef __WIN32__
1685     /* Bad hack for DOS/Windows */
1686     if(setmode(fileno(fp), O_BINARY) == -1)
1687     {
1688     perror("Set binary mode for stdout");
1689     return 1;
1690     }
1691     #endif
1692     }
1693 bertho 1.5
1694 bertho 1.7 make_layout(rcs);
1695    
1696     if(!imagemap)
1697 bertho 1.5 {
1698 bertho 1.7 /* Create an image */
1699     im = make_image(rcs);
1700    
1701     switch(conf.image_type)
1702     {
1703 bertho 1.5 #ifdef HAVE_IMAGE_GIF
1704 bertho 1.7 # ifndef HAVE_IMAGE_PNG
1705     default:
1706     # endif
1707     case IMAGE_GIF:
1708     gdImageGif(im, fp);
1709     break;
1710 bertho 1.5 #endif
1711     #ifdef HAVE_IMAGE_PNG
1712 bertho 1.7 default:
1713     case IMAGE_PNG:
1714     gdImagePng(im, fp);
1715     break;
1716 bertho 1.5 #endif
1717     #ifdef HAVE_IMAGE_JPEG
1718     # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
1719 bertho 1.7 default:
1720 bertho 1.5 # endif
1721 bertho 1.7 case IMAGE_JPEG:
1722     gdImageJpeg(im, fp, conf.image_quality);
1723     break;
1724 bertho 1.5 #endif
1725 bertho 1.7 }
1726    
1727     gdImageDestroy(im);
1728     }
1729     else
1730     {
1731     /* Create an imagemap */
1732     make_imagemap(rcs, fp);
1733 bertho 1.5 }
1734    
1735 bertho 1.1 if(outfile)
1736     fclose(fp);
1737 bertho 1.6
1738 bertho 1.1 return 0;
1739     }
1740    

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0