/[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.30, Sun Mar 9 22:36:50 2003 UTC revision 1.55, Tue Jun 14 20:40:14 2005 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,2002,2003  B. Stultiens   * Copyright (C) 2001,2002,2003,2004  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 23  Line 23 
23    
24  #include <stdio.h>  #include <stdio.h>
25  #include <stdlib.h>  #include <stdlib.h>
26    #include <stdarg.h>
27  #include <unistd.h>  #include <unistd.h>
28  #include <string.h>  #include <string.h>
29  #include <assert.h>  #include <assert.h>
30  #include <sys/types.h>  #include <sys/types.h>
31  #include <sys/stat.h>  #include <sys/stat.h>
32  #include <sys/wait.h>  #ifdef HAVE_SYS_WAIT_H
33    # include <sys/wait.h>
34    #endif
35  #include <fcntl.h>  #include <fcntl.h>
36  #include <regex.h>  #include <regex.h>
37  #include <errno.h>  #include <errno.h>
# Line 36  Line 39 
39  #include <time.h>  #include <time.h>
40  #include <limits.h>  #include <limits.h>
41  #include <regex.h>  #include <regex.h>
42    #include <math.h>
43    
44  #ifdef HAVE_GETOPT_H  #ifdef HAVE_GETOPT_H
45  # include <getopt.h>  # include <getopt.h>
# Line 58  Line 62 
62  /*#define NOGDFILL      1*/  /*#define NOGDFILL      1*/
63  /*#define DEBUG_IMAGEMAP        1*/  /*#define DEBUG_IMAGEMAP        1*/
64    
65  #define LOOPSAFEGUARD   100     /* Max itterations in possible infinite loops */  #define LOOPSAFEGUARD   10000   /* Max itterations in possible infinite loops */
66    
67  #ifndef MAX  #ifndef MAX
68  # define MAX(a,b)       ((a) > (b) ? (a) : (b))  # define MAX(a,b)       ((a) > (b) ? (a) : (b))
# Line 77  Line 81 
81  #define ALIGN_VB        0x20  #define ALIGN_VB        0x20
82  #define ALIGN_VX        0xf0  #define ALIGN_VX        0xf0
83    
84    #ifndef M_PI    /* math.h should have defined this */
85    # define M_PI 3.14159265358979323846
86    #endif
87    #define ROUND(f)        ((f >= 0.0)?((int)(f + 0.5)):((int)(f - 0.5)))
88    
89    #define ARROW_LENGTH    12      /* Default arrow dimensions */
90    #define ARROW_WIDTH     3
91    
92  /*  /*
93   **************************************************************************   **************************************************************************
94   * Globals   * Globals
# Line 85  Line 97 
97    
98  config_t conf;  config_t conf;
99  int debuglevel;  int debuglevel;
100  color_t white_color = {255, 255, 255, 0};  
101  color_t black_color = {0, 0, 0, 0};  static color_t white_color = {255, 255, 255, 0};
102    static color_t black_color = {0, 0, 0, 0};
103    
104    static branch_t *subtree_branch = NULL;         /* Set to the (first) subtree branch that we want to show */
105    static revision_t *subtree_rev = NULL;          /* Set to the subtree revision which branches we want to show */
106    
107    static msg_stack_t *msg_stack = NULL;           /* Messages that would otherwise be sent to stderr goto the image */
108    static int nmsg_stack = 0;
109    
110  /*  /*
111   **************************************************************************   **************************************************************************
# Line 101  Line 120 
120  static void add_string_str_html(const char *s, int maxlen);  static void add_string_str_html(const char *s, int maxlen);
121  static void add_string_str_len(const char *s, int maxlen);  static void add_string_str_len(const char *s, int maxlen);
122    
123    static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h);
124    
125  /*  /*
126   **************************************************************************   **************************************************************************
127   * Debug routines   * Debug routines
# Line 207  Line 228 
228    
229  /*  /*
230   **************************************************************************   **************************************************************************
231     * Error/Warning Message helpers
232     **************************************************************************
233     */
234    #define MSGBUFSIZE      256
235    void stack_msg(int severity, const char *fmt, ...)
236    {
237            va_list va;
238            int i;
239            char *buf = xmalloc(MSGBUFSIZE);
240            switch(severity)
241            {
242            case MSG_WARN:  sprintf(buf, "Warning: "); break;
243            case MSG_ERR:   sprintf(buf, "Error: "); break;
244            default:        sprintf(buf, "Unqualified error: "); break;
245            }
246            i = strlen(buf);
247            assert(i < MSGBUFSIZE);
248            va_start(va, fmt);
249            vsnprintf(buf+i, MSGBUFSIZE-i, fmt, va);
250            va_end(va);
251            if(!msg_stack)
252                    msg_stack = xmalloc(sizeof(*msg_stack));
253            else
254            {
255                    msg_stack = xrealloc(msg_stack, (nmsg_stack+1)*sizeof(*msg_stack));
256            }
257            msg_stack[nmsg_stack].msg = buf;
258            msg_stack[nmsg_stack].severity = severity;
259            nmsg_stack++;
260    }
261    
262    /*
263     **************************************************************************
264   * Read the rcs file   * Read the rcs file
265   **************************************************************************   **************************************************************************
266   */   */
267  rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)  static rcsfile_t *get_rcsfile(const char *cvsroot, const char *module, const char *file)
268  {  {
269          char *cmd = NULL;          char *cmd = NULL;
270          int rv;          int rv;
# Line 261  Line 315 
315   * Sort and find helpers   * Sort and find helpers
316   **************************************************************************   **************************************************************************
317   */   */
318  int count_dots(const char *s)  static int count_dots(const char *s)
319  {  {
320          int i;          int i;
321          for(i = 0; *s; s++)          for(i = 0; *s; s++)
# Line 272  Line 326 
326          return i;          return i;
327  }  }
328    
329  int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)  static int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
330  {  {
331          int d1, d2;          int d1, d2;
332          char *c1, *c2;          char *c1, *c2;
# Line 438  Line 492 
492          return r;          return r;
493  }  }
494    
495  void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)  static int sort_branch_height(const void *b1, const void *b2)
496    {
497            return (*(branch_t **)b1)->nrevs - (*(branch_t **)b2)->nrevs;
498    }
499    
500    static void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
501  {  {
502          branch_t *b;          branch_t *b;
503          dtext_t *text;          dtext_t *text;
# Line 448  Line 507 
507    
508          if(head->flag)          if(head->flag)
509          {          {
510                  fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);                  stack_msg(MSG_ERR, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
511                  return;                  return;
512          }          }
513          head->flag++;          head->flag++;
# Line 482  Line 541 
541                                  currev->branches[currev->nbranches] = (*bl)[btag];                                  currev->branches[currev->nbranches] = (*bl)[btag];
542                                  currev->nbranches++;                                  currev->nbranches++;
543                          }                          }
544                            if(conf.branch_resort)
545                                    qsort(currev->branches, currev->nbranches, sizeof(currev->branches[0]), sort_branch_height);
546                  }                  }
547    
548                  /* Walk through the next list */                  /* Walk through the next list */
# Line 491  Line 552 
552                  head = find_delta(sdl, nsdl, head->next);                  head = find_delta(sdl, nsdl, head->next);
553                  if(!head)                  if(!head)
554                  {                  {
555                          fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);                          stack_msg(MSG_ERR, "Next revision (%s) not found in deltalist\n", head->next->rev);
556                          return;                          return;
557                  }                  }
558                  if(head->flag)                  if(head->flag)
559                  {                  {
560                          fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);                          stack_msg(MSG_ERR, "Circular reference on '%s'\n", head->rev->rev);
561                          return;                          return;
562                  }                  }
563                  head->flag++;                  head->flag++;
# Line 543  Line 604 
604          head = find_delta(sdelta, nsdelta, rcs->head);          head = find_delta(sdelta, nsdelta, rcs->head);
605          if(!head)          if(!head)
606          {          {
607                  fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);                  stack_msg(MSG_ERR, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
608                  return 0;                  return 0;
609          }          }
610          bl = NULL;          bl = NULL;
# Line 610  Line 671 
671          char *r;          char *r;
672          if(!dots)          if(!dots)
673          {          {
674                  fprintf(stderr, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);                  stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Cannot determine parent branch revision\n", c);
675                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */
676          }          }
677          if(dots & 1)          if(dots & 1)
# Line 621  Line 682 
682                  assert(cptr != NULL);                  assert(cptr != NULL);
683                  if(dots == 1)                  if(dots == 1)
684                  {                  {
685                          fprintf(stderr, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);                          stack_msg(MSG_ERR, "FIXME: previous_rev(\"%s\"): Going beyond top-level?\n", c);
686                          /* FIXME: What is the parent of 1.1? */                          /* FIXME: What is the parent of 1.1? */
687                          cptr[1] = '\0';                          cptr[1] = '\0';
688                          strcat(r, "0");                          strcat(r, "0");
# Line 679  Line 740 
740          return dup_string();          return dup_string();
741  }  }
742    
743  int assign_tags(rcsfile_t *rcs)  static void find_merges_cvsnt(rcsfile_t *rcs)
744    {
745            int i;
746    
747            if(!conf.merge_cvsnt)
748                    return;
749    
750            for(i = 0; i < rcs->nsrev; i++)
751            {
752                    revision_t **r;
753    
754                    if(!rcs->srev[i]->delta->mergepoint)
755                            continue;
756    
757                    r = bsearch(rcs->srev[i]->delta->mergepoint->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
758                    if(!r)
759                            continue;
760                    rcs->merges = xrealloc(rcs->merges, sizeof(rcs->merges[0]) * (rcs->nmerges+1));
761                    rcs->merges[rcs->nmerges].type = TR_REVISION;
762                    rcs->merges[rcs->nmerges].from.rev = *r;
763                    rcs->merges[rcs->nmerges].to.rev = rcs->srev[i];
764                    rcs->nmerges++;
765                    (*r)->stripped = -1;
766                    rcs->srev[i]->stripped = -1;
767            }
768    }
769    
770    static void find_merges(rcsfile_t *rcs)
771  {  {
772          int i;          int i;
         int nr;  
773          int err;          int err;
774            int rcflags = REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0);
775          regex_t *refrom = NULL;          regex_t *refrom = NULL;
776          regex_t *reto = NULL;          regex_t *reto = NULL;
777          regmatch_t *matchfrom = NULL;          regmatch_t *matchfrom = NULL;
778    
779          if(conf.merge_from && conf.merge_from[0] && conf.merge_to && conf.merge_to[0])          if(!conf.merge_from || !conf.merge_from[0] || !conf.merge_to || !conf.merge_to[0])
780                    return;
781    
782            refrom = xmalloc(sizeof(*refrom));
783            reto = xmalloc(sizeof(*reto));
784    
785            /* Compile the 'from' regex match for merge identification */
786            err = regcomp(refrom, conf.merge_from, rcflags);
787            if(err)
788            {
789                    char *msg;
790                    i = regerror(err, refrom, NULL, 0);
791                    msg = xmalloc(i+1);
792                    regerror(err, refrom, msg, i+1);
793                    stack_msg(MSG_WARN, "%s", msg);
794                    xfree(msg);
795                    xfree(refrom);
796                    xfree(reto);
797                    return;
798            }
799            else
800                    matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));
801    
802            for(i = 0; i < rcs->tags->ntags; i++)
803          {          {
804                  refrom = xmalloc(sizeof(*refrom));                  tag_t *t = rcs->tags->tags[i];
                 reto = xmalloc(sizeof(*reto));  
805    
806                  /* Compile the 'from' regex match for merge identification */                  /* Must be revision tags and not detached */
807                  err = regcomp(refrom, conf.merge_from, REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0));                  if(t->rev->isbranch || !t->logrev)
808                  if(err)                          continue;
809    
810                    /* Try to find merge tag matches */
811                    if(!regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))
812                  {                  {
813                          if(!quiet)                          int n;
814                            char *to;
815    
816                            to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);
817                            if(to)
818                          {                          {
819                                  char *msg;                                  err = regcomp(reto, to, rcflags);
820                                  i = regerror(err, refrom, NULL, 0);                                  if(err)
821                                  msg = xmalloc(i+1);                                  {
822                                  regerror(err, refrom, msg, i+1);                                          char *msg;
823                                  fprintf(stderr, "%s\n", msg);                                          i = regerror(err, reto, NULL, 0);
824                                  xfree(msg);                                          msg = xmalloc(i+1);
825                          }                                          regerror(err, reto, msg, i+1);
826                          xfree(refrom);                                          stack_msg(MSG_WARN, "%s", msg);
827                          xfree(reto);                                          xfree(msg);
828                          refrom = NULL;                                  }
829                          reto = NULL;                                  else if(!err)
830                                    {
831                                            for(n = 0; n < rcs->tags->ntags; n++)
832                                            {
833                                                    tag_t *nt = rcs->tags->tags[n];
834                                                    /* From and To never should match the same tag or belong to a branch */
835                                                    if(n == i || nt->rev->isbranch || !nt->logrev)
836                                                            continue;
837    
838                                                    if(!regexec(reto, nt->tag, 0, NULL, 0))
839                                                    {
840                                                            /* Tag matches */
841                                                            rcs->merges = xrealloc(rcs->merges,
842                                                                            sizeof(rcs->merges[0]) * (rcs->nmerges+1));
843                                                            rcs->merges[rcs->nmerges].type = TR_TAG;
844                                                            rcs->merges[rcs->nmerges].to.tag = nt;
845                                                            rcs->merges[rcs->nmerges].from.tag = t;
846                                                            rcs->nmerges++;
847                                                            if(!conf.tag_ignore_merge)
848                                                            {
849                                                                    nt->ignore = 0;
850                                                                    t->ignore = 0;
851                                                            }
852                                                            /* We cannot (should not) match multiple times */
853                                                            if(!conf.merge_findall)
854                                                                    break;
855                                                    }
856                                            }
857                                            regfree(reto);
858                                    }
859                                    xfree(to);
860                            }
861                    }
862            }
863            if(matchfrom)   xfree(matchfrom);
864            if(refrom)      { regfree(refrom); xfree(refrom); }
865            if(reto)        xfree(reto);
866    }
867    
868    static void assign_tags(rcsfile_t *rcs)
869    {
870            int i;
871            int nr;
872            regex_t *regextag = NULL;
873    
874            if(conf.tag_ignore && conf.tag_ignore[0])
875            {
876                    int err;
877                    regextag = xmalloc(sizeof(*regextag));
878                    err = regcomp(regextag, conf.tag_ignore, REG_EXTENDED | REG_NOSUB | (conf.tag_nocase ? REG_ICASE : 0));
879                    if(err)
880                    {
881                            char *msg;
882                            i = regerror(err, regextag, NULL, 0);
883                            msg = xmalloc(i+1);
884                            regerror(err, regextag, msg, i+1);
885                            stack_msg(MSG_WARN, "%s", msg);
886                            xfree(msg);
887                            xfree(regextag);
888                            regextag = NULL;
889                  }                  }
                 else  
                         matchfrom = xmalloc((refrom->re_nsub+1) * sizeof(*matchfrom));  
890          }          }
891    
892          for(i = nr = 0; i < rcs->nbranches; i++)          for(i = nr = 0; i < rcs->nbranches; i++)
# Line 743  Line 917 
917          }          }
918    
919          /* We should have at least two tags (HEAD and MAIN) */          /* We should have at least two tags (HEAD and MAIN) */
920          assert(rcs->tags != 0);          assert(rcs->tags != NULL);
921    
922          for(i = 0; i < rcs->tags->ntags; i++)          for(i = 0; i < rcs->tags->ntags; i++)
923          {          {
# Line 768  Line 942 
942                                  xfree(rev.rev);                                  xfree(rev.rev);
943                                  if(!r)                                  if(!r)
944                                  {                                  {
945                                          if(!quiet)                                          stack_msg(MSG_WARN, "No branch found for tag '%s:%s'", t->tag, t->rev->branch);
                                                 fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);  
946                                  }                                  }
947                                  else                                  else
948                                  {                                  {
# Line 799  Line 972 
972                          revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);                          revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
973                          if(!r)                          if(!r)
974                          {                          {
975                                  if(!quiet)                                  stack_msg(MSG_WARN, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
                                         fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);  
976                          }                          }
977                          else                          else
978                          {                          {
# Line 819  Line 991 
991                                                  rr->tags[rr->ntags] = t;                                                  rr->tags[rr->ntags] = t;
992                                          rr->ntags++;                                          rr->ntags++;
993                                  }                                  }
994                            }
995    
996                                  /* Try to find merge tag matches */                          if(conf.tag_negate)
997                                  if(refrom && !regexec(refrom, t->tag, refrom->re_nsub+1, matchfrom, 0))                                  t->ignore++;
998                                  {                          /* Mark the tag ignored if it matches the configuration */
999                                          int n;                          if(regextag && !regexec(regextag, t->tag, 0, NULL, 0))
1000                                          char *to;                          {
1001                                    if(conf.tag_negate)
1002                                          to = build_regex(refrom->re_nsub+1, matchfrom, t->tag);                                          t->ignore--;
1003                                          if(to)                                  else
1004                                          {                                          t->ignore++;
                                                 err = regcomp(reto, to, REG_EXTENDED | (conf.merge_nocase ? REG_ICASE : 0));  
                                                 if(err && !quiet)  
                                                 {  
                                                         char *msg;  
                                                         i = regerror(err, reto, NULL, 0);  
                                                         msg = xmalloc(i+1);  
                                                         regerror(err, reto, msg, i+1);  
                                                         fprintf(stderr, "%s\n", msg);  
                                                 }  
                                                 else if(!err)  
                                                 {  
                                                         for(n = 0; n < rcs->tags->ntags; n++)  
                                                         {  
                                                                 /* From and To never should match the same tag */  
                                                                 if(n == i)  
                                                                         continue;  
   
                                                                 if(!regexec(reto, rcs->tags->tags[n]->tag, 0, NULL, REG_NOSUB))  
                                                                 {  
                                                                         /* Tag matches */  
                                                                         rcs->merges = xrealloc(rcs->merges,  
                                                                                         sizeof(rcs->merges[0]) * (rcs->nmerges+1));  
                                                                         rcs->merges[rcs->nmerges].to = rcs->tags->tags[n];  
                                                                         rcs->merges[rcs->nmerges].from = t;  
                                                                         rcs->nmerges++;  
                                                                         /* We cannot (should not) match multiple times */  
                                                                         n = rcs->tags->ntags;  
                                                                 }  
                                                         }  
                                                         regfree(reto);  
                                                 }  
                                                 xfree(to);  
                                         }  
                                 }  
1005                          }                          }
1006                  }                  }
1007          }          }
# Line 879  Line 1018 
1018                  *b = rcs->branches[0];                  *b = rcs->branches[0];
1019                  rcs->branches[0] = t;                  rcs->branches[0] = t;
1020          }          }
1021          if(matchfrom)   xfree(matchfrom);  
1022          if(refrom)      { regfree(refrom); xfree(refrom); }          if(regextag)
1023          if(reto)        xfree(reto);          {
1024          return 1;                  regfree(regextag);
1025                    xfree(regextag);
1026            }
1027  }  }
1028    
1029  /*  /*
# Line 936  Line 1077 
1077          time_t t;          time_t t;
1078          char *buf;          char *buf;
1079          int nbuf;          int nbuf;
1080            char *env;
1081    
1082          memset(&tm, 0, sizeof(tm));          memset(&tm, 0, sizeof(tm));
1083          n = sscanf(d, "%d.%d.%d.%d.%d.%d",          n = sscanf(d, "%d.%d.%d.%d.%d.%d",
# Line 944  Line 1086 
1086          tm.tm_mon--;          tm.tm_mon--;
1087          if(tm.tm_year > 1900)          if(tm.tm_year > 1900)
1088                  tm.tm_year -= 1900;                  tm.tm_year -= 1900;
1089    
1090            env = getenv("TZ");
1091            putenv("TZ=UTC0");
1092          t = mktime(&tm);          t = mktime(&tm);
1093            if(env)
1094            {
1095                    char *c = xmalloc(strlen(env) + 3 + 1); /* Extra space for TZ and = */
1096                    sprintf(c, "TZ=%s", env);
1097                    putenv(c);
1098                    xfree(c);
1099            }
1100            else
1101                    putenv("TZ");
1102    
1103          if(n != 6 || t == (time_t)(-1))          if(n != 6 || t == (time_t)(-1))
1104          {          {
1105                  add_string_str("<invalid date>");                  add_string_str("<invalid date>");
# Line 952  Line 1107 
1107          }          }
1108    
1109          tmp = localtime(&t);          tmp = localtime(&t);
1110          nbuf = strlen(conf.date_format) * 16;   /* Should be enough to hold all types of expansions */          nbuf = (strlen(conf.date_format)+1) * 16;       /* Should be enough to hold all types of expansions */
1111          buf = xmalloc(nbuf);          buf = xmalloc(nbuf);
1112          strftime(buf, nbuf, conf.date_format, tmp);          strftime(buf, nbuf, conf.date_format, tmp);
1113          add_string_str(buf);          add_string_str(buf);
# Line 978  Line 1133 
1133                                  if(maxlen < 0)                                  if(maxlen < 0)
1134                                          *cptr++ = ' ';                                          *cptr++ = ' ';
1135                                  else                                  else
1136                                          cptr += sprintf(cptr, "<br>");                                          cptr += sprintf(cptr, "<br%s>", conf.html_level == HTMLLEVEL_X ? " /" : "");
1137                          }                          }
1138                  }                  }
1139                  else if(*s >= 0x7f || *s == '"')                  else if(*s >= 0x7f || *s == '"')
# Line 987  Line 1142 
1142                          cptr += sprintf(cptr, "&lt;");                          cptr += sprintf(cptr, "&lt;");
1143                  else if(*s == '>')                  else if(*s == '>')
1144                          cptr += sprintf(cptr, "&gt;");                          cptr += sprintf(cptr, "&gt;");
1145                    else if(*s == '&')
1146                            cptr += sprintf(cptr, "&amp;");
1147                    else if(*s == '"')
1148                            cptr += sprintf(cptr, "&quot;");
1149                  else                  else
1150                          *cptr++ = *s;                          *cptr++ = *s;
1151                  l++;                  l++;
# Line 1007  Line 1166 
1166          xfree(str);          xfree(str);
1167  }  }
1168    
1169  char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)  static char *expand_string(const char *s, rcsfile_t *rcs, revision_t *r, rev_t *rev, rev_t *prev, tag_t *tag)
1170  {  {
1171          char nb[32];          char nb[32];
1172          char nr[32];          char nr[32];
# Line 1021  Line 1180 
1180    
1181          zap_string();          zap_string();
1182    
1183          sprintf(nb, "%d", rcs->nbranches);          sprintf(nb, "%d", rcs->nbranches + rcs->nfolds);
1184          sprintf(nr, "%d", rcs->nsrev);          sprintf(nr, "%d", rcs->nsrev);
1185          for(; *s; s++)          for(; *s; s++)
1186          {          {
# Line 1141  Line 1300 
1300                                  for(; *s; s++)                                  for(; *s; s++)
1301                                  {                                  {
1302                                          if(*s == '%' && s[1] == ')')                                          if(*s == '%' && s[1] == ')')
1303                                            {
1304                                                    s++;
1305                                                  break;                                                  break;
1306                                            }
1307                                  }                                  }
1308                                  if(!*s)                                  if(!*s)
1309                                  {                                  {
1310                                          s--;    /* To end outer loop */                                          s--;    /* To end outer loop */
1311                                          if(!quiet)                                          stack_msg(MSG_WARN, "string expand: Missing %%) in expansion");
                                                 fprintf(stderr, "string expand: Missing %%) in expansion\n");  
1312                                  }                                  }
1313                                  break;                                  break;
1314                          case ')':                          case ')':
# Line 1235  Line 1396 
1396  static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)  static void draw_rbox(gdImagePtr im, int x1, int y1, int x2, int y2, int r, color_t *color, color_t *bgcolor)
1397  {  {
1398          int r2 = 2*r;          int r2 = 2*r;
1399            if(!r)
1400                    gdImageFilledRectangle(im, x1, y1, x2, y2, bgcolor->id);
1401    #ifdef HAVE_GDIMAGEFILLEDARC
1402            else
1403            {
1404                    gdImageFilledArc(im, x1+r, y1+r, r2, r2, 180, 270, bgcolor->id, gdArc);
1405                    gdImageFilledArc(im, x2-r, y1+r, r2, r2, 270, 360, bgcolor->id, gdArc);
1406                    gdImageFilledArc(im, x1+r, y2-r, r2, r2,  90, 180, bgcolor->id, gdArc);
1407                    gdImageFilledArc(im, x2-r, y2-r, r2, r2,   0,  90, bgcolor->id, gdArc);
1408                    gdImageFilledRectangle(im, x1+r, y1, x2-r, y1+r, bgcolor->id);
1409                    gdImageFilledRectangle(im, x1, y1+r, x2, y2-r, bgcolor->id);
1410                    gdImageFilledRectangle(im, x1+r, y2-r, x2-r, y2, bgcolor->id);
1411            }
1412    #endif
1413          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);          gdImageLine(im, x1+r, y1, x2-r, y1, color->id);
1414          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);          gdImageLine(im, x1+r, y2, x2-r, y2, color->id);
1415          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);          gdImageLine(im, x1, y1+r, x1, y2-r, color->id);
# Line 1257  Line 1432 
1432                          gdImageArc(im, x2-r, y2-r+1, r2, r2,   0,  90, black_color.id);                          gdImageArc(im, x2-r, y2-r+1, r2, r2,   0,  90, black_color.id);
1433                  }                  }
1434                  gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);                  gdImageArc(im, x2-r, y2-r, r2, r2,   0,  90, color->id);
1435          }  #if !defined(NOGDFILL) && !defined(HAVE_GDIMAGEFILLEDARC)
1436  #ifndef NOGDFILL                  /* BUG: We clip manually because libgd segfaults on out of bound values */
1437          gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);                  if((x1+x2)/2 >= 0 && (x1+x2)/2 < gdImageSX(im) && (y1+y2)/2 >= 0 && (y1+y2)/2 < gdImageSY(im))
1438                            gdImageFillToBorder(im, (x1+x2)/2, (y1+y2)/2, color->id, bgcolor->id);
1439  #endif  #endif
1440            }
1441  }  }
1442    
1443  static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)  static void draw_string(gdImagePtr im, char *s, font_t *f, int x, int y, int align, color_t *c)
# Line 1341  Line 1518 
1518          }          }
1519          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);          draw_rbox(im, lx, ty, rx, ty+r->h, 0, &conf.rev_color, &conf.rev_bgcolor);
1520          ty += conf.rev_tspace;          ty += conf.rev_tspace;
1521          draw_string(im, r->rev->rev, &conf.rev_font, x2, ty, ALIGN_HC, &conf.rev_color);          if(!conf.rev_hidenumber)
1522          ty += get_sheight(r->rev->rev, &conf.rev_font);          {
1523                    draw_string(im, r->rev->rev, &conf.rev_font, x2, ty, ALIGN_HC, &conf.rev_color);
1524                    ty += get_sheight(r->rev->rev, &conf.rev_font);
1525            }
1526          draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, &conf.rev_text_color);          draw_stringnl(im, r->revtext, &conf.rev_text_font, x2, ty, ALIGN_HC, &conf.rev_text_color);
1527          ty += get_sheight(r->revtext, &conf.rev_text_font);          ty += get_sheight(r->revtext, &conf.rev_text_font);
1528          for(i = 0; i < r->ntags; i++)          for(i = 0; i < r->ntags; i++)
# Line 1374  Line 1554 
1554          }          }
1555          draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);          draw_rbox(im, lx+xp, yp, rx+xp, yp+b->h, 5, &conf.branch_color, &conf.branch_bgcolor);
1556          yy = conf.branch_tspace;          yy = conf.branch_tspace;
1557          draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_color);          if(!b->nfolds)
         yy += get_sheight(b->branch->branch, &conf.branch_font);  
         for(i = 0; i < b->ntags; i++)  
