/[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.8, Sun Mar 4 03:19:18 2001 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    
 /*  
  * 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.  
  */  
   
22  #include <stdio.h>  #include <stdio.h>
23  #include <stdlib.h>  #include <stdlib.h>
24  #include <unistd.h>  #include <unistd.h>
# Line 68  Line 31 
31  #include <regex.h>  #include <regex.h>
32  #include <errno.h>  #include <errno.h>
33  #include <getopt.h>  #include <getopt.h>
34    #include <ctype.h>
35    
36  #include <gd.h>  #include <gd.h>
37  #include <gdfontt.h>  #include <gdfontt.h>
38    
39    #include "config.h"
40  #include "cvsgraph.h"  #include "cvsgraph.h"
41  #include "utils.h"  #include "utils.h"
42  #include "readconf.h"  #include "readconf.h"
43    #include "rcs.h"
44    
45    #if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG) && !defined(HAVE_IMAGE_JPEG)
46    # error No image output format available. Check libgd
47    #endif
48    
49    
50  /*#define DEBUG         1*/  /*#define DEBUG         1*/
51    
 #define RLOGCMD         "/usr/bin/rlog"  
 #define DEVNULL         "/dev/null"  
52  #define CONFFILENAME    "cvsgraph.conf"  #define CONFFILENAME    "cvsgraph.conf"
53    
54  #ifndef ETCDIR  #ifndef ETCDIR
# Line 103  Line 72 
72  #define ALIGN_VB        0x20  #define ALIGN_VB        0x20
73  #define ALIGN_VX        0xf0  #define ALIGN_VX        0xf0
74    
 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;  
   
75  /*  /*
76   **************************************************************************   **************************************************************************
77   * Globals   * Globals
78   **************************************************************************   **************************************************************************
79   */   */
80    
 char *rlogcmd = RLOGCMD;  
 char *devnull = DEVNULL;  
   
81  config_t conf;  config_t conf;
82    int debuglevel;
83    
84  /*  /*
85   **************************************************************************   **************************************************************************
86   * Debug routines   * Dubug routines
87   **************************************************************************   **************************************************************************
88   */   */
89  #ifdef DEBUG  void dump_rev(char *p, rev_t *r)
 void dump_revid(const char *s, revid_t *r)  
90  {  {
91          fprintf(stderr, "%s.branch  : '%s'\n", s, r->branch);          printf("%s", p);
92          fprintf(stderr, "%s.rev     : '%s'\n", s, r->rev);          if(r)
93          fprintf(stderr, "%s.isbranch: %d\n", s, r->isbranch);                  printf("'%s', '%s', %d\n", r->rev, r->branch, r->isbranch);
94            else
95                    printf("<null>\n");
96  }  }
97    
98  void dump_tag(const char *s, tag_t *t)  void dump_id(char *p, char *d)
99  {  {
100          fprintf(stderr, "%s", s);          printf("%s", p);
101          dump_revid(t->tag, t->rev);          if(d)
102                    printf("'%s'\n", d);
103            else
104                    printf("<null>\n");
105  }  }
106    
107  void dump_rev(revision_t *r)  void dump_idrev(char *p, idrev_t *t)
108  {  {
109          int i;          printf("%s", p);
110          dump_revid("Revision", r->rev);          if(t)
111          fprintf(stderr, "Revision.Info   : '%s'\n", r->info);          {
112          fprintf(stderr, "Revision.Comment: '%s'\n", r->comment);                  printf("'%s' -> ", t->id);
113          for(i = 0; i < r->ntags; i++)                  dump_rev("", t->rev);
114                  dump_tag("Revision.Tag: ", r->tags[i]);          }
115            else
116                    printf("<null>\n");
117    }
118    
119    void dump_tag(char *p, tag_t *t)
120    {
121            printf("%s", p);
122            if(t)
123            {
124                    printf("'%s' -> ", t->tag);
125                    dump_rev("", t->rev);
126            }
127            else
128                    printf("<null>\n");
129  }  }
130    
131  void dump_branch(branch_t *b)  void dump_delta(char *p, delta_t *d)
132  {  {
133          int i;          int i;
134          fprintf(stderr, "Branch: '%s'\n", b->branch);          printf("%sdelta.rev   : ", p);
135          if(b->tag)          dump_rev("", d->rev);
136                  dump_tag("branchtag:", b->tag);          printf("%sdelta.date  : %s\n", p, d->date);
137          for(i = 0; i < b->nrevs; i++)          printf("%sdelta.author: %s\n", p, d->author);
138                  fprintf(stderr, "Branch.Rev: '%s'\n", b->revs[i]->rev->rev);          printf("%sdelta.state : %s\n", p, d->state);
139            for(i = 0; d->branches && i < d->branches->nrevs; i++)
140            {
141                    printf("%sdelta.branch: ", p);
142                    dump_rev("", d->branches->revs[i]);
143            }
144            printf("%sdelta.next  : ", p);
145            dump_rev("", d->next);
146            printf("\n");
147  }  }
148    
149  void dump_log(rcsfilelog_t *r)  void dump_dtext(char *p, dtext_t *d)
150    {
151            printf("%sdtext.rev  : ", p);
152            dump_rev("", d->rev);
153            printf("%sdtext.log  : %d bytes\n", p, d->log ? strlen(d->log) : -1);
154            printf("%sdtext.text : %d bytes\n", p, d->text ? strlen(d->text) : -1);
155            printf("\n");
156    }
157    
158    void dump_rcsfile(rcsfile_t *rcs)
159  {  {
160          int i;          int i;
161            printf("root   : '%s'\n", rcs->root);
162            printf("module : '%s'\n", rcs->module);
163            printf("file   : '%s'\n", rcs->file);
164            dump_rev("head   : ", rcs->head);
165            dump_rev("branch : ", rcs->branch);
166            printf("access :\n");
167            for(i = 0; rcs->access && i < rcs->access->nids; i++)
168                    dump_id("\t", rcs->access->ids[i]);
169            printf("tags   :\n");
170            for(i = 0; rcs->tags && i < rcs->tags->ntags; i++)
171                    dump_tag("\t", rcs->tags->tags[i]);
172            printf("locks  :%s\n", rcs->strict ? " (strict)" : "");
173            for(i = 0; rcs->locks && i < rcs->locks->nidrevs; i++)
174                    dump_idrev("\t", rcs->locks->idrevs[i]);
175            printf("comment: '%s'\n", rcs->comment);
176            printf("expand : '%s'\n", rcs->expand ? rcs->expand : "(default -kv)");
177            printf("deltas :\n");
178            for(i = 0; rcs->deltas && i < rcs->deltas->ndeltas; i++)
179                    dump_delta("\t", rcs->deltas->deltas[i]);
180            printf("desc   : '%s'\n", rcs->desc);
181            printf("dtexts :\n");
182            for(i = 0; rcs->dtexts && i < rcs->dtexts->ndtexts; i++)
183                    dump_dtext("\t", rcs->dtexts->dtexts[i]);
184    
185          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]);  
186  }  }
 #endif  
