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

Diff of /cvsgraph/cvsgraph.c

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

revision 1.2, Tue Feb 20 22:36:38 2001 UTC revision 1.36, Thu Jul 15 15:40:37 2004 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,2003  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    #include <regex.h>
39    #include <math.h>
40    
41    #ifdef HAVE_GETOPT_H
42    # include <getopt.h>
43    #endif
44    
45  #include <gd.h>  #include <gd.h>
46  #include <gdfontt.h>  #include <gdfontt.h>
47    
48  #include "cvsgraph.h"  #include "cvsgraph.h"
49  #include "utils.h"  #include "utils.h"
50  #include "readconf.h"  #include "readconf.h"
51    #include "rcs.h"
52    
53  /*#define DEBUG         1*/  #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
54    # error No image output format available. Check libgd
55    #endif
56    
 #define RLOGCMD         "/usr/bin/rlog"  
 #define DEVNULL         "/dev/null"  
 #define CONFFILENAME    "cvsgraph.conf"  
57    
58  #ifndef ETCDIR  /*#define DEBUG         1*/
59  # define ETCDIR         "/usr/local/etc"  /*#define NOGDFILL      1*/
60  #endif  /*#define DEBUG_IMAGEMAP        1*/
61    
62    #define LOOPSAFEGUARD   10000   /* Max itterations in possible infinite loops */
63    
64  #ifndef MAX  #ifndef MAX
65  # define MAX(a,b)       ((a) > (b) ? (a) : (b))  # define MAX(a,b)       ((a) > (b) ? (a) : (b))
# Line 97  Line 72 
72  #define ALIGN_HL        0x00  #define ALIGN_HL        0x00
73  #define ALIGN_HC        0x01  #define ALIGN_HC        0x01
74  #define ALIGN_HR        0x02  #define ALIGN_HR        0x02
75  #define ALIGN_HX        0x0f  #define ALIGN_HX        0x0f
76  #define ALIGN_VT        0x00  #define ALIGN_VT        0x00
77  #define ALIGN_VC        0x10  #define ALIGN_VC        0x10
78  #define ALIGN_VB        0x20  #define ALIGN_VB        0x20
79  #define ALIGN_VX        0xf0  #define ALIGN_VX        0xf0
80    
81  typedef struct __revid_t  #ifndef M_PI    /* math.h should have defined this */
82  {  # define M_PI 3.14159265358979323846
83          char    *branch;  #endif
84          char    *rev;  #define ROUND(f)        ((f >= 0.0)?((int)(f + 0.5)):((int)(f - 0.5)))
85          int     isbranch;  
86  } revid_t;  #define ARROW_LENGTH    12      /* Default arrow dimensions */
87    #define ARROW_WIDTH     3
 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;  
88    
89  /*  /*
90   **************************************************************************   **************************************************************************
# Line 169  Line 92 
92   **************************************************************************   **************************************************************************
93   */   */
94    
 char *rlogcmd = RLOGCMD;  
 char *devnull = DEVNULL;  
   
95  config_t conf;  config_t conf;
96    int debuglevel;
97    color_t white_color = {255, 255, 255, 0};
98    color_t black_color = {0, 0, 0, 0};
99    
100    /*
101     **************************************************************************
102     * Forwards
103     **************************************************************************
104     */
105    static void zap_string(void);
106    static char *dup_string(void);
107    static void add_string_str(const char *s);
108    static void add_string_ch(int ch);
109    static void add_string_date(const char *d);
110    static void add_string_str_html(const char *s, int maxlen);
111    static void add_string_str_len(const char *s, int maxlen);
112    
113  /*  /*
114   **************************************************************************   **************************************************************************
115   * Debug routines   * Debug routines
116   **************************************************************************   **************************************************************************
117   */   */
118  #ifdef DEBUG  static void dump_rev(char *p, rev_t *r)
 void dump_revid(const char *s, revid_t *r)  
119  {  {
120          fprintf(stderr, "%s.branch  : '%s'\n", s, r->branch);          printf("%s", p);
121          fprintf(stderr, "%s.rev     : '%s'\n", s, r->rev);          if(r)
122          fprintf(stderr, "%s.isbranch: %d\n", s, r->isbranch);                  printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
123            else
124                    printf("<null>\n");
125  }  }
126    
127  void dump_tag(const char *s, tag_t *t)  static void dump_id(char *p, char *d)
128  {  {
129          fprintf(stderr, "%s", s);          printf("%s", p);
130          dump_revid(t->tag, t->rev);          if(d)
131                    printf("'%s'\n", d);
132            else
133                    printf("<null>\n");
134  }  }
135    
136  void dump_rev(revision_t *r)  static void dump_idrev(char *p, idrev_t *t)
137  {  {
138          int i;          printf("%s", p);
139          dump_revid("Revision", r->rev);          if(t)
140          fprintf(stderr, "Revision.Info   : '%s'\n", r->info);          {
141          fprintf(stderr, "Revision.Comment: '%s'\n", r->comment);                  printf("'%s' -> ", t->id);
142          for(i = 0; i < r->ntags; i++)                  dump_rev("", t->rev);
143                  dump_tag("Revision.Tag: ", r->tags[i]);          }
144            else
145                    printf("<null>\n");
146    }
147    
148    static void dump_tag(char *p, tag_t *t)
149    {
150            printf("%s", p);
151            if(t)
152            {
153                    printf("'%s' -> ", t->tag);
154                    dump_rev("", t->rev);
155            }
156            else
157                    printf("<null>\n");
158  }  }
159    
160  void dump_branch(branch_t *b)  static void dump_delta(char *p, delta_t *d)
161  {  {
162          int i;          int i;
163          fprintf(stderr, "Branch: '%s'\n", b->branch);          printf("%sdelta.rev   : ", p);
164          if(b->tag)          dump_rev("", d->rev);
165                  dump_tag("branchtag:", b->tag);          printf("%sdelta.date  : %s\n", p, d->date);
166          for(i = 0; i < b->nrevs; i++)          printf("%sdelta.author: %s\n", p, d->author);
167                  fprintf(stderr, "Branch.Rev: '%s'\n", b->revs[i]->rev->rev);          printf("%sdelta.state : %s\n", p, d->state);
168            for(i = 0; d->branches && i < d->branches->nrevs; i++)
169            {
170                    printf("%sdelta.branch: ", p);
171                    dump_rev("", d->branches->revs[i]);
172            }
173            printf("%sdelta.next  : ", p);
174            dump_rev("", d->next);
175            printf("\n");
176    }
177    
178    static void dump_dtext(char *p, dtext_t *d)
179    {
180            printf("%sdtext.rev  : ", p);
181            dump_rev("", d->rev);
182            printf("%sdtext.log  : %d bytes\n", p, d->log ? strlen(d->log) : -1);
183            printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
184            printf("\n");
185  }  }
186    
187  void dump_log(rcsfilelog_t *r)  static void dump_rcsfile(rcsfile_t *rcs)
188  {  {
189          int i;          int i;
190            printf("root   : '%s'\n", rcs->root);
191            printf("module : '%s'\n", rcs->module);
192            printf("file   : '%s'\n", rcs->file);
193            dump_rev("head   : ", rcs->head);
194            dump_rev("branch : ", rcs->branch);
195            printf("access :\n");
196            for(i = 0; rcs->access && i < rcs->access->nids; i++)
197                    dump_id("\t", rcs->access->ids[i]);
198            printf("tags   :\n");
199            for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
200                    dump_tag("\t", rcs->tags->tags[i]);
201            printf("locks  :%s\n", rcs->strict ? " (strict)" : "");
202            for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
203                    dump_idrev("\t", rcs->locks->idrevs[i]);
204            printf("comment: '%s'\n", rcs->comment);
205            printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
206            printf("deltas :\n");
207            for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
208                    dump_delta("\t", rcs->deltas->deltas[i]);
209            printf("desc   : '%s'\n", rcs->desc);
210            printf("dtexts :\n");
211            for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
212                    dump_dtext("\t", rcs->dtexts->dtexts[i]);
213    
214          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]);  
215  }  }
 #endif  
