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

Diff of /cvsgraph/cvsgraph.c

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

revision 1.2, Tue Feb 20 22:36:38 2001 UTC revision 1.15, Wed Nov 28 16:19:49 2001 UTC
# Line 19  Line 19 
19   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20   */   */
21    
22  /*  #include "config.h"
  * Approx. layout of a cvs/rcs log:  
  *  
  * ws           ::= "[ \t]*"  
  * rev_nr       ::= "[:digit:]+(\.[:digit:]+)*"  
  * path_name    ::= "/?(([^\n/]*)/)+"  
  * file_name    ::= "[^\n]+"  
  * file_path    ::= "{file_path}{file_name}"  
  * tag          ::= "[^,.$@:;\0-\037]+"  
  * number       ::= "[:digit:]+"  
  * separator    ::= "(-{28})|(={78)\n"  
  *  
  * The header is identified with this snippet until  
  * a {separator} is encountered:  
  *      "RCS file:{ws}{file_path}"  
  *      "Working file:{ws}{file_name}"  
  *      "head:{ws}{rev_nr}"  
  *      "branch:{ws}{rev_nr}?"  
  *      "locks:{ws}[^\n]*"  
  *      "access list:{ws}[^\n]*"  
  *      "symbolic names:"  
  *      "(\t{tag}:{rev_nr}\n)*"  
  *      "keyword substitution:{ws}[^\n]*"  
  *      "total revisions:{ws}{number};{ws}selected revisions:{ws}{number}"  
  *      "description:"  
  *      "<any text you can imagine until a separator>"  
  *  
  * Each revision is identiefied with:  
  *      "{separator}"  
  *      "revision{ws}{rev_nr}"  
  *      "date: 2001/02/15 20:17:37;  author: bertho;  state: Exp;  lines: +2 -0  
  *      "any text as a comment until a separator>"  
  *  
  * The last revision has the "={78}" separator. Eventually, a next file may be  
  * appended.  
  */  
23    
24  #include <stdio.h>  #include <stdio.h>
25  #include <stdlib.h>  #include <stdlib.h>
# Line 67  Line 32 
32  #include <fcntl.h>  #include <fcntl.h>
33  #include <regex.h>  #include <regex.h>
34  #include <errno.h>  #include <errno.h>
35  #include <getopt.h>  #include <ctype.h>
36    #include <time.h>
37    
38    #ifdef HAVE_GETOPT_H
39    # include <getopt.h>
40    #endif
41    
42  #include <gd.h>  #include <gd.h>
43  #include <gdfontt.h>  #include <gdfontt.h>
44    
45  #include "cvsgraph.h"  #include "cvsgraph.h"
46  #include "utils.h"  #include "utils.h"
47  #include "readconf.h"  #include "readconf.h"
48    #include "rcs.h"
49    
50    #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
51    # error No image output format available. Check libgd
52    #endif
53    
54    
55  /*#define DEBUG         1*/  /*#define DEBUG         1*/
56    
 #define RLOGCMD         "/usr/bin/rlog"  
 #define DEVNULL         "/dev/null"  
57  #define CONFFILENAME    "cvsgraph.conf"  #define CONFFILENAME    "cvsgraph.conf"
58    
59  #ifndef ETCDIR  #ifndef ETCDIR
# Line 97  Line 71 
71  #define ALIGN_HL        0x00  #define ALIGN_HL        0x00
72  #define ALIGN_HC        0x01  #define ALIGN_HC        0x01
73  #define ALIGN_HR        0x02  #define ALIGN_HR        0x02
74  #define ALIGN_HX        0x0f  #define ALIGN_HX        0x0f
75  #define ALIGN_VT        0x00  #define ALIGN_VT        0x00
76  #define ALIGN_VC        0x10  #define ALIGN_VC        0x10
77  #define ALIGN_VB        0x20  #define ALIGN_VB        0x20
78  #define ALIGN_VX        0xf0  #define ALIGN_VX        0xf0
   
 typedef struct __revid_t  
 {  
         char    *branch;  
         char    *rev;  
         int     isbranch;  
 } revid_t;  
   
 typedef struct __tag_t  
 {  
         char    *tag;  
         revid_t *rev;  
 } tag_t;  
   
 struct __branch_t;  
   
 typedef struct __revision_t  
 {  
         revid_t         *rev;  
         char            *info;  
         char            *comment;  
         tag_t           **tags;  
         int             ntags;  
         struct __branch_t       **branches;  
         int             nbranches;  
         int             w, h;  
         int             x, y;  
 } revision_t;  
   
 typedef struct __branch_t  
 {  
         char            *branch;  
         revision_t      *branchpoint;  
         tag_t           *tag;  
         revision_t      **revs;  
         int             nrevs;  
         int             tw, th;  
         int             w, h;  
         int             x, y;  
 } branch_t;  
   
 typedef struct __rcsfilelog_t  
 {  
         char            *path;  
         char            *name;  
         revid_t         *head;  
         char            *branch;  
         char            *locks;  
         char            *access;  
         char            *keyword;  
         char            *totalrevs;  
         char            *comment;  
         tag_t           **tags;  
         int             ntags;  
         revision_t      **revs;  
         int             nrevs;  
         branch_t        **branches;  
         int             nbranches;  
         int             tw, th;  
 } rcsfilelog_t;  
79    
80  /*  /*
81   **************************************************************************   **************************************************************************
# Line 169  Line 83 
83   **************************************************************************   **************************************************************************
84   */   */
85    
 char *rlogcmd = RLOGCMD;  
 char *devnull = DEVNULL;  
   
86  config_t conf;  config_t conf;
87    int debuglevel;
88    color_t white_color = {255, 255, 255, 0};
89    color_t black_color = {0, 0, 0, 0};
90    
91    
92  /*  /*
93   **************************************************************************   **************************************************************************
94   * Debug routines   * Dubug routines
95   **************************************************************************   **************************************************************************
96   */   */
97  #ifdef DEBUG  void dump_rev(char *p, rev_t *r)
 void dump_revid(const char *s, revid_t *r)  
98  {  {
99          fprintf(stderr, "%s.branch  : '%s'\n", s, r->branch);          printf("%s", p);
100          fprintf(stderr, "%s.rev     : '%s'\n", s, r->rev);          if(r)
101          fprintf(stderr, "%s.isbranch: %d\n", s, r->isbranch);                  printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
102            else
103                    printf("<null>\n");
104  }  }
105    
106  void dump_tag(const char *s, tag_t *t)  void dump_id(char *p, char *d)
107  {  {
108          fprintf(stderr, "%s", s);          printf("%s", p);
109          dump_revid(t->tag, t->rev);          if(d)
110                    printf("'%s'\n", d);
111            else
112                    printf("<null>\n");
113  }  }
114    
115  void dump_rev(revision_t *r)  void dump_idrev(char *p, idrev_t *t)
116  {  {
117          int i;          printf("%s", p);
118          dump_revid("Revision", r->rev);          if(t)
119          fprintf(stderr, "Revision.Info   : '%s'\n", r->info);          {
120          fprintf(stderr, "Revision.Comment: '%s'\n", r->comment);                  printf("'%s' -> ", t->id);
121          for(i = 0; i < r->ntags; i++)                  dump_rev("", t->rev);
122                  dump_tag("Revision.Tag: ", r->tags[i]);          }
123            else
124                    printf("<null>\n");
125  }  }
126    
127  void dump_branch(branch_t *b)  void dump_tag(char *p, tag_t *t)
128    {
129            printf("%s", p);
130            if(t)
131            {
132                    printf("'%s' -> ", t->tag);
133                    dump_rev("", t->rev);
134            }
135            else
136                    printf("<null>\n");
137    }
138    
139    void dump_delta(char *p, delta_t *d)
140  {  {
141          int i;          int i;
142          fprintf(stderr, "Branch: '%s'\n", b->branch);          printf("%sdelta.rev   : ", p);
143          if(b->tag)          dump_rev("", d->rev);
144                  dump_tag("branchtag:", b->tag);          printf("%sdelta.date  : %s\n", p, d->date);
145          for(i = 0; i < b->nrevs; i++)          printf("%sdelta.author: %s\n", p, d->author);
146                  fprintf(stderr, "Branch.Rev: '%s'\n", b->revs[i]->rev->rev);          printf("%sdelta.state : %s\n", p, d->state);
147            for(i = 0; d->branches && i < d->branches->nrevs; i++)
148            {
149                    printf("%sdelta.branch: ", p);
150                    dump_rev("", d->branches->revs[i]);
151            }
152            printf("%sdelta.next  : ", p);
153            dump_rev("", d->next);
154            printf("\n");
155    }
156    
157    void dump_dtext(char *p, dtext_t *d)
158    {
159            printf("%sdtext.rev  : ", p);
160            dump_rev("", d->rev);
161            printf("%sdtext.log  : %d bytes\n", p, d->log ? strlen(d->log) : -1);
162            printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
163            printf("\n");
164  }  }
165    
166  void dump_log(rcsfilelog_t *r)  void dump_rcsfile(rcsfile_t *rcs)
167  {  {
168          int i;          int i;
169            printf("root   : '%s'\n", rcs->root);
170            printf("module : '%s'\n", rcs->module);
171            printf("file   : '%s'\n", rcs->file);
172            dump_rev("head   : ", rcs->head);
173            dump_rev("branch : ", rcs->branch);
174            printf("access :\n");
175            for(i = 0; rcs->access && i < rcs->access->nids; i++)
176                    dump_id("\t", rcs->access->ids[i]);
177            printf("tags   :\n");
178            for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
179                    dump_tag("\t", rcs->tags->tags[i]);
180            printf("locks  :%s\n", rcs->strict ? " (strict)" : "");
181            for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
182                    dump_idrev("\t", rcs->locks->idrevs[i]);
183            printf("comment: '%s'\n", rcs->comment);
184            printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
185            printf("deltas :\n");
186            for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
187                    dump_delta("\t", rcs->deltas->deltas[i]);
188            printf("desc   : '%s'\n", rcs->desc);
189            printf("dtexts :\n");
190            for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
191                    dump_dtext("\t", rcs->dtexts->dtexts[i]);
192    
193          fprintf(stderr, "Path   : '%s'\n", r->path);          fflush(stdout);
         fprintf(stderr, "Name   : '%s'\n", r->name);  
         dump_revid("Head", r->head);  
         fprintf(stderr, "Branch : '%s'\n", r->branch);  
         fprintf(stderr, "Locks  : '%s'\n", r->locks);  
         fprintf(stderr, "Access : '%s'\n", r->access);  
         fprintf(stderr, "Keyword: '%s'\n", r->keyword);  
         fprintf(stderr, "Total  : '%s'\n", r->totalrevs);  
         fprintf(stderr, "Comment: '%s'\n", r->comment);  
         for(i = 0; i < r->ntags; i++)  
                 dump_tag("", r->tags[i]);  
         for(i = 0; i < r->nrevs; i++)  
                 dump_rev(r->revs[i]);  
         for(i = 0; i < r->nbranches; i++)  
                 dump_branch(r->branches[i]);  
194  }  }
 #endif  