187    
188  /*  /*
189   **************************************************************************   **************************************************************************
190   * Retrieve the log entries   * Read the rcs file
191   **************************************************************************   **************************************************************************
192   */   */
193  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)
194  {  {
         pid_t pid;  
         int nul;  
         FILE *tmp;  
195          char *cmd = NULL;          char *cmd = NULL;
196          int status;          int rv;
         mode_t um;  
197    
198          if((nul = open(devnull, O_RDWR, S_IRUSR|S_IWUSR)) == -1)          cmd = xmalloc(strlen(cvsroot) + strlen(module) + strlen(file) + 2 + 1);
199            sprintf(cmd, "%s/%s/%s", cvsroot, module, file);
200            if(!(rcsin = fopen(cmd, "r")))
201                    return NULL;
202            input_file = cmd;
203            line_number = 1;
204            rv = rcsparse();
205            fclose(rcsin);
206            if(rv)
207                  return NULL;                  return NULL;
208            xfree(cmd);
209            input_file = NULL;
210            rcsfile->root = xstrdup(cvsroot);
211            rcsfile->module = xstrdup(module);
212            rcsfile->file = xstrdup(file);
213            return rcsfile;
214    }
215    
216          um = umask(0177);       /* Set tempfiles to max 0600 permissions */  /*
217          if((tmp = tmpfile()) == NULL)   **************************************************************************
218     * Sort and find helpers
219     **************************************************************************
220     */
221    int count_dots(const char *s)
222    {
223            int i;
224            for(i = 0; *s; s++)
225          {          {
226                  close(nul);                  if(*s == '.')
227                  return NULL;                          i++;
228          }          }
229          umask(um);          return i;
230    }
231    
232          cmd = xmalloc(strlen(cvsroot) + + strlen(module) + strlen(file) + 2 + 1);  int compare_rev(int bcmp, const rev_t *r1, const rev_t *r2)
233          sprintf(cmd, "%s/%s/%s", cvsroot, module, file);  {
234            int d1, d2;
235            char *c1, *c2;
236            char *v1, *v2;
237            char *s1, *s2;
238            int retval = 0;
239            assert(r1 != NULL);
240            assert(r2 != NULL);
241            if(bcmp)
242            {
243                    assert(r1->branch != NULL);
244                    assert(r2->branch != NULL);
245                    c1 = r1->branch;
246                    c2 = r2->branch;
247            }
248            else
249            {
250                    assert(r1->rev != NULL);
251                    assert(r2->rev != NULL);
252                    c1 = r1->rev;
253                    c2 = r2->rev;
254            }
255    
256          switch(pid = fork())          d1 = count_dots(c1);
257            d2 = count_dots(c2);
258            if(d1 != d2)
259          {          {
260          case -1:        /* Error */                  return d1 - d2;
                 close(nul);  
                 fclose(tmp);  
                 xfree(cmd);  
                 return NULL;  
         case 0:         /* Child */  
                 if((dup2(nul, STDIN_FILENO)) == -1)     exit(126);  
                 if((dup2(fileno(tmp), STDOUT_FILENO)) == -1)    exit(126);  
                 if((dup2(nul, STDERR_FILENO)) == -1)    exit(126);  
                 close(nul);  
                 fclose(tmp);  
                 execl(rlogcmd, rlogcmd, cmd, NULL);  
                 exit(127);  
                 break;  
         default:        /* Parent */  
                 close(nul);  
                 xfree(cmd);  
                 while(1)  
                 {  
                         if(waitpid(pid, &status, 0) == -1)  
                         {  
                                 if(errno != EINTR)  
                                 {  
                                         fclose(tmp);  
                                         return NULL;  
                                 }  
                         }  
                         else  
                                 break;  
                 }  
                 break;  
261          }          }
262    
263          if(WIFEXITED(status) && WEXITSTATUS(status) == 0)          s1 = v1 = xstrdup(c1);
264            s2 = v2 = xstrdup(c2);
265            while(1)
266          {          {
267                  if(fseek(tmp, 0, SEEK_SET) != (off_t)-1)                  char *vc1 = strchr(s1, '.');
268                    char *vc2 = strchr(s2, '.');
269                    if(vc1 && vc2)
270                            *vc1 = *vc2 = '\0';
271                    if(*s1 && *s2)
272                  {                  {
273                          return tmp;                          d1 = atoi(s1);
274                  }                          d2 = atoi(s2);
275                  else                          if(d1 != d2)
276                  {                          {
277                          fclose(tmp);                                  retval = d1 - d2;
278                          return NULL;                                  break;
279                            }
280                  }                  }
281                    if(!vc1 || !vc2)
282                            break;
283                    s1 = vc1 + 1;
284                    s2 = vc2 + 1;
285          }          }
286          else          xfree(v1);
287                  fclose(tmp);          xfree(v2);
288          return NULL;          return retval;
289  }  }
290    
291  /*  /*
292   **************************************************************************   **************************************************************************
293   * Parse the log entries   * Reorganise the rcsfile for the branches
294     *
295     * Basically, we have a list of deltas (i.e. administrative info on
296     * revisions) and a list of delta text (the actual logs and diffs).
297     * The deltas are linked through the 'next' and the 'branches' fields
298     * which describe the tree-structure of revisions.
299     * The reorganisation means that we put each delta and corresponding
300     * delta text in a revision structure and assign it to a specific
301     * branch. This is required because we want to be able to calculate
302     * the bounding boxes of each branch. The revisions expand vertically
303     * and the branches expand horizontally.
304     * The reorganisation is performed in these steps:
305     * 1 - sort deltas and detla text on revision number for quick lookup
306     * 2 - start at the denoted head revision:
307     *      * create a branch structure and add this revision
308     *      * for each 'branches' in the delta do:
309     *              - walk all 'branches' of the delta and recursively goto 2
310     *                with the denoted branch delta as new head
311     *              - backlink the newly create sub-branch to the head revision
312     *                so that we can draw them recursively
313     *      * set head to the 'next' field and goto 2 until no next is
314     *        available
315     * 3 - update the administration
316   **************************************************************************   **************************************************************************
317   */   */
318  char *strip_dup(const char *s)  int sort_delta(const void *d1, const void *d2)
319  {  {
320          int l = strlen(s);          return compare_rev(0, (*(delta_t **)d1)->rev, (*(delta_t **)d2)->rev);
321          char *c = xmalloc(l+1);  }
322    
323          strcpy(c, s);  int search_delta(const void *r, const void *d)
324          while(*c == ' ' || *c == '\t')  {
325          {          return compare_rev(0, (rev_t *)r, (*(delta_t **)d)->rev);
                 memmove(c, c+1, l--);  
         }  
         while(l && strchr(" \t\r\n", c[l]))  
                 c[l--] = '\0';  
         return c;  
326  }  }
327    
328  revid_t *make_revid(const char *s)  delta_t *find_delta(delta_t **dl, int n, rev_t *r)
329  {  {
330          char *c = strip_dup(s);          delta_t **d;
331          char *cptr;          d = bsearch(r, dl, n, sizeof(*dl), search_delta);
332          int dots = 0;          if(!d)
333          revid_t *r = xmalloc(sizeof(*r));                  return NULL;
334          for(cptr = c; *cptr; cptr++)          return *d;
335          {  }
336                  if(*cptr == '.')  
337                          dots++;  int sort_dtext(const void *d1, const void *d2)
338          }  {
339          if(!dots)          return compare_rev(0, (*(dtext_t **)d1)->rev, (*(dtext_t **)d2)->rev);
340          {  }
341                  r->rev = xstrdup("");  
342                  r->branch = xstrdup(s);  int search_dtext(const void *r, const void *d)
343                  r->isbranch = 1;  {
344          }          return compare_rev(0, (rev_t *)r, (*(dtext_t **)d)->rev);
345          else if(!*c)  }
346    
347    dtext_t *find_dtext(dtext_t **dl, int n, rev_t *r)
348    {
349            dtext_t **d;
350            d = bsearch(r, dl, n, sizeof(*dl), search_dtext);
351            if(!d)
352                    return NULL;
353            return *d;
354    }
355    
356    rev_t *dup_rev(const rev_t *r)
357    {
358            rev_t *t = xmalloc(sizeof(*t));
359            t->rev = xstrdup(r->rev);
360            t->branch = xstrdup(r->branch);
361            t->isbranch = r->isbranch;
362            return t;
363    }
364    
365    branch_t *new_branch(delta_t *d, dtext_t *t)
366    {
367            branch_t *b = xmalloc(sizeof(*b));
368            revision_t *r = xmalloc(sizeof(*r));
369            r->delta = d;
370            r->dtext = t;
371            r->rev = d->rev;
372            r->branch = b;
373            b->branch = dup_rev(d->rev);
374            b->branch->isbranch = 1;
375            b->nrevs = 1;
376            b->revs = xmalloc(sizeof(b->revs[0]));
377            b->revs[0] = r;
378            return b;
379    }
380    
381    revision_t *add_to_branch(branch_t *b, delta_t *d, dtext_t *t)
382    {
383            revision_t *r = xmalloc(sizeof(*r));
384            r->delta = d;
385            r->dtext = t;
386            r->rev = d->rev;
387            r->branch = b;
388            b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));
389            b->revs[b->nrevs] = r;
390            b->nrevs++;
391            return r;
392    }
393    
394    void build_branch(branch_t ***bl, int *nbl, delta_t **sdl, int nsdl, dtext_t **sdt, int nsdt, delta_t *head)
395    {
396            branch_t *b;
397            dtext_t *text;
398            revision_t *currev;
399    
400            assert(head != NULL);
401    
402            if(head->flag)
403          {          {
404                  r->rev = xstrdup("?.?");                  fprintf(stderr, "Circular reference on '%s' in branchpoint\n", head->rev->rev);
405                  r->branch = xstrdup("?");                  return;
406          }          }
407          else if(dots & 1)          head->flag++;
408            text = find_dtext(sdt, nsdt, head->rev);
409    
410            /* Create a new branch for this head */
411            b = new_branch(head, text);
412            *bl = xrealloc(*bl, (*nbl+1)*sizeof((*bl)[0]));
413            (*bl)[*nbl] = b;
414            (*nbl)++;
415            currev = b->revs[0];
416            while(1)
417          {          {
418                  char *t;                  /* Process all sub-branches */
419                  r->rev = c;                  if(head->branches)
                 r->branch = xstrdup(c);  
                 cptr = strrchr(r->branch, '.');  
                 assert(cptr != NULL);  
                 *cptr = '\0';  
                 t = strrchr(r->branch, '.');  
                 if((t&& !strcmp(t+1, "0")) || (!t && !strcmp(r->branch, "0")))  
420                  {                  {
421                          /* Magic branch numbers "x.x.0.x" */                          int i;
422                          r->isbranch = 1;                          for(i = 0; i < head->branches->nrevs; i++)
423                          if(t)                          {
424                                  strcpy(t+1, cptr+1);                                  delta_t *d = find_delta(sdl, nsdl, head->branches->revs[i]);
425                          else                                  int btag = *nbl;
426                                  strcpy(r->branch, cptr+1);                                  if(!d)
427                                            continue;
428                                    build_branch(bl, nbl, sdl, nsdl, sdt, nsdt, d);
429    
430                                    /* Set the new branch's origin */
431                                    (*bl)[btag]->branchpoint = currev;
432    
433                                    /* Add branch to this revision */
434                                    currev->branches = xrealloc(currev->branches, (currev->nbranches+1)*sizeof(currev->branches[0]));
435                                    currev->branches[currev->nbranches] = (*bl)[btag];
436                                    currev->nbranches++;
437                            }
438                    }
439    
440                    /* Walk through the next list */
441                    if(!head->next)
442                            return;
443    
444                    head = find_delta(sdl, nsdl, head->next);
445                    if(!head)
446                    {
447                            fprintf(stderr, "Next revision (%s) not found in deltalist\n", head->next->rev);
448                            return;
449                  }                  }
450                    if(head->flag)
451                    {
452                            fprintf(stderr, "Circular reference on '%s'\n", head->rev->rev);
453                            return;
454                    }
455                    head->flag++;
456                    text = find_dtext(sdt, nsdt, head->rev);
457                    currev = add_to_branch(b, head, text);
458          }          }
459          else  }
460    
461    int reorganise_branches(rcsfile_t *rcs)
462    {
463            delta_t **sdelta;
464            int nsdelta;
465            dtext_t **sdtext;
466            int nsdtext;
467            delta_t *head;
468            branch_t **bl;
469            int nbl;
470            int i;
471    
472            assert(rcs->deltas != NULL);
473            assert(rcs->head != NULL);
474    
475            /* Make a new list for quick lookup */
476            nsdelta = rcs->deltas->ndeltas;
477            sdelta = xmalloc(nsdelta * sizeof(sdelta[0]));
478            memcpy(sdelta, rcs->deltas->deltas, nsdelta * sizeof(sdelta[0]));
479            qsort(sdelta, nsdelta, sizeof(sdelta[0]), sort_delta);
480    
481            /* Do the same for the delta text */
482            nsdtext = rcs->dtexts->ndtexts;
483            sdtext = xmalloc(nsdtext * sizeof(sdtext[0]));
484            memcpy(sdtext, rcs->dtexts->dtexts, nsdtext * sizeof(sdtext[0]));
485            qsort(sdtext, nsdtext, sizeof(sdtext[0]), sort_dtext);
486    
487            /* Start from the head of the trunk */
488            head = find_delta(sdelta, nsdelta, rcs->head);
489            if(!head)
490          {          {
491                  r->isbranch = 1;                  fprintf(stderr, "Head revision (%s) not found in deltalist\n", rcs->head->rev);
492                  r->branch = c;                  return 0;
                 r->rev = xmalloc(strlen(c) + 3);  
                 strcpy(r->rev, c);  
                 strcat(r->rev, ".?");  
493          }          }
494          return r;          bl = NULL;
495            nbl = 0;
496            build_branch(&bl, &nbl, sdelta, nsdelta, sdtext, nsdtext, head);
497    
498            /* Reverse the head branch */
499            for(i = 0; i < bl[0]->nrevs/2; i++)
500            {
501                    revision_t *r;
502                    r = bl[0]->revs[i];
503                    bl[0]->revs[i] = bl[0]->revs[bl[0]->nrevs-i-1];
504                    bl[0]->revs[bl[0]->nrevs-i-1] = r;
505            }
506    
507            /* Update the branch-number of the head because it was reversed */
508            xfree(bl[0]->branch->branch);
509            bl[0]->branch->branch = xstrdup(bl[0]->revs[0]->rev->branch);
510    
511            /* Keep the admin */
512            rcs->branches = bl;
513            rcs->nbranches = nbl;
514            rcs->sdelta = sdelta;
515            rcs->nsdelta = nsdelta;
516            rcs->sdtext = sdtext;
517            rcs->nsdtext = nsdtext;
518            rcs->active = bl[0];
519            return 1;
520  }  }
521    
522  char *add_comment(char *c, const char *n)  /*
523     **************************************************************************
524     * Assign the symbolic tags to the revisions and branches
525     *
526     * The tags point to revision numbers somewhere in the tree structure
527     * of branches and revisions. First we make a sorted list of all
528     * revisions and then we assign each tag to the proper revision.
529     **************************************************************************
530     */
531    int sort_revision(const void *r1, const void *r2)
532  {  {
533          int l;          return compare_rev(0, (*(revision_t **)r1)->delta->rev, (*(revision_t **)r2)->delta->rev);
534    }
535    
536    int search_revision(const void *t, const void *r)
537    {
538            return compare_rev(0, (rev_t *)t, (*(revision_t **)r)->delta->rev);
539    }
540    
541    int sort_branch(const void *b1, const void *b2)
542    {
543            return compare_rev(1, (*(branch_t **)b1)->branch, (*(branch_t **)b2)->branch);
544    }
545    
546    int search_branch(const void *t, const void *b)
547    {
548            return compare_rev(1, (rev_t *)t, (*(branch_t **)b)->branch);
549    }
550    
551    char *previous_rev(const char *c)
552    {
553            int dots = count_dots(c);
554            char *cptr;
555          char *r;          char *r;
556          assert(n != NULL);          if(!dots)
557          l = strlen(n);                  return xstrdup("1.0");  /* FIXME: don't know what the parent is */
558          if(!c)          if(dots & 1)
         {  
                 r = xmalloc(l+1);  
                 strcpy(r, n);  
         }  
         else  
559          {          {
560                  r = xmalloc(l+strlen(c)+1+1);                  /* Is is a revision we want the parent of */
561                  strcpy(r, c);                  r = xstrdup(c);
562                  strcat(r, "\n");                  cptr = strrchr(r, '.');
563                  strcat(r, n);                  assert(cptr != NULL);
564                    if(dots == 1)
565                    {
566                            /* FIXME: What is the parent of 1.1? */
567                            cptr[1] = '\0';
568                            strcat(r, "0");
569                            return r;
570                    }
571                    /* Here we have a "x.x[.x.x]+" case */
572                    *cptr = '\0';
573                    cptr = strrchr(r, '.');
574                    assert(cptr != NULL);
575                    *cptr = '\0';
576                    return r;
577          }          }
578            /* It is a branch we want the parent of */
579            r = xstrdup(c);
580            cptr = strrchr(r, '.');
581            assert(cptr != NULL);
582            *cptr = '\0';
583          return r;          return r;
584  }  }
585    
586  int get_line(FILE *fp, char *buf, int maxlen)  int assign_tags(rcsfile_t *rcs)
587  {  {
588          int n;          int i;
589          int seennl;          int nr;
590  retry:  
591          seennl = 0;          for(i = nr = 0; i < rcs->nbranches; i++)
592          if(!fgets(buf, maxlen, fp))                  nr += rcs->branches[i]->nrevs;
593                  return feof(fp) ? 0 : -1;  
594          n = strlen(buf);          rcs->srev = xmalloc(nr * sizeof(rcs->srev[0]));
595          while(n && buf[n-1] == '\n')          rcs->nsrev = nr;
596          {          for(i = nr = 0; i < rcs->nbranches; i++)
                 seennl = 1;  
                 buf[--n] = '\0';  
         }  
         if(!n)  
                 goto retry;  
         if(!seennl)  
597          {          {
598                  while(fgetc(fp) != '\n')                  memcpy(&rcs->srev[nr], rcs->branches[i]->revs, rcs->branches[i]->nrevs * sizeof(rcs->branches[i]->revs[0]));
599                          ;                  nr += rcs->branches[i]->nrevs;
600          }          }
         return n;  
 }  
601    
602  rcsfilelog_t *parse_log(FILE *fp)          qsort(rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), sort_revision);
603  {          qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
         rcsfilelog_t *p;  
         int state = 0;  
         regex_t rerev;  
         regex_t reinfo;  
604    
605          if(regcomp(&rerev, "^revision[ \\t]*[0-9]+(\\.[0-9]+)*", REG_EXTENDED))          if(!rcs->branch)
                 return NULL;  
         if(regcomp(&reinfo, "^date:[^;]*;[ \\t]*author:[^;]*;[ \\t]+state:", REG_EXTENDED))  
606          {          {
607                  regfree(&rerev);                  /* The main trunk is the active trunk */
608                  return NULL;                  rcs->tags->tags = xrealloc(rcs->tags->tags, (rcs->tags->ntags+1)*sizeof(rcs->tags->tags[0]));
609                    rcs->tags->tags[rcs->tags->ntags] = xmalloc(sizeof(tag_t));
610                    rcs->tags->tags[rcs->tags->ntags]->tag = xstrdup("MAIN");
611                    rcs->tags->tags[rcs->tags->ntags]->rev = xmalloc(sizeof(rev_t));
612                    rcs->tags->tags[rcs->tags->ntags]->rev->rev = xstrdup(rcs->active->branch->rev);
613                    rcs->tags->tags[rcs->tags->ntags]->rev->branch = xstrdup(rcs->active->branch->branch);
614                    rcs->tags->tags[rcs->tags->ntags]->rev->isbranch = 1;
615                    rcs->tags->ntags++;
616          }          }
617          p = xmalloc(sizeof(*p));  
618          while(state != 4)          /* We should have at least two tags (HEAD and MAIN) */
619            assert(rcs->tags != 0);
620    
621            for(i = 0; i < rcs->tags->ntags; i++)
622          {          {
623                  char buf[256];                  tag_t *t = rcs->tags->tags[i];
624                  int n;                  if(t->rev->isbranch)
                 n = get_line(fp, buf, sizeof(buf));  
                 if(!n)  
                         break;  
                 if(n == -1)  
625                  {                  {
626                          perror("tempfile read");                          branch_t **b;
627                          xfree(p);  add_btag:
628                          regfree(&rerev);                          b = bsearch(t->rev, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
629                          regfree(&reinfo);                          if(!b)
630                          return NULL;                          {
631                  }                                  rev_t rev;
632                  switch(state)                                  revision_t **r;
633                  {                                  /* This happens for the magic branch numbers if there are
634                  case 0: /* Prologue */                                   * no commits withing the new branch yet. So, we add the
635  more_prologue:                                   * branch and try continue.
636                          if(!strncmp(buf, "RCS file:", 9))                                   */
637                          {                                  rev.rev = previous_rev(t->rev->branch);
638                                  p->path = strip_dup(buf+9);                                  rev.branch = NULL;
639                          }                                  rev.isbranch = 0;
640                          else if(!strncmp(buf, "Working file:", 13))                                  r = bsearch(&rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
641                          {                                  xfree(rev.rev);
642                                  p->name = strip_dup(buf+13);                                  if(!r)
                         }  
                         else if(!strncmp(buf, "head:", 5))  
                         {  
                                 p->head = make_revid(buf+5);  
                         }  
                         else if(!strncmp(buf, "branch:", 7))  
                         {  
                                 p->branch = strip_dup(buf+7);  
                         }  
                         else if(!strncmp(buf, "locks:", 6))  
                         {  
                                 p->locks = strip_dup(buf+6);  
                         }  
                         else if(!strncmp(buf, "access list:", 12))  
                         {  
                                 p->access = strip_dup(buf+12);  
                         }  
                         else if(!strncmp(buf, "keyword substitution:", 21))  
                         {  
                                 p->keyword = strip_dup(buf+21);  
                         }  
                         else if(!strncmp(buf, "total revisions:", 16))  
                         {  
                                 p->totalrevs = strip_dup(buf+16);  
                         }  
                         else if(!strncmp(buf, "description:", 12))  
                         {  
                                 state = 2;  
                         }  
                         else if(!strncmp(buf, "symbolic names:", 15))  
                         {  
                                 state = 1;  
                         }  
                         else  
                         {  
                                 fprintf(stderr, "Unknown keyword(s) in line '%s' (state=%d)\n", buf, state);  
                                 xfree(p);  
                                 return NULL;  
                         }  
                         break;  
                 case 1: /* Tags */  
                         if(*buf != '\t')  
                         {  
                                 state = 0;  
                                 goto more_prologue;  
                         }  
                         else  
                         {  
                                 char *rev = strrchr(buf, ':');  
                                 tag_t *t;  
                                 if(!rev)  
643                                  {                                  {
644                                          state = 2;                                          fprintf(stderr, "No branch found for tag '%s:%s'\n", t->tag, t->rev->branch);
645                                          goto more_prologue;                                  }
646                                    else
647                                    {
648                                            rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1)*sizeof(rcs->branches[0]));
649                                            rcs->branches[rcs->nbranches] = xmalloc(sizeof(branch_t));
650                                            rcs->branches[rcs->nbranches]->branch = dup_rev(t->rev);
651                                            rcs->branches[rcs->nbranches]->branchpoint = *r;
652                                            (*r)->branches = xrealloc((*r)->branches, ((*r)->nbranches+1)*sizeof((*r)->branches[0]));
653                                            (*r)->branches[(*r)->nbranches] = rcs->branches[rcs->nbranches];
654                                            (*r)->nbranches++;
655                                            rcs->nbranches++;
656                                            /* Resort the branches */
657                                            qsort(rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), sort_branch);
658                                            goto add_btag;
659                                  }                                  }
                                 *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;  
660                          }                          }
                         if(!p->nrevs)  
                                 p->comment = add_comment(p->comment, buf);  
661                          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))  
                         {  
                                 revision_t *r = xmalloc(sizeof(*r));  
                                 p->revs = xrealloc(p->revs, (p->nrevs+1) * sizeof(p->revs[0]));  
                                 p->revs[p->nrevs] = r;  
                                 p->nrevs++;  
                                 r->rev = make_revid(buf+8);  
                         }  
                         else if(!regexec(&reinfo, buf, 0, NULL, 0))  
662                          {                          {
663                                  assert(p->nrevs > 0);                                  branch_t *bb = *b;
664                                  p->revs[p->nrevs-1]->info = strip_dup(buf);                                  bb->tags = xrealloc(bb->tags, (bb->ntags+1)*sizeof(bb->tags[0]));
665                                    bb->tags[bb->ntags] = t;
666                                    bb->ntags++;
667                          }                          }
668                    }
669                    else
670                    {
671                            revision_t **r = bsearch(t->rev, rcs->srev, rcs->nsrev, sizeof(rcs->srev[0]), search_revision);
672                            if(!r)
673                                    fprintf(stderr, "No revision found for tag '%s:%s'\n", t->tag, t->rev->rev);
674                          else                          else
675                          {                          {
676                                  /* No match means the description/comment */                                  revision_t *rr = *r;
677                                  state = 2;                                  rr->tags = xrealloc(rr->tags, (rr->ntags+1)*sizeof(rr->tags[0]));
678                                  goto add_description;                                  rr->tags[rr->ntags] = t;
679                                    rr->ntags++;
680                          }                          }
                         break;  
                 default:  
                         fprintf(stderr, "Illegal state (%d) in parser\n", state);  
                         xfree(p);  
                         regfree(&rerev);  
                         regfree(&reinfo);  
                         return NULL;  
681                  }                  }
682          }          }
683          regfree(&rerev);  
684          regfree(&reinfo);          /* We need to reset the first in the list of branches to the
685          return p;           * active branch to ensure the drawing of all
686             */
687            if(rcs->active != rcs->branches[0])
688            {
689                    branch_t **b = bsearch(rcs->active->branch, rcs->branches, rcs->nbranches, sizeof(rcs->branches[0]), search_branch);
690                    branch_t *t;
691                    assert(b != NULL);
692                    t = *b;
693                    *b = rcs->branches[0];
694                    rcs->branches[0] = t;
695            }
696            return 1;
697  }  }
698    
699  /*  /*
700   **************************************************************************   **************************************************************************
701   * Sort and find helpers   * String expansion
702   **************************************************************************   **************************************************************************
703   */   */
704  int tag_sort(const void *t1, const void *t2)  static char *_string;
705  {  static int _nstring;
706  #define TAGPTR(t)       (*((tag_t **)t))  static int _nastring;
         return strcmp(TAGPTR(t1)->rev->rev, TAGPTR(t2)->rev->rev);  
 #undef TAGPTR  
 }  
   
 int rev_sort(const void *v1, const void *v2)  
 {  
 #define REVPTR(t)       (*((revision_t **)t))  
         /* FIXME: This can lead to a segfault when no '.' is found */  
         return atoi(strrchr(REVPTR(v1)->rev->rev, '.') + 1) -  
                 atoi(strrchr(REVPTR(v2)->rev->rev, '.') + 1);  
 #undef REVPTR  
 }  