216    
217  /*  /*
218   **************************************************************************   **************************************************************************
219   * Retrieve the log entries   * Read the rcs file
220   **************************************************************************   **************************************************************************
221   */   */
222  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)
223  {  {
         pid_t pid;  
         int nul;  
         FILE *tmp;  
224          char *cmd = NULL;          char *cmd = NULL;
225          int status;          int rv;
         mode_t um;  
   
         if((nul = open(devnull, O_RDWR, S_IRUSR|S_IWUSR)) == -1)  
                 return NULL;  
   
         um = umask(0177);       /* Set tempfiles to max 0600 permissions */  
         if((tmp = tmpfile()) == NULL)  
         {  
                 close(nul);  
                 return NULL;  
         }  
         umask(um);  
   
         cmd = xmalloc(strlen(cvsroot) + + strlen(module) + strlen(file) + 2 + 1);  
         sprintf(cmd, "%s/%s/%s", cvsroot, module, file);  
   
         switch(pid = fork())  
         {  
         case -1:        /* Error */  
                 close(nul);  
                 fclose(tmp);  
                 xfree(cmd);  
                 return NULL;  
         case 0:         /* Child */  
                 if((dup2(nul, STDIN_FILENO)) == -1)     exit(126);  
                 if((dup2(fileno(tmp), STDOUT_FILENO)) == -1)    exit(126);  
                 if((dup2(nul, STDERR_FILENO)) == -1)    exit(126);  
                 close(nul);  
                 fclose(tmp);  
                 execl(rlogcmd, rlogcmd, cmd, NULL);  
                 exit(127);  
                 break;  
         default:        /* Parent */  
                 close(nul);  
                 xfree(cmd);  
                 while(1)  
                 {  
                         if(waitpid(pid, &status, 0) == -1)  
                         {  
                                 if(errno != EINTR)  
                                 {  
                                         fclose(tmp);  
                                         return NULL;  
                                 }  
                         }  
                         else  
                                 break;  
                 }  
                 break;  
         }  
226    
227          if(WIFEXITED(status) && WEXITSTATUS(status) == 0)          if(file)
228          {          {
229                  if(fseek(tmp, 0, SEEK_SET) != (off_t)-1)                  cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 1);
230                    sprintf(cmd, "%s%s%s", cvsroot, module, file);
231                    if(!(rcsin = fopen(cmd, "rb")))
232                  {                  {
233                          return tmp;                          perror(cmd);
                 }  
                 else  
                 {  
                         fclose(tmp);  
234                          return NULL;                          return NULL;
235                  }                  }
236                    input_file = cmd;
237          }          }
238          else          else
                 fclose(tmp);  
         return NULL;  
 }  
   
 /*  
  **************************************************************************  
  * Parse the log entries  
  **************************************************************************  
  */  
 char *strip_dup(const char *s)  
 {  
         int l = strlen(s);  
         char *c = xmalloc(l+1);  
   
         strcpy(c, s);  
         while(*c == ' ' || *c == '\t')  
         {  
                 memmove(c, c+1, l--);  
         }  
         while(l && strchr(" \t\r\n", c[l]))  
                 c[l--] = '\0';  
         return c;  
 }  
   
 revid_t *make_revid(const char *s)  
 {  
         char *c = strip_dup(s);  
         char *cptr;  
         int dots = 0;  
         revid_t *r = xmalloc(sizeof(*r));  
         for(cptr = c; *cptr; cptr++)  
         {  
                 if(*cptr == '.')  
                         dots++;  
         }  
         if(!dots)  
239          {          {
240                  r->rev = xstrdup("");                  rcsin = stdin;
241                  r->branch = xstrdup(s);                  input_file = "<stdin>";
                 r->isbranch = 1;  
242          }          }
243          else if(!*c)          line_number = 1;
244            rv = rcsparse();
245            if(file)
246          {          {
247                  r->rev = xstrdup("?.?");                  fclose(rcsin);
248                  r->branch = xstrdup("?");                  xfree(cmd);
249          }          }
250          else if(dots & 1)          if(rv)
251                    return NULL;
252            input_file = NULL;
253            if(file)
254          {          {
255                  char *t;                  rcsfile->root = xstrdup(cvsroot);
256                  r->rev = c;                  rcsfile->module = xstrdup(module);
257                  r->branch = xstrdup(c);                  rcsfile->file = xstrdup(file);
                 cptr = strrchr(r->branch, '.');  
                 assert(cptr != NULL);  
                 *cptr = '\0';  
                 t = strrchr(r->branch, '.');  
                 if((t&& !strcmp(t+1, "0")) || (!t && !strcmp(r->branch, "0")))  
                 {  
                         /* Magic branch numbers "x.x.0.x" */  
                         r->isbranch = 1;  
                         if(t)  
                                 strcpy(t+1, cptr+1);  
                         else  
                                 strcpy(r->branch, cptr+1);  
                 }  
258          }          }
259          else          else
260          {          {
261                  r->isbranch = 1;                  rcsfile->root = xstrdup("");
262                  r->branch = c;                  rcsfile->module = xstrdup("");
263                  r->rev = xmalloc(strlen(c) + 3);                  rcsfile->file = xstrdup("<stdin>");
                 strcpy(r->rev, c);  
                 strcat(r->rev, ".?");  
264          }          }
265          return r;          return rcsfile;
266  }  }
267    
268  char *add_comment(char *c, const char *n)  /*
269     **************************************************************************
270     * Sort and find helpers
271     **************************************************************************
272     */
273    int count_dots(const char *s)
274  {  {
275          int l;          int i;
276          char *r;          for(i = 0; *s; s++)
         assert(n != NULL);  
         l = strlen(n);  
         if(!c)  
         {  
                 r = xmalloc(l+1);  
                 strcpy(r, n);  
         }  
         else  
277          {          {
278                  r = xmalloc(l+strlen(c)+1+1);                  if(*s == '.')
279                  strcpy(r, c);                          i++;
                 strcat(r, "\n");  
                 strcat(r, n);  
280          }          }
281          return r;          return i;
282  }  }
283    
284  int get_line(FILE *fp, char *buf, int maxlen)  int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
285  {  {
286          int n;          int d1, d2;
287          int seennl;          char *c1, *c2;
288  retry:          char *v1, *v2;
289          seennl = 0;          char *s1, *s2;
290          if(!fgets(buf, maxlen, fp))          int retval = 0;
291                  return feof(fp) ? 0 : -1;          assert(r1 != NULL);
292          n = strlen(buf);          assert(r2 != NULL);
293          while(n && buf[n-1] == '\n')          if(bcmp)
294          {          {
295                  seennl = 1;                  assert(r1->branch != NULL);
296                  buf[--n] = '\0';                  assert(r2->branch != NULL);
297                    c1 = r1->branch;
298                    c2 = r2->branch;
299          }          }
300          if(!n)          else
                 goto retry;  
         if(!seennl)  
301          {          {
302                  while(fgetc(fp) != '\n')                  assert(r1->rev != NULL);
303                          ;                  assert(r2->rev != NULL);
304                    c1 = r1->rev;
305                    c2 = r2->rev;
306          }          }
         return n;  
 }  
   
 rcsfilelog_t *parse_log(FILE *fp)  
 {  
         rcsfilelog_t *p;  
         int state = 0;  
         regex_t rerev;  
         regex_t reinfo;  
307    
308          if(regcomp(&rerev, "^revision[ \\t]*[0-9]+(\\.[0-9]+)*", REG_EXTENDED))          d1 = count_dots(c1);
309                  return NULL;          d2 = count_dots(c2);
310          if(regcomp(&reinfo, "^date:[^;]*;[ \\t]*author:[^;]*;[ \\t]+state:", REG_EXTENDED))          if(d1 != d2)
311          {          {
312                  regfree(&rerev);                  return d1 - d2;
                 return NULL;  
313          }          }
314          p = xmalloc(sizeof(*p));  
315          while(state != 4)          s1 = v1 = xstrdup(c1);
316            s2 = v2 = xstrdup(c2);
317            while(1)
318          {          {
319                  char buf[256];                  char *vc1 = strchr(s1, '.');
320                  int n;                  char *vc2 = strchr(s2, '.');
321                  n = get_line(fp, buf, sizeof(buf));                  if(vc1 && vc2)
322                  if(!n)                          *vc1 = *vc2 = '\0';
323                          break;                  if(*s1 && *s2)
                 if(n == -1)  
                 {  
                         perror("tempfile read");  
                         xfree(p);  
                         regfree(&rerev);  
                         regfree(&reinfo);  
                         return NULL;  
                 }  
                 switch(state)  
324                  {                  {
325                  case 0: /* Prologue */                          d1 = atoi(s1);
326  more_prologue:                          d2 = atoi(s2);
327                          if(!strncmp(buf, "RCS file:", 9))                          if(d1 != d2)
                         {  
                                 p->path = strip_dup(buf+9);  
                         }  
                         else if(!strncmp(buf, "Working file:", 13))  
                         {  
                                 p->name = strip_dup(buf+13);  
                         }  
                         else if(!strncmp(buf, "head:", 5))  
328                          {                          {
329                                  p->head = make_revid(buf+5);                                  retval = d1 - d2;
                         }  
                         else if(!strncmp(buf, "branch:", 7))  
                         {  
                                 p->branch = strip_dup(buf+7);  
                         }  
                         else if(!strncmp(buf, "locks:", 6))  
                         {  
                                 p->locks = strip_dup(buf+6);  
                         }  
                         else if(!strncmp(buf, "access list:", 12))  
                         {  
                                 p->access = strip_dup(buf+12);  
                         }  
                         else if(!strncmp(buf, "keyword substitution:", 21))  
                         {  
                                 p->keyword = strip_dup(buf+21);  
                         }  
                         else if(!strncmp(buf, "total revisions:", 16))  
                         {  
                                 p->totalrevs = strip_dup(buf+16);  
                         }  
                         else if(!strncmp(buf, "description:", 12))  
                         {  
                                 state = 2;  
                         }  
                         else if(!strncmp(buf, "symbolic names:", 15))  
                         {  
                                 state = 1;  
                         }  
                         else  
                         {  
                                 fprintf(stderr, "Unknown keyword(s) in line '%s' (state=%d)\n", buf, state);  
                                 xfree(p);  
                                 return NULL;  
                         }  
                         break;  
                 case 1: /* Tags */  
                         if(*buf != '\t')  
                         {  
                                 state = 0;  
                                 goto more_prologue;  
                         }  
                         else  
                         {  
                                 char *rev = strrchr(buf, ':');  
                                 tag_t *t;  
                                 if(!rev)  
                                 {  
                                         state = 2;  
                                         goto more_prologue;  
                                 }  
                                 *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;  
330                                  break;                                  break;
331                          }                          }
                         else if(!strcmp(buf, "============================================================================="))  
                         {  
                                 /* End of log */  
                                 state = 4;  
                                 break;  
                         }  
                         if(!p->nrevs)  
                                 p->comment = add_comment(p->comment, buf);  
                         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))  
                         {  
                                 revision_t *r = xmalloc(sizeof(*r));  
                                 p->revs = xrealloc(p->revs, (p->nrevs+1) * sizeof(p->revs[0]));  
                                 p->revs[p->nrevs] = r;  
                                 p->nrevs++;  
                                 r->rev = make_revid(buf+8);  
                         }  
                         else if(!regexec(&reinfo, buf, 0, NULL, 0))  
                         {  
                                 assert(p->nrevs > 0);  
                                 p->revs[p->nrevs-1]->info = strip_dup(buf);  
                         }  
                         else  
                         {  
                                 /* No match means the description/comment */  
                                 state = 2;  
                                 goto add_description;  
                         }  
                         break;  
                 default:  
                         fprintf(stderr, "Illegal state (%d) in parser\n", state);  
                         xfree(p);  
                         regfree(&rerev);  
                         regfree(&reinfo);  
                         return NULL;  
332                  }                  }
333                    if(!vc1 || !vc2)
334                            break;
335                    s1 = vc1 + 1;
336                    s2 = vc2 + 1;
337          }          }
338          regfree(&rerev);          xfree(v1);
339          regfree(&reinfo);          xfree(v2);
340          return p;          return retval;
341  }  }
342    
343  /*  /*
344   **************************************************************************   **************************************************************************
345   * Sort and find helpers   * Reorganise the rcsfile for the branches
346     *
347     * Basically, we have a list of deltas (i.e. administrative info on
348     * revisions) and a list of delta text (the actual logs and diffs).
349     * The deltas are linked through the 'next' and the 'branches' fields
350     * which describe the tree-structure of revisions.
351     * The reorganisation means that we put each delta and corresponding
352     * delta text in a revision structure and assign it to a specific
353     * branch. This is required because we want to be able to calculate
354     * the bounding boxes of each branch. The revisions expand vertically
355     * and the branches expand horizontally.
356     * The reorganisation is performed in these steps:
357     * 1 - sort deltas and delta text on revision number for quick lookup
358     * 2 - start at the denoted head revision:
359     *      * create a branch structure and add this revision
360     *      * for each 'branches' in the delta do:
361     *              - walk all 'branches' of the delta and recursively goto 2
362     *                with the denoted branch delta as new head
363     *              - backlink the newly create sub-branch to the head revision
364     *                so that we can draw them recursively
365     *      * set head to the 'next' field and goto 2 until no next is
366     *        available
367     * 3 - update the administration
368   **************************************************************************   **************************************************************************
369   */   */
370  int tag_sort(const void *t1, const void *t2)  static int sort_delta(const void *d1, const void *d2)
371    {
372            return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
373    }
374    
375    static int search_delta(const void *r, const void *d)
376  {  {
377  #define TAGPTR(t)       (*((tag_t **)t))          return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
         return strcmp(TAGPTR(t1)->rev->rev, TAGPTR(t2)->rev->rev);  
 #undef TAGPTR  
378  }  }
379    
380  int rev_sort(const void *v1, const void *v2)  static delta_t *find_delta(delta_t **dl, int n, rev_t *r)
381  {  {
382  #define REVPTR(t)       (*((revision_t **)t))          delta_t **d;
383          return strcmp(REVPTR(v1)->rev->rev, REVPTR(v2)->rev->rev);          if(!n)
384  #undef REVPTR                  return NULL;
385            d = bsearch(r, dl, n, sizeof(*dl), search_delta);
386            if(!d)
387                    return NULL;
388            return *d;
389  }  }
390    
391  int branch_sort(const void *b1, const void *b2)  static int sort_dtext(const void *d1, const void *d2)
392  {  {
393  #define BPTR(t) (*((branch_t **)t))          return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
         return strcmp(BPTR(b1)->branch, BPTR(b2)->branch);  
 #undef BPTR  
394  }  }
395    
396  int rev_cmp(const void *id, const void *v)  static int search_dtext(const void *r, const void *d)
397  {  {
398  #define REVPTR(t)       (*((revision_t **)t))          return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
         return strcmp(((revid_t *)id)->rev, REVPTR(v)->rev->rev);  
 #undef REVPTR  
399  }  }
400    
401  revision_t *find_revision(rcsfilelog_t * rcs, revid_t *id)  static dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
402  {  {
403          revision_t **r;          dtext_t **d;
404          if(id->isbranch)          if(!n)
405                  return NULL;                  return NULL;
406          r = bsearch(id, rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_cmp);          d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
407          if(!r)          if(!d)
408                  return NULL;                  return NULL;
409          else          return *d;
                 return *r;  
410  }  }
411    
412  int branch_cmp(const void *s, const void *b)  static rev_t *dup_rev(const rev_t *r)
413  {  {
414          return strcmp((const char *)s, (*((branch_t **)b))->branch);          rev_t *t = xmalloc(sizeof(*t));
415            t->rev = xstrdup(r->rev);
416            t->branch = xstrdup(r->branch);
417            t->isbranch = r->isbranch;
418            return t;
419    }
420    
421    static branch_t *new_branch(delta_t *d, dtext_t *t)
422    {
423            branch_t *b = xmalloc(sizeof(*b));
424            revision_t *r = xmalloc(sizeof(*r));
425            r->delta = d;
426            r->dtext = t;
427            r->rev = d->rev;
428            r->branch = b;
429            b->branch = dup_rev(d->rev);
430            b->branch->isbranch = 1;
431            b->nrevs = 1;
432            b->revs = xmalloc(sizeof(b->revs[0]));
433            b->revs[0] = r;
434            return b;
435    }
436    
437    static revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
438    {
439            revision_t *r = xmalloc(sizeof(*r));
440            r->delta = d;
441            r->dtext = t;
442            r->rev = d->rev;
443            r->branch = b;
444            b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
445            b->revs[b->nrevs] = r;
446            b->nrevs++;
447            return r;
448  }  }
449    
450  branch_t *find_branch(rcsfilelog_t *rcs, const char *id)  void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
451  {  {
452          branch_t **b;          branch_t *b;
453          b = bsearch(id, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), branch_cmp);          dtext_t *text;
454          if(!b)          revision_t *currev;
455                  return NULL;  
456          else          assert(head != NULL);
457                  return *b;  
458            if(head->flag)
459            {
460                    fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
461                    return;
462            }
463            head->flag++;
464            text = find_dtext(sdt, nsdt, head->rev);
465    
466            /* Create a new branch for this head */
467            b = new_branch(head, text);
468            *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
469            (*bl)[*nbl] = b;
470            (*nbl)++;
471            currev = b->revs[0];
472            while(1)
473            {
474                    /* Process all sub-branches */
475                    if(head->branches)
476                    {
477                            int i;
478                            for(i = 0; i < head->branches->nrevs; i++)
479                            {
480                                    delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
481                                    int btag = *nbl;
482                                    if(!d)
483                                            continue;
484                                    build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
485    
486                                    /* Set the new branch's origin */
487                                    (*bl)[btag]->branchpoint = currev;
488    
489                                    /* Add branch to this revision */
490                                    currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
491                                    currev->branches[currev->nbranches] = (*bl)[btag];
492                                    currev->nbranches++;
493                            }
494                    }
495    
496                    /* Walk through the next list */
497                    if(!head->next)
498                            return;
499    
500                    head = find_delta(sdl, nsdl, head->next);
501                    if(!head)
502                    {
503                            fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);
504                            return;
505                    }
506                    if(head->flag)
507                    {
508                            fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);
509                            return;
510                    }
511                    head->flag++;
512                    text = find_dtext(sdt, nsdt, head->rev);
513                    currev = add_to_branch(b, head, text);
514            }
515  }  }
516    
517  tag_t *find_branchtag(rcsfilelog_t * rcs, const char *id)  int reorganise_branches(rcsfile_t *rcs)
518  {  {
519            delta_t **sdelta;
520            int nsdelta;
521            dtext_t **sdtext;
522            int nsdtext;
523            delta_t *head;
524            branch_t **bl;
525            int nbl;
526          int i;          int i;
527          for(i = 0; i < rcs->ntags; i++)  
528            assert(rcs->deltas != NULL);
529            assert(rcs->head != NULL);
530    
531            /* Make a new list for quick lookup */
532            nsdelta = rcs->deltas->ndeltas;
533            sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
534            memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
535            qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
536    
537            /* Do the same for the delta text */
538            if(rcs->dtexts)
539            {
540                    nsdtext = rcs->dtexts->ndtexts;
541                    sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
542                    memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
543                    qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
544            }
545            else
546          {          {
547                  if(!rcs->tags[i]->rev->isbranch)                  nsdtext = 0;
548                          continue;                  sdtext = NULL;
549                  if(!strcmp(id, rcs->tags[i]->rev->branch))          }
550                          return rcs->tags[i];  
551            /* Start from the head of the trunk */
552            head = find_delta(sdelta, nsdelta, rcs->head);
553            if(!head)
554            {
555                    fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
556                    return 0;
557          }          }
558          return NULL;          bl = NULL;
559            nbl = 0;
560            build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
561    
562            /* Reverse the head branch */
563            for(i = 0; i < bl[0]->nrevs/2; i++)
564            {
565                    revision_t *r;
566                    r = bl[0]->revs[i];
567                    bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
568                    bl[0]->revs[bl[0]->nrevs-i-1] = r;
569            }
570    
571            /* Update the branch-number of the head because it was reversed */
572            xfree(bl[0]->branch->branch);
573            bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
574    
575            /* Keep the admin */
576            rcs->branches = bl;
577            rcs->nbranches = nbl;
578            rcs->sdelta = sdelta;
579            rcs->nsdelta = nsdelta;
580            rcs->sdtext = sdtext;
581            rcs->nsdtext = nsdtext;
582            rcs->active = bl[0];
583            return 1;
584  }  }
585    
586  /*  /*
587   **************************************************************************   **************************************************************************
588   * Drawing routines   * Assign the symbolic tags to the revisions and branches
589     *
590     * The tags point to revision numbers somewhere in the tree structure
591     * of branches and revisions. First we make a sorted list of all
592     * revisions and then we assign each tag to the proper revision.
593   **************************************************************************   **************************************************************************
594   */   */
595  int get_swidth(const char *s, font_t *f)  static int sort_revision(const void *r1, const void *r2)
596  {  {
597          if(!s)          return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
                 return 0;  
         return strlen(s) * (*f)->w;  
598  }  }
599    
600  int get_sheight(const char *s, font_t *f)  static int search_revision(const void *t, const void *r)
601  {  {
602          int nl;          return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
         if(!s)  
                 return 0;  
         for(nl = 1; *s; s++)  
         {  
                 if(*s == '\n' && s[1])  
                         nl++;  
         }  
         return nl * (*f)->h;  
603  }  }
604    
605  void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color)  static int sort_branch(const void *b1, const void *b2)
606  {  {
607          int r2 = 2*r;          return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
         gdImageLine(im, x1+r, y1, x2-r, y1, color->id);  
         gdImageLine(im, x1+r, y2, x2-r, y2, color->id);  
         gdImageLine(im, x1, y1+r, x1, y2-r, color->id);  
         gdImageLine(im, x2, y1+r, x2, y2-r, color->id);  
         if(r)  
         {  
                 gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);  
                 gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);  
                 gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);  
                 gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);  
         }  