195    
196  /*  /*
197   **************************************************************************   **************************************************************************
198   * Retrieve the log entries   * Read the rcs file
199   **************************************************************************   **************************************************************************
200   */   */
201  FILE *get_log(const char *cvsroot, const char *module, const char *file)  rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
202  {  {
         pid_t pid;  
         int nul;  
         FILE *tmp;  
203          char *cmd = NULL;          char *cmd = NULL;
204          int status;          int rv;
         mode_t um;  
205    
206          if((nul = open(devnull, O_RDWR, S_IRUSR|S_IWUSR)) == -1)          cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
207            sprintf(cmd, "%s%s%s", cvsroot, module, file);
208            if(!(rcsin = fopen(cmd, "rb")))
209            {
210                    perror(cmd);
211                  return NULL;                  return NULL;
212            }
213            input_file = cmd;
214            line_number = 1;
215            rv = rcsparse();
216            fclose(rcsin);
217            if(rv)
218                    return NULL;
219            xfree(cmd);
220            input_file = NULL;
221            rcsfile->root = xstrdup(cvsroot);
222            rcsfile->module = xstrdup(module);
223            rcsfile->file = xstrdup(file);
224            return rcsfile;
225    }
226    
227          um = umask(0177);       /* Set tempfiles to max 0600 permissions */  /*
228          if((tmp = tmpfile()) == NULL)   **************************************************************************
229     * Sort and find helpers
230     **************************************************************************
231     */
232    int count_dots(const char *s)
233    {
234            int i;
235            for(i = 0; *s; s++)
236          {          {
237                  close(nul);                  if(*s == '.')
238                  return NULL;                          i++;
239          }          }
240          umask(um);          return i;
241    }
242    
243          cmd = xmalloc(strlen(cvsroot) + + strlen(module) + strlen(file) + 2 + 1);  int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
244          sprintf(cmd, "%s/%s/%s", cvsroot, module, file);  {
245            int d1, d2;
246            char *c1, *c2;
247            char *v1, *v2;
248            char *s1, *s2;
249            int retval = 0;
250            assert(r1 != NULL);
251            assert(r2 != NULL);
252            if(bcmp)
253            {
254                    assert(r1->branch != NULL);
255                    assert(r2->branch != NULL);
256                    c1 = r1->branch;
257                    c2 = r2->branch;
258            }
259            else
260            {
261                    assert(r1->rev != NULL);
262                    assert(r2->rev != NULL);
263                    c1 = r1->rev;
264                    c2 = r2->rev;
265            }
266    
267          switch(pid = fork())          d1 = count_dots(c1);
268            d2 = count_dots(c2);
269            if(d1 != d2)
270          {          {
271          case -1:        /* Error */                  return d1 - d2;
                 close(nul);  
                 fclose(tmp);  
                 xfree(cmd);  
                 return NULL;  
         case 0:         /* Child */  
                 if((dup2(nul, STDIN_FILENO)) == -1)     exit(126);  
                 if((dup2(fileno(tmp), STDOUT_FILENO)) == -1)    exit(126);  
                 if((dup2(nul, STDERR_FILENO)) == -1)    exit(126);  
                 close(nul);  
                 fclose(tmp);  
                 execl(rlogcmd, rlogcmd, cmd, NULL);  
                 exit(127);  
                 break;  
         default:        /* Parent */  
                 close(nul);  
                 xfree(cmd);  
                 while(1)  
                 {  
                         if(waitpid(pid, &status, 0) == -1)  
                         {  
                                 if(errno != EINTR)  
                                 {  
                                         fclose(tmp);  
                                         return NULL;  
                                 }  
                         }  
                         else  
                                 break;  
                 }  
                 break;  
272          }          }
273    
274          if(WIFEXITED(status) && WEXITSTATUS(status) == 0)          s1 = v1 = xstrdup(c1);
275            s2 = v2 = xstrdup(c2);
276            while(1)
277          {          {
278                  if(fseek(tmp, 0, SEEK_SET) != (off_t)-1)                  char *vc1 = strchr(s1, '.');
279                    char *vc2 = strchr(s2, '.');
280                    if(vc1 && vc2)
281                            *vc1 = *vc2 = '\0';
282                    if(*s1 && *s2)
283                  {                  {
284                          return tmp;                          d1 = atoi(s1);
285                  }                          d2 = atoi(s2);
286                  else                          if(d1 != d2)
287                  {                          {
288                          fclose(tmp);                                  retval = d1 - d2;
289                          return NULL;                                  break;
290                            }
291                  }                  }
292                    if(!vc1 || !vc2)
293                            break;
294                    s1 = vc1 + 1;
295                    s2 = vc2 + 1;
296          }          }
297          else          xfree(v1);
298                  fclose(tmp);          xfree(v2);
299          return NULL;          return retval;
300  }  }
301    
302  /*  /*
303   **************************************************************************   **************************************************************************
304   * Parse the log entries   * Reorganise the rcsfile for the branches
305     *
306     * Basically, we have a list of deltas (i.e. administrative info on
307     * revisions) and a list of delta text (the actual logs and diffs).
308     * The deltas are linked through the 'next' and the 'branches' fields
309     * which describe the tree-structure of revisions.
310     * The reorganisation means that we put each delta and corresponding
311     * delta text in a revision structure and assign it to a specific
312     * branch. This is required because we want to be able to calculate
313     * the bounding boxes of each branch. The revisions expand vertically
314     * and the branches expand horizontally.
315     * The reorganisation is performed in these steps:
316     * 1 - sort deltas and detla text on revision number for quick lookup
317     * 2 - start at the denoted head revision:
318     *      * create a branch structure and add this revision
319     *      * for each 'branches' in the delta do:
320     *              - walk all 'branches' of the delta and recursively goto 2
321     *                with the denoted branch delta as new head
322     *              - backlink the newly create sub-branch to the head revision
323     *                so that we can draw them recursively
324     *      * set head to the 'next' field and goto 2 until no next is
325     *        available
326     * 3 - update the administration
327   **************************************************************************   **************************************************************************
328   */   */
329  char *strip_dup(const char *s)  int sort_delta(const void *d1, const void *d2)
330  {  {
331          int l = strlen(s);          return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
332          char *c = xmalloc(l+1);  }
333    
334          strcpy(c, s);  int search_delta(const void *r, const void *d)
335          while(*c == ' ' || *c == '\t')  {
336          {          return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
                 memmove(c, c+1, l--);  
         }  
         while(l && strchr(" \t\r\n", c[l]))  
                 c[l--] = '\0';  
         return c;  
337  }  }
338    
339  revid_t *make_revid(const char *s)  delta_t *find_delta(delta_t **dl, int n, rev_t *r)
340  {  {
341          char *c = strip_dup(s);          delta_t **d;
342          char *cptr;          d = bsearch(r, dl, n, sizeof(*dl), search_delta);
343          int dots = 0;          if(!d)
344          revid_t *r = xmalloc(sizeof(*r));                  return NULL;
345          for(cptr = c; *cptr; cptr++)          return *d;
346          {  }
347                  if(*cptr == '.')  
348                          dots++;  int sort_dtext(const void *d1, const void *d2)
349          }  {
350          if(!dots)          return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
351          {  }
352                  r->rev = xstrdup("");  
353                  r->branch = xstrdup(s);  int search_dtext(const void *r, const void *d)
354                  r->isbranch = 1;  {
355          }          return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
356          else if(!*c)  }
357    
358    dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
359    {
360            dtext_t **d;
361            d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
362            if(!d)
363                    return NULL;
364            return *d;
365    }
366    
367    rev_t *dup_rev(const rev_t *r)
368    {
369            rev_t *t = xmalloc(sizeof(*t));
370            t->rev = xstrdup(r->rev);
371            t->branch = xstrdup(r->branch);
372            t->isbranch = r->isbranch;
373            return t;
374    }
375    
376    branch_t *new_branch(delta_t *d, dtext_t *t)
377    {
378            branch_t *b = xmalloc(sizeof(*b));
379            revision_t *r = xmalloc(sizeof(*r));
380            r->delta = d;
381            r->dtext = t;
382            r->rev = d->rev;
383            r->branch = b;
384            b->branch = dup_rev(d->rev);
385            b->branch->isbranch = 1;
386            b->nrevs = 1;
387            b->revs = xmalloc(sizeof(b->revs[0]));
388            b->revs[0] = r;
389            return b;
390    }
391    
392    revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
393    {
394            revision_t *r = xmalloc(sizeof(*r));
395            r->delta = d;
396            r->dtext = t;
397            r->rev = d->rev;
398            r->branch = b;
399            b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
400            b->revs[b->nrevs] = r;
401            b->nrevs++;
402            return r;
403    }
404    
405    void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
406    {
407            branch_t *b;
408            dtext_t *text;
409            revision_t *currev;
410    
411            assert(head != NULL);
412    
413            if(head->flag)
414          {          {
415                  r->rev = xstrdup("?.?");                  fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
416                  r->branch = xstrdup("?");                  return;
417          }          }
418          else if(dots & 1)          head->flag++;
419            text = find_dtext(sdt, nsdt, head->rev);
420    
421            /* Create a new branch for this head */
422            b = new_branch(head, text);
423            *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
424            (*bl)[*nbl] = b;
425            (*nbl)++;
426            currev = b->revs[0];
427            while(1)
428          {          {
429                  char *t;                  /* Process all sub-branches */
430                  r->rev = c;                  if(head->branches)
                 r->branch = xstrdup(c);  
                 cptr = strrchr(r->branch, '.');  
                 assert(cptr != NULL);  
                 *cptr = '\0';  
                 t = strrchr(r->branch, '.');  
                 if((t&& !strcmp(t+1, "0")) || (!t && !strcmp(r->branch, "0")))  
431                  {                  {
432                          /* Magic branch numbers "x.x.0.x" */                          int i;
433                          r->isbranch = 1;                          for(i = 0; i < head->branches->nrevs; i++)
434                          if(t)                          {
435                                  strcpy(t+1, cptr+1);                                  delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
436                          else                                  int btag = *nbl;
437                                  strcpy(r->branch, cptr+1);                                  if(!d)
438                                            continue;
439                                    build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
440    
441                                    /* Set the new branch's origin */
442                                    (*bl)[btag]->branchpoint = currev;
443    
444                                    /* Add branch to this revision */
445                                    currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
446                                    currev->branches[currev->nbranches] = (*bl)[btag];
447                                    currev->nbranches++;
448                            }
449                  }                  }
450    
451                    /* Walk through the next list */
452                    if(!head->next)
453                            return;
454    
455                    head = find_delta(sdl, nsdl, head->next);
456                    if(!head)
457                    {
458                            fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);
459                            return;
460                    }
461                    if(head->flag)
462                    {
463                            fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);
464                            return;
465                    }
466                    head->flag++;
467                    text = find_dtext(sdt, nsdt, head->rev);
468                    currev = add_to_branch(b, head, text);
469          }          }
470          else  }
471    
472    int reorganise_branches(rcsfile_t *rcs)
473    {
474            delta_t **sdelta;
475            int nsdelta;
476            dtext_t **sdtext;
477            int nsdtext;
478            delta_t *head;
479            branch_t **bl;
480            int nbl;
481            int i;
482    
483            assert(rcs->deltas != NULL);
484            assert(rcs->head != NULL);
485    
486            /* Make a new list for quick lookup */
487            nsdelta = rcs->deltas->ndeltas;
488            sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
489            memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
490            qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
491    
492            /* Do the same for the delta text */
493            nsdtext = rcs->dtexts->ndtexts;
494            sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
495            memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
496            qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
497    
498            /* Start from the head of the trunk */
499            head = find_delta(sdelta, nsdelta, rcs->head);
500            if(!head)
501          {          {
502                  r->isbranch = 1;                  fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
503                  r->branch = c;                  return 0;
                 r->rev = xmalloc(strlen(c) + 3);  
                 strcpy(r->rev, c);  
                 strcat(r->rev, ".?");  
504          }          }
505          return r;          bl = NULL;
506            nbl = 0;
507            build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
508    
509            /* Reverse the head branch */
510            for(i = 0; i < bl[0]->nrevs/2; i++)
511            {
512                    revision_t *r;
513                    r = bl[0]->revs[i];
514                    bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
515                    bl[0]->revs[bl[0]->nrevs-i-1] = r;
516            }
517    
518            /* Update the branch-number of the head because it was reversed */
519            xfree(bl[0]->branch->branch);
520            bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
521    
522            /* Keep the admin */
523            rcs->branches = bl;
524            rcs->nbranches = nbl;
525            rcs->sdelta = sdelta;
526            rcs->nsdelta = nsdelta;
527            rcs->sdtext = sdtext;
528            rcs->nsdtext = nsdtext;
529            rcs->active = bl[0];
530            return 1;
531    }
532    
533    /*
534     **************************************************************************
535     * Assign the symbolic tags to the revisions and branches
536     *
537     * The tags point to revision numbers somewhere in the tree structure
538     * of branches and revisions. First we make a sorted list of all
539     * revisions and then we assign each tag to the proper revision.
540     **************************************************************************
541     */
542    int sort_revision(const void *r1, const void *r2)
543    {
544            return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
545  }  }
546    
547  char *add_comment(char *c, const char *n)  int search_revision(const void *t, const void *r)
548  {  {
549          int l;          return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
550    }
551    
552    int sort_branch(const void *b1, const void *b2)
553    {
554            return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
555    }
556    
557    int search_branch(const void *t, const void *b)
558    {
559            return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
560    }
561    
562    char *previous_rev(const char *c)
563    {
564            int dots = count_dots(c);
565            char *cptr;
566          char *r;          char *r;
567          assert(n != NULL);          if(!dots)
         l = strlen(n);  
         if(!c)  
568          {          {
569                  r = xmalloc(l+1);                  fprintf(stderr, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
570                  strcpy(r, n);                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */
571          }          }
572          else          if(dots & 1)
573          {          {
574                  r = xmalloc(l+strlen(c)+1+1);                  /* Is is a revision we want the parent of */
575                  strcpy(r, c);                  r = xstrdup(c);
576                  strcat(r, "\n");                  cptr = strrchr(r, '.');
577                  strcat(r, n);                  assert(cptr != NULL);
578                    if(dots == 1)
579                    {
580                            fprintf(stderr, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
581                            /* FIXME: What is the parent of 1.1? */
582                            cptr[1] = '\0';
583                            strcat(r, "0");
584                            return r;
585                    }
586                    /* Here we have a "x.x[.x.x]+" case */
587                    *cptr = '\0';
588                    cptr = strrchr(r, '.');
589                    assert(cptr != NULL);
590                    *cptr = '\0';
591                    return r;
592          }          }
593            /* It is a branch we want the parent of */
594            r = xstrdup(c);
595            cptr = strrchr(r, '.');
596            assert(cptr != NULL);
597            *cptr = '\0';
598          return r;          return r;
599  }  }
600    
601  int get_line(FILE *fp, char *buf, int maxlen)  int assign_tags(rcsfile_t *rcs)
602  {  {
603          int n;          int i;
604          int seennl;          int nr;
605  retry:  
606          seennl = 0;          for(i = nr = 0; i < rcs->nbranches; i++)
607          if(!fgets(buf, maxlen, fp))                  nr += rcs->branches[i]->nrevs;
608                  return feof(fp) ? 0 : -1;  
609          n = strlen(buf);          rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
610          while(n && buf[n-1] == '\n')          rcs->nsrev = nr;
611          {          for(i = nr = 0; i < rcs->nbranches; i++)
                 seennl = 1;  
                 buf[--n] = '\0';  
         }  
         if(!n)  
                 goto retry;  
         if(!seennl)  
612          {          {
613                  while(fgetc(fp) != '\n')                  memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
614                          ;                  nr += rcs->branches[i]->nrevs;
615          }          }
         return n;  
 }  
616    
617  rcsfilelog_t *parse_log(FILE *fp)          qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
618  {          qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
         rcsfilelog_t *p;  
         int state = 0;  
         regex_t rerev;  
         regex_t reinfo;  
619    
620          if(regcomp(&rerev, "^revision[ \\t]*[0-9]+(\\.[0-9]+)*", REG_EXTENDED))          if(!rcs->branch)
                 return NULL;  
         if(regcomp(&reinfo, "^date:[^;]*;[ \\t]*author:[^;]*;[ \\t]+state:", REG_EXTENDED))  
621          {          {
622                  regfree(&rerev);                  /* The main trunk is the active trunk */
623                  return NULL;                  rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
624                    rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
625                    rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
626                    rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
627                    rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
628                    rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
629                    rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
630                    rcs->tags->ntags++;
631          }          }
632          p = xmalloc(sizeof(*p));  
633          while(state != 4)          /* We should have at least two tags (HEAD and MAIN) */
634            assert(rcs->tags != 0);
635    
636            for(i = 0; i < rcs->tags->ntags; i++)
637          {          {
638                  char buf[256];                  tag_t *t = rcs->tags->tags[i];
639                  int n;                  if(t->rev->isbranch)
                 n = get_line(fp, buf, sizeof(buf));  
                 if(!n)  
                         break;  
                 if(n == -1)  
640                  {                  {
641                          perror("tempfile read");                          branch_t **b;
642                          xfree(p);  add_btag:
643                          regfree(&rerev);                          b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
644                          regfree(&reinfo);                          if(!b)
645                          return NULL;                          {
646                  }                                  rev_t rev;
647                  switch(state)                                  revision_t **r;
648                  {                                  /* This happens for the magic branch numbers if there are
649                  case 0: /* Prologue */                                   * no commits within the new branch yet. So, we add the
650  more_prologue:                                   * branch and try to continue.
651                          if(!strncmp(buf, "RCS file:", 9))                                   */
652                          {                                  rev.rev = previous_rev(t->rev->branch);
653                                  p->path = strip_dup(buf+9);                                  rev.branch = NULL;
654                          }                                  rev.isbranch = 0;
655                          else if(!strncmp(buf, "Working file:", 13))                                  r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
656                          {                                  xfree(rev.rev);
657                                  p->name = strip_dup(buf+13);                                  if(!r)
                         }  
                         else if(!strncmp(buf, "head:", 5))  
                         {  
                                 p->head = make_revid(buf+5);  
                         }  
                         else if(!strncmp(buf, "branch:", 7))  
                         {  
                                 p->branch = strip_dup(buf+7);  
                         }  
                         else if(!strncmp(buf, "locks:", 6))  
                         {  
                                 p->locks = strip_dup(buf+6);  
                         }  
                         else if(!strncmp(buf, "access list:", 12))  
                         {  
                                 p->access = strip_dup(buf+12);  
                         }  
                         else if(!strncmp(buf, "keyword substitution:", 21))  
                         {  
                                 p->keyword = strip_dup(buf+21);  
                         }  
                         else if(!strncmp(buf, "total revisions:", 16))  
                         {  
                                 p->totalrevs = strip_dup(buf+16);  
                         }  
                         else if(!strncmp(buf, "description:", 12))  
                         {  
                                 state = 2;  
                         }  
                         else if(!strncmp(buf, "symbolic names:", 15))  
                         {  
                                 state = 1;  
                         }  
                         else  
                         {  
                                 fprintf(stderr, "Unknown keyword(s) in line '%s' (state=%d)\n", buf, state);  
                                 xfree(p);  
                                 return NULL;  
                         }  
                         break;  
                 case 1: /* Tags */  
                         if(*buf != '\t')  
                         {  
                                 state = 0;  
                                 goto more_prologue;  
                         }  
                         else  
                         {  
                                 char *rev = strrchr(buf, ':');  
                                 tag_t *t;  
                                 if(!rev)  
658                                  {                                  {
659                                          state = 2;                                          if(!quiet)
660                                          goto more_prologue;                                                  fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);
661                                    }
662                                    else
663                                    {
664                                            rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
665                                            rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
666                                            rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
667                                            rcs->branches[rcs->nbranches]->branchpoint = *r;
668                                            (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
669                                            (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
670                                            (*r)->nbranches++;
671                                            rcs->nbranches++;
672                                            /* Resort the branches */
673                                            qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
674                                            goto add_btag;
675                                  }                                  }
                                 *rev = '\0';  
                                 t = xmalloc(sizeof(*t));  
                                 t->tag = strip_dup(buf);  
                                 t->rev = make_revid(rev+1);  
                                 p->tags = xrealloc(p->tags, (p->ntags+1) * sizeof(p->tags[0]));  
                                 p->tags[p->ntags] = t;  
                                 p->ntags++;  
                         }  
                         break;  
                 case 2: /* Description */  
 add_description:  
                         if(!strcmp(buf, "----------------------------"))  
                         {  
                                 /* End of description */  
                                 state = 3;  
                                 break;  
                         }  
                         else if(!strcmp(buf, "============================================================================="))  
                         {  
                                 /* End of log */  
                                 state = 4;  
                                 break;  
676                          }                          }
                         if(!p->nrevs)  
                                 p->comment = add_comment(p->comment, buf);  
677                          else                          else
                                 p->revs[p->nrevs-1]->comment = add_comment(p->revs[p->nrevs-1]->comment, buf);  
                         break;  
                 case 3:  
                         if(!regexec(&rerev, buf, 0, NULL, 0))  
678                          {                          {
679                                  revision_t *r = xmalloc(sizeof(*r));                                  branch_t *bb = *b;
680                                  p->revs = xrealloc(p->revs, (p->nrevs+1) * sizeof(p->revs[0]));                                  bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
681                                  p->revs[p->nrevs] = r;                                  bb->tags[bb->ntags] = t;
682                                  p->nrevs++;                                  bb->ntags++;
                                 r->rev = make_revid(buf+8);  
683                          }                          }
684                          else if(!regexec(&reinfo, buf, 0, NULL, 0))                  }
685                    else
686                    {
687                            revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
688                            if(!r)
689                          {                          {
690                                  assert(p->nrevs > 0);                                  if(!quiet)
691                                  p->revs[p->nrevs-1]->info = strip_dup(buf);                                          fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
692                          }                          }
693                          else                          else
694                          {                          {
695                                  /* No match means the description/comment */                                  revision_t *rr = *r;
696                                  state = 2;                                  rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
697                                  goto add_description;                                  rr->tags[rr->ntags] = t;
698                                    rr->ntags++;
699                          }                          }
                         break;  
                 default:  
                         fprintf(stderr, "Illegal state (%d) in parser\n", state);  
                         xfree(p);  
                         regfree(&rerev);  
                         regfree(&reinfo);  
                         return NULL;  
700                  }                  }
701          }          }
702          regfree(&rerev);  
703          regfree(&reinfo);          /* We need to reset the first in the list of branches to the
704          return p;           * active branch to ensure the drawing of all
705             */
706            if(rcs->active != rcs->branches[0])
707            {
708                    branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
709                    branch_t *t;
710                    assert(b != NULL);
711                    t = *b;
712                    *b = rcs->branches[0];
713                    rcs->branches[0] = t;
714            }
715            return 1;
716  }  }
717    
718  /*  /*
719   **************************************************************************   **************************************************************************
720   * Sort and find helpers   * String expansion
721   **************************************************************************   **************************************************************************
722   */   */
723  int tag_sort(const void *t1, const void *t2)  static char *_string;
724  {  static int _nstring;
725  #define TAGPTR(t)       (*((tag_t **)t))  static int _nastring;
         return strcmp(TAGPTR(t1)->rev->rev, TAGPTR(t2)->rev->rev);  
 #undef TAGPTR  
 }  
726    
727  int rev_sort(const void *v1, const void *v2)  void add_string_str(const char *s)
728  {  {
729  #define REVPTR(t)       (*((revision_t **)t))          int l = strlen(s) + 1;
730          return strcmp(REVPTR(v1)->rev->rev, REVPTR(v2)->rev->rev);          if(_nstring + l > _nastring)
731  #undef REVPTR          {
732                    _nastring += 128;
733                    _string = xrealloc(_string, _nastring * sizeof(_string[0]));
734            }
735            memcpy(_string+_nstring, s, l);
736            _nstring += l-1;
737  }  }
738    
739  int branch_sort(const void *b1, const void *b2)  void add_string_ch(int ch)
740  {  {
741  #define BPTR(t) (*((branch_t **)t))          char buf[2];
742          return strcmp(BPTR(b1)->branch, BPTR(b2)->branch);          buf[0] = ch;
743  #undef BPTR          buf[1] = '\0';
744            add_string_str(buf);
745  }  }
746    
747  int rev_cmp(const void *id, const void *v)  void add_string_date(const char *d)
748  {  {
749  #define REVPTR(t)       (*((revision_t **)t))          struct tm tm, *tmp;
750          return strcmp(((revid_t *)id)->rev, REVPTR(v)->rev->rev);          int n;
751  #undef REVPTR          time_t t;
752            char *buf;
753            int nbuf;
754    
755            memset(&tm, 0, sizeof(tm));
756            n = sscanf(d, "%d.%d.%d.%d.%d.%d",
757                            &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
758                            &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
759            tm.tm_mon--;
760            if(tm.tm_year > 1900)
761                    tm.tm_year -= 1900;
762            t = mktime(&tm);
763            if(n != 6 || t == (time_t)(-1))
764            {
765                    add_string_str("<invalid date>");
766                    return;
767            }
768    
769            tmp = localtime(&t);
770            nbuf = strlen(conf.date_format) * 16;   /* Should be enough to hold all types of expansions */
771            buf = xmalloc(nbuf);
772            strftime(buf, nbuf, conf.date_format, tmp);
773            add_string_str(buf);
774            xfree(buf);
775  }  }
776    
777  revision_t *find_revision(rcsfilelog_t * rcs, revid_t *id)  char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
778  {  {
779          revision_t **r;          char nb[32];
780          if(id->isbranch)          char nr[32];
781                  return NULL;          char *base;
         r = bsearch(id, rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_cmp);  
         if(!r)  
                 return NULL;  
         else  
                 return *r;  
 }  
782    
783  int branch_cmp(const void *s, const void *b)          if(!s)
784  {                  return xstrdup("");
         return strcmp((const char *)s, (*((branch_t **)b))->branch);  
 }  
785    
786  branch_t *find_branch(rcsfilelog_t *rcs, const char *id)          _nstring = 0;
787  {          if(_string)
788          branch_t **b;                  _string[0] = '\0';
         b = bsearch(id, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_cmp);  
         if(!b)  
                 return NULL;  
         else  
                 return *b;  
 }  
789    
790  tag_t *find_branchtag(rcsfilelog_t * rcs, const char *id)          sprintf(nb, "%d", rcs->nbranches);
791  {          sprintf(nr, "%d", rcs->nsrev);
792          int i;          for(; *s; s++)
         for(i = 0; i < rcs->ntags; i++)  
793          {          {
794                  if(!rcs->tags[i]->rev->isbranch)                  if(*s == '%')
795                          continue;                  {
796                  if(!strcmp(id, rcs->tags[i]->rev->branch))                          switch(*++s)
797                          return rcs->tags[i];                          {
798                            case 'c':
799                            case 'C':
800                                    add_string_str(conf.cvsroot);
801                                    if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
802                                    {
803                                            /* Strip the trailing '/' */
804                                            _nstring--;
805                                            _string[_nstring] = '\0';
806                                    }
807                                    break;
808                            case 'f':
809                            case 'F':
810                                    base = strrchr(rcs->file, '/');
811                                    if(!base)
812                                            add_string_str(rcs->file);
813                                    else
814                                            add_string_str(base+1);
815                                    if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
816                                    {
817                                            _nstring -= 2;
818                                            _string[_nstring] = '\0';
819                                    }
820                                    break;
821                            case 'p':
822                                    base = strrchr(rcs->file, '/');
823                                    if(base)
824                                    {
825                                            char *c = xstrdup(rcs->file);
826                                            base = strrchr(c, '/');
827                                            assert(base != NULL);
828                                            base[1] = '\0';
829                                            add_string_str(c);
830                                            xfree(c);
831                                    }
832                                    /*
833                                     * We should not add anything here because we can encounter
834                                     * a completely empty path, in which case we do not want
835                                     * to add any slash. This prevents a inadvertent root redirect.
836                                     *
837                                     * else
838                                     *      add_string_str("/");
839                                     */
840                                    break;
841                            case 'm':
842                            case 'M':
843                                    add_string_str(conf.cvsmodule);
844                                    if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
845                                    {
846                                            /* Strip the trailing '/' */
847                                            _nstring--;
848                                            _string[_nstring] = '\0';
849                                    }
850                                    break;
851                            case 'r': add_string_str(nr); break;
852                            case 'b': add_string_str(nb); break;
853                            case '%': add_string_ch('%'); break;
854                            case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
855                            case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
856                            case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
857                            case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
858                            case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
859                            case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
860                            case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
861                            case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
862                            case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
863                            case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
864                            case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
865                            case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
866                            case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
867                            case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
868                            case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
869                            case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
870                            case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
871                            default:
872                                    add_string_ch('%');
873                                    add_string_ch(*s);
874                                    break;
875                            }
876                    }
877                    else
878                            add_string_ch(*s);
879          }          }
880          return NULL;          return xstrdup(_string);
881  }  }
882    
883  /*  /*
# Line 670  Line 887 
887   */   */
888  int get_swidth(const char *s, font_t *f)  int get_swidth(const char *s, font_t *f)
889  {  {
890          if(!s)          int n;
891            int m;
892            if(!s || !*s)
893                  return 0;                  return 0;
894          return strlen(s) * (*f)->w;          for(n = m = 0; *s; n++, s++)
895            {
896                    if(*s == '\n')
897                    {
898                            if(n > m)
899                                    m = n;
900                            n = 0;
901                    }
902            }
903            if(n > m)
904                    m = n;
905            return m * (*f)->w;
906  }  }
907    
908  int get_sheight(const char *s, font_t *f)  int get_sheight(const char *s, font_t *f)
909  {  {
910          int nl;          int nl;
911          if(!s)          if(!s || !*s)
912                  return 0;                  return 0;
913          for(nl = 1; *s; s++)          for(nl = 1; *s; s++)
914          {          {
# Line 688  Line 918 
918          return nl * (*f)->h;          return nl * (*f)->h;
919  }  }
920    
921  void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color)  void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
922  {  {
923          int r2 = 2*r;          int r2 = 2*r;
924          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
925          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
926          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
927          gdImageLine(im, x2, y1+r, x2, y2-r, color->id);          gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
928            if(conf.box_shadow)
929            {
930                    gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, black_color.id);
931                    gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, black_color.id);
932            }
933          if(r)          if(r)
934          {          {
935                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
936                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
937                  gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);                  gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);
938                  gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);                  gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);
939                    if(conf.box_shadow)
940                    {
941                            /* FIXME: Pixelization is not correct here */
942                            gdImageArc(im, x2-r+1, y2-r+1, r2, r2,   0,  90, black_color.id);
943                    }
944          }          }
945            gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
946  }  }
947    
948  void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)  void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
# Line 724  Line 965 
965          gdImageString(im, *f, x+xx+1, y+yy, s, c->id);          gdImageString(im, *f, x+xx+1, y+yy, s, c->id);
966  }  }
967    
968    void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
969    {
970            char *t;
971            char *d;
972            d = s = xstrdup(s);
973            do
974            {
975                    t = strchr(s, '\n');
976                    if(t)
977                            *t = '\0';
978                    draw_string(im, s, f, x, y, align, c);
979                    y += get_sheight(s, f);
980                    s = t+1;
981            } while(t);
982            xfree(d);
983    }
984    
985  void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)  void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)
986  {  {
987          int lx = cx - r->w/2;          int lx = cx - r->w/2;
988          int rx = lx + r->w;          int rx = lx + r->w;
989          int i;          int i;
990          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color);          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
991          ty += conf.rev_tspace;          ty += conf.rev_tspace;
992          draw_string(im, r->rev->rev, &conf.rev_font, cx, ty, ALIGN_HC, &conf.rev_color);          draw_string(im, r->rev->rev, &conf.rev_font, cx, ty, ALIGN_HC, &conf.rev_color);
993          ty += get_sheight(r->rev->rev, &conf.rev_font);          ty += get_sheight(r->rev->rev, &conf.rev_font);
994            draw_stringnl(im, r->revtext, &conf.rev_text_font, cx, ty, ALIGN_HC, &conf.rev_text_color);
995            ty += get_sheight(r->revtext, &conf.rev_text_font);
996          for(i = 0; i < r->ntags; i++)          for(i = 0; i < r->ntags; i++)
997          {          {
998                  draw_string(im, r->tags[i]->tag, &conf.tag_font, cx, ty, ALIGN_HC, &conf.tag_color);                  draw_string(im, r->tags[i]->tag, &conf.tag_font, cx, ty, ALIGN_HC, &conf.tag_color);
# Line 746  Line 1006 
1006          int rx = lx + b->w;          int rx = lx + b->w;
1007          int yy;          int yy;
1008          int i;          int i;
1009          draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color);          /*draw_rbox(im, cx-b->tw/2-1, ty-1, cx+b->tw/2+1, ty+b->th+1, 0, &conf.title_color);*/
1010            draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1011          yy = conf.branch_tspace;          yy = conf.branch_tspace;
1012          draw_string(im, b->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);          draw_string(im, b->branch->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
1013          yy += get_sheight(b->branch, &conf.branch_font);          yy += get_sheight(b->branch->branch, &conf.branch_font);
1014          if(b->tag)          for(i = 0; i < b->ntags; i++)
1015          {          {
1016                  draw_string(im, b->tag->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);                  draw_string(im, b->tags[i]->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
1017                    yy += get_sheight(b->tags[i]->tag, &conf.branch_font);
1018          }          }
1019    
1020          ty += b->h;          ty += b->h;
# Line 765  Line 1027 
1027          }          }
1028  }  }
1029    
 static char *_title;  
 static int _ntitle;  
 static int _natitle;  
   
 void add_title_str(const char *s)  
 {  
         int l = strlen(s) + 1;  
         if(_ntitle + l > _natitle)  
         {  
                 _natitle += 128;  
                 _title = xrealloc(_title, _natitle * sizeof(_title[0]));  
         }  
         memcpy(_title+_ntitle, s, l);  
         _ntitle += l-1;  
 }  
   
 void add_title_ch(int ch)  
 {  
         char buf[2];  
         buf[0] = ch;  
         buf[1] = '\0';  
         add_title_str(buf);  
 }  
   
 char *expand_title(rcsfilelog_t *rcs)  
 {  
         char nb[32];  
         char nr[32];  
         char *cptr;  
   
         sprintf(nb, "%d", rcs->nbranches);  
         sprintf(nr, "%d", rcs->nrevs);  
         for(cptr = conf.title; *cptr; cptr++)  
         {  
                 if(*cptr == '%')  
                 {  
                         switch(*++cptr)  
                         {  
                         case 'c': add_title_str(conf.cvsroot); break;  
                         case 'f': add_title_str(rcs->name); break;  
                         case 'm': add_title_str(conf.cvsmodule); break;  
                         case 'r': add_title_str(nr); break;  
                         case 'b': add_title_str(nb); break;  
                         case '%': add_title_ch('%'); break;  
                         default:  
                                 add_title_ch('%');  
                                 add_title_ch(*cptr);  
                                 break;  
                         }  
                 }  
                 else  
                         add_title_ch(*cptr);  
         }  
         return _title;  
 }  
   
 void draw_title(gdImagePtr im, char *title)  
 {  
         char *t;  
         char *s = title;  
         int x = conf.title_x;  
         int y = conf.title_y;  
         do  
         {  
                 t = strchr(s, '\n');  
                 if(t)  
                         *t = '\0';  
                 draw_string(im, s, &conf.title_font, x, y, conf.title_align, &conf.title_color);  
                 y += get_sheight(s, &conf.title_font);  
                 s = t+1;  
         } while(t);  
 }  
   
