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

Diff of /cvsgraph/cvsgraph.c

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

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

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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0