707    
708  int branch_sort(const void *b1, const void *b2)  void add_string_str(const char *s)
709  {  {
710  #define BPTR(t) (*((branch_t **)t))          int l = strlen(s) + 1;
711          return strcmp(BPTR(b1)->branch, BPTR(b2)->branch);          if(_nstring + l > _nastring)
712  #undef BPTR          {
713                    _nastring += 128;
714                    _string = xrealloc(_string, _nastring * sizeof(_string[0]));
715            }
716            memcpy(_string+_nstring, s, l);
717            _nstring += l-1;
718  }  }
719    
720  int rev_cmp(const void *id, const void *v)  void add_string_ch(int ch)
721  {  {
722  #define REVPTR(t)       (*((revision_t **)t))          char buf[2];
723          return strcmp(((revid_t *)id)->rev, REVPTR(v)->rev->rev);          buf[0] = ch;
724  #undef REVPTR          buf[1] = '\0';
725            add_string_str(buf);
726  }  }
727    
728  revision_t *find_revision(rcsfilelog_t * rcs, revid_t *id)  char *expand_string(const char *s, rcsfile_t *rcs, rev_t *rev, tag_t *tag)
729  {  {
730          revision_t **r;          char nb[32];
731          if(id->isbranch)          char nr[32];
732                  return NULL;          char *base;
         r = bsearch(id, rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_cmp);  
         if(!r)  
                 return NULL;  
         else  
                 return *r;  
 }  
733    
734  int branch_cmp(const void *s, const void *b)          if(!s)
735  {                  return xstrdup("");
         return strcmp((const char *)s, (*((branch_t **)b))->branch);  
 }  
736    
737  branch_t *find_branch(rcsfilelog_t *rcs, const char *id)          _nstring = 0;
738  {          if(_string)
739          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;  
 }  
740    
741  tag_t *find_branchtag(rcsfilelog_t * rcs, const char *id)          sprintf(nb, "%d", rcs->nbranches);
742  {          sprintf(nr, "%d", rcs->nsrev);
743          int i;          for(; *s; s++)
         for(i = 0; i < rcs->ntags; i++)  
744          {          {
745                  if(!rcs->tags[i]->rev->isbranch)                  if(*s == '%')
746                          continue;                  {
747                  if(!strcmp(id, rcs->tags[i]->rev->branch))                          switch(*++s)
748                          return rcs->tags[i];                          {
749                            case 'c': add_string_str(conf.cvsroot); break;
750                            case 'f':
751                            case 'F':
752                                    base = strrchr(rcs->file, '/');
753                                    if(!base)
754                                            add_string_str(rcs->file);
755                                    else
756                                            add_string_str(base+1);
757                                    if(*s == 'F' && _string[_nstring-1] == 'v' && _string[_nstring-2] == ',')
758                                    {
759                                            _nstring -= 2;
760                                            _string[_nstring] = '\0';
761                                    }
762                                    break;
763                            case 'p':
764                                    base = strrchr(rcs->file, '/');
765                                    if(base)
766                                    {
767                                            char *c = xstrdup(rcs->file);
768                                            base = strrchr(c, '/');
769                                            assert(base != NULL);
770                                            base[1] = '\0';
771                                            add_string_str(c);
772                                            xfree(c);
773                                    }
774                                    else
775                                            add_string_str("/");
776                                    break;
777                            case 'm': add_string_str(conf.cvsmodule); break;
778                            case 'r': add_string_str(nr); break;
779                            case 'b': add_string_str(nb); break;
780                            case '%': add_string_ch('%'); break;
781                            case '0': if(conf.expand[0]) add_string_str(conf.expand[0]); break;
782                            case '1': if(conf.expand[1]) add_string_str(conf.expand[1]); break;
783                            case '2': if(conf.expand[2]) add_string_str(conf.expand[2]); break;
784                            case '3': if(conf.expand[3]) add_string_str(conf.expand[3]); break;
785                            case '4': if(conf.expand[4]) add_string_str(conf.expand[4]); break;
786                            case '5': if(conf.expand[5]) add_string_str(conf.expand[5]); break;
787                            case '6': if(conf.expand[6]) add_string_str(conf.expand[6]); break;
788                            case '7': if(conf.expand[7]) add_string_str(conf.expand[7]); break;
789                            case '8': if(conf.expand[8]) add_string_str(conf.expand[8]); break;
790                            case '9': if(conf.expand[9]) add_string_str(conf.expand[9]); break;
791                            case 'R': if(rev && rev->rev) add_string_str(rev->rev); break;
792                            case 'B': if(rev && rev->branch) add_string_str(rev->branch); break;
793                            case 't': if(tag && tag->tag) add_string_str(tag->tag); break;
794                            default:
795                                    add_string_ch('%');
796                                    add_string_ch(*s);
797                                    break;
798                            }
799                    }
800                    else
801                            add_string_ch(*s);
802          }          }
803          return NULL;          return xstrdup(_string);
804  }  }
805    
806  /*  /*
# Line 748  Line 886 
886          int rx = lx + b->w;          int rx = lx + b->w;
887          int yy;          int yy;
888          int i;          int i;
889            /*draw_rbox(im, cx-b->tw/2-1, ty-1, cx+b->tw/2+1, ty+b->th+1, 0, &conf.title_color);*/
890          draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color);          draw_rbox(im, lx, ty, rx, ty+b->h, 5, &conf.branch_color);
891          yy = conf.branch_tspace;          yy = conf.branch_tspace;
892          draw_string(im, b->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);          draw_string(im, b->branch->branch, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
893          yy += get_sheight(b->branch, &conf.branch_font);          yy += get_sheight(b->branch->branch, &conf.branch_font);
894          if(b->tag)          for(i = 0; i < b->ntags; i++)
895          {          {
896                  draw_string(im, b->tag->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);                  draw_string(im, b->tags[i]->tag, &conf.branch_font, cx, ty+yy, ALIGN_HC, &conf.branch_color);
897                    yy += get_sheight(b->tags[i]->tag, &conf.branch_font);
898          }          }
899    
900          ty += b->h;          ty += b->h;
# Line 767  Line 907 
907          }          }
908  }  }
909    
 static char *_title;  
 static int _ntitle;  
 static int _natitle;  
   
 void add_title_str(const char *s)  
 {  
         int l = strlen(s) + 1;  
         if(_ntitle + l > _natitle)  
         {  
                 _natitle += 128;  
                 _title = xrealloc(_title, _natitle * sizeof(_title[0]));  
         }  
         memcpy(_title+_ntitle, s, l);  
         _ntitle += l-1;  
 }  
   
 void add_title_ch(int ch)  
 {  
         char buf[2];  
         buf[0] = ch;  
         buf[1] = '\0';  
         add_title_str(buf);  
 }  
   
 char *expand_title(rcsfilelog_t *rcs)  
 {  
         char nb[32];  
         char nr[32];  
         char *cptr;  
   
         sprintf(nb, "%d", rcs->nbranches);  
         sprintf(nr, "%d", rcs->nrevs);  
         for(cptr = conf.title; *cptr; cptr++)  
         {  
                 if(*cptr == '%')  
                 {  
                         switch(*++cptr)  
                         {  
                         case 'c': add_title_str(conf.cvsroot); break;  
                         case 'f': add_title_str(rcs->name); break;  
                         case 'm': add_title_str(conf.cvsmodule); break;  
                         case 'r': add_title_str(nr); break;  
                         case 'b': add_title_str(nb); break;  
                         case '%': add_title_ch('%'); break;  
                         default:  
                                 add_title_ch('%');  
                                 add_title_ch(*cptr);  
                                 break;  
                         }  
                 }  
                 else  
                         add_title_ch(*cptr);  
         }  
         return _title;  
 }  
   
