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&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&ci=tip", 171 179 g.zBaseURL, zD); 172 - style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", 173 - g.zBaseURL,zD); 180 + if( hasTrunk ){ 181 + style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&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&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&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> </td> 503 515 <td> </td> 504 516 <td> </td> 505 517 <td> </td> 506 518 <td align=center><b>X</b></td> 507 519 <td> </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> </td> 525 +<td> </td> 526 +<td> </td> 527 +<td> </td> 528 +<td> </td> 529 +<td> </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> </td> 513 535 <td> </td> 514 536 <td> </td> 515 537 <td> </td>