/[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.4, Fri Feb 23 00:12:42 2001 UTC revision 1.21, Mon Feb 18 01:31:34 2002 UTC
# Line 2  Line 2 
2   * CvsGraph graphical representation generator of brances and revisions   * CvsGraph graphical representation generator of brances and revisions
3   * of a file in cvs/rcs.   * of a file in cvs/rcs.
4   *   *
5   * Copyright (C) 2001  B. Stultiens   * Copyright (C) 2001,2002  B. Stultiens
6   *   *
7   * This program is free software; you can redistribute it and/or modify   * This program is free software; you can redistribute it and/or modify
8   * it under the terms of the GNU General Public License as published by   * it under the terms of the GNU General Public License as published by
# 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    #include <limits.h>
38    
39    #ifdef HAVE_GETOPT_H
40    # include <getopt.h>
41    #endif
42    
43  #include <gd.h>  #include <gd.h>
44  #include <gdfontt.h>  #include <gdfontt.h>
45    
46  #include "cvsgraph.h"  #include "cvsgraph.h"
47  #include "utils.h"  #include "utils.h"
48  #include "readconf.h"  #include "readconf.h"
49    #include "rcs.h"
50    
51  /*#define DEBUG         1*/  #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
52    # error No image output format available. Check libgd
53    #endif
54    
 #define RLOGCMD         "/usr/bin/rlog"  
 #define DEVNULL         "/dev/null"  
 #define CONFFILENAME    "cvsgraph.conf"  
55    
56  #ifndef ETCDIR  /*#define DEBUG         1*/
57  # define ETCDIR         "/usr/local/etc"  /*#define NOGDFILL      1*/
58  #endif  
59    #define LOOPSAFEGUARD   100     /* Max itterations in possible infinite loops */
60    
61  #ifndef MAX  #ifndef MAX
62  # define MAX(a,b)       ((a) > (b) ? (a) : (b))  # define MAX(a,b)       ((a) > (b) ? (a) : (b))
# Line 97  Line 69 
69  #define ALIGN_HL        0x00  #define ALIGN_HL        0x00
70  #define ALIGN_HC        0x01  #define ALIGN_HC        0x01
71  #define ALIGN_HR        0x02  #define ALIGN_HR        0x02
72  #define ALIGN_HX        0x0f  #define ALIGN_HX        0x0f
73  #define ALIGN_VT        0x00  #define ALIGN_VT        0x00
74  #define ALIGN_VC        0x10  #define ALIGN_VC        0x10
75  #define ALIGN_VB        0x20  #define ALIGN_VB        0x20
76  #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;  
77    
78  /*  /*
79   **************************************************************************   **************************************************************************
# Line 169  Line 81 
81   **************************************************************************   **************************************************************************
82   */   */
83    
 char *rlogcmd = RLOGCMD;  
 char *devnull = DEVNULL;  
   
84  config_t conf;  config_t conf;
85    int debuglevel;
86    color_t white_color = {255, 255, 255, 0};
87    color_t black_color = {0, 0, 0, 0};
88    
89    
90  /*  /*
91   **************************************************************************   **************************************************************************
92   * Debug routines   * Debug routines
93   **************************************************************************   **************************************************************************
94   */   */
95  #ifdef DEBUG  static void dump_rev(char *p, rev_t *r)
 void dump_revid(const char *s, revid_t *r)  
96  {  {
97          fprintf(stderr, "%s.branch  : '%s'\n", s, r->branch);          printf("%s", p);
98          fprintf(stderr, "%s.rev     : '%s'\n", s, r->rev);          if(r)
99          fprintf(stderr, "%s.isbranch: %d\n", s, r->isbranch);                  printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
100            else
101                    printf("<null>\n");
102  }  }
103    
104  void dump_tag(const char *s, tag_t *t)  static void dump_id(char *p, char *d)
105  {  {
106          fprintf(stderr, "%s", s);          printf("%s", p);
107          dump_revid(t->tag, t->rev);          if(d)
108                    printf("'%s'\n", d);
109            else
110                    printf("<null>\n");
111  }  }
112    
113  void dump_rev(revision_t *r)  static void dump_idrev(char *p, idrev_t *t)
114  {  {
115          int i;          printf("%s", p);
116          dump_revid("Revision", r->rev);          if(t)
117          fprintf(stderr, "Revision.Info   : '%s'\n", r->info);          {
118          fprintf(stderr, "Revision.Comment: '%s'\n", r->comment);                  printf("'%s' -> ", t->id);
119          for(i = 0; i < r->ntags; i++)                  dump_rev("", t->rev);
120                  dump_tag("Revision.Tag: ", r->tags[i]);          }
121            else
122                    printf("<null>\n");
123  }  }
124    
125  void dump_branch(branch_t *b)  static void dump_tag(char *p, tag_t *t)
126    {
127            printf("%s", p);
128            if(t)
129            {
130                    printf("'%s' -> ", t->tag);
131                    dump_rev("", t->rev);
132            }
133            else
134                    printf("<null>\n");
135    }
136    
137    static void dump_delta(char *p, delta_t *d)
138  {  {
139          int i;          int i;
140          fprintf(stderr, "Branch: '%s'\n", b->branch);          printf("%sdelta.rev   : ", p);
141          if(b->tag)          dump_rev("", d->rev);
142                  dump_tag("branchtag:", b->tag);          printf("%sdelta.date  : %s\n", p, d->date);
143          for(i = 0; i < b->nrevs; i++)          printf("%sdelta.author: %s\n", p, d->author);
144                  fprintf(stderr, "Branch.Rev: '%s'\n", b->revs[i]->rev->rev);          printf("%sdelta.state : %s\n", p, d->state);
145            for(i = 0; d->branches && i < d->branches->nrevs; i++)
146            {
147                    printf("%sdelta.branch: ", p);
148                    dump_rev("", d->branches->revs[i]);
149            }
150            printf("%sdelta.next  : ", p);
151            dump_rev("", d->next);
152            printf("\n");
153    }
154    
155    static void dump_dtext(char *p, dtext_t *d)
156    {
157            printf("%sdtext.rev  : ", p);
158            dump_rev("", d->rev);
159            printf("%sdtext.log  : %d bytes\n", p, d->log ? strlen(d->log) : -1);
160            printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
161            printf("\n");
162  }  }
163    
164  void dump_log(rcsfilelog_t *r)  static void dump_rcsfile(rcsfile_t *rcs)
165  {  {
166          int i;          int i;
167            printf("root   : '%s'\n", rcs->root);
168            printf("module : '%s'\n", rcs->module);
169            printf("file   : '%s'\n", rcs->file);
170            dump_rev("head   : ", rcs->head);
171            dump_rev("branch : ", rcs->branch);
172            printf("access :\n");
173            for(i = 0; rcs->access && i < rcs->access->nids; i++)
174                    dump_id("\t", rcs->access->ids[i]);
175            printf("tags   :\n");
176            for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
177                    dump_tag("\t", rcs->tags->tags[i]);
178            printf("locks  :%s\n", rcs->strict ? " (strict)" : "");
179            for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
180                    dump_idrev("\t", rcs->locks->idrevs[i]);
181            printf("comment: '%s'\n", rcs->comment);
182            printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
183            printf("deltas :\n");
184            for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
185                    dump_delta("\t", rcs->deltas->deltas[i]);
186            printf("desc   : '%s'\n", rcs->desc);
187            printf("dtexts :\n");
188            for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
189                    dump_dtext("\t", rcs->dtexts->dtexts[i]);
190    
191          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]);  
192  }  }
 #endif  
