/[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 - (show annotations)
Sat Feb 24 00:35:13 2001 UTC (16 years, 7 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 /*
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 "config.h"
76 #include "cvsgraph.h"
77 #include "utils.h"
78 #include "readconf.h"
79
80 #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 /*#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 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 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 return compare_revid(REVPTR(v1)->rev, REVPTR(v2)->rev);
672 #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 return compare_revid((revid_t *)id, REVPTR(v)->rev);
686 #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 move_branch(b, x, r->y + r->h);
1055 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 #ifdef DEBUG
1147 setvbuf(stdout, NULL, 0, _IONBF);
1148 setvbuf(stderr, NULL, 0, _IONBF);
1149 #endif
1150
1151 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
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 if(outfile)
1328 fclose(fp);
1329 gdImageDestroy(im);
1330 return 0;
1331 }

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0