1030  void draw_connector(gdImagePtr im, branch_t *b)  void draw_connector(gdImagePtr im, branch_t *b)
1031  {  {
1032          revision_t *r = b->branchpoint;          revision_t *r = b->branchpoint;
1033          int x1 = r->x + r->w/2 + 2;          int x1 = r->cx + r->w/2 + 2;
1034          int y1 = r->y + r->h/2;          int y1 = r->y + r->h/2;
1035          int x2 = b->x;          int x2 = b->cx;
1036          int y2 = b->y;          int y2 = b->y;
1037          gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);          gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1038          gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);          gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1039  }  }
1040    
1041  gdImagePtr make_image(rcsfilelog_t *rcs)  void alloc_color(gdImagePtr im, color_t *c)
1042    {
1043            c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1044    }
1045    
1046    gdImagePtr make_image(rcsfile_t *rcs)
1047  {  {
1048          gdImagePtr im;          gdImagePtr im;
1049          int i;          int i;
1050            char *cptr;
1051    
1052          im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);          cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1053          conf.color_bg.id = gdImageColorAllocate(im, conf.color_bg.r, conf.color_bg.g, conf.color_bg.b);          i = get_swidth(cptr, &conf.title_font);
1054          conf.tag_color.id = gdImageColorAllocate(im, conf.tag_color.r, conf.tag_color.g, conf.tag_color.b);          if(rcs->tw+conf.margin_left+conf.margin_right > i)
1055          conf.rev_color.id = gdImageColorAllocate(im, conf.rev_color.r, conf.rev_color.g, conf.rev_color.b);                  i = rcs->tw+conf.margin_left+conf.margin_right;
1056          conf.branch_color.id = gdImageColorAllocate(im, conf.branch_color.r, conf.branch_color.g, conf.branch_color.b);          im = gdImageCreate(i, rcs->th+conf.margin_top+conf.margin_bottom);
1057          conf.branch_bgcolor.id = gdImageColorAllocate(im, conf.branch_bgcolor.r, conf.branch_bgcolor.g, conf.branch_bgcolor.b);          alloc_color(im, &conf.color_bg);
1058          conf.title_color.id = gdImageColorAllocate(im, conf.title_color.r, conf.title_color.g, conf.title_color.b);          alloc_color(im, &conf.tag_color);
1059            alloc_color(im, &conf.rev_color);
1060            alloc_color(im, &conf.rev_bgcolor);
1061            alloc_color(im, &conf.rev_text_color);
1062            alloc_color(im, &conf.branch_color);
1063            alloc_color(im, &conf.branch_bgcolor);
1064            alloc_color(im, &conf.title_color);
1065            alloc_color(im, &black_color);
1066            alloc_color(im, &white_color);
1067    
1068          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
1069                  draw_branch(im, rcs->branches[i]->x, rcs->branches[i]->y, rcs->branches[i]);                  draw_branch(im, rcs->branches[i]->cx, rcs->branches[i]->y, rcs->branches[i]);
1070          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
1071          {          {
1072                  if(rcs->branches[i]->branchpoint)                  if(rcs->branches[i]->branchpoint)
1073                          draw_connector(im, rcs->branches[i]);                          draw_connector(im, rcs->branches[i]);
1074          }          }
1075          draw_title(im, expand_title(rcs));          draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
1076            xfree(cptr);
1077    
1078          return im;          return im;
1079  }  }
1080    
1081    /*
1082     **************************************************************************
1083     * Layout routines
1084     **************************************************************************
1085     */
1086  void move_branch(branch_t *b, int x, int y)  void move_branch(branch_t *b, int x, int y)
1087  {  {
1088          int i;          int i;
1089          b->x += x;          b->cx += x;
1090          b->y += y;          b->y += y;
1091          for(i = 0; i < b->nrevs; i++)          for(i = 0; i < b->nrevs; i++)
1092          {          {
1093                  b->revs[i]->x += x;                  b->revs[i]->cx += x;
1094                  b->revs[i]->y += y;                  b->revs[i]->y += y;
1095          }          }
1096  }  }
1097    
1098    void reposition_branch(revision_t *r, int *x, int *w)
1099    {
1100            int i, j;
1101            for(j = 0; j < r->nbranches; j++)
1102            {
1103                    branch_t *b = r->branches[j];
1104                    *x += *w + conf.rev_minline + b->tw/2 - b->cx;
1105                    *w = b->tw/2;
1106                    move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
1107                    *x = b->cx;
1108                    /* Recurse to move branches of branched revisions */
1109                    for(i = b->nrevs-1; i >= 0; i--)
1110                    {
1111                            reposition_branch(b->revs[i], x, w);
1112                    }
1113            }
1114    }
1115    
1116  void rect_union(int *x, int *y, int *w, int *h, branch_t *b)  void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
1117  {  {
1118          int x1 = *x;          int x1 = *x;
1119          int x2 = x1 + *w;          int x2 = x1 + *w;
1120          int y1 = *y;          int y1 = *y;
1121          int y2 = y1 + *h;          int y2 = y1 + *h;
1122          int xx1 = b->x - b->tw/2;          int xx1 = b->cx - b->tw/2;
1123          int xx2 = xx1 + b->tw;          int xx2 = xx1 + b->tw;
1124          int yy1 = b->y;          int yy1 = b->y;
1125          int yy2 = yy1 + b->th;          int yy2 = yy1 + b->th;
# Line 906  Line 1133 
1133          *h = y2 - y1;          *h = y2 - y1;
1134  }  }
1135    
1136  void make_layout(rcsfilelog_t *rcs)  int branch_intersects(int top, int bottom, int left, branch_t *b)
1137    {
1138            int br = b->cx + b->tw/2;
1139            int bt = b->y - conf.branch_connect - conf.branch_margin/2;
1140            int bb = b->y + b->th + conf.branch_margin/2;
1141            return !(bt > bottom || bb < top || br >= left);
1142    }
1143    
1144    int kern_branch(rcsfile_t *rcs, branch_t *b)
1145    {
1146            int left = b->cx - b->tw/2;
1147            int top = b->y - conf.branch_connect - conf.branch_margin/2;
1148            int bottom = b->y + b->th + conf.branch_margin/2;
1149            int i;
1150            int xpos = 0;
1151    
1152            for(i = 0; i < rcs->nbranches; i++)
1153            {
1154                    branch_t *bp = rcs->branches[i];
1155                    if(bp == b)
1156                            continue;
1157                    if(branch_intersects(top, bottom, left, bp))
1158                    {
1159                            int m = bp->cx + bp->tw/2 + conf.branch_margin;
1160                            if(m > xpos)
1161                                    xpos = m;
1162                    }
1163            }
1164            if(xpos && (b->cx - b->tw/2) - xpos > 0)
1165            {
1166                    move_branch(b, xpos - (b->cx - b->tw/2), 0);
1167                    return 1;
1168            }
1169            return 0;
1170    }
1171    
1172    void make_layout(rcsfile_t *rcs)
1173  {  {
1174          int i, j;          int i, j;
1175          int x, y;          int x, y;
1176          int w, h;          int w, h;
1177          int w2;          int w2;
1178            int moved;
1179    
1180          /* Calculate the box-sizes of the revisions */          /* Calculate the box-sizes of the revisions */
1181          for(i = 0; i < rcs->nrevs; i++)          for(i = 0; i < rcs->nsrev; i++)
1182          {          {
1183                  revision_t *rp;                  revision_t *rp;
1184                  int w;                  int w;
1185                  int h;                  int h;
1186                  rp = rcs->revs[i];                  rp = rcs->srev[i];
1187                  w = get_swidth(rp->rev->rev, &conf.rev_font);                  rp->revtext = expand_string(conf.rev_text, rcs, rp, rp->rev, NULL, rp->ntags ? rp->tags[0] : NULL);
1188                  h = get_sheight(rp->rev->rev, &conf.rev_font);                  w = get_swidth(rp->revtext, &conf.rev_text_font);
1189                    j = get_swidth(rp->rev->rev, &conf.rev_font);
1190                    if(j > w)
1191                            w = j;
1192                    h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);
1193                  for(j = 0; j < rp->ntags; j++)                  for(j = 0; j < rp->ntags; j++)
1194                  {                  {
1195                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
# Line 938  Line 1206 
1206                  branch_t *bp = rcs->branches[i];                  branch_t *bp = rcs->branches[i];
1207                  int w;                  int w;
1208                  int h;                  int h;
1209                  w = get_swidth(bp->branch, &conf.branch_font);                  w = get_swidth(bp->branch->branch, &conf.branch_font);
1210                  h = get_sheight(bp->branch, &conf.branch_font);                  h = get_sheight(bp->branch->branch, &conf.branch_font);
1211                  if(bp->tag)                  for(j = 0; j < bp->ntags; j++)
1212                  {                  {
1213                          int ww = get_swidth(bp->tag->tag, &conf.branch_font);                          int ww = get_swidth(bp->tags[j]->tag, &conf.branch_font);
1214                          if(ww > w) w = ww;                          if(ww > w) w = ww;
1215                          h += get_sheight(bp->tag->tag, &conf.branch_font) + conf.branch_separator;                          h += get_sheight(bp->tags[j]->tag, &conf.branch_font);
1216                  }                  }
1217                  w += conf.branch_lspace + conf.branch_rspace;                  w += conf.branch_lspace + conf.branch_rspace;
1218                  h += conf.branch_tspace + conf.branch_bspace;                  h += conf.branch_tspace + conf.branch_bspace;
# Line 966  Line 1234 
1234                  branch_t *b = rcs->branches[i];                  branch_t *b = rcs->branches[i];
1235                  x = b->tw/2;                  x = b->tw/2;
1236                  y = b->h;                  y = b->h;
1237                  b->x = x;                  b->cx = x;
1238                  b->y = 0;                  b->y = 0;
1239                  for(j = 0; j < b->nrevs; j++)                  for(j = 0; j < b->nrevs; j++)
1240                  {                  {
1241                          y += conf.rev_minline;                          y += conf.rev_minline;
1242                          b->revs[j]->x = x;                          b->revs[j]->cx = x;
1243                          b->revs[j]->y = y;                          b->revs[j]->y = y;
1244                          y += b->revs[j]->h;                          y += b->revs[j]->h;
1245                  }                  }
1246          }          }
1247    
1248          /* Reposition the branches FIXME: Should be recursive on branchpoint revisions within branches... */          /* Reposition the branches */
1249          x = rcs->branches[0]->x;          x = rcs->branches[0]->cx;
1250          w2 = rcs->branches[0]->tw / 2;          w2 = rcs->branches[0]->tw / 2;
1251          for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)          for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
1252          {          {
1253                  revision_t *r = rcs->branches[0]->revs[i];                  reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
1254                  for(j = 0; j < r->nbranches; j++)          }
1255    
1256            /* Try to move branches left if there is room (kerning) */
1257            for(moved = 1; moved; )
1258            {
1259                    moved = 0;
1260                    for(i = 1; i < rcs->nbranches; i++)
1261                  {                  {
1262                          branch_t *b = r->branches[j];                          moved += kern_branch(rcs, rcs->branches[i]);
                         x += w2 + conf.rev_minline + b->tw/2 - b->x;  
                         w2 = b->tw/2;  
                         move_branch(b, x, r->y + r->h);  
                         x = b->x;  
1263                  }                  }
1264          }          }
1265    
1266            /* Move everything w.r.t. the top-left margin */
1267          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
1268                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
1269    
1270          /* Calculate overall image size */          /* Calculate overall image size */
1271          x = rcs->branches[0]->x - rcs->branches[0]->tw/2;          x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
1272          y = rcs->branches[0]->y;          y = rcs->branches[0]->y;
1273          w = rcs->branches[0]->tw;          w = rcs->branches[0]->tw;
1274          h = rcs->branches[0]->th;          h = rcs->branches[0]->th;
# Line 1009  Line 1280 
1280    
1281  /*  /*
1282   **************************************************************************   **************************************************************************
1283     * Imagemap functions
1284     **************************************************************************
1285     */
1286    void make_imagemap(rcsfile_t *rcs, FILE *fp)
1287    {
1288            int i, j;
1289            char *href;
1290            char *alt;
1291            fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
1292            for(i = 0; i < rcs->nbranches; i++)
1293            {
1294                    branch_t *b = rcs->branches[i];
1295                    tag_t *tag = b->ntags ? b->tags[0] : NULL;
1296                    href = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
1297                    alt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
1298                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1299                                    href,
1300                                    b->cx - b->w/2, b->y, b->cx + b->w/2, b->y + b->h,
1301                                    alt);
1302                    xfree(href);
1303                    xfree(alt);
1304                    for(j = 0; j < b->nrevs; j++)
1305                    {
1306                            revision_t *r = b->revs[j];
1307                            revision_t* r1;
1308                            int xoff;
1309                            int x1;
1310                            int x2;
1311                            int y1;
1312    
1313                            tag = r->ntags ? r->tags[0] : NULL;
1314                            href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
1315                            alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
1316                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1317                                    href,
1318                                    r->cx - r->w/2, r->y, r->cx + r->w/2, r->y + r->h,
1319                                    alt);
1320                            xfree(href);
1321                            xfree(alt);
1322                            if ( j > 0 || b->branchpoint )
1323                            {
1324                                if ( j > 0 )
1325                                {
1326                                    r1 = b->revs[j-1];
1327                                    xoff = MIN(r->w, r1->w)/4;
1328                                    y1 = r1->y + r1->h;
1329                                }
1330                                else
1331                                {
1332                                    r1 = b->branchpoint;
1333                                    xoff = MIN(r->w, b->w)/4;
1334                                    y1 = b->y + b->h;
1335                                }
1336                                x1 = r->cx - xoff;
1337                                x2 = r->cx + xoff;
1338    
1339                                href = expand_string(conf.map_diff_href, rcs, r, r->rev, r1->rev, tag);
1340                                alt = expand_string(conf.map_diff_alt, rcs, r, r->rev, r1->rev, tag);
1341                                fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1342                                        href,
1343                                        x1, y1 + 1, x2, r->y - 1,
1344                                        alt);
1345                                xfree(href);
1346                                xfree(alt);
1347                            }
1348                    }
1349            }
1350            fprintf(fp, "</map>\n");
1351    }
1352    
1353    /*
1354     **************************************************************************
1355   * Configuration   * Configuration
1356   **************************************************************************   **************************************************************************
1357   */   */
# Line 1016  Line 1359 
1359  {  {
1360          FILE *fp;          FILE *fp;
1361          int r;          int r;
1362    
1363          if(path)          if(path)
1364          {          {
1365                  if((fp = fopen(path, "r")) == NULL)                  if((fp = fopen(path, "r")) == NULL)
1366                  {                  {
1367                          return 0;                          return 0;
1368                  }                  }
1369                    else
1370                            input_file = path;
1371          }          }
1372          else          else
1373          {          {
# Line 1031  Line 1377 
1377                          {                          {
1378                                  return 0;                                  return 0;
1379                          }                          }
1380                            else
1381                                    input_file = ETCDIR "/" CONFFILENAME;
1382                  }                  }
1383                    else
1384                            input_file = "./" CONFFILENAME;
1385          }          }
1386    
1387          yyin = fp;          yyin = fp;
1388          r = yyparse();          r = yyparse();
1389          fclose(fp);          fclose(fp);
1390            input_file = NULL;
1391          return r == 0;          return r == 0;
1392  }  }
1393    
# Line 1047  Line 1398 
1398   */   */
1399  static const char usage_str[] =  static const char usage_str[] =
1400          "Usage: cvsgraph [options] <file>\n"          "Usage: cvsgraph [options] <file>\n"
1401          "  -c <file>  Read alternative config from <file>\n"          "  -c <file>    Read alternative config from <file>\n"
1402          "  -h         This message\n"          "  -d <level>   Enable debug mode at <level>\n"
1403          "  -m <mod>   Use <mod> as cvs module\n"          "  -h           This message\n"
1404          "  -o <file>  Output to <file>\n"          "  -i           Generate an imagemap instead of image\n"
1405          "  -r <path>  Use <path> as cvsroot path\n"          "  -M <name>    Use <name> as imagemap name\n"
1406          "  -V         Print version and exit\n"          "  -m <mod>     Use <mod> as cvs module\n"
1407            "  -o <file>    Output to <file>\n"
1408            "  -q           Be quiet (i.e. no warnings)\n"
1409            "  -r <path>    Use <path> as cvsroot path\n"
1410            "  -V           Print version and exit\n"
1411            "  -[0-9] <txt> Use <txt> for expansion\n"
1412          ;          ;
1413    
1414  #define VERSION_STR     "1.0.0"  #define VERSION_STR     "1.1.4"
1415  #define NOTICE_STR      "Copyright (c) 2001 B.Stultiens"  #define NOTICE_STR      "Copyright (c) 2001 B.Stultiens"
1416    
1417  void add_tag(rcsfilelog_t *rcs, const char *tag, const char *rev)  void append_slash(char **path)
1418  {  {
1419          rcs->tags = xrealloc(rcs->tags, (rcs->ntags+1)*sizeof(rcs->tags[0]));          int l;
1420          rcs->tags[rcs->ntags] = xmalloc(sizeof(tag_t));          assert(path != NULL);
1421          rcs->tags[rcs->ntags]->tag = strip_dup(tag);          assert(*path != NULL);
1422          rcs->tags[rcs->ntags]->rev = make_revid(rev);          l = strlen(*path);
1423          rcs->ntags++;          if(!l || (*path)[l-1] == '/')
1424                    return;
1425            *path = xrealloc(*path, l+2);
1426            strcat(*path, "/");
1427  }  }
1428    
1429  int main(int argc, char *argv[])  int main(int argc, char *argv[])
1430  {  {
1431            extern int yy_flex_debug;
1432            extern int rcs_flex_debug;
1433            extern int yydebug;
1434            extern int rcsdebug;
1435          int optc;          int optc;
1436          char *confpath = NULL;          char *confpath = NULL;
1437          char *outfile = NULL;          char *outfile = NULL;
1438          char *cvsroot = NULL;          char *cvsroot = NULL;
1439          char *cvsmodule = NULL;          char *cvsmodule = NULL;
1440            int imagemap = 0;
1441            char *imgmapname = NULL;
1442          int lose = 0;          int lose = 0;
1443          FILE *fp;          FILE *fp;
1444          int n;          rcsfile_t *rcs;
         rcsfilelog_t *rcs;  
1445          gdImagePtr im;          gdImagePtr im;
1446    
1447          while((optc = getopt(argc, argv, "c:hm:o:r:V")) != EOF)          while((optc = getopt(argc, argv, "0:1:2:3:4:5:6:7:8:9:c:d:hiM:m:o:qr:V")) != EOF)
1448          {          {
1449                  switch(optc)                  switch(optc)
1450                  {                  {
1451                  case 'c':                  case 'c':
1452                          confpath = xstrdup(optarg);                          confpath = xstrdup(optarg);
1453                          break;                          break;
1454                    case 'd':
1455                            debuglevel = strtol(optarg, NULL, 0);
1456                            break;
1457                    case 'i':
1458                            imagemap = 1;
1459                            break;
1460                    case 'M':
1461                            imgmapname = xstrdup(optarg);
1462                            break;
1463                  case 'm':                  case 'm':
1464                          cvsmodule = xstrdup(optarg);                          cvsmodule = xstrdup(optarg);
1465                          break;                          break;
1466                  case 'o':                  case 'o':
1467                          outfile = xstrdup(optarg);                          outfile = xstrdup(optarg);
1468                          break;                          break;
1469                    case 'q':
1470                            quiet = 1;
1471                            break;
1472                  case 'r':                  case 'r':
1473                          cvsroot = xstrdup(optarg);                          cvsroot = xstrdup(optarg);
1474                          break;                          break;
# Line 1103  Line 1479 
1479                          fprintf(stdout, "%s", usage_str);                          fprintf(stdout, "%s", usage_str);
1480                          return 0;                          return 0;
1481                  default:                  default:
1482                          lose++;                          if(isdigit(optc))
1483                            {
1484                                    conf.expand[optc-'0'] = xstrdup(optarg);
1485                            }
1486                            else
1487                                    lose++;
1488                  }                  }
1489          }          }
1490    
# Line 1119  Line 1500 
1500                  return 1;                  return 1;
1501          }          }
1502    
1503            if(debuglevel)
1504            {
1505                    setvbuf(stdout, NULL, 0, _IONBF);
1506                    setvbuf(stderr, NULL, 0, _IONBF);
1507            }
1508            yy_flex_debug = (debuglevel & DEBUG_CONF_LEX) != 0;
1509            rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
1510            yydebug = (debuglevel & DEBUG_CONF_YACC) != 0;
1511            rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
1512    
1513          /* Set defaults */          /* Set defaults */
1514          if(!conf.tag_font)      conf.tag_font = gdFontTiny;          conf.tag_font           = gdFontTiny;
1515          if(!conf.rev_font)      conf.rev_font = gdFontTiny;          conf.rev_font           = gdFontTiny;
1516          if(!conf.branch_font)   conf.branch_font = gdFontTiny;          conf.branch_font        = gdFontTiny;
1517          if(!conf.title_font)    conf.title_font = gdFontTiny;          conf.title_font         = gdFontTiny;
1518            conf.rev_text_font      = gdFontTiny;
1519    
1520            conf.cvsroot            = xstrdup("");
1521            conf.cvsmodule          = xstrdup("");
1522            conf.date_format        = xstrdup("%d-%b-%Y %H:%M:%S");
1523            conf.title              = xstrdup("");
1524            conf.map_name           = xstrdup("CvsGraphImageMap");
1525            conf.map_branch_href    = xstrdup("href=\"unset: conf.map_branch_href\"");
1526            conf.map_branch_alt     = xstrdup("alt=\"%B\"");
1527            conf.map_rev_href       = xstrdup("href=\"unset: conf.map_rev_href\"");
1528            conf.map_rev_alt        = xstrdup("alt=\"%R\"");
1529            conf.map_diff_href      = xstrdup("href=\"unset: conf.map_diff_href\"");
1530            conf.map_diff_alt       = xstrdup("alt=\"%P &lt;-&gt; %R\"");
1531            conf.rev_text           = xstrdup("%d");
1532    
1533            conf.color_bg           = white_color;
1534            conf.branch_bgcolor     = white_color;
1535            conf.branch_color       = black_color;
1536            conf.rev_color          = black_color;
1537            conf.rev_bgcolor        = white_color;
1538            conf.tag_color          = black_color;
1539            conf.title_color        = black_color;
1540            conf.rev_text_color     = black_color;
1541    
1542            conf.image_quality      = 100;
1543    
1544          if(!read_config(confpath))          if(!read_config(confpath))
1545          {          {
# Line 1132  Line 1548 
1548          }          }
1549    
1550          /* Set overrides */          /* Set overrides */
1551          if(cvsroot)             conf.cvsroot = cvsroot;          if(cvsroot)     conf.cvsroot = cvsroot;
1552          if(cvsmodule)           conf.cvsmodule = cvsmodule;          if(cvsmodule)   conf.cvsmodule = cvsmodule;
1553            if(imgmapname)  conf.map_name = imgmapname;
1554    
1555          if((fp = get_log(conf.cvsroot, conf.cvsmodule, argv[optind])) == NULL)          append_slash(&conf.cvsroot);
1556          {          append_slash(&conf.cvsmodule);
                 fprintf(stderr, "Error getting log for '%s'\n", argv[optind]);  
                 return 1;  
         }  