193    
194  /*  /*
195   **************************************************************************   **************************************************************************
196   * Retrieve the log entries   * Read the rcs file
197   **************************************************************************   **************************************************************************
198   */   */
199  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)
200  {  {
         pid_t pid;  
         int nul;  
         FILE *tmp;  
201          char *cmd = NULL;          char *cmd = NULL;
202          int status;          int rv;
         mode_t um;  
203    
204          if((nul = open(devnull, O_RDWR, S_IRUSR|S_IWUSR)) == -1)          if(file)
                 return NULL;  
   
         um = umask(0177);       /* Set tempfiles to max 0600 permissions */  
         if((tmp = tmpfile()) == NULL)  
205          {          {
206                  close(nul);                  cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
207                  return NULL;                  sprintf(cmd, "%s%s%s", cvsroot, module, file);
208                    if(!(rcsin = fopen(cmd, "rb")))
209                    {
210                            perror(cmd);
211                            return NULL;
212                    }
213                    input_file = cmd;
214          }          }
215          umask(um);          else
   
         cmd = xmalloc(strlen(cvsroot) + + strlen(module) + strlen(file) + 2 + 1);  
         sprintf(cmd, "%s/%s/%s", cvsroot, module, file);  
   
         switch(pid = fork())  
216          {          {
217          case -1:        /* Error */                  rcsin = stdin;
218                  close(nul);                  input_file = "<stdin>";
219                  fclose(tmp);          }
220                  xfree(cmd);          line_number = 1;
221                  return NULL;          rv = rcsparse();
222          case 0:         /* Child */          if(file)
223                  if((dup2(nul, STDIN_FILENO)) == -1)     exit(126);          {
224                  if((dup2(fileno(tmp), STDOUT_FILENO)) == -1)    exit(126);                  fclose(rcsin);
                 if((dup2(nul, STDERR_FILENO)) == -1)    exit(126);  
                 close(nul);  
                 fclose(tmp);  
                 execl(rlogcmd, rlogcmd, cmd, NULL);  
                 exit(127);  
                 break;  
         default:        /* Parent */  
                 close(nul);  
225                  xfree(cmd);                  xfree(cmd);
                 while(1)  
                 {  
                         if(waitpid(pid, &status, 0) == -1)  
                         {  
                                 if(errno != EINTR)  
                                 {  
                                         fclose(tmp);  
                                         return NULL;  
                                 }  
                         }  
                         else  
                                 break;  
                 }  
                 break;  
226          }          }
227            if(rv)
228          if(WIFEXITED(status) && WEXITSTATUS(status) == 0)                  return NULL;
229            input_file = NULL;
230            if(file)
231          {          {
232                  if(fseek(tmp, 0, SEEK_SET) != (off_t)-1)                  rcsfile->root = xstrdup(cvsroot);
233                  {                  rcsfile->module = xstrdup(module);
234                          return tmp;                  rcsfile->file = xstrdup(file);
                 }  
                 else  
                 {  
                         fclose(tmp);  
                         return NULL;  
                 }  
235          }          }
236          else          else
237                  fclose(tmp);          {
238          return NULL;                  rcsfile->root = xstrdup("");
239                    rcsfile->module = xstrdup("");
240                    rcsfile->file = xstrdup("<stdin>");
241            }
242            return rcsfile;
243  }  }
244    
245  /*  /*
246   **************************************************************************   **************************************************************************
247   * Parse the log entries   * Sort and find helpers
248   **************************************************************************   **************************************************************************
249   */   */
250  char *strip_dup(const char *s)  int count_dots(const char *s)
251  {  {
252          int l = strlen(s);          int i;
253          char *c = xmalloc(l+1);          for(i = 0; *s; s++)
   
         strcpy(c, s);  
         while(*c == ' ' || *c == '\t')  
254          {          {
255                  memmove(c, c+1, l--);                  if(*s == '.')
256                            i++;
257          }          }
258          while(l && strchr(" \t\r\n", c[l]))          return i;
                 c[l--] = '\0';  
         return c;  
259  }  }
260    
261  revid_t *make_revid(const char *s)  int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
262  {  {
263          char *c = strip_dup(s);          int d1, d2;
264          char *cptr;          char *c1, *c2;
265          int dots = 0;          char *v1, *v2;
266          revid_t *r = xmalloc(sizeof(*r));          char *s1, *s2;
267          for(cptr = c; *cptr; cptr++)          int retval = 0;
268            assert(r1 != NULL);
269            assert(r2 != NULL);
270            if(bcmp)
271          {          {
272                  if(*cptr == '.')                  assert(r1->branch != NULL);
273                          dots++;                  assert(r2->branch != NULL);
274                    c1 = r1->branch;
275                    c2 = r2->branch;
276          }          }
277          if(!dots)          else
278          {          {
279                  r->rev = xstrdup("");                  assert(r1->rev != NULL);
280                  r->branch = xstrdup(s);                  assert(r2->rev != NULL);
281                  r->isbranch = 1;                  c1 = r1->rev;
282                    c2 = r2->rev;
283          }          }
284          else if(!*c)  
285            d1 = count_dots(c1);
286            d2 = count_dots(c2);
287            if(d1 != d2)
288          {          {
289                  r->rev = xstrdup("?.?");                  return d1 - d2;
                 r->branch = xstrdup("?");  
290          }          }
291          else if(dots & 1)  
292            s1 = v1 = xstrdup(c1);
293            s2 = v2 = xstrdup(c2);
294            while(1)
295          {          {
296                  char *t;                  char *vc1 = strchr(s1, '.');
297                  r->rev = c;                  char *vc2 = strchr(s2, '.');
298                  r->branch = xstrdup(c);                  if(vc1 && vc2)
299                  cptr = strrchr(r->branch, '.');                          *vc1 = *vc2 = '\0';
300                  assert(cptr != NULL);                  if(*s1 && *s2)
                 *cptr = '\0';  
                 t = strrchr(r->branch, '.');  
                 if((t&& !strcmp(t+1, "0")) || (!t && !strcmp(r->branch, "0")))  
301                  {                  {
302                          /* Magic branch numbers "x.x.0.x" */                          d1 = atoi(s1);
303                          r->isbranch = 1;                          d2 = atoi(s2);
304                          if(t)                          if(d1 != d2)
305                                  strcpy(t+1, cptr+1);                          {
306                          else                                  retval = d1 - d2;
307                                  strcpy(r->branch, cptr+1);                                  break;
308                            }
309                  }                  }
310                    if(!vc1 || !vc2)
311                            break;
312                    s1 = vc1 + 1;
313                    s2 = vc2 + 1;
314          }          }
315          else          xfree(v1);
316          {          xfree(v2);
317                  r->isbranch = 1;          return retval;
318                  r->branch = c;  }
319                  r->rev = xmalloc(strlen(c) + 3);  
320                  strcpy(r->rev, c);  /*
321                  strcat(r->rev, ".?");   **************************************************************************
322          }   * Reorganise the rcsfile for the branches
323     *
324     * Basically, we have a list of deltas (i.e. administrative info on
325     * revisions) and a list of delta text (the actual logs and diffs).
326     * The deltas are linked through the 'next' and the 'branches' fields
327     * which describe the tree-structure of revisions.
328     * The reorganisation means that we put each delta and corresponding
329     * delta text in a revision structure and assign it to a specific
330     * branch. This is required because we want to be able to calculate
331     * the bounding boxes of each branch. The revisions expand vertically
332     * and the branches expand horizontally.
333     * The reorganisation is performed in these steps:
334     * 1 - sort deltas and delta text on revision number for quick lookup
335     * 2 - start at the denoted head revision:
336     *      * create a branch structure and add this revision
337     *      * for each 'branches' in the delta do:
338     *              - walk all 'branches' of the delta and recursively goto 2
339     *                with the denoted branch delta as new head
340     *              - backlink the newly create sub-branch to the head revision
341     *                so that we can draw them recursively
342     *      * set head to the 'next' field and goto 2 until no next is
343     *        available
344     * 3 - update the administration
345     **************************************************************************
346     */
347    static int sort_delta(const void *d1, const void *d2)
348    {
349            return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
350    }
351    
352    static int search_delta(const void *r, const void *d)
353    {
354            return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
355    }
356    
357    static delta_t *find_delta(delta_t **dl, int n, rev_t *r)
358    {
359            delta_t **d;
360            d = bsearch(r, dl, n, sizeof(*dl), search_delta);
361            if(!d)
362                    return NULL;
363            return *d;
364    }
365    
366    static int sort_dtext(const void *d1, const void *d2)
367    {
368            return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
369    }
370    
371    static int search_dtext(const void *r, const void *d)
372    {
373            return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
374    }
375    
376    static dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
377    {
378            dtext_t **d;
379            d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
380            if(!d)
381                    return NULL;
382            return *d;
383    }
384    
385    static rev_t *dup_rev(const rev_t *r)
386    {
387            rev_t *t = xmalloc(sizeof(*t));
388            t->rev = xstrdup(r->rev);
389            t->branch = xstrdup(r->branch);
390            t->isbranch = r->isbranch;
391            return t;
392    }
393    
394    static branch_t *new_branch(delta_t *d, dtext_t *t)
395    {
396            branch_t *b = xmalloc(sizeof(*b));
397            revision_t *r = xmalloc(sizeof(*r));
398            r->delta = d;
399            r->dtext = t;
400            r->rev = d->rev;
401            r->branch = b;
402            b->branch = dup_rev(d->rev);
403            b->branch->isbranch = 1;
404            b->nrevs = 1;
405            b->revs = xmalloc(sizeof(b->revs[0]));
406            b->revs[0] = r;
407            return b;
408    }
409    
410    static revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
411    {
412            revision_t *r = xmalloc(sizeof(*r));
413            r->delta = d;
414            r->dtext = t;
415            r->rev = d->rev;
416            r->branch = b;
417            b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
418            b->revs[b->nrevs] = r;
419            b->nrevs++;
420          return r;          return r;
421  }  }
422    
423  char *add_comment(char *c, const char *n)  void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
424  {  {
425          int l;          branch_t *b;
426          char *r;          dtext_t *text;
427          assert(n != NULL);          revision_t *currev;
428          l = strlen(n);  
429          if(!c)          assert(head != NULL);
430    
431            if(head->flag)
432          {          {
433                  r = xmalloc(l+1);                  fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
434                  strcpy(r, n);                  return;
435          }          }
436          else          head->flag++;
437            text = find_dtext(sdt, nsdt, head->rev);
438    
439            /* Create a new branch for this head */
440            b = new_branch(head, text);
441            *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
442            (*bl)[*nbl] = b;
443            (*nbl)++;
444            currev = b->revs[0];
445            while(1)
446          {          {
447                  r = xmalloc(l+strlen(c)+1+1);                  /* Process all sub-branches */
448                  strcpy(r, c);                  if(head->branches)
449                  strcat(r, "\n");                  {
450                  strcat(r, n);                          int i;
451                            for(i = 0; i < head->branches->nrevs; i++)
452                            {
453                                    delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
454                                    int btag = *nbl;
455                                    if(!d)
456                                            continue;
457                                    build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
458    
459                                    /* Set the new branch's origin */
460                                    (*bl)[btag]->branchpoint = currev;
461    
462                                    /* Add branch to this revision */
463                                    currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
464                                    currev->branches[currev->nbranches] = (*bl)[btag];
465                                    currev->nbranches++;
466                            }
467                    }
468    
469                    /* Walk through the next list */
470                    if(!head->next)
471                            return;
472    
473                    head = find_delta(sdl, nsdl, head->next);
474                    if(!head)
475                    {
476                            fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);
477                            return;
478                    }
479                    if(head->flag)
480                    {
481                            fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);
482                            return;
483                    }
484                    head->flag++;
485                    text = find_dtext(sdt, nsdt, head->rev);
486                    currev = add_to_branch(b, head, text);
487          }          }
         return r;  
488  }  }
489    
490  int get_line(FILE *fp, char *buf, int maxlen)  int reorganise_branches(rcsfile_t *rcs)
491  {  {
492          int n;          delta_t **sdelta;
493          int seennl;          int nsdelta;
494  retry:          dtext_t **sdtext;
495          seennl = 0;          int nsdtext;
496          if(!fgets(buf, maxlen, fp))          delta_t *head;
497                  return feof(fp) ? 0 : -1;          branch_t **bl;
498          n = strlen(buf);          int nbl;
499          while(n && buf[n-1] == '\n')          int i;
500    
501            assert(rcs->deltas != NULL);
502            assert(rcs->head != NULL);
503    
504            /* Make a new list for quick lookup */
505            nsdelta = rcs->deltas->ndeltas;
506            sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
507            memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
508            qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
509    
510            /* Do the same for the delta text */
511            nsdtext = rcs->dtexts->ndtexts;
512            sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
513            memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
514            qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
515    
516            /* Start from the head of the trunk */
517            head = find_delta(sdelta, nsdelta, rcs->head);
518            if(!head)
519          {          {
520                  seennl = 1;                  fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
521                  buf[--n] = '\0';                  return 0;
522          }          }
523          if(!n)          bl = NULL;
524                  goto retry;          nbl = 0;
525          if(!seennl)          build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
526    
527            /* Reverse the head branch */
528            for(i = 0; i < bl[0]->nrevs/2; i++)
529          {          {
530                  while(fgetc(fp) != '\n')                  revision_t *r;
531                          ;                  r = bl[0]->revs[i];
532          }                  bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
533          return n;                  bl[0]->revs[bl[0]->nrevs-i-1] = r;
534            }
535    
536            /* Update the branch-number of the head because it was reversed */
537            xfree(bl[0]->branch->branch);
538            bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
539    
540            /* Keep the admin */
541            rcs->branches = bl;
542            rcs->nbranches = nbl;
543            rcs->sdelta = sdelta;
544            rcs->nsdelta = nsdelta;
545            rcs->sdtext = sdtext;
546            rcs->nsdtext = nsdtext;
547            rcs->active = bl[0];
548            return 1;
549  }  }
550    
551  rcsfilelog_t *parse_log(FILE *fp)  /*
552     **************************************************************************
553     * Assign the symbolic tags to the revisions and branches
554     *
555     * The tags point to revision numbers somewhere in the tree structure
556     * of branches and revisions. First we make a sorted list of all
557     * revisions and then we assign each tag to the proper revision.
558     **************************************************************************
559     */
560    static int sort_revision(const void *r1, const void *r2)
561  {  {
562          rcsfilelog_t *p;          return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
563          int state = 0;  }
         regex_t rerev;  
         regex_t reinfo;  
564    
565          if(regcomp(&rerev, "^revision[ \\t]*[0-9]+(\\.[0-9]+)*", REG_EXTENDED))  static int search_revision(const void *t, const void *r)
566                  return NULL;  {
567          if(regcomp(&reinfo, "^date:[^;]*;[ \\t]*author:[^;]*;[ \\t]+state:", REG_EXTENDED))          return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
568    }
569    
570    static int sort_branch(const void *b1, const void *b2)
571    {
572            return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
573    }
574    
575    static int search_branch(const void *t, const void *b)
576    {
577            return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
578    }
579    
580    static char *previous_rev(const char *c)
581    {
582            int dots = count_dots(c);
583            char *cptr;
584            char *r;
585            if(!dots)
586          {          {
587                  regfree(&rerev);                  fprintf(stderr, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
588                  return NULL;                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */
589          }          }
590          p = xmalloc(sizeof(*p));          if(dots & 1)
         while(state != 4)  
591          {          {
592                  char buf[256];                  /* Is is a revision we want the parent of */
593                  int n;                  r = xstrdup(c);
594                  n = get_line(fp, buf, sizeof(buf));                  cptr = strrchr(r, '.');
595                  if(!n)                  assert(cptr != NULL);
596                          break;                  if(dots == 1)
597                  if(n == -1)                  {
598                  {                          fprintf(stderr, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
599                          perror("tempfile read");                          /* FIXME: What is the parent of 1.1? */
600                          xfree(p);                          cptr[1] = '\0';
601                          regfree(&rerev);                          strcat(r, "0");
602                          regfree(&reinfo);                          return r;
                         return NULL;  
603                  }                  }
604                  switch(state)                  /* Here we have a "x.x[.x.x]+" case */
605                    *cptr = '\0';
606                    cptr = strrchr(r, '.');
607                    assert(cptr != NULL);
608                    *cptr = '\0';
609                    return r;
610            }
611            /* It is a branch we want the parent of */
612            r = xstrdup(c);
613            cptr = strrchr(r, '.');
614            assert(cptr != NULL);
615            *cptr = '\0';
616            return r;
617    }
618    
619    int assign_tags(rcsfile_t *rcs)
620    {
621            int i;
622            int nr;
623    
624            for(i = nr = 0; i < rcs->nbranches; i++)
625                    nr += rcs->branches[i]->nrevs;
626    
627            rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
628            rcs->nsrev = nr;
629            for(i = nr = 0; i < rcs->nbranches; i++)
630            {
631                    memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
632                    nr += rcs->branches[i]->nrevs;
633            }
634    
635            qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
636            qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
637    
638            if(!rcs->branch)
639            {
640                    /* The main trunk is the active trunk */
641                    rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
642                    rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
643                    rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
644                    rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
645                    rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
646                    rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
647                    rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
648                    rcs->tags->ntags++;
649            }
650    
651            /* We should have at least two tags (HEAD and MAIN) */
652            assert(rcs->tags != 0);
653    
654            for(i = 0; i < rcs->tags->ntags; i++)
655            {
656                    tag_t *t = rcs->tags->tags[i];
657                    if(t->rev->isbranch)
658                  {                  {
659                  case 0: /* Prologue */                          branch_t **b;
660  more_prologue:  add_btag:
661                          if(!strncmp(buf, "RCS file:", 9))                          b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
662                          {                          if(!b)
663                                  p->path = strip_dup(buf+9);                          {
664                          }                                  rev_t rev;
665                          else if(!strncmp(buf, "Working file:", 13))                                  revision_t **r;
666                          {                                  /* This happens for the magic branch numbers if there are
667                                  p->name = strip_dup(buf+13);                                   * no commits within the new branch yet. So, we add the
668                          }                                   * branch and try to continue.
669                          else if(!strncmp(buf, "head:", 5))                                   */
670                          {                                  rev.rev = previous_rev(t->rev->branch);
671                                  p->head = make_revid(buf+5);                                  rev.branch = NULL;
672                          }                                  rev.isbranch = 0;
673                          else if(!strncmp(buf, "branch:", 7))                                  r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
674                          {                                  xfree(rev.rev);
675                                  p->branch = strip_dup(buf+7);                                  if(!r)
                         }  
                         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)  
676                                  {                                  {
677                                          state = 2;                                          if(!quiet)
678                                          goto more_prologue;                                                  fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);
679                                    }
680                                    else
681                                    {
682                                            rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
683                                            rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
684                                            rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
685                                            rcs->branches[rcs->nbranches]->branchpoint = *r;
686                                            (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
687                                            (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
688                                            (*r)->nbranches++;
689                                            rcs->nbranches++;
690                                            /* Resort the branches */
691                                            qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
692                                            goto add_btag;
693                                  }                                  }
                                 *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;  
694                          }                          }
                         if(!p->nrevs)  
                                 p->comment = add_comment(p->comment, buf);  
695                          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))  
696                          {                          {
697                                  revision_t *r = xmalloc(sizeof(*r));                                  branch_t *bb = *b;
698                                  p->revs = xrealloc(p->revs, (p->nrevs+1) * sizeof(p->revs[0]));                                  bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
699                                  p->revs[p->nrevs] = r;                                  bb->tags[bb->ntags] = t;
700                                  p->nrevs++;                                  bb->ntags++;
                                 r->rev = make_revid(buf+8);  
701                          }                          }
702                          else if(!regexec(&reinfo, buf, 0, NULL, 0))                  }
703                    else
704                    {
705                            revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
706                            if(!r)
707                          {                          {
708                                  assert(p->nrevs > 0);                                  if(!quiet)
709                                  p->revs[p->nrevs-1]->info = strip_dup(buf);                                          fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
710                          }                          }
711                          else                          else
712                          {                          {
713                                  /* No match means the description/comment */                                  revision_t *rr = *r;
714                                  state = 2;                                  rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
715                                  goto add_description;                                  rr->tags[rr->ntags] = t;
716                                    rr->ntags++;
717                          }                          }
                         break;  
                 default:  
                         fprintf(stderr, "Illegal state (%d) in parser\n", state);  
                         xfree(p);  
                         regfree(&rerev);  
                         regfree(&reinfo);  
                         return NULL;  
718                  }                  }
719          }          }
720          regfree(&rerev);  
721          regfree(&reinfo);          /* We need to reset the first in the list of branches to the
722          return p;           * active branch to ensure the drawing of all
723             */
724            if(rcs->active != rcs->branches[0])
725            {
726                    branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
727                    branch_t *t;
728                    assert(b != NULL);
729                    t = *b;
730                    *b = rcs->branches[0];
731                    rcs->branches[0] = t;
732            }
733            return 1;
734  }  }
735    
736  /*  /*
737   **************************************************************************   **************************************************************************
738   * Sort and find helpers   * String expansion
739   **************************************************************************   **************************************************************************
740   */   */
741  int tag_sort(const void *t1, const void *t2)  static char *_string;
742  {  static int _nstring;
743  #define TAGPTR(t)       (*((tag_t **)t))  static int _nastring;
         return strcmp(TAGPTR(t1)->rev->rev, TAGPTR(t2)->rev->rev);  
 #undef TAGPTR  
 }  