1558          {          {
1559                  draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_tag_color);                  if(!conf.rev_hidenumber)
1560                  yy += get_sheight(b->tags[i]->tag, &conf.branch_font);                  {
1561                            draw_string(im, b->branch->branch, &conf.branch_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_color);
1562                            yy += get_sheight(b->branch->branch, &conf.branch_font);
1563                    }
1564                    for(i = 0; i < b->ntags; i++)
1565                    {
1566                            draw_string(im, b->tags[i]->tag, &conf.branch_tag_font, x2+xp, yp+yy, ALIGN_HC, &conf.branch_tag_color);
1567                            yy += get_sheight(b->tags[i]->tag, &conf.branch_tag_font);
1568                    }
1569            }
1570            else
1571            {
1572                    int y1, y2;
1573                    int tx = lx + b->fw + conf.branch_lspace;
1574                    int nx = tx - get_swidth(" ", &conf.branch_font);
1575                    draw_string(im, b->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1576                    y1 = get_sheight(b->branch->branch, &conf.branch_font);
1577                    draw_string(im, b->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1578                    y2 = get_sheight(b->tags[0]->tag, &conf.branch_font);
1579                    yy += MAX(y1, y2);
1580                    for(i = 0; i < b->nfolds; i++)
1581                    {
1582                            draw_string(im, b->folds[i]->branch->branch, &conf.branch_font, nx+xp, yp+yy, ALIGN_HR, &conf.branch_color);
1583                            y1 = get_sheight(b->folds[i]->branch->branch, &conf.branch_font);
1584                            draw_string(im, b->folds[i]->tags[0]->tag, &conf.branch_tag_font, tx+xp, yp+yy, ALIGN_HL, &conf.branch_tag_color);
1585                            y2 = get_sheight(b->folds[i]->tags[0]->tag, &conf.branch_tag_font);
1586                            yy += MAX(y1, y2);
1587                    }
1588          }          }
1589  }  }
1590    
# Line 1396  Line 1601 
1601          line[1] = gdTransparent;          line[1] = gdTransparent;
1602          line[3] = conf.rev_color.id;          line[3] = conf.rev_color.id;
1603    
1604            /* Trivial clip the branch */
1605            if(conf.left_right)
1606            {
1607                    if(b->cx > gdImageSX(im) || b->cx+b->tw < 0 || b->y-b->th/2 > gdImageSY(im) || b->y+b->th/2 < 0)
1608                            return;
1609            }
1610            else
1611            {
1612                    if(b->cx-b->tw/2 > gdImageSX(im) || b->cx+b->tw/2 < 0 || b->y > gdImageSY(im) || b->y+b->th < 0)
1613                            return;
1614            }
1615    
1616          draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);          draw_branch_box(im, b, 0, conf.left_right ? b->y - b->h/2 : b->y);
1617    
1618          if(conf.left_right)          if(conf.left_right)
# Line 1406  Line 1623 
1623                          for(i = 0; i < b->nrevs; i++)                          for(i = 0; i < b->nrevs; i++)
1624                          {                          {
1625                                  revision_t *r = b->revs[i];                                  revision_t *r = b->revs[i];
1626                                  gdImageSetStyle(im, line, r->stripped ? 4 : 1);                                  gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1627                                  gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);                                  gdImageLine(im, xx, r->y, r->cx+r->w, r->y, gdStyled);
1628                                  for(sign = l = 1; l < conf.thick_lines; l++)                                  for(sign = l = 1; l < conf.thick_lines; l++)
1629                                  {                                  {
# Line 1417  Line 1634 
1634                                  draw_rev(im, r);                                  draw_rev(im, r);
1635                                  xx = r->cx;                                  xx = r->cx;
1636                          }                          }
1637                          if(conf.branch_dupbox)                          if(conf.branch_dupbox && b->nrevs)
1638                          {                          {
1639                                  i = b->cx - b->tw + b->w;                                  i = b->cx - b->tw + b->w;
1640                                  gdImageLine(im, xx, b->y, i+b->w, b->y, conf.rev_color.id);                                  gdImageLine(im, xx, b->y, i+b->w, b->y, conf.rev_color.id);
# Line 1436  Line 1653 
1653                          for(i = 0; i < b->nrevs; i++)                          for(i = 0; i < b->nrevs; i++)
1654                          {                          {
1655                                  revision_t *r = b->revs[i];                                  revision_t *r = b->revs[i];
1656                                  gdImageSetStyle(im, line, r->stripped ? 4 : 1);                                  gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1657                                  gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);                                  gdImageLine(im, xx, r->y, r->cx, r->y, gdStyled);
1658                                  for(sign = l = 1; l < conf.thick_lines; l++)                                  for(sign = l = 1; l < conf.thick_lines; l++)
1659                                  {                                  {
# Line 1447  Line 1664 
1664                                  draw_rev(im, r);                                  draw_rev(im, r);
1665                                  xx = r->cx + r->w;                                  xx = r->cx + r->w;
1666                          }                          }
1667                          if(conf.branch_dupbox)                          if(conf.branch_dupbox && b->nrevs)
1668                          {                          {
1669                                  i = b->cx + b->tw - b->w;                                  i = b->cx + b->tw - b->w;
1670                                  gdImageLine(im, xx, b->y, i, b->y, conf.rev_color.id);                                  gdImageLine(im, xx, b->y, i, b->y, conf.rev_color.id);
# Line 1469  Line 1686 
1686                          for(i = 0; i < b->nrevs; i++)                          for(i = 0; i < b->nrevs; i++)
1687                          {                          {
1688                                  revision_t *r = b->revs[i];                                  revision_t *r = b->revs[i];
1689                                  gdImageSetStyle(im, line, r->stripped ? 4 : 1);                                  gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1690                                  gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);                                  gdImageLine(im, r->cx, yy, r->cx, r->y+r->h, gdStyled);
1691                                  for(sign = l = 1; l < conf.thick_lines; l++)                                  for(sign = l = 1; l < conf.thick_lines; l++)
1692                                  {                                  {
# Line 1480  Line 1697 
1697                                  draw_rev(im, r);                                  draw_rev(im, r);
1698                                  yy = r->y;                                  yy = r->y;
1699                          }                          }
1700                          if(conf.branch_dupbox)                          if(conf.branch_dupbox && b->nrevs)
1701                          {                          {
1702                                  i = b->y - b->th + b->h;                                  i = b->y - b->th + b->h;
1703                                  gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);                                  gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
# Line 1499  Line 1716 
1716                          for(i = 0; i < b->nrevs; i++)                          for(i = 0; i < b->nrevs; i++)
1717                          {                          {
1718                                  revision_t *r = b->revs[i];                                  revision_t *r = b->revs[i];
1719                                  gdImageSetStyle(im, line, r->stripped ? 4 : 1);                                  gdImageSetStyle(im, line, r->stripped > 0 ? 4 : 1);
1720                                  gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);                                  gdImageLine(im, r->cx, yy, r->cx, r->y, gdStyled);
1721                                  for(sign = l = 1; l < conf.thick_lines; l++)                                  for(sign = l = 1; l < conf.thick_lines; l++)
1722                                  {                                  {
# Line 1510  Line 1727 
1727                                  draw_rev(im, r);                                  draw_rev(im, r);
1728                                  yy = r->y + r->h;                                  yy = r->y + r->h;
1729                          }                          }
1730                          if(conf.branch_dupbox)                          if(conf.branch_dupbox && b->nrevs)
1731                          {                          {
1732                                  i = b->y + b->th - b->h;                                  i = b->y + b->th - b->h;
1733                                  gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);                                  gdImageLine(im, b->cx, yy, b->cx, i, conf.rev_color.id);
# Line 1565  Line 1782 
1782          }          }
1783  }  }
1784    
1785  static void draw_merges(gdImagePtr im, rcsfile_t *rcs)  static void draw_merges(gdImagePtr im, rcsfile_t *rcs, int dot)
1786  {  {
1787          int i;          int i;
1788          for(i = 0; i < rcs->nmerges; i++)          for(i = 0; i < rcs->nmerges; i++)
1789          {          {
1790                  revision_t *fr = rcs->merges[i].from->logrev;                  revision_t *fr;
1791                  revision_t *tr = rcs->merges[i].to->logrev;                  revision_t *tr;
1792                    int colorid;
1793                  int x1, x2, y1, y2;                  int x1, x2, y1, y2;
1794                  if(!fr || !tr)                  switch(rcs->merges[i].type)
1795                          continue;       /* This can happen with detached tags */                  {
1796                    case TR_TAG:
1797                            fr = rcs->merges[i].from.tag->logrev;
1798                            tr = rcs->merges[i].to.tag->logrev;
1799                            colorid = conf.merge_color.id;
1800                            break;
1801                    case TR_REVISION:
1802                            fr = rcs->merges[i].from.rev;
1803                            tr = rcs->merges[i].to.rev;
1804                            colorid = conf.merge_cvsnt_color.id;
1805                            break;
1806                    default:
1807                            continue;
1808                    }
1809                    if(!fr || !tr || fr == tr)
1810                            continue;       /* This can happen with detached tags and self-references */
1811                  if(conf.left_right)                  if(conf.left_right)
1812                  {                  {
1813                          if(fr->y < tr->y)                          if(fr->branch == tr->branch)
1814                          {                          {
1815                                  y1 = fr->y + fr->h/2;                                  y1 = fr->y - fr->h/2;
1816                                  y2 = tr->y - tr->h/2;                                  y2 = tr->y - tr->h/2;
1817                          }                          }
1818                          else                          else
1819                          {                          {
1820                                  y1 = fr->y - fr->h/2;                                  if(fr->y < tr->y)
1821                                  y2 = tr->y + tr->h/2;                                  {
1822                                            y1 = fr->y + fr->h/2;
1823                                            y2 = tr->y - tr->h/2;
1824                                    }
1825                                    else
1826                                    {
1827                                            y1 = fr->y - fr->h/2;
1828                                            y2 = tr->y + tr->h/2;
1829                                    }
1830                          }                          }
1831                          x1 = fr->cx + fr->w/2;                          x1 = fr->cx + fr->w/2;
1832                          x2 = tr->cx + tr->w/2;                          x2 = tr->cx + tr->w/2;
1833                  }                  }
1834                  else                  else
1835                  {                  {
1836                          if(fr->cx < tr->cx)                          if(fr->branch == tr->branch)
1837                          {                          {
1838                                  x1 = fr->cx + fr->w/2;                                  x1 = fr->cx - fr->w/2;
1839                                  x2 = tr->cx - tr->w/2;                                  x2 = tr->cx - tr->w/2;
1840                          }                          }
1841                          else                          else
1842                          {                          {
1843                                  x1 = fr->cx - fr->w/2;                                  if(fr->cx < tr->cx)
1844                                  x2 = tr->cx + tr->w/2;                                  {
1845                                            x1 = fr->cx + fr->w/2;
1846                                            x2 = tr->cx - tr->w/2;
1847                                    }
1848                                    else
1849                                    {
1850                                            x1 = fr->cx - fr->w/2;
1851                                            x2 = tr->cx + tr->w/2;
1852                                    }
1853                            }
1854                            if(rcs->merges[i].type == TR_TAG)
1855                            {
1856                                    y1 = fr->y + rcs->merges[i].from.tag->yofs;
1857                                    y2 = tr->y + rcs->merges[i].to.tag->yofs;
1858                            }
1859                            else
1860                            {
1861                                    y1 = fr->y + fr->h/2;
1862                                    y2 = tr->y + tr->h/2;
1863                          }                          }
                         y1 = fr->y + fr->h/2;  
                         y2 = tr->y + tr->h/2;  
1864                  }                  }
1865                  gdImageArc(im, x2, y2, 8, 8, 0, 360, conf.merge_color.id);                  if(dot && !conf.merge_arrows)
                 gdImageFillToBorder(im, x2, y2, conf.merge_color.id, conf.merge_color.id);  
                 if(conf.left_right)  
1866                  {                  {
1867                          if(y1 > y2)                          int o = conf.left_right ? 1 : 0;
1868                            gdImageArc(im, x2, y2+o, 8, 8, 0, 360, colorid);
1869                            /* BUG: We clip manually because libgd segfaults on out of bound values */
1870                            if(x2+1 >= 0 && x2+1 < gdImageSX(im) && y2+o+1 >= 0 && y2+o+1 < gdImageSY(im))
1871                                    gdImageFillToBorder(im, x2+1, y2+o+1, colorid, colorid);
1872                    }
1873                    else if(dot && conf.merge_arrows)
1874                    {
1875                            /*
1876                             * Arrow patch from Haroon Rafique <haroon.rafique@utoronto.ca>
1877                             * Slightly adapted to be more configurable.
1878                             */
1879                            int sx, sy;     /* start point coordinates */
1880                            int ex, ey;     /* end point coordinates */
1881                            double theta;
1882                            double u1, v1, u2, v2;
1883                            gdPoint p[3];
1884    
1885                            sx = x1; sy = y1;
1886                            ex = x2; ey = y2;
1887                            if(conf.left_right)
1888                          {                          {
1889                                  gdImageLine(im, x1, y1, x1, y1-3, conf.merge_color.id);                                  if(fr->branch == tr->branch)
1890                                  gdImageLine(im, x2, y2+1, x2, y2+3+1, conf.merge_color.id);                                  {
1891                                  gdImageLine(im, x1, y1-3, x2, y2+3+1, conf.merge_color.id);                                          int yy = (y1 < y2 ? y1 : y2) - 5;
1892                                            /* line from (x1,yy) to (x2,yy) */
1893                                            sy = ey = yy;
1894                                    }
1895                                    else
1896                                    {
1897                                            if(y1 > y2)
1898                                            {
1899                                                    /* line from (x1,y1-3) to (x2,y2+3+1) */
1900                                                    sy = y1-3;
1901                                                    ey = y2+3+1;
1902                                            }
1903                                            else
1904                                            {
1905                                                    /* line from (x1,y1+3+1) to (x2,y2-3) */
1906                                                    sy = y1+3+1;
1907                                                    ey = y2-3;
1908                                            }
1909                                    }
1910                          }                          }
1911                          else                          else
1912                          {                          {
1913                                  gdImageLine(im, x1, y1+1, x1, y1+3+1, conf.merge_color.id);                                  if(fr->branch == tr->branch)
1914                                  gdImageLine(im, x2, y2, x2, y2-3, conf.merge_color.id);                                  {
1915                                  gdImageLine(im, x1, y1+3+1, x2, y2-3, conf.merge_color.id);                                          int xx = (x1 < x2 ? x1 : x2) - 5;
1916                                            /* line from (xx,y1) to (xx,y2) */
1917                                            sx = ex = xx;
1918                                    }
1919                                    else
1920                                    {
1921                                            if(x1 > x2)
1922                                            {
1923                                                    /* line from (x1-3,y1) to (x2+3,y2) */
1924                                                    sx = x1-3;
1925                                                    ex = x2+3;
1926                                            }
1927                                            else
1928                                            {
1929                                                    /* line from (x1+3,y1) to (x2-3,y2) */
1930                                                    sx = x1+3;
1931                                                    ex = x2-3;
1932                                            }
1933                                    }
1934                          }                          }
1935                            /*
1936                             * inspiration for arrow code comes from arrows.c in the
1937                             * graphviz package. Thank you, AT&T
1938                             */
1939                            /* theta in radians */
1940                            theta = atan2((double)(sy-ey), (double)(sx-ex));
1941                            u1 = (double)conf.arrow_length * cos(theta);
1942                            v1 = (double)conf.arrow_length * sin(theta);
1943                            u2 = (double)conf.arrow_width  * cos(theta + M_PI/2.0);
1944                            v2 = (double)conf.arrow_width  * sin(theta + M_PI/2.0);
1945                            /* points of polygon (triangle) */
1946                            p[0].x = ROUND(ex + u1 - u2);
1947                            p[0].y = ROUND(ey + v1 - v2);
1948                            p[1].x = ex;
1949                            p[1].y = ey;
1950                            p[2].x = ROUND(ex + u1 + u2);
1951                            p[2].y = ROUND(ey + v1 + v2);
1952                            /* draw the polygon (triangle) */
1953                            gdImageFilledPolygon(im, p, 3, colorid);
1954                  }                  }
1955                  else                  else
1956                  {                  {
1957                          if(x1 > x2)                          if(conf.left_right)
1958                          {                          {
1959                                  gdImageLine(im, x1, y1, x1-3, y1, conf.merge_color.id);                                  if(fr->branch == tr->branch)
1960                                  gdImageLine(im, x2, y2, x2+3, y2, conf.merge_color.id);                                  {
1961                                  gdImageLine(im, x1-3, y1, x2+3, y2, conf.merge_color.id);                                          int yy = (y1 < y2 ? y1 : y2) - 5;
1962                                            gdImageLine(im, x1, y1, x1, yy, colorid);
1963                                            gdImageLine(im, x2, y2, x2, yy, colorid);
1964                                            gdImageLine(im, x1, yy, x2, yy, colorid);
1965                                    }
1966                                    else
1967                                    {
1968                                            if(y1 > y2)
1969                                            {
1970                                                    gdImageLine(im, x1, y1, x1, y1-3, colorid);
1971                                                    gdImageLine(im, x2, y2+1, x2, y2+3+1, colorid);
1972                                                    gdImageLine(im, x1, y1-3, x2, y2+3+1, colorid);
1973                                            }
1974                                            else
1975                                            {
1976                                                    gdImageLine(im, x1, y1+1, x1, y1+3+1, colorid);
1977                                                    gdImageLine(im, x2, y2, x2, y2-3, colorid);
1978                                                    gdImageLine(im, x1, y1+3+1, x2, y2-3, colorid);
1979                                            }
1980                                    }
1981                          }                          }
1982                          else                          else
1983                          {                          {
1984                                  gdImageLine(im, x1, y1, x1+3, y1, conf.merge_color.id);                                  if(fr->branch == tr->branch)
1985                                  gdImageLine(im, x2, y2, x2-3, y2, conf.merge_color.id);                                  {
1986                                  gdImageLine(im, x1+3, y1, x2-3, y2, conf.merge_color.id);                                          int xx = (x1 < x2 ? x1 : x2) - 5;
1987                                            gdImageLine(im, xx, y1, x1, y1, colorid);
1988                                            gdImageLine(im, xx, y2, x2, y2, colorid);
1989                                            gdImageLine(im, xx, y1, xx, y2, colorid);
1990                                    }
1991                                    else
1992                                    {
1993                                            if(x1 > x2)
1994                                            {
1995                                                    gdImageLine(im, x1, y1, x1-3, y1, colorid);
1996                                                    gdImageLine(im, x2, y2, x2+3, y2, colorid);
1997                                                    gdImageLine(im, x1-3, y1, x2+3, y2, colorid);
1998                                            }
1999                                            else
2000                                            {
2001                                                    gdImageLine(im, x1, y1, x1+3, y1, colorid);
2002                                                    gdImageLine(im, x2, y2, x2-3, y2, colorid);
2003                                                    gdImageLine(im, x1+3, y1, x2-3, y2, colorid);
2004                                            }
2005                                    }
2006                          }                          }
2007                  }                  }
2008          }          }
2009  }  }
2010    
2011    static void draw_messages(gdImagePtr im, int offset)
2012    {
2013            int i;
2014    
2015            for(i = 0; i < nmsg_stack; i++)
2016            {
2017                    draw_stringnl(im, msg_stack[i].msg, &conf.msg_font, conf.margin_left, offset, ALIGN_HL|ALIGN_VT, &conf.msg_color);
2018                    offset += msg_stack[i].h;
2019            }
2020    }
2021    
2022  static void alloc_color(gdImagePtr im, color_t *c)  static void alloc_color(gdImagePtr im, color_t *c)
2023  {  {
2024          c->id = gdImageColorAllocate(im, c->r, c->g, c->b);          c->id = gdImageColorAllocate(im, c->r, c->g, c->b);
2025  }  }
2026    
2027  gdImagePtr make_image(rcsfile_t *rcs)  static gdImagePtr make_image(rcsfile_t *rcs)
2028  {  {
2029          gdImagePtr im;          gdImagePtr im;
2030          int i;          int i;
2031          char *cptr;          char *cptr;
2032            int w, h;
2033            int subx = 0, suby = 0;
2034            int subw, subh;
2035            int msgh = 0;
2036    
2037            if(subtree_branch)
2038            {
2039                    subw = 0;
2040                    subh = 0;
2041                    if(subtree_rev)
2042                    {
2043                            for(i = 0; i < subtree_rev->nbranches; i++)
2044                                    calc_subtree_size(subtree_rev->branches[i], &subx, &suby, &subw, &subh);
2045                    }
2046                    else
2047                            calc_subtree_size(subtree_branch, &subx, &suby, &subw, &subh);
2048            }
2049            else
2050            {
2051                    subw = rcs->tw;
2052                    subh = rcs->th;
2053            }
2054    
2055          cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);          cptr = expand_string(conf.title, rcs, NULL, NULL, NULL, NULL);
2056            w = subw + conf.margin_left + conf.margin_right;
2057            h = subh + conf.margin_top + conf.margin_bottom;
2058          i = get_swidth(cptr, &conf.title_font);          i = get_swidth(cptr, &conf.title_font);
2059          if(rcs->tw+conf.margin_left+conf.margin_right > i)          if(i > w)
2060                  i = rcs->tw+conf.margin_left+conf.margin_right;                  w = i;
2061          im = gdImageCreate(i, rcs->th+conf.margin_top+conf.margin_bottom);  
2062            if(!quiet && nmsg_stack)
2063            {
2064                    int msgw = 0;
2065                    for(i = 0; i < nmsg_stack; i++)
2066                    {
2067                            int ww = msg_stack[i].w = get_swidth(msg_stack[i].msg, &conf.msg_font);
2068                            int hh = msg_stack[i].h = get_sheight(msg_stack[i].msg, &conf.msg_font);
2069                            msgh += hh;
2070                            h += hh;
2071                            if(ww > msgw)
2072                                    msgw = ww;
2073                    }
2074                    if(msgw > w)
2075                            w = msgw;
2076            }
2077    
2078            im = gdImageCreate(w, h);
2079          alloc_color(im, &conf.color_bg);          alloc_color(im, &conf.color_bg);
2080          alloc_color(im, &conf.tag_color);          alloc_color(im, &conf.tag_color);
2081          alloc_color(im, &conf.rev_color);          alloc_color(im, &conf.rev_color);
# Line 1666  Line 2086 
2086          alloc_color(im, &conf.branch_bgcolor);          alloc_color(im, &conf.branch_bgcolor);
2087          alloc_color(im, &conf.title_color);          alloc_color(im, &conf.title_color);
2088          alloc_color(im, &conf.merge_color);          alloc_color(im, &conf.merge_color);
2089            alloc_color(im, &conf.merge_cvsnt_color);
2090            alloc_color(im, &conf.msg_color);
2091          alloc_color(im, &black_color);          alloc_color(im, &black_color);
2092          alloc_color(im, &white_color);          alloc_color(im, &white_color);
2093    
# Line 1673  Line 2095 
2095                  gdImageColorTransparent(im, conf.color_bg.id);                  gdImageColorTransparent(im, conf.color_bg.id);
2096    
2097          if(!conf.merge_front)          if(!conf.merge_front)
2098                  draw_merges(im, rcs);                  draw_merges(im, rcs, 0);
2099    
2100          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
2101                  draw_branch(im, rcs->branches[i]);          {
2102                    if(!rcs->branches[i]->folded && !(subtree_branch && !rcs->branches[i]->subtree_draw))
2103                            draw_branch(im, rcs->branches[i]);
2104            }
2105    
2106            draw_merges(im, rcs, 1);        /* The dots of the merge dest */
2107    
2108          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
2109          {          {
2110                  if(rcs->branches[i]->branchpoint)                  if(rcs->branches[i]->branchpoint)
2111                          draw_connector(im, rcs->branches[i]);                          draw_connector(im, rcs->branches[i]);
2112          }          }
2113    
2114            /* Clear the margins if we have a partial tree */
2115            if(subtree_branch)
2116            {
2117                    gdImageFilledRectangle(im, 0, 0, w-1, conf.margin_top-1, conf.color_bg.id);
2118                    gdImageFilledRectangle(im, 0, 0, conf.margin_left-1, h-1, conf.color_bg.id);
2119                    gdImageFilledRectangle(im, 0, h-conf.margin_bottom, w-1, h-1, conf.color_bg.id);
2120                    gdImageFilledRectangle(im, w-conf.margin_right, 0, w-1, h-1, conf.color_bg.id);
2121            }
2122    
2123          draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);          draw_stringnl(im, cptr, &conf.title_font, conf.title_x, conf.title_y, conf.title_align, &conf.title_color);
2124          xfree(cptr);          xfree(cptr);
2125    
2126          if(conf.merge_front)          if(conf.merge_front)
2127                  draw_merges(im, rcs);                  draw_merges(im, rcs, 0);
2128    
2129            if(!quiet)
2130                    draw_messages(im, h - conf.margin_bottom/2 - msgh);
2131    
2132          return im;          return im;
2133  }  }
# Line 1774  Line 2215 
2215                  /* Recurse to move branches of branched revisions */                  /* Recurse to move branches of branched revisions */
2216                  for(i = b->nrevs-1; i >= 0; i--)                  for(i = b->nrevs-1; i >= 0; i--)
2217                  {                  {
2218                          initial_reposition_branch(b->revs[i], y, h);                          initial_reposition_branch_lr(b->revs[i], y, h);
2219                  }                  }
2220          }          }
2221  }  }
# Line 1813  Line 2254 
2254          *h = y2 - y1;          *h = y2 - y1;
2255  }  }
2256    
2257    static void calc_subtree_size(branch_t *b, int *x, int *y, int *w, int *h)
2258    {
2259            int i, j;
2260    
2261            rect_union(x, y, w, h, b);
2262    
2263            for(i = 0; i < b->nrevs; i++)
2264            {
2265                    for(j = 0; j < b->revs[i]->nbranches; j++)
2266                            calc_subtree_size(b->revs[i]->branches[j], x, y, w, h);
2267            }
2268    }
2269    
2270  static int branch_intersects(int top, int bottom, int left, branch_t *b)  static int branch_intersects(int top, int bottom, int left, branch_t *b)
2271  {  {
2272          int br = b->cx + b->tw/2;          int br = b->cx + b->tw/2;
# Line 1906  Line 2360 
2360                  fprintf(stderr, "kern_tree: moved=%d\n", moved);                  fprintf(stderr, "kern_tree: moved=%d\n", moved);
2361  #endif  #endif
2362          }          }
2363          if(!quiet && !safeguard)          if(!safeguard)
2364                  fprintf(stderr, "kern_tree: safeguard terminated possible infinite loop; please report.\n");                  stack_msg(MSG_WARN, "kern_tree: safeguard terminated possible infinite loop; please report.");
2365          return totalmoved;          return totalmoved;
2366  }  }
2367    
# Line 1920  Line 2374 
2374                  if(r == b->revs[i])                  if(r == b->revs[i])
2375                          return i;                          return i;
2376          }          }
2377          fprintf(stderr, "index_of_revision: Cannot find revision in branch\n");          stack_msg(MSG_ERR, "index_of_revision: Cannot find revision in branch\n");
2378          return 0;          return 0;
2379  }  }
2380    
# Line 1929  Line 2383 
2383          if(l)   *l = br->cx - br->tw/2;          if(l)   *l = br->cx - br->tw/2;
2384          if(r)   *r = br->cx + br->tw/2;          if(r)   *r = br->cx + br->tw/2;
2385          if(t)   *t = br->y;          if(t)   *t = br->y;
2386          if(b)   *b = br->y + br->th + (conf.branch_dupbox ? conf.rev_minline + br->h : 0);          if(b)   *b = br->y + br->th + ((conf.branch_dupbox && br->nrevs) ? conf.rev_minline + br->h : 0);
2387  }  }
2388    
2389  static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)  static void branch_ext_bbox(branch_t *br, int *l, int *r, int *t, int *b)
# Line 2081  Line 2535 
2535    
2536          if(!tagbr->branchpoint || !colbr->branchpoint)          if(!tagbr->branchpoint || !colbr->branchpoint)
2537          {          {
2538                  if(!quiet)                  stack_msg(MSG_WARN, "space_available: Trying to stretch the top?");
                         fprintf(stderr, "space_available: Trying to stretch the top?\n");  
2539                  return 0;                  return 0;
2540          }          }
2541    
# Line 2127  Line 2580 
2580                          branchpoint = ancestor->branchpoint;                          branchpoint = ancestor->branchpoint;
2581                          if(!branchpoint)                          if(!branchpoint)
2582                          {                          {
2583                                  if(!quiet)                                  stack_msg(MSG_WARN, "space_available: No common ancestor?");
                                         fprintf(stderr, "space_available: No common ancestor?\n");  
2584                                  return 0;                                  return 0;
2585                          }                          }
2586                          ancestor = branchpoint->branch;                          ancestor = branchpoint->branch;
# Line 2229  Line 2681 
2681          return col;          return col;
2682  }  }
2683    
2684  void auto_stretch(rcsfile_t *rcs)  static void auto_stretch(rcsfile_t *rcs)
2685  {  {
2686          int i;          int i;
2687          int safeguard;          int safeguard;
# Line 2282  Line 2734 
2734                          }                          }
2735                  }                  }
2736          }          }
2737          if(!quiet && !safeguard)          if(!safeguard)
2738                  fprintf(stderr, "auto_stretch: safeguard terminated possible infinite loop; please report.\n");                  stack_msg(MSG_ERR, "auto_stretch: safeguard terminated possible infinite loop; please report.");
2739  }  }
2740    
2741  void make_layout(rcsfile_t *rcs)  static void fold_branch(rcsfile_t *rcs, revision_t *r)
2742    {
2743            int i, j;
2744            branch_t *btag = NULL;
2745    
2746            for(i = 0; i < r->nbranches; i++)
2747            {
2748                    branch_t *b = r->branches[i];
2749                    if(!b->nrevs && b->ntags < 2)
2750                    {
2751                            /* No commits in this branch and no duplicate tags */
2752                            if(!btag)
2753                                    btag = b;
2754                            else
2755                            {
2756                                    /* We have consecutive empty branches, fold */
2757                                    b->folded = 1;
2758                                    b->folded_to = btag;
2759                                    for(j = 0; j < rcs->nbranches; j++)
2760                                    {
2761                                            if(b == rcs->branches[j])
2762                                            {
2763                                                    /* Zap the branch from the admin */
2764                                                    memmove(&rcs->branches[j],
2765                                                            &rcs->branches[j+1],
2766                                                            (rcs->nbranches - j - 1)*sizeof(rcs->branches[0]));
2767                                                    rcs->nbranches--;
2768                                                    rcs->nfolds++;
2769                                                    break;
2770                                            }
2771    
2772                                    }
2773                                    memmove(&r->branches[i], &r->branches[i+1], (r->nbranches - i - 1)*sizeof(r->branches[0]));
2774                                    r->nbranches--;
2775                                    i--;    /* We have one less now */
2776    
2777                                    /* Add to the fold-list */
2778                                    btag->folds = xrealloc(btag->folds, (btag->nfolds+1) * sizeof(btag->folds[0]));
2779                                    btag->folds[btag->nfolds] = b;
2780                                    btag->nfolds++;
2781                            }
2782                    }
2783                    else
2784                    {
2785                            if(!conf.branch_foldall)
2786                                    btag = NULL;    /* Start a new box */
2787                            /* Recursively fold sub-branches */
2788                            for(j = 0; j < b->nrevs; j++)
2789                                    fold_branch(rcs, b->revs[j]);
2790                    }
2791            }
2792    }
2793    
2794    static void mark_subtree(branch_t *b)
2795    {
2796            int i, j;
2797            b->subtree_draw = 1;
2798            for(i = 0; i < b->nrevs; i++)
2799            {
2800                    for(j = 0; j < b->revs[i]->nbranches; j++)
2801                            mark_subtree(b->revs[i]->branches[j]);
2802            }
2803    }
2804    
2805    static void make_layout(rcsfile_t *rcs)
2806  {  {
2807          int i, j;          int i, j;
2808          int x, y;          int x, y;
# Line 2302  Line 2818 
2818                          branch_t *bp = rcs->branches[i];                          branch_t *bp = rcs->branches[i];
2819                          for(j = fr; j < bp->nrevs-1; j++)                          for(j = fr; j < bp->nrevs-1; j++)
2820                          {                          {
2821                                  if(!bp->revs[j]->ntags && !bp->revs[j]->nbranches)                                  if(!bp->revs[j]->ntags && bp->revs[j]->stripped >= 0 && !bp->revs[j]->nbranches)
2822                                  {                                  {
2823                                            bp->revs[j]->stripped = 1;
2824                                          memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));                                          memmove(&bp->revs[j], &bp->revs[j+1], (bp->nrevs-j-1) * sizeof(bp->revs[0]));
2825                                          bp->nrevs--;                                          bp->nrevs--;
                                         bp->revs[j]->stripped = 1;  
2826                                          j--;                                          j--;
2827                                  }                                  }
2828                          }                          }
2829                  }                  }
2830          }          }
2831    
2832            /* Find the sub-tree(s) we want to see */
2833            if(conf.branch_subtree && conf.branch_subtree[0])
2834            {
2835                    branch_t **b;
2836                    revision_t **r;
2837                    rev_t rev;
2838                    int k;
2839                    char *tag = conf.branch_subtree;
2840    
2841                    /* First translate any symbolic tag to a real branch/revision number */
2842                    if(rcs->tags)
2843                    {
2844                            for(k = 0; k < rcs->tags->ntags; k++)
2845                            {
2846                                    if(!strcmp(conf.branch_subtree, rcs->tags->tags[k]->tag))
2847                                    {
2848                                            if(rcs->tags->tags[k]->rev->isbranch)
2849                                                    tag = rcs->tags->tags[k]->rev->branch;
2850                                            else
2851                                                    tag = rcs->tags->tags[k]->rev->rev;
2852                                            break;
2853                                    }
2854                            }
2855                    }
2856    
2857                    /* Find the corresponding branch */
2858                    rev.branch = tag;
2859                    rev.rev = NULL;
2860                    rev.isbranch = 1;
2861                    b = bsearch(&rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
2862                    if(b)
2863                    {
2864                            if((*b)->branchpoint)
2865                            {
2866                                    subtree_branch = *b;
2867                                    for(k = 0; k < (*b)->branchpoint->nbranches; k++)
2868                                            mark_subtree((*b)->branchpoint->branches[k]);
2869                            }
2870                            /*
2871                             * else -> we want everything.
2872                             * This happens for the top level branch because it has no
2873                             * branchpoint. We do not set the subtree_branch, which then
2874                             * results in drawing the whole tree as if we did not select a
2875                             * particular branch.
2876                             */
2877                    }
2878                    else
2879                    {
2880                            /* Maybe it is a revision we want all subtrees from */
2881                            rev.rev = tag;
2882                            rev.branch = NULL;
2883                            rev.isbranch = 0;
2884                            r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
2885                            if(r)
2886                            {
2887                                    if((*r)->nbranches)
2888                                    {
2889                                            subtree_branch = (*r)->branches[0];
2890                                            subtree_rev = *r;
2891                                            for(k = 0; k < (*r)->nbranches; k++)
2892                                                    mark_subtree((*r)->branches[k]);
2893                                    }
2894                                    /*
2895                                     * else -> we select everything.
2896                                     * This happens for the any revision that has no branches.
2897                                     * We do not set the subtree_branch, which then results in
2898                                     * drawing the whole tree as if we did not select a
2899                                     * particular revision's branches.
2900                                     */
2901                            }
2902                    }
2903            }
2904    
2905            /* Fold all empty branches in one box on the same branchpoint */
2906            if(conf.branch_fold)
2907            {
2908                    for(i = 0; i < rcs->branches[0]->nrevs; i++)
2909                    {
2910                            if(rcs->branches[0]->revs[i]->nbranches > 0)
2911                                    fold_branch(rcs, rcs->branches[0]->revs[i]);
2912                    }
2913            }
2914    
2915            /* Remove all unwanted tags */
2916            for(i = 0; i < rcs->nbranches; i++)
2917            {
2918                    branch_t *bp = rcs->branches[i];
2919                    for(j = 0; j < bp->nrevs; j++)
2920                    {
2921                            revision_t *r = bp->revs[j];
2922                            int k;
2923                            for(k = 0; k < r->ntags; k++)
2924                            {
2925                                    if(r->tags[k]->ignore > 0)
2926                                    {
2927                                            memmove(&r->tags[k], &r->tags[k+1], (r->ntags-k-1) * sizeof(r->tags[0]));
2928                                            r->ntags--;
2929                                            k--;
2930                                    }
2931                            }
2932                    }
2933            }
2934    
2935          /* Calculate the box-sizes of the revisions */          /* Calculate the box-sizes of the revisions */
2936          for(i = 0; i < rcs->nsrev; i++)          for(i = 0; i < rcs->nsrev; i++)
2937          {          {
# Line 2325  Line 2944 
2944                  j = get_swidth(rp->rev->rev, &conf.rev_font);                  j = get_swidth(rp->rev->rev, &conf.rev_font);
2945                  if(j > w)                  if(j > w)
2946                          w = j;                          w = j;
2947                  h = get_sheight(rp->revtext, &conf.rev_text_font) + get_sheight(rp->rev->rev, &conf.rev_font);                  h = get_sheight(rp->revtext, &conf.rev_text_font);
2948                    if(!conf.rev_hidenumber)
2949                            h += get_sheight(rp->rev->rev, &conf.rev_font);
2950                  for(j = 0; j < rp->ntags; j++)                  for(j = 0; j < rp->ntags; j++)
2951                  {                  {
2952                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);                          int ww = get_swidth(rp->tags[j]->tag, &conf.tag_font);
2953                            int th;
2954                          if(ww > w) w = ww;                          if(ww > w) w = ww;
2955                          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;
2956                            rp->tags[j]->yofs = h + th/2 + conf.rev_tspace;
2957                            h += th;
2958                  }                  }
2959                  rp->w = w + conf.rev_lspace + conf.rev_rspace;                  rp->w = w + conf.rev_lspace + conf.rev_rspace;
2960                  rp->h = h + conf.rev_tspace + conf.rev_bspace;                  rp->h = h + conf.rev_tspace + conf.rev_bspace;
# Line 2342  Line 2966 
2966                  branch_t *bp = rcs->branches[i];                  branch_t *bp = rcs->branches[i];
2967                  int w;                  int w;
2968                  int h;                  int h;
2969                  w = get_swidth(bp->branch->branch, &conf.branch_font);                  if(!bp->nfolds)
                 h = get_sheight(bp->branch->branch, &conf.branch_font);  
                 for(j = 0; j < bp->ntags; j++)  
2970                  {                  {
2971                          int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);                          w = get_swidth(bp->branch->branch, &conf.branch_font);
2972                          if(ww > w) w = ww;                          if(conf.rev_hidenumber)
2973                          h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);                                  h = 0;
2974                            else
2975                                    h = get_sheight(bp->branch->branch, &conf.branch_font);
2976                            for(j = 0; j < bp->ntags; j++)
2977                            {
2978                                    int ww = get_swidth(bp->tags[j]->tag, &conf.branch_tag_font);
2979                                    if(ww > w) w = ww;
2980                                    h += get_sheight(bp->tags[j]->tag, &conf.branch_tag_font);
2981                            }
2982                    }
2983                    else
2984                    {
2985                            int h1, h2;
2986                            int w1, w2;
2987                            int fw;
2988                            w1 = get_swidth(bp->branch->branch, &conf.branch_font);
2989                            w1 += get_swidth(" ", &conf.branch_font);
2990                            w2 = get_swidth(bp->tags[0]->tag, &conf.branch_tag_font);
2991                            fw = w1;
2992                            w = w1 + w2;
2993                            h1 = get_sheight(bp->branch->branch, &conf.branch_font);
2994                            h2 = get_sheight(bp->tags[0]->tag, &conf.branch_tag_font);
2995                            h = MAX(h1, h2);
2996                            for(j = 0; j < bp->nfolds; j++)
2997                            {
2998                                    w1 = get_swidth(bp->folds[j]->branch->branch, &conf.branch_font);
2999                                    w1 += get_swidth(" ", &conf.branch_font);
3000                                    w2 = get_swidth(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
3001                                    if(w1 > fw)
3002                                            fw = w1;
3003                                    if(w1 + w2 > w)
3004                                            w = w1 + w2;
3005                                    h1 = get_sheight(bp->folds[j]->branch->branch, &conf.branch_font);
3006                                    h2 = get_sheight(bp->folds[j]->tags[0]->tag, &conf.branch_tag_font);
3007                                    h += MAX(h1, h2);
3008                            }
3009                            bp->fw = fw;
3010                  }                  }
3011                  w += conf.branch_lspace + conf.branch_rspace;                  w += conf.branch_lspace + conf.branch_rspace;
3012                  h += conf.branch_tspace + conf.branch_bspace;                  h += conf.branch_tspace + conf.branch_bspace;
# Line 2362  Line 3020 
3020                                          h = bp->revs[j]->h;                                          h = bp->revs[j]->h;
3021                                  w += bp->revs[j]->w + conf.rev_minline;                                  w += bp->revs[j]->w + conf.rev_minline;
3022                          }                          }
3023                          if(conf.branch_dupbox)                          if(conf.branch_dupbox && bp->nrevs)
3024                                  w += bp->w + conf.rev_minline;                                  w += bp->w + conf.rev_minline;
3025                  }                  }
3026                  else                  else
# Line 2373  Line 3031 
3031                                          w = bp->revs[j]->w;                                          w = bp->revs[j]->w;
3032                                  h += bp->revs[j]->h + conf.rev_minline;                                  h += bp->revs[j]->h + conf.rev_minline;
3033                          }                          }
3034                          if(conf.branch_dupbox)                          if(conf.branch_dupbox && bp->nrevs)
3035                                  h += bp->h + conf.rev_minline;                                  h += bp->h + conf.rev_minline;
3036                  }                  }
3037                  bp->th = h;                  bp->th = h;
# Line 2445  Line 3103 
3103          if(conf.auto_stretch && !conf.left_right)          if(conf.auto_stretch && !conf.left_right)
3104                  auto_stretch(rcs);                  auto_stretch(rcs);
3105    
         /* Move everything w.r.t. the top-left margin */  
         for(i = 0; i < rcs->nbranches; i++)  
                 move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);  
   
