/[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.5 - (hide annotations)
Sat Feb 24 00:35:13 2001 UTC (16 years, 8 months ago) by bertho
Branch: MAIN
CVS Tags: REL_1_0_1
Changes since 1.4: +97 -5 lines
File MIME type: text/plain
Fixed the soring of revisions permanently. All sections of the revision
numbers are important.
Implemented better control over libgd with autoconf and added support
for generation of png and jpeg from the configuration file (thanks to
Kurt L. Sussman for a patch, although I modified it a bit).
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     cmd = xmalloc(strlen(cvsroot) + + strlen(module) + strlen(file) + 2 + 1);
270     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     void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
953     {
954     int x1 = *x;
955     int x2 = x1 + *w;
956     int y1 = *y;
957     int y2 = y1 + *h;
958     int xx1 = b->x - b->tw/2;
959     int xx2 = xx1 + b->tw;
960     int yy1 = b->y;
961     int yy2 = yy1 + b->th;
962     x1 = MIN(x1, xx1);
963     x2 = MAX(x2, xx2);
964     y1 = MIN(y1, yy1);
965     y2 = MAX(y2, yy2);
966     *x = x1;
967     *y = y1;
968     *w = x2 - x1;
969     *h = y2 - y1;
970     }
971    
972     void make_layout(rcsfilelog_t *rcs)
973     {
974     int i, j;
975     int x, y;
976     int w, h;
977     int w2;
978    
979     /* Calculate the box-sizes of the revisions */
980     for(i = 0; i < rcs->nrevs; i++)
981     {
982     revision_t *rp;
983     int w;
984     int h;
985     rp = rcs->revs[i];
986     w = get_swidth(rp->rev->rev, &conf.rev_font);
987     h = get_sheight(rp->rev->rev, &conf.rev_font);
988     for(j = 0; j < rp->ntags; j++)
989     {
990     int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
991     if(ww > w) w = ww;
992     h += get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
993     }
994     rp->w = w + conf.rev_lspace + conf.rev_rspace;
995     rp->h = h + conf.rev_tspace + conf.rev_bspace;
996     }
997    
998     /* Calculate the box-sizes of the branches */
999     for(i = 0; i < rcs->nbranches; i++)
1000     {
1001     branch_t *bp = rcs->branches[i];
1002     int w;
1003     int h;
1004     w = get_swidth(bp->branch, &conf.branch_font);
1005     h = get_sheight(bp->branch, &conf.branch_font);
1006     if(bp->tag)
1007     {
1008     int ww = get_swidth(bp->tag->tag, &conf.branch_font);
1009     if(ww > w) w = ww;
1010     h += get_sheight(bp->tag->tag, &conf.branch_font) + conf.branch_separator;
1011     }
1012     w += conf.branch_lspace + conf.branch_rspace;
1013     h += conf.branch_tspace + conf.branch_bspace;
1014     bp->w = w;
1015     bp->h = h;
1016     for(j = 0; j < bp->nrevs; j++)
1017     {
1018     if(bp->revs[j]->w > w)
1019     w = bp->revs[j]->w;
1020     h += bp->revs[j]->h + conf.rev_minline;
1021     }
1022     bp->th = h;
1023     bp->tw = w;
1024     }
1025    
1026     /* Calculate the relative positions of revs in a branch */
1027     for(i = 0; i < rcs->nbranches; i++)
1028     {
1029     branch_t *b = rcs->branches[i];
1030     x = b->tw/2;
1031     y = b->h;
1032     b->x = x;
1033     b->y = 0;
1034     for(j = 0; j < b->nrevs; j++)
1035     {
1036     y += conf.rev_minline;
1037     b->revs[j]->x = x;
1038     b->revs[j]->y = y;
1039     y += b->revs[j]->h;
1040     }
1041     }
1042    
1043     /* Reposition the branches FIXME: Should be recursive on branchpoint revisions within branches... */
1044     x = rcs->branches[0]->x;
1045     w2 = rcs->branches[0]->tw / 2;
1046     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
1047     {
1048     revision_t *r = rcs->branches[0]->revs[i];
1049     for(j = 0; j < r->nbranches; j++)
1050     {
1051     branch_t *b = r->branches[j];
1052     x += w2 + conf.rev_minline + b->tw/2 - b->x;
1053     w2 = b->tw/2;
1054 bertho 1.2 move_branch(b, x, r->y + r->h);
1055 bertho 1.1 x = b->x;
1056     }
1057     }
1058    
1059     for(i = 0; i < rcs->nbranches; i++)
1060     move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
1061    
1062     /* Calculate overall image size */
1063     x = rcs->branches[0]->x - rcs->branches[0]->tw/2;
1064     y = rcs->branches[0]->y;
1065     w = rcs->branches[0]->tw;
1066     h = rcs->branches[0]->th;
1067     for(i = 1; i < rcs->nbranches; i++)
1068     rect_union(&x, &y, &w, &h, rcs->branches[i]);
1069     rcs->tw = w;
1070     rcs->th = h;
1071     }
1072    
1073     /*
1074     **************************************************************************
1075     * Configuration
1076     **************************************************************************
1077     */
1078     int read_config(const char *path)
1079     {
1080     FILE *fp;
1081     int r;
1082     if(path)
1083     {
1084     if((fp = fopen(path, "r")) == NULL)
1085     {
1086     return 0;
1087     }
1088     }
1089     else
1090     {
1091     if((fp = fopen("./" CONFFILENAME, "r")) == NULL)
1092     {
1093     if((fp = fopen(ETCDIR "/" CONFFILENAME, "r")) == NULL)
1094     {
1095     return 0;
1096     }
1097     }
1098     }
1099    
1100     yyin = fp;
1101     r = yyparse();
1102     fclose(fp);
1103     return r == 0;
1104     }
1105    
1106     /*
1107     **************************************************************************
1108     * Program entry
1109     **************************************************************************
1110     */
1111     static const char usage_str[] =
1112     "Usage: cvsgraph [options] <file>\n"
1113     " -c <file> Read alternative config from <file>\n"
1114     " -h This message\n"
1115     " -m <mod> Use <mod> as cvs module\n"
1116     " -o <file> Output to <file>\n"
1117     " -r <path> Use <path> as cvsroot path\n"
1118     " -V Print version and exit\n"
1119     ;
1120    
1121     #define VERSION_STR "1.0.0"
1122     #define NOTICE_STR "Copyright (c) 2001 B.Stultiens"
1123    
1124     void add_tag(rcsfilelog_t *rcs, const char *tag, const char *rev)
1125     {
1126     rcs->tags = xrealloc(rcs->tags, (rcs->ntags+1)*sizeof(rcs->tags[0]));
1127     rcs->tags[rcs->ntags] = xmalloc(sizeof(tag_t));
1128     rcs->tags[rcs->ntags]->tag = strip_dup(tag);
1129     rcs->tags[rcs->ntags]->rev = make_revid(rev);
1130     rcs->ntags++;
1131     }
1132    
1133     int main(int argc, char *argv[])
1134     {
1135     int optc;
1136     char *confpath = NULL;
1137     char *outfile = NULL;
1138     char *cvsroot = NULL;
1139     char *cvsmodule = NULL;
1140     int lose = 0;
1141     FILE *fp;
1142     int n;
1143     rcsfilelog_t *rcs;
1144     gdImagePtr im;
1145    
1146 bertho 1.5 #ifdef DEBUG
1147     setvbuf(stdout, NULL, 0, _IONBF);
1148     setvbuf(stderr, NULL, 0, _IONBF);
1149     #endif
1150    
1151 bertho 1.1 while((optc = getopt(argc, argv, "c:hm:o:r:V")) != EOF)
1152     {
1153     switch(optc)
1154     {
1155     case 'c':
1156     confpath = xstrdup(optarg);
1157     break;
1158     case 'm':
1159     cvsmodule = xstrdup(optarg);
1160     break;
1161     case 'o':
1162     outfile = xstrdup(optarg);
1163     break;
1164     case 'r':
1165     cvsroot = xstrdup(optarg);
1166     break;
1167     case 'V':
1168     fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
1169     return 0;
1170     case 'h':
1171     fprintf(stdout, "%s", usage_str);
1172     return 0;
1173     default:
1174     lose++;
1175     }
1176     }
1177    
1178     if(optind >= argc)
1179     {
1180     fprintf(stderr, "Missing inputfile\n");
1181     lose++;
1182     }
1183    
1184     if(lose)
1185     {
1186     fprintf(stderr, "%s", usage_str);
1187     return 1;
1188     }
1189    
1190     /* Set defaults */
1191     if(!conf.tag_font) conf.tag_font = gdFontTiny;
1192     if(!conf.rev_font) conf.rev_font = gdFontTiny;
1193     if(!conf.branch_font) conf.branch_font = gdFontTiny;
1194     if(!conf.title_font) conf.title_font = gdFontTiny;
1195    
1196     if(!read_config(confpath))
1197     {
1198     fprintf(stderr, "Error reading config file\n");
1199     return 1;
1200     }
1201    
1202     /* Set overrides */
1203     if(cvsroot) conf.cvsroot = cvsroot;
1204     if(cvsmodule) conf.cvsmodule = cvsmodule;
1205    
1206     if((fp = get_log(conf.cvsroot, conf.cvsmodule, argv[optind])) == NULL)
1207     {
1208     fprintf(stderr, "Error getting log for '%s'\n", argv[optind]);
1209     return 1;
1210     }
1211    
1212     rcs = parse_log(fp);
1213     if(!rcs)
1214     {
1215     fprintf(stderr, "Error parsing log\n");
1216     return 1;
1217     }
1218     fclose(fp);
1219    
1220     /* Add implicit tags */
1221     add_tag(rcs, "HEAD", rcs->head->rev);
1222     add_tag(rcs, "MAIN", "1");
1223    
1224     /* We now have the log. Sort and reorganize a little */
1225     qsort(rcs->tags, rcs->ntags, sizeof(rcs->tags[0]), tag_sort);
1226     qsort(rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_sort);
1227    
1228     /* Assign tags to revisions */
1229     for(n = 0; n < rcs->ntags; n++)
1230     {
1231     revision_t *r = find_revision(rcs, rcs->tags[n]->rev);
1232     if(!r)
1233     continue;
1234     r->tags = xrealloc(r->tags, (r->ntags+1) * sizeof(r->tags[0]));
1235     r->tags[r->ntags] = rcs->tags[n];
1236     r->ntags++;
1237     }
1238    
1239     /* Isolate the branches */
1240     for(n = 0; n < rcs->nrevs; n++)
1241     {
1242     branch_t *b = find_branch(rcs, rcs->revs[n]->rev->branch);
1243     if(!b)
1244     {
1245     rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1) * sizeof(rcs->branches[0]));
1246     b = xmalloc(sizeof(*b));
1247     b->branch = xstrdup(rcs->revs[n]->rev->branch);
1248     b->tag = find_branchtag(rcs, rcs->revs[n]->rev->branch);
1249     rcs->branches[rcs->nbranches] = b;
1250     rcs->nbranches++;
1251     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_sort);
1252     }
1253     b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
1254     b->revs[b->nrevs] = rcs->revs[n];
1255     b->nrevs++;
1256     }
1257    
1258     /* Find the branchpoints */
1259     for(n = 0; n < rcs->nbranches; n++)
1260     {
1261     char *prev = xstrdup(rcs->branches[n]->branch);
1262     char *cptr = strrchr(prev, '.');
1263     revision_t *r;
1264     revid_t rid;
1265     if(!cptr) /* Main branch number */
1266     continue;
1267     *cptr = '\0';
1268     rid.isbranch = 0;
1269     rid.branch = "";
1270     rid.rev = prev;
1271     r = find_revision(rcs, &rid);
1272     if(!r)
1273     {
1274     /* Hm, disjoint branch... */
1275     fprintf(stderr, "Hm, don't know how to handle disjoint branches (%s)\n", prev);
1276     assert(r != NULL);
1277     }
1278     rcs->branches[n]->branchpoint = r;
1279     r->branches = xrealloc(r->branches, (r->nbranches+1)*sizeof(r->branches[0]));
1280     r->branches[r->nbranches] = rcs->branches[n];
1281     r->nbranches++;
1282     }
1283    
1284     make_layout(rcs);
1285    
1286     #ifdef DEBUG
1287     dump_log(rcs);
1288     #endif
1289     im = make_image(rcs);
1290     if(outfile)
1291     {
1292     if((fp = fopen(outfile, "w")) == NULL)
1293     {
1294     perror(outfile);
1295     return 1;
1296     }
1297     }
1298     else
1299     fp = stdout;
1300 bertho 1.5
1301     switch(conf.image_type)
1302     {
1303     #ifdef HAVE_IMAGE_GIF
1304     default:
1305     case IMAGE_GIF:
1306     gdImageGif(im, fp);
1307     break;
1308     #endif
1309     #ifdef HAVE_IMAGE_PNG
1310     # ifndef HAVE_IMAGE_GIF
1311     default:
1312     # endif
1313     case IMAGE_PNG:
1314     gdImagePng(im, fp);
1315     break;
1316     #endif
1317     #ifdef HAVE_IMAGE_JPEG
1318     # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
1319     default:
1320     # endif
1321     case IMAGE_JPEG:
1322     gdImageJpeg(im, fp, conf.image_quality);
1323     break;
1324     #endif
1325     }
1326    
1327 bertho 1.1 if(outfile)
1328     fclose(fp);
1329     gdImageDestroy(im);
1330     return 0;
1331     }
1332    

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0