Changes On Branch StvPrivateHook2
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch StvPrivateHook2 Excluding Merge-Ins

This is equivalent to a diff from f88368742d to e6dce6a16a

2010-10-29
21:11
merge from trunk and add sqlite shell to windows make check-in: 6d334ac9ed user: wolfgang tags: StvPrivateHook2
2010-10-28
17:41
merge from trunk Leaf check-in: e6dce6a16a user: wolfgang tags: StvPrivateHook2
14:41
Fix a few harmless compiler warnings. check-in: d03718ad5f user: drh tags: trunk
2010-10-27
20:39
added missing dependency in dmc windows make for resource file check-in: 304c71a09c user: wolfgang tags: StvPrivateHook2
2010-10-17
16:37
merge from old hook branch check-in: 9cf288de27 user: wolfgang tags: StvPrivateHook2
2010-10-16
19:07
PellesC doesn't have pgmptr, update Makefile Leaf check-in: f88368742d user: wolfgang tags: wolfgangHelpCmd
17:33
merge from trunk check-in: 586b0eb144 user: wolfgang tags: wolfgangHelpCmd

Changes to Makefile.

    39     39   #### Extra arguments for linking the finished binary.  Fossil needs
    40     40   #    to link against the Z-Lib compression library.  There are no
    41     41   #    other dependencies.  We sometimes add the -static option here
    42     42   #    so that we can build a static executable that will run in a
    43     43   #    chroot jail.
    44     44   #
    45     45   LIB = -lz $(LDFLAGS)
    46         -HOST_OS!= uname -s
    47         -LIB.SunOS= -lsocket -lnsl
    48         -LIB += $(LIB.$(HOST_OS))
    49     46   
    50     47   # If using HTTPS:
    51     48   LIB += -lcrypto -lssl
    52     49   
    53     50   #### Tcl shell for use in running the fossil testsuite.
    54     51   #
    55     52   TCLSH = tclsh
    56     53   
    57     54   # You should not need to change anything below this line
    58     55   ###############################################################################
           56  +#
           57  +# Automatic platform-specific options.
           58  +HOST_OS!= uname -s
           59  +
           60  +LIB.SunOS= -lsocket -lnsl
           61  +LIB += $(LIB.$(HOST_OS))
           62  +
           63  +TCC.DragonFly += -DUSE_PREAD
           64  +TCC.FreeBSD += -DUSE_PREAD
           65  +TCC.NetBSD += -DUSE_PREAD
           66  +TCC.OpenBSD += -DUSE_PREAD
           67  +TCC += $(TCC.$(HOST_OS))
           68  +
    59     69   include $(SRCDIR)/main.mk

Changes to src/branch.c.

    33     33     char *zUuid;           /* Artifact ID of origin */
    34     34     Stmt q;                /* Generic query */
    35     35     const char *zBranch;   /* Name of the new branch */
    36     36     char *zDate;           /* Date that branch was created */
    37     37     char *zComment;        /* Check-in comment for the new branch */
    38     38     const char *zColor;    /* Color of the new branch */
    39     39     Blob branch;           /* manifest for the new branch */
    40         -  Blob parent;           /* root check-in manifest */
    41         -  Manifest mParent;      /* Parsed parent manifest */
           40  +  Manifest *pParent;     /* Parsed parent manifest */
    42     41     Blob mcksum;           /* Self-checksum on the manifest */
    43     42     const char *zDateOvrd; /* Override date string */
    44     43     const char *zUserOvrd; /* Override user name */
    45     44    
    46     45     noSign = find_option("nosign","",0)!=0;
    47     46     zColor = find_option("bgcolor","c",1);
    48     47     zDateOvrd = find_option("date-override",0,1);
................................................................................
    69     68   
    70     69     user_select();
    71     70     db_begin_transaction();
    72     71     rootid = name_to_rid(g.argv[4]);
    73     72     if( rootid==0 ){
    74     73       fossil_fatal("unable to locate check-in off of which to branch");
    75     74     }
           75  +
           76  +  pParent = manifest_get(rootid, CFTYPE_MANIFEST);
           77  +  if( pParent==0 ){
           78  +    fossil_fatal("%s is not a valid check-in", g.argv[4]);
           79  +  }
    76     80   
    77     81     /* Create a manifest for the new branch */
    78     82     blob_zero(&branch);
           83  +  if( pParent->zBaseline ){
           84  +    blob_appendf(&branch, "B %s\n", pParent->zBaseline);
           85  +  }
    79     86     zComment = mprintf("Create new branch named \"%h\"", zBranch);
    80     87     blob_appendf(&branch, "C %F\n", zComment);
    81     88     zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
    82     89     zDate[10] = 'T';
    83     90     blob_appendf(&branch, "D %s\n", zDate);
    84     91   
    85     92     /* Copy all of the content from the parent into the branch */
    86         -  content_get(rootid, &parent);
    87         -  manifest_parse(&mParent, &parent);
    88         -  if( mParent.type!=CFTYPE_MANIFEST ){
    89         -    fossil_fatal("%s is not a valid check-in", g.argv[4]);
    90         -  }
    91         -  for(i=0; i<mParent.nFile; ++i){
    92         -    if( mParent.aFile[i].zPerm[0] ){
    93         -      blob_appendf(&branch, "F %F %s %s\n",
    94         -                   mParent.aFile[i].zName,
    95         -                   mParent.aFile[i].zUuid,
    96         -                   mParent.aFile[i].zPerm);
    97         -    }else{
    98         -      blob_appendf(&branch, "F %F %s\n",
    99         -                   mParent.aFile[i].zName,
   100         -                   mParent.aFile[i].zUuid);
           93  +  for(i=0; i<pParent->nFile; ++i){
           94  +    blob_appendf(&branch, "F %F", pParent->aFile[i].zName);
           95  +    if( pParent->aFile[i].zUuid ){
           96  +      blob_appendf(&branch, " %s", pParent->aFile[i].zUuid);
           97  +      if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){
           98  +        blob_appendf(&branch, " %s", pParent->aFile[i].zPerm);
           99  +      }
   101    100       }
          101  +    blob_append(&branch, "\n", 1);
   102    102     }
   103    103     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
   104    104     blob_appendf(&branch, "P %s\n", zUuid);
   105         -  blob_appendf(&branch, "R %s\n", mParent.zRepoCksum);
   106         -  manifest_clear(&mParent);
          105  +  blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
          106  +  manifest_destroy(pParent);
   107    107   
   108    108     /* Add the symbolic branch name and the "branch" tag to identify
   109    109     ** this as a new branch */
   110    110     if( zColor!=0 ){
   111    111       blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
   112    112     }
   113    113     blob_appendf(&branch, "T *branch * %F\n", zBranch);

Changes to src/browse.c.

    69     69   ** to the "dir" page for the directory.
    70     70   **
    71     71   ** There is no hyperlink on the file element of the path.
    72     72   **
    73     73   ** The computed string is appended to the pOut blob.  pOut should
    74     74   ** have already been initialized.
    75     75   */
    76         -void hyperlinked_path(const char *zPath, Blob *pOut){
           76  +void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){
    77     77     int i, j;
    78     78     char *zSep = "";
    79     79   
    80     80     for(i=0; zPath[i]; i=j){
    81     81       for(j=i; zPath[j] && zPath[j]!='/'; j++){}
    82     82       if( zPath[j] && g.okHistory ){
    83         -      blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", 
    84         -                   zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]);
           83  +      if( zCI ){
           84  +        blob_appendf(pOut, "%s<a href=\"%s/dir?ci=%S&amp;name=%#T\">%#h</a>", 
           85  +                     zSep, g.zBaseURL, zCI, j, zPath, j-i, &zPath[i]);
           86  +      }else{
           87  +        blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", 
           88  +                     zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]);
           89  +      }
    85     90       }else{
    86     91         blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
    87     92       }
    88     93       zSep = "/";
    89     94       while( zPath[j]=='/' ){ j++; }
    90     95     }
    91     96   }
................................................................................
    97    102   ** Query parameters:
    98    103   **
    99    104   **    name=PATH        Directory to display.  Required.
   100    105   **    ci=LABEL         Show only files in this check-in.  Optional.
   101    106   */
   102    107   void page_dir(void){
   103    108     const char *zD = P("name");
          109  +  int nD = zD ? strlen(zD)+1 : 0;
   104    110     int mxLen;
   105    111     int nCol, nRow;
   106    112     int cnt, i;
   107    113     char *zPrefix;
   108    114     Stmt q;
   109    115     const char *zCI = P("ci");
   110    116     int rid = 0;
   111    117     char *zUuid = 0;
   112         -  Blob content;
   113    118     Blob dirname;
   114         -  Manifest m;
          119  +  Manifest *pM = 0;
   115    120     const char *zSubdirLink;
   116    121   
   117    122     login_check_credentials();
   118    123     if( !g.okHistory ){ login_needed(); return; }
   119    124     style_header("File List");
   120    125     sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
   121    126                             pathelementFunc, 0, 0);
................................................................................
   124    129     if( zD && strlen(zD)==0 ){ zD = 0; }
   125    130   
   126    131     /* If a specific check-in is requested, fetch and parse it.  If the
   127    132     ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
   128    133     ** files from all check-ins to be displayed.
   129    134     */
   130    135     if( zCI ){
   131         -    if( (rid = name_to_rid(zCI))==0 || content_get(rid, &content)==0 ){
   132         -      zCI = 0;  /* No artifact named zCI exists */
   133         -    }else if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){
   134         -      zCI = 0;  /* The artifact exists but is not a manifest */
          136  +    pM = manifest_get_by_name(zCI, &rid);
          137  +    if( pM ){
          138  +      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   135    139       }else{
   136         -      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
          140  +      zCI = 0;
   137    141       }
   138    142     }
   139    143   
   140    144   
   141    145     /* Compute the title of the page */  
   142    146     blob_zero(&dirname);
   143    147     if( zD ){
   144    148       blob_append(&dirname, "in directory ", -1);
   145         -    hyperlinked_path(zD, &dirname);
          149  +    hyperlinked_path(zD, &dirname, zCI);
   146    150       zPrefix = mprintf("%h/", zD);
   147    151     }else{
   148    152       blob_append(&dirname, "in the top-level directory", -1);
   149    153       zPrefix = "";
   150    154     }
   151    155     if( zCI ){
   152    156       char zShort[20];
................................................................................
   158    162       if( zD ){
   159    163         style_submenu_element("Top", "Top", "%s/dir?ci=%S", g.zTop, zUuid);
   160    164         style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD);
   161    165       }else{
   162    166         style_submenu_element("All", "All", "%s/dir", g.zBaseURL);
   163    167       }
   164    168     }else{
          169  +    int hasTrunk;
   165    170       @ <h2>The union of all files from all check-ins
   166    171       @ %s(blob_str(&dirname))</h2>
          172  +    hasTrunk = db_exists(
          173  +                  "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'",
          174  +                  TAG_BRANCH);
   167    175       zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix);
   168    176       if( zD ){
   169    177         style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL);
   170    178         style_submenu_element("Tip", "Tip", "%s/dir?name=%t&amp;ci=tip",
   171    179                               g.zBaseURL, zD);
   172         -      style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&amp;ci=trunk",
   173         -                             g.zBaseURL,zD);
          180  +      if( hasTrunk ){
          181  +        style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&amp;ci=trunk",
          182  +                               g.zBaseURL,zD);
          183  +      }
   174    184       }else{
   175    185         style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL);
   176         -      style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL);
          186  +      if( hasTrunk ){
          187  +        style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL);
          188  +      }
   177    189       }
   178    190     }
   179    191   
   180    192     /* Compute the temporary table "localfiles" containing the names
   181    193     ** of all files and subdirectories in the zD[] directory.  
   182    194     **
   183    195     ** Subdirectory names begin with "/".  This causes them to sort
   184    196     ** first and it also gives us an easy way to distinguish files
   185    197     ** from directories in the loop that follows.
   186    198     */
   187    199     db_multi_exec(
   188    200        "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);"
   189         -     "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);"
   190    201     );
   191    202     if( zCI ){
   192    203       Stmt ins;
   193         -    int i;
   194         -    db_prepare(&ins, "INSERT INTO allfiles VALUES(:x, :u)");
   195         -    for(i=0; i<m.nFile; i++){
   196         -      db_bind_text(&ins, ":x", m.aFile[i].zName);
   197         -      db_bind_text(&ins, ":u", m.aFile[i].zUuid);
          204  +    ManifestFile *pFile;
          205  +    ManifestFile *pPrev = 0;
          206  +    int nPrev = 0;
          207  +    int c;
          208  +
          209  +    db_prepare(&ins,
          210  +       "INSERT OR IGNORE INTO localfiles VALUES(pathelement(:x,0), :u)"
          211  +    );
          212  +    manifest_file_rewind(pM);
          213  +    while( (pFile = manifest_file_next(pM,0))!=0 ){
          214  +      if( nD>0 && memcmp(pFile->zName, zD, nD-1)!=0 ) continue;
          215  +      if( pPrev && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 ){
          216  +        continue;
          217  +      }
          218  +      db_bind_text(&ins, ":x", &pFile->zName[nD]);
          219  +      db_bind_text(&ins, ":u", pFile->zUuid);
   198    220         db_step(&ins);
   199    221         db_reset(&ins);
          222  +      pPrev = pFile;
          223  +      for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){}
          224  +      if( c=='/' ) nPrev++;
   200    225       }
   201    226       db_finalize(&ins);
   202         -  }else{
          227  +  }else if( zD ){
   203    228       db_multi_exec(
   204         -      "INSERT INTO allfiles SELECT name, NULL FROM filename"
   205         -    );
   206         -  }
   207         -  if( zD ){
   208         -    db_multi_exec(
   209         -       "INSERT OR IGNORE INTO localfiles "
   210         -       "  SELECT pathelement(x,%d), u FROM allfiles"
   211         -       "   WHERE x GLOB '%q/*'",
   212         -       strlen(zD)+1, zD
          229  +      "INSERT OR IGNORE INTO localfiles"
          230  +      " SELECT pathelement(name,%d), NULL FROM filename"
          231  +      "  WHERE name GLOB '%q/*'",
          232  +      nD, zD
   213    233       );
   214    234     }else{
   215    235       db_multi_exec(
   216         -       "INSERT OR IGNORE INTO localfiles "
   217         -       "  SELECT pathelement(x,0), u FROM allfiles"
          236  +      "INSERT OR IGNORE INTO localfiles"
          237  +      " SELECT pathelement(name,0), NULL FROM filename"
   218    238       );
   219    239     }
   220    240   
   221    241     /* Generate a multi-column table listing the contents of zD[]
   222    242     ** directory.
   223    243     */
   224    244     mxLen = db_int(12, "SELECT max(length(x)) FROM localfiles /*scan*/");
................................................................................
   244    264         @ <li><a href="%s(g.zBaseURL)/artifact?name=%s(zUuid)">%h(zFN)</a></li>
   245    265       }else{
   246    266         @ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN)
   247    267         @     </a></li>
   248    268       }
   249    269     }
   250    270     db_finalize(&q);
          271  +  manifest_destroy(pM);
   251    272     @ </ul></td></tr></table>
   252    273     style_footer_cmdref("ls",0);
   253    274   }

Changes to src/checkin.c.

   262    262   void extra_cmd(void){
   263    263     Blob path;
   264    264     Blob repo;
   265    265     Stmt q;
   266    266     int n;
   267    267     const char *zIgnoreFlag = find_option("ignore",0,1);
   268    268     int allFlag = find_option("dotfiles",0,0)!=0;
          269  +  int outputManifest = db_get_boolean("manifest",0);
   269    270   
   270    271     db_must_be_within_tree();
   271    272     db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
   272    273     n = strlen(g.zLocalRoot);
   273    274     blob_init(&path, g.zLocalRoot, n-1);
   274    275     if( zIgnoreFlag==0 ){
   275    276       zIgnoreFlag = db_get("ignore-glob", 0);
   276    277     }
   277    278     vfile_scan(0, &path, blob_size(&path), allFlag);
   278    279     db_prepare(&q, 
   279    280         "SELECT x FROM sfile"
   280         -      " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_',"
          281  +      " WHERE x NOT IN ('%s','%s','_FOSSIL_',"
   281    282                          "'_FOSSIL_-journal','.fos','.fos-journal',"
   282    283                          "'_FOSSIL_-wal','_FOSSIL_-shm','.fos-wal',"
   283    284                          "'.fos-shm')"
   284    285         "   AND NOT %s"
   285    286         " ORDER BY 1",
          287  +      outputManifest ? "manifest" : "_FOSSIL_",
          288  +      outputManifest ? "manifest.uuid" : "_FOSSIL_",
   286    289         glob_expr("x", zIgnoreFlag)
   287    290     );
   288    291     if( file_tree_name(g.zRepositoryName, &repo, 0) ){
   289    292       db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo);
   290    293     }
   291    294     while( db_step(&q)==SQLITE_ROW ){
   292    295       printf("%s\n", db_column_text(&q, 0));
................................................................................
   539    542     assert( strlen(zDate)==19 );
   540    543     assert( zDate[10]==' ' );
   541    544     zDate[10] = 'T';
   542    545     return zDate;
   543    546   }
   544    547   
   545    548   /*
   546         -** Return TRUE (non-zero) if a file named "zFilename" exists in
   547         -** the checkout identified by vid.
   548         -**
   549         -** The original purpose of this routine was to check for the presence of
   550         -** a "checked-in" file named "manifest" or "manifest.uuid" so as to avoid
   551         -** overwriting that file with automatically generated files.
          549  +** Create a manifest.
   552    550   */
   553         -int file_exists_in_checkout(int vid, const char *zFilename){
   554         -  return db_exists("SELECT 1 FROM vfile WHERE vid=%d AND pathname=%Q",
   555         -                   vid, zFilename);
          551  +static void create_manifest(
          552  +  Blob *pOut,                 /* Write the manifest here */
          553  +  const char *zBaselineUuid,  /* UUID of baseline, or zero */
          554  +  Manifest *pBaseline,        /* Make it a delta manifest if not zero */
          555  +  Blob *pComment,             /* Check-in comment text */
          556  +  int vid,                    /* blob-id of the parent manifest */
          557  +  int verifyDate,             /* Verify that child is younger */
          558  +  Blob *pCksum,               /* Repository checksum.  May be 0 */
          559  +  const char *zDateOvrd,      /* Date override.  If 0 then use 'now' */
          560  +  const char *zUserOvrd,      /* User override.  If 0 then use g.zLogin */
          561  +  const char *zBranch,        /* Branch name.  May be 0 */
          562  +  const char *zBgColor,       /* Background color.  May be 0 */
          563  +  int *pnFBcard               /* Number of generated B- and F-cards */
          564  +){
          565  +  char *zDate;                /* Date of the check-in */
          566  +  char *zParentUuid;          /* UUID of parent check-in */
          567  +  Blob filename;              /* A single filename */
          568  +  int nBasename;              /* Size of base filename */
          569  +  Stmt q;                     /* Query of files changed */
          570  +  Stmt q2;                    /* Query of merge parents */
          571  +  Blob mcksum;                /* Manifest checksum */
          572  +  ManifestFile *pFile;        /* File from the baseline */
          573  +  int nFBcard = 0;            /* Number of B-cards and F-cards */
          574  +
          575  +  assert( pBaseline==0 || pBaseline->zBaseline==0 );
          576  +  assert( pBaseline==0 || zBaselineUuid!=0 );
          577  +  blob_zero(pOut);
          578  +  zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
          579  +  if( pBaseline ){
          580  +    blob_appendf(pOut, "B %s\n", zBaselineUuid);
          581  +    manifest_file_rewind(pBaseline);
          582  +    pFile = manifest_file_next(pBaseline, 0);
          583  +    nFBcard++;
          584  +  }else{
          585  +    pFile = 0;
          586  +  }
          587  +  blob_appendf(pOut, "C %F\n", blob_str(pComment));
          588  +  zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
          589  +  blob_appendf(pOut, "D %s\n", zDate);
          590  +  zDate[10] = ' ';
          591  +  db_prepare(&q,
          592  +    "SELECT pathname, uuid, origname, blob.rid, isexe"
          593  +    "  FROM vfile JOIN blob ON vfile.mrid=blob.rid"
          594  +    " WHERE NOT deleted AND vfile.vid=%d"
          595  +    " ORDER BY 1", vid);
          596  +  blob_zero(&filename);
          597  +  blob_appendf(&filename, "%s", g.zLocalRoot);
          598  +  nBasename = blob_size(&filename);
          599  +  while( db_step(&q)==SQLITE_ROW ){
          600  +    const char *zName = db_column_text(&q, 0);
          601  +    const char *zUuid = db_column_text(&q, 1);
          602  +    const char *zOrig = db_column_text(&q, 2);
          603  +    int frid = db_column_int(&q, 3);
          604  +    int isexe = db_column_int(&q, 4);
          605  +    const char *zPerm;
          606  +    int cmp;
          607  +    blob_append(&filename, zName, -1);
          608  +#if !defined(_WIN32)
          609  +    /* For unix, extract the "executable" permission bit directly from
          610  +    ** the filesystem.  On windows, the "executable" bit is retained
          611  +    ** unchanged from the original. */
          612  +    isexe = file_isexe(blob_str(&filename));
          613  +#endif
          614  +    if( isexe ){
          615  +      zPerm = " x";
          616  +    }else{
          617  +      zPerm = "";
          618  +    }
          619  +    if( !g.markPrivate ) content_make_public(frid);
          620  +    while( pFile && strcmp(pFile->zName,zName)<0 ){
          621  +      blob_appendf(pOut, "F %F\n", pFile->zName);
          622  +      pFile = manifest_file_next(pBaseline, 0);
          623  +      nFBcard++;
          624  +    }
          625  +    cmp = 1;
          626  +    if( pFile==0
          627  +      || (cmp = strcmp(pFile->zName,zName))!=0
          628  +      || strcmp(pFile->zUuid, zUuid)!=0
          629  +    ){
          630  +      blob_resize(&filename, nBasename);
          631  +      if( zOrig==0 || strcmp(zOrig,zName)==0 ){
          632  +        blob_appendf(pOut, "F %F %s%s\n", zName, zUuid, zPerm);
          633  +      }else{
          634  +        if( zPerm[0]==0 ){ zPerm = " w"; }
          635  +        blob_appendf(pOut, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig);
          636  +      }
          637  +      nFBcard++;
          638  +    }
          639  +    if( cmp==0 ) pFile = manifest_file_next(pBaseline,0);
          640  +  }
          641  +  blob_reset(&filename);
          642  +  db_finalize(&q);
          643  +  while( pFile ){
          644  +    blob_appendf(pOut, "F %F\n", pFile->zName);
          645  +    pFile = manifest_file_next(pBaseline, 0);
          646  +    nFBcard++;
          647  +  }
          648  +  blob_appendf(pOut, "P %s", zParentUuid);
          649  +  if( verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate);
          650  +  free(zParentUuid);
          651  +  db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id");
          652  +  db_bind_int(&q2, ":id", 0);
          653  +  while( db_step(&q2)==SQLITE_ROW ){
          654  +    char *zMergeUuid;
          655  +    int mid = db_column_int(&q2, 0);
          656  +    if( !g.markPrivate && content_is_private(mid) ) continue;
          657  +    zMergeUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid);
          658  +    if( zMergeUuid ){
          659  +      blob_appendf(pOut, " %s", zMergeUuid);
          660  +      if( verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate);
          661  +      free(zMergeUuid);
          662  +    }
          663  +  }
          664  +  db_finalize(&q2);
          665  +  free(zDate);
          666  +
          667  +  blob_appendf(pOut, "\n");
          668  +  if( pCksum ) blob_appendf(pOut, "R %b\n", pCksum);
          669  +  if( zBranch && zBranch[0] ){
          670  +    Stmt q;
          671  +    if( zBgColor && zBgColor[0] ){
          672  +      blob_appendf(pOut, "T *bgcolor * %F\n", zBgColor);
          673  +    }
          674  +    blob_appendf(pOut, "T *branch * %F\n", zBranch);
          675  +    blob_appendf(pOut, "T *sym-%F *\n", zBranch);
          676  +
          677  +    /* Cancel all other symbolic tags */
          678  +    db_prepare(&q,
          679  +        "SELECT tagname FROM tagxref, tag"
          680  +        " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
          681  +        "   AND tagtype>0 AND tagname GLOB 'sym-*'"
          682  +        "   AND tagname!='sym-'||%Q"
          683  +        " ORDER BY tagname",
          684  +        vid, zBranch);
          685  +    while( db_step(&q)==SQLITE_ROW ){
          686  +      const char *zTag = db_column_text(&q, 0);
          687  +      blob_appendf(pOut, "T -%F *\n", zTag);
          688  +    }
          689  +    db_finalize(&q);
          690  +  }  
          691  +  blob_appendf(pOut, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
          692  +  md5sum_blob(pOut, &mcksum);
          693  +  blob_appendf(pOut, "Z %b\n", &mcksum);
          694  +  if( pnFBcard ) *pnFBcard = nFBcard;
   556    695   }
          696  +
   557    697   
   558    698   /*
   559    699   ** COMMAND: ci
   560    700   ** COMMAND: commit
   561    701   **
   562    702   ** Usage: %fossil commit ?OPTIONS? ?FILE...?
   563    703   **
................................................................................
   588    728   **
   589    729   **    --comment|-m COMMENT-TEXT
   590    730   **    --branch NEW-BRANCH-NAME
   591    731   **    --bgcolor COLOR
   592    732   **    --nosign
   593    733   **    --force|-f
   594    734   **    --private
          735  +**    --baseline
          736  +**    --delta
   595    737   **    
   596    738   */
   597    739   void commit_cmd(void){
   598         -  int rc;
   599         -  int vid, nrid, nvid;
   600         -  Blob comment;
   601         -  const char *zComment;
   602         -  Stmt q;
   603         -  Stmt q2;
   604         -  char *zUuid, *zDate;
          740  +  int hasChanges;        /* True if unsaved changes exist */
          741  +  int vid;               /* blob-id of parent version */
          742  +  int nrid;              /* blob-id of a modified file */
          743  +  int nvid;              /* Blob-id of the new check-in */
          744  +  Blob comment;          /* Check-in comment */
          745  +  const char *zComment;  /* Check-in comment */
          746  +  Stmt q;                /* Query to find files that have been modified */
          747  +  char *zUuid;           /* UUID of the new check-in */
   605    748     int noSign = 0;        /* True to omit signing the manifest using GPG */
   606    749     int isAMerge = 0;      /* True if checking in a merge */
   607    750     int forceFlag = 0;     /* Force a fork */
          751  +  int forceDelta = 0;    /* Force a delta-manifest */
          752  +  int forceBaseline = 0; /* Force a baseline-manifest */
   608    753     char *zManifestFile;   /* Name of the manifest file */
   609         -  int nBasename;         /* Length of "g.zLocalRoot/" */
          754  +  int useCksum;          /* True if checksums should be computed and verified */
          755  +  int outputManifest;    /* True to output "manifest" and "manifest.uuid" */
          756  +  int testRun;           /* True for a test run.  Debugging only */
   610    757     const char *zBranch;   /* Create a new branch with this name */
   611    758     const char *zBgColor;  /* Set background color when branching */
   612    759     const char *zDateOvrd; /* Override date string */
   613    760     const char *zUserOvrd; /* Override user name */
   614    761     const char *zComFile;  /* Read commit message from this file */
   615         -  Blob filename;         /* complete filename */
   616         -  Blob manifest;
          762  +  Blob manifest;         /* Manifest in baseline form */
   617    763     Blob muuid;            /* Manifest uuid */
   618         -  Blob mcksum;           /* Self-checksum on the manifest */
   619    764     Blob cksum1, cksum2;   /* Before and after commit checksums */
   620    765     Blob cksum1b;          /* Checksum recorded in the manifest */
          766  +  int szD;               /* Size of the delta manifest */
          767  +  int szB;               /* Size of the baseline manifest */
   621    768    
   622    769     url_proxy_options();
   623    770     noSign = find_option("nosign",0,0)!=0;
          771  +  forceDelta = find_option("delta",0,0)!=0;
          772  +  forceBaseline = find_option("baseline",0,0)!=0;
          773  +  if( forceDelta && forceBaseline ){
          774  +    fossil_fatal("cannot use --delta and --baseline together");
          775  +  }
          776  +  testRun = find_option("test",0,0)!=0;
   624    777     zComment = find_option("comment","m",1);
   625    778     forceFlag = find_option("force", "f", 0)!=0;
   626    779     zBranch = find_option("branch","b",1);
   627    780     zBgColor = find_option("bgcolor",0,1);
   628    781     zComFile = find_option("message-file", "M", 1);
   629    782     if( find_option("private",0,0) ){
   630    783       g.markPrivate = 1;
................................................................................
   632    785       if( zBgColor==0 ) zBgColor = "#fec084";  /* Orange */
   633    786     }
   634    787     zDateOvrd = find_option("date-override",0,1);
   635    788     zUserOvrd = find_option("user-override",0,1);
   636    789     db_must_be_within_tree();
   637    790     noSign = db_get_boolean("omitsign", 0)|noSign;
   638    791     if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
          792  +  useCksum = db_get_boolean("repo-cksum", 1);
          793  +  outputManifest = db_get_boolean("manifest", 0);
   639    794     verify_all_options();
          795  +
          796  +  /* So that older versions of Fossil (that do not understand delta-
          797  +  ** manifest) can continue to use this repository, do not create a new
          798  +  ** delta-manifest unless this repository already contains one or more
          799  +  ** delta-manifets, or unless the delta-manifest is explicitly requested
          800  +  ** by the --delta option.
          801  +  */
          802  +  if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){
          803  +    forceBaseline = 1;
          804  +  }
   640    805   
   641    806     /* Get the ID of the parent manifest artifact */
   642    807     vid = db_lget_int("checkout", 0);
   643    808     if( content_is_private(vid) ){
   644    809       g.markPrivate = 1;
   645    810     }
   646    811   
................................................................................
   683    848     /*
   684    849     ** Check that the user exists.
   685    850     */
   686    851     if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
   687    852       fossil_fatal("no such user: %s", g.zLogin);
   688    853     }
   689    854     
   690         -  rc = unsaved_changes();
          855  +  hasChanges = unsaved_changes();
   691    856     db_begin_transaction();
   692    857     db_record_repository_filename(0);
   693         -  if( rc==0 && !isAMerge && !forceFlag ){
          858  +  if( hasChanges==0 && !isAMerge && !forceFlag ){
   694    859       fossil_fatal("nothing has changed");
   695    860     }
   696    861   
   697    862     /* If one or more files that were named on the command line have not
   698    863     ** been modified, bail out now.
   699    864     */
   700    865     if( g.aCommitFile ){
................................................................................
   722    887     */
   723    888     if( db_exists("SELECT 1 FROM tagxref"
   724    889                   " WHERE tagid=%d AND rid=%d AND tagtype>0",
   725    890                   TAG_CLOSED, vid) ){
   726    891       fossil_fatal("cannot commit against a closed leaf");
   727    892     }
   728    893   
   729         -  vfile_aggregate_checksum_disk(vid, &cksum1);
          894  +  if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1);
   730    895     if( zComment ){
   731    896       blob_zero(&comment);
   732    897       blob_append(&comment, zComment, -1);
   733    898     }else if( zComFile ){
   734    899       blob_zero(&comment);
   735    900       blob_read_from_file(&comment, zComFile);
   736    901     }else{
................................................................................
   777    942         content_deltify(rid, nrid, 0);
   778    943       }
   779    944       db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id);
   780    945       db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
   781    946     }
   782    947     db_finalize(&q);
   783    948   
   784         -  /* Create the manifest */
   785         -  blob_zero(&manifest);
          949  +  /* Create the new manifest */
   786    950     if( blob_size(&comment)==0 ){
   787    951       blob_append(&comment, "(no comment)", -1);
   788    952     }
   789         -  blob_appendf(&manifest, "C %F\n", blob_str(&comment));
   790         -  zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
   791         -  blob_appendf(&manifest, "D %s\n", zDate);
   792         -  zDate[10] = ' ';
   793         -  db_prepare(&q,
   794         -    "SELECT pathname, uuid, origname, blob.rid, isexe"
   795         -    "  FROM vfile JOIN blob ON vfile.mrid=blob.rid"
   796         -    " WHERE NOT deleted AND vfile.vid=%d"
   797         -    " ORDER BY 1", vid);
   798         -  blob_zero(&filename);
   799         -  blob_appendf(&filename, "%s", g.zLocalRoot);
   800         -  nBasename = blob_size(&filename);
   801         -  while( db_step(&q)==SQLITE_ROW ){
   802         -    const char *zName = db_column_text(&q, 0);
   803         -    const char *zUuid = db_column_text(&q, 1);
   804         -    const char *zOrig = db_column_text(&q, 2);
   805         -    int frid = db_column_int(&q, 3);
   806         -    int isexe = db_column_int(&q, 4);
   807         -    const char *zPerm;
   808         -    blob_append(&filename, zName, -1);
   809         -#if !defined(_WIN32)
   810         -    /* For unix, extract the "executable" permission bit directly from
   811         -    ** the filesystem.  On windows, the "executable" bit is retained
   812         -    ** unchanged from the original. */
   813         -    isexe = file_isexe(blob_str(&filename));
   814         -#endif
   815         -    if( isexe ){
   816         -      zPerm = " x";
   817         -    }else{
   818         -      zPerm = "";
   819         -    }
   820         -    blob_resize(&filename, nBasename);
   821         -    if( zOrig==0 || strcmp(zOrig,zName)==0 ){
   822         -      blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm);
   823         -    }else{
   824         -      if( zPerm[0]==0 ){ zPerm = " w"; }
   825         -      blob_appendf(&manifest, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig);
   826         -    }
   827         -    if( !g.markPrivate ) content_make_public(frid);
   828         -  }
   829         -  blob_reset(&filename);
   830         -  db_finalize(&q);
   831         -  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
   832         -  blob_appendf(&manifest, "P %s", zUuid);
   833         -
   834         -  if( !forceFlag ){
   835         -    checkin_verify_younger(vid, zUuid, zDate);
   836         -    db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id");
   837         -    db_bind_int(&q2, ":id", 0);
   838         -    while( db_step(&q2)==SQLITE_ROW ){
   839         -      int mid = db_column_int(&q2, 0);
   840         -      if( !g.markPrivate && content_is_private(mid) ) continue;
   841         -      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid);
   842         -      if( zUuid ){
   843         -        blob_appendf(&manifest, " %s", zUuid);
   844         -        checkin_verify_younger(mid, zUuid, zDate);
   845         -        free(zUuid);
   846         -      }
   847         -    }
   848         -    db_finalize(&q2);
          953  +  if( forceDelta ){
          954  +    blob_zero(&manifest);
          955  +  }else{
          956  +    create_manifest(&manifest, 0, 0, &comment, vid,
          957  +                    !forceFlag, useCksum ? &cksum1 : 0,
          958  +                    zDateOvrd, zUserOvrd, zBranch, zBgColor, &szB);
   849    959     }
   850    960   
   851         -  blob_appendf(&manifest, "\n");
   852         -  blob_appendf(&manifest, "R %b\n", &cksum1);
   853         -  if( zBranch && zBranch[0] ){
   854         -    Stmt q;
   855         -    if( zBgColor && zBgColor[0] ){
   856         -      blob_appendf(&manifest, "T *bgcolor * %F\n", zBgColor);
          961  +  /* See if a delta-manifest would be more appropriate */
          962  +  if( !forceBaseline ){
          963  +    const char *zBaselineUuid;
          964  +    Manifest *pParent;
          965  +    Manifest *pBaseline;
          966  +    pParent = manifest_get(vid, CFTYPE_MANIFEST);
          967  +    if( pParent && pParent->zBaseline ){
          968  +      zBaselineUuid = pParent->zBaseline;
          969  +      pBaseline = manifest_get_by_name(zBaselineUuid, 0);
          970  +    }else{
          971  +      zBaselineUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
          972  +      pBaseline = pParent;
   857    973       }
   858         -    blob_appendf(&manifest, "T *branch * %F\n", zBranch);
   859         -    blob_appendf(&manifest, "T *sym-%F *\n", zBranch);
   860         -
   861         -    /* Cancel all other symbolic tags */
   862         -    db_prepare(&q,
   863         -        "SELECT tagname FROM tagxref, tag"
   864         -        " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid"
   865         -        "   AND tagtype>0 AND tagname GLOB 'sym-*'"
   866         -        "   AND tagname!='sym-'||%Q"
   867         -        " ORDER BY tagname",
   868         -        vid, zBranch);
   869         -    while( db_step(&q)==SQLITE_ROW ){
   870         -      const char *zTag = db_column_text(&q, 0);
   871         -      blob_appendf(&manifest, "T -%F *\n", zTag);
          974  +    if( pBaseline ){
          975  +      Blob delta;
          976  +      create_manifest(&delta, zBaselineUuid, pBaseline, &comment, vid,
          977  +                      !forceFlag, useCksum ? &cksum1 : 0,
          978  +                      zDateOvrd, zUserOvrd, zBranch, zBgColor, &szD);
          979  +      /*
          980  +      ** At this point, two manifests have been constructed, either of
          981  +      ** which would work for this checkin.  The first manifest (held
          982  +      ** in the "manifest" variable) is a baseline manifest and the second
          983  +      ** (held in variable named "delta") is a delta manifest.  The
          984  +      ** question now is: which manifest should we use?
          985  +      **
          986  +      ** Let B be the number of F-cards in the baseline manifest and
          987  +      ** let D be the number of F-cards in the delta manifest, plus one for
          988  +      ** the B-card.  (B is held in the szB variable and D is held in the
          989  +      ** szD variable.)  Assume that all delta manifests adds X new F-cards.
          990  +      ** Then to minimize the total number of F- and B-cards in the repository,
          991  +      ** we should use the delta manifest if and only if:
          992  +      **
          993  +      **      D*D < B*X - X*X
          994  +      **
          995  +      ** X is an unknown here, but for most repositories, we will not be
          996  +      ** far wrong if we assume X=3.
          997  +      */
          998  +      if( forceDelta || (szD*szD)<(szB*3-9) ){
          999  +        blob_reset(&manifest);
         1000  +        manifest = delta;
         1001  +      }else{
         1002  +        blob_reset(&delta);
         1003  +      }
         1004  +    }else if( forceDelta ){
         1005  +      fossil_panic("unable to find a baseline-manifest for the delta");
   872   1006       }
   873         -    db_finalize(&q);
   874         -  }  
   875         -  blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
   876         -  md5sum_blob(&manifest, &mcksum);
   877         -  blob_appendf(&manifest, "Z %b\n", &mcksum);
         1007  +  }
   878   1008     if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){
   879   1009       Blob ans;
   880   1010       blob_zero(&ans);
   881   1011       prompt_user("unable to sign manifest.  continue (y/N)? ", &ans);
   882   1012       if( blob_str(&ans)[0]!='y' ){
   883   1013         fossil_exit(1);
   884   1014       }
   885   1015     }
   886         -  if( !file_exists_in_checkout(vid, "manifest") ){
         1016  +
         1017  +  /* If the --test option is specified, output the manifest file
         1018  +  ** and rollback the transaction.  
         1019  +  */
         1020  +  if( testRun ){
         1021  +    blob_write_to_file(&manifest, "");
         1022  +  }
         1023  +
         1024  +  if( outputManifest ){
   887   1025       zManifestFile = mprintf("%smanifest", g.zLocalRoot);
   888   1026       blob_write_to_file(&manifest, zManifestFile);
   889   1027       blob_reset(&manifest);
   890   1028       blob_read_from_file(&manifest, zManifestFile);
   891   1029       free(zManifestFile);
   892   1030     }
   893   1031     nvid = content_put(&manifest, 0, 0);
................................................................................
   895   1033       fossil_panic("trouble committing manifest: %s", g.zErrMsg);
   896   1034     }
   897   1035     db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
   898   1036     manifest_crosslink(nvid, &manifest);
   899   1037     content_deltify(vid, nvid, 0);
   900   1038     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
   901   1039     printf("New_Version: %s\n", zUuid);
   902         -  if( !file_exists_in_checkout(vid, "manifest.uuid") ){
         1040  +  if( outputManifest ){
   903   1041       zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
   904   1042       blob_zero(&muuid);
   905   1043       blob_appendf(&muuid, "%s\n", zUuid);
   906   1044       blob_write_to_file(&muuid, zManifestFile);
   907   1045       free(zManifestFile);
   908   1046       blob_reset(&muuid);
   909   1047     }
................................................................................
   916   1054       "UPDATE vfile SET vid=%d;"
   917   1055       "UPDATE vfile SET rid=mrid, chnged=0, deleted=0, origname=NULL"
   918   1056       " WHERE file_is_selected(id);"
   919   1057       , vid, nvid
   920   1058     );
   921   1059     db_lset_int("checkout", nvid);
   922   1060   
   923         -  /* Verify that the repository checksum matches the expected checksum
   924         -  ** calculated before the checkin started (and stored as the R record
   925         -  ** of the manifest file).
   926         -  */
   927         -  vfile_aggregate_checksum_repository(nvid, &cksum2);
   928         -  if( blob_compare(&cksum1, &cksum2) ){
   929         -    fossil_panic("tree checksum does not match repository after commit");
   930         -  }
         1061  +  if( useCksum ){
         1062  +    /* Verify that the repository checksum matches the expected checksum
         1063  +    ** calculated before the checkin started (and stored as the R record
         1064  +    ** of the manifest file).
         1065  +    */
         1066  +    vfile_aggregate_checksum_repository(nvid, &cksum2);
         1067  +    if( blob_compare(&cksum1, &cksum2) ){
         1068  +      fossil_panic("tree checksum does not match repository after commit");
         1069  +    }
   931   1070   
   932         -  /* Verify that the manifest checksum matches the expected checksum */
   933         -  vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b);
   934         -  if( blob_compare(&cksum1, &cksum1b) ){
   935         -    fossil_panic("manifest checksum does not agree with manifest: "
   936         -                 "%b versus %b", &cksum1, &cksum1b);
   937         -  }
   938         -  if( blob_compare(&cksum1, &cksum2) ){
   939         -    fossil_panic("tree checksum does not match manifest after commit: "
   940         -                 "%b versus %b", &cksum1, &cksum2);
   941         -  }
         1071  +    /* Verify that the manifest checksum matches the expected checksum */
         1072  +    vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b);
         1073  +    if( blob_compare(&cksum1, &cksum1b) ){
         1074  +      fossil_panic("manifest checksum does not agree with manifest: "
         1075  +                   "%b versus %b", &cksum1, &cksum1b);
         1076  +    }
         1077  +    if( blob_compare(&cksum1, &cksum2) ){
         1078  +      fossil_panic("tree checksum does not match manifest after commit: "
         1079  +                   "%b versus %b", &cksum1, &cksum2);
         1080  +    }
   942   1081   
   943         -  /* Verify that the commit did not modify any disk images. */
   944         -  vfile_aggregate_checksum_disk(nvid, &cksum2);
   945         -  if( blob_compare(&cksum1, &cksum2) ){
   946         -    fossil_panic("tree checksums before and after commit do not match");
         1082  +    /* Verify that the commit did not modify any disk images. */
         1083  +    vfile_aggregate_checksum_disk(nvid, &cksum2);
         1084  +    if( blob_compare(&cksum1, &cksum2) ){
         1085  +      fossil_panic("tree checksums before and after commit do not match");
         1086  +    }
   947   1087     }
   948   1088   
   949   1089     /* Clear the undo/redo stack */
   950   1090     undo_reset();
   951   1091   
   952   1092     /* Commit */
   953   1093     db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
         1094  +  if( testRun ){
         1095  +    db_end_transaction(1);
         1096  +    exit(1);
         1097  +  }
   954   1098     db_end_transaction(0);
   955   1099   
   956   1100     if( !g.markPrivate ){
   957   1101       autosync(AUTOSYNC_PUSH);  
   958   1102     }
   959   1103     if( count_nonbranch_children(vid)>1 ){
   960   1104       printf("**** warning: a fork has occurred *****\n");
   961   1105     }
   962   1106   }

