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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0