3106          /* Calculate overall image size */          /* Calculate overall image size */
3107          if(conf.left_right)          if(conf.left_right)
3108          {          {
# Line 2479  Line 3133 
3133                                  for(j = 0; j < b->nrevs; j++)                                  for(j = 0; j < b->nrevs; j++)
3134                                  {                                  {
3135                                          revision_t *r = b->revs[j];                                          revision_t *r = b->revs[j];
3136                                          r->cx = x - r->cx - r->w + conf.margin_left;                                          r->cx = x - r->cx - r->w;
3137                                  }                                  }
3138                                  b->cx = x - b->cx - b->w + conf.margin_left;                                  b->cx = x - b->cx - b->w;
3139                          }                          }
3140                  }                  }
3141                  else                  else
# Line 2493  Line 3147 
3147                                  for(j = 0; j < b->nrevs; j++)                                  for(j = 0; j < b->nrevs; j++)
3148                                  {                                  {
3149                                          revision_t *r = b->revs[j];                                          revision_t *r = b->revs[j];
3150                                          r->y = y - r->y - r->h + conf.margin_top;                                          r->y = y - r->y - r->h;
3151                                  }                                  }
3152                                  b->y = y - b->y - b->h + conf.margin_top;                                  b->y = y - b->y - b->h;
3153                          }                          }
3154                  }                  }
3155          }          }
3156    
3157            /* Relocate the lot if we only draw a sub-tree */
3158            if(subtree_branch)
3159            {
3160                    int xx, yy;
3161    
3162                    if(subtree_branch->folded)      /* Fix the reference if the branch got folded */
3163                            subtree_branch = subtree_branch->folded_to;
3164    
3165                    xx = conf.left_right ? subtree_branch->cx : subtree_branch->cx - subtree_branch->tw/2;
3166                    yy = conf.left_right ? subtree_branch->y - subtree_branch->th/2 : subtree_branch->y;
3167                    if(subtree_branch != rcs->branches[0])
3168                    {
3169                            if(conf.left_right)
3170                                    xx -= conf.branch_connect;
3171                            else
3172                                    yy -= conf.branch_connect;
3173                    }
3174                    for(i = 0; i < rcs->nbranches; i++)
3175                            move_branch(rcs->branches[i], -xx, -yy);
3176            }
3177    
3178            /* Move everything w.r.t. the top-left margin */
3179            for(i = 0; i < rcs->nbranches; i++)
3180                    move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
3181  }  }
3182    
3183  /*  /*
# Line 2506  Line 3185 
3185   * Imagemap functions   * Imagemap functions
3186   **************************************************************************   **************************************************************************
3187   */   */
3188  void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)  static void map_merge_box(rcsfile_t *rcs, FILE *fp, revision_t *fr, revision_t *tr, gdImagePtr im, int x1, int y1, int x2, int y2)
3189    {
3190            char *href = expand_string(conf.map_merge_href, rcs, tr, tr->rev, fr->rev, NULL);
3191            char *alt = expand_string(conf.map_merge_alt, rcs, tr, tr->rev, fr->rev, NULL);
3192            const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
3193    
3194            if(x1 > 0 && x2 > 0 && y1 > 0 && y2 > 0)
3195                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3196                                            href, x1, y1, x2, y2, alt, htp);
3197            xfree(alt);
3198            xfree(href);
3199    
3200            if(im)
3201            {
3202                    gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3203                    gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3204                    gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3205            }
3206    }
3207    
3208    static void map_merges(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3209    {
3210            int i;
3211            int tagh2 = get_sheight("Hg", &conf.tag_font) / 2;
3212            int bm = conf.branch_margin / 2;
3213    
3214            for(i = 0; i < rcs->nmerges; i++)
3215            {
3216                    revision_t *fr;
3217                    revision_t *tr;
3218                    int x1, x2, y1, y2;
3219                    switch(rcs->merges[i].type)
3220                    {
3221                    case TR_TAG:
3222                            fr = rcs->merges[i].from.tag->logrev;
3223                            tr = rcs->merges[i].to.tag->logrev;
3224                            break;
3225                    case TR_REVISION:
3226                            fr = rcs->merges[i].from.rev;
3227                            tr = rcs->merges[i].to.rev;
3228                            break;
3229                    default:
3230                            continue;
3231                    }
3232                    if(!fr || !tr || fr == tr)
3233                            continue;       /* This can happen with detached tags and self-references */
3234                    if(conf.left_right)
3235                    {
3236                            if(fr->branch == tr->branch)
3237                            {
3238                                    y1 = fr->y - fr->h/2;
3239                                    y2 = tr->y - tr->h/2;
3240                            }
3241                            else
3242                            {
3243                                    if(fr->y < tr->y)
3244                                    {
3245                                            y1 = fr->y + fr->h/2;
3246                                            y2 = tr->y - tr->h/2;
3247                                    }
3248                                    else
3249                                    {
3250                                            y1 = fr->y - fr->h/2;
3251                                            y2 = tr->y + tr->h/2;
3252                                    }
3253                            }
3254                            x1 = fr->cx + fr->w/2;
3255                            x2 = tr->cx + tr->w/2;
3256                    }
3257                    else
3258                    {
3259                            if(fr->branch == tr->branch)
3260                            {
3261                                    x1 = fr->cx - fr->w/2;
3262                                    x2 = tr->cx - tr->w/2;
3263                            }
3264                            else
3265                            {
3266                                    if(fr->cx < tr->cx)
3267                                    {
3268                                            x1 = fr->cx + fr->w/2;
3269                                            x2 = tr->cx - tr->w/2;
3270                                    }
3271                                    else
3272                                    {
3273                                            x1 = fr->cx - fr->w/2;
3274                                            x2 = tr->cx + tr->w/2;
3275                                    }
3276                            }
3277                            if(rcs->merges[i].type == TR_TAG)
3278                            {
3279                                    y1 = fr->y + rcs->merges[i].from.tag->yofs;
3280                                    y2 = tr->y + rcs->merges[i].to.tag->yofs;
3281                            }
3282                            else
3283                            {
3284                                    y1 = fr->y + fr->h/2;
3285                                    y2 = tr->y + tr->h/2;
3286                            }
3287                    }
3288    
3289                    if(conf.left_right)
3290                    {
3291                            if(fr->branch == tr->branch)
3292                            {
3293                                    map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3294                                    map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3295                            }
3296                            else
3297                            {
3298                                    if(y1 > y2)
3299                                    {
3300                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-bm, x1+bm, y1);
3301                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2, x2+bm, y2+bm);
3302                                    }
3303                                    else
3304                                    {
3305                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1, x1+bm, y1+bm);
3306                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-bm, x2+bm, y2);
3307                                    }
3308                            }
3309                    }
3310                    else
3311                    {
3312                            if(fr->branch == tr->branch)
3313                            {
3314                                    map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3315                                    map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3316                            }
3317                            else
3318                            {
3319                                    if(x1 > x2)
3320                                    {
3321                                            map_merge_box(rcs, fp, fr, tr, im, x1-bm, y1-tagh2, x1, y1+tagh2);
3322                                            map_merge_box(rcs, fp, fr, tr, im, x2, y2-tagh2, x2+bm, y2+tagh2);
3323                                    }
3324                                    else
3325                                    {
3326                                            map_merge_box(rcs, fp, fr, tr, im, x1, y1-tagh2, x1+bm, y1+tagh2);
3327                                            map_merge_box(rcs, fp, fr, tr, im, x2-bm, y2-tagh2, x2, y2+tagh2);
3328                                    }
3329                            }
3330                    }
3331            }
3332    }
3333    
3334    static void make_imagemap(rcsfile_t *rcs, FILE *fp, gdImagePtr im)
3335  {  {
3336          int i, j;          int i, j;
3337          const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";          const char *htp = conf.html_level == HTMLLEVEL_X ? " /" : "";
# Line 2527  Line 3352 
3352          {          {
3353                  branch_t *b = rcs->branches[i];                  branch_t *b = rcs->branches[i];
3354                  tag_t *tag = b->ntags ? b->tags[0] : NULL;                  tag_t *tag = b->ntags ? b->tags[0] : NULL;
3355                  char *bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);                  char *bhref;
3356                  char *balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);                  char *balt;
3357                  int x1;                  int x1;
3358                  int x2;                  int x2;
3359                  int y1;                  int y1;
3360                  int y2;                  int y2;
3361    
3362                  if(conf.left_right)                  if(subtree_branch && !b->subtree_draw)
3363                            continue;
3364    
3365                    bhref = expand_string(conf.map_branch_href, rcs, NULL, b->branch, NULL, tag);
3366                    balt = expand_string(conf.map_branch_alt, rcs, NULL, b->branch, NULL, tag);
3367    
3368                    if(!b->nfolds)
3369                  {                  {
3370                          x1 = b->cx;                          if(conf.left_right)
3371                          y1 = b->y - b->h/2;                          {
3372                          x2 = b->cx + b->w;                                  x1 = b->cx;
3373                          y2 = b->y + b->h/2;                                  y1 = b->y - b->h/2;
3374                                    x2 = b->cx + b->w;
3375                                    y2 = b->y + b->h/2;
3376                            }
3377                            else
3378                            {
3379                                    x1 = b->cx - b->w/2;
3380                                    y1 = b->y;
3381                                    x2 = b->cx + b->w/2;
3382                                    y2 = b->y + b->h;
3383                            }
3384                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3385                                            bhref, x1, y1, x2, y2, balt, htp);
3386                            if(im)
3387                            {
3388                                    gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);
3389                                    gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);
3390                                    gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);
3391                            }
3392                  }                  }
3393                  else                  else
3394                  {                  {
3395                          x1 = b->cx - b->w/2;                          int yy1, yy2, yy;
3396                          y1 = b->y;                          if(conf.left_right)
3397                          x2 = b->cx + b->w/2;                          {
3398                          y2 = b->y + b->h;                                  x1 = b->cx + conf.branch_lspace;
3399                  }                                  y1 = b->y - b->h/2 + conf.branch_tspace;
3400                  fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",                          }
3401                                  bhref, x1, y1, x2, y2, balt, htp);                          else
3402                  if(im)                          {
3403                  {                                  x1 = b->cx - b->w/2 + conf.branch_lspace;
3404                          gdImageFilledRectangle(im, x1-2, y1-2, x1+2, y1+2, conf.title_color.id);                                  y1 = b->y + conf.branch_tspace;
3405                          gdImageFilledRectangle(im, x2-2, y2-2, x2+2, y2+2, conf.tag_color.id);                          }
3406                          gdImageLine(im, x1, y1, x2, y2, conf.title_color.id);                          x2 = x1 + b->w - conf.branch_rspace;
3407    
3408                            yy1 = get_sheight(b->branch->branch, &conf.branch_font);
3409                            yy2 = get_sheight(b->tags[0]->tag, &conf.branch_tag_font);
3410                            yy = MAX(yy1, yy2);
3411                            y2 = y1 + yy;
3412                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3413                                            bhref, x1, y1, x2, y2, balt, htp);
3414    
3415                            y1 += yy;
3416                            y2 += yy;
3417                            for(j = 0; j < b->nfolds; j++)
3418                            {
3419                                    branch_t *fb = b->folds[j];
3420                                    tag_t *t = fb->tags[0];
3421                                    xfree(bhref);
3422                                    xfree(balt);
3423                                    bhref = expand_string(conf.map_branch_href, rcs, NULL, fb->branch, NULL, t);
3424                                    balt = expand_string(conf.map_branch_alt, rcs, NULL, fb->branch, NULL, t);
3425                                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s%s>\n",
3426                                                    bhref, x1, y1, x2, y2, balt, htp);
3427                                    yy1 = get_sheight(fb->branch->branch, &conf.branch_font);
3428                                    yy2 = get_sheight(fb->tags[0]->tag, &conf.branch_tag_font);
3429                                    yy = MAX(yy1, yy2);
3430                                    y1 += yy;
3431                                    y2 += yy;
3432                            }
3433                  }                  }
3434    
3435                  for(j = 0; j < b->nrevs; j++)                  for(j = 0; j < b->nrevs; j++)
3436                  {                  {
3437                          revision_t *r = b->revs[j];                          revision_t *r = b->revs[j];
# Line 2664  Line 3540 
3540                                  xfree(alt);                                  xfree(alt);
3541                          }                          }
3542                  }                  }
3543                  if(conf.branch_dupbox)                  if(conf.branch_dupbox && b->nrevs)
3544                  {                  {
3545                          if(conf.left_right)                          if(conf.left_right)
3546                          {                          {
# Line 2692  Line 3568 
3568                  xfree(bhref);                  xfree(bhref);
3569                  xfree(balt);                  xfree(balt);
3570          }          }
3571    
3572            map_merges(rcs, fp, im);
3573    
3574          fprintf(fp, "</map>\n");          fprintf(fp, "</map>\n");
3575  }  }
3576    
# Line 2723  Line 3602 
3602          "  -[0-9] <txt> Use <txt> for expansion\n"          "  -[0-9] <txt> Use <txt> for expansion\n"
3603          ;          ;
3604    
3605  #define VERSION_STR     "1.3.1"  #define VERSION_STR     "1.5.2"
3606  #define NOTICE_STR      "Copyright (c) 2001,2002,2003 B.Stultiens"  #define NOTICE_STR      "Copyright (c) 2001,2002,2003,2004 B.Stultiens"
3607    
3608  static void append_slash(char **path)  static void append_slash(char **path)
3609  {  {
# Line 2865  Line 3744 
3744          conf.branch_tag_font.gdfont     = gdFontTiny;          conf.branch_tag_font.gdfont     = gdFontTiny;
3745          conf.title_font.gdfont          = gdFontTiny;          conf.title_font.gdfont          = gdFontTiny;
3746          conf.rev_text_font.gdfont       = gdFontTiny;          conf.rev_text_font.gdfont       = gdFontTiny;
3747            conf.msg_font.gdfont            = gdFontTiny;
3748    
3749          conf.anti_alias         = 1;          conf.anti_alias         = 1;
3750          conf.thick_lines        = 1;          conf.thick_lines        = 1;
3751            conf.branch_fold        = 1;
3752    
3753          conf.cvsroot            = xstrdup("");          conf.cvsroot            = xstrdup("");
3754          conf.cvsmodule          = xstrdup("");          conf.cvsmodule          = xstrdup("");
# Line 2880  Line 3761 
3761          conf.map_rev_alt        = xstrdup("alt=\"%R\"");          conf.map_rev_alt        = xstrdup("alt=\"%R\"");
3762          conf.map_diff_href      = xstrdup("href=\"unset: conf.map_diff_href\"");          conf.map_diff_href      = xstrdup("href=\"unset: conf.map_diff_href\"");
3763          conf.map_diff_alt       = xstrdup("alt=\"%P &lt;-&gt; %R\"");          conf.map_diff_alt       = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3764            conf.map_merge_href     = xstrdup("href=\"unset: conf.map_merge_href\"");
3765            conf.map_merge_alt      = xstrdup("alt=\"%P &lt;-&gt; %R\"");
3766          conf.rev_text           = xstrdup("%d");          conf.rev_text           = xstrdup("%d");
3767            conf.branch_subtree     = xstrdup("");
3768            conf.tag_ignore         = xstrdup("");
3769          conf.merge_from         = xstrdup("");          conf.merge_from         = xstrdup("");
3770          conf.merge_to           = xstrdup("");          conf.merge_to           = xstrdup("");
3771            conf.merge_arrows       = 1;
3772            conf.arrow_width        = ARROW_WIDTH;
3773            conf.arrow_length       = ARROW_LENGTH;
3774    
3775          conf.color_bg           = white_color;          conf.color_bg           = white_color;
3776          conf.branch_bgcolor     = white_color;          conf.branch_bgcolor     = white_color;
# Line 2891  Line 3779 
3779          conf.rev_color          = black_color;          conf.rev_color          = black_color;
3780          conf.rev_bgcolor        = white_color;          conf.rev_bgcolor        = white_color;
3781          conf.merge_color        = black_color;          conf.merge_color        = black_color;
3782            conf.merge_cvsnt_color  = black_color;
3783          conf.tag_color          = black_color;          conf.tag_color          = black_color;
3784          conf.title_color        = black_color;          conf.title_color        = black_color;
3785          conf.rev_text_color     = black_color;          conf.rev_text_color     = black_color;
3786            conf.msg_color          = black_color;
3787    
3788          conf.image_quality      = 100;          conf.image_quality      = 100;
3789            conf.image_compress     = -1;   /* Use default zlib setting */
3790          conf.rev_maxline        = -1;   /* Checked later to set to default */          conf.rev_maxline        = -1;   /* Checked later to set to default */
3791    
3792          read_config(confpath);          read_config(confpath);
# Line 2915  Line 3806 
3806    
3807          if(conf.rev_minline >= conf.rev_maxline)          if(conf.rev_minline >= conf.rev_maxline)
3808          {          {
3809                  if(conf.auto_stretch && !quiet)                  if(conf.auto_stretch)
3810                          fprintf(stderr, "Auto stretch is only possible if rev_minline < rev_maxline\n");                          stack_msg(MSG_WARN, "Auto stretch is only possible if rev_minline < rev_maxline");
3811                  conf.auto_stretch = 0;                  conf.auto_stretch = 0;
3812          }          }
3813    
# Line 2925  Line 3816 
3816          if(conf.thick_lines > 11)          if(conf.thick_lines > 11)
3817                  conf.thick_lines = 11;                  conf.thick_lines = 11;
3818    
3819            if(conf.image_quality < 0 || conf.image_quality > 100)
3820            {
3821                    stack_msg(MSG_WARN, "JPEG quality (image_quality) must be between 0 and 100");
3822                    conf.image_quality = 100;
3823            }
3824    
3825            if(conf.image_compress < -1 || conf.image_compress > 9)
3826            {
3827                    stack_msg(MSG_WARN, "PNG compression (image_compress) must be between -1 and 9");
3828                    conf.image_compress = -1;
3829            }
3830    
3831          append_slash(&conf.cvsroot);          append_slash(&conf.cvsroot);
3832          append_slash(&conf.cvsmodule);          append_slash(&conf.cvsmodule);
3833    
# Line 2953  Line 3856 
3856          if(!reorganise_branches(rcs))          if(!reorganise_branches(rcs))
3857                  return 1;                  return 1;
3858    
3859          if(!assign_tags(rcs))          assign_tags(rcs);
3860                  return 1;          find_merges(rcs);
3861            find_merges_cvsnt(rcs);
3862    
3863          if(outfile)          if(outfile)
3864          {          {
# Line 2983  Line 3887 
3887          {          {
3888                  /* Create an image */                  /* Create an image */
3889                  im = make_image(rcs);                  im = make_image(rcs);
3890    
3891                    if(conf.image_interlace)
3892                            gdImageInterlace(im, 1);
3893    
3894  #ifdef DEBUG_IMAGEMAP  #ifdef DEBUG_IMAGEMAP
3895                  {                  {
3896                          FILE *nulfile = fopen("/dev/null", "w");                          FILE *nulfile = fopen("/dev/null", "w");
# Line 3003  Line 3911 
3911  #ifdef HAVE_IMAGE_PNG  #ifdef HAVE_IMAGE_PNG
3912                  default:                  default:
3913                  case IMAGE_PNG:                  case IMAGE_PNG:
3914    #ifdef HAVE_GDIMAGEPNGEX
3915                            gdImagePngEx(im, fp, conf.image_compress);
3916    #else
3917                          gdImagePng(im, fp);                          gdImagePng(im, fp);
3918    #endif
3919                          break;                          break;
3920  #endif  #endif
3921  #ifdef HAVE_IMAGE_JPEG  #ifdef HAVE_IMAGE_JPEG

Legend:
Removed from v.1.30  
changed lines
  Added in v.1.55

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0