910  void draw_title(gdImagePtr im, char *title)  void draw_title(gdImagePtr im, char *title)
911  {  {
912          char *t;          char *t;
# Line 843  Line 927 
927  void draw_connector(gdImagePtr im, branch_t *b)  void draw_connector(gdImagePtr im, branch_t *b)
928  {  {
929          revision_t *r = b->branchpoint;          revision_t *r = b->branchpoint;
930          int x1 = r->x + r->w/2 + 2;          int x1 = r->cx + r->w/2 + 2;
931          int y1 = r->y + r->h/2;          int y1 = r->y + r->h/2;
932          int x2 = b->x;          int x2 = b->cx;
933          int y2 = b->y;          int y2 = b->y;
934          gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);          gdImageLine(im, x1, y1, x2, y1, conf.branch_color.id);
935          gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);          gdImageLine(im, x2, y1, x2, y2, conf.branch_color.id);
936  }  }
937    
938  gdImagePtr make_image(rcsfilelog_t *rcs)  gdImagePtr make_image(rcsfile_t *rcs)
939  {  {
940          gdImagePtr im;          gdImagePtr im;
941          int i;          int i;
942            char *cptr;
943    
944          im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);          im = gdImageCreate(rcs->tw+conf.margin_left+conf.margin_right, rcs->th+conf.margin_top+conf.margin_bottom);
945          conf.color_bg.id = gdImageColorAllocate(im, conf.color_bg.r, conf.color_bg.g, conf.color_bg.b);          conf.color_bg.id = gdImageColorAllocate(im, conf.color_bg.r, conf.color_bg.g, conf.color_bg.b);
# Line 865  Line 950 
950          conf.title_color.id = gdImageColorAllocate(im, conf.title_color.r, conf.title_color.g, conf.title_color.b);          conf.title_color.id = gdImageColorAllocate(im, conf.title_color.r, conf.title_color.g, conf.title_color.b);
951    
952          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
953                  draw_branch(im, rcs->branches[i]->x, rcs->branches[i]->y, rcs->branches[i]);                  draw_branch(im, rcs->branches[i]->cx, rcs->branches[i]->y, rcs->branches[i]);
954          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
955          {          {
956                  if(rcs->branches[i]->branchpoint)                  if(rcs->branches[i]->branchpoint)
957                          draw_connector(im, rcs->branches[i]);                          draw_connector(im, rcs->branches[i]);
958          }          }
959          draw_title(im, expand_title(rcs));          cptr = expand_string(conf.title, rcs, NULL, NULL);
960            draw_title(im, cptr);
961            xfree(cptr);
962    
963          return im;          return im;
964  }  }
965    
966    /*
967     **************************************************************************
968     * Layout routines
969     **************************************************************************
970     */
971  void move_branch(branch_t *b, int x, int y)  void move_branch(branch_t *b, int x, int y)
972  {  {
973          int i;          int i;
974          b->x += x;          b->cx += x;
975          b->y += y;          b->y += y;
976          for(i = 0; i < b->nrevs; i++)          for(i = 0; i < b->nrevs; i++)
977          {          {
978                  b->revs[i]->x += x;                  b->revs[i]->cx += x;
979                  b->revs[i]->y += y;                  b->revs[i]->y += y;
980          }          }
981  }  }
982    
983    void reposition_branch(revision_t *r, int *x, int *w)
984    {
985            int i, j;
986            for(j = 0; j < r->nbranches; j++)
987            {
988                    branch_t *b = r->branches[j];
989                    *x += *w + conf.rev_minline + b->tw/2 - b->cx;
990                    *w = b->tw/2;
991                    move_branch(b, *x, r->y + r->h/2 + conf.branch_connect);
992                    *x = b->cx;
993                    /* Recurse to move branches of branched revisions */
994                    for(i = b->nrevs-1; i >= 0; i--)
995                    {
996                            reposition_branch(b->revs[i], x, w);
997                    }
998            }
999    }
1000    
1001  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)
1002  {  {
1003          int x1 = *x;          int x1 = *x;
1004          int x2 = x1 + *w;          int x2 = x1 + *w;
1005          int y1 = *y;          int y1 = *y;
1006          int y2 = y1 + *h;          int y2 = y1 + *h;
1007          int xx1 = b->x - b->tw/2;          int xx1 = b->cx - b->tw/2;
1008          int xx2 = xx1 + b->tw;          int xx2 = xx1 + b->tw;
1009          int yy1 = b->y;          int yy1 = b->y;
1010          int yy2 = yy1 + b->th;          int yy2 = yy1 + b->th;
# Line 908  Line 1018 
1018          *h = y2 - y1;          *h = y2 - y1;
1019  }  }
1020    
1021  void make_layout(rcsfilelog_t *rcs)  int branch_intersects(int top, int bottom, int left, branch_t *b)
1022    {
1023            int br = b->cx + b->tw/2;
1024            int bt = b->y - conf.branch_connect - conf.branch_margin/2;
1025            int bb = b->y + b->th + conf.branch_margin/2;
1026            return !(bt > bottom || bb < top || br >= left);
1027    }
1028    
1029    int kern_branch(rcsfile_t *rcs, branch_t *b)
1030    {
1031            int left = b->cx - b->tw/2;
1032            int top = b->y - conf.branch_connect - conf.branch_margin/2;
1033            int bottom = b->y + b->th + conf.branch_margin/2;
1034            int i;
1035            int xpos = 0;
1036    
1037            for(i = 0; i < rcs->nbranches; i++)
1038            {
1039                    branch_t *bp = rcs->branches[i];
1040                    if(bp == b)
1041                            continue;
1042                    if(branch_intersects(top, bottom, left, bp))
1043                    {
1044                            int m = bp->cx + bp->tw/2 + conf.branch_margin;
1045                            if(m > xpos)
1046                                    xpos = m;
1047                    }
1048            }
1049            if(xpos && (b->cx - b->tw/2) - xpos > 0)
1050            {
1051                    move_branch(b, xpos - (b->cx - b->tw/2), 0);
1052                    return 1;
1053            }
1054            return 0;
1055    }
1056    
1057    void make_layout(rcsfile_t *rcs)
1058  {  {
1059          int i, j;          int i, j;
1060          int x, y;          int x, y;
1061          int w, h;          int w, h;
1062          int w2;          int w2;
1063            int moved;
1064    
1065          /* Calculate the box-sizes of the revisions */          /* Calculate the box-sizes of the revisions */
1066          for(i = 0; i < rcs->nrevs; i++)          for(i = 0; i < rcs->nsrev; i++)
1067          {          {
1068                  revision_t *rp;                  revision_t *rp;
1069                  int w;                  int w;
1070                  int h;                  int h;
1071                  rp = rcs->revs[i];                  rp = rcs->srev[i];
1072                  w = get_swidth(rp->rev->rev, &conf.rev_font);                  w = get_swidth(rp->rev->rev, &conf.rev_font);
1073                  h = get_sheight(rp->rev->rev, &conf.rev_font);                  h = get_sheight(rp->rev->rev, &conf.rev_font);
1074                  for(j = 0; j < rp->ntags; j++)                  for(j = 0; j < rp->ntags; j++)
# Line 940  Line 1087 
1087                  branch_t *bp = rcs->branches[i];                  branch_t *bp = rcs->branches[i];
1088                  int w;                  int w;
1089                  int h;                  int h;
1090                  w = get_swidth(bp->branch, &conf.branch_font);                  w = get_swidth(bp->branch->branch, &conf.branch_font);
1091                  h = get_sheight(bp->branch, &conf.branch_font);                  h = get_sheight(bp->branch->branch, &conf.branch_font);
1092                  if(bp->tag)                  for(j = 0; j < bp->ntags; j++)
1093                  {                  {
1094                          int ww = get_swidth(bp->tag->tag, &conf.branch_font);                          int ww = get_swidth(bp->tags[j]->tag, &conf.branch_font);
1095                          if(ww > w) w = ww;                          if(ww > w) w = ww;
1096                          h += get_sheight(bp->tag->tag, &conf.branch_font) + conf.branch_separator;                          h += get_sheight(bp->tags[j]->tag, &conf.branch_font);
1097                  }                  }
1098                  w += conf.branch_lspace + conf.branch_rspace;                  w += conf.branch_lspace + conf.branch_rspace;
1099                  h += conf.branch_tspace + conf.branch_bspace;                  h += conf.branch_tspace + conf.branch_bspace;
# Line 968  Line 1115 
1115                  branch_t *b = rcs->branches[i];                  branch_t *b = rcs->branches[i];
1116                  x = b->tw/2;                  x = b->tw/2;
1117                  y = b->h;                  y = b->h;
1118                  b->x = x;                  b->cx = x;
1119                  b->y = 0;                  b->y = 0;
1120                  for(j = 0; j < b->nrevs; j++)                  for(j = 0; j < b->nrevs; j++)
1121                  {                  {
1122                          y += conf.rev_minline;                          y += conf.rev_minline;
1123                          b->revs[j]->x = x;                          b->revs[j]->cx = x;
1124                          b->revs[j]->y = y;                          b->revs[j]->y = y;
1125                          y += b->revs[j]->h;                          y += b->revs[j]->h;
1126                  }                  }
1127          }          }
1128    
1129          /* Reposition the branches FIXME: Should be recursive on branchpoint revisions within branches... */          /* Reposition the branches */
1130          x = rcs->branches[0]->x;          x = rcs->branches[0]->cx;
1131          w2 = rcs->branches[0]->tw / 2;          w2 = rcs->branches[0]->tw / 2;
1132          for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)          for(i = rcs->branches[0]->nrevs-1; i >= 0; i--)
1133          {          {
1134                  revision_t *r = rcs->branches[0]->revs[i];                  reposition_branch(rcs->branches[0]->revs[i], &x, &w2);
1135                  for(j = 0; j < r->nbranches; j++)          }
1136    
1137            /* Try to move branches left if there is room (kerning) */
1138            for(moved = 1; moved; )
1139            {
1140                    moved = 0;
1141                    for(i = 1; i < rcs->nbranches; i++)
1142                  {                  {
1143                          branch_t *b = r->branches[j];                          moved += kern_branch(rcs, rcs->branches[i]);
                         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;  
1144                  }                  }
1145          }          }
1146    
1147            /* Move everything w.r.t. the top-left margin */
1148          for(i = 0; i < rcs->nbranches; i++)          for(i = 0; i < rcs->nbranches; i++)
1149                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);                  move_branch(rcs->branches[i], conf.margin_left, conf.margin_top);
1150    
1151          /* Calculate overall image size */          /* Calculate overall image size */
1152          x = rcs->branches[0]->x - rcs->branches[0]->tw/2;          x = rcs->branches[0]->cx - rcs->branches[0]->tw/2;
1153          y = rcs->branches[0]->y;          y = rcs->branches[0]->y;
1154          w = rcs->branches[0]->tw;          w = rcs->branches[0]->tw;
1155          h = rcs->branches[0]->th;          h = rcs->branches[0]->th;
# Line 1011  Line 1161 
1161    
1162  /*  /*
1163   **************************************************************************   **************************************************************************
1164     * Imagemap functions
1165     **************************************************************************
1166     */
1167    void make_imagemap(rcsfile_t *rcs, FILE *fp)
1168    {
1169            int i, j;
1170            char *href;
1171            char *alt;
1172            fprintf(fp, "<map name=\"%s\">\n", conf.map_name);
1173            for(i = 0; i < rcs->nbranches; i++)
1174            {
1175                    branch_t *b = rcs->branches[i];
1176                    tag_t *tag = b->ntags ? b->tags[0] : NULL;
1177                    href = expand_string(conf.map_branch_href, rcs, b->branch, tag);
1178                    alt = expand_string(conf.map_branch_alt, rcs, b->branch, tag);
1179                    fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1180                                    href,
1181                                    b->cx - b->w/2, b->y, b->cx + b->w/2, b->y + b->h,
1182                                    alt);
1183                    xfree(href);
1184                    xfree(alt);
1185                    for(j = 0; j < b->nrevs; j++)
1186                    {
1187                            revision_t *r = b->revs[j];
1188                            tag = r->ntags ? r->tags[0] : NULL;
1189                            href = expand_string(conf.map_rev_href, rcs, r->rev, tag);
1190                            alt = expand_string(conf.map_rev_alt, rcs, r->rev, tag);
1191                            fprintf(fp, "\t<area shape=\"rect\" %s coords=\"%d,%d,%d,%d\" %s>\n",
1192                                    href,
1193                                    r->cx - r->w/2, r->y, r->cx + r->w/2, r->y + r->h,
1194                                    alt);
1195                            xfree(href);
1196                            xfree(alt);
1197                    }
1198            }
1199            fprintf(fp, "</map>\n");
1200    }
1201    
1202    /*
1203     **************************************************************************
1204   * Configuration   * Configuration
1205   **************************************************************************   **************************************************************************
1206   */   */
# Line 1018  Line 1208 
1208  {  {
1209          FILE *fp;          FILE *fp;
1210          int r;          int r;
1211    
1212          if(path)          if(path)
1213          {          {
1214                  if((fp = fopen(path, "r")) == NULL)                  if((fp = fopen(path, "r")) == NULL)
1215                  {                  {
1216                          return 0;                          return 0;
1217                  }                  }
1218                    else
1219                            input_file = path;
1220          }          }
1221          else          else
1222          {          {
# Line 1033  Line 1226 
1226                          {                          {
1227                                  return 0;                                  return 0;
1228                          }                          }
1229                            else
1230                                    input_file = ETCDIR "/" CONFFILENAME;
1231                  }                  }
1232                    else
1233                            input_file = "./" CONFFILENAME;
1234          }          }
1235    
1236          yyin = fp;          yyin = fp;
1237          r = yyparse();          r = yyparse();
1238          fclose(fp);          fclose(fp);
1239            input_file = NULL;
1240          return r == 0;          return r == 0;
1241  }  }
1242    
# Line 1049  Line 1247 
1247   */   */
1248  static const char usage_str[] =  static const char usage_str[] =
1249          "Usage: cvsgraph [options] <file>\n"          "Usage: cvsgraph [options] <file>\n"
1250          "  -c <file>  Read alternative config from <file>\n"          "  -c <file>    Read alternative config from <file>\n"
1251          "  -h         This message\n"          "  -d <level>   Enable debug mode at <level>\n"
1252          "  -m <mod>   Use <mod> as cvs module\n"          "  -h           This message\n"
1253          "  -o <file>  Output to <file>\n"          "  -i           Generate an imagemap instead of image\n"
1254          "  -r <path>  Use <path> as cvsroot path\n"          "  -M <name>    Use <name> as imagemap name\n"
1255          "  -V         Print version and exit\n"          "  -m <mod>     Use <mod> as cvs module\n"
1256            "  -o <file>    Output to <file>\n"
1257            "  -q           Be quiet (i.e. no warnings)\n"
1258            "  -r <path>    Use <path> as cvsroot path\n"
1259            "  -V           Print version and exit\n"
1260            "  -[0-9] <txt> Use <txt> for expansion\n"
1261          ;          ;
1262    
1263  #define VERSION_STR     "1.0.0"  #define VERSION_STR     "1.1.0"
1264  #define NOTICE_STR      "Copyright (c) 2001 B.Stultiens"  #define NOTICE_STR      "Copyright (c) 2001 B.Stultiens"
1265    
 void add_tag(rcsfilelog_t *rcs, const char *tag, const char *rev)  
 {  
         rcs->tags = xrealloc(rcs->tags, (rcs->ntags+1)*sizeof(rcs->tags[0]));  
         rcs->tags[rcs->ntags] = xmalloc(sizeof(tag_t));  
         rcs->tags[rcs->ntags]->tag = strip_dup(tag);  
         rcs->tags[rcs->ntags]->rev = make_revid(rev);  
         rcs->ntags++;  
 }  
   
