/[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.3 - (hide annotations)
Wed Feb 21 17:30:06 2001 UTC (16 years, 8 months ago) by bertho
Branch: MAIN
Changes since 1.2: +3 -1 lines
File MIME type: text/plain
Fix sorting bug. Thanks to Tanaka Akira for sending the patch.
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 bertho 1.3 /* FIXME: This can lead to a segfault when no '.' is found */
609     return atoi(strrchr(REVPTR(v1)->rev->rev, '.') + 1) -
610     atoi(strrchr(REVPTR(v2)->rev->rev, '.') + 1);
611 bertho 1.1 #undef REVPTR
612     }
613    
614     int branch_sort(const void *b1, const void *b2)
615     {
616     #define BPTR(t) (*((branch_t **)t))
617     return strcmp(BPTR(b1)->branch, BPTR(b2)->branch);
618     #undef BPTR
619     }
620    
621     int rev_cmp(const void *id, const void *v)
622     {
623     #define REVPTR(t) (*((revision_t **)t))
624     return strcmp(((revid_t *)id)->rev, REVPTR(v)->rev->rev);
625     #undef REVPTR
626     }
627    
628     revision_t *find_revision(rcsfilelog_t * rcs, revid_t *id)
629     {
630     revision_t **r;
631     if(id->isbranch)
632     return NULL;
633     r = bsearch(id, rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_cmp);
634     if(!r)
635     return NULL;
636     else
637     return *r;
638     }
639    
640     int branch_cmp(const void *s, const void *b)
641     {
642     return strcmp((const char *)s, (*((branch_t **)b))->branch);
643     }
644    
645     branch_t *find_branch(rcsfilelog_t *rcs, const char *id)
646     {
647     branch_t **b;
648     b = bsearch(id, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_cmp);
649     if(!b)
650     return NULL;
651     else
652     return *b;
653     }
654    
655     tag_t *find_branchtag(rcsfilelog_t * rcs, const char *id)
656     {
657     int i;
658     for(i = 0; i < rcs->ntags; i++)
659     {
660     if(!rcs->tags[i]->rev->isbranch)
661     continue;
662     if(!strcmp(id, rcs->tags[i]->rev->branch))
663     return rcs->tags[i];
664     }
665     return NULL;
666     }
667    
668     /*
669     **************************************************************************
670     * Drawing routines
671     **************************************************************************
672     */
673     int get_swidth(const char *s, font_t *f)
674     {
675     if(!s)
676     return 0;
677     return strlen(s) * (*f)->w;
678     }
679    
680     int get_sheight(const char *s, font_t *f)
681     {
682     int nl;
683     if(!s)
684     return 0;
685     for(nl = 1; *s; s++)
686     {
687     if(*s == '\n' && s[1])
688     nl++;
689     }
690     return nl * (*f)->h;
691     }
692    
693     void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color)
694     {
695     int r2 = 2*r;
696     gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
697     gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
698     gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
699     gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
700     if(r)
701     {
702     gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
703     gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
704     gdImageArc(im, x1+r, y2-r, r2, r2, 90, 180, color->id);
705     gdImageArc(im, x2-r, y2-r, r2, r2, 0, 90, color->id);
706     }
707     }
708    
709     void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
710     {
711     int xx, yy;
712     switch(align & ALIGN_HX)
713     {
714     default:
715     case ALIGN_HL: xx = 0; break;
716     case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
717     case ALIGN_HR: xx = -get_swidth(s, f); break;
718     }
719     switch(align & ALIGN_VX)
720     {
721     default:
722     case ALIGN_VT: yy = 0; break;
723     case ALIGN_VC: yy = -get_sheight(s, f)/2; break;
724     case ALIGN_VB: yy = -get_sheight(s, f); break;
725     }
726     gdImageString(im, *f, x+xx+1, y+yy, s, c->id);
727     }
728    
729     void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)
730     {
731     int lx = cx - r->w/2;
732     int rx = lx + r->w;
733     int i;
734     draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color);
735     ty += conf.rev_tspace;
736     draw_string(im, r->rev->rev, &conf.rev_font, cx, ty, ALIGN_HC, &conf.rev_color);
737     ty += get_sheight(r->rev->rev, &conf.rev_font);
738     for(i = 0; i < r->ntags; i++)
739     {
740     draw_string(im, r->tags[i]->tag, &conf.tag_font, cx, ty, ALIGN_HC, &conf.tag_color);
741     ty += get_sheight(r->tags[i]->tag, &conf.tag_font);
742     }
743     }
744    
745     void draw_branch(gdImagePtr im, int cx, int ty, branch_t *b)
746     {
747     int lx = cx - b->w/2;
748     int rx = lx + b->w;
749     int yy;
750     int i;
751     draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color);
752     yy = conf.branch_tspace;
753     draw_string(im, b->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
754     yy += get_sheight(b->branch, &conf.branch_font);
755     if(b->tag)
756     {
757     draw_string(im, b->tag->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
758     }
759    
760     ty += b->h;
761     for(i = 0; i < b->nrevs; i++)
762     {
763     gdImageLine(im, cx, ty, cx, ty+conf.rev_minline, conf.rev_color.id);
764     ty += conf.rev_minline;
765     draw_rev(im, cx, ty, b->revs[i]);
766     ty += b->revs[i]->h;
767     }
768     }
769    
770     static char *_title;
771     static int _ntitle;
772     static int _natitle;
773    
774     void add_title_str(const char *s)
775     {
776     int l = strlen(s) + 1;
777     if(_ntitle + l > _natitle)
778     {
779     _natitle += 128;
780     _title = xrealloc(_title, _natitle * sizeof(_title[0]));
781     }
782     memcpy(_title+_ntitle, s, l);
783     _ntitle += l-1;
784     }
785    
786     void add_title_ch(int ch)
787     {
788     char buf[2];
789     buf[0] = ch;
790     buf[1] = '\0';
791     add_title_str(buf);
792     }
793    
794     char *expand_title(rcsfilelog_t *rcs)
795     {
796     char nb[32];
797     char nr[32];
798     char *cptr;
799    
800     sprintf(nb, "%d", rcs->nbranches);
801     sprintf(nr, "%d", rcs->nrevs);
802     for(cptr = conf.title; *cptr; cptr++)
803     {
804     if(*cptr == '%')
805     {
806     switch(*++cptr)
807     {
808     case 'c': add_title_str(conf.cvsroot); break;
809     case 'f': add_title_str(rcs->name); break;
810     case 'm': add_title_str(conf.cvsmodule); break;
811     case 'r': add_title_str(nr); break;
812     case 'b': add_title_str(nb); break;
813     case '%': add_title_ch('%'); break;
814     default:
815     add_title_ch('%');
816     add_title_ch(*cptr);
817     break;
818     }
819     }
820     else
821     add_title_ch(*cptr);
822     }
823     return _title;
824     }
825    
826     void draw_title(gdImagePtr im, char *title)
827     {
828     char *t;
829     char *s = title;
830     int x = conf.title_x;
831     int y = conf.title_y;
832     do
833     {
834     t = strchr(s, '\n');
835     if(t)
836     *t = '\0';
837     draw_string(im, s, &conf.title_font, x, y, conf.title_align, &conf.title_color);
838     y += get_sheight(s, &conf.title_font);
839     s = t+1;
840     } while(t);
841     }
842    
843     void draw_connector(gdImagePtr im, branch_t *b)
844     {
845     revision_t *r = b->branchpoint;
846     int x1 = r->x + r->w/2 + 2;
847     int y1 = r->y + r->h/2;
848     int x2 = b->x;
849     int y2 = b->y;
850     gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
851     gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
852     }
853    
854     gdImagePtr make_image(rcsfilelog_t *rcs)
855     {
856     gdImagePtr im;
857     int i;
858    
859     im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);
860     conf.color_bg.id = gdImageColorAllocate(im, conf.color_bg.r, conf.color_bg.g, conf.color_bg.b);
861     conf.tag_color.id = gdImageColorAllocate(im, conf.tag_color.r, conf.tag_color.g, conf.tag_color.b);
862     conf.rev_color.id = gdImageColorAllocate(im, conf.rev_color.r, conf.rev_color.g, conf.rev_color.b);
863     conf.branch_color.id = gdImageColorAllocate(im, conf.branch_color.r, conf.branch_color.g, conf.branch_color.b);
864     conf.branch_bgcolor.id = gdImageColorAllocate(im, conf.branch_bgcolor.r, conf.branch_bgcolor.g, conf.branch_bgcolor.b);
865     conf.title_color.id = gdImageColorAllocate(im, conf.title_color.r, conf.title_color.g, conf.title_color.b);
866    
867     for(i = 0; i < rcs->nbranches; i++)
868     draw_branch(im, rcs->branches[i]->x, rcs->branches[i]->y, rcs->branches[i]);
869     for(i = 0; i < rcs->nbranches; i++)
870     {
871     if(rcs->branches[i]->branchpoint)
872     draw_connector(im, rcs->branches[i]);
873     }
874     draw_title(im, expand_title(rcs));
875    
876     return im;
877     }
878    
879     void move_branch(branch_t *b, int x, int y)
880     {
881     int i;
882     b->x += x;
883     b->y += y;
884     for(i = 0; i < b->nrevs; i++)
885     {
886     b->revs[i]->x += x;
887     b->revs[i]->y += y;
888     }
889     }
890    
891     void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
892     {
893     int x1 = *x;
894     int x2 = x1 + *w;
895     int y1 = *y;
896     int y2 = y1 + *h;
897     int xx1 = b->x - b->tw/2;
898     int xx2 = xx1 + b->tw;
899     int yy1 = b->y;
900     int yy2 = yy1 + b->th;
901     x1 = MIN(x1, xx1);
902     x2 = MAX(x2, xx2);
903     y1 = MIN(y1, yy1);
904     y2 = MAX(y2, yy2);
905     *x = x1;
906     *y = y1;
907     *w = x2 - x1;
908     *h = y2 - y1;
909     }
910    
911     void make_layout(rcsfilelog_t *rcs)
912     {
913     int i, j;
914     int x, y;
915     int w, h;
916     int w2;
917    
918     /* Calculate the box-sizes of the revisions */
919     for(i = 0; i < rcs->nrevs; i++)
920     {
921     revision_t *rp;
922     int w;
923     int h;
924     rp = rcs->revs[i];
925     w = get_swidth(rp->rev->rev, &conf.rev_font);
926     h = get_sheight(rp->rev->rev, &conf.rev_font);
927     for(j = 0; j < rp->ntags; j++)
928     {
929     int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
930     if(ww > w) w = ww;
931     h += get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
932     }
933     rp->w = w + conf.rev_lspace + conf.rev_rspace;
934     rp->h = h + conf.rev_tspace + conf.rev_bspace;
935     }
936    
937     /* Calculate the box-sizes of the branches */
938     for(i = 0; i < rcs->nbranches; i++)
939     {
940     branch_t *bp = rcs->branches[i];
941     int w;
942     int h;
943     w = get_swidth(bp->branch, &conf.branch_font);
944     h = get_sheight(bp->branch, &conf.branch_font);
945     if(bp->tag)
946     {
947     int ww = get_swidth(bp->tag->tag, &conf.branch_font);
948     if(ww > w) w = ww;
949     h += get_sheight(bp->tag->tag, &conf.branch_font) + conf.branch_separator;
950     }
951     w += conf.branch_lspace + conf.branch_rspace;
952     h += conf.branch_tspace + conf.branch_bspace;
953     bp->w = w;
954     bp->h = h;
955     for(j = 0; j < bp->nrevs; j++)
956     {
957     if(bp->revs[j]->w > w)
958     w = bp->revs[j]->w;
959     h += bp->revs[j]->h + conf.rev_minline;
960     }
961     bp->th = h;
962     bp->tw = w;
963     }
964    
965     /* Calculate the relative positions of revs in a branch */
966     for(i = 0; i < rcs->nbranches; i++)
967     {
968     branch_t *b = rcs->branches[i];
969     x = b->tw/2;
970     y = b->h;
971     b->x = x;
972     b->y = 0;
973     for(j = 0; j < b->nrevs; j++)
974     {
975     y += conf.rev_minline;
976     b->revs[j]->x = x;
977     b->revs[j]->y = y;
978     y += b->revs[j]->h;
979     }
980     }
981    
982     /* Reposition the branches FIXME: Should be recursive on branchpoint revisions within branches... */
983     x = rcs->branches[0]->x;
984     w2 = rcs->branches[0]->tw / 2;
985     for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
986     {
987     revision_t *r = rcs->branches[0]->revs[i];
988     for(j = 0; j < r->nbranches; j++)
989     {
990     branch_t *b = r->branches[j];
991     x += w2 + conf.rev_minline + b->tw/2 - b->x;
992     w2 = b->tw/2;
993 bertho 1.2 move_branch(b, x, r->y + r->h);
994 bertho 1.1 x = b->x;
995     }
996     }
997    
998     for(i = 0; i < rcs->nbranches; i++)
999     move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
1000    
1001     /* Calculate overall image size */
1002     x = rcs->branches[0]->x - rcs->branches[0]->tw/2;
1003     y = rcs->branches[0]->y;
1004     w = rcs->branches[0]->tw;
1005     h = rcs->branches[0]->th;
1006     for(i = 1; i < rcs->nbranches; i++)
1007     rect_union(&x, &y, &w, &h, rcs->branches[i]);
1008     rcs->tw = w;
1009     rcs->th = h;
1010     }
1011    
1012     /*
1013     **************************************************************************
1014     * Configuration
1015     **************************************************************************
1016     */
1017     int read_config(const char *path)
1018     {
1019     FILE *fp;
1020     int r;
1021     if(path)
1022     {
1023     if((fp = fopen(path, "r")) == NULL)
1024     {
1025     return 0;
1026     }
1027     }
1028     else
1029     {
1030     if((fp = fopen("./" CONFFILENAME, "r")) == NULL)
1031     {
1032     if((fp = fopen(ETCDIR "/" CONFFILENAME, "r")) == NULL)
1033     {
1034     return 0;
1035     }
1036     }
1037     }
1038    
1039     yyin = fp;
1040     r = yyparse();
1041     fclose(fp);
1042     return r == 0;
1043     }
1044    
1045     /*
1046     **************************************************************************
1047     * Program entry
1048     **************************************************************************
1049     */
1050     static const char usage_str[] =
1051     "Usage: cvsgraph [options] <file>\n"
1052     " -c <file> Read alternative config from <file>\n"
1053     " -h This message\n"
1054     " -m <mod> Use <mod> as cvs module\n"
1055     " -o <file> Output to <file>\n"
1056     " -r <path> Use <path> as cvsroot path\n"
1057     " -V Print version and exit\n"
1058     ;
1059    
1060     #define VERSION_STR "1.0.0"
1061     #define NOTICE_STR "Copyright (c) 2001 B.Stultiens"
1062    
1063     void add_tag(rcsfilelog_t *rcs, const char *tag, const char *rev)
1064     {
1065     rcs->tags = xrealloc(rcs->tags, (rcs->ntags+1)*sizeof(rcs->tags[0]));
1066     rcs->tags[rcs->ntags] = xmalloc(sizeof(tag_t));
1067     rcs->tags[rcs->ntags]->tag = strip_dup(tag);
1068     rcs->tags[rcs->ntags]->rev = make_revid(rev);
1069     rcs->ntags++;
1070     }
1071    
1072     int main(int argc, char *argv[])
1073     {
1074     int optc;
1075     char *confpath = NULL;
1076     char *outfile = NULL;
1077     char *cvsroot = NULL;
1078     char *cvsmodule = NULL;
1079     int lose = 0;
1080     FILE *fp;
1081     int n;
1082     rcsfilelog_t *rcs;
1083     gdImagePtr im;
1084    
1085     while((optc = getopt(argc, argv, "c:hm:o:r:V")) != EOF)
1086     {
1087     switch(optc)
1088     {
1089     case 'c':
1090     confpath = xstrdup(optarg);
1091     break;
1092     case 'm':
1093     cvsmodule = xstrdup(optarg);
1094     break;
1095     case 'o':
1096     outfile = xstrdup(optarg);
1097     break;
1098     case 'r':
1099     cvsroot = xstrdup(optarg);
1100     break;
1101     case 'V':
1102     fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
1103     return 0;
1104     case 'h':
1105     fprintf(stdout, "%s", usage_str);
1106     return 0;
1107     default:
1108     lose++;
1109     }
1110     }
1111    
1112     if(optind >= argc)
1113     {
1114     fprintf(stderr, "Missing inputfile\n");
1115     lose++;
1116     }
1117    
1118     if(lose)
1119     {
1120     fprintf(stderr, "%s", usage_str);
1121     return 1;
1122     }
1123    
1124     /* Set defaults */
1125     if(!conf.tag_font) conf.tag_font = gdFontTiny;
1126     if(!conf.rev_font) conf.rev_font = gdFontTiny;
1127     if(!conf.branch_font) conf.branch_font = gdFontTiny;
1128     if(!conf.title_font) conf.title_font = gdFontTiny;
1129    
1130     if(!read_config(confpath))
1131     {
1132     fprintf(stderr, "Error reading config file\n");
1133     return 1;
1134     }
1135    
1136     /* Set overrides */
1137     if(cvsroot) conf.cvsroot = cvsroot;
1138     if(cvsmodule) conf.cvsmodule = cvsmodule;
1139    
1140     if((fp = get_log(conf.cvsroot, conf.cvsmodule, argv[optind])) == NULL)
1141     {
1142     fprintf(stderr, "Error getting log for '%s'\n", argv[optind]);
1143     return 1;
1144     }
1145    
1146     rcs = parse_log(fp);
1147     if(!rcs)
1148     {
1149     fprintf(stderr, "Error parsing log\n");
1150     return 1;
1151     }
1152     fclose(fp);
1153    
1154     /* Add implicit tags */
1155     add_tag(rcs, "HEAD", rcs->head->rev);
1156     add_tag(rcs, "MAIN", "1");
1157    
1158     /* We now have the log. Sort and reorganize a little */
1159     qsort(rcs->tags, rcs->ntags, sizeof(rcs->tags[0]), tag_sort);
1160     qsort(rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_sort);
1161    
1162     /* Assign tags to revisions */
1163     for(n = 0; n < rcs->ntags; n++)
1164     {
1165     revision_t *r = find_revision(rcs, rcs->tags[n]->rev);
1166     if(!r)
1167     continue;
1168     r->tags = xrealloc(r->tags, (r->ntags+1) * sizeof(r->tags[0]));
1169     r->tags[r->ntags] = rcs->tags[n];
1170     r->ntags++;
1171     }
1172    
1173     /* Isolate the branches */
1174     for(n = 0; n < rcs->nrevs; n++)
1175     {
1176     branch_t *b = find_branch(rcs, rcs->revs[n]->rev->branch);
1177     if(!b)
1178     {
1179     rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1) * sizeof(rcs->branches[0]));
1180     b = xmalloc(sizeof(*b));
1181     b->branch = xstrdup(rcs->revs[n]->rev->branch);
1182     b->tag = find_branchtag(rcs, rcs->revs[n]->rev->branch);
1183     rcs->branches[rcs->nbranches] = b;
1184     rcs->nbranches++;
1185     qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_sort);
1186     }
1187     b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
1188     b->revs[b->nrevs] = rcs->revs[n];
1189     b->nrevs++;
1190     }
1191    
1192     /* Find the branchpoints */
1193     for(n = 0; n < rcs->nbranches; n++)
1194     {
1195     char *prev = xstrdup(rcs->branches[n]->branch);
1196     char *cptr = strrchr(prev, '.');
1197     revision_t *r;
1198     revid_t rid;
1199     if(!cptr) /* Main branch number */
1200     continue;
1201     *cptr = '\0';
1202     rid.isbranch = 0;
1203     rid.branch = "";
1204     rid.rev = prev;
1205     r = find_revision(rcs, &rid);
1206     if(!r)
1207     {
1208     /* Hm, disjoint branch... */
1209     fprintf(stderr, "Hm, don't know how to handle disjoint branches (%s)\n", prev);
1210     assert(r != NULL);
1211     }
1212     rcs->branches[n]->branchpoint = r;
1213     r->branches = xrealloc(r->branches, (r->nbranches+1)*sizeof(r->branches[0]));
1214     r->branches[r->nbranches] = rcs->branches[n];
1215     r->nbranches++;
1216     }
1217    
1218     make_layout(rcs);
1219    
1220     #ifdef DEBUG
1221     dump_log(rcs);
1222     #endif
1223     im = make_image(rcs);
1224     if(outfile)
1225     {
1226     if((fp = fopen(outfile, "w")) == NULL)
1227     {
1228     perror(outfile);
1229     return 1;
1230     }
1231     }
1232     else
1233     fp = stdout;
1234     gdImageGif(im, fp);
1235     if(outfile)
1236     fclose(fp);
1237     gdImageDestroy(im);
1238     return 0;
1239     }
1240    

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0