744    
745  int rev_sort(const void *v1, const void *v2)  static void add_string_str(const char *s)
746  {  {
747  #define REVPTR(t)       (*((revision_t **)t))          int l = strlen(s) + 1;
748          /* FIXME: This can lead to a segfault when no '.' is found */          if(_nstring + l > _nastring)
749          return atoi(strrchr(REVPTR(v1)->rev->rev, '.') + 1) -          {
750                  atoi(strrchr(REVPTR(v2)->rev->rev, '.') + 1);                  _nastring += 128;
751  #undef REVPTR                  _string = xrealloc(_string, _nastring * sizeof(_string[0]));
752            }
753            memcpy(_string+_nstring, s, l);
754            _nstring += l-1;
755  }  }
756    
757  int branch_sort(const void *b1, const void *b2)  static void add_string_ch(int ch)
758  {  {
759  #define BPTR(t) (*((branch_t **)t))          char buf[2];
760          return strcmp(BPTR(b1)->branch, BPTR(b2)->branch);          buf[0] = ch;
761  #undef BPTR          buf[1] = '\0';
762            add_string_str(buf);
763  }  }
764    
765  int rev_cmp(const void *id, const void *v)  static void add_string_date(const char *d)
766  {  {
767  #define REVPTR(t)       (*((revision_t **)t))          struct tm tm, *tmp;
768          return strcmp(((revid_t *)id)->rev, REVPTR(v)->rev->rev);          int n;
769  #undef REVPTR          time_t t;
770            char *buf;
771            int nbuf;
772    
773            memset(&tm, 0, sizeof(tm));
774            n = sscanf(d, "%d.%d.%d.%d.%d.%d",
775                            &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
776                            &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
777            tm.tm_mon--;
778            if(tm.tm_year > 1900)
779                    tm.tm_year -= 1900;
780            t = mktime(&tm);
781            if(n != 6 || t == (time_t)(-1))
782            {
783                    add_string_str("<invalid date>");
784                    return;
785            }
786    
787            tmp = localtime(&t);
788            nbuf = strlen(conf.date_format) * 16;   /* Should be enough to hold all types of expansions */
789            buf = xmalloc(nbuf);
790            strftime(buf, nbuf, conf.date_format, tmp);
791            add_string_str(buf);
792            xfree(buf);
793  }  }
794    
795  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)
796  {  {
797          revision_t **r;          char nb[32];
798          if(id->isbranch)          char nr[32];
799                  return NULL;          char *base;
         r = bsearch(id, rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_cmp);  
         if(!r)  
                 return NULL;  
         else  
                 return *r;  
 }  
800    
801  int branch_cmp(const void *s, const void *b)          if(!s)
802  {                  return xstrdup("");
         return strcmp((const char *)s, (*((branch_t **)b))->branch);  
 }  
803    
804  branch_t *find_branch(rcsfilelog_t *rcs, const char *id)          _nstring = 0;
805  {          if(_string)
806          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;  
 }  
807    
808  tag_t *find_branchtag(rcsfilelog_t * rcs, const char *id)          sprintf(nb, "%d", rcs->nbranches);
809  {          sprintf(nr, "%d", rcs->nsrev);
810          int i;          for(; *s; s++)
         for(i = 0; i < rcs->ntags; i++)  
811          {          {
812                  if(!rcs->tags[i]->rev->isbranch)                  if(*s == '%')
813                          continue;                  {
814                  if(!strcmp(id, rcs->tags[i]->rev->branch))                          switch(*++s)
815                          return rcs->tags[i];                          {
816                            case 'c':
817                            case 'C':
818                                    add_string_str(conf.cvsroot);
819                                    if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
820                                    {
821                                            /* Strip the trailing '/' */
822                                            _nstring--;
823                                            _string[_nstring] = '\0';
824                                    }
825                                    break;
826                            case 'f':
827                            case 'F':
828                                    base = strrchr(rcs->file, '/');
829                                    if(!base)
830                                            add_string_str(rcs->file);
831                                    else
832                                            add_string_str(base+1);
833                                    if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
834                                    {
835                                            _nstring -= 2;
836                                            _string[_nstring] = '\0';
837                                    }
838                                    break;
839                            case 'p':
840                                    base = strrchr(rcs->file, '/');
841                                    if(base)
842                                    {
843                                            char *c = xstrdup(rcs->file);
844                                            base = strrchr(c, '/');
845                                            assert(base != NULL);
846                                            base[1] = '\0';
847                                            add_string_str(c);
848                                            xfree(c);
849                                    }
850                                    /*
851                                     * We should not add anything here because we can encounter
852                                     * a completely empty path, in which case we do not want
853                                     * to add any slash. This prevents an inadvertent root redirect.
854                                     *
855                                     * else
856                                     *      add_string_str("/");
857                                     */
858                                    break;
859                            case 'm':
860                            case 'M':
861                                    add_string_str(conf.cvsmodule);
862                                    if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
863                                    {
864                                            /* Strip the trailing '/' */
865                                            _nstring--;
866                                            _string[_nstring] = '\0';
867                                    }
868                                    break;
869                            case 'r': add_string_str(nr); break;
870                            case 'b': add_string_str(nb); break;
871                            case '%': add_string_ch('%'); break;
872                            case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
873                            case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
874                            case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
875                            case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
876                            case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
877                            case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
878                            case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
879                            case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
880                            case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
881                            case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
882                            case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
883                            case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
884                            case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
885                            case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
886                            case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
887                            case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
888                            case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
889                            default:
890                                    add_string_ch('%');
891                                    add_string_ch(*s);
892                                    break;
893                            }
894                    }
895                    else
896                            add_string_ch(*s);
897          }          }
898          return NULL;          return xstrdup(_string);
899  }  }
900    
901  /*  /*
# Line 670  Line 903 
903   * Drawing routines   * Drawing routines
904   **************************************************************************   **************************************************************************
905   */   */
906  int get_swidth(const char *s, font_t *f)  static int get_swidth(const char *s, font_t *f)
907  {  {
908          if(!s)          int n;
909            int m;
910            if(!s || !*s)
911                  return 0;                  return 0;
912          return strlen(s) * (*f)->w;  
913    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
914            if(conf.use_ttf && f->ttfont)
915            {
916                    int bb[8];
917                    char *e;
918    #ifdef HAVE_GDIMAGESTRINGFT
919                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
920    #else
921                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
922    #endif
923                    if(!e)
924                            return bb[2] - bb[6];
925            }
926    #endif
927            for(n = m = 0; *s; n++, s++)
928            {
929                    if(*s == '\n')
930                    {
931                            if(n > m)
932                                    m = n;
933                            n = 0;
934                    }
935            }
936            if(n > m)
937                    m = n;
938            return m * f->gdfont->w;
939  }  }
940    
941  int get_sheight(const char *s, font_t *f)  static int get_sheight(const char *s, font_t *f)
942  {  {
943          int nl;          int nl;
944          if(!s)          if(!s || !*s)
945                  return 0;                  return 0;
946    
947    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
948            if(conf.use_ttf && f->ttfont)
949            {
950                    int bb[8];
951                    char *e;
952    #ifdef HAVE_GDIMAGESTRINGFT
953                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
954    #else
955                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
956    #endif
957                    if(!e)
958                            return bb[3] - bb[7] + 4;
959            }
960    #endif
961          for(nl = 1; *s; s++)          for(nl = 1; *s; s++)
962          {          {
963                  if(*s == '\n' && s[1])                  if(*s == '\n' && s[1])
964                          nl++;                          nl++;
965          }          }
966          return nl * (*f)->h;          return nl * f->gdfont->h;
967  }  }
968    
969  void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color)  static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
970  {  {
971          int r2 = 2*r;          int r2 = 2*r;
972          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
973          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
974          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
975          gdImageLine(im, x2, y1+r, x2, y2-r, color->id);          gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
976            if(conf.box_shadow)
977            {
978                    gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, black_color.id);
979                    gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, black_color.id);
980            }
981          if(r)          if(r)
982          {          {
983                    /* FIXME: Pixelization is not perfect */
984                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);                  gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
985                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);                  gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
986                  gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);                  gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);
987                    if(conf.box_shadow)
988                    {
989                            gdImageArc(im, x2-r+1, y2-r+1, r2, r2,   0,  90, black_color.id);
990                            gdImageArc(im, x2-r+1, y2-r, r2, r2,   0,  90, black_color.id);
991                            gdImageArc(im, x2-r, y2-r+1, r2, r2,   0,  90, black_color.id);
992                    }
993                  gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);                  gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);
994          }          }
995    #ifndef NOGDFILL
996            gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
997    #endif
998  }  }
999    
1000  void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)  static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1001  {  {
1002            int h = get_sheight(s, f);
1003          int xx, yy;          int xx, yy;
1004          switch(align & ALIGN_HX)          switch(align & ALIGN_HX)
1005          {          {
# Line 720  Line 1012 
1012          {          {
1013          default:          default:
1014          case ALIGN_VT: yy = 0; break;          case ALIGN_VT: yy = 0; break;
1015          case ALIGN_VC: yy = -get_sheight(s, f)/2; break;          case ALIGN_VC: yy = h/2; break;
1016          case ALIGN_VB: yy = -get_sheight(s, f); break;          case ALIGN_VB: yy = h; break;
1017          }          }
1018          gdImageString(im, *f, x+xx+1, y+yy, s, c->id);  #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1019            if(conf.use_ttf && f->ttfont)
1020            {
1021                    int bb[8];
1022                    char *e;
1023                    int cid = conf.anti_alias ? c->id : -c->id;
1024    #ifdef HAVE_GDIMAGESTRINGFT
1025                    e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1026    #else
1027                    e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1028    #endif
1029                    if(!e)
1030                            return;
1031            }
1032    #endif
1033            yy = -yy;
1034            gdImageString(im, f->gdfont, x+xx+1, y+yy, s, c->id);
1035  }  }
1036    
1037  void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)  static void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1038  {  {
1039          int lx = cx - r->w/2;          char *t;
1040            char *d;
1041            d = s = xstrdup(s);
1042            do
1043            {
1044                    t = strchr(s, '\n');
1045                    if(t)
1046                            *t = '\0';
1047                    draw_string(im, s, f, x, y, align, c);
1048                    y += get_sheight(s, f);
1049                    s = t+1;
1050            } while(t);
1051            xfree(d);
1052    }
1053    
1054    static void draw_rev(gdImagePtr im, revision_t *r)
1055    {
1056            int lx = r->cx - r->w/2;
1057          int rx = lx + r->w;          int rx = lx + r->w;
1058          int i;          int i;
1059          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color);          int ty = r->y;
1060            draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
1061          ty += conf.rev_tspace;          ty += conf.rev_tspace;
1062          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, r->cx, ty, ALIGN_HC, &conf.rev_color);
1063          ty += get_sheight(r->rev->rev, &conf.rev_font);          ty += get_sheight(r->rev->rev, &conf.rev_font);
1064            draw_stringnl(im, r->revtext, &conf.rev_text_font, r->cx, ty, ALIGN_HC, &conf.rev_text_color);
1065            ty += get_sheight(r->revtext, &conf.rev_text_font);
1066          for(i = 0; i < r->ntags; i++)          for(i = 0; i < r->ntags; i++)
1067          {          {
1068                  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, r->cx, ty, ALIGN_HC, &conf.tag_color);
1069                  ty += get_sheight(r->tags[i]->tag, &conf.tag_font);                  ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1070          }          }
1071  }  }
1072    
1073  void draw_branch(gdImagePtr im, int cx, int ty, branch_t *b)  static void draw_branch_box(gdImagePtr im, branch_t *b, int ypos)
1074  {  {
1075          int lx = cx - b->w/2;          int lx = b->cx - b->w/2;
1076          int rx = lx + b->w;          int rx = lx + b->w;
         int yy;  
1077          int i;          int i;
1078          draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color);          int yy;
1079    
1080            draw_rbox(im, lx, ypos, rx, ypos+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1081          yy = conf.branch_tspace;          yy = conf.branch_tspace;
1082          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, b->cx, ypos+yy, ALIGN_HC, &conf.branch_color);
1083          yy += get_sheight(b->branch, &conf.branch_font);          yy += get_sheight(b->branch->branch, &conf.branch_font);
1084          if(b->tag)          for(i = 0; i < b->ntags; i++)
1085          {          {
1086                  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_tag_font, b->cx, ypos+yy, ALIGN_HC, &conf.branch_tag_color);
1087                    yy += get_sheight(b->tags[i]->tag, &conf.branch_font);
1088          }          }
1089    }
1090    
1091    static void draw_branch(gdImagePtr im, branch_t *b)
1092    {
1093            int yy;
1094            int i;
1095            int line[4];
1096            int l;
1097            int sign;
1098    
1099            line[0] = conf.rev_color.id;
1100            line[1] = gdTransparent;
1101            line[1] = gdTransparent;
1102            line[3] = conf.rev_color.id;
1103    
1104          ty += b->h;          draw_branch_box(im, b, b->y);
1105    
1106            if(conf.upside_down)
1107            {
1108                    yy = b->y;
1109                    for(i = 0; i < b->nrevs; i++)
1110                    {
1111                            revision_t *r = b->revs[i];
1112                            gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1113                            gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1114                            for(sign = l = 1; l < conf.thick_lines; l++)
1115                            {
1116                                    int pp = (l+1)/2*sign;
1117                                    gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1118                                    sign *= -1;
1119                            }
1120                            draw_rev(im, r);
1121                            yy = r->y;
1122                    }
1123                    if(conf.branch_dupbox)
1124                    {
1125                            i = b->y - b->th + b->h;
1126                            gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1127                            for(sign = l = 1; l < conf.thick_lines; l++)
1128                            {
1129                                    int pp = (l+1)/2*sign;
1130                                    gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1131                                    sign *= -1;
1132                            }
1133                            draw_branch_box(im, b, i);
1134                    }
1135            }
1136            else
1137            {
1138                    yy = b->y + b->h;
1139                    for(i = 0; i < b->nrevs; i++)
1140                    {
1141                            revision_t *r = b->revs[i];
1142                            gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1143                            gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1144                            for(sign = l = 1; l < conf.thick_lines; l++)
1145                            {
1146                                    int pp = (l+1)/2*sign;
1147                                    gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1148                                    sign *= -1;
1149                            }
1150                            draw_rev(im, r);
1151                            yy = r->y + r->h;
1152                    }
1153                    if(conf.branch_dupbox)
1154                    {
1155                            i = b->y + b->th - b->h;
1156                            gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1157                            for(sign = l = 1; l < conf.thick_lines; l++)
1158                            {
1159                                    int pp = (l+1)/2*sign;
1160                                    gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1161                                    sign *= -1;
1162                            }
1163                            draw_branch_box(im, b, i);
1164                    }
1165            }
1166    }
1167    
1168    static void draw_connector(gdImagePtr im, branch_t *b)
1169    {
1170            int l;
1171            int sign;
1172            revision_t *r = b->branchpoint;
1173            int x1 = r->cx + r->w/2 + 2;
1174            int y1 = r->y + r->h/2;
1175            int x2 = b->cx;
1176            int y2 = b->y;
1177            if(conf.upside_down)
1178                    y2 += b->h;
1179            gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1180            gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1181            for(sign = l = 1; l < conf.thick_lines; l++)
1182            {
1183                    int pp = (l+1)/2*sign;
1184                    gdImageLine(im, x1, y1+pp, x2-pp, y1+pp, conf.branch_color.id);
1185                    gdImageLine(im, x2+pp, y1-pp, x2+pp, y2, conf.branch_color.id);
1186                    sign *= -1;
1187            }
1188    }
1189    
1190    static void alloc_color(gdImagePtr im, color_t *c)
1191    {
1192            c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1193    }
1194    
1195    gdImagePtr make_image(rcsfile_t *rcs)
1196    {
1197            gdImagePtr im;
1198            int i;
1199            char *cptr;
1200    
1201            cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1202            i = get_swidth(cptr, &conf.title_font);
1203            if(rcs->tw+conf.margin_left+conf.margin_right > i)
1204                    i = rcs->tw+conf.margin_left+conf.margin_right;
1205            im = gdImageCreate(i, rcs->th+conf.margin_top+conf.margin_bottom);
1206            alloc_color(im, &conf.color_bg);
1207            alloc_color(im, &conf.tag_color);
1208            alloc_color(im, &conf.rev_color);
1209            alloc_color(im, &conf.rev_bgcolor);
1210            alloc_color(im, &conf.rev_text_color);
1211            alloc_color(im, &conf.branch_color);
1212            alloc_color(im, &conf.branch_tag_color);
1213            alloc_color(im, &conf.branch_bgcolor);
1214            alloc_color(im, &conf.title_color);
1215            alloc_color(im, &black_color);
1216            alloc_color(im, &white_color);
1217    
1218            if(conf.transparent_bg)
1219                    gdImageColorTransparent(im, conf.color_bg.id);
1220    
1221            for(i = 0; i < rcs->nbranches; i++)
1222                    draw_branch(im, rcs->branches[i]);
1223            for(i = 0; i < rcs->nbranches; i++)
1224            {
1225                    if(rcs->branches[i]->branchpoint)
1226                            draw_connector(im, rcs->branches[i]);
1227            }
1228            draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
1229            xfree(cptr);
1230    
1231            return im;
1232    }
1233    
1234    /*
1235     **************************************************************************
1236     * Layout routines
1237     *
1238     * Branch BBox:
1239     *      left   = center_x - total_width / 2     (cx-tw)/2
1240     *      right  = center_x + total_width / 2     (cx+tw)/2
1241     *      top    = y_pos                          (y)
1242     *      bottom = y_pos + total_height           (y+th)
1243     *
1244     * Margins of branches:
1245     *
1246     *         .              .
1247     *         .              .
1248     *         +--------------+
1249     *            ^
1250     *            | branch_margin           .
1251     *            v                         .
1252     * ----------------+                    .
1253     *                 | ^                  |
1254     *                 | | branch_connect   |
1255     *                 | v                  |
1256     *..-+      +t-----+------+      +------+------+
1257     *   |      l             |      |             |
1258     *   | <--> | branch bbox | <--> | branch bbox |
1259     *   |   |  |             r   |  |             |
1260     *..-+   |  +------------b+   |  +-------------+
1261     *       |    ^               branch_margin
1262     *       |    | branch_margin
1263     *       |    v
1264     *       |  +-------------+
1265     *       |  .             .
1266     *       |  .             .
1267     *       |
1268     *       branch_margin
1269     *
1270     * FIXME: There are probable som +/-1 errors in the code...
1271     *        (notably shadows are not calculated in the margins)
1272     **************************************************************************
1273     */
1274    static void move_branch(branch_t *b, int x, int y)
1275    {
1276            int i;
1277            b->cx += x;
1278            b->y += y;
1279          for(i = 0; i < b->nrevs; i++)          for(i = 0; i < b->nrevs; i++)
1280          {          {
1281                  gdImageLine(im, cx, ty, cx, ty+conf.rev_minline, conf.rev_color.id);                  b->revs[i]->cx += x;
1282                  ty += conf.rev_minline;                  b->revs[i]->y += y;
1283                  draw_rev(im, cx, ty, b->revs[i]);          }
1284                  ty += b->revs[i]->h;  }
1285    
1286    static void initial_reposition_branch(revision_t *r, int *x, int *w)
1287    {
1288            int i, j;
1289            for(j = 0; j < r->nbranches; j++)
1290            {
1291                    branch_t *b = r->branches[j];
1292                    *x += *w + conf.rev_minline + b->tw/2 - b->cx;
1293                    *w = b->tw/2;
1294                    move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
1295                    *x = b->cx;
1296                    /* Recurse to move branches of branched revisions */
1297                    for(i = b->nrevs-1; i >= 0; i--)
1298                    {
1299                            initial_reposition_branch(b->revs[i], x, w);
1300                    }
1301          }          }
1302  }  }
1303    
1304  static char *_title;  static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
1305  static int _ntitle;  {
1306  static int _natitle;          int x1 = *x;
1307            int x2 = x1 + *w;
1308            int y1 = *y;
1309            int y2 = y1 + *h;
1310            int xx1 = b->cx - b->tw/2;
1311            int xx2 = xx1 + b->tw;
1312            int yy1 = b->y;
1313            int yy2 = yy1 + b->th;
1314            x1 = MIN(x1, xx1);
1315            x2 = MAX(x2, xx2);
1316            y1 = MIN(y1, yy1);
1317            y2 = MAX(y2, yy2);
1318            *x = x1;
1319            *y = y1;
1320            *w = x2 - x1;
1321            *h = y2 - y1;
1322    }
1323    
1324    static int branch_intersects(int top, int bottom, int left, branch_t *b)
1325    {
1326            int br = b->cx + b->tw/2;
1327            int bt = b->y - conf.branch_connect - conf.branch_margin/2;
1328            int bb = b->y + b->th + conf.branch_margin/2;
1329            return !(bt > bottom || bb < top || br >= left);
1330    }
1331    
1332    static int kern_branch(rcsfile_t *rcs, branch_t *b)
1333    {
1334            int left = b->cx - b->tw/2;
1335            int top = b->y - conf.branch_connect - conf.branch_margin/2;
1336            int bottom = b->y + b->th + conf.branch_margin/2;
1337            int i;
1338            int xpos = 0;
1339    
1340            for(i = 0; i < rcs->nbranches; i++)
1341            {
1342                    branch_t *bp = rcs->branches[i];
1343                    if(bp == b)
1344                            continue;
1345                    if(branch_intersects(top, bottom, left, bp))
1346                    {
1347                            int m = bp->cx + bp->tw/2 + conf.branch_margin;
1348                            if(m > xpos)
1349                                    xpos = m;
1350                    }
1351            }
1352            if(xpos && (b->cx - b->tw/2) - xpos > 0)
1353            {
1354                    move_branch(b, xpos - (b->cx - b->tw/2), 0);
1355                    return 1;
1356            }
1357            return 0;
1358    }
1359    
1360    static int kern_tree(rcsfile_t *rcs)
1361    {
1362            int i;
1363            int moved;
1364            int safeguard;
1365            int totalmoved = 0;
1366            for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
1367            {
1368                    moved = 0;
1369                    for(i = 1; i < rcs->nbranches; i++)
1370                    {
1371                            moved += kern_branch(rcs, rcs->branches[i]);
1372                    }
1373                    totalmoved += moved;
1374    #ifdef DEBUG
1375                    fprintf(stderr, "kern_tree: moved=%d\n", moved);
1376    #endif
1377            }
1378            if(!quiet && !safeguard)
1379                    fprintf(stderr, "kern_tree: safeguard terminated possible infinite loop; please report.\n");
1380            return totalmoved;
1381    }
1382    
1383    static int index_of_revision(revision_t *r)
1384    {
1385            branch_t *b = r->branch;
1386            int i;
1387            for(i = 0; i < b->nrevs; i++)
1388            {
1389                    if(r == b->revs[i])
1390                            return i;
1391            }
1392            fprintf(stderr, "index_of_revision: Cannot find revision in branch\n");
1393            return 0;
1394    }
1395    
1396    static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
1397    {
1398            if(l)   *l = br->cx - br->tw/2;
1399            if(r)   *r = br->cx + br->tw/2;
1400            if(t)   *t = br->y;
1401            if(b)   *b = br->y + br->th + (conf.branch_dupbox ? conf.rev_minline + br->h : 0);
1402    }
1403    
1404    static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
1405    {
1406            int extra = conf.branch_margin & 1;     /* Correct +/-1 error on div 2 */
1407            branch_bbox(br, l, r, t, b);
1408            if(l)   *l -= conf.branch_margin/2;
1409            if(r)   *r += conf.branch_margin/2 + extra;
1410            if(t)   *t -= conf.branch_connect + conf.branch_margin/2;
1411            if(b)   *b += conf.branch_margin/2 + extra;
1412    }
1413    
1414    static int branch_distance(branch_t *br1, branch_t *br2)
1415    {
1416            int l1, r1, t1, b1;
1417            int l2, r2, t2, b2;
1418            assert(br1 != NULL);
1419            assert(br2 != NULL);
1420            branch_bbox(br1, &l1, &r1, NULL, NULL);
1421            branch_bbox(br2, &l2, &r2, NULL, NULL);
1422            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
1423            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
1424            /* Return:
1425             * - 0 if branches have no horizontal overlap
1426             * - positive if b1 is left of b2
1427             * - negative if b2 is left of b1
1428             */
1429            if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
1430                    return l1 < l2 ? l2 - r1 : -(l1 - r2);
1431            else
1432                    return 0;
1433    }
1434    
1435    static int space_needed(branch_t *br1, branch_t *br2)
1436    {
1437            int t1, b1;
1438            int t2, b2;
1439            assert(br1 != NULL);
1440            assert(br2 != NULL);
1441            assert(br1->cx < br2->cx);      /* br1 must be left of br2 */
1442            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
1443            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
1444            /* Return:
1445             * - positive if top br1 is located lower than br2
1446             * - negatve is top br2 is located lower than br1
1447             */
1448            if(t1 > t2)
1449                    return -(t1 - b2);
1450            else
1451                    return t2 - b1;
1452    }
1453    
1454  void add_title_str(const char *s)  static void move_yr_branch(branch_t *b, int dy)
1455  {  {
1456          int l = strlen(s) + 1;          int i, j;
1457          if(_ntitle + l > _natitle)  #ifdef DEBUG
1458    //      fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);
1459    #endif
1460            b->y += dy;
1461            for(i = 0; i < b->nrevs; i++)
1462          {          {
1463                  _natitle += 128;                  b->revs[i]->y += dy;
1464                  _title = xrealloc(_title, _natitle * sizeof(_title[0]));                  for(j = 0; j < b->revs[i]->nbranches; j++)
1465                    {
1466    #ifdef DEBUG
1467    //                      fprintf(stderr, ".");
1468    #endif
1469                            move_yr_branch(b->revs[i]->branches[j], dy);
1470                    }
1471          }          }
         memcpy(_title+_ntitle, s, l);  
         _ntitle += l-1;  
1472  }  }
1473    
1474  void add_title_ch(int ch)  static void move_trunk(revision_t *r, int dy)
1475  {  {
1476          char buf[2];          int i, j;
1477          buf[0] = ch;          branch_t *b = r->branch;
1478          buf[1] = '\0';          b->th += dy;
1479          add_title_str(buf);          for(i = index_of_revision(r); i < b->nrevs; i++)
1480            {
1481    #ifdef DEBUG
1482                    fprintf(stderr, "move_trunk: start %s, moving %s by %d (b's %d)\n", r->rev->rev, b->revs[i]->rev->rev, dy, b->revs[i]->nbranches);
1483    #endif
1484                    b->revs[i]->y += dy;
1485                    for(j = 0; j < b->revs[i]->nbranches; j++)
1486                    {
1487                            move_yr_branch(b->revs[i]->branches[j], dy);
1488                    }
1489            }
1490  }  }
1491    
1492  char *expand_title(rcsfilelog_t *rcs)  static int space_below(rcsfile_t *rcs, revision_t *r)
1493  {  {
1494          char nb[32];          int i, j;
1495          char nr[32];          int bl, br, bb;
1496          char *cptr;          int space = INT_MAX;
1497            branch_t *b = r->branch;
1498            branch_t *minb = NULL;
1499    
1500          sprintf(nb, "%d", rcs->nbranches);          branch_ext_bbox(b, &bl, &br, NULL, &bb);
1501          sprintf(nr, "%d", rcs->nrevs);          for(i = 0; i < rcs->nbranches; i++)
         for(cptr = conf.title; *cptr; cptr++)  
1502          {          {
1503                  if(*cptr == '%')                  int tbl, tbr, tbt;
1504                    branch_t *tb = rcs->branches[i];
1505                    branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
1506                    if(tb == b)
1507                            continue;
1508                    if(tbt > bb)    /* Must be below our branch */
1509                  {                  {
1510                          switch(*++cptr)                          if(tb->branchpoint)     /* Take account for the horiz connector */
1511                                    tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
1512                            if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
1513                          {                          {
1514                          case 'c': add_title_str(conf.cvsroot); break;                                  int s = tbt - bb - conf.branch_connect;
1515                          case 'f': add_title_str(rcs->name); break;                                  if(s < space)
1516                          case 'm': add_title_str(conf.cvsmodule); break;                                  {
1517                          case 'r': add_title_str(nr); break;                                          space = s;
1518                          case 'b': add_title_str(nb); break;                                          minb = tb;
1519                          case '%': add_title_ch('%'); break;                                  }
1520                          default:                          }
1521                                  add_title_ch('%');                  }
1522                                  add_title_ch(*cptr);          }
1523                                  break;          if(b->branchpoint)
1524            {
1525                    for(i = index_of_revision(r); i < b->nrevs; i++)
1526                    {
1527                            for(j = 0; j < b->revs[i]->nbranches; j++)
1528                            {
1529                                    int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
1530                                    if(s < space)
1531                                            space = s;
1532                          }                          }
1533                  }                  }
                 else  
                         add_title_ch(*cptr);  
1534          }          }
1535          return _title;  #ifdef DEBUG
1536            fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
1537    #endif
1538            return space;
1539  }  }
1540    
1541  void draw_title(gdImagePtr im, char *title)  static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
1542  {  {
1543          char *t;          int i;
1544          char *s = title;          int space = 0;
1545          int x = conf.title_x;          int nlinks = 0;
1546          int y = conf.title_y;          revision_t *r;
1547          do          branch_t *b;
1548            branch_t *ancestor;
1549            revision_t *branchpoint;
1550    
1551            if(!tagbr->branchpoint || !colbr->branchpoint)
1552          {          {
1553                  t = strchr(s, '\n');                  if(!quiet)
1554                  if(t)                          fprintf(stderr, "space_available: Trying to stretch the top?\n");
1555                          *t = '\0';                  return 0;
1556                  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);  
 }  
1557    
1558  void draw_connector(gdImagePtr im, branch_t *b)          r = colbr->branchpoint;
1559  {          b = r->branch;
1560          revision_t *r = b->branchpoint;          branchpoint = tagbr->branchpoint;
1561          int x1 = r->x + r->w/2 + 2;          ancestor = branchpoint->branch;
1562          int y1 = r->y + r->h/2;          assert(b != NULL);
1563          int x2 = b->x;          assert(ancestor != NULL);
1564          int y2 = b->y;  
1565          gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);          while(1)
1566          gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);          {
1567                    int s;
1568                    int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
1569                    for(i = index_of_revision(r); i >= rtag; i--)
1570                    {
1571                            if(i > 0)
1572                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
1573                            else
1574                                    s = b->revs[i]->y - (b->y + b->h);
1575                            if(s < conf.rev_maxline)
1576                            {
1577                                    space += conf.rev_maxline - s;
1578                                    nlinks++;
1579                            }
1580                    }
1581                    s = space_below(rcs, r);
1582                    if(s < space)
1583                            space = s;
1584    #ifdef DEBUG
1585                    if(space < 0)
1586                            return -1;
1587    #endif
1588                    if(b == ancestor)
1589                            break;
1590                    r = b->branchpoint;
1591                    if(!r)
1592                    {
1593                            /* Not a common ancestor */
1594                            r = colbr->branchpoint;
1595                            b = r->branch;
1596                            branchpoint = ancestor->branchpoint;
1597                            if(!branchpoint)
1598                            {
1599                                    if(!quiet)
1600                                            fprintf(stderr, "space_available: No common ancestor?\n");
1601                                    return 0;
1602                            }
1603                            ancestor = branchpoint->branch;
1604                            assert(ancestor != NULL);
1605                            nlinks = 0;
1606                            space = 0;
1607                            continue;       /* Restart with a new ancestor */
1608                    }
1609                    b = r->branch;
1610            }
1611            if(nl)
1612                    *nl = nlinks;           /* Return the number of links that can stretch */
1613            if(bpcommon)
1614                    *bpcommon = branchpoint;        /* Return the ancestral branchpoint on the common branch */
1615            return space;
1616  }  }
1617    
1618  gdImagePtr make_image(rcsfilelog_t *rcs)  static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
1619  {  {
1620          gdImagePtr im;          revision_t *r;
1621            revision_t *bpcommon = NULL;
1622            branch_t *ancestor = NULL;
1623            branch_t *b;
1624          int i;          int i;
1625            int space;
1626            int nlinks;
1627            int dy;
1628            int rest;
1629    
1630            space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
1631            if(bpcommon)
1632                    ancestor = bpcommon->branch;
1633    
1634    #ifdef DEBUG
1635            if(space == -1)
1636                    return 0;
1637            fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
1638    #endif
1639            if(space < totalstretch)
1640                    return 0;
1641    
1642          im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);          dy = totalstretch / nlinks;
1643          conf.color_bg.id = gdImageColorAllocate(im, conf.color_bg.r, conf.color_bg.g, conf.color_bg.b);          rest = totalstretch - dy * nlinks;
         conf.tag_color.id = gdImageColorAllocate(im, conf.tag_color.r, conf.tag_color.g, conf.tag_color.b);  
         conf.rev_color.id = gdImageColorAllocate(im, conf.rev_color.r, conf.rev_color.g, conf.rev_color.b);  
         conf.branch_color.id = gdImageColorAllocate(im, conf.branch_color.r, conf.branch_color.g, conf.branch_color.b);  
         conf.branch_bgcolor.id = gdImageColorAllocate(im, conf.branch_bgcolor.r, conf.branch_bgcolor.g, conf.branch_bgcolor.b);  
         conf.title_color.id = gdImageColorAllocate(im, conf.title_color.r, conf.title_color.g, conf.title_color.b);  
1644    
1645          for(i = 0; i < rcs->nbranches; i++)          r = br1->branchpoint;
1646                  draw_branch(im, rcs->branches[i]->x, rcs->branches[i]->y, rcs->branches[i]);          b = r->branch;
1647          for(i = 0; i < rcs->nbranches; i++)          while(1)
1648          {          {
1649                  if(rcs->branches[i]->branchpoint)                  int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
1650                          draw_connector(im, rcs->branches[i]);                  for(i = index_of_revision(r); i >= rtag; i--)
1651                    {
1652                            int s, q;
1653                            if(i > 0)
1654                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
1655                            else
1656                                    s = b->revs[i]->y - (b->y + b->h);
1657                            q = conf.rev_maxline - s;
1658                            if(q > 0)
1659                            {
1660                                    int d = rest ? rest/nlinks+1 : 0;
1661                                    if(q >= dy+d)
1662                                    {
1663                                            move_trunk(b->revs[i], dy+d);
1664                                    }
1665                                    else
1666                                    {
1667                                            move_trunk(b->revs[i], q);
1668                                            rest += dy+d - q;
1669                                    }
1670                                    rest -= d;
1671                                    nlinks--;
1672                            }
1673                    }
1674                    if(b == ancestor)
1675                            break;
1676                    r = b->branchpoint;
1677                    assert(r != NULL);      /* else 'space_available' wouldn't have returned positively */
1678                    b = r->branch;
1679          }          }
1680          draw_title(im, expand_title(rcs));          return 1;
   
         return im;  
1681  }  }
1682    
1683  void move_branch(branch_t *b, int x, int y)  static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
1684  {  {
1685          int i;          int i;
1686          b->x += x;          int dist = INT_MAX;
1687          b->y += y;          branch_t *col = NULL;
1688          for(i = 0; i < b->nrevs; i++)  
1689            for(i = 0; i < rcs->nbranches; i++)
1690          {          {
1691                  b->revs[i]->x += x;                  int t = branch_distance(rcs->branches[i], b);
1692                  b->revs[i]->y += y;                  if(t > 0 && t < dist)
1693                    {
1694                            dist = t;
1695                            col = rcs->branches[i];
1696                    }
1697          }          }
1698            return col;
1699  }  }
1700    
1701  void rect_union(int *x, int *y, int *w, int *h, branch_t *b)  void auto_stretch(rcsfile_t *rcs)
1702  {  {
1703          int x1 = *x;          int i;
1704          int x2 = x1 + *w;          int safeguard;
1705          int y1 = *y;  
1706          int y2 = y1 + *h;          for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
1707          int xx1 = b->x - b->tw/2;          {
1708          int xx2 = xx1 + b->tw;                  int bl, pr;
1709          int yy1 = b->y;                  branch_t *b = rcs->branches[i];
1710          int yy2 = yy1 + b->th;                  if(!b->branchpoint)
1711          x1 = MIN(x1, xx1);                          continue;
1712          x2 = MAX(x2, xx2);                  branch_bbox(b, &bl, NULL, NULL, NULL);
1713          y1 = MIN(y1, yy1);                  branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
1714          y2 = MAX(y2, yy2);                  if(bl - conf.branch_margin - pr > 0)
1715          *x = x1;                  {
1716          *y = y1;                          branch_t *col;
1717          *w = x2 - x1;                          int spaceneeded;
1718          *h = y2 - y1;                          /* There is a potential to move branch b further left.
1719                             * All branches obstructing this one from moving further
1720                             * left must be originating from revisions below
1721                             * b->branchpoint until a common ancester.
1722                             * So, we search all branches for a branch that lies left
1723                             * of b and is closest to b. This is then the collission
1724                             * branch that needs to be moved.
1725                             */
1726                            col = find_collision_branch(rcs, b);
1727                            if(!col)
1728                                    continue;
1729                            spaceneeded = space_needed(col, b);
1730                            if(spaceneeded < 0)
1731                                    continue;
1732    #ifdef DEBUG
1733                            fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
1734    #endif
1735                            /* Trace the collision branch back to find the common ancester
1736                             * of both col and b. All revisions encountered while traversing
1737                             * backwards must be stretched, including all revisions on the
1738                             * common ancester from where the branches sprout.
1739                             */
1740                            if(stretch_branches(rcs, col, b, spaceneeded))
1741                            {
1742                                    if(kern_tree(rcs))
1743                                    {
1744                                            /* Restart the process because movement can
1745                                             * cause more movement.
1746                                             */
1747                                            i = 0 - 1;      /* -1 for the i++ of the loop */
1748                                            safeguard--;    /* Prevent infinite loop, just in case */
1749                                    }
1750                                    //return;
1751                            }
1752                    }
1753            }
1754            if(!quiet && !safeguard)
1755                    fprintf(stderr, "auto_stretch: safeguard terminated possible infinite loop; please report.\n");
1756  }  }
1757    
1758  void make_layout(rcsfilelog_t *rcs)  void make_layout(rcsfile_t *rcs)
1759  {  {
1760          int i, j;          int i, j;
1761          int x, y;          int x, y;
1762          int w, h;          int w, h;
1763          int w2;          int w2;
1764    
1765            /* Remove all unwanted revisions */
1766            if(conf.strip_untagged)
1767            {
1768                    int fr = conf.strip_first_rev ? 0 : 1;
1769                    for(i = 0; i < rcs->nbranches; i++)
1770                    {
1771                            branch_t *bp = rcs->branches[i];
1772                            for(j = fr; j < bp->nrevs-1; j++)
1773                            {
1774                                    if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)
1775                                    {
1776                                            memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
1777                                            bp->nrevs--;
1778                                            bp->revs[j]->stripped = 1;
1779                                            j--;
1780                                    }
1781                            }
1782                    }
1783            }
1784    
1785          /* Calculate the box-sizes of the revisions */          /* Calculate the box-sizes of the revisions */
1786          for(i = 0; i < rcs->nrevs; i++)          for(i = 0; i < rcs->nsrev; i++)
1787          {          {
1788                  revision_t *rp;                  revision_t *rp;
1789                  int w;                  int w;
1790                  int h;                  int h;
1791                  rp = rcs->revs[i];                  rp = rcs->srev[i];
1792                  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);
1793                  h = get_sheight(rp->rev->rev, &conf.rev_font);                  w = get_swidth(rp->revtext, &conf.rev_text_font);
1794                    j = get_swidth(rp->rev->rev, &conf.rev_font);
1795                    if(j > w)
1796                            w = j;
1797                    h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);
1798                  for(j = 0; j < rp->ntags; j++)                  for(j = 0; j < rp->ntags; j++)
1799                  {                  {
1800                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
# Line 940  Line 1811 
1811                  branch_t *bp = rcs->branches[i];                  branch_t *bp = rcs->branches[i];
1812                  int w;                  int w;
1813                  int h;                  int h;
1814                  w = get_swidth(bp->branch, &conf.branch_font);                  w = get_swidth(bp->branch->branch, &conf.branch_font);
1815                  h = get_sheight(bp->branch, &conf.branch_font);                  h = get_sheight(bp->branch->branch, &conf.branch_font);
1816                  if(bp->tag)                  for(j = 0; j < bp->ntags; j++)
1817                  {                  {
1818                          int ww = get_swidth(bp->tag->tag, &conf.branch_font);                          int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
1819                          if(ww > w) w = ww;                          if(ww > w) w = ww;
1820                          h += get_sheight(bp->tag->tag, &conf.branch_font) + conf.branch_separator;                          h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
1821                  }                  }
1822                  w += conf.branch_lspace + conf.branch_rspace;                  w += conf.branch_lspace + conf.branch_rspace;
1823                  h += conf.branch_tspace + conf.branch_bspace;                  h += conf.branch_tspace + conf.branch_bspace;
# Line 958  Line 1829 
1829                                  w = bp->revs[j]->w;                                  w = bp->revs[j]->w;
1830                          h += bp->revs[j]->h + conf.rev_minline;                          h += bp->revs[j]->h + conf.rev_minline;
1831                  }                  }
1832                    if(conf.branch_dupbox)
1833                            h += bp->h + conf.rev_minline;
1834                  bp->th = h;                  bp->th = h;
1835                  bp->tw = w;                  bp->tw = w;
1836          }          }
# Line 968  Line 1841 
1841                  branch_t *b = rcs->branches[i];                  branch_t *b = rcs->branches[i];
1842                  x = b->tw/2;                  x = b->tw/2;
1843                  y = b->h;                  y = b->h;
1844                  b->x = x;                  b->cx = x;
1845                  b->y = 0;                  b->y = 0;
1846                  for(j = 0; j < b->nrevs; j++)                  for(j = 0; j < b->nrevs; j++)
1847                  {                  {
1848                          y += conf.rev_minline;                          y += conf.rev_minline;
1849                          b->revs[j]->x = x;                          b->revs[j]->cx = x;
1850                          b->revs[j]->y = y;                          b->revs[j]->y = y;
1851                          y += b->revs[j]->h;                          y += b->revs[j]->h;
1852                  }                  }
1853          }          }
1854    
1855          /* Reposition the branches FIXME: Should be recursive on branchpoint revisions within branches... */          /* Initially reposition the branches from bottom to top progressively right */
1856          x = rcs->branches[0]->x;          x = rcs->branches[0]->cx;
1857          w2 = rcs->branches[0]->tw / 2;          w2 = rcs->branches[0]->tw / 2;
1858          for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)          for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
1859          {          {
1860                  revision_t *r = rcs->branches[0]->revs[i];                  initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
                 for(j = 0; j < r->nbranches; j++)  
                 {  
                         branch_t *b = r->branches[j];  
                         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;  
                 }  
1861          }          }
1862    
1863            /* Initially move branches left if there is room */
1864            kern_tree(rcs);
1865    
1866            /* Try to kern the branches more by expanding the inter-revision spacing */
1867            if(conf.auto_stretch)
1868                    auto_stretch(rcs);
1869    
1870            /* Move everything w.r.t. the top-left margin */
1871          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
1872                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
1873    
1874          /* Calculate overall image size */          /* Calculate overall image size */
1875          x = rcs->branches[0]->x - rcs->branches[0]->tw/2;          x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
1876          y = rcs->branches[0]->y;          y = rcs->branches[0]->y;
1877          w = rcs->branches[0]->tw;          w = rcs->branches[0]->tw;
1878          h = rcs->branches[0]->th;          h = rcs->branches[0]->th;
# Line 1007  Line 1880 
1880                  rect_union(&x, &y, &w, &h, rcs->branches[i]);                  rect_union(&x, &y, &w, &h, rcs->branches[i]);
1881          rcs->tw = w;          rcs->tw = w;
1882          rcs->th = h;          rcs->th = h;
1883    
1884            /* Flip the entire tree */
1885            if(conf.upside_down)
1886            {
1887                    y += rcs->th;
1888                    for(i = 0; i < rcs->nbranches; i++)
1889                    {
1890                            branch_t *b = rcs->branches[i];
1891                            for(j = 0; j < b->nrevs; j++)
1892                            {
1893                                    revision_t *r = b->revs[j];
1894                                    r->y = y - r->y - r->h + conf.margin_top;
1895                            }
1896                            b->y = y - b->y - b->h + conf.margin_top;
1897                    }
1898            }
1899  }  }
1900    
1901  /*  /*
1902   **************************************************************************   **************************************************************************
1903   * Configuration   * Imagemap functions
1904   **************************************************************************   **************************************************************************
1905   */   */
1906  int read_config(const char *path)  void make_imagemap(rcsfile_t *rcs, FILE *fp)
1907  {  {
1908          FILE *fp;          int i, j;
1909          int r;          fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
1910          if(path)          for(i = 0; i < rcs->nbranches; i++)
         {  
                 if((fp = fopen(path, "r")) == NULL)  
                 {  
                         return 0;  
                 }  
         }  
         else  
1911          {          {
1912                  if((fp = fopen("./" CONFFILENAME, "r")) == NULL)                  branch_t *b = rcs->branches[i];
1913                    tag_t *tag = b->ntags ? b->tags[0] : NULL;
1914                    char *bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
1915                    char *balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
1916                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1917                                    bhref,
1918                                    b->cx - b->w/2, b->y, b->cx + b->w/2, b->y + b->h,
1919                                    balt);
1920                    for(j = 0; j < b->nrevs; j++)
1921                  {                  {
1922                          if((fp = fopen(ETCDIR "/" CONFFILENAME, "r")) == NULL)                          revision_t *r = b->revs[j];
1923                            revision_t* r1;
1924                            int xoff;
1925                            int x1;
1926                            int x2;
1927                            int y1;
1928                            int y2;
1929                            char *href;
1930                            char *alt;
1931    
1932                            tag = r->ntags ? r->tags[0] : NULL;
1933                            href = expand_string(conf.map_rev_href, rcs, r, r->rev, NULL, tag);
1934                            alt = expand_string(conf.map_rev_alt, rcs, r, r->rev, NULL, tag);
1935                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1936                                    href,
1937                                    r->cx - r->w/2, r->y, r->cx + r->w/2, r->y + r->h,
1938                                    alt);
1939                            xfree(href);
1940                            xfree(alt);
1941                            if(j > 0 || b->branchpoint)
1942                          {                          {
1943                                  return 0;                                  if(j > 0)
1944                                    {
1945                                            r1 = b->revs[j-1];
1946                                            xoff = MIN(r->w, r1->w)/4;
1947                                            y1 = conf.upside_down ? r1->y : r1->y + r1->h;
1948                                    }
1949                                    else
1950                                    {
1951                                            r1 = b->branchpoint;
1952                                            xoff = MIN(r->w, b->w)/4;
1953                                            y1 = conf.upside_down ? b->y : b->y + b->h;
1954                                    }
1955                                    x1 = r->cx - xoff;
1956                                    x2 = r->cx + xoff;
1957                                    y2 = conf.upside_down ? r->y + r->h : r->y;
1958                                    href = expand_string(conf.map_diff_href, rcs, r, r->rev, r1->rev, tag);
1959                                    alt = expand_string(conf.map_diff_alt, rcs, r, r->rev, r1->rev, tag);
1960                                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1961                                            href,
1962                                            x1, y1 + 1, x2, y2 - 1,
1963                                            alt);
1964                                    xfree(href);
1965                                    xfree(alt);
1966                          }                          }
1967                  }                  }
1968                    if(conf.branch_dupbox)
1969                    {
1970                            int y;
1971                            if(conf.upside_down)
1972                                    y = b->y + b->h - b->th;
1973                            else
1974                                    y = b->y - b->h + b->th;
1975                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1976                                            bhref,
1977                                            b->cx - b->w/2, y, b->cx + b->w/2, y + b->h,
1978                                            balt);
1979                    }
1980                    xfree(bhref);
1981                    xfree(balt);
1982          }          }
1983            fprintf(fp, "</map>\n");
         yyin = fp;  
         r = yyparse();  
         fclose(fp);  
         return r == 0;  