1266  int main(int argc, char *argv[])  int main(int argc, char *argv[])
1267  {  {
1268            extern int yy_flex_debug;
1269            extern int rcs_flex_debug;
1270            extern int yydebug;
1271            extern int rcsdebug;
1272          int optc;          int optc;
1273          char *confpath = NULL;          char *confpath = NULL;
1274          char *outfile = NULL;          char *outfile = NULL;
1275          char *cvsroot = NULL;          char *cvsroot = NULL;
1276          char *cvsmodule = NULL;          char *cvsmodule = NULL;
1277            int imagemap = 0;
1278            char *imgmapname = NULL;
1279          int lose = 0;          int lose = 0;
1280          FILE *fp;          FILE *fp;
1281          int n;          rcsfile_t *rcs;
         rcsfilelog_t *rcs;  
1282          gdImagePtr im;          gdImagePtr im;
1283    
1284          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:c:d:hiM:m:o:qr:V")) != EOF)
1285          {          {
1286                  switch(optc)                  switch(optc)
1287                  {                  {
1288                  case 'c':                  case 'c':
1289                          confpath = xstrdup(optarg);                          confpath = xstrdup(optarg);
1290                          break;                          break;
1291                    case 'd':
1292                            debuglevel = strtol(optarg, NULL, 0);
1293                            break;
1294                    case 'i':
1295                            imagemap = 1;
1296                            break;
1297                    case 'M':
1298                            imgmapname = xstrdup(optarg);
1299                            break;
1300                  case 'm':                  case 'm':
1301                          cvsmodule = xstrdup(optarg);                          cvsmodule = xstrdup(optarg);
1302                          break;                          break;
1303                  case 'o':                  case 'o':
1304                          outfile = xstrdup(optarg);                          outfile = xstrdup(optarg);
1305                          break;                          break;
1306                    case 'q':
1307                            quiet = 1;
1308                            break;
1309                  case 'r':                  case 'r':
1310                          cvsroot = xstrdup(optarg);                          cvsroot = xstrdup(optarg);
1311                          break;                          break;
# Line 1105  Line 1316 
1316                          fprintf(stdout, "%s", usage_str);                          fprintf(stdout, "%s", usage_str);
1317                          return 0;                          return 0;
1318                  default:                  default:
1319                          lose++;                          if(isdigit(optc))
1320                            {
1321                                    conf.expand[optc-'0'] = xstrdup(optarg);
1322                            }
1323                            else
1324                                    lose++;
1325                  }                  }
1326          }          }
1327    
# Line 1121  Line 1337 
1337                  return 1;                  return 1;
1338          }          }
1339    
1340            if(debuglevel)
1341            {
1342                    setvbuf(stdout, NULL, 0, _IONBF);
1343                    setvbuf(stderr, NULL, 0, _IONBF);
1344            }
1345            yy_flex_debug = (debuglevel & DEBUG_CONF_LEX) != 0;
1346            rcs_flex_debug = (debuglevel & DEBUG_RCS_LEX) != 0;
1347            yydebug = (debuglevel & DEBUG_CONF_YACC) != 0;
1348            rcsdebug = (debuglevel & DEBUG_RCS_YACC) != 0;
1349    
1350          /* Set defaults */          /* Set defaults */
1351          if(!conf.tag_font)      conf.tag_font = gdFontTiny;          if(!conf.tag_font)      conf.tag_font = gdFontTiny;
1352          if(!conf.rev_font)      conf.rev_font = gdFontTiny;          if(!conf.rev_font)      conf.rev_font = gdFontTiny;
# Line 1134  Line 1360 
1360          }          }
1361    
1362          /* Set overrides */          /* Set overrides */
1363          if(cvsroot)             conf.cvsroot = cvsroot;          if(cvsroot)     conf.cvsroot = cvsroot;
1364          if(cvsmodule)           conf.cvsmodule = cvsmodule;          if(cvsmodule)   conf.cvsmodule = cvsmodule;
1365            if(imgmapname)  conf.map_name = imgmapname;
1366    
1367          if((fp = get_log(conf.cvsroot, conf.cvsmodule, argv[optind])) == NULL)          rcs = get_rcsfile(conf.cvsroot, conf.cvsmodule, argv[optind]);
         {  
                 fprintf(stderr, "Error getting log for '%s'\n", argv[optind]);  
                 return 1;  
         }  
   
         rcs = parse_log(fp);  