Changes to src/checkout.c.

    76     76     return vid;
    77     77   }
    78     78   
    79     79   /*
    80     80   ** Load a vfile from a record ID.
    81     81   */
    82     82   void load_vfile_from_rid(int vid){
    83         -  Blob manifest;
    84         -
    85     83     if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){
    86     84       return;
    87     85     }
    88         -  content_get(vid, &manifest);
    89         -  vfile_build(vid, &manifest);
    90         -  blob_reset(&manifest);
           86  +  vfile_build(vid);
    91     87   }
    92     88   
    93     89   /*
    94     90   ** Set or clear the vfile.isexe flag for a file.
    95     91   */
    96     92   static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){
    97         -  db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q",
    98         -                onoff, vid, zFilename);
           93  +  static Stmt s;
           94  +  db_static_prepare(&s,
           95  +    "UPDATE vfile SET isexe=:isexe"
           96  +    " WHERE vid=:vid AND pathname=:path AND isexe!=:isexe"
           97  +  );
           98  +  db_bind_int(&s, ":isexe", onoff);
           99  +  db_bind_int(&s, ":vid", vid);
          100  +  db_bind_text(&s, ":path", zFilename);
          101  +  db_step(&s);
          102  +  db_reset(&s);
    99    103   }
   100    104   
   101    105   /*
   102    106   ** Set or clear the execute permission bit (as appropriate) for all
   103    107   ** files in the current check-out.
   104         -**
   105         -** If the checkout does not have explicit files named "manifest" and
   106         -** "manifest.uuid" then automatically generate files with those names
   107         -** containing, respectively, the text of the manifest and the artifact
   108         -** ID of the manifest.
          108  +*/
          109  +void checkout_set_all_exe(int vid){
          110  +  Blob filename;
          111  +  int baseLen;
          112  +  Manifest *pManifest;
          113  +  ManifestFile *pFile;
          114  +
          115  +  /* Check the EXE permission status of all files
          116  +  */
          117  +  pManifest = manifest_get(vid, CFTYPE_MANIFEST);
          118  +  if( pManifest==0 ) return;
          119  +  blob_zero(&filename);
          120  +  blob_appendf(&filename, "%s/", g.zLocalRoot);
          121  +  baseLen = blob_size(&filename);
          122  +  manifest_file_rewind(pManifest);
          123  +  while( (pFile = manifest_file_next(pManifest, 0))!=0 ){
          124  +    int isExe;
          125  +    blob_append(&filename, pFile->zName, -1);
          126  +    isExe = pFile->zPerm && strstr(pFile->zPerm, "x");
          127  +    file_setexe(blob_str(&filename), isExe);
          128  +    set_or_clear_isexe(pFile->zName, vid, isExe);
          129  +    blob_resize(&filename, baseLen);
          130  +  }
          131  +  blob_reset(&filename);
          132  +  manifest_destroy(pManifest);
          133  +}
          134  +
          135  +
          136  +/*
          137  +** If the "manifest" setting is true, then automatically generate
          138  +** files named "manifest" and "manifest.uuid" containing, respectively,
          139  +** the text of the manifest and the artifact ID of the manifest.
   109    140   */
   110    141   void manifest_to_disk(int vid){
   111    142     char *zManFile;
   112    143     Blob manifest;
   113    144     Blob hash;
   114         -  Blob filename;
   115         -  int baseLen;
   116         -  int i;
   117         -  int seenManifest = 0;
   118         -  int seenManifestUuid = 0;
   119         -  Manifest m;
   120    145   
   121         -  /* Check the EXE permission status of all files
   122         -  */
   123         -  blob_zero(&manifest);
   124         -  content_get(vid, &manifest);
   125         -  manifest_parse(&m, &manifest);
   126         -  blob_zero(&filename);
   127         -  blob_appendf(&filename, "%s/", g.zLocalRoot);
   128         -  baseLen = blob_size(&filename);
   129         -  for(i=0; i<m.nFile; i++){ 
   130         -    int isExe;
   131         -    blob_append(&filename, m.aFile[i].zName, -1);
   132         -    isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x");
   133         -    file_setexe(blob_str(&filename), isExe);
   134         -    set_or_clear_isexe(m.aFile[i].zName, vid, isExe);
   135         -    blob_resize(&filename, baseLen);
   136         -    if( memcmp(m.aFile[i].zName, "manifest", 8)==0 ){
   137         -      if( m.aFile[i].zName[8]==0 ) seenManifest = 1;
   138         -      if( strcmp(&m.aFile[i].zName[8], ".uuid")==0 ) seenManifestUuid = 1;
   139         -    }
   140         -  }
   141         -  blob_reset(&filename);
   142         -  manifest_clear(&m);
   143         -
   144         -  blob_zero(&manifest);
   145         -  content_get(vid, &manifest);
   146         -  if( !seenManifest ){
          146  +  if( db_get_boolean("manifest",0) ){
          147  +    blob_zero(&manifest);
          148  +    content_get(vid, &manifest);
   147    149       zManFile = mprintf("%smanifest", g.zLocalRoot);
   148    150       blob_write_to_file(&manifest, zManFile);
   149    151       free(zManFile);
   150         -  }
   151         -  if( !seenManifestUuid ){
   152    152       blob_zero(&hash);
   153    153       sha1sum_blob(&manifest, &hash);
   154    154       zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
   155    155       blob_append(&hash, "\n", 1);
   156    156       blob_write_to_file(&hash, zManFile);
   157    157       free(zManFile);
   158    158       blob_reset(&hash);
          159  +  }else{
          160  +    if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){
          161  +      zManFile = mprintf("%smanifest", g.zLocalRoot);
          162  +      unlink(zManFile);
          163  +      free(zManFile);
          164  +    }
          165  +    if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){
          166  +      zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
          167  +      unlink(zManFile);
          168  +      free(zManFile);
          169  +    }
   159    170     }
          171  +    
   160    172   }
   161    173   
   162    174   /*
   163    175   ** COMMAND: checkout
   164    176   ** COMMAND: co
   165    177   **
   166    178   ** Usage: %fossil checkout VERSION ?-f|--force? ?--keep?
................................................................................
   226    238     if( !keepFlag ){
   227    239       uncheckout(prior);
   228    240     }
   229    241     db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
   230    242     if( !keepFlag ){
   231    243       vfile_to_disk(vid, 0, 1, promptFlag);
   232    244     }
          245  +  checkout_set_all_exe(vid);
   233    246     manifest_to_disk(vid);
   234    247     db_lset_int("checkout", vid);
   235    248     undo_reset();
   236    249     db_multi_exec("DELETE FROM vmerge");
   237    250     if( !keepFlag ){
   238    251       vfile_aggregate_checksum_manifest(vid, &cksum1, &cksum1b);
   239    252       vfile_aggregate_checksum_disk(vid, &cksum2);

Changes to src/configure.c.

    72     72     { "css",                    CONFIGSET_SKIN },
    73     73     { "header",                 CONFIGSET_SKIN },
    74     74     { "footer",                 CONFIGSET_SKIN },
    75     75     { "logo-mimetype",          CONFIGSET_SKIN },
    76     76     { "logo-image",             CONFIGSET_SKIN },
    77     77     { "project-name",           CONFIGSET_PROJ },
    78     78     { "project-description",    CONFIGSET_PROJ },
           79  +  { "manifest",               CONFIGSET_PROJ },
    79     80     { "index-page",             CONFIGSET_SKIN },
    80     81     { "timeline-block-markup",  CONFIGSET_SKIN },
    81     82     { "timeline-max-comment",   CONFIGSET_SKIN },
    82     83     { "ticket-table",           CONFIGSET_TKT  },
    83     84     { "ticket-common",          CONFIGSET_TKT  },
    84     85     { "ticket-newpage",         CONFIGSET_TKT  },
    85     86     { "ticket-viewpage",        CONFIGSET_TKT  },

Changes to src/content.c.

   279    279     }else{
   280    280       bag_insert(&contentCache.available, rid);
   281    281     }
   282    282     return rc;
   283    283   }
   284    284   
   285    285   /*
   286         -** COMMAND:  artifact
          286  +** COMMAND: artifact
   287    287   **
   288         -** Usage: %fossil artifact ARTIFACT-ID  ?OUTPUT-FILENAME?
          288  +** Usage: %fossil artifact ARTIFACT-ID ?OUTPUT-FILENAME? ?OPTIONS?
   289    289   **
   290    290   ** Extract an artifact by its SHA1 hash and write the results on
   291    291   ** standard output, or if the optional 4th argument is given, in
   292    292   ** the named output file.
          293  +**
          294  +** Options:
          295  +**
          296  +**    -R|--repository FILE       Extract artifacts from repository FILE
   293    297   */
   294    298   void artifact_cmd(void){
   295    299     int rid;
   296    300     Blob content;
   297    301     const char *zFile;
   298         -  if( g.argc!=4 && g.argc!=3 ) usage("RECORDID ?FILENAME?");
          302  +  db_find_and_open_repository(1);
          303  +  if( g.argc!=4 && g.argc!=3 ) usage("ARTIFACT-ID ?FILENAME? ?OPTIONS?");
   299    304     zFile = g.argc==4 ? g.argv[3] : "-";
   300         -  db_must_be_within_tree();
   301    305     rid = name_to_rid(g.argv[2]);
   302    306     content_get(rid, &content);
   303    307     blob_write_to_file(&content, zFile);
   304    308   }
   305    309   
   306    310   /*
   307    311   ** COMMAND:  test-content-rawget
................................................................................
   319    323     rid = name_to_rid(g.argv[2]);
   320    324     blob_zero(&content);
   321    325     db_blob(&content, "SELECT content FROM blob WHERE rid=%d", rid);
   322    326     blob_uncompress(&content, &content);
   323    327     blob_write_to_file(&content, zFile);
   324    328   }
   325    329   
          330  +/*
          331  +** The following flag is set to disable the automatic calls to
          332  +** manifest_crosslink() when a record is dephantomized.  This
          333  +** flag can be set (for example) when doing a clone when we know
          334  +** that rebuild will be run over all records at the conclusion
          335  +** of the operation.
          336  +*/
          337  +static int ignoreDephantomizations = 0;
          338  +
   326    339   /*
   327    340   ** When a record is converted from a phantom to a real record,
   328    341   ** if that record has other records that are derived by delta,
   329    342   ** then call manifest_crosslink() on those other records.
          343  +**
          344  +** If the formerly phantom record or any of the other records
          345  +** derived by delta from the former phantom are a baseline manifest,
          346  +** then also invoke manifest_crosslink() on the delta-manifests
          347  +** associated with that baseline.
   330    348   **
   331    349   ** Tail recursion is used to minimize stack depth.
   332    350   */
   333    351   void after_dephantomize(int rid, int linkFlag){
   334    352     Stmt q;
   335    353     int nChildAlloc = 0;
   336    354     int *aChild = 0;
          355  +  Blob content;
   337    356   
          357  +  if( ignoreDephantomizations ) return;
   338    358     while( rid ){
   339    359       int nChildUsed = 0;
   340    360       int i;
          361  +
          362  +    /* Parse the object rid itself */
   341    363       if( linkFlag ){
   342         -      Blob content;
   343    364         content_get(rid, &content);
   344    365         manifest_crosslink(rid, &content);
   345    366         blob_reset(&content);
   346    367       }
          368  +
          369  +    /* Parse all delta-manifests that depend on baseline-manifest rid */
          370  +    db_prepare(&q, "SELECT rid FROM orphan WHERE baseline=%d", rid);
          371  +    while( db_step(&q)==SQLITE_ROW ){
          372  +      int child = db_column_int(&q, 0);
          373  +      if( nChildUsed>=nChildAlloc ){
          374  +        nChildAlloc = nChildAlloc*2 + 10;
          375  +        aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild));
          376  +      }
          377  +      aChild[nChildUsed++] = child;
          378  +    }
          379  +    db_finalize(&q);
          380  +    for(i=0; i<nChildUsed; i++){
          381  +      content_get(aChild[i], &content);
          382  +      manifest_crosslink(aChild[i], &content);
          383  +      blob_reset(&content);
          384  +    }
          385  +    if( nChildUsed ){
          386  +      db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid);
          387  +    }
          388  +
          389  +    /* Recursively dephantomize all artifacts that are derived by
          390  +    ** delta from artifact rid */
          391  +    nChildUsed = 0;
   347    392       db_prepare(&q, "SELECT rid FROM delta WHERE srcid=%d", rid);
   348    393       while( db_step(&q)==SQLITE_ROW ){
   349    394         int child = db_column_int(&q, 0);
   350    395         if( nChildUsed>=nChildAlloc ){
   351    396           nChildAlloc = nChildAlloc*2 + 10;
   352    397           aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild));
   353    398         }
   354    399         aChild[nChildUsed++] = child;
   355    400       }
   356    401       db_finalize(&q);
   357    402       for(i=1; i<nChildUsed; i++){
   358    403         after_dephantomize(aChild[i], 1);
   359    404       }
          405  +
          406  +    /* Tail recursion for the common case where only a single artifact
          407  +    ** is derived by delta from rid... */
   360    408       rid = nChildUsed>0 ? aChild[0] : 0;
   361    409       linkFlag = 1;
   362    410     }
   363    411     free(aChild);
   364    412   }
          413  +
          414  +/*
          415  +** Turn dephantomization processing on or off.
          416  +*/
          417  +void content_enable_dephantomize(int onoff){
          418  +  ignoreDephantomizations = !onoff;
          419  +}
   365    420   
   366    421   /*
   367    422   ** Write content into the database.  Return the record ID.  If the
   368    423   ** content is already in the database, just return the record ID.
   369    424   **
   370    425   ** If srcId is specified, then pBlob is delta content from
   371    426   ** the srcId record.  srcId might be a phantom.

Changes to src/db.c.

  1508   1508     char const *name;     /* Name of the setting */
  1509   1509     char const *var;      /* Internal variable name used by db_set() */
  1510   1510     int width;            /* Width of display.  0 for boolean values */
  1511   1511     char const *def;      /* Default value */
  1512   1512   };
  1513   1513   #endif /* INTERFACE */
  1514   1514   struct stControlSettings const ctrlSettings[] = {
  1515         -  { "auto-captcha",  "autocaptcha",    0, "0"                   },
  1516         -  { "auto-shun",     0,                0, "1"                   },
  1517         -  { "autosync",      0,                0, "0"                   },
  1518         -  { "binary-glob",   0,                0, "1"                   },
  1519         -  { "clearsign",     0,                0, "0"                   },
  1520         -  { "diff-command",  0,               16, "diff"                },
  1521         -  { "dont-push",     0,                0, "0"                   },
         1515  +  { "auto-captcha",  "autocaptcha",    0, "on"                  },
         1516  +  { "auto-shun",     0,                0, "on"                  },
         1517  +  { "autosync",      0,                0, "on"                  },
         1518  +  { "binary-glob",   0,               32, ""                    },
         1519  +  { "clearsign",     0,                0, "off"                 },
         1520  +  { "diff-command",  0,               16, ""                    },
         1521  +  { "dont-push",     0,                0, "off"                 },
  1522   1522     { "editor",        0,               16, ""                    },
  1523   1523     { "gdiff-command", 0,               16, "gdiff"               },
  1524   1524     { "ignore-glob",   0,               40, ""                    },
  1525   1525     { "http-port",     0,               16, "8080"                },
  1526         -  { "localauth",     0,                0, "0"                   },
  1527         -  { "mtime-changes", 0,                0, "0"                   },
         1526  +  { "localauth",     0,                0, "off"                 },
         1527  +  { "manifest",      0,                0, "off"                 },
         1528  +  { "mtime-changes", 0,                0, "off"                 },
  1528   1529     { "pgp-command",   0,               32, "gpg --clearsign -o " },
  1529   1530     { "proxy",         0,               32, "off"                 },
         1531  +  { "push-hook-cmd", 0,               32, ""                    },
         1532  +  { "push-hook-force",
         1533  +                     0,                0, ""                    },
         1534  +  { "push-hook-pattern-client",
         1535  +                     0,               32, ""                    },
         1536  +  { "push-hook-pattern-server",
         1537  +                     0,               32, ""                    },
         1538  +  { "push-hook-privilege",
         1539  +                     0,               1,  ""                    },
         1540  +  { "repo-cksum",    0,                0, "on"                  },
  1530   1541     { "ssh-command",   0,               32, ""                    },
  1531   1542     { "web-browser",   0,               32, ""                    },
  1532   1543     { 0,0,0,0 }
  1533   1544   };
  1534   1545   
  1535   1546   /*
  1536   1547   ** COMMAND: settings
................................................................................
  1584   1595   **                  Example:  *.o,*.obj,*.exe
  1585   1596   **
  1586   1597   **    localauth     If enabled, require that HTTP connections from
  1587   1598   **                  127.0.0.1 be authenticated by password.  If
  1588   1599   **                  false, all HTTP requests from localhost have
  1589   1600   **                  unrestricted access to the repository.
  1590   1601   **
         1602  +**    manifest      If enabled, automatically create files "manifest" and
         1603  +**                  "manifest.uuid" in every checkout.  The SQLite and
         1604  +**                  Fossil repositories both require this.  Default: off.
         1605  +**
  1591   1606   **    mtime-changes Use file modification times (mtimes) to detect when
  1592   1607   **                  files have been modified.  (Default "on".)
  1593   1608   **
  1594   1609   **    pgp-command   Command used to clear-sign manifests at check-in.
  1595   1610   **                  The default is "gpg --clearsign -o ".
  1596   1611   **
  1597   1612   **    proxy         URL of the HTTP proxy.  If undefined or "off" then
  1598   1613   **                  the "http_proxy" environment variable is consulted.
  1599   1614   **                  If the http_proxy environment variable is undefined
  1600   1615   **                  then a direct HTTP connection is used.
         1616  +**
         1617  +**    push-hook-cmd this is the command line, that will be activated
         1618  +**                  as push hook. Output redirects should be added to
         1619  +**                  this command line.
         1620  +**                  The complete command line looks like:
         1621  +**                    command name: the configured value for push-hook-cmd
         1622  +**                    argument 1:   timestamp followed by random-number
         1623  +**                    argument 2:   pattern sent by client
         1624  +**                  As fallback, stdin/stderr are redirected to files
         1625  +**                    hook-log-<timestamp followed by random-number>
         1626  +**
         1627  +**    push-hook-force
         1628  +**                  if this is set on the client, it will request always
         1629  +**                  the hook activation, even if no files where pushed on
         1630  +**                  the sync.
         1631  +**                  if this is set on the server, it will accept hook
         1632  +**                  activiation, even if no files where pushed.
         1633  +**                     Default: on
         1634  +**
         1635  +**    push-hook-pattern-client
         1636  +**                  if set, a client push will sent this message to the
         1637  +**                  server, to activate the push hook command.
         1638  +**                     Default: on
         1639  +**
         1640  +**    push-hook-pattern-server
         1641  +**                  if set, and a client send this pattern at the end of
         1642  +**                  a push, the push hook command will be executed. This
         1643  +**                  might be a prefix of the pattern, sent by the client.
         1644  +**
         1645  +**    push-hook-privilege
         1646  +**                  if set, the user doing the push needs this privilege
         1647  +**                  to trigger the hook. Valid privileges are:
         1648  +**                    s (setup), a (admin), i (checkin) or o (checkout)
         1649  +**
         1650  +**    repo-cksum    Compute checksums over all files in each checkout
         1651  +**                  as a double-check of correctness.  Defaults to "on".
         1652  +**                  Disable on large repositories for a performance
         1653  +**                  improvement.
  1601   1654   **
  1602   1655   **    ssh-command   Command used to talk to a remote machine with
  1603   1656   **                  the "ssh://" protocol.
  1604   1657   **
  1605   1658   **    web-browser   A shell command used to launch your preferred
  1606   1659   **                  web browser when given a URL as an argument.
  1607   1660   **                  Defaults to "start" on windows, "open" on Mac,
................................................................................
  1625   1678     }
  1626   1679     if( g.argc==2 ){
  1627   1680       for(i=0; ctrlSettings[i].name; i++){
  1628   1681         print_setting(ctrlSettings[i].name);
  1629   1682       }
  1630   1683     }else if( g.argc==3 || g.argc==4 ){
  1631   1684       const char *zName = g.argv[2];
         1685  +    int isManifest;
  1632   1686       int n = strlen(zName);
  1633   1687       for(i=0; ctrlSettings[i].name; i++){
  1634   1688         if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break;
  1635   1689       }
  1636   1690       if( !ctrlSettings[i].name ){
  1637   1691         fossil_fatal("no such setting: %s", zName);
  1638   1692       }
         1693  +    isManifest = strcmp(ctrlSettings[i].name, "manifest")==0;
         1694  +    if( isManifest && globalFlag ){
         1695  +      fossil_fatal("cannot set 'manifest' globally");
         1696  +    }
  1639   1697       if( unsetFlag ){
  1640   1698         db_unset(ctrlSettings[i].name, globalFlag);
  1641   1699       }else if( g.argc==4 ){
  1642   1700         db_set(ctrlSettings[i].name, g.argv[3], globalFlag);
  1643   1701       }else{
         1702  +      isManifest = 0;
  1644   1703         print_setting(ctrlSettings[i].name);
  1645   1704       }
         1705  +    if( isManifest ){
         1706  +      manifest_to_disk(db_lget_int("checkout", 0));
         1707  +    }
  1646   1708     }else{
  1647   1709       usage("?PROPERTY? ?VALUE?");
  1648   1710     }
  1649   1711   }
  1650   1712   
  1651   1713   /*
  1652   1714   ** The input in a a timespan measured in days.  Return a string which

Changes to src/delta.c.

   193    193   }
   194    194   
   195    195   /*
   196    196   ** Compute a 32-bit checksum on the N-byte buffer.  Return the result.
   197    197   */
   198    198   static unsigned int checksum(const char *zIn, size_t N){
   199    199     const unsigned char *z = (const unsigned char *)zIn;
   200         -  unsigned sum = 0;
          200  +  unsigned sum0 = 0;
          201  +  unsigned sum1 = 0;
          202  +  unsigned sum2 = 0;
          203  +  unsigned sum3 = 0;
   201    204     while(N >= 16){
   202         -    sum += ((unsigned)z[0] + z[4] + z[8] + z[12]) << 24;
   203         -    sum += ((unsigned)z[1] + z[5] + z[9] + z[13]) << 16;
   204         -    sum += ((unsigned)z[2] + z[6] + z[10]+ z[14]) << 8;
   205         -    sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
          205  +    sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
          206  +    sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
          207  +    sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
          208  +    sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
   206    209       z += 16;
   207    210       N -= 16;
   208    211     }
   209    212     while(N >= 4){
   210         -    sum += (z[0]<<24) | (z[1]<<16) | (z[2]<<8) | z[3];
          213  +    sum0 += z[0];
          214  +    sum1 += z[1];
          215  +    sum2 += z[2];
          216  +    sum3 += z[3];
   211    217       z += 4;
   212    218       N -= 4;
   213    219     }
          220  +  sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
   214    221     switch(N){
   215         -    case 3:   sum += (z[2] << 8);
   216         -    case 2:   sum += (z[1] << 16);
   217         -    case 1:   sum += (z[0] << 24);
          222  +    case 3:   sum3 += (z[2] << 8);
          223  +    case 2:   sum3 += (z[1] << 16);
          224  +    case 1:   sum3 += (z[0] << 24);
   218    225       default:  ;
   219    226     }
   220         -  return sum;
          227  +  return sum3;
   221    228   }
   222    229   
   223    230   /*
   224    231   ** Create a new delta.
   225    232   **
   226    233   ** The delta is written into a preallocated buffer, zDelta, which 
   227    234   ** should be at least 60 bytes longer than the target file, zOut.
................................................................................
   510    517     int lenSrc,            /* Length of the source file */
   511    518     const char *zDelta,    /* Delta to apply to the pattern */
   512    519     int lenDelta,          /* Length of the delta */
   513    520     char *zOut             /* Write the output into this preallocated buffer */
   514    521   ){
   515    522     unsigned int limit;
   516    523     unsigned int total = 0;
          524  +#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST
   517    525     char *zOrigOut = zOut;
          526  +#endif
   518    527   
   519    528     limit = getInt(&zDelta, &lenDelta);
   520    529     if( *zDelta!='\n' ){
   521    530       /* ERROR: size integer not terminated by "\n" */
   522    531       return -1;
   523    532     }
   524    533     zDelta++; lenDelta--;
................................................................................
   565    574           zDelta += cnt;
   566    575           lenDelta -= cnt;
   567    576           break;
   568    577         }
   569    578         case ';': {
   570    579           zDelta++; lenDelta--;
   571    580           zOut[0] = 0;
          581  +#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST
   572    582           if( cnt!=checksum(zOrigOut, total) ){
   573    583             /* ERROR:  bad checksum */
   574    584             return -1;
   575    585           }
          586  +#endif
   576    587           if( total!=limit ){
   577    588             /* ERROR: generated size does not match predicted size */
   578    589             return -1;
   579    590           }
   580    591           return total;
   581    592         }
   582    593         default: {

Changes to src/diffcmd.c.

    33     33   int portable_system(const char *zOrigCmd){
    34     34     int rc;
    35     35   #if defined(_WIN32)
    36     36     /* On windows, we have to put double-quotes around the entire command.
    37     37     ** Who knows why - this is just the way windows works.
    38     38     */
    39     39     char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
           40  +  fflush(0);
    40     41     rc = system(zNewCmd);
    41     42     free(zNewCmd);
    42     43   #else
    43     44     /* On unix, evaluate the command directly.
    44     45     */
    45     46     rc = system(zOrigCmd);
    46     47   #endif 
................................................................................
   353    354   */
   354    355   static void diff_all_two_versions(
   355    356     const char *zFrom,
   356    357     const char *zTo,
   357    358     const char *zDiffCmd,
   358    359     int diffFlags
   359    360   ){
   360         -  Manifest mFrom, mTo;
   361         -  int iFrom, iTo;
          361  +  Manifest *pFrom, *pTo;
          362  +  ManifestFile *pFromFile, *pToFile;
   362    363     int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
   363    364     int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
   364    365   
   365         -  manifest_from_name(zFrom, &mFrom);
   366         -  manifest_from_name(zTo, &mTo);
   367         -  iFrom = iTo = 0;
   368         -  while( iFrom<mFrom.nFile || iTo<mTo.nFile ){
          366  +  pFrom = manifest_get_by_name(zFrom, 0);
          367  +  manifest_file_rewind(pFrom);
          368  +  pFromFile = manifest_file_next(pFrom,0);
          369  +  pTo = manifest_get_by_name(zTo, 0);
          370  +  manifest_file_rewind(pTo);
          371  +  pToFile = manifest_file_next(pTo,0);
          372  +
          373  +  while( pFromFile || pToFile ){
   369    374       int cmp;
   370         -    if( iFrom>=mFrom.nFile ){
          375  +    if( pFromFile==0 ){
   371    376         cmp = +1;
   372         -    }else if( iTo>=mTo.nFile ){
          377  +    }else if( pToFile==0 ){
   373    378         cmp = -1;
   374    379       }else{
   375         -      cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName);
          380  +      cmp = strcmp(pFromFile->zName, pToFile->zName);
   376    381       }
   377    382       if( cmp<0 ){
   378         -      printf("DELETED %s\n", mFrom.aFile[iFrom].zName);
          383  +      printf("DELETED %s\n", pFromFile->zName);
   379    384         if( asNewFlag ){
   380         -        diff_manifest_entry(&mFrom.aFile[iFrom], 0, zDiffCmd, ignoreEolWs);
          385  +        diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs);
   381    386         }
   382         -      iFrom++;
          387  +      pFromFile = manifest_file_next(pFrom,0);
   383    388       }else if( cmp>0 ){
   384         -      printf("ADDED   %s\n", mTo.aFile[iTo].zName);
          389  +      printf("ADDED   %s\n", pToFile->zName);
   385    390         if( asNewFlag ){
   386         -        diff_manifest_entry(0, &mTo.aFile[iTo], zDiffCmd, ignoreEolWs);
          391  +        diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs);
   387    392         }
   388         -      iTo++;
   389         -    }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){
          393  +      pToFile = manifest_file_next(pTo,0);
          394  +    }else if( strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
   390    395         /* No changes */
   391         -      iFrom++;
   392         -      iTo++;
          396  +      pFromFile = manifest_file_next(pFrom,0);
          397  +      pToFile = manifest_file_next(pTo,0);
   393    398       }else{
   394         -      printf("CHANGED %s\n", mFrom.aFile[iFrom].zName);
   395         -      diff_manifest_entry(&mFrom.aFile[iFrom], &mTo.aFile[iTo],
   396         -                          zDiffCmd, ignoreEolWs);
   397         -      iFrom++;
   398         -      iTo++;
          399  +      printf("CHANGED %s\n", pFromFile->zName);
          400  +      diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs);
          401  +      pFromFile = manifest_file_next(pFrom,0);
          402  +      pToFile = manifest_file_next(pTo,0);
   399    403       }
   400    404     }
   401         -  manifest_clear(&mFrom);
   402         -  manifest_clear(&mTo);
          405  +  manifest_destroy(pFrom);
          406  +  manifest_destroy(pTo);
   403    407   }
   404    408   
   405    409   /*
   406    410   ** COMMAND: diff
   407    411   ** COMMAND: gdiff
   408    412   **
   409    413   ** Usage: %fossil diff|gdiff ?options? ?FILE?

Changes to src/doc.c.

   386    386                       " WHERE vid=%d AND fname=%Q", vid, zName);
   387    387       if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
   388    388         goto doc_not_found;
   389    389       }
   390    390   
   391    391       if( rid==0 ){
   392    392         Stmt s;
   393         -      Blob baseline;
   394         -      Manifest m;
          393  +      Manifest *pM;
          394  +      ManifestFile *pFile;
   395    395   
   396    396         /* Add the vid baseline to the cache */
   397    397         if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
   398    398           db_multi_exec("DELETE FROM vcache");
   399    399         }
   400         -      if( content_get(vid, &baseline)==0 ){
   401         -        goto doc_not_found;
   402         -      }
   403         -      if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){
          400  +      pM = manifest_get(vid, CFTYPE_MANIFEST);
          401  +      if( pM==0 ){
   404    402           goto doc_not_found;
   405    403         }
   406    404         db_prepare(&s,
   407    405           "INSERT INTO vcache(vid,fname,rid)"
   408    406           " SELECT %d, :fname, rid FROM blob"
   409    407           "  WHERE uuid=:uuid",
   410    408           vid
   411    409         );
   412         -      for(i=0; i<m.nFile; i++){
   413         -        db_bind_text(&s, ":fname", m.aFile[i].zName);
   414         -        db_bind_text(&s, ":uuid", m.aFile[i].zUuid);
          410  +      manifest_file_rewind(pM);
          411  +      while( (pFile = manifest_file_next(pM,0))!=0 ){
          412  +        db_bind_text(&s, ":fname", pFile->zName);
          413  +        db_bind_text(&s, ":uuid", pFile->zUuid);
   415    414           db_step(&s);
   416    415           db_reset(&s);
   417    416         }
   418    417         db_finalize(&s);
   419         -      manifest_clear(&m);
          418  +      manifest_destroy(pM);
   420    419   
   421    420         /* Try again to find the file */
   422    421         rid = db_int(0, "SELECT rid FROM vcache"
   423    422                         " WHERE vid=%d AND fname=%Q", vid, zName);
   424    423       }
   425    424       if( rid==0 ){
   426    425         goto doc_not_found;

Changes to src/encode.c.

   265    265   }
   266    266   
   267    267   /*
   268    268   ** Decode a fossilized string in-place.
   269    269   */
   270    270   void defossilize(char *z){
   271    271     int i, j, c;
   272         -  for(i=j=0; z[i]; i++){
   273         -    c = z[i];
          272  +  for(i=0; (c=z[i])!=0 && c!='\\'; i++){}
          273  +  if( c==0 ) return;
          274  +  for(j=i; (c=z[i])!=0; i++){
   274    275       if( c=='\\' && z[i+1] ){
   275    276         i++;
   276    277         switch( z[i] ){
   277    278           case 'n':  c = '\n';  break;
   278    279           case 's':  c = ' ';   break;
   279    280           case 't':  c = '\t';  break;
   280    281           case 'r':  c = '\r';  break;

Changes to src/event.c.

    61     61     int rid = 0;             /* rid of the event artifact */
    62     62     char *zUuid;             /* UUID corresponding to rid */
    63     63     const char *zEventId;    /* Event identifier */
    64     64     char *zETime;            /* Time of the event */
    65     65     char *zATime;            /* Time the artifact was created */
    66     66     int specRid;             /* rid specified by aid= parameter */
    67     67     int prevRid, nextRid;    /* Previous or next edits of this event */
    68         -  Manifest m;              /* Parsed event artifact */
    69         -  Blob content;            /* Original event artifact content */
           68  +  Manifest *pEvent;        /* Parsed event artifact */
    70     69     Blob fullbody;           /* Complete content of the event body */
    71     70     Blob title;              /* Title extracted from the event body */
    72     71     Blob tail;               /* Event body that comes after the title */
    73     72     Stmt q1;                 /* Query to search for the event */
    74     73     int showDetail;          /* True to show details */
    75     74   
    76     75   
................................................................................
   111    110       return;
   112    111     }
   113    112     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   114    113     showDetail = atoi(PD("detail","0"));
   115    114   
   116    115     /* Extract the event content.
   117    116     */
   118         -  memset(&m, 0, sizeof(m));
   119         -  blob_zero(&m.content);
   120         -  content_get(rid, &content);
   121         -  manifest_parse(&m, &content);
   122         -  if( m.type!=CFTYPE_EVENT ){
          117  +  pEvent = manifest_get(rid, CFTYPE_EVENT);
          118  +  if( pEvent==0 ){
   123    119       fossil_panic("Object #%d is not an event", rid);
   124    120     }
   125         -  blob_init(&fullbody, m.zWiki, -1);
          121  +  blob_init(&fullbody, pEvent->zWiki, -1);
   126    122     if( wiki_find_title(&fullbody, &title, &tail) ){
   127    123       style_header(blob_str(&title));
   128    124     }else{
   129    125       style_header("Event %S", zEventId);
   130    126       tail = fullbody;
   131    127     }
   132    128     if( g.okWrWiki && g.okWrite && nextRid==0 ){
   133    129       style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
   134    130                             g.zTop, zEventId);
   135    131     }
   136         -  zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate);
          132  +  zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
   137    133     style_submenu_element("Context", "Context", "%s/timeline?c=%T",
   138    134                           g.zTop, zETime);
   139    135     if( g.okHistory ){
   140    136       if( showDetail ){
   141    137         style_submenu_element("Plain", "Plain", "%s/event?name=%s&amp;aid=%s",
   142    138                               g.zTop, zEventId, zUuid);
   143    139         if( nextRid ){
................................................................................
   164    160     }
   165    161   
   166    162     if( showDetail && g.okHistory ){
   167    163       int i;
   168    164       const char *zClr = 0;
   169    165       Blob comment;
   170    166   
   171         -    zATime = db_text(0, "SELECT datetime(%.17g)", m.rDate);
          167  +    zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
   172    168       @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at
   173    169       @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>]
   174         -    @ entered by user <b>%h(m.zUser)</b> on
          170  +    @ entered by user <b>%h(pEvent->zUser)</b> on
   175    171       @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p>
   176    172       @ <blockquote>
   177         -    for(i=0; i<m.nTag; i++){
   178         -      if( strcmp(m.aTag[i].zName,"+bgcolor")==0 ){
   179         -        zClr = m.aTag[i].zValue;
          173  +    for(i=0; i<pEvent->nTag; i++){
          174  +      if( strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
          175  +        zClr = pEvent->aTag[i].zValue;
   180    176         }
   181    177       }
   182    178       if( zClr && zClr[0]==0 ) zClr = 0;
   183    179       if( zClr ){
   184    180         @ <div style="background-color: %h(zClr);">
   185    181       }else{
   186    182         @ <div>
   187    183       }
   188         -    blob_init(&comment, m.zComment, -1);
          184  +    blob_init(&comment, pEvent->zComment, -1);
   189    185       wiki_convert(&comment, 0, WIKI_INLINE);
   190    186       blob_reset(&comment);
   191    187       @ </div>
   192    188       @ </blockquote><hr />
   193    189     }  
   194    190   
   195    191     wiki_convert(&tail, 0, 0);
   196    192     style_footer();
   197         -  manifest_clear(&m);
          193  +  manifest_destroy(pEvent);
   198    194   }
   199    195   
   200    196   /*
   201    197   ** WEBPAGE: eventedit
   202    198   ** URL: /eventedit?name=EVENTID
   203    199   **
   204    200   ** Edit an event.  If name is omitted, create a new event.
................................................................................
   257    253     if( strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
   258    254   
   259    255   
   260    256     /* If editing an existing event, extract the key fields to use as
   261    257     ** a starting point for the edit.
   262    258     */
   263    259     if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
   264         -    Manifest m;
   265         -    Blob content;
   266         -    memset(&m, 0, sizeof(m));
   267         -    blob_zero(&m.content);
   268         -    content_get(rid, &content);
   269         -    manifest_parse(&m, &content);
   270         -    if( m.type==CFTYPE_EVENT ){
   271         -      if( zBody==0 ) zBody = m.zWiki;
          260  +    Manifest *pEvent;
          261  +    pEvent = manifest_get(rid, CFTYPE_EVENT);
          262  +    if( pEvent && pEvent->type==CFTYPE_EVENT ){
          263  +      if( zBody==0 ) zBody = pEvent->zWiki;
   272    264         if( zETime==0 ){
   273         -        zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate);
          265  +        zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
   274    266         }
   275         -      if( zComment==0 ) zComment = m.zComment;
          267  +      if( zComment==0 ) zComment = pEvent->zComment;
   276    268       }
   277    269       if( zTags==0 ){
   278    270         zTags = db_text(0,
   279    271           "SELECT group_concat(substr(tagname,5),', ')"
   280    272           "  FROM tagxref, tag"
   281    273           " WHERE tagxref.rid=%d"
   282    274           "   AND tagxref.tagid=tag.tagid"

Changes to src/finfo.c.

   132    132       "   AND event.objid=mlink.mid"
   133    133       " ORDER BY event.mtime DESC /*sort*/",
   134    134       TAG_BRANCH,
   135    135       zFilename
   136    136     );
   137    137     blob_zero(&title);
   138    138     blob_appendf(&title, "History of ");
   139         -  hyperlinked_path(zFilename, &title);
          139  +  hyperlinked_path(zFilename, &title, 0);
   140    140     @ <h2>%b(&title)</h2>
   141    141     blob_reset(&title);
   142    142     pGraph = graph_init();
   143    143     @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
   144    144     @ <table class="timelineTable">
   145    145     while( db_step(&q)==SQLITE_ROW ){
   146    146       const char *zDate = db_column_text(&q, 0);

Changes to src/http.c.

   265    265         }
   266    266         z[j] = z[i];
   267    267       }
   268    268       z[j] = 0;
   269    269       fossil_fatal("server sends error: %s", z);
   270    270     }
   271    271     if( g.fHttpTrace ){
   272         -    printf("HTTP RECEIVE:\n%s\n=======================\n", blob_str(pReply));
          272  +    /*printf("HTTP RECEIVE:\n%s\n=======================\n",blob_str(pReply));*/
   273    273     }else{
   274    274       blob_uncompress(pReply, pReply);
   275    275     }
   276    276   
   277    277     /*
   278    278     ** Close the connection to the server if appropriate.
   279    279     **

Changes to src/info.c.

   543    543     }else{
   544    544       style_header("Wiki Information");
   545    545       rid = 0;
   546    546     }
   547    547     db_finalize(&q);
   548    548     showTags(rid, "wiki-*");
   549    549     if( rid ){
   550         -    Blob content;
   551         -    Manifest m;
   552         -    memset(&m, 0, sizeof(m));
   553         -    blob_zero(&m.content);
   554         -    content_get(rid, &content);
   555         -    manifest_parse(&m, &content);
   556         -    if( m.type==CFTYPE_WIKI ){
          550  +    Manifest *pWiki;
          551  +    pWiki = manifest_get(rid, CFTYPE_WIKI);
          552  +    if( pWiki ){
   557    553         Blob wiki;
   558         -      blob_init(&wiki, m.zWiki, -1);
          554  +      blob_init(&wiki, pWiki->zWiki, -1);
   559    555         @ <div class="section">Content</div>
   560    556         wiki_convert(&wiki, 0, 0);
   561    557         blob_reset(&wiki);
   562    558       }
   563         -    manifest_clear(&m);
          559  +    manifest_destroy(pWiki);
   564    560     }
   565    561     style_footer_cmdref("info",0);
   566    562   }
   567    563   
   568    564   /*
   569    565   ** Show a webpage error message
   570    566   */
................................................................................
   580    576     style_footer();
   581    577   }
   582    578   
   583    579   /*
   584    580   ** Find an checkin based on query parameter zParam and parse its
   585    581   ** manifest.  Return the number of errors.
   586    582   */
   587         -static int vdiff_parse_manifest(const char *zParam, int *pRid, Manifest *pM){
          583  +static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){
   588    584     int rid;
   589         -  Blob content;
   590    585   
   591    586     *pRid = rid = name_to_rid_www(zParam);
   592    587     if( rid==0 ){
   593    588       webpage_error("Missing \"%s\" query parameter.", zParam);
   594         -    return 1;
          589  +    return 0;
   595    590     }
   596    591     if( !is_a_version(rid) ){
   597    592       webpage_error("Artifact %s is not a checkin.", P(zParam));
   598         -    return 1;
          593  +    return 0;
   599    594     }
   600         -  content_get(rid, &content);
   601         -  manifest_parse(pM, &content);
   602         -  return 0;
          595  +  return manifest_get(rid, CFTYPE_MANIFEST);
   603    596   }
   604    597   
   605    598   /*
   606    599   ** Output a description of a check-in
   607    600   */
   608    601   void checkin_description(int rid){
   609    602     Stmt q;
................................................................................
   637    630   ** Show all differences between two checkins.  
   638    631   */
   639    632   void vdiff_page(void){
   640    633     int ridFrom, ridTo;
   641    634     int showDetail = atoi(PD("detail","0"));
   642    635     const char *zFrom = P("from");
   643    636     const char *zTo = P("to");
   644         -  int iFrom, iTo;
   645         -  Manifest mFrom, mTo;
          637  +  Manifest *pFrom, *pTo;
          638  +  ManifestFile *pFileFrom, *pFileTo;
   646    639   
   647    640     login_check_credentials();
   648    641     if( !g.okRead ){ login_needed(); return; }
   649    642     login_anonymous_available();
   650    643   
   651    644     if( !zFrom || !zFrom[0] || !zTo || !zTo[0] ){
   652    645       /* if from or to or both are bissing, show a form to enter
................................................................................
   663    656       @  name="to" value="%s(zTo?zTo:"")" /></td><td></td></tr>
   664    657       @ <tr><td>details:</td><td><input type="checkbox" name="detail"
   665    658       @  checked="checked" value="1" /></td></tr>
   666    659       @ <tr><td></td><td></td><td>
   667    660       @  <input type="submit" name="diff" value="diff" /></td></tr></table>
   668    661       @ </div></form>
   669    662       style_footer_cmdref("diff",0);
   670         -    return;
   671         -  }else if(    vdiff_parse_manifest("from", &ridFrom, &mFrom) 
   672         -            || vdiff_parse_manifest("to", &ridTo, &mTo)
   673         -  ){
   674    663       return;
   675    664     }
          665  +  pFrom = vdiff_parse_manifest("from", &ridFrom);
          666  +  if( pFrom==0 ) return;
          667  +  pTo = vdiff_parse_manifest("to", &ridTo);
          668  +  if( pTo==0 ) return;
          669  +  showDetail = atoi(PD("detail","0"));
   676    670     style_header("Check-in Differences");
   677    671     @ <h2>Difference From:</h2><blockquote>
   678    672     checkin_description(ridFrom);
   679    673     @ </blockquote><h2>To:</h2><blockquote>
   680    674     checkin_description(ridTo);
   681    675     @ </blockquote><hr /><p>
   682    676   
   683         -  iFrom = iTo = 0;
   684         -  while( iFrom<mFrom.nFile && iTo<mTo.nFile ){
          677  +  manifest_file_rewind(pFrom);
          678  +  pFileFrom = manifest_file_next(pFrom, 0);
          679  +  manifest_file_rewind(pTo);
          680  +  pFileTo = manifest_file_next(pTo, 0);
          681  +  while( pFileFrom || pFileTo ){
   685    682       int cmp;
   686         -    if( iFrom>=mFrom.nFile ){
          683  +    if( pFileFrom==0 ){
   687    684         cmp = +1;
   688         -    }else if( iTo>=mTo.nFile ){
          685  +    }else if( pFileTo==0 ){
   689    686         cmp = -1;
   690    687       }else{
   691         -      cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName);
          688  +      cmp = strcmp(pFileFrom->zName, pFileTo->zName);
   692    689       }
   693    690       if( cmp<0 ){
   694         -      append_file_change_line(mFrom.aFile[iFrom].zName, 
   695         -                              mFrom.aFile[iFrom].zUuid, 0, 0);
   696         -      iFrom++;
          691  +      append_file_change_line(pFileFrom->zName, 
          692  +                              pFileFrom->zUuid, 0, 0);
          693  +      pFileFrom = manifest_file_next(pFrom, 0);
   697    694       }else if( cmp>0 ){
   698         -      append_file_change_line(mTo.aFile[iTo].zName, 
   699         -                              0, mTo.aFile[iTo].zUuid, 0);
   700         -      iTo++;
   701         -    }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){
          695  +      append_file_change_line(pFileTo->zName, 
          696  +                              0, pFileTo->zUuid, 0);
          697  +      pFileTo = manifest_file_next(pTo, 0);
          698  +    }else if( strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){
   702    699         /* No changes */
   703         -      iFrom++;
   704         -      iTo++;
          700  +      pFileFrom = manifest_file_next(pFrom, 0);
          701  +      pFileTo = manifest_file_next(pTo, 0);
   705    702       }else{
   706         -      append_file_change_line(mFrom.aFile[iFrom].zName, 
   707         -                              mFrom.aFile[iFrom].zUuid,
   708         -                              mTo.aFile[iTo].zUuid, showDetail);
   709         -      iFrom++;
   710         -      iTo++;
          703  +      append_file_change_line(pFileFrom->zName, 
          704  +                              pFileFrom->zUuid,
          705  +                              pFileTo->zUuid, showDetail);
          706  +      pFileFrom = manifest_file_next(pFrom, 0);
          707  +      pFileTo = manifest_file_next(pTo, 0);
   711    708       }
   712    709     }
   713         -  manifest_clear(&mFrom);
   714         -  manifest_clear(&mTo);
          710  +  manifest_destroy(pFrom);
          711  +  manifest_destroy(pTo);
   715    712   
   716    713     style_footer_cmdref("diff",0);
   717    714   }
   718    715   
   719    716   /*
   720    717   ** Write a description of an object to the www reply.
   721    718   **
................................................................................
  1067   1064   ** Look for "ci" and "filename" query parameters.  If found, try to
  1068   1065   ** use them to extract the record ID of an artifact for the file.
  1069   1066   */
  1070   1067   int artifact_from_ci_and_filename(void){
  1071   1068     const char *zFilename;
  1072   1069     const char *zCI;
  1073   1070     int cirid;
  1074         -  Blob content;
  1075         -  Manifest m;
  1076         -  int i;
         1071  +  Manifest *pManifest;
         1072  +  ManifestFile *pFile;
  1077   1073   
  1078   1074     zCI = P("ci");
  1079   1075     if( zCI==0 ) return 0;
  1080   1076     zFilename = P("filename");
  1081   1077     if( zFilename==0 ) return 0;
  1082   1078     cirid = name_to_rid_www("ci");
  1083         -  if( !content_get(cirid, &content) ) return 0;
  1084         -  if( !manifest_parse(&m, &content) ) return 0;
  1085         -  if( m.type!=CFTYPE_MANIFEST ) return 0;
  1086         -  for(i=0; i<m.nFile; i++){
  1087         -    if( strcmp(zFilename, m.aFile[i].zName)==0 ){
  1088         -      return db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", m.aFile[i].zUuid);
         1079  +  pManifest = manifest_get(cirid, CFTYPE_MANIFEST);
         1080  +  if( pManifest==0 ) return 0;
         1081  +  manifest_file_rewind(pManifest);
         1082  +  while( (pFile = manifest_file_next(pManifest,0))!=0 ){
         1083  +    if( strcmp(zFilename, pFile->zName)==0 ){
         1084  +      int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid);
         1085  +      manifest_destroy(pManifest);
         1086  +      return rid;
  1089   1087       }
  1090   1088     }
  1091   1089     return 0;
  1092   1090   }
  1093   1091   
  1094   1092   
  1095   1093   /*
................................................................................
  1192   1190   ** WEBPAGE: tinfo
  1193   1191   ** URL: /tinfo?name=ARTIFACTID
  1194   1192   **
  1195   1193   ** Show the details of a ticket change control artifact.
  1196   1194   */
  1197   1195   void tinfo_page(void){
  1198   1196     int rid;
  1199         -  Blob content;
  1200   1197     char *zDate;
  1201   1198     const char *zUuid;
  1202   1199     char zTktName[20];
  1203         -  Manifest m;
         1200  +  Manifest *pTktChng;
  1204   1201   
  1205   1202     login_check_credentials();
  1206   1203     if( !g.okRdTkt ){ login_needed(); return; }
  1207   1204     rid = name_to_rid_www("name");
  1208   1205     if( rid==0 ){ fossil_redirect_home(); }
  1209   1206     zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1210   1207     if( g.okAdmin ){
................................................................................
  1212   1209         style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&amp;sub=1",
  1213   1210               g.zTop, zUuid);
  1214   1211       }else{
  1215   1212         style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
  1216   1213               g.zTop, zUuid);
  1217   1214       }
  1218   1215     }
  1219         -  content_get(rid, &content);
  1220         -  if( manifest_parse(&m, &content)==0 ){
  1221         -    fossil_redirect_home();
  1222         -  }
  1223         -  if( m.type!=CFTYPE_TICKET ){
         1216  +  pTktChng = manifest_get(rid, CFTYPE_TICKET);
         1217  +  if( pTktChng==0 ){
  1224   1218       fossil_redirect_home();
  1225   1219     }
  1226   1220     style_header("Ticket Change Details");
  1227         -  zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate);
  1228         -  memcpy(zTktName, m.zTicketUuid, 10);
         1221  +  zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate);
         1222  +  memcpy(zTktName, pTktChng->zTicketUuid, 10);
  1229   1223     zTktName[10] = 0;
  1230   1224     if( g.okHistory ){
  1231         -    @ <h2>Changes to ticket <a href="%s(m.zTicketUuid)">%s(zTktName)</a></h2>
         1225  +    @ <h2>Changes to ticket
         1226  +    @ <a href="%s(pTktChng->zTicketUuid)">%s(zTktName)</a></h2>
  1232   1227       @
  1233         -    @ <p>By %h(m.zUser) on %s(zDate).  See also:
         1228  +    @ <p>By %h(pTktChng->zUser) on %s(zDate).  See also:
  1234   1229       @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and
  1235         -    @ <a href="%s(g.zTop)/tkthistory/%s(m.zTicketUuid)">ticket history</a>
  1236         -    @ </p>
         1230  +    @ <a href="%s(g.zTop)/tkthistory/%s(pTktChng->zTicketUuid)">ticket
         1231  +    @ history</a></p>
  1237   1232     }else{
  1238   1233       @ <h2>Changes to ticket %s(zTktName)</h2>
  1239   1234       @
  1240         -    @ <p>By %h(m.zUser) on %s(zDate).
         1235  +    @ <p>By %h(pTktChng->zUser) on %s(zDate).
  1241   1236       @ </p>
  1242   1237     }
  1243   1238     @
  1244   1239     @ <ol>
  1245   1240     free(zDate);
  1246         -  ticket_output_change_artifact(&m);
  1247         -  manifest_clear(&m);
         1241  +  ticket_output_change_artifact(pTktChng);
         1242  +  manifest_destroy(pTktChng);
  1248   1243     style_footer_cmdref("info",0);
  1249   1244   }
  1250   1245   
  1251   1246   
  1252   1247   /*
  1253   1248   ** WEBPAGE: info
  1254   1249   ** URL: info/ARTIFACTID

Changes to src/manifest.c.

    24     24   #include "manifest.h"
    25     25   #include <assert.h>
    26     26   
    27     27   #if INTERFACE
    28     28   /*
    29     29   ** Types of control files
    30     30   */
           31  +#define CFTYPE_ANY        0
    31     32   #define CFTYPE_MANIFEST   1
    32     33   #define CFTYPE_CLUSTER    2
    33     34   #define CFTYPE_CONTROL    3
    34     35   #define CFTYPE_WIKI       4
    35     36   #define CFTYPE_TICKET     5
    36     37   #define CFTYPE_ATTACHMENT 6
    37     38   #define CFTYPE_EVENT      7
           39  +
           40  +/*
           41  +** A single F-card within a manifest
           42  +*/
           43  +struct ManifestFile { 
           44  +  char *zName;           /* Name of a file */
           45  +  char *zUuid;           /* UUID of the file */
           46  +  char *zPerm;           /* File permissions */
           47  +  char *zPrior;          /* Prior name if the name was changed */
           48  +};
           49  +
    38     50   
    39     51   /*
    40     52   ** A parsed manifest or cluster.
    41     53   */
    42     54   struct Manifest {
    43     55     Blob content;         /* The original content blob */
    44     56     int type;             /* Type of artifact.  One of CFTYPE_xxxxx */
           57  +  int rid;              /* The blob-id for this manifest */
           58  +  char *zBaseline;      /* Baseline manifest.  The B card. */
           59  +  Manifest *pBaseline;  /* The actual baseline manifest */
    45     60     char *zComment;       /* Decoded comment.  The C card. */
    46     61     double rDate;         /* Date and time from D card.  0.0 if no D card. */
    47     62     char *zUser;          /* Name of the user from the U card. */
    48     63     char *zRepoCksum;     /* MD5 checksum of the baseline content.  R card. */
    49     64     char *zWiki;          /* Text of the wiki page.  W card. */
    50     65     char *zWikiTitle;     /* Name of the wiki page. L card. */
    51     66     double rEventDate;    /* Date of an event.  E card. */
................................................................................
    52     67     char *zEventId;       /* UUID for an event.  E card. */
    53     68     char *zTicketUuid;    /* UUID for a ticket. K card. */
    54     69     char *zAttachName;    /* Filename of an attachment. A card. */
    55     70     char *zAttachSrc;     /* UUID of document being attached. A card. */
    56     71     char *zAttachTarget;  /* Ticket or wiki that attachment applies to.  A card */
    57     72     int nFile;            /* Number of F cards */
    58     73     int nFileAlloc;       /* Slots allocated in aFile[] */
    59         -  struct ManifestFile { 
    60         -    char *zName;           /* Name of a file */
    61         -    char *zUuid;           /* UUID of the file */
    62         -    char *zPerm;           /* File permissions */
    63         -    char *zPrior;          /* Prior name if the name was changed */
    64         -    int iRename;           /* index of renamed name in prior/next manifest */
    65         -  } *aFile;             /* One entry for each F card */
           74  +  int iFile;            /* Index of current file in iterator */
           75  +  ManifestFile *aFile;  /* One entry for each F-card */
    66     76     int nParent;          /* Number of parents. */
    67     77     int nParentAlloc;     /* Slots allocated in azParent[] */
    68     78     char **azParent;      /* UUIDs of parents.  One for each P card argument */
    69     79     int nCChild;          /* Number of cluster children */
    70     80     int nCChildAlloc;     /* Number of closts allocated in azCChild[] */
    71     81     char **azCChild;      /* UUIDs of referenced objects in a cluster. M cards */
    72     82     int nTag;             /* Number of T Cards */
................................................................................
    85     95   };
    86     96   #endif
    87     97   
    88     98   /*
    89     99   ** A cache of parsed manifests.  This reduces the number of
    90    100   ** calls to manifest_parse() when doing a rebuild.
    91    101   */
    92         -#define MX_MANIFEST_CACHE 4
          102  +#define MX_MANIFEST_CACHE 6
    93    103   static struct {
    94    104     int nxAge;
    95         -  int aRid[MX_MANIFEST_CACHE];
    96    105     int aAge[MX_MANIFEST_CACHE];
    97         -  Manifest aLine[MX_MANIFEST_CACHE];
          106  +  Manifest *apManifest[MX_MANIFEST_CACHE];
    98    107   } manifestCache;
    99    108   
   100    109   
   101    110   /*
   102    111   ** Clear the memory allocated in a manifest object
   103    112   */
   104         -void manifest_clear(Manifest *p){
   105         -  blob_reset(&p->content);
   106         -  free(p->aFile);
   107         -  free(p->azParent);
   108         -  free(p->azCChild);
   109         -  free(p->aTag);
   110         -  free(p->aField);
   111         -  memset(p, 0, sizeof(*p));
          113  +void manifest_destroy(Manifest *p){
          114  +  if( p ){
          115  +    blob_reset(&p->content);
          116  +    free(p->aFile);
          117  +    free(p->azParent);
          118  +    free(p->azCChild);
          119  +    free(p->aTag);
          120  +    free(p->aField);
          121  +    if( p->pBaseline ) manifest_destroy(p->pBaseline);
          122  +    fossil_free(p);
          123  +  }
   112    124   }
   113    125   
   114    126   /*
   115    127   ** Add an element to the manifest cache using LRU replacement.
   116    128   */
   117         -void manifest_cache_insert(int rid, Manifest *p){
   118         -  int i;
   119         -  for(i=0; i<MX_MANIFEST_CACHE; i++){
   120         -    if( manifestCache.aRid[i]==0 ) break;
   121         -  }
   122         -  if( i>=MX_MANIFEST_CACHE ){
   123         -    int oldest = 0;
   124         -    int oldestAge = manifestCache.aAge[0];
   125         -    for(i=1; i<MX_MANIFEST_CACHE; i++){
   126         -      if( manifestCache.aAge[i]<oldestAge ){
   127         -        oldest = i;
   128         -        oldestAge = manifestCache.aAge[i];
   129         -      }
   130         -    }
   131         -    manifest_clear(&manifestCache.aLine[oldest]);
   132         -    i = oldest;
   133         -  }
   134         -  manifestCache.aAge[i] = ++manifestCache.nxAge;
   135         -  manifestCache.aRid[i] = rid;
   136         -  manifestCache.aLine[i] = *p;
          129  +void manifest_cache_insert(Manifest *p){
          130  +  while( p ){
          131  +    int i;
          132  +    Manifest *pBaseline = p->pBaseline;
          133  +    p->pBaseline = 0;
          134  +    for(i=0; i<MX_MANIFEST_CACHE; i++){
          135  +      if( manifestCache.apManifest[i]==0 ) break;
          136  +    }
          137  +    if( i>=MX_MANIFEST_CACHE ){
          138  +      int oldest = 0;
          139  +      int oldestAge = manifestCache.aAge[0];
          140  +      for(i=1; i<MX_MANIFEST_CACHE; i++){
          141  +        if( manifestCache.aAge[i]<oldestAge ){
          142  +          oldest = i;
          143  +          oldestAge = manifestCache.aAge[i];
          144  +        }
          145  +      }
          146  +      manifest_destroy(manifestCache.apManifest[oldest]);
          147  +      i = oldest;
          148  +    }
          149  +    manifestCache.aAge[i] = ++manifestCache.nxAge;
          150  +    manifestCache.apManifest[i] = p;
          151  +    p = pBaseline;
          152  +  }
   137    153   }
   138    154   
   139    155   /*
   140    156   ** Try to extract a line from the manifest cache. Return 1 if found.
   141    157   ** Return 0 if not found.
   142    158   */
   143         -int manifest_cache_find(int rid, Manifest *p){
          159  +static Manifest *manifest_cache_find(int rid){
   144    160     int i;
          161  +  Manifest *p;
   145    162     for(i=0; i<MX_MANIFEST_CACHE; i++){
   146         -    if( manifestCache.aRid[i]==rid ){
   147         -      *p = manifestCache.aLine[i];
   148         -      manifestCache.aRid[i] = 0;
   149         -      return 1;
          163  +    if( manifestCache.apManifest[i] && manifestCache.apManifest[i]->rid==rid ){
          164  +      p = manifestCache.apManifest[i];
          165  +      manifestCache.apManifest[i] = 0;
          166  +      return p;
   150    167       }
   151    168     }
   152    169     return 0;
   153    170   }
   154    171   
   155    172   /*
   156    173   ** Clear the manifest cache.
   157    174   */
   158    175   void manifest_cache_clear(void){
   159    176     int i;
   160    177     for(i=0; i<MX_MANIFEST_CACHE; i++){
   161         -    if( manifestCache.aRid[i]>0 ){
   162         -      manifest_clear(&manifestCache.aLine[i]);
          178  +    if( manifestCache.apManifest[i] ){
          179  +      manifest_destroy(manifestCache.apManifest[i]);
   163    180       }
   164    181     }
   165    182     memset(&manifestCache, 0, sizeof(manifestCache));
   166    183   }
   167    184   
   168    185   #ifdef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM
   169    186   # define md5sum_init(X)
   170    187   # define md5sum_step_text(X,Y)
   171    188   #endif
          189  +
          190  +/*
          191  +** Remove the PGP signature from the artifact, if there is one.
          192  +*/
          193  +static void remove_pgp_signature(char **pz, int *pn){
          194  +  char *z = *pz;
          195  +  int n = *pn;
          196  +  int i;
          197  +  if( memcmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return;
          198  +  for(i=34; i<n && (z[i-1]!='\n' || z[i-2]!='\n'); i++){}
          199  +  if( i>=n ) return;
          200  +  z += i;
          201  +  n -= i;
          202  +  *pz = z;
          203  +  for(i=n-1; i>=0; i--){
          204  +    if( z[i]=='\n' && memcmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){
          205  +      n = i+1;
          206  +      break;
          207  +    }
          208  +  }
          209  +  *pn = n;
          210  +  return;
          211  +}
          212  +
          213  +/*
          214  +** Verify the Z-card checksum on the artifact, if there is such a
          215  +** checksum.  Return 0 if there is no Z-card.  Return 1 if the Z-card
          216  +** exists and is correct.  Return 2 if the Z-card exists and has the wrong
          217  +** value.
          218  +**
          219  +**   0123456789 123456789 123456789 123456789 
          220  +**   Z aea84f4f863865a8d59d0384e4d2a41c
          221  +*/
          222  +static int verify_z_card(const char *z, int n){
          223  +  if( n<35 ) return 0;
          224  +  if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0;
          225  +  md5sum_init();
          226  +  md5sum_step_text(z, n-35);
          227  +  if( memcmp(&z[n-33], md5sum_finish(0), 32)==0 ){
          228  +    return 1;
          229  +  }else{
          230  +    return 2;
          231  +  }
          232  +}
          233  +
          234  +/*
          235  +** A structure used for rapid parsing of the Manifest file
          236  +*/
          237  +typedef struct ManifestText ManifestText;
          238  +struct ManifestText {
          239  +  char *z;           /* The first character of the next token */
          240  +  char *zEnd;        /* One character beyond the end of the manifest */
          241  +  int atEol;         /* True if z points to the start of a new line */
          242  +};
          243  +
          244  +/*
          245  +** Return a pointer to the next token.  The token is zero-terminated.
          246  +** Return NULL if there are no more tokens on the current line.
          247  +*/
          248  +static char *next_token(ManifestText *p, int *pLen){
          249  +  char *z;
          250  +  char *zStart;
          251  +  int c;
          252  +  if( p->atEol ) return 0;
          253  +  zStart = z = p->z;
          254  +  while( (c=(*z))!=' ' && c!='\n' ){ z++; }
          255  +  *z = 0;
          256  +  p->z = &z[1];
          257  +  p->atEol = c=='\n';
          258  +  if( pLen ) *pLen = z - zStart;
          259  +  return zStart;
          260  +}
          261  +
          262  +/*
          263  +** Return the card-type for the next card.  Or, return 0 if there are no
          264  +** more cards or if we are not at the end of the current card.
          265  +*/
          266  +static char next_card(ManifestText *p){
          267  +  char c;
          268  +  if( !p->atEol || p->z>=p->zEnd ) return 0;
          269  +  c = p->z[0];
          270  +  if( p->z[1]==' ' ){
          271  +    p->z += 2;
          272  +    p->atEol = 0;
          273  +  }else if( p->z[1]=='\n' ){
          274  +    p->z += 2;
          275  +    p->atEol = 1;
          276  +  }else{
          277  +    c = 0;
          278  +  }
          279  +  return c;
          280  +}
   172    281   
   173    282   /*
   174    283   ** Parse a blob into a Manifest object.  The Manifest object
   175    284   ** takes over the input blob and will free it when the
   176    285   ** Manifest object is freed.  Zeros are inserted into the blob
   177    286   ** as string terminators so that blob should not be used again.
   178    287   **
................................................................................
   193    302   ** The file consists of zero or more cards, one card per line.
   194    303   ** (Except: the content of the W card can extend of multiple lines.)
   195    304   ** Each card is divided into tokens by a single space character.
   196    305   ** The first token is a single upper-case letter which is the card type.
   197    306   ** The card type determines the other parameters to the card.
   198    307   ** Cards must occur in lexicographical order.
   199    308   */
   200         -int manifest_parse(Manifest *p, Blob *pContent){
   201         -  int seenHeader = 0;
          309  +static Manifest *manifest_parse(Blob *pContent, int rid){
          310  +  Manifest *p;
   202    311     int seenZ = 0;
   203    312     int i, lineNo=0;
   204         -  Blob line, token, a1, a2, a3, a4;
          313  +  ManifestText x;
   205    314     char cPrevType = 0;
          315  +  char cType;
          316  +  char *z;
          317  +  int n;
          318  +  char *zUuid;
          319  +  int sz = 0;
   206    320   
   207    321     /* Every control artifact ends with a '\n' character.  Exit early
   208         -  ** if that is not the case for this artifact. */
   209         -  i = blob_size(pContent);
   210         -  if( i<=0 || blob_buffer(pContent)[i-1]!='\n' ){
          322  +  ** if that is not the case for this artifact.
          323  +  */
          324  +  z = blob_buffer(pContent);
          325  +  n = blob_size(pContent);
          326  +  if( n<=0 || z[n-1]!='\n' ){
          327  +    blob_reset(pContent);
          328  +    return 0;
          329  +  }
          330  +
          331  +  /* Strip off the PGP signature if there is one.  Then verify the
          332  +  ** Z-card.
          333  +  */
          334  +  remove_pgp_signature(&z, &n);
          335  +  if( verify_z_card(z, n)==0 ){
          336  +    blob_reset(pContent);
          337  +    return 0;
          338  +  }
          339  +
          340  +  /* Verify that the first few characters of the artifact look like
          341  +  ** a control artifact.
          342  +  */
          343  +  if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){
   211    344       blob_reset(pContent);
   212    345       return 0;
   213    346     }
   214    347   
          348  +  /* Allocate a Manifest object to hold the parsed control artifact.
          349  +  */
          350  +  p = fossil_malloc( sizeof(*p) );
   215    351     memset(p, 0, sizeof(*p));
   216    352     memcpy(&p->content, pContent, sizeof(p->content));
          353  +  p->rid = rid;
   217    354     blob_zero(pContent);
   218    355     pContent = &p->content;
   219    356   
   220         -  blob_zero(&a1);
   221         -  blob_zero(&a2);
   222         -  blob_zero(&a3);
   223         -  md5sum_init();
   224         -  while( blob_line(pContent, &line) ){
   225         -    char *z = blob_buffer(&line);
          357  +  /* Begin parsing, card by card.
          358  +  */
          359  +  x.z = z;
          360  +  x.zEnd = &z[n];
          361  +  x.atEol = 1;
          362  +  while( (cType = next_card(&x))!=0 && cType>=cPrevType ){
   226    363       lineNo++;
   227         -    if( z[0]=='-' ){
   228         -      if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){
   229         -        goto manifest_syntax_error;
   230         -      }
   231         -      if( seenHeader ){
   232         -        break;
   233         -      }
   234         -      while( blob_line(pContent, &line)>2 ){}
   235         -      if( blob_line(pContent, &line)==0 ) break;
   236         -      z = blob_buffer(&line);
   237         -    }
   238         -    if( z[0]<cPrevType ){
   239         -      /* Lines of a manifest must occur in lexicographical order */
   240         -      goto manifest_syntax_error;
   241         -    }
   242         -    cPrevType = z[0];
   243         -    seenHeader = 1;
   244         -    if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error;
   245         -    switch( z[0] ){
          364  +    switch( cType ){
   246    365         /*
   247    366         **     A <filename> <target> ?<source>?
   248    367         **
   249    368         ** Identifies an attachment to either a wiki page or a ticket.
   250    369         ** <source> is the artifact that is the attachment.  <source>
   251    370         ** is omitted to delete an attachment.  <target> is the name of
   252    371         ** a wiki page or ticket to which that attachment is connected.
   253    372         */
   254    373         case 'A': {
   255    374           char *zName, *zTarget, *zSrc;
   256         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   257         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   258         -        if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
          375  +        int nTarget = 0, nSrc = 0;
          376  +        zName = next_token(&x, 0);
          377  +        zTarget = next_token(&x, &nTarget);
          378  +        zSrc = next_token(&x, &nSrc);
          379  +        if( zName==0 || zTarget==0 ) goto manifest_syntax_error;      
   259    380           if( p->zAttachName!=0 ) goto manifest_syntax_error;
   260         -        zName = blob_terminate(&a1);
   261         -        zTarget = blob_terminate(&a2);
   262         -        blob_token(&line, &a3);
   263         -        zSrc = blob_terminate(&a3);
   264    381           defossilize(zName);
   265    382           if( !file_is_simple_pathname(zName) ){
   266    383             goto manifest_syntax_error;
   267    384           }
   268    385           defossilize(zTarget);
   269         -        if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE))
          386  +        if( (nTarget!=UUID_SIZE || !validate16(zTarget, UUID_SIZE))
   270    387              && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){
   271    388             goto manifest_syntax_error;
   272    389           }
   273         -        if( blob_size(&a3)>0
   274         -         && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){
          390  +        if( zSrc && (nSrc!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){
   275    391             goto manifest_syntax_error;
   276    392           }
   277    393           p->zAttachName = (char*)file_tail(zName);
   278    394           p->zAttachSrc = zSrc;
   279    395           p->zAttachTarget = zTarget;
   280    396           break;
   281    397         }
          398  +
          399  +      /*
          400  +      **    B <uuid>
          401  +      **
          402  +      ** A B-line gives the UUID for the baselinen of a delta-manifest.
          403  +      */
          404  +      case 'B': {
          405  +        if( p->zBaseline ) goto manifest_syntax_error;
          406  +        p->zBaseline = next_token(&x, &sz);
          407  +        if( p->zBaseline==0 ) goto manifest_syntax_error;
          408  +        if( sz!=UUID_SIZE ) goto manifest_syntax_error;
          409  +        if( !validate16(p->zBaseline, UUID_SIZE) ) goto manifest_syntax_error;
          410  +        break;
          411  +      }
          412  +
   282    413   
   283    414         /*
   284    415         **     C <comment>
   285    416         **
   286    417         ** Comment text is fossil-encoded.  There may be no more than
   287    418         ** one C line.  C lines are required for manifests and are
   288    419         ** disallowed on all other control files.
   289    420         */
   290    421         case 'C': {
   291         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   292    422           if( p->zComment!=0 ) goto manifest_syntax_error;
   293         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   294         -        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
   295         -        p->zComment = blob_terminate(&a1);
          423  +        p->zComment = next_token(&x, 0);
          424  +        if( p->zComment==0 ) goto manifest_syntax_error;
   296    425           defossilize(p->zComment);
   297    426           break;
   298    427         }
   299    428   
   300    429         /*
   301    430         **     D <timestamp>
   302    431         **
   303    432         ** The timestamp should be ISO 8601.   YYYY-MM-DDtHH:MM:SS
   304    433         ** There can be no more than 1 D line.  D lines are required
   305    434         ** for all control files except for clusters.
   306    435         */
   307    436         case 'D': {
   308         -        char *zDate;
   309         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   310         -        if( p->rDate!=0.0 ) goto manifest_syntax_error;
   311         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   312         -        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
   313         -        zDate = blob_terminate(&a1);
   314         -        p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate);
          437  +        if( p->rDate>0.0 ) goto manifest_syntax_error;
          438  +        p->rDate = db_double(0.0, "SELECT julianday(%Q)", next_token(&x,0));
          439  +        if( p->rDate<=0.0 ) goto manifest_syntax_error;
   315    440           break;
   316    441         }
   317    442   
   318    443         /*
   319    444         **     E <timestamp> <uuid>
   320    445         **
   321    446         ** An "event" card that contains the timestamp of the event in the 
   322    447         ** format YYYY-MM-DDtHH:MM:SS and a unique identifier for the event.
   323    448         ** The event timestamp is distinct from the D timestamp.  The D
   324    449         ** timestamp is when the artifact was created whereas the E timestamp
   325    450         ** is when the specific event is said to occur.
   326    451         */
   327    452         case 'E': {
   328         -        char *zEDate;
   329         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   330         -        if( p->rEventDate!=0.0 ) goto manifest_syntax_error;
   331         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   332         -        if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
   333         -        if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error;
   334         -        zEDate = blob_terminate(&a1);
   335         -        p->rEventDate = db_double(0.0, "SELECT julianday(%Q)", zEDate);
          453  +        if( p->rEventDate>0.0 ) goto manifest_syntax_error;
          454  +        p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0));
   336    455           if( p->rEventDate<=0.0 ) goto manifest_syntax_error;
   337         -        if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error;
   338         -        p->zEventId = blob_terminate(&a2);
          456  +        p->zEventId = next_token(&x, &sz);
          457  +        if( sz!=UUID_SIZE ) goto manifest_syntax_error;
   339    458           if( !validate16(p->zEventId, UUID_SIZE) ) goto manifest_syntax_error;
   340    459           break;
   341    460         }
   342    461   
   343    462         /*
   344         -      **     F <filename> <uuid> ?<permissions>? ?<old-name>?
          463  +      **     F <filename> ?<uuid>? ?<permissions>? ?<old-name>?
   345    464         **
   346    465         ** Identifies a file in a manifest.  Multiple F lines are
   347    466         ** allowed in a manifest.  F lines are not allowed in any
   348    467         ** other control file.  The filename and old-name are fossil-encoded.
   349    468         */
   350    469         case 'F': {
   351         -        char *zName, *zUuid, *zPerm, *zPriorName;
   352         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   353         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   354         -        if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
   355         -        zName = blob_terminate(&a1);
   356         -        zUuid = blob_terminate(&a2);
   357         -        blob_token(&line, &a3);
   358         -        zPerm = blob_terminate(&a3);
   359         -        if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error;
   360         -        if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
          470  +        char *zName, *zPerm, *zPriorName;
          471  +        zName = next_token(&x,0);
          472  +        if( zName==0 ) goto manifest_syntax_error;
   361    473           defossilize(zName);
   362    474           if( !file_is_simple_pathname(zName) ){
   363    475             goto manifest_syntax_error;
   364    476           }
   365         -        blob_token(&line, &a4);
   366         -        zPriorName = blob_terminate(&a4);
   367         -        if( zPriorName[0] ){
          477  +        zUuid = next_token(&x, &sz);
          478  +        if( p->zBaseline==0 || zUuid!=0 ){
          479  +          if( sz!=UUID_SIZE ) goto manifest_syntax_error;
          480  +          if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
          481  +        }
          482  +        zPerm = next_token(&x,0);
          483  +        zPriorName = next_token(&x,0);
          484  +        if( zPriorName ){
   368    485             defossilize(zPriorName);
   369    486             if( !file_is_simple_pathname(zPriorName) ){
   370    487               goto manifest_syntax_error;
   371    488             }
   372         -        }else{
   373         -          zPriorName = 0;
   374    489           }
   375    490           if( p->nFile>=p->nFileAlloc ){
   376    491             p->nFileAlloc = p->nFileAlloc*2 + 10;
   377    492             p->aFile = fossil_realloc(p->aFile, 
   378    493                                       p->nFileAlloc*sizeof(p->aFile[0]) );
   379    494           }
   380    495           i = p->nFile++;
   381    496           p->aFile[i].zName = zName;
   382    497           p->aFile[i].zUuid = zUuid;
   383    498           p->aFile[i].zPerm = zPerm;
   384    499           p->aFile[i].zPrior = zPriorName;
   385         -        p->aFile[i].iRename = -1;
   386    500           if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){
   387    501             goto manifest_syntax_error;
   388    502           }
   389    503           break;
   390    504         }
   391    505   
   392    506         /*
................................................................................
   395    509         ** Specifies a name value pair for ticket.  If the first character
   396    510         ** of <name> is "+" then the <value> is appended to any preexisting
   397    511         ** value.  If <value> is omitted then it is understood to be an
   398    512         ** empty string.
   399    513         */
   400    514         case 'J': {
   401    515           char *zName, *zValue;
   402         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   403         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   404         -        blob_token(&line, &a2);
   405         -        if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error;
   406         -        zName = blob_terminate(&a1);
   407         -        zValue = blob_terminate(&a2);
          516  +        zName = next_token(&x,0);
          517  +        zValue = next_token(&x,0);
          518  +        if( zName==0 ) goto manifest_syntax_error;
          519  +        if( zValue==0 ) zValue = "";
   408    520           defossilize(zValue);
   409    521           if( p->nField>=p->nFieldAlloc ){
   410    522             p->nFieldAlloc = p->nFieldAlloc*2 + 10;
   411    523             p->aField = fossil_realloc(p->aField,
   412    524                                  p->nFieldAlloc*sizeof(p->aField[0]) );
   413    525           }
   414    526           i = p->nField++;
................................................................................
   424    536         /*
   425    537         **    K <uuid>
   426    538         **
   427    539         ** A K-line gives the UUID for the ticket which this control file
   428    540         ** is amending.
   429    541         */
   430    542         case 'K': {
   431         -        char *zUuid;
   432         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   433         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   434         -        zUuid = blob_terminate(&a1);
   435         -        if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
   436         -        if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
   437    543           if( p->zTicketUuid!=0 ) goto manifest_syntax_error;
   438         -        p->zTicketUuid = zUuid;
          544  +        p->zTicketUuid = next_token(&x, &sz);
          545  +        if( sz!=UUID_SIZE ) goto manifest_syntax_error;
          546  +        if( !validate16(p->zTicketUuid, UUID_SIZE) ) goto manifest_syntax_error;
   439    547           break;
   440    548         }
   441    549   
   442    550         /*
   443    551         **     L <wikititle>
   444    552         **
   445    553         ** The wiki page title is fossil-encoded.  There may be no more than
   446    554         ** one L line.
   447    555         */
   448    556         case 'L': {
   449         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   450    557           if( p->zWikiTitle!=0 ) goto manifest_syntax_error;
   451         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   452         -        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
   453         -        p->zWikiTitle = blob_terminate(&a1);
          558  +        p->zWikiTitle = next_token(&x,0);
          559  +        if( p->zWikiTitle==0 ) goto manifest_syntax_error;
   454    560           defossilize(p->zWikiTitle);
   455    561           if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){
   456    562             goto manifest_syntax_error;
   457    563           }
   458    564           break;
   459    565         }
   460    566   
................................................................................
   461    567         /*
   462    568         **    M <uuid>
   463    569         **
   464    570         ** An M-line identifies another artifact by its UUID.  M-lines
   465    571         ** occur in clusters only.
   466    572         */
   467    573         case 'M': {
   468         -        char *zUuid;
   469         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   470         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   471         -        zUuid = blob_terminate(&a1);
   472         -        if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
          574  +        zUuid = next_token(&x, &sz);
          575  +        if( zUuid==0 ) goto manifest_syntax_error;
          576  +        if( sz!=UUID_SIZE ) goto manifest_syntax_error;
   473    577           if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
   474    578           if( p->nCChild>=p->nCChildAlloc ){
   475    579             p->nCChildAlloc = p->nCChildAlloc*2 + 10;
   476    580             p->azCChild = fossil_realloc(p->azCChild
   477    581                                    , p->nCChildAlloc*sizeof(p->azCChild[0]) );
   478    582           }
   479    583           i = p->nCChild++;
................................................................................
   488    592         **     P <uuid> ...
   489    593         **
   490    594         ** Specify one or more other artifacts where are the parents of
   491    595         ** this artifact.  The first parent is the primary parent.  All
   492    596         ** others are parents by merge.
   493    597         */
   494    598         case 'P': {
   495         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   496         -        while( blob_token(&line, &a1) ){
   497         -          char *zUuid;
   498         -          if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
   499         -          zUuid = blob_terminate(&a1);
          599  +        while( (zUuid = next_token(&x, &sz))!=0 ){
          600  +          if( sz!=UUID_SIZE ) goto manifest_syntax_error;
   500    601             if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
   501    602             if( p->nParent>=p->nParentAlloc ){
   502    603               p->nParentAlloc = p->nParentAlloc*2 + 5;
   503    604               p->azParent = fossil_realloc(p->azParent,
   504    605                                  p->nParentAlloc*sizeof(char*));
   505    606             }
   506    607             i = p->nParent++;
................................................................................
   512    613         /*
   513    614         **     R <md5sum>
   514    615         **
   515    616         ** Specify the MD5 checksum over the name and content of all files
   516    617         ** in the manifest.
   517    618         */
   518    619         case 'R': {
   519         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   520    620           if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
   521         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   522         -        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
   523         -        if( blob_size(&a1)!=32 ) goto manifest_syntax_error;
   524         -        p->zRepoCksum = blob_terminate(&a1);
          621  +        p->zRepoCksum = next_token(&x, &sz);
          622  +        if( sz!=32 ) goto manifest_syntax_error;
   525    623           if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error;
   526    624           break;
   527    625         }
   528    626   
   529    627         /*
   530    628         **    T (+|*|-)<tagname> <uuid> ?<value>?
   531    629         **
................................................................................
   538    636         ** The tag is applied to <uuid>.  If <uuid> is "*" then the tag is
   539    637         ** applied to the current manifest.  If <value> is provided then 
   540    638         ** the tag is really a property with the given value.
   541    639         **
   542    640         ** Tags are not allowed in clusters.  Multiple T lines are allowed.
   543    641         */
   544    642         case 'T': {
   545         -        char *zName, *zUuid, *zValue;
   546         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   547         -        if( blob_token(&line, &a1)==0 ){
   548         -          goto manifest_syntax_error;
   549         -        }
   550         -        if( blob_token(&line, &a2)==0 ){
   551         -          goto manifest_syntax_error;
   552         -        }
   553         -        zName = blob_terminate(&a1);
   554         -        zUuid = blob_terminate(&a2);
   555         -        if( blob_token(&line, &a3)==0 ){
   556         -          zValue = 0;
   557         -        }else{
   558         -          zValue = blob_terminate(&a3);
   559         -          defossilize(zValue);
   560         -        }
   561         -        if( blob_size(&a2)==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){
          643  +        char *zName, *zValue;
          644  +        zName = next_token(&x, 0);
          645  +        if( zName==0 ) goto manifest_syntax_error;
          646  +        zUuid = next_token(&x, &sz);
          647  +        if( zUuid==0 ) goto manifest_syntax_error;
          648  +        zValue = next_token(&x, 0);
          649  +        if( zValue ) defossilize(zValue);
          650  +        if( sz==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){
   562    651             /* A valid uuid */
   563         -        }else if( blob_size(&a2)==1 && zUuid[0]=='*' ){
          652  +        }else if( sz==1 && zUuid[0]=='*' ){
   564    653             zUuid = 0;
   565    654           }else{
   566    655             goto manifest_syntax_error;
   567    656           }
   568    657           defossilize(zName);
   569    658           if( zName[0]!='-' && zName[0]!='+' && zName[0]!='*' ){
   570    659             goto manifest_syntax_error;
................................................................................
   591    680         **     U ?<login>?
   592    681         **
   593    682         ** Identify the user who created this control file by their
   594    683         ** login.  Only one U line is allowed.  Prohibited in clusters.
   595    684         ** If the user name is omitted, take that to be "anonymous".
   596    685         */
   597    686         case 'U': {
   598         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   599    687           if( p->zUser!=0 ) goto manifest_syntax_error;
   600         -        if( blob_token(&line, &a1)==0 ){
          688  +        p->zUser = next_token(&x, 0);
          689  +        if( p->zUser==0 ){
   601    690             p->zUser = "anonymous";
   602    691           }else{
   603         -          p->zUser = blob_terminate(&a1);
   604    692             defossilize(p->zUser);
   605    693           }
   606         -        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
   607    694           break;
   608    695         }
   609    696   
   610    697         /*
   611    698         **     W <size>
   612    699         **
   613    700         ** The next <size> bytes of the file contain the text of the wiki
   614    701         ** page.  There is always an extra \n before the start of the next
   615    702         ** record.
   616    703         */
   617    704         case 'W': {
   618         -        int size;
          705  +        char *zSize;
          706  +        int size, c;
   619    707           Blob wiki;
   620         -        md5sum_step_text(blob_buffer(&line), blob_size(&line));
   621         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   622         -        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
   623         -        if( !blob_is_int(&a1, &size) ) goto manifest_syntax_error;
          708  +        zSize = next_token(&x, 0);
          709  +        if( zSize==0 ) goto manifest_syntax_error;
          710  +        if( x.atEol==0 ) goto manifest_syntax_error;
          711  +        for(size=0; (c = zSize[0])>='0' && c<='9'; zSize++){
          712  +           size = size*10 + c - '0';
          713  +        }
   624    714           if( size<0 ) goto manifest_syntax_error;
   625    715           if( p->zWiki!=0 ) goto manifest_syntax_error;
   626    716           blob_zero(&wiki);
   627         -        if( blob_extract(pContent, size+1, &wiki)!=size+1 ){
   628         -          goto manifest_syntax_error;
   629         -        }
   630         -        p->zWiki = blob_buffer(&wiki);
   631         -        md5sum_step_text(p->zWiki, size+1);
   632         -        if( p->zWiki[size]!='\n' ) goto manifest_syntax_error;
   633         -        p->zWiki[size] = 0;
          717  +        if( (&x.z[size+1])>=x.zEnd ) goto manifest_syntax_error;
          718  +        p->zWiki = x.z;
          719  +        x.z += size;
          720  +        if( x.z[0]!='\n' ) goto manifest_syntax_error;
          721  +        x.z[0] = 0;
          722  +        x.z++;
   634    723           break;
   635    724         }
   636    725   
   637    726   
   638    727         /*
   639    728         **     Z <md5sum>
   640    729         **
................................................................................
   643    732         ** line.  This must be the last record.
   644    733         **
   645    734         ** This card is required for all control file types except for
   646    735         ** Manifest.  It is not required for manifest only for historical
   647    736         ** compatibility reasons.
   648    737         */
   649    738         case 'Z': {
   650         -#ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM
   651         -        int rc;
   652         -        Blob hash;
   653         -#endif
   654         -        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
   655         -        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
   656         -        if( blob_size(&a1)!=32 ) goto manifest_syntax_error;
   657         -        if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error;
   658         -#ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM
   659         -        md5sum_finish(&hash);
   660         -        rc = blob_compare(&hash, &a1);
   661         -        blob_reset(&hash);
   662         -        if( rc!=0 ) goto manifest_syntax_error;
   663         -#endif
          739  +        zUuid = next_token(&x, &sz);
          740  +        if( sz!=32 ) goto manifest_syntax_error;
          741  +        if( !validate16(zUuid, 32) ) goto manifest_syntax_error;
   664    742           seenZ = 1;
   665    743           break;
   666    744         }
   667    745         default: {
   668    746           goto manifest_syntax_error;
   669    747         }
   670    748       }
   671    749     }
   672         -  if( !seenHeader ) goto manifest_syntax_error;
          750  +  if( x.z<x.zEnd ) goto manifest_syntax_error;
   673    751   
   674         -  if( p->nFile>0 || p->zRepoCksum!=0 ){
          752  +  if( p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline ){
   675    753       if( p->nCChild>0 ) goto manifest_syntax_error;
   676    754       if( p->rDate<=0.0 ) goto manifest_syntax_error;
   677    755       if( p->nField>0 ) goto manifest_syntax_error;
   678    756       if( p->zTicketUuid ) goto manifest_syntax_error;
   679    757       if( p->zWiki ) goto manifest_syntax_error;
   680    758       if( p->zWikiTitle ) goto manifest_syntax_error;
   681    759       if( p->zEventId ) goto manifest_syntax_error;
................................................................................
   752    830       if( p->nField>0 ) goto manifest_syntax_error;
   753    831       if( p->zTicketUuid ) goto manifest_syntax_error;
   754    832       if( p->zWikiTitle ) goto manifest_syntax_error;
   755    833       if( p->zTicketUuid ) goto manifest_syntax_error;
   756    834       p->type = CFTYPE_MANIFEST;
   757    835     }
   758    836     md5sum_init();
   759         -  return 1;
          837  +  return p;
   760    838   
   761    839   manifest_syntax_error:
   762    840     /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
   763    841     md5sum_init();
   764         -  manifest_clear(p);
          842  +  manifest_destroy(p);
   765    843     return 0;
   766    844   }
          845  +
          846  +/*
          847  +** Get a manifest given the rid for the control artifact.  Return
          848  +** a pointer to the manifest on success or NULL if there is a failure.
          849  +*/
          850  +Manifest *manifest_get(int rid, int cfType){
          851  +  Blob content;
          852  +  Manifest *p;
          853  +  p = manifest_cache_find(rid);
          854  +  if( p ){
          855  +    if( cfType!=CFTYPE_ANY && cfType!=p->type ){
          856  +      manifest_cache_insert(p);
          857  +      p = 0;
          858  +    }
          859  +    return p;
          860  +  }
          861  +  content_get(rid, &content);
          862  +  p = manifest_parse(&content, rid);
          863  +  if( p && cfType!=CFTYPE_ANY && cfType!=p->type ){
          864  +    manifest_destroy(p);
          865  +    p = 0;
          866  +  }
          867  +  return p;
          868  +}
          869  +
          870  +/*
          871  +** Given a checkin name, load and parse the manifest for that checkin.
          872  +** Throw a fatal error if anything goes wrong.
          873  +*/
          874  +Manifest *manifest_get_by_name(const char *zName, int *pRid){
          875  +  int rid;
          876  +  Manifest *p;
          877  +
          878  +  rid = name_to_rid(zName);
          879  +  if( !is_a_version(rid) ){
          880  +    fossil_fatal("no such checkin: %s", zName);
          881  +  }
          882  +  if( pRid ) *pRid = rid;
          883  +  p = manifest_get(rid, CFTYPE_MANIFEST);
          884  +  if( p==0 ){
          885  +    fossil_fatal("cannot parse manifest for checkin: %s", zName);
          886  +  }
          887  +  return p;
          888  +}
   767    889   
   768    890   /*
   769    891   ** COMMAND: test-parse-manifest
   770    892   **
   771    893   ** Usage: %fossil test-parse-manifest FILENAME ?N?
   772    894   **
   773    895   ** Parse the manifest and discarded.  Use for testing only.
   774    896   */
   775    897   void manifest_test_parse_cmd(void){
   776         -  Manifest m;
          898  +  Manifest *p;
   777    899     Blob b;
   778    900     int i;
   779    901     int n = 1;
          902  +  sqlite3_open(":memory:", &g.db);
   780    903     if( g.argc!=3 && g.argc!=4 ){
   781    904       usage("FILENAME");
   782    905     }
   783         -  db_must_be_within_tree();
   784    906     blob_read_from_file(&b, g.argv[2]);
   785    907     if( g.argc>3 ) n = atoi(g.argv[3]);
   786    908     for(i=0; i<n; i++){
   787    909       Blob b2;
   788    910       blob_copy(&b2, &b);
   789         -    manifest_parse(&m, &b2);
   790         -    manifest_clear(&m);
          911  +    p = manifest_parse(&b2, 0);
          912  +    manifest_destroy(p);
   791    913     }
   792    914   }
          915  +
          916  +/*
          917  +** Fetch the baseline associated with the delta-manifest p.
          918  +** Return 0 on success.  If unable to parse the baseline,
          919  +** throw an error.  If the baseline is a manifest, throw an
          920  +** error if throwError is true, or record that p is an orphan
          921  +** and return 1 throwError is false.
          922  +*/
          923  +static int fetch_baseline(Manifest *p, int throwError){
          924  +  if( p->zBaseline!=0 && p->pBaseline==0 ){
          925  +    int rid = uuid_to_rid(p->zBaseline, 0);
          926  +    if( rid==0 && !throwError ){
          927  +      rid = content_new(p->zBaseline);
          928  +      db_multi_exec(
          929  +         "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)",
          930  +         rid, p->rid
          931  +      );
          932  +      return 1;
          933  +    }
          934  +    p->pBaseline = manifest_get(rid, CFTYPE_MANIFEST);
          935  +    if( p->pBaseline==0 ){
          936  +      if( !throwError && db_exists("SELECT 1 FROM phantom WHERE rid=%d",rid) ){
          937  +        db_multi_exec(
          938  +           "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)",
          939  +           rid, p->rid
          940  +        );
          941  +        return 1;
          942  +      }    
          943  +      fossil_fatal("cannot access baseline manifest %S", p->zBaseline);
          944  +    }
          945  +  }
          946  +  return 0;
          947  +}
          948  +
          949  +/*
          950  +** Rewind a manifest-file iterator back to the beginning of the manifest.
          951  +*/
          952  +void manifest_file_rewind(Manifest *p){
          953  +  p->iFile = 0;
          954  +  fetch_baseline(p, 1);
          955  +  if( p->pBaseline ){
          956  +    p->pBaseline->iFile = 0;
          957  +  }
          958  +}
          959  +
          960  +/*
          961  +** Advance to the next manifest-file.
          962  +**
          963  +** Return NULL for end-of-records or if there is an error.  If an error
          964  +** occurs and pErr!=0 then store 1 in *pErr.
          965  +*/
          966  +ManifestFile *manifest_file_next(
          967  +  Manifest *p,   
          968  +  int *pErr
          969  +){
          970  +  ManifestFile *pOut = 0;
          971  +  if( pErr ) *pErr = 0;
          972  +  if( p->pBaseline==0 ){
          973  +    /* Manifest p is a baseline-manifest.  Just scan down the list
          974  +    ** of files. */
          975  +    if( p->iFile<p->nFile ) pOut = &p->aFile[p->iFile++];
          976  +  }else{
          977  +    /* Manifest p is a delta-manifest.  Scan the baseline but amend the
          978  +    ** file list in the baseline with changes described by p.
          979  +    */
          980  +    Manifest *pB = p->pBaseline;
          981  +    int cmp;
          982  +    while(1){
          983  +      if( pB->iFile>=pB->nFile ){
          984  +        /* We have used all entries out of the baseline.  Return the next
          985  +        ** entry from the delta. */
          986  +        if( p->iFile<p->nFile ) pOut = &p->aFile[p->iFile++];
          987  +        break;
          988  +      }else if( p->iFile>=p->nFile ){
          989  +        /* We have used all entries from the delta.  Return the next
          990  +        ** entry from the baseline. */
          991  +        if( pB->iFile<pB->nFile ) pOut = &pB->aFile[pB->iFile++];
          992  +        break;
          993  +      }else if( (cmp = strcmp(pB->aFile[pB->iFile].zName,
          994  +                              p->aFile[p->iFile].zName)) < 0 ){
          995  +        /* The next baseline entry comes before the next delta entry.
          996  +        ** So return the baseline entry. */
          997  +        pOut = &pB->aFile[pB->iFile++];
          998  +        break;
          999  +      }else if( cmp>0 ){
         1000  +        /* The next delta entry comes before the next baseline
         1001  +        ** entry so return the delta entry */
         1002  +        pOut = &p->aFile[p->iFile++];
         1003  +        break;
         1004  +      }else if( p->aFile[p->iFile].zUuid ){
         1005  +        /* The next delta entry is a replacement for the next baseline
         1006  +        ** entry.  Skip the baseline entry and return the delta entry */
         1007  +        pB->iFile++;
         1008  +        pOut = &p->aFile[p->iFile++];
         1009  +        break;
         1010  +      }else{
         1011  +        /* The next delta entry is a delete of the next baseline
         1012  +        ** entry.  Skip them both.  Repeat the loop to find the next
         1013  +        ** non-delete entry. */
         1014  +        pB->iFile++;
         1015  +        p->iFile++;
         1016  +        continue;
         1017  +      }
         1018  +    }
         1019  +  }
         1020  +  return pOut;
         1021  +}
   793   1022   
   794   1023   /*
   795   1024   ** Translate a filename into a filename-id (fnid).  Create a new fnid
   796   1025   ** if no previously exists.
   797   1026   */
   798   1027   static int filename_to_fnid(const char *zFilename){
   799   1028     static Stmt q1, s1;
................................................................................
   816   1045   
   817   1046   /*
   818   1047   ** Add a single entry to the mlink table.  Also add the filename to
   819   1048   ** the filename table if it is not there already.
   820   1049   */
   821   1050   static void add_one_mlink(
   822   1051     int mid,                  /* The record ID of the manifest */
   823         -  const char *zFromUuid,    /* UUID for the mlink.pid field */
   824         -  const char *zToUuid,      /* UUID for the mlink.fid field */
         1052  +  const char *zFromUuid,    /* UUID for the mlink.pid. "" to add file */
         1053  +  const char *zToUuid,      /* UUID for the mlink.fid. "" to delele */
   825   1054     const char *zFilename,    /* Filename */
   826         -  const char *zPrior        /* Previous filename.  NULL if unchanged */
         1055  +  const char *zPrior        /* Previous filename. NULL if unchanged */
   827   1056   ){
   828   1057     int fnid, pfnid, pid, fid;
   829   1058     static Stmt s1;
   830   1059   
   831   1060     fnid = filename_to_fnid(zFilename);
   832   1061     if( zPrior==0 ){
   833   1062       pfnid = 0;
   834   1063     }else{
   835   1064       pfnid = filename_to_fnid(zPrior);
   836   1065     }
   837         -  if( zFromUuid==0 ){
         1066  +  if( zFromUuid==0 || zFromUuid[0]==0 ){
   838   1067       pid = 0;
   839   1068     }else{
   840   1069       pid = uuid_to_rid(zFromUuid, 1);
   841   1070     }
   842         -  if( zToUuid==0 ){
         1071  +  if( zToUuid==0 || zToUuid[0]==0 ){
   843   1072       fid = 0;
   844   1073     }else{
   845   1074       fid = uuid_to_rid(zToUuid, 1);
   846   1075     }
   847   1076     db_static_prepare(&s1,
   848   1077       "INSERT INTO mlink(mid,pid,fid,fnid,pfnid)"
   849   1078       "VALUES(:m,:p,:f,:n,:pfn)"
................................................................................
   856   1085     db_exec(&s1);
   857   1086     if( pid && fid ){
   858   1087       content_deltify(pid, fid, 0);
   859   1088     }
   860   1089   }
   861   1090   
   862   1091   /*
   863         -** Locate a file named zName in the aFile[] array of the given
   864         -** manifest.  We assume that filenames are in sorted order.
   865         -** Use a binary search.  Return turn the index of the matching
   866         -** entry.  Or return -1 if not found.
         1092  +** Do a binary search to find a file in the p->aFile[] array.  
         1093  +**
         1094  +** As an optimization, guess that the file we seek is at index p->iFile.
         1095  +** That will usually be the case.  If it is not found there, then do the
         1096  +** actual binary search.
         1097  +**
         1098  +** Update p->iFile to be the index of the file that is found.
   867   1099   */
   868         -static int find_file_in_manifest(Manifest *p, const char *zName){
         1100  +static ManifestFile *manifest_file_seek_base(Manifest *p, const char *zName){
   869   1101     int lwr, upr;
   870   1102     int c;
   871   1103     int i;
   872   1104     lwr = 0;
   873   1105     upr = p->nFile - 1;
         1106  +  if( p->iFile>=lwr && p->iFile<upr ){
         1107  +    c = strcmp(p->aFile[p->iFile+1].zName, zName);
         1108  +    if( c==0 ){
         1109  +      return &p->aFile[++p->iFile];
         1110  +    }else if( c>0 ){
         1111  +      upr = p->iFile;
         1112  +    }else{
         1113  +      lwr = p->iFile+1;
         1114  +    }
         1115  +  }
   874   1116     while( lwr<=upr ){
   875   1117       i = (lwr+upr)/2;
   876   1118       c = strcmp(p->aFile[i].zName, zName);
   877   1119       if( c<0 ){
   878   1120         lwr = i+1;
   879   1121       }else if( c>0 ){
   880   1122         upr = i-1;
   881   1123       }else{
   882         -      return i;
         1124  +      p->iFile = i;
         1125  +      return &p->aFile[i];
   883   1126       }
   884   1127     }
   885         -  return -1;
         1128  +  return 0;
         1129  +}
         1130  +
         1131  +/*
         1132  +** Locate a file named zName in the aFile[] array of the given manifest.
         1133  +** Return a pointer to the appropriate ManifestFile object.  Return NULL
         1134  +** if not found.
         1135  +**
         1136  +** This routine works even if p is a delta-manifest.  The pointer 
         1137  +** returned might be to the baseline.
         1138  +**
         1139  +** We assume that filenames are in sorted order and use a binary search.
         1140  +*/
         1141  +ManifestFile *manifest_file_seek(Manifest *p, const char *zName){
         1142  +  ManifestFile *pFile;
         1143  +  
         1144  +  pFile = manifest_file_seek_base(p, zName);
         1145  +  if( pFile && pFile->zUuid==0 ) return 0;
         1146  +  if( pFile==0 && p->zBaseline ){
         1147  +    fetch_baseline(p, 1);
         1148  +    pFile = manifest_file_seek_base(p->pBaseline, zName);
         1149  +  }
         1150  +  return pFile;
         1151  +}
         1152  +
         1153  +/*
         1154  +** This strcmp() function handles NULL arguments.  NULLs sort first.
         1155  +*/
         1156  +static int strcmp_null(const char *zOne, const char *zTwo){
         1157  +  if( zOne==0 ){
         1158  +    if( zTwo==0 ) return 0;
         1159  +    return -1;
         1160  +  }else if( zTwo==0 ){
         1161  +    return +1;
         1162  +  }else{
         1163  +    return strcmp(zOne, zTwo);
         1164  +  }
   886   1165   }
   887   1166   
   888   1167   /*
   889   1168   ** Add mlink table entries associated with manifest cid.  The
   890   1169   ** parent manifest is pid.
   891   1170   **
   892   1171   ** A single mlink entry is added for every file that changed content
................................................................................
   893   1172   ** and/or name going from pid to cid.
   894   1173   **
   895   1174   ** Deleted files have mlink.fid=0.
   896   1175   ** Added files have mlink.pid=0.
   897   1176   ** Edited files have both mlink.pid!=0 and mlink.fid!=0
   898   1177   */
   899   1178   static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){
   900         -  Manifest other;
   901   1179     Blob otherContent;
   902   1180     int otherRid;
   903         -  int i, j;
         1181  +  int i, rc;
         1182  +  ManifestFile *pChildFile, *pParentFile;
         1183  +  Manifest **ppOther;
         1184  +  static Stmt eq;
   904   1185   
   905         -  if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", cid) ){
   906         -    return;
   907         -  }
         1186  +  db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid");
         1187  +  db_bind_int(&eq, ":mid", cid);
         1188  +  rc = db_step(&eq);
         1189  +  db_reset(&eq);
         1190  +  if( rc==SQLITE_ROW ) return;
         1191  +
   908   1192     assert( pParent==0 || pChild==0 );
   909   1193     if( pParent==0 ){
   910         -    pParent = &other;
         1194  +    ppOther = &pParent;
   911   1195       otherRid = pid;
   912   1196     }else{
   913         -    pChild = &other;
         1197  +    ppOther = &pChild;
   914   1198       otherRid = cid;
   915   1199     }
   916         -  if( manifest_cache_find(otherRid, &other)==0 ){
         1200  +  if( (*ppOther = manifest_cache_find(otherRid))==0 ){
   917   1201       content_get(otherRid, &otherContent);
   918   1202       if( blob_size(&otherContent)==0 ) return;
   919         -    if( manifest_parse(&other, &otherContent)==0 ) return;
   920         -  }
   921         -  content_deltify(pid, cid, 0);
   922         -
   923         -  /* Use the iRename fields to find the cross-linkage between
   924         -  ** renamed files.  */
   925         -  for(j=0; j<pChild->nFile; j++){
   926         -    const char *zPrior = pChild->aFile[j].zPrior;
   927         -    if( zPrior && zPrior[0] ){
   928         -      i = find_file_in_manifest(pParent, zPrior);
   929         -      if( i>=0 ){
   930         -        pChild->aFile[j].iRename = i;
   931         -        pParent->aFile[i].iRename = j;
         1203  +    *ppOther = manifest_parse(&otherContent, otherRid);
         1204  +    if( *ppOther==0 ) return;
         1205  +  }
         1206  +  if( fetch_baseline(pParent, 0) || fetch_baseline(pChild, 0) ){
         1207  +    manifest_destroy(*ppOther);
         1208  +    return;
         1209  +  }
         1210  +  if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){
         1211  +    content_deltify(pid, cid, 0); 
         1212  +  }else if( pChild->zBaseline==0 && pParent->zBaseline!=0 ){
         1213  +    content_deltify(pParent->pBaseline->rid, cid, 0);
         1214  +  }
         1215  +  
         1216  +  for(i=0, pChildFile=pChild->aFile; i<pChild->nFile; i++, pChildFile++){
         1217  +    if( pChildFile->zPrior ){
         1218  +       pParentFile = manifest_file_seek(pParent, pChildFile->zPrior);
         1219  +       if( pParentFile ){
         1220  +         add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,
         1221  +                       pChildFile->zName, pChildFile->zPrior);
         1222  +       }
         1223  +    }else{
         1224  +       pParentFile = manifest_file_seek(pParent, pChildFile->zName);
         1225  +       if( pParentFile==0 ){
         1226  +         if( pChildFile->zUuid ){
         1227  +           add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0);
         1228  +         }
         1229  +       }else if( strcmp_null(pChildFile->zUuid, pParentFile->zUuid)!=0 ){
         1230  +         add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,
         1231  +                       pChildFile->zName, 0);
         1232  +       }
         1233  +    }
         1234  +  }
         1235  +  if( pParent->zBaseline && pChild->zBaseline ){
         1236  +    for(i=0, pParentFile=pParent->aFile; i<pParent->nFile; i++, pParentFile++){
         1237  +      if( pParentFile->zUuid ) continue;
         1238  +      pChildFile = manifest_file_seek(pChild, pParentFile->zName);
         1239  +      if( pChildFile ){
         1240  +        add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0);
   932   1241         }
   933   1242       }
   934   1243     }
   935         -
   936         -  /* Construct the mlink entries */
   937         -  for(i=j=0; i<pParent->nFile && j<pChild->nFile; ){
   938         -    int c;
   939         -    if( pParent->aFile[i].iRename>=0 ){
   940         -      i++;
   941         -    }else if( (c = strcmp(pParent->aFile[i].zName, pChild->aFile[j].zName))<0 ){
   942         -      add_one_mlink(cid, pParent->aFile[i].zUuid,0,pParent->aFile[i].zName,0);
   943         -      i++;
   944         -    }else if( c>0 ){
   945         -      int rn = pChild->aFile[j].iRename;
   946         -      if( rn>=0 ){
   947         -        add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, 
   948         -                      pChild->aFile[j].zName, pParent->aFile[rn].zName);
   949         -      }else{
   950         -        add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0);
   951         -      }
   952         -      j++;
   953         -    }else{
   954         -      if( strcmp(pParent->aFile[i].zUuid, pChild->aFile[j].zUuid)!=0 ){
   955         -        add_one_mlink(cid, pParent->aFile[i].zUuid, pChild->aFile[j].zUuid, 
   956         -                      pChild->aFile[j].zName, 0);
   957         -      }
   958         -      i++;
   959         -      j++;
   960         -    }
   961         -  }
   962         -  while( i<pParent->nFile ){
   963         -    if( pParent->aFile[i].iRename<0 ){
   964         -      add_one_mlink(cid, pParent->aFile[i].zUuid, 0, pParent->aFile[i].zName,0);
   965         -    }
   966         -    i++;
   967         -  }
   968         -  while( j<pChild->nFile ){
   969         -    int rn = pChild->aFile[j].iRename;
   970         -    if( rn>=0 ){
   971         -      add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, 
   972         -                    pChild->aFile[j].zName, pParent->aFile[rn].zName);
   973         -    }else{
   974         -      add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0);
   975         -    }
   976         -    j++;
   977         -  }
   978         -  manifest_cache_insert(otherRid, &other);
         1244  +  manifest_cache_insert(*ppOther);
   979   1245   }
   980   1246   
   981   1247   /*
   982   1248   ** True if manifest_crosslink_begin() has been called but
   983   1249   ** manifest_crosslink_end() is still pending.
   984   1250   */
   985   1251   static int manifest_crosslink_busy = 0;
................................................................................
  1112   1378   ** Historical note:  This routine original processed manifests only.
  1113   1379   ** Processing for other control artifacts was added later.  The name
  1114   1380   ** of the routine, "manifest_crosslink", and the name of this source
  1115   1381   ** file, is a legacy of its original use.
  1116   1382   */
  1117   1383   int manifest_crosslink(int rid, Blob *pContent){
  1118   1384     int i;
  1119         -  Manifest m;
         1385  +  Manifest *p;
  1120   1386     Stmt q;
  1121   1387     int parentid = 0;
  1122   1388   
  1123         -  if( manifest_cache_find(rid, &m) ){
         1389  +  if( (p = manifest_cache_find(rid))!=0 ){
  1124   1390       blob_reset(pContent);
  1125         -  }else if( manifest_parse(&m, pContent)==0 ){
         1391  +  }else if( (p = manifest_parse(pContent, rid))==0 ){
         1392  +    return 0;
         1393  +  }
         1394  +  if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
         1395  +    manifest_destroy(p);
  1126   1396       return 0;
  1127   1397     }
  1128         -  if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){
  1129         -    manifest_clear(&m);
         1398  +  if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){
         1399  +    manifest_destroy(p);
  1130   1400       return 0;
  1131   1401     }
  1132   1402     db_begin_transaction();
  1133         -  if( m.type==CFTYPE_MANIFEST ){
         1403  +  if( p->type==CFTYPE_MANIFEST ){
  1134   1404       if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
  1135   1405         char *zCom;
  1136         -      for(i=0; i<m.nParent; i++){
  1137         -        int pid = uuid_to_rid(m.azParent[i], 1);
         1406  +      for(i=0; i<p->nParent; i++){
         1407  +        int pid = uuid_to_rid(p->azParent[i], 1);
  1138   1408           db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
  1139         -                      "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
         1409  +                      "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate);
  1140   1410           if( i==0 ){
  1141         -          add_mlink(pid, 0, rid, &m);
         1411  +          add_mlink(pid, 0, rid, p);
  1142   1412             parentid = pid;
  1143   1413           }
  1144   1414         }
  1145   1415         db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
  1146   1416         while( db_step(&q)==SQLITE_ROW ){
  1147   1417           int cid = db_column_int(&q, 0);
  1148         -        add_mlink(rid, &m, cid, 0);
         1418  +        add_mlink(rid, p, cid, 0);
  1149   1419         }
  1150   1420         db_finalize(&q);
  1151   1421         db_multi_exec(
  1152   1422           "REPLACE INTO event(type,mtime,objid,user,comment,"
  1153   1423                              "bgcolor,euser,ecomment)"
  1154   1424           "VALUES('ci',"
  1155   1425           "  coalesce("
................................................................................
  1156   1426           "    (SELECT julianday(value) FROM tagxref WHERE tagid=%d AND rid=%d),"
  1157   1427           "    %.17g"
  1158   1428           "  ),"
  1159   1429           "  %d,%Q,%Q,"
  1160   1430           "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0),"
  1161   1431           "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
  1162   1432           "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
  1163         -        TAG_DATE, rid, m.rDate,
  1164         -        rid, m.zUser, m.zComment, 
         1433  +        TAG_DATE, rid, p->rDate,
         1434  +        rid, p->zUser, p->zComment, 
  1165   1435           TAG_BGCOLOR, rid,
  1166   1436           TAG_USER, rid,
  1167   1437           TAG_COMMENT, rid
  1168   1438         );
  1169   1439         zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
  1170   1440                           " WHERE rowid=last_insert_rowid()");
  1171         -      wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE);
         1441  +      wiki_extract_links(zCom, rid, 0, p->rDate, 1, WIKI_INLINE);
  1172   1442         free(zCom);
         1443  +
         1444  +      /* If this is a delta-manifest, record the fact that this repository
         1445  +      ** contains delta manifests, to free the "commit" logic to generate
         1446  +      ** new delta manifests.
         1447  +      */
         1448  +      if( p->zBaseline!=0 ){
         1449  +        static int once = 0;
         1450  +        if( !once ){
         1451  +          db_set_int("seen-delta-manifest", 1, 0);
         1452  +          once = 0;
         1453  +        }
         1454  +      }
  1173   1455       }
  1174   1456     }
  1175         -  if( m.type==CFTYPE_CLUSTER ){
  1176         -    tag_insert("cluster", 1, 0, rid, m.rDate, rid);
  1177         -    for(i=0; i<m.nCChild; i++){
         1457  +  if( p->type==CFTYPE_CLUSTER ){
         1458  +    static Stmt del1;
         1459  +    tag_insert("cluster", 1, 0, rid, p->rDate, rid);
         1460  +    db_static_prepare(&del1, "DELETE FROM unclustered WHERE rid=:rid");
         1461  +    for(i=0; i<p->nCChild; i++){
  1178   1462         int mid;
  1179         -      mid = uuid_to_rid(m.azCChild[i], 1);
         1463  +      mid = uuid_to_rid(p->azCChild[i], 1);
  1180   1464         if( mid>0 ){
  1181         -        db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid);
         1465  +        db_bind_int(&del1, ":rid", mid);
         1466  +        db_step(&del1);
         1467  +        db_reset(&del1);
  1182   1468         }
  1183   1469       }
  1184   1470     }
  1185         -  if( m.type==CFTYPE_CONTROL
  1186         -   || m.type==CFTYPE_MANIFEST
  1187         -   || m.type==CFTYPE_EVENT
         1471  +  if( p->type==CFTYPE_CONTROL
         1472  +   || p->type==CFTYPE_MANIFEST
         1473  +   || p->type==CFTYPE_EVENT
  1188   1474     ){
  1189         -    for(i=0; i<m.nTag; i++){
         1475  +    for(i=0; i<p->nTag; i++){
  1190   1476         int tid;
  1191   1477         int type;
  1192         -      if( m.aTag[i].zUuid ){
  1193         -        tid = uuid_to_rid(m.aTag[i].zUuid, 1);
         1478  +      if( p->aTag[i].zUuid ){
         1479  +        tid = uuid_to_rid(p->aTag[i].zUuid, 1);
  1194   1480         }else{
  1195   1481           tid = rid;
  1196   1482         }
  1197   1483         if( tid ){
  1198         -        switch( m.aTag[i].zName[0] ){
         1484  +        switch( p->aTag[i].zName[0] ){
  1199   1485             case '-':  type = 0;  break;  /* Cancel prior occurances */
  1200   1486             case '+':  type = 1;  break;  /* Apply to target only */
  1201   1487             case '*':  type = 2;  break;  /* Propagate to descendants */
  1202   1488             default:
  1203         -            fossil_fatal("unknown tag type in manifest: %s", m.aTag);
         1489  +            fossil_fatal("unknown tag type in manifest: %s", p->aTag);
  1204   1490               return 0;
  1205   1491           }
  1206         -        tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue, 
  1207         -                   rid, m.rDate, tid);
         1492  +        tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, 
         1493  +                   rid, p->rDate, tid);
  1208   1494         }
  1209   1495       }
  1210   1496       if( parentid ){
  1211   1497         tag_propagate_all(parentid);
  1212   1498       }
  1213   1499     }
  1214         -  if( m.type==CFTYPE_WIKI ){
  1215         -    char *zTag = mprintf("wiki-%s", m.zWikiTitle);
         1500  +  if( p->type==CFTYPE_WIKI ){
         1501  +    char *zTag = mprintf("wiki-%s", p->zWikiTitle);
  1216   1502       int tagid = tag_findid(zTag, 1);
  1217   1503       int prior;
  1218   1504       char *zComment;
  1219   1505       int nWiki;
  1220   1506       char zLength[40];
  1221         -    while( fossil_isspace(m.zWiki[0]) ) m.zWiki++;
  1222         -    nWiki = strlen(m.zWiki);
         1507  +    while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
         1508  +    nWiki = strlen(p->zWiki);
  1223   1509       sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
  1224         -    tag_insert(zTag, 1, zLength, rid, m.rDate, rid);
         1510  +    tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
  1225   1511       free(zTag);
  1226   1512       prior = db_int(0,
  1227   1513         "SELECT rid FROM tagxref"
  1228   1514         " WHERE tagid=%d AND mtime<%.17g"
  1229   1515         " ORDER BY mtime DESC",
  1230         -      tagid, m.rDate
         1516  +      tagid, p->rDate
  1231   1517       );
  1232   1518       if( prior ){
  1233   1519         content_deltify(prior, rid, 0);
  1234   1520       }
  1235   1521       if( nWiki>0 ){
  1236         -      zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
         1522  +      zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle);
  1237   1523       }else{
  1238         -      zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle);
         1524  +      zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle);
  1239   1525       }
  1240   1526       db_multi_exec(
  1241   1527         "REPLACE INTO event(type,mtime,objid,user,comment,"
  1242   1528         "                  bgcolor,euser,ecomment)"
  1243   1529         "VALUES('w',%.17g,%d,%Q,%Q,"
  1244   1530         "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
  1245   1531         "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
  1246   1532         "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
  1247         -      m.rDate, rid, m.zUser, zComment, 
         1533  +      p->rDate, rid, p->zUser, zComment, 
  1248   1534         TAG_BGCOLOR, rid,
  1249   1535         TAG_BGCOLOR, rid,
  1250   1536         TAG_USER, rid,
  1251   1537         TAG_COMMENT, rid
  1252   1538       );
  1253   1539       free(zComment);
  1254   1540     }
  1255         -  if( m.type==CFTYPE_EVENT ){
  1256         -    char *zTag = mprintf("event-%s", m.zEventId);
         1541  +  if( p->type==CFTYPE_EVENT ){
         1542  +    char *zTag = mprintf("event-%s", p->zEventId);
  1257   1543       int tagid = tag_findid(zTag, 1);
  1258   1544       int prior, subsequent;
  1259   1545       int nWiki;
  1260   1546       char zLength[40];
  1261         -    while( fossil_isspace(m.zWiki[0]) ) m.zWiki++;
  1262         -    nWiki = strlen(m.zWiki);
         1547  +    while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
         1548  +    nWiki = strlen(p->zWiki);
  1263   1549       sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
  1264         -    tag_insert(zTag, 1, zLength, rid, m.rDate, rid);
         1550  +    tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
  1265   1551       free(zTag);
  1266   1552       prior = db_int(0,
  1267   1553         "SELECT rid FROM tagxref"
  1268   1554         " WHERE tagid=%d AND mtime<%.17g"
  1269   1555         " ORDER BY mtime DESC",
  1270         -      tagid, m.rDate
         1556  +      tagid, p->rDate
  1271   1557       );
  1272   1558       if( prior ){
  1273   1559         content_deltify(prior, rid, 0);
  1274   1560         db_multi_exec(
  1275   1561           "DELETE FROM event"
  1276   1562           " WHERE type='e'"
  1277   1563           "   AND tagid=%d"
................................................................................
  1279   1565           tagid, tagid
  1280   1566         );
  1281   1567       }
  1282   1568       subsequent = db_int(0,
  1283   1569         "SELECT rid FROM tagxref"
  1284   1570         " WHERE tagid=%d AND mtime>%.17g"
  1285   1571         " ORDER BY mtime",
  1286         -      tagid, m.rDate
         1572  +      tagid, p->rDate
  1287   1573       );
  1288   1574       if( subsequent ){
  1289   1575         content_deltify(rid, subsequent, 0);
  1290   1576       }else{
  1291   1577         db_multi_exec(
  1292   1578           "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)"
  1293   1579           "VALUES('e',%.17g,%d,%d,%Q,%Q,"
  1294   1580           "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
  1295         -        m.rEventDate, rid, tagid, m.zUser, m.zComment, 
         1581  +        p->rEventDate, rid, tagid, p->zUser, p->zComment, 
  1296   1582           TAG_BGCOLOR, rid
  1297   1583         );
  1298   1584       }
  1299   1585     }
  1300         -  if( m.type==CFTYPE_TICKET ){
         1586  +  if( p->type==CFTYPE_TICKET ){
  1301   1587       char *zTag;
  1302   1588   
  1303   1589       assert( manifest_crosslink_busy==1 );
  1304         -    zTag = mprintf("tkt-%s", m.zTicketUuid);
  1305         -    tag_insert(zTag, 1, 0, rid, m.rDate, rid);
         1590  +    zTag = mprintf("tkt-%s", p->zTicketUuid);
         1591  +    tag_insert(zTag, 1, 0, rid, p->rDate, rid);
  1306   1592       free(zTag);
  1307   1593       db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
  1308         -                  m.zTicketUuid);
         1594  +                  p->zTicketUuid);
  1309   1595     }
  1310         -  if( m.type==CFTYPE_ATTACHMENT ){
         1596  +  if( p->type==CFTYPE_ATTACHMENT ){
  1311   1597       db_multi_exec(
  1312   1598          "INSERT INTO attachment(attachid, mtime, src, target,"
  1313   1599                                           "filename, comment, user)"
  1314   1600          "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
  1315         -       rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName,
  1316         -       (m.zComment ? m.zComment : ""), m.zUser
         1601  +       rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
         1602  +       (p->zComment ? p->zComment : ""), p->zUser
  1317   1603       );
  1318   1604       db_multi_exec(
  1319   1605          "UPDATE attachment SET isLatest = (mtime=="
  1320   1606             "(SELECT max(mtime) FROM attachment"
  1321   1607             "  WHERE target=%Q AND filename=%Q))"
  1322   1608          " WHERE target=%Q AND filename=%Q",
  1323         -       m.zAttachTarget, m.zAttachName,
  1324         -       m.zAttachTarget, m.zAttachName
         1609  +       p->zAttachTarget, p->zAttachName,
         1610  +       p->zAttachTarget, p->zAttachName
  1325   1611       );
  1326         -    if( strlen(m.zAttachTarget)!=UUID_SIZE
  1327         -     || !validate16(m.zAttachTarget, UUID_SIZE) 
         1612  +    if( strlen(p->zAttachTarget)!=UUID_SIZE
         1613  +     || !validate16(p->zAttachTarget, UUID_SIZE) 
  1328   1614       ){
  1329   1615         char *zComment;
  1330         -      if( m.zAttachSrc && m.zAttachSrc[0] ){
         1616  +      if( p->zAttachSrc && p->zAttachSrc[0] ){
  1331   1617           zComment = mprintf("Add attachment \"%h\" to wiki page [%h]",
  1332         -             m.zAttachName, m.zAttachTarget);
         1618  +             p->zAttachName, p->zAttachTarget);
  1333   1619         }else{
  1334   1620           zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
  1335         -             m.zAttachName, m.zAttachTarget);
         1621  +             p->zAttachName, p->zAttachTarget);
  1336   1622         }
  1337   1623         db_multi_exec(
  1338   1624           "REPLACE INTO event(type,mtime,objid,user,comment)"
  1339   1625           "VALUES('w',%.17g,%d,%Q,%Q)",
  1340         -        m.rDate, rid, m.zUser, zComment
         1626  +        p->rDate, rid, p->zUser, zComment
  1341   1627         );
  1342   1628         free(zComment);
  1343   1629       }else{
  1344   1630         char *zComment;
  1345         -      if( m.zAttachSrc && m.zAttachSrc[0] ){
         1631  +      if( p->zAttachSrc && p->zAttachSrc[0] ){
  1346   1632           zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]",
  1347         -             m.zAttachName, m.zAttachTarget);
         1633  +             p->zAttachName, p->zAttachTarget);
  1348   1634         }else{
  1349   1635           zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
  1350         -             m.zAttachName, m.zAttachTarget);
         1636  +             p->zAttachName, p->zAttachTarget);
  1351   1637         }
  1352   1638         db_multi_exec(
  1353   1639           "REPLACE INTO event(type,mtime,objid,user,comment)"
  1354   1640           "VALUES('t',%.17g,%d,%Q,%Q)",
  1355         -        m.rDate, rid, m.zUser, zComment
         1641  +        p->rDate, rid, p->zUser, zComment
  1356   1642         );
  1357   1643         free(zComment);
  1358   1644       }
  1359   1645     }
  1360   1646     db_end_transaction(0);
  1361         -  if( m.type==CFTYPE_MANIFEST ){
  1362         -    manifest_cache_insert(rid, &m);
         1647  +  if( p->type==CFTYPE_MANIFEST ){
         1648  +    manifest_cache_insert(p);
  1363   1649     }else{
  1364         -    manifest_clear(&m);
         1650  +    manifest_destroy(p);
  1365   1651     }
  1366   1652     return 1;
  1367   1653   }
  1368         -
  1369         -/*
  1370         -** Given a checkin name, load and parse the manifest for that checkin.
  1371         -** Throw a fatal error if anything goes wrong.
  1372         -*/
  1373         -void manifest_from_name(
  1374         -  const char *zName,
  1375         -  Manifest *pM
  1376         -){
  1377         -  int rid;
  1378         -  Blob content;
  1379         -
  1380         -  rid = name_to_rid(zName);
  1381         -  if( !is_a_version(rid) ){
  1382         -    fossil_fatal("no such checkin: %s", zName);
  1383         -  }
  1384         -  content_get(rid, &content);
  1385         -  if( !manifest_parse(pM, &content) ){
  1386         -    fossil_fatal("cannot parse manifest for checkin: %s", zName);
  1387         -  }
  1388         -}

Changes to src/md5.c.

    39     39     int isInit;
    40     40     uint32 buf[4];
    41     41     uint32 bits[2];
    42     42     unsigned char in[64];
    43     43   };
    44     44   typedef struct Context MD5Context;
    45     45   
           46  +#if defined(__i386__) || defined(__x86_64__) || defined(_WIN32)
           47  +# define byteReverse(A,B)
           48  +#else
    46     49   /*
    47         - * Note: this code is harmless on little-endian machines.
           50  + * Convert an array of integers to little-endian.
           51  + * Note: this code is a no-op on little-endian machines.
    48     52    */
    49     53   static void byteReverse (unsigned char *buf, unsigned longs){
    50     54           uint32 t;
    51     55           do {
    52     56                   t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
    53     57                               ((unsigned)buf[1]<<8 | buf[0]);
    54     58                   *(uint32 *)buf = t;
    55     59                   buf += 4;
    56     60           } while (--longs);
    57     61   }
           62  +#endif
           63  +
    58     64   /* The four core functions - F1 is optimized somewhat */
    59     65   
    60     66   /* #define F1(x, y, z) (x & y | ~x & z) */
    61     67   #define F1(x, y, z) (z ^ (x & (y ^ z)))
    62     68   #define F2(x, y, z) F1(z, x, y)
    63     69   #define F3(x, y, z) (x ^ y ^ z)
    64     70   #define F4(x, y, z) (y ^ (x | ~z))
................................................................................
   312    318   
   313    319   /*
   314    320   ** Add the content of a blob to the incremental MD5 checksum.
   315    321   */
   316    322   void md5sum_step_blob(Blob *p){
   317    323     md5sum_step_text(blob_buffer(p), blob_size(p));
   318    324   }
          325  +
          326  +/*
          327  +** For trouble-shooting only:
          328  +**
          329  +** Report the current state of the incremental checksum.
          330  +*/
          331  +const char *md5sum_current_state(void){
          332  +  unsigned int cksum = 0;
          333  +  unsigned int *pFirst, *pLast;
          334  +  static char zResult[12];
          335  +
          336  +  pFirst = (unsigned int*)&incrCtx;
          337  +  pLast = (unsigned int*)((&incrCtx)+1);
          338  +  while( pFirst<pLast ){
          339  +    cksum += *pFirst;
          340  +    pFirst++;
          341  +  }
          342  +  sqlite3_snprintf(sizeof(zResult), zResult, "%08x", cksum);
          343  +  return zResult;
          344  +}
   319    345   
   320    346   /*
   321    347   ** Finish the incremental MD5 checksum.  Store the result in blob pOut
   322    348   ** if pOut!=0.  Also return a pointer to the result.  
   323    349   **
   324    350   ** This resets the incremental checksum preparing for the next round
   325    351   ** of computation.  The return pointer points to a static buffer that

Changes to src/rebuild.c.

    80     80   static int totalSize;       /* Total number of artifacts to process */
    81     81   static int processCnt;      /* Number processed so far */
    82     82   static int ttyOutput;       /* Do progress output */
    83     83   static Bag bagDone;         /* Bag of records rebuilt */
    84     84   
    85     85   static char *zFNameFormat;  /* Format string for filenames on deconstruct */
    86     86   static int prefixLength;    /* Length of directory prefix for deconstruct */
           87  +
           88  +
           89  +/*
           90  +** Draw the percent-complete message.
           91  +** The input is actually the permill complete.
           92  +*/
           93  +static void percent_complete(int permill){
           94  +  static int lastOutput = -1;
           95  +  if( permill>lastOutput ){
           96  +    printf("  %d.%d%% complete...\r", permill/10, permill%10);
           97  +    fflush(stdout);
           98  +    lastOutput = permill;
           99  +  }
          100  +}
          101  +
    87    102   
    88    103   /*
    89    104   ** Called after each artifact is processed
    90    105   */
    91    106   static void rebuild_step_done(rid){
    92    107     /* assert( bag_find(&bagDone, rid)==0 ); */
    93    108     bag_insert(&bagDone, rid);
    94    109     if( ttyOutput ){
    95    110       processCnt++;
    96    111       if (!g.fQuiet) {
    97         -      printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize));
    98         -      fflush(stdout);
          112  +      percent_complete((processCnt*1000)/totalSize);
    99    113       }
   100    114     }
   101    115   }
   102    116   
   103    117   /*
   104    118   ** Rebuild cross-referencing information for the artifact
   105    119   ** rid with content pBase and all of its descendants.  This
................................................................................
   165    179       }
   166    180       blob_reset(pUse);
   167    181       rebuild_step_done(rid);
   168    182     
   169    183       /* Call all children recursively */
   170    184       rid = 0;
   171    185       for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){
   172         -      Stmt q2;
          186  +      static Stmt q2;
   173    187         int sz;
   174         -      db_prepare(&q2, "SELECT content, size FROM blob WHERE rid=%d", cid);
          188  +      db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid");
          189  +      db_bind_int(&q2, ":rid", cid);
   175    190         if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){
   176    191           Blob delta, next;
   177    192           db_ephemeral_blob(&q2, 0, &delta);
   178    193           blob_uncompress(&delta, &delta);
   179    194           blob_delta_apply(pBase, &delta, &next);
   180    195           blob_reset(&delta);
   181         -        db_finalize(&q2);
          196  +        db_reset(&q2);
   182    197           if( i<nChild ){
   183    198             rebuild_step(cid, sz, &next);
   184    199           }else{
   185    200             /* Tail recursion */
   186    201             rid = cid;
   187    202             size = sz;
   188    203             blob_reset(pBase);
   189    204             *pBase = next;
   190    205           }
   191    206         }else{
   192         -        db_finalize(&q2);
          207  +        db_reset(&q2);
   193    208           blob_reset(pBase);
   194    209         }
   195    210       }
   196    211       bag_clear(&children);
   197    212     }
   198    213   }
   199    214   
................................................................................
   230    245   ** ability of fossil to accept records in any order and still
   231    246   ** construct a sane repository.
   232    247   */
   233    248   int rebuild_db(int randomize, int doOut){
   234    249     Stmt s;
   235    250     int errCnt = 0;
   236    251     char *zTable;
          252  +  int incrSize;
   237    253   
   238    254     bag_init(&bagDone);
   239    255     ttyOutput = doOut;
   240    256     processCnt = 0;
   241    257     if (!g.fQuiet) {
   242         -    printf("0 (0%%)...\r");
   243         -    fflush(stdout);
          258  +    percent_complete(0);
   244    259     }
   245    260     db_multi_exec(zSchemaUpdates);
   246    261     for(;;){
   247    262       zTable = db_text(0,
   248    263          "SELECT name FROM sqlite_master /*scan*/"
   249    264          " WHERE type='table'"
   250    265          " AND name NOT IN ('blob','delta','rcvfrom','user',"
................................................................................
   268    283        "DELETE FROM unclustered"
   269    284        " WHERE rid IN (SELECT rid FROM shun JOIN blob USING(uuid))"
   270    285     );
   271    286     db_multi_exec(
   272    287       "DELETE FROM config WHERE name IN ('remote-code', 'remote-maxid')"
   273    288     );
   274    289     totalSize = db_int(0, "SELECT count(*) FROM blob");
          290  +  incrSize = totalSize/100;
          291  +  totalSize += incrSize*2;
   275    292     db_prepare(&s,
   276    293        "SELECT rid, size FROM blob /*scan*/"
   277    294        " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
   278    295        "   AND NOT EXISTS(SELECT 1 FROM delta WHERE rid=blob.rid)"
   279    296     );
   280    297     manifest_crosslink_begin();
   281    298     while( db_step(&s)==SQLITE_ROW ){
................................................................................
   305    322         db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
   306    323         rebuild_step_done(rid);
   307    324       }
   308    325     }
   309    326     db_finalize(&s);
   310    327     manifest_crosslink_end();
   311    328     rebuild_tag_trunk();
          329  +  if (!g.fQuiet) {
          330  +    processCnt += incrSize;
          331  +    percent_complete((processCnt*1000)/totalSize);
          332  +  }
          333  +  create_cluster();
          334  +  if (!g.fQuiet) {
          335  +    processCnt += incrSize;
          336  +    percent_complete((processCnt*1000)/totalSize);
          337  +  }
   312    338     if(!g.fQuiet && ttyOutput ){
   313    339       printf("\n");
   314    340     }
   315    341     return errCnt;
   316    342   }
   317    343   
   318    344   /*
................................................................................
   369    395       "UPDATE config SET value=lower(hex(randomblob(20)))"
   370    396       " WHERE name='project-code';"
   371    397       "UPDATE config SET value='detached-' || value"
   372    398       " WHERE name='project-name' AND value NOT GLOB 'detached-*';"
   373    399     );
   374    400     db_end_transaction(0);
   375    401   }
          402  +
          403  +/*
          404  +** COMMAND:  test-create-clusters
          405  +**
          406  +** Create clusters for all unclustered artifacts if the number of unclustered
          407  +** artifacts exceeds the current clustering threshold.
          408  +*/
          409  +void test_createcluster_cmd(void){
          410  +  if( g.argc==3 ){
          411  +    db_open_repository(g.argv[2]);
          412  +  }else{
          413  +    db_find_and_open_repository(1);
          414  +    if( g.argc!=2 ){
          415  +      usage("?REPOSITORY-FILENAME?");
          416  +    }
          417  +    db_close();
          418  +    db_open_repository(g.zRepositoryName);
          419  +  }
          420  +  db_begin_transaction();
          421  +  create_cluster();
          422  +  db_end_transaction(0);
          423  +}
   376    424   
   377    425   /*
   378    426   ** COMMAND: scrub
   379    427   ** %fossil scrub [--verily] [--force] [REPOSITORY]
   380    428   **
   381    429   ** The command removes sensitive information (such as passwords) from a
   382    430   ** repository so that the respository can be sent to an untrusted reader.

Changes to src/schema.c.

   239    239   @
   240    240   @ -- A record of phantoms.  A phantom is a record for which we know the
   241    241   @ -- UUID but we do not (yet) know the file content.
   242    242   @ --
   243    243   @ CREATE TABLE phantom(
   244    244   @   rid INTEGER PRIMARY KEY         -- Record ID of the phantom
   245    245   @ );
          246  +@
          247  +@ -- A record of orphaned delta-manifests.  An orphan is a delta-manifest
          248  +@ -- for which we have content, but its baseline-manifest is a phantom.
          249  +@ -- We have to track all orphan maniftests so that when the baseline arrives,
          250  +@ -- we know to process the orphaned deltas.
          251  +@ CREATE TABLE orphan(
          252  +@   rid INTEGER PRIMARY KEY,        -- Delta manifest with a phantom baseline
          253  +@   baseline INTEGER                -- Phantom baseline of this orphan
          254  +@ );
          255  +@ CREATE INDEX orphan_baseline ON orphan(baseline);
   246    256   @
   247    257   @ -- Unclustered records.  An unclustered record is a record (including
   248    258   @ -- a cluster records themselves) that is not mentioned by some other
   249    259   @ -- cluster.
   250    260   @ --
   251    261   @ -- Phantoms are usually included in the unclustered table.  A new cluster
   252    262   @ -- will never be created that contains a phantom.  But another repository

Changes to src/sha1.c.

     1      1   /*
     2         -** This implementation of SHA1 is adapted from the example implementation
     3         -** contained in RFC-3174.
            2  +** This implementation of SHA1.
     4      3   */
     5         -/*
     6         - * If you do not have the ISO standard stdint.h header file, then you
     7         - * must typdef the following:
     8         - *    name              meaning
     9         - *  */
    10         -#if defined(__DMC__) || defined(_MSC_VER)
    11         -  typedef  unsigned long uint32_t; //unsigned 32 bit integer
    12         -  typedef  unsigned char  uint8_t; //unsigned  8 bit integer (i.e., unsigned char)
    13         -#else
    14         -#  include <stdint.h>
    15         -#endif
    16      4   #include <sys/types.h>
    17      5   #include "config.h"
    18      6   #include "sha1.h"
    19      7   
    20         -#define SHA1HashSize 20
    21         -#define shaSuccess 0
    22         -#define shaInputTooLong 1
    23         -#define shaStateError 2
    24      8   
    25      9   /*
    26         - *  This structure will hold context information for the SHA-1
    27         - *  hashing operation
    28         - */
           10  +** The SHA1 implementation below is adapted from:
           11  +**
           12  +**  $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $
           13  +**  $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $
           14  +**
           15  +** SHA-1 in C
           16  +** By Steve Reid <steve@edmweb.com>
           17  +** 100% Public Domain
           18  +*/
    29     19   typedef struct SHA1Context SHA1Context;
    30     20   struct SHA1Context {
    31         -    uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest  */
    32         -
    33         -    uint32_t Length_Low;            /* Message length in bits      */
    34         -    uint32_t Length_High;           /* Message length in bits      */
    35         -
    36         -    int Message_Block_Index;   /* Index into message block array   */
    37         -    uint8_t Message_Block[64];      /* 512-bit message blocks      */
    38         -
    39         -    int Computed;               /* Is the digest computed?         */
    40         -    int Corrupted;             /* Is the message digest corrupted? */
           21  +  unsigned int state[5];
           22  +  unsigned int count[2];
           23  +  unsigned char buffer[64];
    41     24   };
    42     25   
    43     26   /*
    44         - *  sha1.c
    45         - *
    46         - *  Description:
    47         - *      This file implements the Secure Hashing Algorithm 1 as
    48         - *      defined in FIPS PUB 180-1 published April 17, 1995.
           27  + * blk0() and blk() perform the initial expand.
           28  + * I got the idea of expanding during the round function from SSLeay
    49     29    *
    50         - *      The SHA-1, produces a 160-bit message digest for a given
    51         - *      data stream.  It should take about 2**n steps to find a
    52         - *      message with the same digest as a given message and
    53         - *      2**(n/2) to find any two messages with the same digest,
    54         - *      when n is the digest size in bits.  Therefore, this
    55         - *      algorithm can serve as a means of providing a
    56         - *      "fingerprint" for a message.
           30  + * blk0le() for little-endian and blk0be() for big-endian.
           31  + */
           32  +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
           33  +#define blk0le(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
           34  +    |(rol(block->l[i],8)&0x00FF00FF))
           35  +#define blk0be(i) block->l[i]
           36  +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
           37  +    ^block->l[(i+2)&15]^block->l[i&15],1))
           38  +
           39  +/*
           40  + * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1
    57     41    *
    58         - *  Portability Issues:
    59         - *      SHA-1 is defined in terms of 32-bit "words".  This code
    60         - *      uses <stdint.h> (included via "sha1.h" to define 32 and 8
    61         - *      bit unsigned integer types.  If your C compiler does not
    62         - *      support 32 bit unsigned integers, this code is not
    63         - *      appropriate.
    64         - *
    65         - *  Caveats:
    66         - *      SHA-1 is designed to work with messages less than 2^64 bits
    67         - *      long.  Although SHA-1 allows a message digest to be generated
    68         - *      for messages of any number of bits less than 2^64, this
    69         - *      implementation only works with messages with a length that is
    70         - *      a multiple of the size of an 8-bit character.
    71         - *
           42  + * Rl0() for little-endian and Rb0() for big-endian.  Endianness is 
           43  + * determined at run-time.
           44  + */
           45  +#define Rl0(v,w,x,y,z,i) \
           46  +    z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=rol(w,30);
           47  +#define Rb0(v,w,x,y,z,i) \
           48  +    z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=rol(w,30);
           49  +#define R1(v,w,x,y,z,i) \
           50  +    z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
           51  +#define R2(v,w,x,y,z,i) \
           52  +    z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
           53  +#define R3(v,w,x,y,z,i) \
           54  +    z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
           55  +#define R4(v,w,x,y,z,i) \
           56  +    z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
           57  +
           58  +typedef union {
           59  +    unsigned char c[64];
           60  +    unsigned int l[16];
           61  +} CHAR64LONG16;
           62  +
           63  +/*
           64  + * Hash a single 512-bit block. This is the core of the algorithm.
    72     65    */
    73         -
    74         -/*
    75         - *  Define the SHA1 circular left shift macro
    76         - */
    77         -#define SHA1CircularShift(bits,word) \
    78         -                (((word) << (bits)) | ((word) >> (32-(bits))))
    79         -
    80         -/* Local Function Prototyptes */
    81         -static void SHA1PadMessage(SHA1Context *);
    82         -static void SHA1ProcessMessageBlock(SHA1Context *);
    83         -
    84         -/*
    85         - *  SHA1Reset
    86         - *
    87         - *  Description:
    88         - *      This function will initialize the SHA1Context in preparation
    89         - *      for computing a new SHA1 message digest.
    90         - *
    91         - *  Parameters:
    92         - *      context: [in/out]
    93         - *          The context to reset.
    94         - *
    95         - *  Returns:
    96         - *      sha Error Code.
    97         - *
    98         - */
    99         -static int SHA1Reset(SHA1Context *context)
           66  +void SHA1Transform(unsigned int state[5], const unsigned char buffer[64])
   100     67   {
   101         -    context->Length_Low             = 0;
   102         -    context->Length_High            = 0;
   103         -    context->Message_Block_Index    = 0;
   104         -
   105         -    context->Intermediate_Hash[0]   = 0x67452301;
   106         -    context->Intermediate_Hash[1]   = 0xEFCDAB89;
   107         -    context->Intermediate_Hash[2]   = 0x98BADCFE;
   108         -    context->Intermediate_Hash[3]   = 0x10325476;
   109         -    context->Intermediate_Hash[4]   = 0xC3D2E1F0;
   110         -
   111         -    context->Computed   = 0;
   112         -    context->Corrupted  = 0;
   113         -
   114         -    return shaSuccess;
   115         -}
   116         -
   117         -/*
   118         - *  SHA1Result
   119         - *
   120         - *  Description:
   121         - *      This function will return the 160-bit message digest into the
   122         - *      Message_Digest array  provided by the caller.
   123         - *      NOTE: The first octet of hash is stored in the 0th element,
   124         - *            the last octet of hash in the 19th element.
   125         - *
   126         - *  Parameters:
   127         - *      context: [in/out]
   128         - *          The context to use to calculate the SHA-1 hash.
   129         - *      Message_Digest: [out]
   130         - *          Where the digest is returned.
   131         - *
   132         - *  Returns:
   133         - *      sha Error Code.
   134         - *
           68  +  unsigned int a, b, c, d, e;
           69  +  CHAR64LONG16 *block;
           70  +  static int one = 1;
           71  +  CHAR64LONG16 workspace;
           72  +
           73  +  block = &workspace;
           74  +  (void)memcpy(block, buffer, 64);
           75  +
           76  +  /* Copy context->state[] to working vars */
           77  +  a = state[0];
           78  +  b = state[1];
           79  +  c = state[2];
           80  +  d = state[3];
           81  +  e = state[4];
           82  +
           83  +  /* 4 rounds of 20 operations each. Loop unrolled. */
           84  +  if( 1 == *(unsigned char*)&one ){
           85  +    Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3);
           86  +    Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7);
           87  +    Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11);
           88  +    Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15);
           89  +  }else{
           90  +    Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3);
           91  +    Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7);
           92  +    Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11);
           93  +    Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15);
           94  +  }
           95  +  R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
           96  +  R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
           97  +  R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
           98  +  R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
           99  +  R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
          100  +  R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
          101  +  R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
          102  +  R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
          103  +  R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
          104  +  R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
          105  +  R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
          106  +  R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
          107  +  R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
          108  +  R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
          109  +  R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
          110  +  R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
          111  +
          112  +  /* Add the working vars back into context.state[] */
          113  +  state[0] += a;
          114  +  state[1] += b;
          115  +  state[2] += c;
          116  +  state[3] += d;
          117  +  state[4] += e;
          118  +
          119  +  /* Wipe variables */
          120  +  a = b = c = d = e = 0;
          121  +}
          122  +
          123  +
          124  +/*
          125  + * SHA1Init - Initialize new context
   135    126    */
   136         -static int SHA1Result( SHA1Context *context,
   137         -                uint8_t Message_Digest[SHA1HashSize])
   138         -{
   139         -    int i;
   140         -
   141         -    if (context->Corrupted)
   142         -    {
   143         -        return context->Corrupted;
   144         -    }
   145         -
   146         -    if (!context->Computed)
   147         -    {
   148         -        SHA1PadMessage(context);
   149         -        for(i=0; i<64; ++i)
   150         -        {
   151         -            /* message may be sensitive, clear it out */
   152         -            context->Message_Block[i] = 0;
   153         -        }
   154         -        context->Length_Low = 0;    /* and clear length */
   155         -        context->Length_High = 0;
   156         -        context->Computed = 1;
   157         -
   158         -    }
   159         -
   160         -    for(i = 0; i < SHA1HashSize; ++i)
   161         -    {
   162         -        Message_Digest[i] = context->Intermediate_Hash[i>>2]
   163         -                            >> 8 * ( 3 - ( i & 0x03 ) );
   164         -    }
   165         -
   166         -    return shaSuccess;
          127  +static void SHA1Init(SHA1Context *context){
          128  +    /* SHA1 initialization constants */
          129  +    context->state[0] = 0x67452301;
          130  +    context->state[1] = 0xEFCDAB89;
          131  +    context->state[2] = 0x98BADCFE;
          132  +    context->state[3] = 0x10325476;
          133  +    context->state[4] = 0xC3D2E1F0;
          134  +    context->count[0] = context->count[1] = 0;
   167    135   }
          136  +
   168    137   
   169    138   /*
   170         - *  SHA1Input
   171         - *
   172         - *  Description:
   173         - *      This function accepts an array of octets as the next portion
   174         - *      of the message.
   175         - *
   176         - *  Parameters:
   177         - *      context: [in/out]
   178         - *          The SHA context to update
   179         - *      message_array: [in]
   180         - *          An array of characters representing the next portion of
   181         - *          the message.
   182         - *      length: [in]
   183         - *          The length of the message in message_array
   184         - *
   185         - *  Returns:
   186         - *      sha Error Code.
   187         - *
          139  + * Run your data through this.
   188    140    */
   189         -static
   190         -int SHA1Input(    SHA1Context    *context,
   191         -                  const uint8_t  *message_array,
   192         -                  unsigned       length)
   193         -{
   194         -    if (!length)
   195         -    {
   196         -        return shaSuccess;
          141  +static void SHA1Update(
          142  +  SHA1Context *context,
          143  +  const unsigned char *data,
          144  +  unsigned int len
          145  +){
          146  +    unsigned int i, j;
          147  +
          148  +    j = context->count[0];
          149  +    if ((context->count[0] += len << 3) < j)
          150  +	context->count[1] += (len>>29)+1;
          151  +    j = (j >> 3) & 63;
          152  +    if ((j + len) > 63) {
          153  +	(void)memcpy(&context->buffer[j], data, (i = 64-j));
          154  +	SHA1Transform(context->state, context->buffer);
          155  +	for ( ; i + 63 < len; i += 64)
          156  +	    SHA1Transform(context->state, &data[i]);
          157  +	j = 0;
          158  +    } else {
          159  +	i = 0;
   197    160       }
   198         -
   199         -    if (context->Computed)
   200         -    {
   201         -        context->Corrupted = shaStateError;
   202         -
   203         -        return shaStateError;
   204         -    }
   205         -
   206         -    if (context->Corrupted)
   207         -    {
   208         -         return context->Corrupted;
   209         -    }
   210         -    while(length-- && !context->Corrupted)
   211         -    {
   212         -    context->Message_Block[context->Message_Block_Index++] =
   213         -                    (*message_array & 0xFF);
   214         -
   215         -    context->Length_Low += 8;
   216         -    if (context->Length_Low == 0)
   217         -    {
   218         -        context->Length_High++;
   219         -        if (context->Length_High == 0)
   220         -        {
   221         -            /* Message is too long */
   222         -            context->Corrupted = 1;
   223         -        }
   224         -    }
   225         -
   226         -    if (context->Message_Block_Index == 64)
   227         -    {
   228         -        SHA1ProcessMessageBlock(context);
   229         -    }
   230         -
   231         -    message_array++;
   232         -    }
   233         -
   234         -    return shaSuccess;
          161  +    (void)memcpy(&context->buffer[j], &data[i], len - i);
   235    162   }
   236    163   
   237         -/*
   238         - *  SHA1ProcessMessageBlock
   239         - *
   240         - *  Description:
   241         - *      This function will process the next 512 bits of the message
   242         - *      stored in the Message_Block array.
   243         - *
   244         - *  Parameters:
   245         - *      None.
   246         - *
   247         - *  Returns:
   248         - *      Nothing.
   249         - *
   250         - *  Comments:
   251         - *      Many of the variable names in this code, especially the
   252         - *      single character names, were used because those were the
   253         - *      names used in the publication.
   254         - *
   255         - *
   256         - */
   257         -static void SHA1ProcessMessageBlock(SHA1Context *context)
   258         -{
   259         -    const uint32_t K[] =    {       /* Constants defined in SHA-1   */
   260         -                            0x5A827999,
   261         -                            0x6ED9EBA1,
   262         -                            0x8F1BBCDC,
   263         -                            0xCA62C1D6
   264         -                            };
   265         -    int           t;                 /* Loop counter                */
   266         -    uint32_t      temp;              /* Temporary word value        */
   267         -    uint32_t      W[80];             /* Word sequence               */
   268         -    uint32_t      A, B, C, D, E;     /* Word buffers                */
   269         -
   270         -    /*
   271         -     *  Initialize the first 16 words in the array W
   272         -     */
   273         -    for(t = 0; t < 16; t++)
   274         -    {
   275         -        W[t] = context->Message_Block[t * 4] << 24;
   276         -        W[t] |= context->Message_Block[t * 4 + 1] << 16;
   277         -        W[t] |= context->Message_Block[t * 4 + 2] << 8;
   278         -        W[t] |= context->Message_Block[t * 4 + 3];
   279         -    }
   280         -
   281         -    for(t = 16; t < 80; t++)
   282         -    {
   283         -       W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
   284         -    }
   285         -
   286         -    A = context->Intermediate_Hash[0];
   287         -    B = context->Intermediate_Hash[1];
   288         -    C = context->Intermediate_Hash[2];
   289         -    D = context->Intermediate_Hash[3];
   290         -    E = context->Intermediate_Hash[4];
   291         -
   292         -    for(t = 0; t < 20; t++)
   293         -    {
   294         -        temp =  SHA1CircularShift(5,A) +
   295         -                ((B & C) | ((~B) & D)) + E + W[t] + K[0];
   296         -        E = D;
   297         -        D = C;
   298         -        C = SHA1CircularShift(30,B);
   299         -
   300         -        B = A;
   301         -        A = temp;
   302         -    }
   303         -
   304         -    for(t = 20; t < 40; t++)
   305         -    {
   306         -        temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
   307         -        E = D;
   308         -        D = C;
   309         -        C = SHA1CircularShift(30,B);
   310         -        B = A;
   311         -        A = temp;
   312         -    }
   313         -
   314         -    for(t = 40; t < 60; t++)
   315         -    {
   316         -        temp = SHA1CircularShift(5,A) +
   317         -               ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
   318         -        E = D;
   319         -        D = C;
   320         -        C = SHA1CircularShift(30,B);
   321         -        B = A;
   322         -        A = temp;
   323         -    }
   324         -
   325         -    for(t = 60; t < 80; t++)
   326         -    {
   327         -        temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
   328         -        E = D;
   329         -        D = C;
   330         -        C = SHA1CircularShift(30,B);
   331         -        B = A;
   332         -        A = temp;
   333         -    }
   334         -
   335         -    context->Intermediate_Hash[0] += A;
   336         -    context->Intermediate_Hash[1] += B;
   337         -    context->Intermediate_Hash[2] += C;
   338         -    context->Intermediate_Hash[3] += D;
   339         -    context->Intermediate_Hash[4] += E;
   340         -
   341         -    context->Message_Block_Index = 0;
   342         -}
   343    164   
   344    165   /*
   345         - *  SHA1PadMessage
   346         - *
   347         -
   348         - *  Description:
   349         - *      According to the standard, the message must be padded to an even
   350         - *      512 bits.  The first padding bit must be a '1'.  The last 64
   351         - *      bits represent the length of the original message.  All bits in
   352         - *      between should be 0.  This function will pad the message
   353         - *      according to those rules by filling the Message_Block array
   354         - *      accordingly.  It will also call the ProcessMessageBlock function
   355         - *      provided appropriately.  When it returns, it can be assumed that
   356         - *      the message digest has been computed.
   357         - *
   358         - *  Parameters:
   359         - *      context: [in/out]
   360         - *          The context to pad
   361         - *      ProcessMessageBlock: [in]
   362         - *          The appropriate SHA*ProcessMessageBlock function
   363         - *  Returns:
   364         - *      Nothing.
   365         - *
          166  + * Add padding and return the message digest.
   366    167    */
   367         -static void SHA1PadMessage(SHA1Context *context)
   368         -{
   369         -    /*
   370         -     *  Check to see if the current message block is too small to hold
   371         -     *  the initial padding bits and length.  If so, we will pad the
   372         -     *  block, process it, and then continue padding into a second
   373         -     *  block.
   374         -     */
   375         -    if (context->Message_Block_Index > 55)
   376         -    {
   377         -        context->Message_Block[context->Message_Block_Index++] = 0x80;
   378         -        while(context->Message_Block_Index < 64)
   379         -        {
   380         -            context->Message_Block[context->Message_Block_Index++] = 0;
   381         -        }
          168  +static void SHA1Final(SHA1Context *context, unsigned char digest[20]){
          169  +    unsigned int i;
          170  +    unsigned char finalcount[8];
   382    171   
   383         -        SHA1ProcessMessageBlock(context);
   384         -
   385         -        while(context->Message_Block_Index < 56)
   386         -        {
   387         -            context->Message_Block[context->Message_Block_Index++] = 0;
   388         -        }
          172  +    for (i = 0; i < 8; i++) {
          173  +	finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)]
          174  +	 >> ((3-(i & 3)) * 8) ) & 255);	 /* Endian independent */
   389    175       }
   390         -    else
   391         -    {
   392         -        context->Message_Block[context->Message_Block_Index++] = 0x80;
   393         -        while(context->Message_Block_Index < 56)
   394         -        {
          176  +    SHA1Update(context, (const unsigned char *)"\200", 1);
          177  +    while ((context->count[0] & 504) != 448)
          178  +	SHA1Update(context, (const unsigned char *)"\0", 1);
          179  +    SHA1Update(context, finalcount, 8);  /* Should cause a SHA1Transform() */
   395    180   
   396         -            context->Message_Block[context->Message_Block_Index++] = 0;
   397         -        }
          181  +    if (digest) {
          182  +	for (i = 0; i < 20; i++)
          183  +	    digest[i] = (unsigned char)
          184  +		((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
   398    185       }
   399         -
   400         -    /*
   401         -     *  Store the message length as the last 8 octets
   402         -     */
   403         -    context->Message_Block[56] = context->Length_High >> 24;
   404         -    context->Message_Block[57] = context->Length_High >> 16;
   405         -    context->Message_Block[58] = context->Length_High >> 8;
   406         -    context->Message_Block[59] = context->Length_High;
   407         -    context->Message_Block[60] = context->Length_Low >> 24;
   408         -    context->Message_Block[61] = context->Length_Low >> 16;
   409         -    context->Message_Block[62] = context->Length_Low >> 8;
   410         -    context->Message_Block[63] = context->Length_Low;
   411         -
   412         -    SHA1ProcessMessageBlock(context);
   413    186   }
   414    187   
   415    188   
   416    189   /*
   417    190   ** Convert a digest into base-16.  digest should be declared as
   418    191   ** "unsigned char digest[20]" in the calling function.  The SHA1
   419    192   ** digest is stored in the first 20 bytes.  zBuf should
................................................................................
   439    212   static int incrInit = 0;
   440    213   
   441    214   /*
   442    215   ** Add more text to the incremental SHA1 checksum.
   443    216   */
   444    217   void sha1sum_step_text(const char *zText, int nBytes){
   445    218     if( !incrInit ){
   446         -    SHA1Reset(&incrCtx);
          219  +    SHA1Init(&incrCtx);
   447    220       incrInit = 1;
   448    221     }
   449    222     if( nBytes<=0 ){
   450    223       if( nBytes==0 ) return;
   451    224       nBytes = strlen(zText);
   452    225     }
   453         -  SHA1Input(&incrCtx, (unsigned char*)zText, nBytes);
          226  +  SHA1Update(&incrCtx, (unsigned char*)zText, nBytes);
   454    227   }
   455    228   
   456    229   /*
   457    230   ** Add the content of a blob to the incremental SHA1 checksum.
   458    231   */
   459    232   void sha1sum_step_blob(Blob *p){
   460    233     sha1sum_step_text(blob_buffer(p), blob_size(p));
................................................................................
   468    241   ** of computation.  The return pointer points to a static buffer that
   469    242   ** is overwritten by subsequent calls to this function.
   470    243   */
   471    244   char *sha1sum_finish(Blob *pOut){
   472    245     unsigned char zResult[20];
   473    246     static char zOut[41];
   474    247     sha1sum_step_text(0,0);
   475         -  SHA1Result(&incrCtx, zResult);
          248  +  SHA1Final(&incrCtx, zResult);
   476    249     incrInit = 0;
   477    250     DigestToBase16(zResult, zOut);
   478    251     if( pOut ){
   479    252       blob_zero(pOut);
   480    253       blob_append(pOut, zOut, 40);
   481    254     }
   482    255     return zOut;
................................................................................
   495    268     unsigned char zResult[20];
   496    269     char zBuf[10240];
   497    270   
   498    271     in = fopen(zFilename,"rb");
   499    272     if( in==0 ){
   500    273       return 1;
   501    274     }
   502         -  SHA1Reset(&ctx);
          275  +  SHA1Init(&ctx);
   503    276     for(;;){
   504    277       int n;
   505    278       n = fread(zBuf, 1, sizeof(zBuf), in);
   506    279       if( n<=0 ) break;
   507         -    SHA1Input(&ctx, (unsigned char*)zBuf, (unsigned)n);
          280  +    SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n);
   508    281     }
   509    282     fclose(in);
   510    283     blob_zero(pCksum);
   511    284     blob_resize(pCksum, 40);
   512         -  SHA1Result(&ctx, zResult);
          285  +  SHA1Final(&ctx, zResult);
   513    286     DigestToBase16(zResult, blob_buffer(pCksum));
   514    287     return 0;
   515    288   }
   516    289   
   517    290   /*
   518    291   ** Compute the SHA1 checksum of a blob in memory.  Store the resulting
   519    292   ** checksum in the blob pCksum.  pCksum is assumed to be either
................................................................................
   521    294   **
   522    295   ** Return the number of errors.
   523    296   */
   524    297   int sha1sum_blob(const Blob *pIn, Blob *pCksum){
   525    298     SHA1Context ctx;
   526    299     unsigned char zResult[20];
   527    300   
   528         -  SHA1Reset(&ctx);
   529         -  SHA1Input(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn));
          301  +  SHA1Init(&ctx);
          302  +  SHA1Update(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn));
   530    303     if( pIn==pCksum ){
   531    304       blob_reset(pCksum);
   532    305     }else{
   533    306       blob_zero(pCksum);
   534    307     }
   535    308     blob_resize(pCksum, 40);
   536         -  SHA1Result(&ctx, zResult);
          309  +  SHA1Final(&ctx, zResult);
   537    310     DigestToBase16(zResult, blob_buffer(pCksum));
   538    311     return 0;
   539    312   }
   540    313   
   541    314   /*
   542    315   ** Compute the SHA1 checksum of a zero-terminated string.  The
   543    316   ** result is held in memory obtained from mprintf().
   544    317   */
   545    318   char *sha1sum(const char *zIn){
   546    319     SHA1Context ctx;
   547    320     unsigned char zResult[20];
   548    321     char zDigest[41];
   549    322   
   550         -  SHA1Reset(&ctx);
   551         -  SHA1Input(&ctx, (unsigned const char*)zIn, strlen(zIn));
   552         -  SHA1Result(&ctx, zResult);
          323  +  SHA1Init(&ctx);
          324  +  SHA1Update(&ctx, (unsigned const char*)zIn, strlen(zIn));
          325  +  SHA1Final(&ctx, zResult);
   553    326     DigestToBase16(zResult, zDigest);
   554    327     return mprintf("%s", zDigest);
   555    328   }
   556    329   
   557    330   /*
   558    331   ** Convert a cleartext password for a specific user into a SHA1 hash.
   559    332   ** 
................................................................................
   573    346   */
   574    347   char *sha1_shared_secret(const char *zPw, const char *zLogin){
   575    348     static char *zProjectId = 0;
   576    349     SHA1Context ctx;
   577    350     unsigned char zResult[20];
   578    351     char zDigest[41];
   579    352   
   580         -  SHA1Reset(&ctx);
          353  +  SHA1Init(&ctx);
   581    354     if( zProjectId==0 ){
   582    355       zProjectId = db_get("project-code", 0);
   583    356   
   584    357       /* On the first xfer request of a clone, the project-code is not yet
   585    358       ** known.  Use the cleartext password, since that is all we have.
   586    359       */
   587    360       if( zProjectId==0 ){
   588    361         return mprintf("%s", zPw);
   589    362       }
   590    363     }
   591         -  SHA1Input(&ctx, (unsigned char*)zProjectId, strlen(zProjectId));
   592         -  SHA1Input(&ctx, (unsigned char*)"/", 1);
   593         -  SHA1Input(&ctx, (unsigned char*)zLogin, strlen(zLogin));
   594         -  SHA1Input(&ctx, (unsigned char*)"/", 1);
   595         -  SHA1Input(&ctx, (unsigned const char*)zPw, strlen(zPw));
   596         -  SHA1Result(&ctx, zResult);
          364  +  SHA1Update(&ctx, (unsigned char*)zProjectId, strlen(zProjectId));
          365  +  SHA1Update(&ctx, (unsigned char*)"/", 1);
          366  +  SHA1Update(&ctx, (unsigned char*)zLogin, strlen(zLogin));
          367  +  SHA1Update(&ctx, (unsigned char*)"/", 1);
          368  +  SHA1Update(&ctx, (unsigned const char*)zPw, strlen(zPw));
          369  +  SHA1Final(&ctx, zResult);
   597    370     DigestToBase16(zResult, zDigest);
   598    371     return mprintf("%s", zDigest);
   599    372   }
   600    373   
   601    374   /*
   602    375   ** COMMAND: sha1sum
   603    376   ** %fossil sha1sum FILE...

Changes to src/sync.c.

   163    163   ** is used.
   164    164   **
   165    165   ** The URL specified normally becomes the new "remote-url" used for
   166    166   ** subsequent <a>push</a>, <a>pull</a>, and <a>sync</a> operations.  However,
   167    167   ** the "--once" command-line option makes the URL a one-time-use URL
   168    168   ** that is not saved.
   169    169   **
   170         -** See also: <a>clone</a>, <a>pull</a>, <a>sync</a>, <a>remote-url</a>
          170  +** If configured (<a>setting</a> push-hook-..), the push hook command will be
          171  +** executed on the server after pushing files.
          172  +**
          173  +** See also: <a>callhook</a>, <a>clone</a>, <a>pull</a>, <a>sync</a>, <a>remote-url</a>
   171    174   */
   172    175   void push_cmd(void){
   173    176     process_sync_args();
   174    177     client_sync(1,0,0,0,0);
   175    178   }
   176    179   
   177    180   
................................................................................
   194    197   ** command is used.
   195    198   **
   196    199   ** The URL specified normally becomes the new "remote-url" used for
   197    200   ** subsequent <a>push</a>, <a>pull</a>, and <a>sync</a> operations.  However,
   198    201   ** the "--once" command-line option makes the URL a one-time-use URL
   199    202   ** that is not saved.
   200    203   **
   201         -** See also:  <a>clone</a>, <a>push</a>, <a>pull</a>, <a>remote-url</a>
          204  +** If configured (<a>setting</a> push-hook-..), the push hook command will be
          205  +** executed on the server after pushing files.
          206  +**
          207  +** See also: <a>callhook</a>, <a>clone</a>, <a>push</a>, <a>pull</a>, <a>remote-url</a>
   202    208   */
   203    209   void sync_cmd(void){
   204    210     int syncFlags = process_sync_args();
   205    211     client_sync(1,1,0,syncFlags,0);
   206    212   }
   207    213   
   208    214   /*

Changes to src/tkt.c.

   212    212   /*
   213    213   ** Rebuild an entire entry in the TICKET table
   214    214   */
   215    215   void ticket_rebuild_entry(const char *zTktUuid){
   216    216     char *zTag = mprintf("tkt-%s", zTktUuid);
   217    217     int tagid = tag_findid(zTag, 1);
   218    218     Stmt q;
   219         -  Manifest manifest;
   220         -  Blob content;
          219  +  Manifest *pTicket;
   221    220     int createFlag = 1;
   222    221     
   223    222     db_multi_exec(
   224    223        "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
   225    224     );
   226    225     db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
   227    226     while( db_step(&q)==SQLITE_ROW ){
   228    227       int rid = db_column_int(&q, 0);
   229         -    content_get(rid, &content);
   230         -    manifest_parse(&manifest, &content);
   231         -    ticket_insert(&manifest, createFlag, rid);
   232         -    manifest_ticket_event(rid, &manifest, createFlag, tagid);
   233         -    manifest_clear(&manifest);
          228  +    pTicket = manifest_get(rid, CFTYPE_TICKET);
          229  +    if( pTicket ){
          230  +      ticket_insert(pTicket, createFlag, rid);
          231  +      manifest_ticket_event(rid, pTicket, createFlag, tagid);
          232  +      manifest_destroy(pTicket);
          233  +    }
   234    234       createFlag = 0;
   235    235     }
   236    236     db_finalize(&q);
   237    237   }
   238    238   
   239    239   /*
   240    240   ** Create the subscript interpreter and load the "common" code.
................................................................................
   751    751       "  FROM attachment, blob"
   752    752       " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
   753    753       "   AND blob.rid=attachid"
   754    754       " ORDER BY 1 DESC",
   755    755       tagid, tagid
   756    756     );
   757    757     while( db_step(&q)==SQLITE_ROW ){
   758         -    Blob content;
   759         -    Manifest m;
          758  +    Manifest *pTicket;
   760    759       char zShort[12];
   761    760       const char *zDate = db_column_text(&q, 0);
   762    761       int rid = db_column_int(&q, 1);
   763    762       const char *zChngUuid = db_column_text(&q, 2);
   764    763       const char *zFile = db_column_text(&q, 4);
   765    764       memcpy(zShort, zChngUuid, 10);
   766    765       zShort[10] = 0;
................................................................................
   775    774           @ <p>Add attachment "%h(zFile)"
   776    775         }
   777    776         @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
   778    777         @ (rid %d(rid)) by
   779    778         hyperlink_to_user(zUser,zDate," on");
   780    779         hyperlink_to_date(zDate, ".</p>");
   781    780       }else{
   782         -      content_get(rid, &content);
   783         -      if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){
          781  +      pTicket = manifest_get(rid, CFTYPE_TICKET);
          782  +      if( pTicket ){
   784    783           @
   785    784           @ <p>Ticket change
   786    785           @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
   787    786           @ (rid %d(rid)) by
   788         -        hyperlink_to_user(m.zUser,zDate," on");
          787  +        hyperlink_to_user(pTicket->zUser,zDate," on");
   789    788           hyperlink_to_date(zDate, ":");
   790    789           @ </p>
   791         -        ticket_output_change_artifact(&m);
          790  +        ticket_output_change_artifact(pTicket);
   792    791         }
   793         -      manifest_clear(&m);
          792  +      manifest_destroy(pTicket);
   794    793       }
   795    794     }
   796    795     db_finalize(&q);
   797    796     style_footer_cmdref("timeline",0);
   798    797   }
   799    798   
   800    799   /*

Changes to src/update.c.

   277    277     */
   278    278     if( nochangeFlag ){
   279    279       db_end_transaction(1);  /* With --nochange, rollback changes */
   280    280     }else{
   281    281       if( g.argc<=3 ){
   282    282         /* All files updated.  Shift the current checkout to the target. */
   283    283         db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
          284  +      checkout_set_all_exe(vid);
   284    285         manifest_to_disk(tid);
   285    286         db_lset_int("checkout", tid);
   286    287       }else{
   287    288         /* A subset of files have been checked out.  Keep the current
   288    289         ** checkout unchanged. */
   289    290         db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
   290    291       }
................................................................................
   300    301   */
   301    302   int historical_version_of_file(
   302    303     const char *revision,    /* The checkin containing the file */
   303    304     const char *file,        /* Full treename of the file */
   304    305     Blob *content,           /* Put the content here */
   305    306     int errCode              /* Error code if file not found.  Panic if 0. */
   306    307   ){
   307         -  Blob mfile;
   308         -  Manifest m;
   309         -  int i, rid=0;
          308  +  Manifest *pManifest;
          309  +  ManifestFile *pFile;
          310  +  int rid=0;
   310    311     
   311    312     if( revision ){
   312    313       rid = name_to_rid(revision);
   313    314     }else{
   314    315       rid = db_lget_int("checkout", 0);
   315    316     }
   316    317     if( !is_a_version(rid) ){
   317    318       if( errCode>0 ) return errCode;
   318    319       fossil_fatal("no such checkin: %s", revision);
   319    320     }
   320         -  content_get(rid, &mfile);
          321  +  pManifest = manifest_get(rid, CFTYPE_MANIFEST);
   321    322     
   322         -  if( manifest_parse(&m, &mfile) ){
   323         -    for(i=0; i<m.nFile; i++){
   324         -      if( strcmp(m.aFile[i].zName, file)==0 ){
   325         -        rid = uuid_to_rid(m.aFile[i].zUuid, 0);
   326         -        manifest_clear(&m);
          323  +  if( pManifest ){
          324  +    manifest_file_rewind(pManifest);
          325  +    while( (pFile = manifest_file_next(pManifest,0))!=0 ){
          326  +      if( strcmp(pFile->zName, file)==0 ){
          327  +        rid = uuid_to_rid(pFile->zUuid, 0);
          328  +        manifest_destroy(pManifest);
   327    329           return content_get(rid, content);
   328    330         }
   329    331       }
   330         -    manifest_clear(&m);
          332  +    manifest_destroy(pManifest);
   331    333       if( errCode<=0 ){
   332    334         fossil_fatal("file %s does not exist in checkin: %s", file, revision);
   333    335       }
   334    336     }else if( errCode<=0 ){
   335    337       fossil_panic("could not parse manifest for checkin: %s", revision);
   336    338     }
   337    339     return errCode;
................................................................................
   384    386         blob_reset(&fname);
   385    387       }
   386    388     }else{
   387    389       int vid;
   388    390       vid = db_lget_int("checkout", 0);
   389    391       vfile_check_signature(vid, 0);
   390    392       db_multi_exec(
          393  +      "DELETE FROM vmerge;"
   391    394         "INSERT INTO torevert "
   392    395         "SELECT pathname"
   393    396         "  FROM vfile "
   394         -      " WHERE chnged OR deleted OR rid=0 OR pathname!=origname"
          397  +      " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
   395    398       );
   396    399     }
   397    400     blob_zero(&record);
   398    401     db_prepare(&q, "SELECT name FROM torevert");
   399    402     while( db_step(&q)==SQLITE_ROW ){
   400    403       zFile = db_column_text(&q, 0);
   401    404       if( zRevision!=0 ){

Changes to src/vfile.c.

    83     83           fossil_panic("bad object id: %d", rid);
    84     84         }
    85     85       }
    86     86     }
    87     87   }
    88     88   
    89     89   /*
    90         -** Build a catalog of all files in a baseline.
    91         -** We scan the baseline file for lines of the form:
    92         -**
    93         -**     F NAME UUID
    94         -**
    95         -** Each such line makes an entry in the VFILE table.
           90  +** Build a catalog of all files in a checkin.
    96     91   */
    97         -void vfile_build(int vid, Blob *p){
           92  +void vfile_build(int vid){
    98     93     int rid;
    99         -  char *zName, *zUuid;
   100     94     Stmt ins;
   101         -  Blob line, token, name, uuid;
   102         -  int seenHeader = 0;
           95  +  Manifest *p;
           96  +  ManifestFile *pFile;
           97  +
   103     98     db_begin_transaction();
   104     99     vfile_verify_not_phantom(vid, 0, 0);
          100  +  p = manifest_get(vid, CFTYPE_MANIFEST);
          101  +  if( p==0 ) return;
   105    102     db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
   106    103     db_prepare(&ins,
   107    104       "INSERT INTO vfile(vid,rid,mrid,pathname) "
   108    105       " VALUES(:vid,:id,:id,:name)");
   109    106     db_bind_int(&ins, ":vid", vid);
   110         -  while( blob_line(p, &line) ){
   111         -    char *z = blob_buffer(&line);
   112         -    if( z[0]=='-' ){
   113         -      if( seenHeader ) break;
   114         -      while( blob_line(p, &line)>2 ){}
   115         -      if( blob_line(p, &line)==0 ) break;
   116         -    }
   117         -    seenHeader = 1;
   118         -    if( z[0]!='F' || z[1]!=' ' ) continue;
   119         -    blob_token(&line, &token);  /* Skip the "F" token */
   120         -    if( blob_token(&line, &name)==0 ) break;
   121         -    if( blob_token(&line, &uuid)==0 ) break;
   122         -    zName = blob_str(&name);
   123         -    defossilize(zName);
   124         -    zUuid = blob_str(&uuid);
   125         -    rid = uuid_to_rid(zUuid, 0);
   126         -    vfile_verify_not_phantom(rid, zName, zUuid);
   127         -    if( rid>0 && file_is_simple_pathname(zName) ){
   128         -      db_bind_int(&ins, ":id", rid);
   129         -      db_bind_text(&ins, ":name", zName);
   130         -      db_step(&ins);
   131         -      db_reset(&ins);
   132         -    }
   133         -    blob_reset(&name);
   134         -    blob_reset(&uuid);
          107  +  manifest_file_rewind(p);
          108  +  while( (pFile = manifest_file_next(p,0))!=0 ){
          109  +    rid = uuid_to_rid(pFile->zUuid, 0);
          110  +    vfile_verify_not_phantom(rid, pFile->zName, pFile->zUuid);
          111  +    db_bind_int(&ins, ":id", rid);
          112  +    db_bind_text(&ins, ":name", pFile->zName);
          113  +    db_step(&ins);
          114  +    db_reset(&ins);
   135    115     }
   136    116     db_finalize(&ins);
          117  +  manifest_destroy(p);
   137    118     db_end_transaction(0);
   138    119   }
   139    120   
   140    121   /*
   141    122   ** Check the file signature of the disk image for every VFILE of vid.
   142    123   **
   143    124   ** Set the VFILE.CHNGED field on every file that has changed.  Also 
................................................................................
   367    348           md5sum_step_text(" 0\n", -1);
   368    349           continue;
   369    350         }
   370    351         fseek(in, 0L, SEEK_END);
   371    352         sprintf(zBuf, " %ld\n", ftell(in));
   372    353         fseek(in, 0L, SEEK_SET);
   373    354         md5sum_step_text(zBuf, -1);
          355  +      /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/
   374    356         for(;;){
   375    357           int n;
   376    358           n = fread(zBuf, 1, sizeof(zBuf), in);
   377    359           if( n<=0 ) break;
   378    360           md5sum_step_text(zBuf, n);
   379    361         }
   380    362         fclose(in);
................................................................................
   420    402     while( db_step(&q)==SQLITE_ROW ){
   421    403       const char *zName = db_column_text(&q, 0);
   422    404       int rid = db_column_int(&q, 1);
   423    405       md5sum_step_text(zName, -1);
   424    406       content_get(rid, &file);
   425    407       sprintf(zBuf, " %d\n", blob_size(&file));
   426    408       md5sum_step_text(zBuf, -1);
          409  +    /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/
   427    410       md5sum_step_blob(&file);
   428    411       blob_reset(&file);
   429    412     }
   430    413     db_finalize(&q);
   431    414     md5sum_finish(pOut);
   432    415   }
   433    416   
................................................................................
   436    419   ** file in manifest vid.  The file names are part of the checksum.
   437    420   ** Return the resulting checksum in blob pOut.
   438    421   **
   439    422   ** If pManOut is not NULL then fill it with the checksum found in the
   440    423   ** "R" card near the end of the manifest.  
   441    424   */
   442    425   void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){
   443         -  int i, fid;
   444         -  Blob file, mfile;
   445         -  Manifest m;
          426  +  int fid;
          427  +  Blob file;
          428  +  Manifest *pManifest;
          429  +  ManifestFile *pFile;
   446    430     char zBuf[100];
   447    431   
   448    432     blob_zero(pOut);
   449    433     if( pManOut ){
   450    434       blob_zero(pManOut);
   451    435     }
   452    436     db_must_be_within_tree();
   453         -  content_get(vid, &mfile);
   454         -  if( manifest_parse(&m, &mfile)==0 ){
          437  +  pManifest = manifest_get(vid, CFTYPE_MANIFEST);
          438  +  if( pManifest==0 ){
   455    439       fossil_panic("manifest file (%d) is malformed", vid);
   456    440     }
   457         -  for(i=0; i<m.nFile; i++){
   458         -    fid = uuid_to_rid(m.aFile[i].zUuid, 0);
   459         -    md5sum_step_text(m.aFile[i].zName, -1);
          441  +  manifest_file_rewind(pManifest);
          442  +  while( (pFile = manifest_file_next(pManifest,0))!=0 ){
          443  +    fid = uuid_to_rid(pFile->zUuid, 0);
          444  +    md5sum_step_text(pFile->zName, -1);
   460    445       content_get(fid, &file);
   461    446       sprintf(zBuf, " %d\n", blob_size(&file));
   462    447       md5sum_step_text(zBuf, -1);
   463    448       md5sum_step_blob(&file);
   464    449       blob_reset(&file);
   465    450     }
   466    451     if( pManOut ){
   467         -    if( m.zRepoCksum ){
   468         -      blob_append(pManOut, m.zRepoCksum, -1);
          452  +    if( pManifest->zRepoCksum ){
          453  +      blob_append(pManOut, pManifest->zRepoCksum, -1);
   469    454       }else{
   470    455         blob_zero(pManOut);
   471    456       }
   472    457     }
   473         -  manifest_clear(&m);
          458  +  manifest_destroy(pManifest);
   474    459     md5sum_finish(pOut);
   475    460   }
   476    461   
   477    462   /*
   478    463   ** COMMAND: test-agg-cksum
   479    464   */
   480    465   void test_agg_cksum_cmd(void){

Changes to src/wiki.c.

   125    125   ** URL: /wiki?name=PAGENAME
   126    126   */
   127    127   void wiki_page(void){
   128    128     char *zTag;
   129    129     int rid = 0;
   130    130     int isSandbox;
   131    131     Blob wiki;
   132         -  Manifest m;
          132  +  Manifest *pWiki = 0;
   133    133     const char *zPageName;
   134    134     char *zBody = mprintf("%s","<i>Empty Page</i>");
   135    135     Stmt q;
   136    136     int cnt = 0;
   137    137   
   138    138     login_check_credentials();
   139    139     if( !g.okRdWiki ){ login_needed(); return; }
................................................................................
   177    177       zTag = mprintf("wiki-%s", zPageName);
   178    178       rid = db_int(0, 
   179    179         "SELECT rid FROM tagxref"
   180    180         " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
   181    181         " ORDER BY mtime DESC", zTag
   182    182       );
   183    183       free(zTag);
   184         -    memset(&m, 0, sizeof(m));
   185         -    blob_zero(&m.content);
   186         -    if( rid ){
   187         -      Blob content;
   188         -      content_get(rid, &content);
   189         -      manifest_parse(&m, &content);
   190         -      if( m.type==CFTYPE_WIKI && m.zWiki ){
   191         -        while( fossil_isspace(m.zWiki[0]) ) m.zWiki++;
   192         -        if( m.zWiki[0] ) zBody = m.zWiki;
   193         -      }
          184  +    pWiki = manifest_get(rid, CFTYPE_WIKI);
          185  +    if( pWiki ){
          186  +      while( fossil_isspace(pWiki->zWiki[0]) ) pWiki->zWiki++;
          187  +      if( pWiki->zWiki[0] ) zBody = pWiki->zWiki;
   194    188       }
   195    189     }
   196    190     if( !g.isHome ){
   197    191       if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){
   198    192         style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
   199    193              g.zTop, zPageName);
   200    194       }
................................................................................
   247    241       @ </li>
   248    242     }
   249    243     if( cnt ){
   250    244       @ </ul>
   251    245     }
   252    246     db_finalize(&q);
   253    247    
   254         -  if( !isSandbox ){
   255         -    manifest_clear(&m);
   256         -  }
          248  +  manifest_destroy(pWiki);
   257    249     /* don'tshow the help cross reference, because we are rendering
   258    250     ** the wiki conten!
   259    251     ** style_footer_cmdref("wiki","export");
   260    252     */
   261    253     style_footer();
   262    254   }
   263    255   
................................................................................
   266    258   ** URL: /wikiedit?name=PAGENAME
   267    259   */
   268    260   void wikiedit_page(void){
   269    261     char *zTag;
   270    262     int rid = 0;
   271    263     int isSandbox;
   272    264     Blob wiki;
   273         -  Manifest m;
          265  +  Manifest *pWiki = 0;
   274    266     const char *zPageName;
   275    267     char *zHtmlPageName;
   276    268     int n;
   277    269     const char *z;
   278    270     char *zBody = (char*)P("w");
   279    271   
   280    272     if( zBody ){
................................................................................
   300    292         " ORDER BY mtime DESC", zTag
   301    293       );
   302    294       free(zTag);
   303    295       if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){
   304    296         login_needed();
   305    297         return;
   306    298       }
   307         -    memset(&m, 0, sizeof(m));
   308         -    blob_zero(&m.content);
   309         -    if( rid && zBody==0 ){
   310         -      Blob content;
   311         -      content_get(rid, &content);
   312         -      manifest_parse(&m, &content);
   313         -      if( m.type==CFTYPE_WIKI ){
   314         -        zBody = m.zWiki;
   315         -      }
          299  +    if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){
          300  +      zBody = pWiki->zWiki;
   316    301       }
   317    302     }
   318    303     if( P("submit")!=0 && zBody!=0 ){
   319    304       char *zDate;
   320    305       Blob cksum;
   321    306       int nrid;
   322    307       blob_zero(&wiki);
................................................................................
   379    364     @ <textarea name="w" class="wikiedit" cols="80" 
   380    365     @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
   381    366     @ <br />
   382    367     @ <input type="submit" name="preview" value="Preview Your Changes" />
   383    368     @ <input type="submit" name="submit" value="Apply These Changes" />
   384    369     @ <input type="submit" name="cancel" value="Cancel" />
   385    370     @ </div></form>
   386         -  if( !isSandbox ){
   387         -    manifest_clear(&m);
   388         -  }
          371  +  manifest_destroy(pWiki);
   389    372     style_footer_cmdref("wiki"," / <a href=\"wiki_rules\">wiki format</a>");
   390    373   }
   391    374   
   392    375   /*
   393    376   ** WEBPAGE: wikinew
   394    377   ** URL /wikinew
   395    378   **
................................................................................
   479    462       return;
   480    463     }
   481    464     if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){
   482    465       char *zDate;
   483    466       Blob cksum;
   484    467       int nrid;
   485    468       Blob body;
   486         -    Blob content;
   487    469       Blob wiki;
   488         -    Manifest m;
          470  +    Manifest *pWiki = 0;
   489    471   
   490    472       blob_zero(&body);
   491    473       if( isSandbox ){
   492    474         blob_appendf(&body, db_get("sandbox",""));
   493    475         appendRemark(&body);
   494    476         db_set("sandbox", blob_str(&body), 0);
   495    477       }else{
   496    478         login_verify_csrf_secret();
   497         -      content_get(rid, &content);
   498         -      manifest_parse(&m, &content);
   499         -      if( m.type==CFTYPE_WIKI ){
   500         -        blob_append(&body, m.zWiki, -1);
          479  +      pWiki = manifest_get(rid, CFTYPE_WIKI);
          480  +      if( pWiki ){
          481  +        blob_append(&body, pWiki->zWiki, -1);
          482  +        manifest_destroy(pWiki);
   501    483         }
   502         -      manifest_clear(&m);
   503    484         blob_zero(&wiki);
   504    485         db_begin_transaction();
   505    486         zDate = db_text(0, "SELECT datetime('now')");
   506    487         zDate[10] = 'T';
   507    488         blob_appendf(&wiki, "D %s\n", zDate);
   508    489         blob_appendf(&wiki, "L %F\n", zPageName);
   509    490         if( rid ){
................................................................................
   614    595   **
   615    596   ** Show the difference between two wiki pages.
   616    597   */
   617    598   void wdiff_page(void){
   618    599     char *zTitle;
   619    600     int rid1, rid2;
   620    601     const char *zPageName;
   621         -  Blob content1, content2;
   622         -  Manifest m1, m2;
          602  +  Manifest *pW1, *pW2 = 0;
   623    603     Blob w1, w2, d;
   624    604   
   625    605     login_check_credentials();
   626    606     rid1 = atoi(PD("a","0"));
   627    607     if( !g.okHistory ){ login_needed(); return; }
   628    608     if( rid1==0 ) fossil_redirect_home();
   629    609     rid2 = atoi(PD("b","0"));
................................................................................
   637    617         "SELECT objid FROM event JOIN tagxref ON objid=rid AND tagxref.tagid="
   638    618                           "(SELECT tagid FROM tag WHERE tagname='wiki-%q')"
   639    619         " WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)"
   640    620         " ORDER BY event.mtime DESC LIMIT 1",
   641    621         zPageName, rid1
   642    622       );
   643    623     }
   644         -  content_get(rid1, &content1);
   645         -  manifest_parse(&m1, &content1);
   646         -  if( m1.type!=CFTYPE_WIKI ) fossil_redirect_home();
   647         -  blob_init(&w1, m1.zWiki, -1);
          624  +  pW1 = manifest_get(rid1, CFTYPE_WIKI);
          625  +  if( pW1==0 ) fossil_redirect_home();
          626  +  blob_init(&w1, pW1->zWiki, -1);
   648    627     blob_zero(&w2);
   649         -  if( rid2 ){
   650         -    content_get(rid2, &content2);
   651         -    manifest_parse(&m2, &content2);
   652         -    if( m2.type==CFTYPE_WIKI ){
   653         -      blob_init(&w2, m2.zWiki, -1);
   654         -    }
          628  +  if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
          629  +    blob_init(&w2, pW2->zWiki, -1);
   655    630     }
   656    631     blob_zero(&d);
   657    632     text_diff(&w2, &w1, &d, 5, 1);
   658    633     @ <pre>
   659    634     @ %h(blob_str(&d))
   660    635     @ </pre>
          636  +  manifest_destroy(pW1);
          637  +  manifest_destroy(pW2);
   661    638     style_footer();
   662    639   }
   663    640   
   664    641   /*
   665    642   ** WEBPAGE: wcontent
   666    643   **
   667    644   **     all=1         Show deleted pages
................................................................................
   919    896     if( n==0 ){
   920    897       goto wiki_cmd_usage;
   921    898     }
   922    899   
   923    900     if( strncmp(g.argv[2],"export",n)==0 ){
   924    901       char const *zPageName;        /* Name of the wiki page to export */
   925    902       char const *zFile;            /* Name of the output file (0=stdout) */
   926         -    int rid;                /* Artifact ID of the wiki page */
   927         -    int i;                  /* Loop counter */
   928         -    char *zBody = 0;        /* Wiki page content */
   929         -    Manifest m;             /* Parsed wiki page content */
          903  +    int rid;                      /* Artifact ID of the wiki page */
          904  +    int i;                        /* Loop counter */
          905  +    char *zBody = 0;              /* Wiki page content */
          906  +    Manifest *pWiki = 0;          /* Parsed wiki page content */
          907  +
   930    908       if( (g.argc!=4) && (g.argc!=5) ){
   931    909         usage("export PAGENAME ?FILE?");
   932    910       }
   933    911       zPageName = g.argv[3];
   934    912       rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
   935    913         " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
   936    914         " ORDER BY x.mtime DESC LIMIT 1",
   937    915         zPageName 
   938    916       );
   939         -    if( rid ){
   940         -      Blob content;
   941         -      content_get(rid, &content);
   942         -      manifest_parse(&m, &content);
   943         -      if( m.type==CFTYPE_WIKI ){
   944         -        zBody = m.zWiki;
   945         -      }
          917  +    if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){
          918  +      zBody = pWiki->zWiki;
   946    919       }
   947    920       if( zBody==0 ){
   948    921         fossil_fatal("wiki page [%s] not found",zPageName);
   949    922       }
   950    923       for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
   951    924       zFile  = (g.argc==4) ? 0 : g.argv[4];
   952    925       if( zFile ){
................................................................................
   960    933         }
   961    934         if( ! zF ){
   962    935           fossil_fatal("wiki export could not open output file for writing.");
   963    936         }
   964    937         fprintf(zF,"%.*s\n", i, zBody);
   965    938         if( doClose ) fclose(zF);
   966    939       }else{
   967         -	printf("%.*s\n", i, zBody);
          940  +      printf("%.*s\n", i, zBody);
   968    941       }
          942  +    manifest_destroy(pWiki);
   969    943       return;
   970    944     }else
   971    945     if( strncmp(g.argv[2],"commit",n)==0
   972    946         || strncmp(g.argv[2],"create",n)==0 ){
   973    947       char *zPageName;
   974    948       Blob content;
   975    949       if( g.argc!=4 && g.argc!=5 ){

Changes to src/xfer.c.

    38     38     int nDeltaSent;     /* Number of deltas sent */
    39     39     int nFileRcvd;      /* Number of files received */
    40     40     int nDeltaRcvd;     /* Number of deltas received */
    41     41     int nDanglingFile;  /* Number of dangling deltas received */
    42     42     int mxSend;         /* Stop sending "file" with pOut reaches this size */
    43     43   };
    44     44   
           45  +/*
           46  +** COMMAND: callhook
           47  +** %fossil callhook PUSHHOOKPATTERN ?--force|-f?
           48  +**
           49  +** Call the push hook command on a server, which will normally be called
           50  +** after a client push (<a>setting</a> push-hook-cmd).
           51  +**
           52  +** If --force is used, the given pattern is not checked against the
           53  +** configuration (<a>setting</a> push-hook-pattern-server).
           54  +**
           55  +** This command only works on the server side, it does not send a message
           56  +** from a client, but executes the hook directly on the server.
           57  +**
           58  +** See also <a>push</a>, <a>sync</a>, <a>setting</a>
           59  +*/
           60  +void callhook_cmd(void){
           61  +  int forceFlag = find_option("force","f",0)!=0;
           62  +
           63  +  db_open_config(1);
           64  +  db_find_and_open_repository(0);
           65  +  if( (g.argc!=3) || (!g.argv[2]) || (!g.argv[2][0]) ){
           66  +    usage("PUSHHOOKPATTERN ?--force?");
           67  +  }
           68  +  if( !forceFlag ){
           69  +    const char *zPushHookPattern = db_get("push-hook-pattern-server", "");
           70  +    int lenPushHookPattern = (zPushHookPattern && zPushHookPattern[0])
           71  +                              ? strlen(zPushHookPattern) : 0;
           72  +    if(    (!lenPushHookPattern)
           73  +        || memcmp(g.argv[2], zPushHookPattern, lenPushHookPattern)
           74  +    ){
           75  +      fossil_fatal("push hook pattern '%s' doesn't match configuration '%s'\n",
           76  +                     g.argv[2],zPushHookPattern);
           77  +    }
           78  +  }
           79  +  post_push_hook(g.argv[2],'C');
           80  +}
           81  +
           82  +/*
           83  +** Let a server-side external agent know that a push has completed. /fatman
           84  +** The second argument controls, how the command is called:
           85  +**   P - client request with pushed files
           86  +**   F - client request without pushed files(FORCE!)
           87  +**   C - server side command line activation
           88  +*/
           89  +void post_push_hook(char const * const zPushHookLine, const char requestType){
           90  +  /*
           91  +  ** TO DO: get the string cmd from a config file? Or the database local
           92  +  ** settings, as someone suggested? Ditto output and error logs. /fatman
           93  +  */
           94  +  const char *zCmd = db_get("push-hook-cmd", "");
           95  +  int allowForced = db_get_boolean("push-hook-force", 0);
           96  +  const char *zHookPriv = db_get("push-hook-privilege","");
           97  +  int privOk = 0;
           98  +
           99  +  if( zHookPriv && *zHookPriv ){
          100  +    switch( *zHookPriv ){
          101  +      
          102  +      case 's':
          103  +        if( g.okSetup ) privOk = 1;
          104  +        break;
          105  +      case 'a':
          106  +        if( g.okAdmin ) privOk = 1;
          107  +        break;
          108  +      case 'i':
          109  +        if( g.okWrite ) privOk = 1;
          110  +        break;
          111  +      case 'o':
          112  +        if( g.okRead ) privOk = 1;
          113  +        break;
          114  +      default:
          115  +        fossil_print("Push hook wrong privilege type '%s'\n", zHookPriv);
          116  +    }
          117  +  }else{
          118  +    privOk = 1;
          119  +  }
          120  +  if( !privOk ){
          121  +    fossil_print("No privilege to activate hook!\n");
          122  +  }else if( requestType!='P' &&  requestType!='C' && requestType!='F' ){
          123  +    fossil_print("Push hook wrong request type '%c'\n", requestType);
          124  +  }else if( requestType=='F' && !allowForced ){
          125  +    fossil_print("Forced push call from client not allowed,"
          126  +                 " skipping call for '%s'\n", zPushHookLine);
          127  +  }else if( zCmd && zCmd[0] ){
          128  +    int rc;
          129  +    char * zCalledCmd;
          130  +    char * zDate;
          131  +    const char *zRnd;
          132  +
          133  +
          134  +    zDate = db_text(0, "SELECT strftime('%%Y%%m%%d%%H%%M%%f','now')");
          135  +    zRnd = db_text(0, "SELECT lower(hex(randomblob(6)))");
          136  +
          137  +    zCalledCmd = mprintf("%s %s-%s %s >hook-log-%s-%s 2>&1",zCmd,zDate,zRnd,zPushHookLine,zDate,zRnd);
          138  +    { /* remove newlines from command */
          139  +      char *zSrc, *zDest;
          140  +
          141  +      for (zSrc=zDest=zCalledCmd;;zSrc++){
          142  +        switch( *zSrc ){
          143  +          case '\0':
          144  +            *zDest=0;
          145  +            break;
          146  +          default:
          147  +            *zDest++ = *zSrc;
          148  +            /* fall through is intended! */
          149  +          case '\n':
          150  +            continue;
          151  +        }
          152  +        break;
          153  +      }
          154  +    }
          155  +    rc = system(zCalledCmd);
          156  +    if (rc != 0) {
          157  +      fossil_print("The post-push-hook command '%s' failed.", zCalledCmd);
          158  +    }
          159  +    free(zCalledCmd);
          160  +    free(zDate);
          161  +  }else{
          162  +    fossil_print("No push hook configured, skipping call for '%s'\n", zPushHookLine);
          163  +  }
          164  +}
    45    165   
    46    166   /*
    47    167   ** The input blob contains a UUID.  Convert it into a record ID.
    48    168   ** Create a phantom record if no prior record exists and
    49    169   ** phantomize is true.
    50    170   **
    51    171   ** Compare to uuid_to_rid().  This routine takes a blob argument
................................................................................
    70    190   }
    71    191   
    72    192   /*
    73    193   ** Remember that the other side of the connection already has a copy
    74    194   ** of the file rid.
    75    195   */
    76    196   static void remote_has(int rid){
    77         -  if( rid ) db_multi_exec("INSERT OR IGNORE INTO onremote VALUES(%d)", rid);
          197  +  if( rid ){
          198  +    static Stmt q;
          199  +    db_static_prepare(&q, "INSERT OR IGNORE INTO onremote VALUES(:r)");
          200  +    db_bind_int(&q, ":r", rid);
          201  +    db_step(&q);
          202  +    db_reset(&q);
          203  +  }
    78    204   }
    79    205   
    80    206   /*
    81    207   ** The aToken[0..nToken-1] blob array is a parse of a "file" line 
    82    208   ** message.  This routine finishes parsing that message and does
    83    209   ** a record insert of the file.
    84    210   **
................................................................................
    93    219   **
    94    220   ** If any error occurs, write a message into pErr which has already
    95    221   ** be initialized to an empty string.
    96    222   **
    97    223   ** Any artifact successfully received by this routine is considered to
    98    224   ** be public and is therefore removed from the "private" table.
    99    225   */
   100         -static void xfer_accept_file(Xfer *pXfer){
          226  +static void xfer_accept_file(Xfer *pXfer, int cloneFlag){
   101    227     int n;
   102    228     int rid;
   103    229     int srcid = 0;
   104    230     Blob content, hash;
   105    231     
   106    232     if( pXfer->nToken<3 
   107    233      || pXfer->nToken>4
................................................................................
   112    238     ){
   113    239       blob_appendf(&pXfer->err, "malformed file line");
   114    240       return;
   115    241     }
   116    242     blob_zero(&content);
   117    243     blob_zero(&hash);
   118    244     blob_extract(pXfer->pIn, n, &content);
   119         -  if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
          245  +  if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){
   120    246       /* Ignore files that have been shunned */
   121    247       return;
          248  +  }
          249  +  if( cloneFlag ){
          250  +    if( pXfer->nToken==4 ){
          251  +      srcid = rid_from_uuid(&pXfer->aToken[2], 1);
          252  +      pXfer->nDeltaRcvd++;
          253  +    }else{
          254  +      srcid = 0;
          255  +      pXfer->nFileRcvd++;
          256  +    }
          257  +    rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid);
          258  +    remote_has(rid);
          259  +    blob_reset(&content);
          260  +    return;
   122    261     }
   123    262     if( pXfer->nToken==4 ){
   124    263       Blob src, next;
   125    264       srcid = rid_from_uuid(&pXfer->aToken[2], 1);
   126    265       if( content_get(srcid, &src)==0 ){
   127    266         rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid);
   128    267         pXfer->nDanglingFile++;
................................................................................
   465    604   
   466    605   /*
   467    606   ** Check to see if the number of unclustered entries is greater than
   468    607   ** 100 and if it is, form a new cluster.  Unclustered phantoms do not
   469    608   ** count toward the 100 total.  And phantoms are never added to a new
   470    609   ** cluster.
   471    610   */
   472         -static void create_cluster(void){
          611  +void create_cluster(void){
   473    612     Blob cluster, cksum;
   474    613     Stmt q;
   475    614     int nUncl;
          615  +  int nRow = 0;
   476    616   
   477    617     /* We should not ever get any private artifacts in the unclustered table.
   478    618     ** But if we do (because of a bug) now is a good time to delete them. */
   479    619     db_multi_exec(
   480    620       "DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)"
   481    621     );
   482    622   
   483    623     nUncl = db_int(0, "SELECT count(*) FROM unclustered /*scan*/"
   484    624                       " WHERE NOT EXISTS(SELECT 1 FROM phantom"
   485    625                                         " WHERE rid=unclustered.rid)");
   486         -  if( nUncl<100 ){
   487         -    return;
   488         -  }
   489         -  blob_zero(&cluster);
   490         -  db_prepare(&q, "SELECT uuid FROM unclustered, blob"
   491         -                 " WHERE NOT EXISTS(SELECT 1 FROM phantom"
   492         -                 "                   WHERE rid=unclustered.rid)"
   493         -                 "   AND unclustered.rid=blob.rid"
   494         -                 "   AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
   495         -                 " ORDER BY 1");
   496         -  while( db_step(&q)==SQLITE_ROW ){
   497         -    blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0));
   498         -  }
   499         -  db_finalize(&q);
   500         -  md5sum_blob(&cluster, &cksum);
   501         -  blob_appendf(&cluster, "Z %b\n", &cksum);
   502         -  blob_reset(&cksum);
   503         -  db_multi_exec("DELETE FROM unclustered");
   504         -  content_put(&cluster, 0, 0);
   505         -  blob_reset(&cluster);
          626  +  if( nUncl>=100 ){
          627  +    blob_zero(&cluster);
          628  +    db_prepare(&q, "SELECT uuid FROM unclustered, blob"
          629  +                   " WHERE NOT EXISTS(SELECT 1 FROM phantom"
          630  +                   "                   WHERE rid=unclustered.rid)"
          631  +                   "   AND unclustered.rid=blob.rid"
          632  +                   "   AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
          633  +                   " ORDER BY 1");
          634  +    while( db_step(&q)==SQLITE_ROW ){
          635  +      blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0));
          636  +      nRow++;
          637  +      if( nRow>=800 && nUncl>nRow+100 ){
          638  +        md5sum_blob(&cluster, &cksum);
          639  +        blob_appendf(&cluster, "Z %b\n", &cksum);
          640  +        blob_reset(&cksum);
          641  +        content_put(&cluster, 0, 0);
          642  +        blob_reset(&cluster);
          643  +        nUncl -= nRow;
          644  +        nRow = 0;
          645  +      }
          646  +    }
          647  +    db_finalize(&q);
          648  +    db_multi_exec("DELETE FROM unclustered");
          649  +    if( nRow>0 ){
          650  +      md5sum_blob(&cluster, &cksum);
          651  +      blob_appendf(&cluster, "Z %b\n", &cksum);
          652  +      blob_reset(&cksum);
          653  +      content_put(&cluster, 0, 0);
          654  +      blob_reset(&cluster);
          655  +    }
          656  +  }
   506    657   }
   507    658   
   508    659   /*
   509    660   ** Send an igot message for every entry in unclustered table.
   510    661   ** Return the number of cards sent.
   511    662   */
   512    663   static int send_unclustered(Xfer *pXfer){
................................................................................
   594    745     Xfer xfer;
   595    746     int deltaFlag = 0;
   596    747     int isClone = 0;
   597    748     int nGimme = 0;
   598    749     int size;
   599    750     int recvConfig = 0;
   600    751     char *zNow;
          752  +  const char *zPushHookPattern = db_get("push-hook-pattern-server", "");
          753  +  int lenPushHookPattern = (zPushHookPattern && zPushHookPattern[0])
          754  +                            ? strlen(zPushHookPattern) : 0;
   601    755   
   602    756     if( strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
   603    757        fossil_redirect_home();
   604    758     }
   605    759     memset(&xfer, 0, sizeof(xfer));
   606    760     blobarray_zero(xfer.aToken, count(xfer.aToken));
   607    761     cgi_set_content_type(g.zContentType);
   608    762     blob_zero(&xfer.err);
   609    763     xfer.pIn = &g.cgiIn;
   610    764     xfer.pOut = cgi_output_blob();
   611         -  xfer.mxSend = db_get_int("max-download", 5000000);
          765  +  xfer.mxSend = db_get_int("max-download", 20000000);
   612    766     g.xferPanic = 1;
   613    767   
   614    768     db_begin_transaction();
   615    769     db_multi_exec(
   616    770        "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);"
   617    771     );
   618         -  zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')");
   619         -  @ # timestamp %s(zNow)
   620    772     manifest_crosslink_begin();
   621    773     while( blob_line(xfer.pIn, &xfer.line) ){
   622         -    if( blob_buffer(&xfer.line)[0]=='#' ) continue;
          774  +    if( blob_buffer(&xfer.line)[0]=='#' ){
          775  +      if(    lenPushHookPattern
          776  +          && blob_buffer(&xfer.line)[1]
          777  +          && blob_buffer(&xfer.line)[2]
          778  +          && (0 == memcmp(blob_buffer(&xfer.line)+2,
          779  +                          zPushHookPattern, lenPushHookPattern))
          780  +      ){
          781  +        post_push_hook(blob_buffer(&xfer.line)+2,blob_buffer(&xfer.line)[1]);
          782  +      }
          783  +      continue;
          784  +    }
   623    785       xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
   624    786   
   625    787       /*   file UUID SIZE \n CONTENT
   626    788       **   file UUID DELTASRC SIZE \n CONTENT
   627    789       **
   628    790       ** Accept a file from the client.
   629    791       */
................................................................................
   630    792       if( blob_eq(&xfer.aToken[0], "file") ){
   631    793         if( !isPush ){
   632    794           cgi_reset_content();
   633    795           @ error not\sauthorized\sto\swrite
   634    796           nErr++;
   635    797           break;
   636    798         }
   637         -      xfer_accept_file(&xfer);
          799  +      xfer_accept_file(&xfer, 0);
   638    800         if( blob_size(&xfer.err) ){
   639    801           cgi_reset_content();
   640    802           @ error %T(blob_str(&xfer.err))
   641    803           nErr++;
   642    804           break;
   643    805         }
   644    806       }else
................................................................................
   716    878             }
   717    879           }else{
   718    880             isPush = 1;
   719    881           }
   720    882         }
   721    883       }else
   722    884   
   723         -    /*    clone
          885  +    /*    clone   ?PROTOCOL-VERSION?  ?SEQUENCE-NUMBER?
   724    886       **
   725    887       ** The client knows nothing.  Tell all.
   726    888       */
   727    889       if( blob_eq(&xfer.aToken[0], "clone") ){
          890  +      int iVers;
   728    891         login_check_credentials();
   729    892         if( !g.okClone ){
   730    893           cgi_reset_content();
   731    894           @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
   732    895           @ error not\sauthorized\sto\sclone
   733    896           nErr++;
   734    897           break;
   735    898         }
   736         -      isClone = 1;
   737         -      isPull = 1;
   738         -      deltaFlag = 1;
          899  +      if( xfer.nToken==3
          900  +       && blob_is_int(&xfer.aToken[1], &iVers)
          901  +       && iVers>=2
          902  +      ){
          903  +        int seqno, max;
          904  +        blob_is_int(&xfer.aToken[2], &seqno);
          905  +        max = db_int(0, "SELECT max(rid) FROM blob");
          906  +        while( xfer.mxSend>blob_size(xfer.pOut) && seqno<=max ){
          907  +          send_file(&xfer, seqno, 0, 1);
          908  +          seqno++;
          909  +        }
          910  +        if( seqno>=max ) seqno = 0;
          911  +        @ clone_seqno %d(seqno)
          912  +      }else{
          913  +        isClone = 1;
          914  +        isPull = 1;
          915  +        deltaFlag = 1;
          916  +      }
   739    917         @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
   740    918       }else
   741    919   
   742    920       /*    login  USER  NONCE  SIGNATURE
   743    921       **
   744    922       ** Check for a valid login.  This has to happen before anything else.
   745    923       ** The client can send multiple logins.  Permissions are cumulative.
................................................................................
   872   1050       create_cluster();
   873   1051       send_unclustered(&xfer);
   874   1052     }
   875   1053     if( recvConfig ){
   876   1054       configure_finalize_receive();
   877   1055     }
   878   1056     manifest_crosslink_end();
         1057  +
         1058  +  /* Send the server timestamp last, in case prior processing happened
         1059  +  ** to use up a significant fraction of our time window.
         1060  +  */
         1061  +  zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')");
         1062  +  @ # timestamp %s(zNow)
         1063  +  free(zNow);
         1064  +
   879   1065     db_end_transaction(0);
   880   1066   }
   881   1067   
   882   1068   /*
   883   1069   ** COMMAND: test-xfer
   884   1070   **
   885   1071   ** This command is used for debugging the server.  There is a single
................................................................................
   941   1127     int size;               /* Size of a config value */
   942   1128     int nFileSend = 0;
   943   1129     int origConfigRcvMask;  /* Original value of configRcvMask */
   944   1130     int nFileRecv;          /* Number of files received */
   945   1131     int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
   946   1132     const char *zCookie;    /* Server cookie */
   947   1133     int nSent, nRcvd;       /* Bytes sent and received (after compression) */
         1134  +  int cloneSeqno = 1;     /* Sequence number for clones */
   948   1135     Blob send;              /* Text we are sending to the server */
   949   1136     Blob recv;              /* Reply we got back from the server */
   950   1137     Xfer xfer;              /* Transfer data */
         1138  +  int pctDone;            /* Percentage done with a message */
         1139  +  int lastPctDone = -1;   /* Last displayed pctDone */
         1140  +  double rArrivalTime;    /* Time at which a message arrived */
   951   1141     const char *zSCode = db_get("server-code", "x");
   952   1142     const char *zPCode = db_get("project-code", 0);
         1143  +  const char *zPushHookPattern = db_get("push-hook-pattern-client", "");
         1144  +  int allowForced = db_get_boolean("push-hook-force", 0);
         1145  +
   953   1146   
   954   1147     if( db_get_boolean("dont-push", 0) ) pushFlag = 0;
   955   1148     if( pushFlag + pullFlag + cloneFlag == 0 
   956   1149        && configRcvMask==0 && configSendMask==0 ) return;
   957   1150   
   958   1151     transport_stats(0, 0, 1);
   959   1152     socket_global_init();