1557    
1558          rcs = parse_log(fp);          rcs = get_rcsfile(conf.cvsroot, conf.cvsmodule, argv[optind]);
1559          if(!rcs)          if(!rcs)
         {  
                 fprintf(stderr, "Error parsing log\n");  
1560                  return 1;                  return 1;
         }  
         fclose(fp);  
1561    
1562          /* Add implicit tags */          if(debuglevel & DEBUG_RCS_FILE)
1563          add_tag(rcs, "HEAD", rcs->head->rev);                  dump_rcsfile(rcs);
         add_tag(rcs, "MAIN", "1");  
   
         /* We now have the log. Sort and reorganize a little */  
         qsort(rcs->tags, rcs->ntags, sizeof(rcs->tags[0]), tag_sort);  
         qsort(rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_sort);  
1564    
1565          /* Assign tags to revisions */          if(!reorganise_branches(rcs))
1566          for(n = 0; n < rcs->ntags; n++)                  return 1;
1567          {  
1568                  revision_t *r = find_revision(rcs, rcs->tags[n]->rev);          if(!assign_tags(rcs))
1569                  if(!r)                  return 1;
                         continue;  
                 r->tags = xrealloc(r->tags, (r->ntags+1) * sizeof(r->tags[0]));  
                 r->tags[r->ntags] = rcs->tags[n];  
                 r->ntags++;  
         }  