1368          if(!rcs)          if(!rcs)
1369          {          {
1370                  fprintf(stderr, "Error parsing log\n");                  fprintf(stderr, "Error reading rcs-file\n");
1371                  return 1;                  return 1;
1372          }          }
         fclose(fp);  
1373    
1374          /* Add implicit tags */          if(debuglevel & DEBUG_RCS_FILE)
1375          add_tag(rcs, "HEAD", rcs->head->rev);                  dump_rcsfile(rcs);
         add_tag(rcs, "MAIN", "1");  
   
         /* We now have the log. Sort and reorganize a little */  
         qsort(rcs->tags, rcs->ntags, sizeof(rcs->tags[0]), tag_sort);  
         qsort(rcs->revs, rcs->nrevs, sizeof(rcs->revs[0]), rev_sort);  
1376    
1377          /* Assign tags to revisions */          if(!reorganise_branches(rcs))
1378          for(n = 0; n < rcs->ntags; n++)                  return 1;
1379          {  
1380                  revision_t *r = find_revision(rcs, rcs->tags[n]->rev);          if(!assign_tags(rcs))
1381                  if(!r)                  return 1;
                         continue;  
                 r->tags = xrealloc(r->tags, (r->ntags+1) * sizeof(r->tags[0]));  
                 r->tags[r->ntags] = rcs->tags[n];  
                 r->ntags++;  
         }  