1984  }  }
1985    
1986  /*  /*
# Line 1049  Line 1990 
1990   */   */
1991  static const char usage_str[] =  static const char usage_str[] =
1992          "Usage: cvsgraph [options] <file>\n"          "Usage: cvsgraph [options] <file>\n"
1993          "  -c <file>  Read alternative config from <file>\n"          "  -b           Add a branch box at both sides of the trunk (config value is negated)\n"
1994          "  -h         This message\n"          "  -c <file>    Read alternative config from <file>\n"
1995          "  -m <mod>   Use <mod> as cvs module\n"          "  -d <level>   Enable debug mode at <level>\n"
1996          "  -o <file>  Output to <file>\n"          "  -h           This message\n"
1997          "  -r <path>  Use <path> as cvsroot path\n"          "  -i           Generate an imagemap instead of image\n"
1998          "  -V         Print version and exit\n"          "  -I <file>    Also write the imagemap to <file>\n"
1999            "  -k           Auto stretch the tree (config value is negated)\n"
2000            "  -M <name>    Use <name> as imagemap name\n"
2001            "  -m <mod>     Use <mod> as cvs module\n"
2002            "  -o <file>    Output to <file>\n"
2003            "  -O <opt=val> Set option opt to value val\n"
2004            "  -q           Be quiet (i.e. no warnings)\n"
2005            "  -r <path>    Use <path> as cvsroot path\n"
2006            "  -s           Strip untagged revisions (config value is negated)\n"
2007            "  -S           Also strip the first revision (config value is negated)\n"
2008            "  -u           Upside down image (mirror vertically; config value is negated)\n"
2009            "  -V           Print version and exit\n"
2010            "  -[0-9] <txt> Use <txt> for expansion\n"
2011          ;          ;
2012    
2013  #define VERSION_STR     "1.0.0"  #define VERSION_STR     "1.2.0"
2014  #define NOTICE_STR      "Copyright (c) 2001 B.Stultiens"  #define NOTICE_STR      "Copyright (c) 2001,2002 B.Stultiens"
2015    
2016  void add_tag(rcsfilelog_t *rcs, const char *tag, const char *rev)  static void append_slash(char **path)
2017  {  {
2018          rcs->tags = xrealloc(rcs->tags, (rcs->ntags+1)*sizeof(rcs->tags[0]));          int l;
2019          rcs->tags[rcs->ntags] = xmalloc(sizeof(tag_t));          assert(path != NULL);
2020          rcs->tags[rcs->ntags]->tag = strip_dup(tag);          assert(*path != NULL);
2021          rcs->tags[rcs->ntags]->rev = make_revid(rev);          l = strlen(*path);
2022          rcs->ntags++;          if(!l || (*path)[l-1] == '/')
2023                    return;
2024            *path = xrealloc(*path, l+2);
2025            strcat(*path, "/");
2026  }  }
2027    
2028  int main(int argc, char *argv[])  int main(int argc, char *argv[])
2029  {  {
2030            extern int rcs_flex_debug;
2031            extern int rcsdebug;
2032          int optc;          int optc;
2033          char *confpath = NULL;          char *confpath = NULL;
2034          char *outfile = NULL;          char *outfile = NULL;
2035          char *cvsroot = NULL;          char *cvsroot = NULL;
2036          char *cvsmodule = NULL;          char *cvsmodule = NULL;
2037            int imagemap = 0;
2038            int upsidedown = 0;
2039            int bdupbox = 0;
2040            int stripuntag = 0;
2041            int stripfirst = 0;
2042            int autostretch = 0;
2043            char *imgmapname = NULL;
2044            char *imgmapfile = NULL;
2045          int lose = 0;          int lose = 0;
2046          FILE *fp;          FILE *fp;
2047          int n;          char *rcsfilename;
2048          rcsfilelog_t *rcs;          rcsfile_t *rcs;
2049          gdImagePtr im;          gdImagePtr im;
2050    
2051          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:bc:d:hI:ikM:m:O:o:qr:SsuV")) != EOF)
2052          {          {
2053                  switch(optc)                  switch(optc)
2054                  {                  {
2055                    case 'b':
2056                            bdupbox = 1;
2057                            break;
2058                  case 'c':                  case 'c':
2059                          confpath = xstrdup(optarg);                          confpath = xstrdup(optarg);
2060                          break;                          break;
2061                    case 'd':
2062                            debuglevel = strtol(optarg, NULL, 0);
2063                            break;
2064                    case 'I':
2065                            imgmapfile = xstrdup(optarg);
2066                            break;
2067                    case 'i':
2068                            imagemap = 1;
2069                            break;
2070                    case 'k':
2071                            autostretch = 1;
2072                            break;
2073                    case 'M':
2074                            imgmapname = xstrdup(optarg);
2075                            break;
2076                  case 'm':                  case 'm':
2077                          cvsmodule = xstrdup(optarg);                          cvsmodule = xstrdup(optarg);
2078                          break;                          break;
2079                    case 'O':
2080                            stack_option(optarg);
2081                            break;
2082                  case 'o':                  case 'o':
2083                          outfile = xstrdup(optarg);                          outfile = xstrdup(optarg);
2084                          break;                          break;
2085                    case 'q':
2086                            quiet = 1;
2087                            break;
2088                  case 'r':                  case 'r':
2089                          cvsroot = xstrdup(optarg);                          cvsroot = xstrdup(optarg);
2090                          break;                          break;
2091                    case 'S':
2092                            stripfirst = 1;
2093                            break;
2094                    case 's':
2095                            stripuntag = 1;
2096                            break;
2097                    case 'u':
2098                            upsidedown = 1;
2099                            break;
2100                  case 'V':                  case 'V':
2101                          fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);                          fprintf(stdout, "cvsgraph v%s, %s\n", VERSION_STR, NOTICE_STR);
2102                          return 0;                          return 0;
# Line 1105  Line 2104 
2104                          fprintf(stdout, "%s", usage_str);                          fprintf(stdout, "%s", usage_str);
2105                          return 0;                          return 0;
2106                  default:                  default:
2107                          lose++;                          if(isdigit(optc))
2108                            {
2109                                    conf.expand[optc-'0'] = xstrdup(optarg);
2110                            }
2111                            else
2112                                    lose++;
2113                  }                  }
2114          }          }
2115    
         if(optind >= argc)  
         {  
                 fprintf(stderr, "Missing inputfile\n");  
                 lose++;  
         }  
   
2116          if(lose)          if(lose)
2117          {          {
2118                  fprintf(stderr, "%s", usage_str);                  fprintf(stderr, "%s", usage_str);
2119                  return 1;                  return 1;
2120          }          }
2121    
2122          /* Set defaults */          if(debuglevel)
         if(!conf.tag_font)      conf.tag_font = gdFontTiny;  
         if(!conf.rev_font)      conf.rev_font = gdFontTiny;  
         if(!conf.branch_font)   conf.branch_font = gdFontTiny;  
         if(!conf.title_font)    conf.title_font = gdFontTiny;  
   
         if(!read_config(confpath))  
2123          {          {
2124                  fprintf(stderr, "Error reading config file\n");                  setvbuf(stdout, NULL, 0, _IONBF);
2125                  return 1;                  setvbuf(stderr, NULL, 0, _IONBF);
2126          }          }
2127            rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
2128            rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
2129    
2130            /* Set defaults */
2131            conf.tag_font.gdfont            = gdFontTiny;
2132            conf.rev_font.gdfont            = gdFontTiny;
2133            conf.branch_font.gdfont         = gdFontTiny;
2134            conf.branch_tag_font.gdfont     = gdFontTiny;
2135            conf.title_font.gdfont          = gdFontTiny;
2136            conf.rev_text_font.gdfont       = gdFontTiny;
2137    
2138            conf.anti_alias         = 1;
2139            conf.thick_lines        = 1;
2140    
2141            conf.cvsroot            = xstrdup("");
2142            conf.cvsmodule          = xstrdup("");
2143            conf.date_format        = xstrdup("%d-%b-%Y %H:%M:%S");
2144            conf.title              = xstrdup("");
2145            conf.map_name           = xstrdup("CvsGraphImageMap");
2146            conf.map_branch_href    = xstrdup("href=\"unset: conf.map_branch_href\"");
2147            conf.map_branch_alt     = xstrdup("alt=\"%B\"");
2148            conf.map_rev_href       = xstrdup("href=\"unset: conf.map_rev_href\"");
2149            conf.map_rev_alt        = xstrdup("alt=\"%R\"");
2150            conf.map_diff_href      = xstrdup("href=\"unset: conf.map_diff_href\"");
2151            conf.map_diff_alt       = xstrdup("alt=\"%P &lt;-&gt; %R\"");
2152            conf.rev_text           = xstrdup("%d");
2153    
2154            conf.color_bg           = white_color;
2155            conf.branch_bgcolor     = white_color;
2156            conf.branch_color       = black_color;
2157            conf.branch_tag_color   = black_color;
2158            conf.rev_color          = black_color;
2159            conf.rev_bgcolor        = white_color;
2160            conf.tag_color          = black_color;
2161            conf.title_color        = black_color;
2162            conf.rev_text_color     = black_color;
2163    
2164            conf.image_quality      = 100;
2165            conf.rev_maxline        = -1;   /* Checked later to set to default */
2166    
2167            read_config(confpath);
2168    
2169            if(conf.rev_maxline == -1)      conf.rev_maxline = 5 * conf.rev_minline;
2170    
2171          /* Set overrides */          /* Set overrides */
2172          if(cvsroot)             conf.cvsroot = cvsroot;          if(cvsroot)     conf.cvsroot = cvsroot;
2173          if(cvsmodule)           conf.cvsmodule = cvsmodule;          if(cvsmodule)   conf.cvsmodule = cvsmodule;
2174            if(imgmapname)  conf.map_name = imgmapname;
2175            if(upsidedown)  conf.upside_down = !conf.upside_down;
2176            if(bdupbox)     conf.branch_dupbox = !conf.branch_dupbox;
2177            if(stripuntag)  conf.strip_untagged = !conf.strip_untagged;
2178            if(stripfirst)  conf.strip_first_rev = !conf.strip_first_rev;
2179            if(autostretch) conf.auto_stretch = !conf.auto_stretch;
2180    
2181            if(conf.rev_minline >= conf.rev_maxline)
2182            {
2183                    if(conf.auto_stretch && !quiet)
2184                            fprintf(stderr, "Auto stretch is only possible if rev_minline < rev_maxline\n");
2185                    conf.auto_stretch = 0;
2186            }
2187    
2188            if(conf.thick_lines < 1)
2189                    conf.thick_lines = 1;
2190            if(conf.thick_lines > 11)
2191                    conf.thick_lines = 11;
2192    
2193          if((fp = get_log(conf.cvsroot, conf.cvsmodule, argv[optind])) == NULL)          append_slash(&conf.cvsroot);
2194            append_slash(&conf.cvsmodule);
2195    
2196            if(optind >= argc)
2197          {          {
2198                  fprintf(stderr, "Error getting log for '%s'\n", argv[optind]);  #ifdef __WIN32__
2199                  return 1;                  /* Bad hack for DOS/Windows */
2200                    if(setmode(fileno(stdin), O_BINARY) == -1)
2201                    {
2202                            perror("Set binary mode for stdin");
2203                            return 1;
2204                    }
2205    #endif
2206                    rcsfilename = NULL;
2207          }          }
2208            else
2209                    rcsfilename = argv[optind];
2210    
2211          rcs = parse_log(fp);          rcs = get_rcsfile(conf.cvsroot, conf.cvsmodule, rcsfilename);
2212          if(!rcs)          if(!rcs)
         {  
                 fprintf(stderr, "Error parsing log\n");  
2213                  return 1;                  return 1;
         }  
         fclose(fp);  
2214    
2215          /* Add implicit tags */          if(debuglevel & DEBUG_RCS_FILE)
2216          add_tag(rcs, "HEAD", rcs->head->rev);                  dump_rcsfile(rcs);
         add_tag(rcs, "MAIN", "1");  
2217    
2218          /* We now have the log. Sort and reorganize a little */          if(!reorganise_branches(rcs))
2219          qsort(rcs->tags, rcs->ntags, sizeof(rcs->tags[0]), tag_sort);                  return 1;
         qsort(rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_sort);  
2220    
2221          /* Assign tags to revisions */          if(!assign_tags(rcs))
2222          for(n = 0; n < rcs->ntags; n++)                  return 1;
         {  
                 revision_t *r = find_revision(rcs, rcs->tags[n]->rev);  
                 if(!r)  
                         continue;  
                 r->tags = xrealloc(r->tags, (r->ntags+1) * sizeof(r->tags[0]));  
                 r->tags[r->ntags] = rcs->tags[n];  
                 r->ntags++;  
         }  
2223    
2224          /* Isolate the branches */          if(outfile)
         for(n = 0; n < rcs->nrevs; n++)  
2225          {          {
2226                  branch_t *b = find_branch(rcs, rcs->revs[n]->rev->branch);                  if((fp = fopen(outfile, "wb")) == NULL)
                 if(!b)  
2227                  {                  {
2228                          rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1) * sizeof(rcs->branches[0]));                          perror(outfile);
2229                          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);  
2230                  }                  }
                 b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));  
                 b->revs[b->nrevs] = rcs->revs[n];  
                 b->nrevs++;  
