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