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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0