608  }  }
609    
610  void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)  static int search_branch(const void *t, const void *b)
611  {  {
612          int xx, yy;          return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
613          switch(align & ALIGN_HX)  }
614    
615    static char *previous_rev(const char *c)
616    {
617            int dots = count_dots(c);
618            char *cptr;
619            char *r;
620            if(!dots)
621          {          {
622          default:                  fprintf(stderr, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
623          case ALIGN_HL: xx = 0; break;                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */
         case ALIGN_HC: xx = -get_swidth(s, f)/2; break;  
         case ALIGN_HR: xx = -get_swidth(s, f); break;  
624          }          }
625          switch(align & ALIGN_VX)          if(dots & 1)
626          {          {
627          default:                  /* Is is a revision we want the parent of */
628          case ALIGN_VT: yy = 0; break;                  r = xstrdup(c);
629          case ALIGN_VC: yy = -get_sheight(s, f)/2; break;                  cptr = strrchr(r, '.');
630          case ALIGN_VB: yy = -get_sheight(s, f); break;                  assert(cptr != NULL);
631                    if(dots == 1)
632                    {
633                            fprintf(stderr, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
634                            /* FIXME: What is the parent of 1.1? */
635                            cptr[1] = '\0';
636                            strcat(r, "0");
637                            return r;
638                    }
639                    /* Here we have a "x.x[.x.x]+" case */
640                    *cptr = '\0';
641                    cptr = strrchr(r, '.');
642                    assert(cptr != NULL);
643                    *cptr = '\0';
644                    return r;
645          }          }
646          gdImageString(im, *f, x+xx+1, y+yy, s, c->id);          /* It is a branch we want the parent of */
647            r = xstrdup(c);
648            cptr = strrchr(r, '.');
649            assert(cptr != NULL);
650            *cptr = '\0';
651            return r;
652  }  }
653    
654  void draw_rev(gdImagePtr im, int cx, int ty, revision_t *r)  static char *build_regex(size_t n, regmatch_t *m, const char *ms)
655  {  {
656          int lx = cx - r->w/2;          char *cptr;
         int rx = lx + r->w;  
657          int i;          int i;
658          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color);  
659          ty += conf.rev_tspace;          if(!conf.merge_to || !conf.merge_to[0])
660          draw_string(im, r->rev->rev, &conf.rev_font, cx, ty, ALIGN_HC, &conf.rev_color);                  return NULL;
661          ty += get_sheight(r->rev->rev, &conf.rev_font);  
662          for(i = 0; i < r->ntags; i++)          zap_string();
663            for(cptr = conf.merge_to; *cptr; cptr++)
664          {          {
665                  draw_string(im, r->tags[i]->tag, &conf.tag_font, cx, ty, ALIGN_HC, &conf.tag_color);                  if(*cptr == '%')
666                  ty += get_sheight(r->tags[i]->tag, &conf.tag_font);                  {
667                            if(cptr[1] >= '1' && cptr[1] <= '9')
668                            {
669                                    int idx = cptr[1] - '0';
670                                    regmatch_t *p = &m[idx];
671                                    if(idx < n && !(p->rm_so == -1 || p->rm_so >= p->rm_eo))
672                                    {
673                                            for(i = p->rm_so; i < p->rm_eo; i++)
674                                            {
675                                                    if(strchr("^$.*+\\[{()", ms[i]))
676                                                            add_string_ch('\\');
677                                                    add_string_ch(ms[i]);
678                                            }
679                                    }
680                                    cptr++;
681                            }
682                            else
683                                    add_string_ch('%');
684                    }
685                    else
686                            add_string_ch(*cptr);
687          }          }
688            return dup_string();
689  }  }
690    
691  void draw_branch(gdImagePtr im, int cx, int ty, branch_t *b)  static void find_merges(rcsfile_t *rcs)
692  {  {
         int lx = cx - b->w/2;  
         int rx = lx + b->w;  
         int yy;  
693          int i;          int i;
694          draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color);          int err;
695          yy = conf.branch_tspace;          int rcflags = REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0);
696          draw_string(im, b->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);          regex_t *refrom = NULL;
697          yy += get_sheight(b->branch, &conf.branch_font);          regex_t *reto = NULL;
698          if(b->tag)          regmatch_t *matchfrom = NULL;
699    
700            if(!conf.merge_from || !conf.merge_from[0] || !conf.merge_to || !conf.merge_to[0])
701                    return;
702    
703            refrom = xmalloc(sizeof(*refrom));
704            reto = xmalloc(sizeof(*reto));
705    
706            /* Compile the 'from' regex match for merge identification */
707            err = regcomp(refrom, conf.merge_from, rcflags);
708            if(err)
709          {          {
710                  draw_string(im, b->tag->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);                  if(!quiet)
711                    {
712                            char *msg;
713                            i = regerror(err, refrom, NULL, 0);
714                            msg = xmalloc(i+1);
715                            regerror(err, refrom, msg, i+1);
716                            fprintf(stderr, "%s\n", msg);
717                            xfree(msg);
718                    }
719                    xfree(refrom);
720                    xfree(reto);
721                    return;
722          }          }
723            else
724                    matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
725    
726          ty += b->h;          for(i = 0; i < rcs->tags->ntags; i++)
727          for(i = 0; i < b->nrevs; i++)          {
728                    tag_t *t = rcs->tags->tags[i];
729    
730                    /* Must be revision tags and not detached */
731                    if(t->rev->isbranch || !t->logrev)
732                            continue;
733    
734                    /* Try to find merge tag matches */
735                    if(!regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
736                    {
737                            int n;
738                            char *to;
739    
740                            to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);
741                            if(to)
742                            {
743                                    err = regcomp(reto, to, rcflags);
744                                    if(err && !quiet)
745                                    {
746                                            char *msg;
747                                            i = regerror(err, reto, NULL, 0);
748                                            msg = xmalloc(i+1);
749                                            regerror(err, reto, msg, i+1);
750                                            fprintf(stderr, "%s\n", msg);
751                                    }
752                                    else if(!err)
753                                    {
754                                            for(n = 0; n < rcs->tags->ntags; n++)
755                                            {
756                                                    tag_t *nt = rcs->tags->tags[n];
757                                                    /* From and To never should match the same tag or belong to a branch */
758                                                    if(n == i || nt->rev->isbranch || !nt->logrev)
759                                                            continue;
760    
761                                                    if(!regexec(reto, nt->tag, 0, NULL, 0))
762                                                    {
763                                                            /* Tag matches */
764                                                            rcs->merges = xrealloc(rcs->merges,
765                                                                            sizeof(rcs->merges[0]) * (rcs->nmerges+1));
766                                                            rcs->merges[rcs->nmerges].to = nt;
767                                                            rcs->merges[rcs->nmerges].from = t;
768                                                            rcs->nmerges++;
769                                                            /* We cannot (should not) match multiple times */
770                                                            break;
771                                                    }
772                                            }
773                                            regfree(reto);
774                                    }
775                                    xfree(to);
776                            }
777                    }
778            }
779            if(matchfrom)   xfree(matchfrom);
780            if(refrom)      { regfree(refrom); xfree(refrom); }
781            if(reto)        xfree(reto);
782    }
783    
784    static void assign_tags(rcsfile_t *rcs)
785    {
786            int i;
787            int nr;
788    
789            for(i = nr = 0; i < rcs->nbranches; i++)
790                    nr += rcs->branches[i]->nrevs;
791    
792            rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
793            rcs->nsrev = nr;
794            for(i = nr = 0; i < rcs->nbranches; i++)
795            {
796                    memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
797                    nr += rcs->branches[i]->nrevs;
798            }
799    
800            qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
801            qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
802    
803            if(!rcs->branch)
804            {
805                    /* The main trunk is the active trunk */
806                    rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
807                    rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
808                    rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
809                    rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
810                    rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
811                    rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
812                    rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
813                    rcs->tags->ntags++;
814            }
815    
816            /* We should have at least two tags (HEAD and MAIN) */
817            assert(rcs->tags != 0);
818    
819            for(i = 0; i < rcs->tags->ntags; i++)
820          {          {
821                  gdImageLine(im, cx, ty, cx, ty+conf.rev_minline, conf.rev_color.id);                  tag_t *t = rcs->tags->tags[i];
822                  ty += conf.rev_minline;                  if(t->rev->isbranch)
823                  draw_rev(im, cx, ty, b->revs[i]);                  {
824                  ty += b->revs[i]->h;                          branch_t **b;
825    add_btag:
826                            b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
827                            if(!b)
828                            {
829                                    rev_t rev;
830                                    revision_t **r;
831                                    /* This happens for the magic branch numbers if there are
832                                     * no commits within the new branch yet. So, we add the
833                                     * branch and try to continue.
834                                     */
835                                    rev.rev = previous_rev(t->rev->branch);
836                                    rev.branch = NULL;
837                                    rev.isbranch = 0;
838                                    r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
839                                    xfree(rev.rev);
840                                    if(!r)
841                                    {
842                                            if(!quiet)
843                                                    fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);
844                                    }
845                                    else
846                                    {
847                                            rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
848                                            rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
849                                            rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
850                                            rcs->branches[rcs->nbranches]->branchpoint = *r;
851                                            (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
852                                            (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
853                                            (*r)->nbranches++;
854                                            rcs->nbranches++;
855                                            /* Resort the branches */
856                                            qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
857                                            goto add_btag;
858                                    }
859                            }
860                            else
861                            {
862                                    branch_t *bb = *b;
863                                    bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
864                                    bb->tags[bb->ntags] = t;
865                                    bb->ntags++;
866                            }
867                    }
868                    else
869                    {
870                            revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
871                            if(!r)
872                            {
873                                    if(!quiet)
874                                            fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
875                            }
876                            else
877                            {
878                                    revision_t *rr = *r;
879                                    t->logrev = rr;
880                                    if(!conf.rev_maxtags || rr->ntags <= conf.rev_maxtags)
881                                    {
882                                            rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
883                                            if(conf.rev_maxtags && rr->ntags == conf.rev_maxtags)
884                                            {
885                                                    rr->tags[rr->ntags] = xmalloc(sizeof(tag_t));
886                                                    rr->tags[rr->ntags]->tag = xstrdup("...");
887                                                    rr->tags[rr->ntags]->rev = t->rev;
888                                            }
889                                            else
890                                                    rr->tags[rr->ntags] = t;
891                                            rr->ntags++;
892                                    }
893                            }
894                    }
895            }
896    
897            /* We need to reset the first in the list of branches to the
898             * active branch to ensure the drawing of all
899             */
900            if(rcs->active != rcs->branches[0])
901            {
902                    branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
903                    branch_t *t;
904                    assert(b != NULL);
905                    t = *b;
906                    *b = rcs->branches[0];
907                    rcs->branches[0] = t;
908          }          }
909  }  }
910    
911  static char *_title;  /*
912  static int _ntitle;   **************************************************************************
913  static int _natitle;   * String expansion
914     **************************************************************************
915     */
916    static char *_string;
917    static int _nstring;
918    static int _nastring;
919    
920    static void zap_string(void)
921    {
922            _nstring = 0;
923            if(_string)
924                    _string[0] = '\0';
925    }
926    
927    static char *dup_string(void)
928    {
929            if(_string)
930                    return xstrdup(_string);
931            else
932                    return "";
933    }
934    
935  void add_title_str(const char *s)  static void add_string_str(const char *s)
936  {  {
937          int l = strlen(s) + 1;          int l = strlen(s) + 1;
938          if(_ntitle + l > _natitle)          if(_nstring + l > _nastring)
939          {          {
940                  _natitle += 128;                  _nastring += MAX(128, l);
941                  _title = xrealloc(_title, _natitle * sizeof(_title[0]));                  _string = xrealloc(_string, _nastring * sizeof(_string[0]));
942          }          }
943          memcpy(_title+_ntitle, s, l);          memcpy(_string+_nstring, s, l);
944          _ntitle += l-1;          _nstring += l-1;
945  }  }
946    
947  void add_title_ch(int ch)  static void add_string_ch(int ch)
948  {  {
949          char buf[2];          char buf[2];
950          buf[0] = ch;          buf[0] = ch;
951          buf[1] = '\0';          buf[1] = '\0';
952          add_title_str(buf);          add_string_str(buf);
953    }
954    
955    static void add_string_date(const char *d)
956    {
957            struct tm tm, *tmp;
958            int n;
959            time_t t;
960            char *buf;
961            int nbuf;
962    
963            memset(&tm, 0, sizeof(tm));
964            n = sscanf(d, "%d.%d.%d.%d.%d.%d",
965                            &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
966                            &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
967            tm.tm_mon--;
968            if(tm.tm_year > 1900)
969                    tm.tm_year -= 1900;
970            t = mktime(&tm);
971            if(n != 6 || t == (time_t)(-1))
972            {
973                    add_string_str("<invalid date>");
974                    return;
975            }
976    
977            tmp = localtime(&t);
978            nbuf = strlen(conf.date_format) * 16;   /* Should be enough to hold all types of expansions */
979            buf = xmalloc(nbuf);
980            strftime(buf, nbuf, conf.date_format, tmp);
981            add_string_str(buf);
982            xfree(buf);
983    }
984    
985    static void add_string_str_html(const char *s, int maxlen)
986    {
987            int l = 0;
988            char *str = xmalloc(6 * strlen(s) + 1); /* Should hold all char entity-expand */
989            char *cptr = str;
990            for(; *s; s++)
991            {
992                    if(maxlen && l > abs(maxlen))
993                    {
994                            cptr += sprintf(cptr, "...");
995                            break;
996                    }
997                    if(*s < 0x20)
998                    {
999                            if(*s == '\n')
1000                            {
1001                                    if(maxlen < 0)
1002                                            *cptr++ = ' ';
1003                                    else
1004                                            cptr += sprintf(cptr, "<br>");
1005                            }
1006                    }
1007                    else if(*s >= 0x7f || *s == '"')
1008                            cptr += sprintf(cptr, "&#%d;", (int)(unsigned char)*s);
1009                    else if(*s == '<')
1010                            cptr += sprintf(cptr, "&lt;");
1011                    else if(*s == '>')
1012                            cptr += sprintf(cptr, "&gt;");
1013                    else
1014                            *cptr++ = *s;
1015                    l++;
1016            }
1017            *cptr = '\0';
1018            add_string_str(str);
1019            xfree(str);
1020    }
1021    
1022    static void add_string_str_len(const char *s, int maxlen)
1023    {
1024            int l = strlen(s);
1025            char *str = xmalloc(l + 1 + 3);
1026            strcpy(str, s);
1027            if(maxlen < l)
1028                    sprintf(&str[maxlen], "...");
1029            add_string_str(str);
1030            xfree(str);
1031  }  }
1032    
1033  char *expand_title(rcsfilelog_t *rcs)  char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1034  {  {
1035          char nb[32];          char nb[32];
1036          char nr[32];          char nr[32];
1037          char *cptr;          char *base;
1038            char *exp;
1039            int l;
1040            char ch;
1041    
1042            if(!s)
1043                    return xstrdup("");
1044    
1045            zap_string();
1046    
1047          sprintf(nb, "%d", rcs->nbranches);          sprintf(nb, "%d", rcs->nbranches);
1048          sprintf(nr, "%d", rcs->nrevs);          sprintf(nr, "%d", rcs->nsrev);
1049          for(cptr = conf.title; *cptr; cptr++)          for(; *s; s++)
1050          {          {
1051                  if(*cptr == '%')                  if(*s == '%')
1052                  {                  {
1053                          switch(*++cptr)                          switch(*++s)
1054                          {                          {
1055                          case 'c': add_title_str(conf.cvsroot); break;                          case 'c':
1056                          case 'f': add_title_str(rcs->name); break;                          case 'C':
1057                          case 'm': add_title_str(conf.cvsmodule); break;                                  add_string_str(conf.cvsroot);
1058                          case 'r': add_title_str(nr); break;                                  if(*s == 'C' && conf.cvsroot[0] && conf.cvsroot[strlen(conf.cvsroot)-1] == '/')
1059                          case 'b': add_title_str(nb); break;                                  {
1060                          case '%': add_title_ch('%'); break;                                          /* Strip the trailing '/' */
1061                                            _nstring--;
1062                                            _string[_nstring] = '\0';
1063                                    }
1064                                    break;
1065                            case 'f':
1066                            case 'F':
1067                                    base = strrchr(rcs->file, '/');
1068                                    if(!base)
1069                                            add_string_str(rcs->file);
1070                                    else
1071                                            add_string_str(base+1);
1072                                    if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
1073                                    {
1074                                            _nstring -= 2;
1075                                            _string[_nstring] = '\0';
1076                                    }
1077                                    break;
1078                            case 'p':
1079                                    base = strrchr(rcs->file, '/');
1080                                    if(base)
1081                                    {
1082                                            char *c = xstrdup(rcs->file);
1083                                            base = strrchr(c, '/');
1084                                            assert(base != NULL);
1085                                            base[1] = '\0';
1086                                            add_string_str(c);
1087                                            xfree(c);
1088                                    }
1089                                    /*
1090                                     * We should not add anything here because we can encounter
1091                                     * a completely empty path, in which case we do not want
1092                                     * to add any slash. This prevents an inadvertent root redirect.
1093                                     *
1094                                     * else
1095                                     *      add_string_str("/");
1096                                     */
1097                                    break;
1098                            case 'm':
1099                            case 'M':
1100                                    add_string_str(conf.cvsmodule);
1101                                    if(*s == 'M' && conf.cvsmodule[0] && conf.cvsmodule[strlen(conf.cvsmodule)-1] == '/')
1102                                    {
1103                                            /* Strip the trailing '/' */
1104                                            _nstring--;
1105                                            _string[_nstring] = '\0';
1106                                    }
1107                                    break;
1108                            case 'r': add_string_str(nr); break;
1109                            case 'b': add_string_str(nb); break;
1110                            case '%': add_string_ch('%'); break;
1111                            case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
1112                            case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
1113                            case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
1114                            case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
1115                            case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
1116                            case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
1117                            case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
1118                            case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
1119                            case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
1120                            case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
1121                            case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
1122                            case 'P': if(prev && prev->rev) add_string_str(prev->rev); break;
1123                            case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
1124                            case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
1125                            case 'd': if(r && r->delta && r->delta->date) add_string_date(r->delta->date); break;
1126                            case 's': if(r && r->delta && r->delta->state) add_string_str(r->delta->state); break;
1127                            case 'a': if(r && r->delta && r->delta->author) add_string_str(r->delta->author); break;
1128                            case 'L':
1129                            case 'l':
1130                                    ch = *s;
1131                                    l = 0;
1132                                    if(s[1] == '[')
1133                                    {
1134                                            char *cptr = strchr(s, ']');
1135                                            char *eptr;
1136                                            if(cptr)
1137                                            {
1138                                                    l = strtol(&s[2], &eptr, 10);
1139                                                    if(eptr != cptr)
1140                                                            l = 0;
1141                                                    else
1142                                                            s = cptr;
1143                                            }
1144                                    }
1145                                    if(!conf.parse_logs)
1146                                            add_string_str("N/A");
1147                                    else if(r && r->dtext && r->dtext->log)
1148                                    {
1149                                            if(ch == 'l')
1150                                                    add_string_str_html(r->dtext->log, l);
1151                                            else
1152                                                    add_string_str_len(r->dtext->log, abs(l));
1153                                    }
1154                                    break;
1155                            case '(':
1156                                    base = dup_string();
1157                                    exp = expand_string(s+1, rcs, r, rev, prev, tag);
1158                                    zap_string();
1159                                    add_string_str(base);
1160                                    add_string_str_html(exp, 0);
1161                                    xfree(base);
1162                                    xfree(exp);
1163                                    /* Find the %) in this recursion level */
1164                                    for(; *s; s++)
1165                                    {
1166                                            if(*s == '%' && s[1] == ')')
1167                                            {
1168                                                    s++;
1169                                                    break;
1170                                            }
1171                                    }
1172                                    if(!*s)
1173                                    {
1174                                            s--;    /* To end outer loop */
1175                                            if(!quiet)
1176                                                    fprintf(stderr, "string expand: Missing %%) in expansion\n");
1177                                    }
1178                                    break;
1179                            case ')':
1180                                    return dup_string();
1181                          default:                          default:
1182                                  add_title_ch('%');                                  add_string_ch('%');
1183                                  add_title_ch(*cptr);                                  add_string_ch(*s);
1184                                  break;                                  break;
1185                          }                          }
1186                  }                  }
1187                  else                  else
1188                          add_title_ch(*cptr);                          add_string_ch(*s);
1189            }
1190            return dup_string();
1191    }
1192    
1193    /*
1194     **************************************************************************
1195     * Drawing routines
1196     **************************************************************************
1197     */
1198    static int get_swidth(const char *s, font_t *f)
1199    {
1200            int n;
1201            int m;
1202            if(!s || !*s)
1203                    return 0;
1204    
1205    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1206            if(conf.use_ttf && f->ttfont)
1207            {
1208                    int bb[8];
1209                    char *e;
1210    #ifdef HAVE_GDIMAGESTRINGFT
1211                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1212    #else
1213                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1214    #endif
1215                    if(!e)
1216                            return bb[2] - bb[6];
1217            }
1218    #endif
1219            for(n = m = 0; *s; n++, s++)
1220            {
1221                    if(*s == '\n')
1222                    {
1223                            if(n > m)
1224                                    m = n;
1225                            n = 0;
1226                    }
1227            }
1228            if(n > m)
1229                    m = n;
1230            return f->gdfont ? m * f->gdfont->w : m;
1231    }
1232    
1233    static int get_sheight(const char *s, font_t *f)
1234    {
1235            int nl;
1236            if(!s || !*s)
1237                    return 0;
1238    
1239    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1240            if(conf.use_ttf && f->ttfont)
1241            {
1242                    int bb[8];
1243                    char *e;
1244    #ifdef HAVE_GDIMAGESTRINGFT
1245                    e = gdImageStringFT(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1246    #else
1247                    e = gdImageStringTTF(NULL, bb, 0, f->ttfont, f->ttsize, 0.0, 0, 0, (char *)s);
1248    #endif
1249                    if(!e)
1250                            return bb[3] - bb[7] + 4;
1251            }
1252    #endif
1253            for(nl = 1; *s; s++)
1254            {
1255                    if(*s == '\n' && s[1])
1256                            nl++;
1257            }
1258            return nl * f->gdfont->h;
1259    }
1260    
1261    static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
1262    {
1263            int r2 = 2*r;
1264            gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1265            gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1266            gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
1267            gdImageLine(im, x2, y1+r, x2, y2-r, color->id);
1268            if(conf.box_shadow)
1269            {
1270                    gdImageLine(im, x1+r+1, y2+1, x2-r, y2+1, black_color.id);
1271                    gdImageLine(im, x2+1, y1+r+1, x2+1, y2-r, black_color.id);
1272            }
1273            if(r)
1274            {
1275                    /* FIXME: Pixelization is not perfect */
1276                    gdImageArc(im, x1+r, y1+r, r2, r2, 180, 270, color->id);
1277                    gdImageArc(im, x2-r, y1+r, r2, r2, 270, 360, color->id);
1278                    gdImageArc(im, x1+r, y2-r, r2, r2,  90, 180, color->id);
1279                    if(conf.box_shadow)
1280                    {
1281                            gdImageArc(im, x2-r+1, y2-r+1, r2, r2,   0,  90, black_color.id);
1282                            gdImageArc(im, x2-r+1, y2-r, r2, r2,   0,  90, black_color.id);
1283                            gdImageArc(im, x2-r, y2-r+1, r2, r2,   0,  90, black_color.id);
1284                    }
1285                    gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);
1286            }
1287    #ifndef NOGDFILL
1288            gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1289    #endif
1290    }
1291    
1292    static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1293    {
1294            int h = get_sheight(s, f);
1295            int xx, yy;
1296            switch(align & ALIGN_HX)
1297            {
1298            default:
1299            case ALIGN_HL: xx = 0; break;
1300            case ALIGN_HC: xx = -get_swidth(s, f)/2; break;
1301            case ALIGN_HR: xx = -get_swidth(s, f); break;
1302            }
1303            switch(align & ALIGN_VX)
1304            {
1305            default:
1306            case ALIGN_VT: yy = 0; break;
1307            case ALIGN_VC: yy = h/2; break;
1308            case ALIGN_VB: yy = h; break;
1309            }
1310    #if defined(HAVE_GDIMAGESTRINGFT) || defined(HAVE_GDIMAGESTRINGTTF)
1311            if(conf.use_ttf && f->ttfont)
1312            {
1313                    int bb[8];
1314                    char *e;
1315                    int cid = conf.anti_alias ? c->id : -c->id;
1316    #ifdef HAVE_GDIMAGESTRINGFT
1317                    e = gdImageStringFT(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1318    #else
1319                    e = gdImageStringTTF(im, bb, cid, f->ttfont, f->ttsize, 0.0, x+xx, y+yy+h-2, (char *)s);
1320    #endif
1321                    if(!e)
1322                            return;
1323          }          }
1324          return _title;  #endif
1325            yy = -yy;
1326            gdImageString(im, f->gdfont, x+xx+1, y+yy, s, c->id);
1327  }  }
1328    
1329  void draw_title(gdImagePtr im, char *title)  static void draw_stringnl(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
1330  {  {
1331          char *t;          char *t;
1332          char *s = title;          char *d;
1333          int x = conf.title_x;          d = s = xstrdup(s);
         int y = conf.title_y;  
1334          do          do
1335          {          {
1336                  t = strchr(s, '\n');                  t = strchr(s, '\n');
1337                  if(t)                  if(t)
1338                          *t = '\0';                          *t = '\0';
1339                  draw_string(im, s, &conf.title_font, x, y, conf.title_align, &conf.title_color);                  draw_string(im, s, f, x, y, align, c);
1340                  y += get_sheight(s, &conf.title_font);                  y += get_sheight(s, f);
1341                  s = t+1;                  s = t+1;
1342          } while(t);          } while(t);
1343            xfree(d);
1344  }  }
1345    
1346  void draw_connector(gdImagePtr im, branch_t *b)  static void draw_rev(gdImagePtr im, revision_t *r)
1347  {  {
1348          revision_t *r = b->branchpoint;          int lx;
1349          int x1 = r->x + r->w/2 + 2;          int rx;
1350          int y1 = r->y + r->h/2;          int x2;
1351          int x2 = b->x;          int i;
1352          int y2 = b->y;          int ty;
1353          gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);  
1354          gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);          if(conf.left_right)
1355            {
1356                    lx = r->cx;
1357                    rx = r->cx + r->w;
1358                    ty = r->y - r->h/2;
1359                    x2 = r->cx + r->w/2;
1360            }
1361            else
1362            {
1363                    lx = r->cx - r->w/2;
1364                    rx = lx + r->w;
1365                    ty = r->y;
1366                    x2 = r->cx;
1367            }
1368            draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
1369            ty += conf.rev_tspace;
1370            draw_string(im, r->rev->rev, &conf.rev_font, x2, ty, ALIGN_HC, &conf.rev_color);
1371            ty += get_sheight(r->rev->rev, &conf.rev_font);
1372            draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, &conf.rev_text_color);
1373            ty += get_sheight(r->revtext, &conf.rev_text_font);
1374            for(i = 0; i < r->ntags; i++)
1375            {
1376                    draw_string(im, r->tags[i]->tag, &conf.tag_font, x2, ty, ALIGN_HC, &conf.tag_color);
1377                    ty += get_sheight(r->tags[i]->tag, &conf.tag_font) + conf.rev_separator;
1378            }
1379  }  }
1380    
1381  gdImagePtr make_image(rcsfilelog_t *rcs)  static void draw_branch_box(gdImagePtr im, branch_t *b, int xp, int yp)
1382  {  {
1383          gdImagePtr im;          int lx;
1384            int rx;
1385            int i;
1386            int yy;
1387            int x2;
1388    
1389            if(conf.left_right)
1390            {
1391                    lx = b->cx;
1392                    rx = lx + b->w;
1393                    x2 = b->cx + b->w/2;
1394            }
1395            else
1396            {
1397                    lx = b->cx - b->w/2;
1398                    rx = lx + b->w;
1399                    x2 = b->cx;
1400            }
1401            draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1402            yy = conf.branch_tspace;
1403            if(!b->nfolds)
1404            {
1405                    draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_color);
1406                    yy += get_sheight(b->branch->branch, &conf.branch_font);
1407                    for(i = 0; i < b->ntags; i++)
1408                    {
1409                            draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_tag_color);
1410                            yy += get_sheight(b->tags[i]->tag, &conf.branch_tag_font);
1411                    }
1412            }
1413            else
1414            {
1415                    int y1, y2;
1416                    int tx = lx + b->fw + conf.branch_lspace;
1417                    int nx = tx - get_swidth(" ", &conf.branch_font);
1418                    draw_string(im, b->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1419                    y1 = get_sheight(b->branch->branch, &conf.branch_font);
1420                    draw_string(im, b->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1421                    y2 = get_sheight(b->tags[0]->tag, &conf.branch_font);
1422                    yy += MAX(y1, y2);
1423                    for(i = 0; i < b->nfolds; i++)
1424                    {
1425                            draw_string(im, b->folds[i]->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1426                            y1 = get_sheight(b->folds[i]->branch->branch, &conf.branch_font);
1427                            draw_string(im, b->folds[i]->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1428                            y2 = get_sheight(b->folds[i]->tags[0]->tag, &conf.branch_tag_font);
1429                            yy += MAX(y1, y2);
1430                    }
1431            }
1432    }
1433    
1434    static void draw_branch(gdImagePtr im, branch_t *b)
1435    {
1436            int yy, xx;
1437          int i;          int i;
1438            int line[4];
1439            int l;
1440            int sign;
1441    
1442          im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);          line[0] = conf.rev_color.id;
1443          conf.color_bg.id = gdImageColorAllocate(im, conf.color_bg.r, conf.color_bg.g, conf.color_bg.b);          line[1] = gdTransparent;
1444          conf.tag_color.id = gdImageColorAllocate(im, conf.tag_color.r, conf.tag_color.g, conf.tag_color.b);          line[1] = gdTransparent;
1445          conf.rev_color.id = gdImageColorAllocate(im, conf.rev_color.r, conf.rev_color.g, conf.rev_color.b);          line[3] = conf.rev_color.id;
         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);  
1446    
1447          for(i = 0; i < rcs->nbranches; i++)          draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1448                  draw_branch(im, rcs->branches[i]->x, rcs->branches[i]->y, rcs->branches[i]);  
1449          for(i = 0; i < rcs->nbranches; i++)          if(conf.left_right)
1450          {          {
1451                  if(rcs->branches[i]->branchpoint)                  if(conf.upside_down)
1452                          draw_connector(im, rcs->branches[i]);                  {
1453                            xx = b->cx;
1454                            for(i = 0; i < b->nrevs; i++)
1455                            {
1456                                    revision_t *r = b->revs[i];
1457                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1458                                    gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1459                                    for(sign = l = 1; l < conf.thick_lines; l++)
1460                                    {
1461                                            int pp = (l+1)/2*sign;
1462                                            gdImageLine(im, xx, r->y+pp, r->cx+r->w, r->y+pp, gdStyled);
1463                                            sign *= -1;
1464                                    }
1465                                    draw_rev(im, r);
1466                                    xx = r->cx;
1467                            }
1468                            if(conf.branch_dupbox && b->nrevs)
1469                            {
1470                                    i = b->cx - b->tw + b->w;
1471                                    gdImageLine(im, xx, b->y, i+b->w, b->y, conf.rev_color.id);
1472                                    for(sign = l = 1; l < conf.thick_lines; l++)
1473                                    {
1474                                            int pp = (l+1)/2*sign;
1475                                            gdImageLine(im, xx, b->y+pp, i+b->w, b->y+pp, conf.rev_color.id);
1476                                            sign *= -1;
1477                                    }
1478                                    draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1479                            }
1480                    }
1481                    else
1482                    {
1483                            xx = b->cx + b->w;
1484                            for(i = 0; i < b->nrevs; i++)
1485                            {
1486                                    revision_t *r = b->revs[i];
1487                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1488                                    gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1489                                    for(sign = l = 1; l < conf.thick_lines; l++)
1490                                    {
1491                                            int pp = (l+1)/2*sign;
1492                                            gdImageLine(im, xx, r->y+pp, r->cx, r->y+pp, gdStyled);
1493                                            sign *= -1;
1494                                    }
1495                                    draw_rev(im, r);
1496                                    xx = r->cx + r->w;
1497                            }
1498                            if(conf.branch_dupbox && b->nrevs)
1499                            {
1500                                    i = b->cx + b->tw - b->w;
1501                                    gdImageLine(im, xx, b->y, i, b->y, conf.rev_color.id);
1502                                    for(sign = l = 1; l < conf.thick_lines; l++)
1503                                    {
1504                                            int pp = (l+1)/2*sign;
1505                                            gdImageLine(im, xx, b->y+pp, i, b->y+pp, conf.rev_color.id);
1506                                            sign *= -1;
1507                                    }
1508                                    draw_branch_box(im, b, i - b->cx, b->y - b->h/2);
1509                            }
1510                    }
1511            }
1512            else
1513            {
1514                    if(conf.upside_down)
1515                    {
1516                            yy = b->y;
1517                            for(i = 0; i < b->nrevs; i++)
1518                            {
1519                                    revision_t *r = b->revs[i];
1520                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1521                                    gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1522                                    for(sign = l = 1; l < conf.thick_lines; l++)
1523                                    {
1524                                            int pp = (l+1)/2*sign;
1525                                            gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y+r->h, gdStyled);
1526                                            sign *= -1;
1527                                    }
1528                                    draw_rev(im, r);
1529                                    yy = r->y;
1530                            }
1531                            if(conf.branch_dupbox && b->nrevs)
1532                            {
1533                                    i = b->y - b->th + b->h;
1534                                    gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1535                                    for(sign = l = 1; l < conf.thick_lines; l++)
1536                                    {
1537                                            int pp = (l+1)/2*sign;
1538                                            gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1539                                            sign *= -1;
1540                                    }
1541                                    draw_branch_box(im, b, 0, i);
1542                            }
1543                    }
1544                    else
1545                    {
1546                            yy = b->y + b->h;
1547                            for(i = 0; i < b->nrevs; i++)
1548                            {
1549                                    revision_t *r = b->revs[i];
1550                                    gdImageSetStyle(im, line, r->stripped ? 4 : 1);
1551                                    gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1552                                    for(sign = l = 1; l < conf.thick_lines; l++)
1553                                    {
1554                                            int pp = (l+1)/2*sign;
1555                                            gdImageLine(im, r->cx+pp, yy, r->cx+pp, r->y, gdStyled);
1556                                            sign *= -1;
1557                                    }
1558                                    draw_rev(im, r);
1559                                    yy = r->y + r->h;
1560                            }
1561                            if(conf.branch_dupbox && b->nrevs)
1562                            {
1563                                    i = b->y + b->th - b->h;
1564                                    gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
1565                                    for(sign = l = 1; l < conf.thick_lines; l++)
1566                                    {
1567                                            int pp = (l+1)/2*sign;
1568                                            gdImageLine(im, b->cx+pp, yy, b->cx+pp, i, conf.rev_color.id);
1569                                            sign *= -1;
1570                                    }
1571                                    draw_branch_box(im, b, 0, i);
1572                            }
1573                    }
1574            }
1575    }
1576    
1577    static void draw_connector(gdImagePtr im, branch_t *b)
1578    {
1579            int l;
1580            int sign;
1581            revision_t *r = b->branchpoint;
1582            int x1 = r->cx + r->w/2 + 2;
1583            int y1 = r->y + r->h/2;
1584            int x2 = b->cx;
1585            int y2 = b->y;
1586    
1587            if(conf.left_right)
1588            {
1589                    x2 = r->cx + r->w/2;
1590                    y2 = r->y + r->h/2 + 3;
1591                    x1 = b->cx;
1592                    y1 = b->y;
1593                    if(conf.upside_down)
1594                            x1 += b->w;
1595            }
1596            else
1597            {
1598                    x1 = r->cx + r->w/2 + 2;
1599                    y1 = r->y + r->h/2;
1600                    x2 = b->cx;
1601                    y2 = b->y;
1602                    if(conf.upside_down)
1603                            y2 += b->h;
1604            }
1605            gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
1606            gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
1607            for(sign = l = 1; l < conf.thick_lines; l++)
1608            {
1609                    int pp = (l+1)/2*sign;
1610                    gdImageLine(im, x1, y1+pp, x2, y1+pp, conf.branch_color.id);
1611                    gdImageLine(im, x2+pp, y1, x2+pp, y2, conf.branch_color.id);
1612                    sign *= -1;
1613            }
1614    }
1615    
1616    static void draw_merges(gdImagePtr im, rcsfile_t *rcs, int dot)
1617    {
1618            int i;
1619            for(i = 0; i < rcs->nmerges; i++)
1620            {
1621                    revision_t *fr = rcs->merges[i].from->logrev;
1622                    revision_t *tr = rcs->merges[i].to->logrev;
1623                    int x1, x2, y1, y2;
1624                    if(!fr || !tr || fr == tr)
1625                            continue;       /* This can happen with detached tags and self-references */
1626                    if(conf.left_right)
1627                    {
1628                            if(fr->branch == tr->branch)
1629                            {
1630                                    y1 = fr->y - fr->h/2;
1631                                    y2 = tr->y - tr->h/2;
1632                            }
1633                            else
1634                            {
1635                                    if(fr->y < tr->y)
1636                                    {
1637                                            y1 = fr->y + fr->h/2;
1638                                            y2 = tr->y - tr->h/2;
1639                                    }
1640                                    else
1641                                    {
1642                                            y1 = fr->y - fr->h/2;
1643                                            y2 = tr->y + tr->h/2;
1644                                    }
1645                            }
1646                            x1 = fr->cx + fr->w/2;
1647                            x2 = tr->cx + tr->w/2;
1648                    }
1649                    else
1650                    {
1651                            if(fr->branch == tr->branch)
1652                            {
1653                                    x1 = fr->cx - fr->w/2;
1654                                    x2 = tr->cx - tr->w/2;
1655                            }
1656                            else
1657                            {
1658                                    if(fr->cx < tr->cx)
1659                                    {
1660                                            x1 = fr->cx + fr->w/2;
1661                                            x2 = tr->cx - tr->w/2;
1662                                    }
1663                                    else
1664                                    {
1665                                            x1 = fr->cx - fr->w/2;
1666                                            x2 = tr->cx + tr->w/2;
1667                                    }
1668                            }
1669                            y1 = fr->y + rcs->merges[i].from->yofs;
1670                            y2 = tr->y + rcs->merges[i].to->yofs;
1671                    }
1672                    if(dot && !conf.merge_arrows)
1673                    {
1674                            int o = conf.left_right ? 1 : 0;
1675                            gdImageArc(im, x2, y2+o, 8, 8, 0, 360, conf.merge_color.id);
1676                            gdImageFillToBorder(im, x2+1, y2+o+1, conf.merge_color.id, conf.merge_color.id);
1677                    }
1678                    else if(dot && conf.merge_arrows)
1679                    {
1680                            /*
1681                             * Arrow patch from Haroon Rafique <haroon.rafique@utoronto.ca>
1682                             * Slightly adapted to be more configurable.
1683                             */
1684                            int sx, sy;     /* start point coordinates */
1685                            int ex, ey;     /* end point coordinates */
1686                            double theta;
1687                            double u1, v1, u2, v2;
1688                            gdPoint p[3];
1689    
1690                            sx = x1; sy = y1;
1691                            ex = x2; ey = y2;
1692                            if(conf.left_right)
1693                            {
1694                                    if(fr->branch == tr->branch)
1695                                    {
1696                                            int yy = (y1 < y2 ? y1 : y2) - 5;
1697                                            /* line from (x1,yy) to (x2,yy) */
1698                                            sy = ey = yy;
1699                                    }
1700                                    else
1701                                    {
1702                                            if(y1 > y2)
1703                                            {
1704                                                    /* line from (x1,y1-3) to (x2,y2+3+1) */
1705                                                    sy = y1-3;
1706                                                    ey = y2+3+1;
1707                                            }
1708                                            else
1709                                            {
1710                                                    /* line from (x1,y1+3+1) to (x2,y2-3) */
1711                                                    sy = y1+3+1;
1712                                                    ey = y2-3;
1713                                            }
1714                                    }
1715                            }
1716                            else
1717                            {
1718                                    if(fr->branch == tr->branch)
1719                                    {
1720                                            int xx = (x1 < x2 ? x1 : x2) - 5;
1721                                            /* line from (xx,y1) to (xx,y2) */
1722                                            sx = ex = xx;
1723                                    }
1724                                    else
1725                                    {
1726                                            if(x1 > x2)
1727                                            {
1728                                                    /* line from (x1-3,y1) to (x2+3,y2) */
1729                                                    sx = x1-3;
1730                                                    ex = x2+3;
1731                                            }
1732                                            else
1733                                            {
1734                                                    /* line from (x1+3,y1) to (x2-3,y2) */
1735                                                    sx = x1+3;
1736                                                    ex = x2-3;
1737                                            }
1738                                    }
1739                            }
1740                            /*
1741                             * inspiration for arrow code comes from arrows.c in the
1742                             * graphviz package. Thank you, AT&T
1743                             */
1744                            /* theta in radians */
1745                            theta = atan2((double)(sy-ey), (double)(sx-ex));
1746                            u1 = (double)conf.arrow_length * cos(theta);
1747                            v1 = (double)conf.arrow_length * sin(theta);
1748                            u2 = (double)conf.arrow_width  * cos(theta + M_PI/2.0);
1749                            v2 = (double)conf.arrow_width  * sin(theta + M_PI/2.0);
1750                            /* points of polygon (triangle) */
1751                            p[0].x = ROUND(ex + u1 - u2);
1752                            p[0].y = ROUND(ey + v1 - v2);
1753                            p[1].x = ex;
1754                            p[1].y = ey;
1755                            p[2].x = ROUND(ex + u1 + u2);
1756                            p[2].y = ROUND(ey + v1 + v2);
1757                            /* draw the polygon (triangle) */
1758                            gdImageFilledPolygon(im, p, 3, conf.merge_color.id);
1759                    }
1760                    else
1761                    {
1762                            if(conf.left_right)
1763                            {
1764                                    if(fr->branch == tr->branch)
1765                                    {
1766                                            int yy = (y1 < y2 ? y1 : y2) - 5;
1767                                            gdImageLine(im, x1, y1, x1, yy, conf.merge_color.id);
1768                                            gdImageLine(im, x2, y2, x2, yy, conf.merge_color.id);
1769                                            gdImageLine(im, x1, yy, x2, yy, conf.merge_color.id);
1770                                    }
1771                                    else
1772                                    {
1773                                            if(y1 > y2)
1774                                            {
1775                                                    gdImageLine(im, x1, y1, x1, y1-3, conf.merge_color.id);
1776                                                    gdImageLine(im, x2, y2+1, x2, y2+3+1, conf.merge_color.id);
1777                                                    gdImageLine(im, x1, y1-3, x2, y2+3+1, conf.merge_color.id);
1778                                            }
1779                                            else
1780                                            {
1781                                                    gdImageLine(im, x1, y1+1, x1, y1+3+1, conf.merge_color.id);
1782                                                    gdImageLine(im, x2, y2, x2, y2-3, conf.merge_color.id);
1783                                                    gdImageLine(im, x1, y1+3+1, x2, y2-3, conf.merge_color.id);
1784                                            }
1785                                    }
1786                            }
1787                            else
1788                            {
1789                                    if(fr->branch == tr->branch)
1790                                    {
1791                                            int xx = (x1 < x2 ? x1 : x2) - 5;
1792                                            gdImageLine(im, xx, y1, x1, y1, conf.merge_color.id);
1793                                            gdImageLine(im, xx, y2, x2, y2, conf.merge_color.id);
1794                                            gdImageLine(im, xx, y1, xx, y2, conf.merge_color.id);
1795                                    }
1796                                    else
1797                                    {
1798                                            if(x1 > x2)
1799                                            {
1800                                                    gdImageLine(im, x1, y1, x1-3, y1, conf.merge_color.id);
1801                                                    gdImageLine(im, x2, y2, x2+3, y2, conf.merge_color.id);
1802                                                    gdImageLine(im, x1-3, y1, x2+3, y2, conf.merge_color.id);
1803                                            }
1804                                            else
1805                                            {
1806                                                    gdImageLine(im, x1, y1, x1+3, y1, conf.merge_color.id);
1807                                                    gdImageLine(im, x2, y2, x2-3, y2, conf.merge_color.id);
1808                                                    gdImageLine(im, x1+3, y1, x2-3, y2, conf.merge_color.id);
1809                                            }
1810                                    }
1811                            }
1812                    }
1813            }
1814    }
1815    
1816    static void alloc_color(gdImagePtr im, color_t *c)
1817    {
1818            c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
1819    }
1820    
1821    gdImagePtr make_image(rcsfile_t *rcs)
1822    {
1823            gdImagePtr im;
1824            int i;
1825            char *cptr;
1826    
1827            cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
1828            i = get_swidth(cptr, &conf.title_font);
1829            if(rcs->tw+conf.margin_left+conf.margin_right > i)
1830                    i = rcs->tw+conf.margin_left+conf.margin_right;
1831            im = gdImageCreate(i, rcs->th+conf.margin_top+conf.margin_bottom);
1832            alloc_color(im, &conf.color_bg);
1833            alloc_color(im, &conf.tag_color);
1834            alloc_color(im, &conf.rev_color);
1835            alloc_color(im, &conf.rev_bgcolor);
1836            alloc_color(im, &conf.rev_text_color);
1837            alloc_color(im, &conf.branch_color);
1838            alloc_color(im, &conf.branch_tag_color);
1839            alloc_color(im, &conf.branch_bgcolor);
1840            alloc_color(im, &conf.title_color);
1841            alloc_color(im, &conf.merge_color);
1842            alloc_color(im, &black_color);
1843            alloc_color(im, &white_color);
1844    
1845            if(conf.transparent_bg)
1846                    gdImageColorTransparent(im, conf.color_bg.id);
1847    
1848            if(!conf.merge_front)
1849                    draw_merges(im, rcs, 0);
1850    
1851            for(i = 0; i < rcs->nbranches; i++)
1852            {
1853                    if(!rcs->branches[i]->folded)
1854                            draw_branch(im, rcs->branches[i]);
1855            }
1856    
1857            draw_merges(im, rcs, 1);        /* The dots of the merge dest */
1858    
1859            for(i = 0; i < rcs->nbranches; i++)
1860            {
1861                    if(rcs->branches[i]->branchpoint)
1862                            draw_connector(im, rcs->branches[i]);
1863            }
1864            draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
1865            xfree(cptr);
1866    
1867            if(conf.merge_front)
1868                    draw_merges(im, rcs, 0);
1869    
1870            return im;
1871    }
1872    
1873    /*
1874     **************************************************************************
1875     * Layout routines
1876     *
1877     * Branch BBox:
1878     *      left   = center_x - total_width / 2     (cx-tw)/2
1879     *      right  = center_x + total_width / 2     (cx+tw)/2
1880     *      top    = y_pos                          (y)
1881     *      bottom = y_pos + total_height           (y+th)
1882     *
1883     * Margins of branches:
1884     *
1885     *         .              .
1886     *         .              .
1887     *         +--------------+
1888     *            ^
1889     *            | branch_margin           .
1890     *            v                         .
1891     * ----------------+                    .
1892     *                 | ^                  |
1893     *                 | | branch_connect   |
1894     *                 | v                  |
1895     *..-+      +t-----+------+      +------+------+
1896     *   |      l             |      |             |
1897     *   | <--> | branch bbox | <--> | branch bbox |
1898     *   |   |  |             r   |  |             |
1899     *..-+   |  +------------b+   |  +-------------+
1900     *       |    ^               branch_margin
1901     *       |    | branch_margin
1902     *       |    v
1903     *       |  +-------------+
1904     *       |  .             .
1905     *       |  .             .
1906     *       |
1907     *       branch_margin
1908     *
1909     * FIXME: There are probable som +/-1 errors in the code...
1910     *        (notably shadows are not calculated in the margins)
1911     **************************************************************************
1912     */
1913    static void move_branch(branch_t *b, int x, int y)
1914    {
1915            int i;
1916            b->cx += x;
1917            b->y += y;
1918            for(i = 0; i < b->nrevs; i++)
1919            {
1920                    b->revs[i]->cx += x;
1921                    b->revs[i]->y += y;
1922            }
1923    }
1924    
1925    static void initial_reposition_branch(revision_t *r, int *x, int *w)
1926    {
1927            int i, j;
1928            for(j = 0; j < r->nbranches; j++)
1929            {
1930                    branch_t *b = r->branches[j];
1931                    *x += *w + conf.rev_minline + b->tw/2 - b->cx;
1932                    *w = b->tw/2;
1933                    move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
1934                    *x = b->cx;
1935                    /* Recurse to move branches of branched revisions */
1936                    for(i = b->nrevs-1; i >= 0; i--)
1937                    {
1938                            initial_reposition_branch(b->revs[i], x, w);
1939                    }
1940            }
1941    }
1942    
1943    static void initial_reposition_branch_lr(revision_t *r, int *y, int *h)
1944    {
1945            int i, j;
1946            for(j = 0; j < r->nbranches; j++)
1947            {
1948                    branch_t *b = r->branches[j];
1949                    *y += *h + conf.rev_minline + b->th/2 - b->y;
1950                    *h = b->th/2;
1951                    move_branch(b, r->cx + r->w/2 + conf.branch_connect, *y);
1952                    *y = b->y;
1953                    /* Recurse to move branches of branched revisions */
1954                    for(i = b->nrevs-1; i >= 0; i--)
1955                    {
1956                            initial_reposition_branch_lr(b->revs[i], y, h);
1957                    }
1958            }
1959    }
1960    
1961    static void rect_union(int *x, int *y, int *w, int *h, branch_t *b)
1962    {
1963            int x1 = *x;
1964            int x2 = x1 + *w;
1965            int y1 = *y;
1966            int y2 = y1 + *h;
1967            int xx1;
1968            int xx2;
1969            int yy1;
1970            int yy2;
1971    
1972            if(conf.left_right)
1973            {
1974                    xx1 = b->cx;
1975                    yy1 = b->y - b->th/2;
1976            }
1977            else
1978            {
1979                    xx1 = b->cx - b->tw/2;
1980                    yy1 = b->y;
1981            }
1982            xx2 = xx1 + b->tw;
1983            yy2 = yy1 + b->th;
1984    
1985            x1 = MIN(x1, xx1);
1986            x2 = MAX(x2, xx2);
1987            y1 = MIN(y1, yy1);
1988            y2 = MAX(y2, yy2);
1989            *x = x1;
1990            *y = y1;
1991            *w = x2 - x1;
1992            *h = y2 - y1;
1993    }
1994    
1995    static int branch_intersects(int top, int bottom, int left, branch_t *b)
1996    {
1997            int br = b->cx + b->tw/2;
1998            int bt = b->y - conf.branch_connect - conf.branch_margin/2;
1999            int bb = b->y + b->th + conf.branch_margin/2;
2000            return !(bt > bottom || bb < top || br >= left);
2001    }
2002    
2003    static int branch_intersects_lr(int left, int right, int top, branch_t *b)
2004    {
2005            int bt = b->y + b->th/2;
2006            int bl = b->cx - conf.branch_connect - conf.branch_margin/2;
2007            int br = b->cx + b->tw + conf.branch_margin/2;
2008            return !(bl > right || br < left || bt >= top);
2009    }
2010    
2011    static int kern_branch(rcsfile_t *rcs, branch_t *b)
2012    {
2013            int left = b->cx - b->tw/2;
2014            int top = b->y - conf.branch_connect - conf.branch_margin/2;
2015            int bottom = b->y + b->th + conf.branch_margin/2;
2016            int i;
2017            int xpos = 0;
2018    
2019            for(i = 0; i < rcs->nbranches; i++)
2020            {
2021                    branch_t *bp = rcs->branches[i];
2022                    if(bp == b)
2023                            continue;
2024                    if(branch_intersects(top, bottom, left, bp))
2025                    {
2026                            int m = bp->cx + bp->tw/2 + conf.branch_margin;
2027                            if(m > xpos)
2028                                    xpos = m;
2029                    }
2030            }
2031            if(xpos && (b->cx - b->tw/2) - xpos > 0)
2032            {
2033                    move_branch(b, xpos - (b->cx - b->tw/2), 0);
2034                    return 1;
2035            }
2036            return 0;
2037    }
2038    
2039    static int kern_branch_lr(rcsfile_t *rcs, branch_t *b)
2040    {
2041            int top = b->y - b->th/2;
2042            int left = b->cx - conf.branch_connect - conf.branch_margin/2;
2043            int right = b->cx + b->tw + conf.branch_margin/2;
2044            int i;
2045            int ypos = 0;
2046    
2047            for(i = 0; i < rcs->nbranches; i++)
2048            {
2049                    branch_t *bp = rcs->branches[i];
2050                    if(bp == b)
2051                            continue;
2052                    if(branch_intersects_lr(left, right, top, bp))
2053                    {
2054                            int m = bp->y + bp->th/2 + conf.branch_margin;
2055                            if(m > ypos)
2056                                    ypos = m;
2057                    }
2058            }
2059            if(ypos && (b->y - b->th/2) - ypos > 0)
2060            {
2061                    move_branch(b, 0, ypos - (b->y - b->th/2));
2062                    return 1;
2063            }
2064            return 0;
2065    }
2066    
2067    static int kern_tree(rcsfile_t *rcs)
2068    {
2069            int i;
2070            int moved;
2071            int safeguard;
2072            int totalmoved = 0;
2073            for(moved = 1, safeguard = LOOPSAFEGUARD; moved && safeguard; safeguard--)
2074            {
2075                    moved = 0;
2076                    for(i = 1; i < rcs->nbranches; i++)
2077                    {
2078                            if(conf.left_right)
2079                                    moved += kern_branch_lr(rcs, rcs->branches[i]);
2080                            else
2081                                    moved += kern_branch(rcs, rcs->branches[i]);
2082                    }
2083                    totalmoved += moved;
2084    #ifdef DEBUG
2085                    fprintf(stderr, "kern_tree: moved=%d\n", moved);
2086    #endif
2087            }
2088            if(!quiet && !safeguard)
2089                    fprintf(stderr, "kern_tree: safeguard terminated possible infinite loop; please report.\n");
2090            return totalmoved;
2091    }
2092    
2093    static int index_of_revision(revision_t *r)
2094    {
2095            branch_t *b = r->branch;
2096            int i;
2097            for(i = 0; i < b->nrevs; i++)
2098            {
2099                    if(r == b->revs[i])
2100                            return i;
2101            }
2102            fprintf(stderr, "index_of_revision: Cannot find revision in branch\n");
2103            return 0;
2104    }
2105    
2106    static void branch_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2107    {
2108            if(l)   *l = br->cx - br->tw/2;
2109            if(r)   *r = br->cx + br->tw/2;
2110            if(t)   *t = br->y;
2111            if(b)   *b = br->y + br->th + ((conf.branch_dupbox && br->nrevs) ? conf.rev_minline + br->h : 0);
2112    }
2113    
2114    static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
2115    {
2116            int extra = conf.branch_margin & 1;     /* Correct +/-1 error on div 2 */
2117            branch_bbox(br, l, r, t, b);
2118            if(l)   *l -= conf.branch_margin/2;
2119            if(r)   *r += conf.branch_margin/2 + extra;
2120            if(t)   *t -= conf.branch_connect + conf.branch_margin/2;
2121            if(b)   *b += conf.branch_margin/2 + extra;
2122    }
2123    
2124    static int branch_distance(branch_t *br1, branch_t *br2)
2125    {
2126            int l1, r1, t1, b1;
2127            int l2, r2, t2, b2;
2128            assert(br1 != NULL);
2129            assert(br2 != NULL);
2130            branch_bbox(br1, &l1, &r1, NULL, NULL);
2131            branch_bbox(br2, &l2, &r2, NULL, NULL);
2132            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2133            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2134            /* Return:
2135             * - 0 if branches have no horizontal overlap
2136             * - positive if b1 is left of b2
2137             * - negative if b2 is left of b1
2138             */
2139            if((t1 > t2 && t1 < b2) || (b1 > t2 && b1 < b2))
2140                    return l1 < l2 ? l2 - r1 : -(l1 - r2);
2141            else
2142                    return 0;
2143    }
2144    
2145    static int space_needed(branch_t *br1, branch_t *br2)
2146    {
2147            int t1, b1;
2148            int t2, b2;
2149            assert(br1 != NULL);
2150            assert(br2 != NULL);
2151            assert(br1->cx < br2->cx);      /* br1 must be left of br2 */
2152            branch_ext_bbox(br1, NULL, NULL, &t1, &b1);
2153            branch_ext_bbox(br2, NULL, NULL, &t2, &b2);
2154            /* Return:
2155             * - positive if top br1 is located lower than br2
2156             * - negatve is top br2 is located lower than br1
2157             */
2158            if(t1 > t2)
2159                    return -(t1 - b2);
2160            else
2161                    return t2 - b1;
2162    }
2163    
2164    static void move_yr_branch(branch_t *b, int dy)
2165    {
2166            int i, j;
2167    #ifdef DEBUG
2168    /*      fprintf(stderr, "move_yr_branch: b=%s, dy=%d\n", b->branch->branch, dy);*/
2169    #endif
2170            b->y += dy;
2171            for(i = 0; i < b->nrevs; i++)
2172            {
2173                    b->revs[i]->y += dy;
2174                    for(j = 0; j < b->revs[i]->nbranches; j++)
2175                    {
2176    #ifdef DEBUG
2177    /*                      fprintf(stderr, ".");*/
2178    #endif
2179                            move_yr_branch(b->revs[i]->branches[j], dy);
2180                    }
2181            }
2182    }
2183    
2184    static void move_trunk(revision_t *r, int dy)
2185    {
2186            int i, j;
2187            branch_t *b = r->branch;
2188            b->th += dy;
2189            for(i = index_of_revision(r); i < b->nrevs; i++)
2190            {
2191    #ifdef DEBUG
2192                    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);
2193    #endif
2194                    b->revs[i]->y += dy;
2195                    for(j = 0; j < b->revs[i]->nbranches; j++)
2196                    {
2197                            move_yr_branch(b->revs[i]->branches[j], dy);
2198                    }
2199            }
2200    }
2201    
2202    static int space_below(rcsfile_t *rcs, revision_t *r)
2203    {
2204            int i, j;
2205            int bl, br, bb;
2206            int space = INT_MAX;
2207            branch_t *b = r->branch;
2208            branch_t *minb = NULL;
2209    
2210            branch_ext_bbox(b, &bl, &br, NULL, &bb);
2211            for(i = 0; i < rcs->nbranches; i++)
2212            {
2213                    int tbl, tbr, tbt;
2214                    branch_t *tb = rcs->branches[i];
2215                    branch_ext_bbox(tb, &tbl, &tbr, &tbt, NULL);
2216                    if(tb == b)
2217                            continue;
2218                    if(tbt > bb)    /* Must be below our branch */
2219                    {
2220                            if(tb->branchpoint)     /* Take account for the horiz connector */
2221                                    tbl = tb->branchpoint->cx + tb->branchpoint->branch->tw/2;
2222                            if((bl >= tbl && bl <= tbr) || (br <= tbr && br >= tbl))
2223                            {
2224                                    int s = tbt - bb - conf.branch_connect;
2225                                    if(s < space)
2226                                    {
2227                                            space = s;
2228                                            minb = tb;
2229                                    }
2230                            }
2231                    }
2232            }
2233            if(b->branchpoint)
2234            {
2235                    for(i = index_of_revision(r); i < b->nrevs; i++)
2236                    {
2237                            for(j = 0; j < b->revs[i]->nbranches; j++)
2238                            {
2239                                    int s = space_below(rcs, b->revs[i]->branches[j]->revs[0]);
2240                                    if(s < space)
2241                                            space = s;
2242                            }
2243                    }
2244            }
2245    #ifdef DEBUG
2246            fprintf(stderr, "space_below: from %s have %d to %s\n", b->branch->branch, space, minb ? minb->branch->branch : "<recursed>");
2247    #endif
2248            return space;
2249    }
2250    
2251    static int space_available(rcsfile_t *rcs, branch_t *colbr, branch_t *tagbr, int *nl, revision_t **bpcommon)
2252    {
2253            int i;
2254            int space = 0;
2255            int nlinks = 0;
2256            revision_t *r;
2257            branch_t *b;
2258            branch_t *ancestor;
2259            revision_t *branchpoint;
2260    
2261            if(!tagbr->branchpoint || !colbr->branchpoint)
2262            {
2263                    if(!quiet)
2264                            fprintf(stderr, "space_available: Trying to stretch the top?\n");
2265                    return 0;
2266            }
2267    
2268            r = colbr->branchpoint;
2269            b = r->branch;
2270            branchpoint = tagbr->branchpoint;
2271            ancestor = branchpoint->branch;
2272            assert(b != NULL);
2273            assert(ancestor != NULL);
2274    
2275            while(1)
2276            {
2277                    int s;
2278                    int rtag = b == ancestor ? index_of_revision(branchpoint)+1 : 0;
2279                    for(i = index_of_revision(r); i >= rtag; i--)
2280                    {
2281                            if(i > 0)
2282                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2283                            else
2284                                    s = b->revs[i]->y - (b->y + b->h);
2285                            if(s < conf.rev_maxline)
2286                            {
2287                                    space += conf.rev_maxline - s;
2288                                    nlinks++;
2289                            }
2290                    }
2291                    s = space_below(rcs, r);
2292                    if(s < space)
2293                            space = s;
2294    #ifdef DEBUG
2295                    if(space < 0)
2296                            return -1;
2297    #endif
2298                    if(b == ancestor)
2299                            break;
2300                    r = b->branchpoint;
2301                    if(!r)
2302                    {
2303                            /* Not a common ancestor */
2304                            r = colbr->branchpoint;
2305                            b = r->branch;
2306                            branchpoint = ancestor->branchpoint;
2307                            if(!branchpoint)
2308                            {
2309                                    if(!quiet)
2310                                            fprintf(stderr, "space_available: No common ancestor?\n");
2311                                    return 0;
2312                            }
2313                            ancestor = branchpoint->branch;
2314                            assert(ancestor != NULL);
2315                            nlinks = 0;
2316                            space = 0;
2317                            continue;       /* Restart with a new ancestor */
2318                    }
2319                    b = r->branch;
2320            }
2321            if(nl)
2322                    *nl = nlinks;           /* Return the number of links that can stretch */
2323            if(bpcommon)
2324                    *bpcommon = branchpoint;        /* Return the ancestral branchpoint on the common branch */
2325            return space;
2326    }
2327    
2328    static int stretch_branches(rcsfile_t *rcs, branch_t *br1, branch_t *br2, int totalstretch)
2329    {
2330            revision_t *r;
2331            revision_t *bpcommon = NULL;
2332            branch_t *ancestor = NULL;
2333            branch_t *b;
2334            int i;
2335            int space;
2336            int nlinks;
2337            int dy;
2338            int rest;
2339    
2340            space = space_available(rcs, br1, br2, &nlinks, &bpcommon);
2341            if(bpcommon)
2342                    ancestor = bpcommon->branch;
2343    
2344    #ifdef DEBUG
2345            if(space == -1)
2346                    return 0;
2347            fprintf(stderr, "stretch_branches: space available %d over %d links common %s\n", space, nlinks, ancestor->branch->branch);
2348    #endif
2349            if(space < totalstretch)
2350                    return 0;
2351    
2352            dy = totalstretch / nlinks;
2353            rest = totalstretch - dy * nlinks;
2354    
2355            r = br1->branchpoint;
2356            b = r->branch;
2357            while(1)
2358            {
2359                    int rtag = b == ancestor ? index_of_revision(bpcommon)+1 : 0;
2360                    for(i = index_of_revision(r); i >= rtag; i--)
2361                    {
2362                            int s, q;
2363                            if(i > 0)
2364                                    s = b->revs[i]->y - (b->revs[i-1]->y + b->revs[i-1]->h);
2365                            else
2366                                    s = b->revs[i]->y - (b->y + b->h);
2367                            q = conf.rev_maxline - s;
2368                            if(q > 0)
2369                            {
2370                                    int d = rest ? rest/nlinks+1 : 0;
2371                                    if(q >= dy+d)
2372                                    {
2373                                            move_trunk(b->revs[i], dy+d);
2374                                    }
2375                                    else
2376                                    {
2377                                            move_trunk(b->revs[i], q);
2378                                            rest += dy+d - q;
2379                                    }
2380                                    rest -= d;
2381                                    nlinks--;
2382                            }
2383                    }
2384                    if(b == ancestor)
2385                            break;
2386                    r = b->branchpoint;
2387                    assert(r != NULL);      /* else 'space_available' wouldn't have returned positively */
2388                    b = r->branch;
2389            }
2390            return 1;
2391    }
2392    
2393    static branch_t *find_collision_branch(rcsfile_t *rcs, branch_t *b)
2394    {
2395            int i;
2396            int dist = INT_MAX;
2397            branch_t *col = NULL;
2398    
2399            for(i = 0; i < rcs->nbranches; i++)
2400            {
2401                    int t = branch_distance(rcs->branches[i], b);
2402                    if(t > 0 && t < dist)
2403                    {
2404                            dist = t;
2405                            col = rcs->branches[i];
2406                    }
2407            }
2408            return col;
2409    }
2410    
2411    void auto_stretch(rcsfile_t *rcs)
2412    {
2413            int i;
2414            int safeguard;
2415    
2416            for(i = 0, safeguard = LOOPSAFEGUARD; i < rcs->nbranches && safeguard; i++)
2417            {
2418                    int bl, pr;
2419                    branch_t *b = rcs->branches[i];
2420                    if(!b->branchpoint)
2421                            continue;
2422                    branch_bbox(b, &bl, NULL, NULL, NULL);
2423                    branch_bbox(b->branchpoint->branch, NULL, &pr, NULL, NULL);
2424                    if(bl - conf.branch_margin - pr > 0)
2425                    {
2426                            branch_t *col;
2427                            int spaceneeded;
2428                            /* There is a potential to move branch b further left.
2429                             * All branches obstructing this one from moving further
2430                             * left must be originating from revisions below
2431                             * b->branchpoint until a common ancester.
2432                             * So, we search all branches for a branch that lies left
2433                             * of b and is closest to b. This is then the collission
2434                             * branch that needs to be moved.
2435                             */
2436                            col = find_collision_branch(rcs, b);
2437                            if(!col)
2438                                    continue;
2439                            spaceneeded = space_needed(col, b);
2440                            if(spaceneeded < 0)
2441                                    continue;
2442    #ifdef DEBUG
2443                            fprintf(stderr, "auto_stretch: %s collides %s need %d\n", b->branch->branch, col->branch->branch, spaceneeded);
2444    #endif
2445                            /* Trace the collision branch back to find the common ancester
2446                             * of both col and b. All revisions encountered while traversing
2447                             * backwards must be stretched, including all revisions on the
2448                             * common ancester from where the branches sprout.
2449                             */
2450                            if(stretch_branches(rcs, col, b, spaceneeded))
2451                            {
2452                                    if(kern_tree(rcs))
2453                                    {
2454                                            /* Restart the process because movement can
2455                                             * cause more movement.
2456                                             */
2457                                            i = 0 - 1;      /* -1 for the i++ of the loop */
2458                                            safeguard--;    /* Prevent infinite loop, just in case */
2459                                    }
2460                                    /*return;*/
2461                            }
2462                    }
2463          }          }
2464          draw_title(im, expand_title(rcs));          if(!quiet && !safeguard)
2465                    fprintf(stderr, "auto_stretch: safeguard terminated possible infinite loop; please report.\n");
         return im;  
