/[CvsGraph]/cvsgraph/cvsgraph.c
ViewVC logotype

Annotate of /cvsgraph/cvsgraph.c

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0