................................................................................
   975   1168     blob_zero(&xfer.line);
   976   1169     origConfigRcvMask = 0;
   977   1170   
   978   1171     /*
   979   1172     ** Always begin with a clone, pull, or push message
   980   1173     */
   981   1174     if( cloneFlag ){
   982         -    blob_appendf(&send, "clone\n");
         1175  +    blob_appendf(&send, "clone 2 %d\n", cloneSeqno);
   983   1176       pushFlag = 0;
   984   1177       pullFlag = 0;
   985   1178       nCardSent++;
   986   1179       /* TBD: Request all transferable configuration values */
         1180  +    content_enable_dephantomize(0);
   987   1181     }else if( pullFlag ){
   988   1182       blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
   989   1183       nCardSent++;
   990   1184     }
   991   1185     if( pushFlag ){
   992   1186       blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
   993   1187       nCardSent++;
................................................................................
  1007   1201       if( zCookie ){
  1008   1202         blob_appendf(&send, "cookie %s\n", zCookie);
  1009   1203       }
  1010   1204       
  1011   1205       /* Generate gimme cards for phantoms and leaf cards
  1012   1206       ** for all leaves.
  1013   1207       */
  1014         -    if( pullFlag || cloneFlag ){
         1208  +    if( pullFlag || (cloneFlag && cloneSeqno==1) ){
  1015   1209         request_phantoms(&xfer, mxPhantomReq);
  1016   1210       }
  1017   1211       if( pushFlag ){
  1018   1212         send_unsent(&xfer);
  1019   1213         nCardSent += send_unclustered(&xfer);
  1020   1214       }
  1021   1215   
................................................................................
  1056   1250       */
  1057   1251       zRandomness = db_text(0, "SELECT hex(randomblob(20))");
  1058   1252       blob_appendf(&send, "# %s\n", zRandomness);
  1059   1253       free(zRandomness);
  1060   1254   
  1061   1255       /* Exchange messages with the server */
  1062   1256       nFileSend = xfer.nFileSent + xfer.nDeltaSent;
  1063         -    fossil_print(zValueFormat, "Send:",
         1257  +    fossil_print(zValueFormat, "Sent:",
  1064   1258                    blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
  1065   1259                    xfer.nFileSent, xfer.nDeltaSent);
  1066   1260       nCardSent = 0;
  1067   1261       nCardRcvd = 0;
  1068   1262       xfer.nFileSent = 0;
  1069   1263       xfer.nDeltaSent = 0;
  1070   1264       xfer.nGimmeSent = 0;
  1071   1265       xfer.nIGotSent = 0;
         1266  +    if( !g.cgiOutput && !g.fQuiet ){
         1267  +      printf("waiting for server...");
         1268  +    }
  1072   1269       fflush(stdout);
  1073   1270       http_exchange(&send, &recv, cloneFlag==0 || nCycle>0);
         1271  +    lastPctDone = -1;
  1074   1272       blob_reset(&send);
         1273  +    rArrivalTime = db_double(0.0, "SELECT julianday('now')");
  1075   1274   
  1076   1275       /* Begin constructing the next message (which might never be
  1077   1276       ** sent) by beginning with the pull or push cards
  1078   1277       */
  1079   1278       if( pullFlag ){
  1080   1279         blob_appendf(&send, "pull %s %s\n", zSCode, zPCode);
  1081   1280         nCardSent++;
................................................................................
  1084   1283         blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
  1085   1284         nCardSent++;
  1086   1285       }
  1087   1286       go = 0;
  1088   1287   
  1089   1288       /* Process the reply that came back from the server */
  1090   1289       while( blob_line(&recv, &xfer.line) ){
         1290  +      if( g.fHttpTrace ){
         1291  +        printf("\rGOT: %.*s", (int)blob_size(&xfer.line),
         1292  +                              blob_buffer(&xfer.line));
         1293  +      }
  1091   1294         if( blob_buffer(&xfer.line)[0]=='#' ){
  1092   1295           const char *zLine = blob_buffer(&xfer.line);
  1093   1296           if( memcmp(zLine, "# timestamp ", 12)==0 ){
  1094   1297             char zTime[20];
  1095   1298             double rDiff;
  1096   1299             sqlite3_snprintf(sizeof(zTime), zTime, "%.19s", &zLine[12]);
  1097         -          rDiff = db_double(9e99, "SELECT julianday('%q') - julianday('now')",
  1098         -                            zTime);
         1300  +          rDiff = db_double(9e99, "SELECT julianday('%q') - %.17g",
         1301  +                            zTime, rArrivalTime);
  1099   1302             if( rDiff<0.0 ) rDiff = -rDiff;
  1100   1303             if( rDiff>9e98 ) rDiff = 0.0;
  1101   1304             if( (rDiff*24.0*3600.0)>=60.0 ){
  1102   1305               fossil_warning("*** time skew *** server time differs by %s",
  1103   1306                              db_timespan_name(rDiff));
  1104   1307               g.clockSkewSeen = 1;
  1105   1308             }
  1106   1309           }
  1107   1310           continue;
  1108   1311         }
  1109   1312         xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
  1110   1313         nCardRcvd++;
  1111   1314         if( !g.cgiOutput && !g.fQuiet ){
  1112         -        printf("\r%d", nCardRcvd);
  1113         -        fflush(stdout);
         1315  +        pctDone = (recv.iCursor*100)/recv.nUsed;
         1316  +        if( pctDone!=lastPctDone ){
         1317  +          printf("\rprocessed: %d%%         ", pctDone);
         1318  +          lastPctDone = pctDone;
         1319  +          fflush(stdout);
         1320  +        }
  1114   1321         }
  1115   1322   
  1116   1323         /*   file UUID SIZE \n CONTENT
  1117   1324         **   file UUID DELTASRC SIZE \n CONTENT
  1118   1325         **
  1119   1326         ** Receive a file transmitted from the server.
  1120   1327         */
  1121   1328         if( blob_eq(&xfer.aToken[0],"file") ){
  1122         -        xfer_accept_file(&xfer);
         1329  +        xfer_accept_file(&xfer, cloneFlag);
  1123   1330         }else
  1124   1331   
  1125   1332         /*   gimme UUID
  1126   1333         **
  1127   1334         ** Server is requesting a file.  If the file is a manifest, assume
  1128   1335         ** that the server will also want to know all of the content files
  1129   1336         ** associated with the manifest and send those too.
................................................................................
  1175   1382           if( blob_eq_str(&xfer.aToken[1], zSCode, -1) ){
  1176   1383             fossil_fatal("server loop");
  1177   1384           }
  1178   1385           if( zPCode==0 ){
  1179   1386             zPCode = mprintf("%b", &xfer.aToken[2]);
  1180   1387             db_set("project-code", zPCode, 0);
  1181   1388           }
  1182         -        blob_appendf(&send, "clone\n");
         1389  +        blob_appendf(&send, "clone 2 %d\n", cloneSeqno);
  1183   1390           nCardSent++;
  1184   1391         }else
  1185   1392         
  1186   1393         /*   config NAME SIZE \n CONTENT
  1187   1394         **
  1188   1395         ** Receive a configuration value from the server.
  1189   1396         */
................................................................................
  1234   1441         **
  1235   1442         ** Each cookie received overwrites the prior cookie from the
  1236   1443         ** same server.
  1237   1444         */
  1238   1445         if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){
  1239   1446           db_set("cookie", blob_str(&xfer.aToken[1]), 0);
  1240   1447         }else
         1448  +
         1449  +      /*    clone_seqno N
         1450  +      **
         1451  +      ** When doing a clone, the server tries to send all of its artifacts
         1452  +      ** in sequence.  This card indicates the sequence number of the next
         1453  +      ** blob that needs to be sent.  If N<=0 that indicates that all blobs
         1454  +      ** have been sent.
         1455  +      */
         1456  +      if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){
         1457  +        blob_is_int(&xfer.aToken[1], &cloneSeqno);
         1458  +      }else
  1241   1459   
  1242   1460         /*   message MESSAGE
  1243   1461         **
  1244   1462         ** Print a message.  Similar to "error" but does not stop processing.
  1245   1463         **
  1246   1464         ** If the "login failed" message is seen, clear the sync password prior
  1247   1465         ** to the next cycle.
................................................................................
  1312   1530       ** there are still phantoms, then go another round.
  1313   1531       */
  1314   1532       nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile;
  1315   1533       if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){
  1316   1534         go = 1;
  1317   1535         mxPhantomReq = nFileRecv*2;
  1318   1536         if( mxPhantomReq<200 ) mxPhantomReq = 200;
         1537  +    }else if( cloneFlag && nFileRecv>0 ){
         1538  +      go = 1;
  1319   1539       }
  1320   1540       nCardRcvd = 0;
  1321   1541       xfer.nFileRcvd = 0;
  1322   1542       xfer.nDeltaRcvd = 0;
  1323   1543       xfer.nDanglingFile = 0;
  1324   1544   
  1325   1545       /* If we have one or more files queued to send, then go
................................................................................
  1327   1547       */
  1328   1548       if( xfer.nFileSent+xfer.nDeltaSent>0 ){
  1329   1549         go = 1;
  1330   1550       }
  1331   1551   
  1332   1552       /* If this is a clone, the go at least two rounds */
  1333   1553       if( cloneFlag && nCycle==1 ) go = 1;
         1554  +
         1555  +    /* Stop the cycle if the server sends a "clone_seqno 0" card */
         1556  +    if( cloneSeqno<=0 ) go = 0;   
  1334   1557     };
         1558  +  if( pushFlag && ( (nFileSend > 0) || allowForced ) ){
         1559  +    if( zPushHookPattern && zPushHookPattern[0] ){
         1560  +      blob_appendf(&send, "#%s%s\n",
         1561  +                   ((nFileSend > 0)?"P":"F"), zPushHookPattern);
         1562  +      fossil_print("Triggering push hook %s '%s'\n",((nFileSend > 0)?"P":"F"),zPushHookPattern);
         1563  +      http_exchange(&send, &recv, cloneFlag==0 || nCycle>0);
         1564  +      blob_reset(&send);
         1565  +      nCardSent++;
         1566  +    }
         1567  +  int allowForced = db_get_boolean("push-hook-force", 0);
         1568  +  }
  1335   1569     transport_stats(&nSent, &nRcvd, 1);
  1336   1570     fossil_print("Total network traffic: %d bytes sent, %d bytes received\n",
  1337   1571                  nSent, nRcvd);
  1338   1572     transport_close();
  1339   1573     transport_global_shutdown();
  1340   1574     db_multi_exec("DROP TABLE onremote");
  1341   1575     manifest_crosslink_end();
         1576  +  content_enable_dephantomize(1);
  1342   1577     db_end_transaction(0);
  1343   1578   }