1570    
1571          /* Isolate the branches */          if(outfile)
         for(n = 0; n < rcs->nrevs; n++)  
1572          {          {
1573                  branch_t *b = find_branch(rcs, rcs->revs[n]->rev->branch);                  if((fp = fopen(outfile, "wb")) == NULL)
                 if(!b)  
1574                  {                  {
1575                          rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1) * sizeof(rcs->branches[0]));                          perror(outfile);
1576                          b = xmalloc(sizeof(*b));                          return 1;
                         b->branch = xstrdup(rcs->revs[n]->rev->branch);  
                         b->tag = find_branchtag(rcs, rcs->revs[n]->rev->branch);  
                         rcs->branches[rcs->nbranches] = b;  
                         rcs->nbranches++;  
                         qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_sort);  
1577                  }                  }
                 b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));  
                 b->revs[b->nrevs] = rcs->revs[n];  
                 b->nrevs++;  
1578          }          }
1579            else
         /* Find the branchpoints */  
         for(n = 0; n < rcs->nbranches; n++)  
1580          {          {
1581                  char *prev = xstrdup(rcs->branches[n]->branch);                  fp = stdout;
1582                  char *cptr = strrchr(prev, '.');  #ifdef __WIN32__
1583                  revision_t *r;                  /* Bad hack for DOS/Windows */
1584                  revid_t rid;                  if(setmode(fileno(fp), O_BINARY) == -1)
1585                  if(!cptr)       /* Main branch number */                  {
1586                          continue;                          perror("Set binary mode for stdout");
1587                  *cptr = '\0';                          return 1;
1588                  rid.isbranch = 0;                  }
1589                  rid.branch = "";  #endif
                 rid.rev = prev;  
                 r = find_revision(rcs, &rid);  
                 if(!r)  
                 {  
                         /* Hm, disjoint branch... */  
                         fprintf(stderr, "Hm, don't know how to handle disjoint branches (%s)\n", prev);  
                         assert(r != NULL);  
                 }  
                 rcs->branches[n]->branchpoint = r;  
                 r->branches = xrealloc(r->branches, (r->nbranches+1)*sizeof(r->branches[0]));  
                 r->branches[r->nbranches] = rcs->branches[n];  
                 r->nbranches++;  
1590          }          }
1591    
1592          make_layout(rcs);          make_layout(rcs);
1593    
1594  #ifdef DEBUG          if(!imagemap)
         dump_log(rcs);  
 #endif  
         im = make_image(rcs);  
         if(outfile)  