1382    
1383          /* Isolate the branches */          if(outfile)
         for(n = 0; n < rcs->nrevs; n++)  
1384          {          {
1385                  branch_t *b = find_branch(rcs, rcs->revs[n]->rev->branch);                  if((fp = fopen(outfile, "w")) == NULL)
                 if(!b)  
1386                  {                  {
1387                          rcs->branches = xrealloc(rcs->branches, (rcs->nbranches+1) * sizeof(rcs->branches[0]));                          perror(outfile);
1388                          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);  
1389                  }                  }
                 b->revs = xrealloc(b->revs, (b->nrevs+1) * sizeof(b->revs[0]));  
                 b->revs[b->nrevs] = rcs->revs[n];  
                 b->nrevs++;  
         }  
   
         /* Find the branchpoints */  
         for(n = 0; n < rcs->nbranches; n++)  
         {  
                 char *prev = xstrdup(rcs->branches[n]->branch);  
                 char *cptr = strrchr(prev, '.');  
                 revision_t *r;  
                 revid_t rid;  
                 if(!cptr)       /* Main branch number */  
                         continue;  
                 *cptr = '\0';  
                 rid.isbranch = 0;  
                 rid.branch = "";  
                 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++;  
1390          }          }
1391            else
1392                    fp = stdout;
1393    
1394          make_layout(rcs);          make_layout(rcs);
1395    
1396  #ifdef DEBUG          if(!imagemap)
         dump_log(rcs);  
 #endif  
         im = make_image(rcs);  
         if(outfile)  
