/[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.6 - (hide annotations)
Mon Feb 26 00:09:20 2001 UTC (16 years, 7 months ago) by bertho
Branch: MAIN
Changes since 1.5: +68 -13 lines
File MIME type: text/plain
- Fixed the recursion problem so that branches of branches are properly placed.
- Fixed a lonely '+' in an expression.
- Started to make an imagemap functionality (not very functional yet)
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     /*
23     * Approx. layout of a cvs/rcs log:
24     *
25     * ws ::= "[ \t]*"
26     * rev_nr ::= "[:digit:]+(\.[:digit:]+)*"
27     * path_name ::= "/?(([^\n/]*)/)+"
28     * file_name ::= "[^\n]+"
29     * file_path ::= "{file_path}{file_name}"
30     * tag ::= "[^,.$@:;\0-\037]+"
31     * number ::= "[:digit:]+"
32     * separator ::= "(-{28})|(={78)\n"
33     *
34     * The header is identified with this snippet until
35     * a {separator} is encountered:
36     * "RCS file:{ws}{file_path}"
37     * "Working file:{ws}{file_name}"
38     * "head:{ws}{rev_nr}"
39     * "branch:{ws}{rev_nr}?"
40     * "locks:{ws}[^\n]*"
41     * "access list:{ws}[^\n]*"
42     * "symbolic names:"
43     * "(\t{tag}:{rev_nr}\n)*"
44     * "keyword substitution:{ws}[^\n]*"
45     * "total revisions:{ws}{number};{ws}selected revisions:{ws}{number}"
46     * "description:"
47     * "<any text you can imagine until a separator>"
48     *
49     * Each revision is identiefied with:
50     * "{separator}"
51     * "revision{ws}{rev_nr}"
52     * "date: 2001/02/15 20:17:37; author: bertho; state: Exp; lines: +2 -0
53     * "any text as a comment until a separator>"
54     *
55     * The last revision has the "={78}" separator. Eventually, a next file may be
56     * appended.
57     */
58    
59     #include <stdio.h>
60     #include <stdlib.h>
61     #include <unistd.h>
62     #include <string.h>
63     #include <assert.h>
64     #include <sys/types.h>
65     #include <sys/stat.h>
66     #include <sys/wait.h>
67     #include <fcntl.h>
68     #include <regex.h>
69     #include <errno.h>
70     #include <getopt.h>
71    
72     #include <gd.h>
73     #include <gdfontt.h>
74    
75 bertho 1.5 #include "config.h"
76 bertho 1.1 #include "cvsgraph.h"
77     #include "utils.h"
78     #include "readconf.h"
79    
80 bertho 1.5 #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
81     # error No image output format available. Check libgd
82     #endif
83    
84    
85 bertho 1.1 /*#define DEBUG 1*/
86    
87     #define RLOGCMD "/usr/bin/rlog"
88     #define DEVNULL "/dev/null"
89     #define CONFFILENAME "cvsgraph.conf"
90    
91     #ifndef ETCDIR
92     # define ETCDIR "/usr/local/etc"
93     #endif
94    
95     #ifndef MAX
96     # define MAX(a,b) ((a) > (b) ? (a) : (b))
97     #endif
98    
99     #ifndef MIN
100     # define MIN(a,b) ((a) < (b) ? (a) : (b))
101     #endif
102    
103     #define ALIGN_HL 0x00
104     #define ALIGN_HC 0x01
105     #define ALIGN_HR 0x02
106     #define ALIGN_HX 0x0f
107     #define ALIGN_VT 0x00
108     #define ALIGN_VC 0x10
109     #define ALIGN_VB 0x20
110     #define ALIGN_VX 0xf0
111    
112     typedef struct __revid_t
113     {
114     char *branch;
115     char *rev;
116     int isbranch;
117     } revid_t;
118    
119     typedef struct __tag_t
120     {
121     char *tag;
122     revid_t *rev;
123     } tag_t;
124    
125     struct __branch_t;
126    
127     typedef struct __revision_t
128     {
129     revid_t *rev;
130     char *info;
131     char *comment;
132     tag_t **tags;
133     int ntags;
134     struct __branch_t **branches;
135     int nbranches;
136     int w, h;
137     int x, y;
138     } revision_t;
139    
140     typedef struct __branch_t
141     {
142     char *branch;
143     revision_t *branchpoint;
144     tag_t *tag;
145     revision_t **revs;
146     int nrevs;
147     int tw, th;
148     int w, h;
149     int x, y;
150     } branch_t;
151    
152     typedef struct __rcsfilelog_t
153     {
154     char *path;
155     char *name;
156     revid_t *head;
157     char *branch;
158     char *locks;
159     char *access;
160     char *keyword;
161     char *totalrevs;
162     char *comment;
163     tag_t **tags;
164     int ntags;
165     revision_t **revs;
166     int nrevs;
167     branch_t **branches;
168     int nbranches;
169     int tw, th;
170     } rcsfilelog_t;
171    
172     /*
173     **************************************************************************
174     * Globals
175     **************************************************************************
176     */
177    
178     char *rlogcmd = RLOGCMD;
179     char *devnull = DEVNULL;
180    
181     config_t conf;
182    
183     /*
184     **************************************************************************
185     * Debug routines
186     **************************************************************************
187     */
188     #ifdef DEBUG
189     void dump_revid(const char *s, revid_t *r)
190     {
191     fprintf(stderr, "%s.branch : '%s'\n", s, r->branch);
192     fprintf(stderr, "%s.rev : '%s'\n", s, r->rev);
193     fprintf(stderr, "%s.isbranch: %d\n", s, r->isbranch);
194     }
195    
196     void dump_tag(const char *s, tag_t *t)
197     {
198     fprintf(stderr, "%s", s);
199     dump_revid(t->tag, t->rev);
200     }
201    
202     void dump_rev(revision_t *r)
203     {
204     int i;
205     dump_revid("Revision", r->rev);
206     fprintf(stderr, "Revision.Info : '%s'\n", r->info);
207     fprintf(stderr, "Revision.Comment: '%s'\n", r->comment);
208     for(i = 0; i < r->ntags; i++)
209     dump_tag("Revision.Tag: ", r->tags[i]);
210     }
211    
212     void dump_branch(branch_t *b)
213     {
214     int i;
215     fprintf(stderr, "Branch: '%s'\n", b->branch);
216     if(b->tag)
217     dump_tag("branchtag:", b->tag);
218     for(i = 0; i < b->nrevs; i++)
219     fprintf(stderr, "Branch.Rev: '%s'\n", b->revs[i]->rev->rev);
220     }
221    
222     void dump_log(rcsfilelog_t *r)
223     {
224     int i;
225    
226     fprintf(stderr, "Path : '%s'\n", r->path);
227     fprintf(stderr, "Name : '%s'\n", r->name);
228     dump_revid("Head", r->head);
229     fprintf(stderr, "Branch : '%s'\n", r->branch);
230     fprintf(stderr, "Locks : '%s'\n", r->locks);
231     fprintf(stderr, "Access : '%s'\n", r->access);
232     fprintf(stderr, "Keyword: '%s'\n", r->keyword);
233     fprintf(stderr, "Total : '%s'\n", r->totalrevs);
234     fprintf(stderr, "Comment: '%s'\n", r->comment);
235     for(i = 0; i < r->ntags; i++)
236     dump_tag("", r->tags[i]);
237     for(i = 0; i < r->nrevs; i++)
238     dump_rev(r->revs[i]);
239     for(i = 0; i < r->nbranches; i++)
240     dump_branch(r->branches[i]);
241     }
242     #endif
243    
244     /*
245     **************************************************************************
246     * Retrieve the log entries
247     **************************************************************************
248     */
249     FILE *get_log(const char *cvsroot, const char *module, const char *file)
250     {
251     pid_t pid;
252     int nul;
253     FILE *tmp;
254     char *cmd = NULL;
255     int status;
256     mode_t um;
257    
258     if((nul = open(devnull, O_RDWR, S_IRUSR|S_IWUSR)) == -1)
259     return NULL;
260    
261     um = umask(0177); /* Set tempfiles to max 0600 permissions */
262     if((tmp = tmpfile()) == NULL)
263     {
264     close(nul);
265     return NULL;
266     }
267     umask(um);
268    
269 bertho 1.6 cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 2 + 1);
270 bertho 1.1 sprintf(cmd, "%s/%s/%s", cvsroot, module, file);
271    
272     switch(pid = fork())
273     {
274     case -1: /* Error */
275     close(nul);
276     fclose(tmp);
277     xfree(cmd);
278     return NULL;
279     case 0: /* Child */
280     if((dup2(nul, STDIN_FILENO)) == -1) exit(126);
281     if((dup2(fileno(tmp), STDOUT_FILENO)) == -1) exit(126);
282     if((dup2(nul, STDERR_FILENO)) == -1) exit(126);
283     close(nul);
284     fclose(tmp);
285     execl(rlogcmd, rlogcmd, cmd, NULL);
286     exit(127);
287     break;
288     default: /* Parent */
289     close(nul);
290     xfree(cmd);
291     while(1)
292     {
293     if(waitpid(pid, &status, 0) == -1)
294     {
295     if(errno != EINTR)
296     {
297     fclose(tmp);
298     return NULL;
299     }
300     }
301     else
302     break;
303     }
304     break;
305     }
306    
307     if(WIFEXITED(status) && WEXITSTATUS(status) == 0)
308     {
309     if(fseek(tmp, 0, SEEK_SET) != (off_t)-1)
310     {
311     return tmp;
312     }
313     else
314     {
315     fclose(tmp);
316     return NULL;
317     }
318     }
319     else
320     fclose(tmp);
321     return NULL;
322     }
323    
324     /*
325     **************************************************************************
326     * Parse the log entries
327     **************************************************************************
328     */
329     char *strip_dup(const char *s)
330     {
331     int l = strlen(s);
332     char *c = xmalloc(l+1);
333    
334     strcpy(c, s);
335     while(*c == ' ' || *c == '\t')
336     {
337     memmove(c, c+1, l--);
338     }
339     while(l && strchr(" \t\r\n", c[l]))
340     c[l--] = '\0';
341     return c;
342     }
343    
344     revid_t *make_revid(const char *s)
345     {
346     char *c = strip_dup(s);
347     char *cptr;
348     int dots = 0;
349     revid_t *r = xmalloc(sizeof(*r));
350     for(cptr = c; *cptr; cptr++)
351     {
352     if(*cptr == '.')
353     dots++;
354     }
355     if(!dots)
356     {
357     r->rev = xstrdup("");
358     r->branch = xstrdup(s);
359     r->isbranch = 1;
360     }
361     else if(!*c)
362     {
363     r->rev = xstrdup("?.?");
364     r->branch = xstrdup("?");
365     }
366     else if(dots & 1)
367     {
368     char *t;
369     r->rev = c;
370     r->branch = xstrdup(c);
371     cptr = strrchr(r->branch, '.');
372     assert(cptr != NULL);
373     *cptr = '\0';
374     t = strrchr(r->branch, '.');
375     if((t&& !strcmp(t+1, "0")) || (!t && !strcmp(r->branch, "0")))
376     {
377     /* Magic branch numbers "x.x.0.x" */
378     r->isbranch = 1;
379     if(t)
380     strcpy(t+1, cptr+1);
381     else
382     strcpy(r->branch, cptr+1);
383     }
384     }
385     else
386     {
387     r->isbranch = 1;
388     r->branch = c;
389     r->rev = xmalloc(strlen(c) + 3);
390     strcpy(r->rev, c);
391     strcat(r->rev, ".?");
392     }
393     return r;
394     }
395    
396     char *add_comment(char *c, const char *n)
397     {
398     int l;
399     char *r;
400     assert(n != NULL);
401     l = strlen(n);
402     if(!c)
403     {
404     r = xmalloc(l+1);
405     strcpy(r, n);
406     }
407     else
408     {
409     r = xmalloc(l+strlen(c)+1+1);
410     strcpy(r, c);
411     strcat(r, "\n");
412     strcat(r, n);
413     }
414     return r;
415     }
416    
417     int get_line(FILE *fp, char *buf, int maxlen)
418     {
419     int n;
420     int seennl;
421     retry:
422     seennl = 0;
423     if(!fgets(buf, maxlen, fp))
424     return feof(fp) ? 0 : -1;
425     n = strlen(buf);
426     while(n && buf[n-1] == '\n')
427     {
428     seennl = 1;
429     buf[--n] = '\0';
430     }
431     if(!n)
432     goto retry;
433     if(!seennl)
434     {
435     while(fgetc(fp) != '\n')
436     ;
437     }
438     return n;
439     }
440    
441     rcsfilelog_t *parse_log(FILE *fp)
442     {
443     rcsfilelog_t *p;
444     int state = 0;
445     regex_t rerev;
446     regex_t reinfo;
447    
448     if(regcomp(&rerev, "^revision[ \\t]*[0-9]+(\\.[0-9]+)*", REG_EXTENDED))
449     return NULL;
450     if(regcomp(&reinfo, "^date:[^;]*;[ \\t]*author:[^;]*;[ \\t]+state:", REG_EXTENDED))
451     {
452     regfree(&rerev);
453     return NULL;
454     }
455     p = xmalloc(sizeof(*p));
456     while(state != 4)
457     {
458     char buf[256];
459     int n;
460     n = get_line(fp, buf, sizeof(buf));
461     if(!n)
462     break;
463     if(n == -1)
464     {
465     perror("tempfile read");
466     xfree(p);
467     regfree(&rerev);
468     regfree(&reinfo);
469     return NULL;
470     }
471     switch(state)
472     {
473     case 0: /* Prologue */
474     more_prologue:
475     if(!strncmp(buf, "RCS file:", 9))
476     {
477     p->path = strip_dup(buf+9);
478     }
479     else if(!strncmp(buf, "Working file:", 13))
480     {
481     p->name = strip_dup(buf+13);
482     }
483     else if(!strncmp(buf, "head:", 5))
484     {
485     p->head = make_revid(buf+5);
486     }
487     else if(!strncmp(buf, "branch:", 7))
488     {
489     p->branch = strip_dup(buf+7);
490     }
491     else if(!strncmp(buf, "locks:", 6))
492     {
493     p->locks = strip_dup(buf+6);
494     }
495     else if(!strncmp(buf, "access list:", 12))
496     {
497     p->access = strip_dup(buf+12);
498     }
499     else if(!strncmp(buf, "keyword substitution:", 21))
500     {
501     p->keyword = strip_dup(buf+21);
502     }
503     else if(!strncmp(buf, "total revisions:", 16))
504     {
505     p->totalrevs = strip_dup(buf+16);
506     }
507     else if(!strncmp(buf, "description:", 12))
508     {
509     state = 2;
510     }
511     else if(!strncmp(buf, "symbolic names:", 15))
512     {
513     state = 1;
514     }
515     else
516     {
517     fprintf(stderr, "Unknown keyword(s) in line '%s' (state=%d)\n", buf, state);
518     xfree(p);
519     return NULL;
520     }
521     break;
522     case 1: /* Tags */
523     if(*buf != '\t')
524     {
525     state = 0;
526     goto more_prologue;
527     }
528     else
529     {
530     char *rev = strrchr(buf, ':');
531     tag_t *t;
532     if(!rev)
533     {
534     state = 2;
535     goto more_prologue;
536     }
537     *rev = '\0';
538     t = xmalloc(sizeof(*t));
539     t->tag = strip_dup(buf);
540     t->rev = make_revid(rev+1);
541     p->tags = xrealloc(p->tags, (p->ntags+1) * sizeof(p->tags[0]));
542     p->tags[p->ntags] = t;
543     p->ntags++;
544     }
545     break;
546     case 2: /* Description */
547     add_description:
548     if(!strcmp(buf, "----------------------------"))
549     {
550     /* End of description */
551     state = 3;
552     break;
553     }
554     else if(!strcmp(buf, "============================================================================="))
555     {
556     /* End of log */
557     state = 4;
558     break;
559     }
560     if(!p->nrevs)
561     p->comment = add_comment(p->comment, buf);
562     else
563     p->revs[p->nrevs-1]->comment = add_comment(p->revs[p->nrevs-1]->comment, buf);
564     break;
565     case 3:
566     if(!regexec(&rerev, buf, 0, NULL, 0))
567     {
568     revision_t *r = xmalloc(sizeof(*r));
569     p->revs = xrealloc(p->revs, (p->nrevs+1) * sizeof(p->revs[0]));
570     p->revs[p->nrevs] = r;
571     p->nrevs++;
572     r->rev = make_revid(buf+8);
573     }
574     else if(!regexec(&reinfo, buf, 0, NULL, 0))
575     {
576     assert(p->nrevs > 0);
577     p->revs[p->nrevs-1]->info = strip_dup(buf);
578     }
579     else
580     {
581     /* No match means the description/comment */
582     state = 2;
583     goto add_description;
584     }
585     break;
586     default:
587     fprintf(stderr, "Illegal state (%d) in parser\n", state);
588     xfree(p);
589     regfree(&rerev);
590     regfree(&reinfo);
591     return NULL;
592     }
593     }
594     regfree(&rerev);
595     regfree(&reinfo);
596     return p;
597     }
598    
599     /*
600     **************************************************************************
601     * Sort and find helpers
602     **************************************************************************
603     */
604 bertho 1.5 int count_dots(const char *s)
605     {
606     int i;
607     for(i = 0; *s; s++)
608     {
609     if(*s == '.')
610     i++;
611     }
612     return i;
613     }
614    
615     int compare_revid(const revid_t *r1, const revid_t *r2)
616     {
617     int d1, d2;
618     char *v1, *v2;
619     char *s1, *s2;
620     int retval = 0;
621     assert(r1 != NULL);
622     assert(r1->rev != NULL);
623     assert(r2 != NULL);
624     assert(r2->rev != NULL);
625    
626     d1 = count_dots(r1->rev);
627     d2 = count_dots(r2->rev);
628     if(d1 != d2)
629     {
630     return d1 - d2;
631     }
632    
633     s1 = v1 = xstrdup(r1->rev);
634     s2 = v2 = xstrdup(r2->rev);
635     while(1)
636     {
637     char *vc1 = strchr(s1, '.');
638     char *vc2 = strchr(s2, '.');
639     if(vc1 && vc2)
640     *vc1 = *vc2 = '\0';
641     if(*s1 && *s2)
642     {
643     d1 = atoi(s1);
644     d2 = atoi(s2);
645     if(d1 != d2)
646     {
647     retval = d1 - d2;
648     break;
649     }
650     }
651     if(!vc1 || !vc2)
652     break;
653     s1 = vc1 + 1;
654     s2 = vc2 + 1;
655     }
656     xfree(v1);
657     xfree(v2);
658     return retval;
659     }
660    
661 bertho 1.1 int tag_sort(const void *t1, const void *t2)
662     {
663     #define TAGPTR(t) (*((tag_t **)t))
664     return strcmp(TAGPTR(t1)->rev->rev, TAGPTR(t2)->rev->rev);
665     #undef TAGPTR
666     }
667    
668     int rev_sort(const void *v1, const void *v2)
669     {
670     #define REVPTR(t) (*((revision_t **)t))
671 bertho 1.5 return compare_revid(REVPTR(v1)->rev, REVPTR(v2)->rev);
672 bertho 1.1 #undef REVPTR
673     }
674    
675     int branch_sort(const void *b1, const void *b2)
676     {
677     #define BPTR(t) (*((branch_t **)t))
678     return strcmp(BPTR(b1)->branch, BPTR(b2)->branch);
679     #undef BPTR
680     }
681    
682     int rev_cmp(const void *id, const void *v)
683     {
684     #define REVPTR(t) (*((revision_t **)t))
685 bertho 1.5 return compare_revid((revid_t *)id, REVPTR(v)->rev);
686 bertho 1.1 #undef REVPTR
687     }
688    
689     revision_t *find_revision(rcsfilelog_t * rcs, revid_t *id)
690     {
691     revision_t **r;
692     if(id->isbranch)
693     return NULL;
694     r = bsearch(id, rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_cmp);
695     if(!r)
696     return NULL;
697     else
698     return *r;
699     }
700    
701     int branch_cmp(const void *s, const void *b)
702     {
703     return strcmp((const char *)s, (*((branch_t **)b))->branch);
704     }
705    
706     branch_t *find_branch(rcsfilelog_t *rcs, const char *id)
707     {
708     branch_t **b;
709     b = bsearch(id, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_cmp);
710     if(!b)
711     return NULL;
712     else
713     return *b;
714     }
715    
716     tag_t *find_branchtag(rcsfilelog_t * rcs, const char *id)
717     {
718     int i;
719     for(i = 0; i < rcs->ntags; i++)
720     {
721     if(!rcs->tags[i]->rev->isbranch)
722     continue;
723     if(!strcmp(id, rcs->tags[i]->rev->branch))
724     return rcs->tags[i];
725     }
726     return NULL;
727     }
728    
729     /*
730     **************************************************************************
731     * Drawing routines
732     **************************************************************************
733     */
734     int get_swidth(const char *s, font_t *f)
735     {
736     if(!s)
737     return 0;
738     return strlen(s) * (*f)->w;
739     }
740    
741     int get_sheight(const char *s, font_t *f)
742     {
743     int nl;
744     if(!s)
745     return 0;
746     for(nl = 1; *s; s++)
747     {
748     if(*s == '\n' && s[1])
749     nl++;
750     }
751     return nl * (*f)->h;
752     }
753    
754     void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color)
755     {
756     int r2 = 2*r;
757     gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
758     gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
759     gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
760     gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
761     if(r)
762     {
763     gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
764     gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
765     gdImageArc(im, x1+r, y2-r, r2, r2, 90, 180, color->id);
766     gdImageArc(im, x2-r, y2-r, r2, r2, 0, 90, color->id);
767     }
768     }
769    
770     void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
771     {
772     int xx, yy;
773     switch(align & ALIGN_HX)
774     {
775     default:
776     case ALIGN_HL: xx = 0; break;
777     case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
778     case ALIGN_HR: xx = -get_swidth(s, f); break;
779     }
780     switch(align & ALIGN_VX)
781     {
782     default:
783     case ALIGN_VT: yy = 0; break;
784     case ALIGN_VC: yy = -get_sheight(s, f)/2; break;
785     case ALIGN_VB: yy = -get_sheight(s, f); break;
786     }
787     gdImageString(im, *f, x+xx+1, y+yy, s, c->id);
788     }
789    
790     void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)
791     {
792     int lx = cx - r->w/2;
793     int rx = lx + r->w;
794     int i;
795     draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color);
796     ty += conf.rev_tspace;
797     draw_string(im, r->rev->rev, &conf.rev_font, cx, ty, ALIGN_HC, &conf.rev_color);
798     ty += get_sheight(r->rev->rev, &conf.rev_font);
799     for(i = 0; i < r->ntags; i++)
800     {
801     draw_string(im, r->tags[i]->tag, &conf.tag_font, cx, ty, ALIGN_HC, &conf.tag_color);
802     ty += get_sheight(r->tags[i]->tag, &conf.tag_font);
803     }
804     }
805    
806     void draw_branch(gdImagePtr im, int cx, int ty, branch_t *b)
807     {
808     int lx = cx - b->w/2;
809     int rx = lx + b->w;
810     int yy;
811     int i;
812     draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color);
813     yy = conf.branch_tspace;
814     draw_string(im, b->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
815     yy += get_sheight(b->branch, &conf.branch_font);
816     if(b->tag)
817     {
818     draw_string(im, b->tag->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
819     }
820    
821     ty += b->h;
822     for(i = 0; i < b->nrevs; i++)
823     {
824     gdImageLine(im, cx, ty, cx, ty+conf.rev_minline, conf.rev_color.id);
825     ty += conf.rev_minline;
826     draw_rev(im, cx, ty, b->revs[i]);
827     ty += b->revs[i]->h;
828     }
829     }
830    
831     static char *_title;
832     static int _ntitle;
833     static int _natitle;
834    
835     void add_title_str(const char *s)
836     {
837     int l = strlen(s) + 1;
838     if(_ntitle + l > _natitle)
839     {
840     _natitle += 128;
841     _title = xrealloc(_title, _natitle * sizeof(_title[0]));
842     }
843     memcpy(_title+_ntitle, s, l);
844     _ntitle += l-1;
845     }
846    
847     void add_title_ch(int ch)
848     {
849     char buf[2];
850     buf[0] = ch;
851     buf[1] = '\0';
852     add_title_str(buf);
853     }
854    
855     char *expand_title(rcsfilelog_t *rcs)
856     {
857     char nb[32];
858     char nr[32];
859     char *cptr;
860    
861     sprintf(nb, "%d", rcs->nbranches);
862     sprintf(nr, "%d", rcs->nrevs);
863     for(cptr = conf.title; *cptr; cptr++)
864     {
865     if(*cptr == '%')
866     {
867     switch(*++cptr)
868     {
869     case 'c': add_title_str(conf.cvsroot); break;
870     case 'f': add_title_str(rcs->name); break;
871     case 'm': add_title_str(conf.cvsmodule); break;
872     case 'r': add_title_str(nr); break;
873     case 'b': add_title_str(nb); break;
874     case '%': add_title_ch('%'); break;
875     default:
876     add_title_ch('%');
877     add_title_ch(*cptr);
878     break;
879     }
880     }
881     else
882     add_title_ch(*cptr);
883     }
884     return _title;
885     }
886    
887     void draw_title(gdImagePtr im, char *title)
888     {
889     char *t;
890     char *s = title;
891     int x = conf.title_x;
892     int y = conf.title_y;
893     do
894     {
895     t = strchr(s, '\n');
896     if(t)
897     *t = '\0';
898     draw_string(im, s, &conf.title_font, x, y, conf.title_align, &conf.title_color);
899     y += get_sheight(s, &conf.title_font);
900     s = t+1;
901     } while(t);
902     }
903    
904     void draw_connector(gdImagePtr im, branch_t *b)
905     {
906     revision_t *r = b->branchpoint;
907     int x1 = r->x + r->w/2 + 2;
908     int y1 = r->y + r->h/2;
909     int x2 = b->x;
910     int y2 = b->y;
911     gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
912     gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
913     }
914    
915     gdImagePtr make_image(rcsfilelog_t *rcs)
916     {
917     gdImagePtr im;
918     int i;
919    
920     im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);
921     conf.color_bg.id = gdImageColorAllocate(im, conf.color_bg.r, conf.color_bg.g, conf.color_bg.b);
922     conf.tag_color.id = gdImageColorAllocate(im, conf.tag_color.r, conf.tag_color.g, conf.tag_color.b);
923     conf.rev_color.id = gdImageColorAllocate(im, conf.rev_color.r, conf.rev_color.g, conf.rev_color.b);
924     conf.branch_color.id = gdImageColorAllocate(im, conf.branch_color.r, conf.branch_color.g, conf.branch_color.b);
925     conf.branch_bgcolor.id = gdImageColorAllocate(im, conf.branch_bgcolor.r, conf.branch_bgcolor.g, conf.branch_bgcolor.b);
926     conf.title_color.id = gdImageColorAllocate(im, conf.title_color.r, conf.title_color.g, conf.title_color.b);
927    
928     for(i = 0; i < rcs->nbranches; i++)
929     draw_branch(im, rcs->branches[i]->x, rcs->branches[i]->y, rcs->branches[i]);
930     for(i = 0; i < rcs->nbranches; i++)
931     {
932     if(rcs->branches[i]->branchpoint)
933     draw_connector(im, rcs->branches[i]);
934     }
935     draw_title(im, expand_title(rcs));
936    
937     return im;
938     }
939    
940     void move_branch(branch_t *b, int x, int y)
941     {
942     int i;
943     b->x += x;
944     b->y += y;
945     for(i = 0; i < b->nrevs; i++)
946     {
947     b->revs[i]->x += x;
948     b->revs[i]->y += y;
949     }
950     }
951    
952 bertho 1.6 void reposition_branch(revision_t *r, int *x, int *w)
953     {
954     int i, j;
955     for(j = 0; j < r->nbranches; j++)
956     {
957     branch_t *b = r->branches[j];
958     *x += *w + conf.rev_minline + b->tw/2 - b->x;
959     *w = b->tw/2;
960     move_branch(b, *x, r->y + r->h);
961     *x = b->x;
962     /* Recurse to move branches of branched revisions */
963     for(i = b->nrevs-1; i >= 0; i--)
964     {
965     reposition_branch(b->revs[i], x, w);
966     }
967     }
968     }
969    
970 bertho 1.1 void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
971     {
972     int x1 = *x;
973     int x2 = x1 + *w;
974     int y1 = *y;
975     int y2 = y1 + *h;
976     int xx1 = b->x - b->tw/2;
977     int xx2 = xx1 + b->tw;
978     int yy1 = b->y;
979     int yy2 = yy1 + b->th;
980     x1 = MIN(x1, xx1);
981     x2 = MAX(x2, xx2);
982     y1 = MIN(y1, yy1);
983     y2 = MAX(y2, yy2);
984     *x = x1;
985     *y = y1;
986     *w = x2 - x1;
987     *h = y2 - y1;
988     }
989    
990     void make_layout(rcsfilelog_t *rcs)
991     {
992     int i, j;
993     int x, y;
994     int w, h;
995     int w2;
996    
997     /* Calculate the box-sizes of the revisions */
998     for(i = 0; i < rcs->nrevs; i++)
999     {
1000     revision_t *rp;
1001     int w;
1002     int h;
1003     rp = rcs->revs[i];
1004     w = get_swidth(rp->rev->rev, &conf.rev_font);
1005     h = get_sheight(rp->rev->rev, &conf.rev_font);
1006     for(j = 0; j < rp->ntags; j++)
1007     {
1008     int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
1009     if(ww > w) w = ww;
1010     h += get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
1011     }
1012     rp->w = w + conf.rev_lspace + conf.rev_rspace;
1013     rp->h = h + conf.rev_tspace + conf.rev_bspace;
1014     }
1015    
1016     /* Calculate the box-sizes of the branches */
1017     for(i = 0; i < rcs->nbranches; i++)
1018     {
1019     branch_t *bp = rcs->branches[i];
1020     int w;
1021     int h;
1022     w = get_swidth(bp->branch, &conf.branch_font);
1023     h = get_sheight(bp->branch, &conf.branch_font);
1024     if(bp->tag)
1025     {
1026     int ww = get_swidth(bp->tag->tag, &conf.branch_font);
1027     if(ww > w) w = ww;
1028     h += get_sheight(bp->tag->tag, &conf.branch_font) + conf.branch_separator;
1029     }
1030     w += conf.branch_lspace + conf.branch_rspace;
1031     h += conf.branch_tspace + conf.branch_bspace;
1032     bp->w = w;
1033     bp->h = h;
1034     for(j = 0; j < bp->nrevs; j++)
1035     {
1036     if(bp->revs[j]->w > w)
1037     w = bp->revs[j]->w;
1038     h += bp->revs[j]->h + conf.rev_minline;
1039     }
1040     bp->th = h;
1041     bp->tw = w;
1042     }
1043    
1044     /* Calculate the relative positions of revs in a branch */
1045     for(i = 0; i < rcs->nbranches; i++)
1046     {
1047     branch_t *b = rcs->branches[i];
1048     x = b->tw/2;
1049     y = b->h;
1050     b->x = x;
1051     b->y = 0;
1052     for(j = 0; j < b->nrevs; j++)
1053     {
1054     y += conf.rev_minline;
1055     b->revs[j]->x = x;
1056     b->revs[j]->y = y;
1057     y += b->revs[j]->h;
1058     }
1059     }
1060    
1061 bertho 1.6 /* Reposition the branches */
1062 bertho 1.1 x = rcs->branches[0]->x;
1063     w2 = rcs->branches[0]->tw / 2;
1064     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
1065     {
1066 bertho 1.6 reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
1067 bertho 1.1 }
1068    
1069     for(i = 0; i < rcs->nbranches; i++)
1070     move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
1071    
1072     /* Calculate overall image size */
1073     x = rcs->branches[0]->x - rcs->branches[0]->tw/2;
1074     y = rcs->branches[0]->y;
1075     w = rcs->branches[0]->tw;
1076     h = rcs->branches[0]->th;
1077     for(i = 1; i < rcs->nbranches; i++)
1078     rect_union(&x, &y, &w, &h, rcs->branches[i]);
1079     rcs->tw = w;
1080     rcs->th = h;
1081     }
1082    
1083     /*
1084     **************************************************************************
1085 bertho 1.6 * Imagemap functions
1086     **************************************************************************
1087     */
1088     void make_imagemap(rcsfilelog_t *rcs, FILE *fp)
1089     {
1090     int i, j;
1091     fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
1092     for(i = 0; i < rcs->nbranches; i++)
1093     {
1094     branch_t *b = rcs->branches[i];
1095     fprintf(fp, "<area shape=\"rect\" href=\"branch_%s\" coords=\"%d,%d,%d,%d\" alt=\"Branch %s\">\n",
1096     b->branch,
1097     b->x - b->w/2, b->y, b->x + b->w/2, b->y + b->h,
1098     b->branch);
1099     for(j = 0; j < b->nrevs; j++)
1100     {
1101     revision_t *r = b->revs[j];
1102     fprintf(fp, "<area shape=\"rect\" href=\"rev_%s\" coords=\"%d,%d,%d,%d\" alt=\"Revision %s\">\n",
1103     r->rev->rev,
1104     r->x - r->w/2, r->y, r->x + r->w/2, r->y + r->h,
1105     r->rev->rev);
1106     }
1107     }
1108     fprintf(fp, "</map>\n");
1109     }
1110    
1111     /*
1112     **************************************************************************
1113 bertho 1.1 * Configuration
1114     **************************************************************************
1115     */
1116     int read_config(const char *path)
1117     {
1118     FILE *fp;
1119     int r;
1120     if(path)
1121     {
1122     if((fp = fopen(path, "r")) == NULL)
1123     {
1124     return 0;
1125     }
1126     }
1127     else
1128     {
1129     if((fp = fopen("./" CONFFILENAME, "r")) == NULL)
1130     {
1131     if((fp = fopen(ETCDIR "/" CONFFILENAME, "r")) == NULL)
1132     {
1133     return 0;
1134     }
1135     }
1136     }
1137    
1138     yyin = fp;
1139     r = yyparse();
1140     fclose(fp);
1141     return r == 0;
1142     }
1143    
1144     /*
1145     **************************************************************************
1146     * Program entry
1147     **************************************************************************
1148     */
1149     static const char usage_str[] =
1150     "Usage: cvsgraph [options] <file>\n"
1151     " -c <file> Read alternative config from <file>\n"
1152     " -h This message\n"
1153 bertho 1.6 " -i <file> Write an imagamap to <file>\n"
1154 bertho 1.1 " -m <mod> Use <mod> as cvs module\n"
1155     " -o <file> Output to <file>\n"
1156     " -r <path> Use <path> as cvsroot path\n"
1157     " -V Print version and exit\n"
1158     ;
1159    
1160 bertho 1.6 #define VERSION_STR "1.0.1"
1161 bertho 1.1 #define NOTICE_STR "Copyright (c) 2001 B.Stultiens"
1162    
1163     void add_tag(rcsfilelog_t *rcs, const char *tag, const char *rev)
1164     {
1165     rcs->tags = xrealloc(rcs->tags, (rcs->ntags+1)*sizeof(rcs->tags[0]));
1166     rcs->tags[rcs->ntags] = xmalloc(sizeof(tag_t));
1167     rcs->tags[rcs->ntags]->tag = strip_dup(tag);
1168     rcs->tags[rcs->ntags]->rev = make_revid(rev);
1169     rcs->ntags++;
1170     }
1171    
1172     int main(int argc, char *argv[])
1173     {
1174     int optc;
1175     char *confpath = NULL;
1176     char *outfile = NULL;
1177     char *cvsroot = NULL;
1178     char *cvsmodule = NULL;
1179 bertho 1.6 char *imagemap = NULL;
1180 bertho 1.1 int lose = 0;
1181     FILE *fp;
1182     int n;
1183     rcsfilelog_t *rcs;
1184     gdImagePtr im;
1185    
1186 bertho 1.5 #ifdef DEBUG
1187     setvbuf(stdout, NULL, 0, _IONBF);
1188     setvbuf(stderr, NULL, 0, _IONBF);
1189     #endif
1190    
1191 bertho 1.6 while((optc = getopt(argc, argv, "c:hi:m:o:r:V")) != EOF)
1192 bertho 1.1 {
1193     switch(optc)
1194     {
1195     case 'c':
1196     confpath = xstrdup(optarg);
1197     break;
1198 bertho 1.6 case 'i':
1199     imagemap = xstrdup(optarg);
1200     break;
1201 bertho 1.1 case 'm':
1202     cvsmodule = xstrdup(optarg);
1203     break;
1204     case 'o':
1205     outfile = xstrdup(optarg);
1206     break;
1207     case 'r':
1208     cvsroot = xstrdup(optarg);
1209     break;
1210     case 'V':
1211     fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
1212     return 0;
1213     case 'h':
1214     fprintf(stdout, "%s", usage_str);
1215     return 0;
1216     default:
1217     lose++;
1218     }
1219     }
1220    
1221     if(optind >= argc)
1222     {
1223     fprintf(stderr, "Missing inputfile\n");
1224     lose++;
1225     }
1226    
1227     if(lose)
1228     {
1229     fprintf(stderr, "%s", usage_str);
1230     return 1;
1231     }
1232    
1233     /* Set defaults */
1234     if(!conf.tag_font) conf.tag_font = gdFontTiny;
1235     if(!conf.rev_font) conf.rev_font = gdFontTiny;
1236     if(!conf.branch_font) conf.branch_font = gdFontTiny;
1237     if(!conf.title_font) conf.title_font = gdFontTiny;
1238    
1239     if(!read_config(confpath))
1240     {
1241     fprintf(stderr, "Error reading config file\n");
1242     return 1;
1243     }
1244    
1245     /* Set overrides */
1246     if(cvsroot) conf.cvsroot = cvsroot;
1247     if(cvsmodule) conf.cvsmodule = cvsmodule;
1248    
1249     if((fp = get_log(conf.cvsroot, conf.cvsmodule, argv[optind])) == NULL)
1250     {
1251     fprintf(stderr, "Error getting log for '%s'\n", argv[optind]);
1252     return 1;
1253     }
1254    
1255     rcs = parse_log(fp);
1256     if(!rcs)
1257     {
1258     fprintf(stderr, "Error parsing log\n");
1259     return 1;
1260     }
1261     fclose(fp);
1262    
1263     /* Add implicit tags */
1264     add_tag(rcs, "HEAD", rcs->head->rev);
1265     add_tag(rcs, "MAIN", "1");
1266    
1267     /* We now have the log. Sort and reorganize a little */
1268     qsort(rcs->tags, rcs->ntags, sizeof(rcs->tags[0]), tag_sort);
1269     qsort(rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_sort);
1270    
1271     /* Assign tags to revisions */
1272     for(n = 0; n < rcs->ntags; n++)
1273     {
1274     revision_t *r = find_revision(rcs, rcs->tags[n]->rev);
1275     if(!r)
1276     continue;
1277     r->tags = xrealloc(r->tags, (r->ntags+1) * sizeof(r->tags[0]));
1278     r->tags[r->ntags] = rcs->tags[n];
1279     r->ntags++;
1280     }
1281    
1282     /* Isolate the branches */
1283     for(n = 0; n < rcs->nrevs; n++)
1284     {
1285     branch_t *b = find_branch(rcs, rcs->revs[n]->rev->branch);
1286     if(!b)
1287     {
1288     rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1) * sizeof(rcs->branches[0]));
1289     b = xmalloc(sizeof(*b));
1290     b->branch = xstrdup(rcs->revs[n]->rev->branch);
1291     b->tag = find_branchtag(rcs, rcs->revs[n]->rev->branch);
1292     rcs->branches[rcs->nbranches] = b;
1293     rcs->nbranches++;
1294     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_sort);
1295     }
1296     b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
1297     b->revs[b->nrevs] = rcs->revs[n];
1298     b->nrevs++;
1299     }
1300    
1301     /* Find the branchpoints */
1302     for(n = 0; n < rcs->nbranches; n++)
1303     {
1304     char *prev = xstrdup(rcs->branches[n]->branch);
1305     char *cptr = strrchr(prev, '.');
1306     revision_t *r;
1307     revid_t rid;
1308     if(!cptr) /* Main branch number */
1309     continue;
1310     *cptr = '\0';
1311     rid.isbranch = 0;
1312     rid.branch = "";
1313     rid.rev = prev;
1314     r = find_revision(rcs, &rid);
1315     if(!r)
1316     {
1317     /* Hm, disjoint branch... */
1318     fprintf(stderr, "Hm, don't know how to handle disjoint branches (%s)\n", prev);
1319     assert(r != NULL);
1320     }
1321     rcs->branches[n]->branchpoint = r;
1322     r->branches = xrealloc(r->branches, (r->nbranches+1)*sizeof(r->branches[0]));
1323     r->branches[r->nbranches] = rcs->branches[n];
1324     r->nbranches++;
1325     }
1326    
1327     make_layout(rcs);
1328    
1329     #ifdef DEBUG
1330     dump_log(rcs);
1331     #endif
1332     im = make_image(rcs);
1333     if(outfile)
1334     {
1335     if((fp = fopen(outfile, "w")) == NULL)
1336     {
1337     perror(outfile);
1338     return 1;
1339     }
1340     }
1341     else
1342     fp = stdout;
1343 bertho 1.5
1344     switch(conf.image_type)
1345     {
1346     #ifdef HAVE_IMAGE_GIF
1347     default:
1348     case IMAGE_GIF:
1349     gdImageGif(im, fp);
1350     break;
1351     #endif
1352     #ifdef HAVE_IMAGE_PNG
1353     # ifndef HAVE_IMAGE_GIF
1354     default:
1355     # endif
1356     case IMAGE_PNG:
1357     gdImagePng(im, fp);
1358     break;
1359     #endif
1360     #ifdef HAVE_IMAGE_JPEG
1361     # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
1362     default:
1363     # endif
1364     case IMAGE_JPEG:
1365     gdImageJpeg(im, fp, conf.image_quality);
1366     break;
1367     #endif
1368     }
1369    
1370 bertho 1.1 if(outfile)
1371     fclose(fp);
1372     gdImageDestroy(im);
1373 bertho 1.6
1374     if(imagemap)
1375     {
1376     if((fp = fopen(imagemap, "w")) == NULL)
1377     {
1378     perror(imagemap);
1379     return 1;
1380     }
1381     make_imagemap(rcs, fp);
1382     fclose(fp);
1383     }
1384    
1385 bertho 1.1 return 0;
1386     }
1387    

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0