2466  }  }
2467    
2468  void move_branch(branch_t *b, int x, int y)  static void fold_branch(rcsfile_t *rcs, revision_t *r)
2469  {  {
2470          int i;          int i, j;
2471          b->x += x;          branch_t *btag = NULL;
2472          b->y += y;  
2473          for(i = 0; i < b->nrevs; i++)          if(r->nbranches < 2)
2474                    return;         /* Should not happen... */
2475    
2476            for(i = 0; i < r->nbranches; i++)
2477          {          {
2478                  b->revs[i]->x += x;                  branch_t *b = r->branches[i];
2479                  b->revs[i]->y += y;                  if(!b->nrevs && b->ntags < 2)
2480          }                  {
2481  }                          /* No commits in this branch and no duplicate tags */
2482                            if(!btag)
2483                                    btag = b;
2484                            else
2485                            {
2486                                    /* We have consecutive empty branches, fold */
2487                                    b->folded = 1;
2488                                    for(j = 0; j < rcs->nbranches; j++)
2489                                    {
2490                                            if(b == rcs->branches[j])
2491                                            {
2492                                                    /* Zap the branch from the admin */
2493                                                    memmove(&rcs->branches[j],
2494                                                            &rcs->branches[j+1],
2495                                                            (rcs->nbranches - j - 1)*sizeof(rcs->branches[0]));
2496                                                    rcs->nbranches--;
2497                                                    break;
2498                                            }
2499    
2500  void rect_union(int *x, int *y, int *w, int *h, branch_t *b)                                  }
2501  {                                  memmove(&r->branches[i], &r->branches[i+1], (r->nbranches - i - 1)*sizeof(r->branches[0]));
2502          int x1 = *x;                                  r->nbranches--;
2503          int x2 = x1 + *w;                                  i--;    /* We have one less now */
2504          int y1 = *y;  
2505          int y2 = y1 + *h;                                  /* Add to the fold-list */
2506          int xx1 = b->x - b->tw/2;                                  btag->folds = xrealloc(btag->folds, (btag->nfolds+1) * sizeof(btag->folds[0]));
2507          int xx2 = xx1 + b->tw;                                  btag->folds[btag->nfolds] = b;
2508          int yy1 = b->y;                                  btag->nfolds++;
2509          int yy2 = yy1 + b->th;                          }
2510          x1 = MIN(x1, xx1);                  }
2511          x2 = MAX(x2, xx2);                  else
2512          y1 = MIN(y1, yy1);                  {
2513          y2 = MAX(y2, yy2);                          if(!conf.branch_foldall)
2514          *x = x1;                                  btag = NULL;    /* Start a new box */
2515          *y = y1;                          /* Recursively fold sub-branches */
2516          *w = x2 - x1;                          for(j = 0; j < b->nrevs; j++)
2517          *h = y2 - y1;                                  fold_branch(rcs, b->revs[j]);
2518                    }
2519            }
2520  }  }
2521    
2522  void make_layout(rcsfilelog_t *rcs)  void make_layout(rcsfile_t *rcs)
2523  {  {
2524          int i, j;          int i, j;
2525          int x, y;          int x, y;
2526          int w, h;          int w, h;
2527          int w2;          int w2;
2528    
2529            /* Remove all unwanted revisions */
2530            if(conf.strip_untagged)
2531            {
2532                    int fr = conf.strip_first_rev ? 0 : 1;
2533                    for(i = 0; i < rcs->nbranches; i++)
2534                    {
2535                            branch_t *bp = rcs->branches[i];
2536                            for(j = fr; j < bp->nrevs-1; j++)
2537                            {
2538                                    if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)
2539                                    {
2540                                            memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
2541                                            bp->nrevs--;
2542                                            bp->revs[j]->stripped = 1;
2543                                            j--;
2544                                    }
2545                            }
2546                    }
2547            }
2548    
2549            /* Fold all empty branched in one box on the same branchpoint */
2550            if(conf.branch_fold)
2551            {
2552                    for(i = 0; i < rcs->branches[0]->nrevs; i++)
2553                    {
2554                            if(rcs->branches[0]->revs[i]->nbranches > 1)
2555                                    fold_branch(rcs, rcs->branches[0]->revs[i]);
2556                    }
2557            }
2558    
2559          /* Calculate the box-sizes of the revisions */          /* Calculate the box-sizes of the revisions */
2560          for(i = 0; i < rcs->nrevs; i++)          for(i = 0; i < rcs->nsrev; i++)
2561          {          {
2562                  revision_t *rp;                  revision_t *rp;
2563                  int w;                  int w;
2564                  int h;                  int h;
2565                  rp = rcs->revs[i];                  rp = rcs->srev[i];
2566                  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);
2567                  h = get_sheight(rp->rev->rev, &conf.rev_font);                  w = get_swidth(rp->revtext, &conf.rev_text_font);
2568                    j = get_swidth(rp->rev->rev, &conf.rev_font);
2569                    if(j > w)
2570                            w = j;
2571                    h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);
2572                  for(j = 0; j < rp->ntags; j++)                  for(j = 0; j < rp->ntags; j++)
2573                  {                  {
2574                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
2575                            int th;
2576                          if(ww > w) w = ww;                          if(ww > w) w = ww;
2577                          h += get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;                          th = get_sheight(rp->tags[j]->tag, &conf.tag_font) + conf.rev_separator;
2578                            rp->tags[j]->yofs = h + th/2 + conf.rev_tspace;
2579                            h += th;
2580                  }                  }
2581                  rp->w = w + conf.rev_lspace + conf.rev_rspace;                  rp->w = w + conf.rev_lspace + conf.rev_rspace;
2582                  rp->h = h + conf.rev_tspace + conf.rev_bspace;                  rp->h = h + conf.rev_tspace + conf.rev_bspace;
# Line 938  Line 2588 
2588                  branch_t *bp = rcs->branches[i];                  branch_t *bp = rcs->branches[i];
2589                  int w;                  int w;
2590                  int h;                  int h;
2591                  w = get_swidth(bp->branch, &conf.branch_font);                  if(!bp->nfolds)
                 h = get_sheight(bp->branch, &conf.branch_font);  
                 if(bp->tag)  
2592                  {                  {
2593                          int ww = get_swidth(bp->tag->tag, &conf.branch_font);                          w = get_swidth(bp->branch->branch, &conf.branch_font);
2594                          if(ww > w) w = ww;                          h = get_sheight(bp->branch->branch, &conf.branch_font);
2595                          h += get_sheight(bp->tag->tag, &conf.branch_font) + conf.branch_separator;                          for(j = 0; j < bp->ntags; j++)
2596                            {
2597                                    int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
2598                                    if(ww > w) w = ww;
2599                                    h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
2600                            }
2601                    }
2602                    else
2603                    {
2604                            int h1, h2;
2605                            int w1, w2;
2606                            int fw;
2607                            w1 = get_swidth(bp->branch->branch, &conf.branch_font);
2608                            w1 += get_swidth(" ", &conf.branch_font);
2609                            w2 = get_swidth(bp->tags[0]->tag, &conf.branch_tag_font);
2610                            fw = w1;
2611                            w = w1 + w2;
2612                            h1 = get_sheight(bp->branch->branch, &conf.branch_font);
2613                            h2 = get_sheight(bp->tags[0]->tag, &conf.branch_tag_font);
2614                            h = MAX(h1, h2);
2615                            for(j = 0; j < bp->nfolds; j++)
2616                            {
2617                                    w1 = get_swidth(bp->folds[j]->branch->branch, &conf.branch_font);
2618                                    w1 += get_swidth(" ", &conf.branch_font);
2619                                    w2 = get_swidth(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2620                                    if(w1 > fw)
2621                                            fw = w1;
2622                                    if(w1 + w2 > w)
2623                                            w = w1 + w2;
2624                                    h1 = get_sheight(bp->folds[j]->branch->branch, &conf.branch_font);
2625                                    h2 = get_sheight(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
2626                                    h += MAX(h1, h2);
2627                            }
2628                            bp->fw = fw;
2629                  }                  }
2630                  w += conf.branch_lspace + conf.branch_rspace;                  w += conf.branch_lspace + conf.branch_rspace;
2631                  h += conf.branch_tspace + conf.branch_bspace;                  h += conf.branch_tspace + conf.branch_bspace;
2632                  bp->w = w;                  bp->w = w;
2633                  bp->h = h;                  bp->h = h;
2634                  for(j = 0; j < bp->nrevs; j++)                  if(conf.left_right)
2635                    {
2636                            for(j = 0; j < bp->nrevs; j++)
2637                            {
2638                                    if(bp->revs[j]->h > h)
2639                                            h = bp->revs[j]->h;
2640                                    w += bp->revs[j]->w + conf.rev_minline;
2641                            }
2642                            if(conf.branch_dupbox && bp->nrevs)
2643                                    w += bp->w + conf.rev_minline;
2644                    }
2645                    else
2646                  {                  {
2647                          if(bp->revs[j]->w > w)                          for(j = 0; j < bp->nrevs; j++)
2648                                  w = bp->revs[j]->w;                          {
2649                          h += bp->revs[j]->h + conf.rev_minline;                                  if(bp->revs[j]->w > w)
2650                                            w = bp->revs[j]->w;
2651                                    h += bp->revs[j]->h + conf.rev_minline;
2652                            }
2653                            if(conf.branch_dupbox && bp->nrevs)
2654                                    h += bp->h + conf.rev_minline;
2655                  }                  }
2656                  bp->th = h;                  bp->th = h;
2657                  bp->tw = w;                  bp->tw = w;
2658          }          }
2659    
2660          /* Calculate the relative positions of revs in a branch */          /* Calculate the relative positions of revs in a branch */
2661          for(i = 0; i < rcs->nbranches; i++)          if(conf.left_right)
2662          {          {
2663                  branch_t *b = rcs->branches[i];                  for(i = 0; i < rcs->nbranches; i++)
2664                  x = b->tw/2;                  {
2665                  y = b->h;                          branch_t *b = rcs->branches[i];
2666                  b->x = x;                          y = b->th/2;
2667                  b->y = 0;                          x = b->w;
2668                  for(j = 0; j < b->nrevs; j++)                          b->y = y;
2669                            b->cx = 0;
2670                            for(j = 0; j < b->nrevs; j++)
2671                            {
2672                                    x += conf.rev_minline;
2673                                    b->revs[j]->y = y;
2674                                    b->revs[j]->cx = x;
2675                                    x += b->revs[j]->w;
2676                            }
2677                    }
2678            }
2679            else
2680            {
2681                    for(i = 0; i < rcs->nbranches; i++)
2682                  {                  {
2683                          y += conf.rev_minline;                          branch_t *b = rcs->branches[i];
2684                          b->revs[j]->x = x;                          x = b->tw/2;
2685                          b->revs[j]->y = y;                          y = b->h;
2686                          y += b->revs[j]->h;                          b->cx = x;
2687                            b->y = 0;
2688                            for(j = 0; j < b->nrevs; j++)
2689                            {
2690                                    y += conf.rev_minline;
2691                                    b->revs[j]->cx = x;
2692                                    b->revs[j]->y = y;
2693                                    y += b->revs[j]->h;
2694                            }
2695                  }                  }
2696          }          }
2697    
2698          /* Reposition the branches FIXME: Should be recursive on branchpoint revisions within branches... */          /* Initially reposition the branches from bottom to top progressively right */
2699          x = rcs->branches[0]->x;          if(conf.left_right)
2700          w2 = rcs->branches[0]->tw / 2;          {
2701          for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)                  x = rcs->branches[0]->y;
2702          {                  w2 = rcs->branches[0]->th / 2;
2703                  revision_t *r = rcs->branches[0]->revs[i];                  for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
2704                  for(j = 0; j < r->nbranches; j++)                  {
2705                  {                          initial_reposition_branch_lr(rcs->branches[0]->revs[i], &x, &w2);
2706                          branch_t *b = r->branches[j];                  }
2707                          x += w2 + conf.rev_minline + b->tw/2 - b->x;          }
2708                          w2 = b->tw/2;          else
2709                          move_branch(b, x, r->y + r->h);          {
2710                          x = b->x;                  x = rcs->branches[0]->cx;
2711                    w2 = rcs->branches[0]->tw / 2;
2712                    for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
2713                    {
2714                            initial_reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
2715                  }                  }
2716          }          }
2717    
2718            /* Initially move branches left if there is room */
2719            kern_tree(rcs);
2720    
2721            /* Try to kern the branches more by expanding the inter-revision spacing */
2722            if(conf.auto_stretch && !conf.left_right)
2723                    auto_stretch(rcs);
2724    
2725            /* Move everything w.r.t. the top-left margin */
2726          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
2727                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
2728    
2729          /* Calculate overall image size */          /* Calculate overall image size */
2730          x = rcs->branches[0]->x - rcs->branches[0]->tw/2;          if(conf.left_right)
2731          y = rcs->branches[0]->y;          {
2732                    x = rcs->branches[0]->cx;
2733                    y = rcs->branches[0]->y - rcs->branches[0]->th/2;
2734            }
2735            else
2736            {
2737                    x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
2738                    y = rcs->branches[0]->y;
2739            }
2740          w = rcs->branches[0]->tw;          w = rcs->branches[0]->tw;
2741          h = rcs->branches[0]->th;          h = rcs->branches[0]->th;
2742          for(i = 1; i < rcs->nbranches; i++)          for(i = 1; i < rcs->nbranches; i++)
2743                  rect_union(&x, &y, &w, &h, rcs->branches[i]);                  rect_union(&x, &y, &w, &h, rcs->branches[i]);
2744          rcs->tw = w;          rcs->tw = w;
2745          rcs->th = h;          rcs->th = h;
2746    
2747            /* Flip the entire tree */
2748            if(conf.upside_down)
2749            {
2750                    if(conf.left_right)
2751                    {
2752                            x += rcs->tw;
2753                            for(i = 0; i < rcs->nbranches; i++)
2754                            {
2755                                    branch_t *b = rcs->branches[i];
2756                                    for(j = 0; j < b->nrevs; j++)
2757                                    {
2758                                            revision_t *r = b->revs[j];