1397          {          {
1398                  if((fp = fopen(outfile, "w")) == NULL)                  /* Create an image */
1399                    im = make_image(rcs);
1400    
1401                    switch(conf.image_type)
1402                  {                  {
1403                          perror(outfile);  #ifdef HAVE_IMAGE_GIF
1404                          return 1;  # ifndef HAVE_IMAGE_PNG
1405                    default:
1406    # endif
1407                    case IMAGE_GIF:
1408                            gdImageGif(im, fp);
1409                            break;
1410    #endif
1411    #ifdef HAVE_IMAGE_PNG
1412                    default:
1413                    case IMAGE_PNG:
1414                            gdImagePng(im, fp);
1415                            break;
1416    #endif
1417    #ifdef HAVE_IMAGE_JPEG
1418    # if !defined(HAVE_IMAGE_GIF) && !defined(HAVE_IMAGE_PNG)
1419                    default:
1420    # endif
1421                    case IMAGE_JPEG:
1422                            gdImageJpeg(im, fp, conf.image_quality);
1423                            break;
1424    #endif
1425                  }                  }
1426    
1427                    gdImageDestroy(im);
1428          }          }
1429          else          else
1430                  fp = stdout;          {
1431          GD_IMAGE_XXX(im, fp);                  /* Create an imagemap */
1432                    make_imagemap(rcs, fp);
1433            }
1434    
1435          if(outfile)          if(outfile)
1436                  fclose(fp);                  fclose(fp);
1437          gdImageDestroy(im);  
1438          return 0;          return 0;
1439  }  }
1440    

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

  ViewVC Help
Powered by ViewVC 1.1.0 with CvsGraph 1.7.0