1595          {          {
1596                  if((fp = fopen(outfile, "w")) == NULL)                  /* Create an image */
1597                    im = make_image(rcs);
1598    
1599                    switch(conf.image_type)
1600                  {                  {
1601                          perror(outfile);  #ifdef HAVE_IMAGE_GIF
1602                          return 1;  # ifndef HAVE_IMAGE_PNG
1603                    default:
1604    # endif
1605                    case IMAGE_GIF:
1606                            gdImageGif(im, fp);
1607                            break;
1608    #endif
1609    #ifdef HAVE_IMAGE_PNG
1610                    default:
1611                    case IMAGE_PNG:
1612                            gdImagePng(im, fp);
1613                            break;
1614    #endif
1615    #ifdef HAVE_IMAGE_JPEG
1616    # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
1617                    default:
1618    # endif
1619                    case IMAGE_JPEG:
1620                            gdImageJpeg(im, fp, conf.image_quality);
1621                            break;
1622    #endif
1623                  }                  }
1624    
1625                    gdImageDestroy(im);
1626          }          }
1627          else          else
1628                  fp = stdout;          {
1629          gdImageGif(im, fp);                  /* Create an imagemap */
1630                    make_imagemap(rcs, fp);
1631            }
1632    
1633          if(outfile)          if(outfile)
1634                  fclose(fp);                  fclose(fp);
1635          gdImageDestroy(im);  
1636          return 0;          return 0;
1637  }  }
1638    

Legend:
Removed from v.1.2  
changed lines
  Added in v.1.15

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0