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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0