2231          }          }
2232            else
         /* Find the branchpoints */  
         for(n = 0; n < rcs->nbranches; n++)  
2233          {          {
2234                  char *prev = xstrdup(rcs->branches[n]->branch);                  fp = stdout;
2235                  char *cptr = strrchr(prev, '.');  #ifdef __WIN32__
2236                  revision_t *r;                  /* Bad hack for DOS/Windows */
2237                  revid_t rid;                  if(setmode(fileno(fp), O_BINARY) == -1)
                 if(!cptr)       /* Main branch number */  
                         continue;  
                 *cptr = '\0';  
                 rid.isbranch = 0;  
                 rid.branch = "";  
                 rid.rev = prev;  
                 r = find_revision(rcs, &rid);  
                 if(!r)  
2238                  {                  {
2239                          /* Hm, disjoint branch... */                          perror("Set binary mode for stdout");
2240                          fprintf(stderr, "Hm, don't know how to handle disjoint branches (%s)\n", prev);                          return 1;
2241                          assert(r != NULL);                  }
2242                  }  #endif
                 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++;  
2243          }          }
2244    
2245          make_layout(rcs);          make_layout(rcs);
2246    
2247  #ifdef DEBUG          if(!imagemap)
2248          dump_log(rcs);          {
2249                    /* Create an image */
2250                    im = make_image(rcs);
2251    
2252                    switch(conf.image_type)
2253                    {
2254    #ifdef HAVE_IMAGE_GIF
2255    # ifndef HAVE_IMAGE_PNG
2256                    default:
2257    # endif
2258                    case IMAGE_GIF:
2259                            gdImageGif(im, fp);
2260                            break;
2261  #endif  #endif
2262          im = make_image(rcs);  #ifdef HAVE_IMAGE_PNG
2263          if(outfile)                  default:
2264                    case IMAGE_PNG:
2265                            gdImagePng(im, fp);
2266                            break;
2267    #endif
2268    #ifdef HAVE_IMAGE_JPEG
2269    # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
2270                    default:
2271    # endif
2272                    case IMAGE_JPEG:
2273                            gdImageJpeg(im, fp, conf.image_quality);
2274                            break;
2275    #endif
2276                    }
2277    
2278                    gdImageDestroy(im);
2279            }
2280            else
2281          {          {
2282                  if((fp = fopen(outfile, "w")) == NULL)                  /* Create an imagemap */
2283                    make_imagemap(rcs, fp);
2284            }
2285    
2286            /* Also create imagemap to file if requested */
2287            if(imgmapfile)
2288            {
2289                    FILE *ifp = fopen(imgmapfile, "wb");
2290                    if(!ifp)
2291                  {                  {
2292                          perror(outfile);                          perror(imgmapfile);
2293                          return 1;                          return 1;
2294                  }                  }
2295                    make_imagemap(rcs, ifp);
2296                    fclose(ifp);
2297          }          }
2298          else  
                 fp = stdout;  
         GD_IMAGE_XXX(im, fp);  
2299          if(outfile)          if(outfile)
2300                  fclose(fp);                  fclose(fp);
2301          gdImageDestroy(im);  
2302          return 0;          return 0;
2303  }  }
2304    

Legend:
Removed from v.1.4  
changed lines
  Added in v.1.21

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0