Changes to src/zip.c.

   311    311   ** added to as part of the zip file. It may be 0 or an empty string,
   312    312   ** in which case it is ignored. The intention is to create a zip which
   313    313   ** politely expands into a subdir instead of filling your current dir
   314    314   ** with source files. For example, pass a UUID or "ProjectName".
   315    315   **
   316    316   */
   317    317   void zip_of_baseline(int rid, Blob *pZip, const char *zDir){
   318         -  int i;
   319         -  Blob mfile, file, hash;
   320         -  Manifest m;
          318  +  Blob mfile, hash, file;
          319  +  Manifest *pManifest;
          320  +  ManifestFile *pFile;
   321    321     Blob filename;
   322    322     int nPrefix;
   323    323     
   324    324     content_get(rid, &mfile);
   325    325     if( blob_size(&mfile)==0 ){
   326    326       blob_zero(pZip);
   327    327       return;
   328    328     }
   329         -  blob_zero(&file);
   330    329     blob_zero(&hash);
   331         -  blob_copy(&file, &mfile);
   332    330     blob_zero(&filename);
   333    331     zip_open();
   334    332   
   335    333     if( zDir && zDir[0] ){
   336    334       blob_appendf(&filename, "%s/", zDir);
   337    335     }
   338    336     nPrefix = blob_size(&filename);
   339    337   
   340         -  if( manifest_parse(&m, &mfile) ){
          338  +  pManifest = manifest_get(rid, CFTYPE_MANIFEST);
          339  +  if( pManifest ){
   341    340       char *zName;
   342         -    zip_set_timedate(m.rDate);
   343         -    blob_append(&filename, "manifest", -1);
   344         -    zName = blob_str(&filename);
   345         -    zip_add_folders(zName);
   346         -    zip_add_file(zName, &file);
   347         -    sha1sum_blob(&file, &hash);
   348         -    blob_reset(&file);
   349         -    blob_append(&hash, "\n", 1);
   350         -    blob_resize(&filename, nPrefix);
   351         -    blob_append(&filename, "manifest.uuid", -1);
   352         -    zName = blob_str(&filename);
   353         -    zip_add_file(zName, &hash);
   354         -    blob_reset(&hash);
   355         -    for(i=0; i<m.nFile; i++){
   356         -      int fid = uuid_to_rid(m.aFile[i].zUuid, 0);
          341  +    zip_set_timedate(pManifest->rDate);
          342  +    if( db_get_boolean("manifest", 0) ){
          343  +      blob_append(&filename, "manifest", -1);
          344  +      zName = blob_str(&filename);
          345  +      zip_add_folders(zName);
          346  +      zip_add_file(zName, &mfile);
          347  +      sha1sum_blob(&mfile, &hash);
          348  +      blob_reset(&mfile);
          349  +      blob_append(&hash, "\n", 1);
          350  +      blob_resize(&filename, nPrefix);
          351  +      blob_append(&filename, "manifest.uuid", -1);
          352  +      zName = blob_str(&filename);
          353  +      zip_add_file(zName, &hash);
          354  +      blob_reset(&hash);
          355  +    }
          356  +    manifest_file_rewind(pManifest);
          357  +    while( (pFile = manifest_file_next(pManifest,0))!=0 ){
          358  +      int fid = uuid_to_rid(pFile->zUuid, 0);
   357    359         if( fid ){
   358    360           content_get(fid, &file);
   359    361           blob_resize(&filename, nPrefix);
   360         -        blob_append(&filename, m.aFile[i].zName, -1);
          362  +        blob_append(&filename, pFile->zName, -1);
   361    363           zName = blob_str(&filename);
   362    364           zip_add_folders(zName);
   363    365           zip_add_file(zName, &file);
   364    366           blob_reset(&file);
   365    367         }
   366    368       }
   367         -    manifest_clear(&m);
   368    369     }else{
   369    370       blob_reset(&mfile);
   370         -    blob_reset(&file);
   371    371     }
          372  +  manifest_destroy(pManifest);
   372    373     blob_reset(&filename);
   373    374     zip_close(pZip);
   374    375   }
   375    376   
   376    377   /*
   377    378   ** COMMAND: zip
   378    379   **

Changes to win/Makefile.dmc.

    37     37   
    38     38   all: $(APPNAME)
    39     39   
    40     40   $(APPNAME) : translate$E mkindex$E headers fossil.res $(OBJ) $(OBJDIR)\link
    41     41   	cd $(OBJDIR) 
    42     42   	$(DMDIR)\bin\link @link
    43     43   
    44         -fossil.res:	$B\win\fossil.rc
    45         -	$(RC) $(RCFLAGS) -o$@ $**
           44  +fossil.res:	$B\win\fossil.rc VERSION.h
           45  +	$(RC) $(RCFLAGS) -o$@ $B\win\fossil.rc
    46     46   
    47     47   $(OBJDIR)\link: $B\win\Makefile.dmc
    48     48   	+echo add allrepo attach bag blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event file finfo graph http http_socket http_ssl http_transport info login main manifest md5 merge merge3 name pivot popen pqueue printf rebuild report rss schema search setup sha1 shun skins stat style sync tag th_main timeline tkt tktsetup undo update url user verify vfile wiki wikiformat winhttp xfer zip sqlite3 th th_lang > $@
    49     49   	+echo fossil >> $@
    50     50   	+echo fossil >> $@
    51     51   	+echo $(LIBS) >> $@
    52     52   	+echo. >> $@

Changes to www/fileformat.wiki.

    93     93   No card may be duplicated.
    94     94   The entire manifest may be PGP clear-signed, but otherwise it
    95     95   may contain no additional text or data beyond what is described here.
    96     96   
    97     97   Allowed cards in the manifest are as follows:
    98     98   
    99     99   <blockquote>
          100  +<b>B</b> <i>baseline-manifest</i><br>
   100    101   <b>C</b> <i>checkin-comment</i><br>
   101    102   <b>D</b> <i>time-and-date-stamp</i><br>
   102    103   <b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br>
   103    104   <b>P</b> <i>SHA1-hash</i>+<br>
   104    105   <b>R</b> <i>repository-checksum</i><br>
   105    106   <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name  <b>*</b> ?value?</i><br>
   106    107   <b>U</b> <i>user-login</i><br>
   107    108   <b>Z</b> <i>manifest-checksum</i>
   108    109   </blockquote>
          110  +
          111  +A manifest may optionally have a single B-card.  The B-card specifies
          112  +another manifest that serves as the "baseline" for this manifest.  A
          113  +manifest that has a B-card is called a delta-manifest and a manifest
          114  +that omits the B-card is a baseline-manifest.  The other manifest
          115  +identified by the argument of the B-card must be a baseline-manifest.
          116  +A baseline-manifest records the complete contents of a checkin.
          117  +A delta-manifest records only changes from its baseline.  
   109    118   
   110    119   A manifest must have exactly one C-card.  The sole argument to
   111    120   the C-card is a check-in comment that describes the check-in that
   112    121   the manifest defines.  The check-in comment is text.  The following
   113    122   escape sequences are applied to the text:
   114    123   A space (ASCII 0x20) is represented as "\s" (ASCII 0x5C, 0x73).  A
   115    124   newline (ASCII 0x0a) is "\n" (ASCII 0x6C, x6E).  A backslash 
................................................................................
   123    132   date and time should be in coordinated universal time (UTC).
   124    133   The format is:
   125    134   
   126    135   <blockquote>
   127    136   <i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i>
   128    137   </blockquote>
   129    138   
   130         -A manifest has zero or more F-cards.  Each F-card defines a file
   131         -(other than the manifest itself) which is part of the check-in that
   132         -the manifest defines.  There are two, three, or four arguments.
          139  +A manifest has zero or more F-cards.  Each F-card identifies a file
          140  +that is part of the check-in.  There are one, two, three, or four arguments.
   133    141   The first argument
   134    142   is the pathname of the file in the check-in relative to the root
   135    143   of the project file hierarchy.  No ".." or "." directories are allowed
   136    144   within the filename.  Space characters are escaped as in C-card
   137    145   comment text.  Backslash characters and newlines are not allowed
   138    146   within filenames.  The directory separator character is a forward
   139    147   slash (ASCII 0x2F).  The second argument to the F-card is the
   140    148   full 40-character lower-case hexadecimal SHA1 hash of the content
   141         -artifact.  The optional 3rd argument defines any special access 
          149  +artifact.  The second argument is required for baseline manifests
          150  +but is optional for delta manifests.  When the second argument to the
          151  +F-card is omitted, it means that the file has been deleted relative
          152  +to the baseline.  The optional 3rd argument defines any special access 
   142    153   permissions associated with the file.  The only special code currently
   143    154   defined is "x" which means that the file is executable.  All files are
   144    155   always readable and writable.  This can be expressed by "w" permission
   145         -if desired but is optional.
          156  +if desired but is optional.  The file format might be extended with
          157  +new permission letters in the future.
   146    158   The optional 4th argument is the name of the same file as it existed in
   147    159   the parent check-in.  If the name of the file is unchanged from its
   148    160   parent, then the 4th argument is omitted.
   149    161   
   150    162   A manifest has zero or one P-cards.  Most manifests have one P-card.
   151    163   The P-card has a varying number of arguments that
   152    164   defines other manifests from which the current manifest
................................................................................
   502    514   <td>&nbsp;</td>
   503    515   <td>&nbsp;</td>
   504    516   <td>&nbsp;</td>
   505    517   <td>&nbsp;</td>
   506    518   <td align=center><b>X</b></td>
   507    519   <td>&nbsp;</td>
   508    520   </tr>
          521  +<tr>
          522  +<td><b>B</b> <i>baseline</i></td>
          523  +<td align=center><b>X</b></td>
          524  +<td>&nbsp;</td>
          525  +<td>&nbsp;</td>
          526  +<td>&nbsp;</td>
          527  +<td>&nbsp;</td>
          528  +<td>&nbsp;</td>
          529  +<td>&nbsp;</td>
          530  +</tr>
   509    531   <tr>
   510    532   <td><b>C</b> <i>comment-text</i></td>
   511    533   <td align=center><b>X</b></td>
   512    534   <td>&nbsp;</td>
   513    535   <td>&nbsp;</td>
   514    536   <td>&nbsp;</td>
   515    537   <td>&nbsp;</td>