Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -41,13 +41,10 @@ # other dependencies. We sometimes add the -static option here # so that we can build a static executable that will run in a # chroot jail. # LIB = -lz $(LDFLAGS) -HOST_OS!= uname -s -LIB.SunOS= -lsocket -lnsl -LIB += $(LIB.$(HOST_OS)) # If using HTTPS: LIB += -lcrypto -lssl #### Tcl shell for use in running the fossil testsuite. @@ -54,6 +51,19 @@ # TCLSH = tclsh # You should not need to change anything below this line ############################################################################### +# +# Automatic platform-specific options. +HOST_OS!= uname -s + +LIB.SunOS= -lsocket -lnsl +LIB += $(LIB.$(HOST_OS)) + +TCC.DragonFly += -DUSE_PREAD +TCC.FreeBSD += -DUSE_PREAD +TCC.NetBSD += -DUSE_PREAD +TCC.OpenBSD += -DUSE_PREAD +TCC += $(TCC.$(HOST_OS)) + include $(SRCDIR)/main.mk Index: src/branch.c ================================================================== --- src/branch.c +++ src/branch.c @@ -35,12 +35,11 @@ const char *zBranch; /* Name of the new branch */ char *zDate; /* Date that branch was created */ char *zComment; /* Check-in comment for the new branch */ const char *zColor; /* Color of the new branch */ Blob branch; /* manifest for the new branch */ - Blob parent; /* root check-in manifest */ - Manifest mParent; /* Parsed parent manifest */ + Manifest *pParent; /* Parsed parent manifest */ Blob mcksum; /* Self-checksum on the manifest */ const char *zDateOvrd; /* Override date string */ const char *zUserOvrd; /* Override user name */ noSign = find_option("nosign","",0)!=0; @@ -71,41 +70,42 @@ db_begin_transaction(); rootid = name_to_rid(g.argv[4]); if( rootid==0 ){ fossil_fatal("unable to locate check-in off of which to branch"); } + + pParent = manifest_get(rootid, CFTYPE_MANIFEST); + if( pParent==0 ){ + fossil_fatal("%s is not a valid check-in", g.argv[4]); + } /* Create a manifest for the new branch */ blob_zero(&branch); + if( pParent->zBaseline ){ + blob_appendf(&branch, "B %s\n", pParent->zBaseline); + } zComment = mprintf("Create new branch named \"%h\"", zBranch); blob_appendf(&branch, "C %F\n", zComment); zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); zDate[10] = 'T'; blob_appendf(&branch, "D %s\n", zDate); /* Copy all of the content from the parent into the branch */ - content_get(rootid, &parent); - manifest_parse(&mParent, &parent); - if( mParent.type!=CFTYPE_MANIFEST ){ - fossil_fatal("%s is not a valid check-in", g.argv[4]); - } - for(i=0; inFile; ++i){ + blob_appendf(&branch, "F %F", pParent->aFile[i].zName); + if( pParent->aFile[i].zUuid ){ + blob_appendf(&branch, " %s", pParent->aFile[i].zUuid); + if( pParent->aFile[i].zPerm && pParent->aFile[i].zPerm[0] ){ + blob_appendf(&branch, " %s", pParent->aFile[i].zPerm); + } + } + blob_append(&branch, "\n", 1); } zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); blob_appendf(&branch, "P %s\n", zUuid); - blob_appendf(&branch, "R %s\n", mParent.zRepoCksum); - manifest_clear(&mParent); + blob_appendf(&branch, "R %s\n", pParent->zRepoCksum); + manifest_destroy(pParent); /* Add the symbolic branch name and the "branch" tag to identify ** this as a new branch */ if( zColor!=0 ){ blob_appendf(&branch, "T *bgcolor * %F\n", zColor); Index: src/browse.c ================================================================== --- src/browse.c +++ src/browse.c @@ -71,19 +71,24 @@ ** There is no hyperlink on the file element of the path. ** ** The computed string is appended to the pOut blob. pOut should ** have already been initialized. */ -void hyperlinked_path(const char *zPath, Blob *pOut){ +void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){ int i, j; char *zSep = ""; for(i=0; zPath[i]; i=j){ for(j=i; zPath[j] && zPath[j]!='/'; j++){} if( zPath[j] && g.okHistory ){ - blob_appendf(pOut, "%s%#h", - zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]); + if( zCI ){ + blob_appendf(pOut, "%s%#h", + zSep, g.zBaseURL, zCI, j, zPath, j-i, &zPath[i]); + }else{ + blob_appendf(pOut, "%s%#h", + zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]); + } }else{ blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); } zSep = "/"; while( zPath[j]=='/' ){ j++; } @@ -99,21 +104,21 @@ ** name=PATH Directory to display. Required. ** ci=LABEL Show only files in this check-in. Optional. */ void page_dir(void){ const char *zD = P("name"); + int nD = zD ? strlen(zD)+1 : 0; int mxLen; int nCol, nRow; int cnt, i; char *zPrefix; Stmt q; const char *zCI = P("ci"); int rid = 0; char *zUuid = 0; - Blob content; Blob dirname; - Manifest m; + Manifest *pM = 0; const char *zSubdirLink; login_check_credentials(); if( !g.okHistory ){ login_needed(); return; } style_header("File List"); @@ -126,25 +131,24 @@ /* If a specific check-in is requested, fetch and parse it. If the ** specific check-in does not exist, clear zCI. zCI==0 will cause all ** files from all check-ins to be displayed. */ if( zCI ){ - if( (rid = name_to_rid(zCI))==0 || content_get(rid, &content)==0 ){ - zCI = 0; /* No artifact named zCI exists */ - }else if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){ - zCI = 0; /* The artifact exists but is not a manifest */ + pM = manifest_get_by_name(zCI, &rid); + if( pM ){ + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); }else{ - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); + zCI = 0; } } /* Compute the title of the page */ blob_zero(&dirname); if( zD ){ blob_append(&dirname, "in directory ", -1); - hyperlinked_path(zD, &dirname); + hyperlinked_path(zD, &dirname, zCI); zPrefix = mprintf("%h/", zD); }else{ blob_append(&dirname, "in the top-level directory", -1); zPrefix = ""; } @@ -160,22 +164,30 @@ style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); }else{ style_submenu_element("All", "All", "%s/dir", g.zBaseURL); } }else{ + int hasTrunk; @

The union of all files from all check-ins @ %s(blob_str(&dirname))

+ hasTrunk = db_exists( + "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'", + TAG_BRANCH); zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); if( zD ){ style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL); style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", g.zBaseURL, zD); - style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", - g.zBaseURL,zD); + if( hasTrunk ){ + style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", + g.zBaseURL,zD); + } }else{ style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL); - style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); + if( hasTrunk ){ + style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); + } } } /* Compute the temporary table "localfiles" containing the names ** of all files and subdirectories in the zD[] directory. @@ -184,39 +196,47 @@ ** first and it also gives us an easy way to distinguish files ** from directories in the loop that follows. */ db_multi_exec( "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);" - "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);" ); if( zCI ){ Stmt ins; - int i; - db_prepare(&ins, "INSERT INTO allfiles VALUES(:x, :u)"); - for(i=0; i0 && memcmp(pFile->zName, zD, nD-1)!=0 ) continue; + if( pPrev && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 ){ + continue; + } + db_bind_text(&ins, ":x", &pFile->zName[nD]); + db_bind_text(&ins, ":u", pFile->zUuid); + db_step(&ins); + db_reset(&ins); + pPrev = pFile; + for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} + if( c=='/' ) nPrev++; + } + db_finalize(&ins); + }else if( zD ){ + db_multi_exec( + "INSERT OR IGNORE INTO localfiles" + " SELECT pathelement(name,%d), NULL FROM filename" + " WHERE name GLOB '%q/*'", + nD, zD + ); + }else{ + db_multi_exec( + "INSERT OR IGNORE INTO localfiles" + " SELECT pathelement(name,0), NULL FROM filename" ); } /* Generate a multi-column table listing the contents of zD[] ** directory. @@ -246,8 +266,9 @@ @
  • %h(zFN) @
  • } } db_finalize(&q); + manifest_destroy(pM); @ style_footer_cmdref("ls",0); } Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -264,10 +264,11 @@ Blob repo; Stmt q; int n; const char *zIgnoreFlag = find_option("ignore",0,1); int allFlag = find_option("dotfiles",0,0)!=0; + int outputManifest = db_get_boolean("manifest",0); db_must_be_within_tree(); db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); n = strlen(g.zLocalRoot); blob_init(&path, g.zLocalRoot, n-1); @@ -275,16 +276,18 @@ zIgnoreFlag = db_get("ignore-glob", 0); } vfile_scan(0, &path, blob_size(&path), allFlag); db_prepare(&q, "SELECT x FROM sfile" - " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_'," + " WHERE x NOT IN ('%s','%s','_FOSSIL_'," "'_FOSSIL_-journal','.fos','.fos-journal'," "'_FOSSIL_-wal','_FOSSIL_-shm','.fos-wal'," "'.fos-shm')" " AND NOT %s" " ORDER BY 1", + outputManifest ? "manifest" : "_FOSSIL_", + outputManifest ? "manifest.uuid" : "_FOSSIL_", glob_expr("x", zIgnoreFlag) ); if( file_tree_name(g.zRepositoryName, &repo, 0) ){ db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); } @@ -541,21 +544,158 @@ zDate[10] = 'T'; return zDate; } /* -** Return TRUE (non-zero) if a file named "zFilename" exists in -** the checkout identified by vid. -** -** The original purpose of this routine was to check for the presence of -** a "checked-in" file named "manifest" or "manifest.uuid" so as to avoid -** overwriting that file with automatically generated files. +** Create a manifest. */ -int file_exists_in_checkout(int vid, const char *zFilename){ - return db_exists("SELECT 1 FROM vfile WHERE vid=%d AND pathname=%Q", - vid, zFilename); +static void create_manifest( + Blob *pOut, /* Write the manifest here */ + const char *zBaselineUuid, /* UUID of baseline, or zero */ + Manifest *pBaseline, /* Make it a delta manifest if not zero */ + Blob *pComment, /* Check-in comment text */ + int vid, /* blob-id of the parent manifest */ + int verifyDate, /* Verify that child is younger */ + Blob *pCksum, /* Repository checksum. May be 0 */ + const char *zDateOvrd, /* Date override. If 0 then use 'now' */ + const char *zUserOvrd, /* User override. If 0 then use g.zLogin */ + const char *zBranch, /* Branch name. May be 0 */ + const char *zBgColor, /* Background color. May be 0 */ + int *pnFBcard /* Number of generated B- and F-cards */ +){ + char *zDate; /* Date of the check-in */ + char *zParentUuid; /* UUID of parent check-in */ + Blob filename; /* A single filename */ + int nBasename; /* Size of base filename */ + Stmt q; /* Query of files changed */ + Stmt q2; /* Query of merge parents */ + Blob mcksum; /* Manifest checksum */ + ManifestFile *pFile; /* File from the baseline */ + int nFBcard = 0; /* Number of B-cards and F-cards */ + + assert( pBaseline==0 || pBaseline->zBaseline==0 ); + assert( pBaseline==0 || zBaselineUuid!=0 ); + blob_zero(pOut); + zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); + if( pBaseline ){ + blob_appendf(pOut, "B %s\n", zBaselineUuid); + manifest_file_rewind(pBaseline); + pFile = manifest_file_next(pBaseline, 0); + nFBcard++; + }else{ + pFile = 0; + } + blob_appendf(pOut, "C %F\n", blob_str(pComment)); + zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); + blob_appendf(pOut, "D %s\n", zDate); + zDate[10] = ' '; + db_prepare(&q, + "SELECT pathname, uuid, origname, blob.rid, isexe" + " FROM vfile JOIN blob ON vfile.mrid=blob.rid" + " WHERE NOT deleted AND vfile.vid=%d" + " ORDER BY 1", vid); + blob_zero(&filename); + blob_appendf(&filename, "%s", g.zLocalRoot); + nBasename = blob_size(&filename); + while( db_step(&q)==SQLITE_ROW ){ + const char *zName = db_column_text(&q, 0); + const char *zUuid = db_column_text(&q, 1); + const char *zOrig = db_column_text(&q, 2); + int frid = db_column_int(&q, 3); + int isexe = db_column_int(&q, 4); + const char *zPerm; + int cmp; + blob_append(&filename, zName, -1); +#if !defined(_WIN32) + /* For unix, extract the "executable" permission bit directly from + ** the filesystem. On windows, the "executable" bit is retained + ** unchanged from the original. */ + isexe = file_isexe(blob_str(&filename)); +#endif + if( isexe ){ + zPerm = " x"; + }else{ + zPerm = ""; + } + if( !g.markPrivate ) content_make_public(frid); + while( pFile && strcmp(pFile->zName,zName)<0 ){ + blob_appendf(pOut, "F %F\n", pFile->zName); + pFile = manifest_file_next(pBaseline, 0); + nFBcard++; + } + cmp = 1; + if( pFile==0 + || (cmp = strcmp(pFile->zName,zName))!=0 + || strcmp(pFile->zUuid, zUuid)!=0 + ){ + blob_resize(&filename, nBasename); + if( zOrig==0 || strcmp(zOrig,zName)==0 ){ + blob_appendf(pOut, "F %F %s%s\n", zName, zUuid, zPerm); + }else{ + if( zPerm[0]==0 ){ zPerm = " w"; } + blob_appendf(pOut, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); + } + nFBcard++; + } + if( cmp==0 ) pFile = manifest_file_next(pBaseline,0); + } + blob_reset(&filename); + db_finalize(&q); + while( pFile ){ + blob_appendf(pOut, "F %F\n", pFile->zName); + pFile = manifest_file_next(pBaseline, 0); + nFBcard++; + } + blob_appendf(pOut, "P %s", zParentUuid); + if( verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); + free(zParentUuid); + db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); + db_bind_int(&q2, ":id", 0); + while( db_step(&q2)==SQLITE_ROW ){ + char *zMergeUuid; + int mid = db_column_int(&q2, 0); + if( !g.markPrivate && content_is_private(mid) ) continue; + zMergeUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); + if( zMergeUuid ){ + blob_appendf(pOut, " %s", zMergeUuid); + if( verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); + free(zMergeUuid); + } + } + db_finalize(&q2); + free(zDate); + + blob_appendf(pOut, "\n"); + if( pCksum ) blob_appendf(pOut, "R %b\n", pCksum); + if( zBranch && zBranch[0] ){ + Stmt q; + if( zBgColor && zBgColor[0] ){ + blob_appendf(pOut, "T *bgcolor * %F\n", zBgColor); + } + blob_appendf(pOut, "T *branch * %F\n", zBranch); + blob_appendf(pOut, "T *sym-%F *\n", zBranch); + + /* Cancel all other symbolic tags */ + db_prepare(&q, + "SELECT tagname FROM tagxref, tag" + " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" + " AND tagtype>0 AND tagname GLOB 'sym-*'" + " AND tagname!='sym-'||%Q" + " ORDER BY tagname", + vid, zBranch); + while( db_step(&q)==SQLITE_ROW ){ + const char *zTag = db_column_text(&q, 0); + blob_appendf(pOut, "T -%F *\n", zTag); + } + db_finalize(&q); + } + blob_appendf(pOut, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); + md5sum_blob(pOut, &mcksum); + blob_appendf(pOut, "Z %b\n", &mcksum); + if( pnFBcard ) *pnFBcard = nFBcard; } + /* ** COMMAND: ci ** COMMAND: commit ** @@ -590,39 +730,52 @@ ** --branch NEW-BRANCH-NAME ** --bgcolor COLOR ** --nosign ** --force|-f ** --private +** --baseline +** --delta ** */ void commit_cmd(void){ - int rc; - int vid, nrid, nvid; - Blob comment; - const char *zComment; - Stmt q; - Stmt q2; - char *zUuid, *zDate; + int hasChanges; /* True if unsaved changes exist */ + int vid; /* blob-id of parent version */ + int nrid; /* blob-id of a modified file */ + int nvid; /* Blob-id of the new check-in */ + Blob comment; /* Check-in comment */ + const char *zComment; /* Check-in comment */ + Stmt q; /* Query to find files that have been modified */ + char *zUuid; /* UUID of the new check-in */ int noSign = 0; /* True to omit signing the manifest using GPG */ int isAMerge = 0; /* True if checking in a merge */ int forceFlag = 0; /* Force a fork */ + int forceDelta = 0; /* Force a delta-manifest */ + int forceBaseline = 0; /* Force a baseline-manifest */ char *zManifestFile; /* Name of the manifest file */ - int nBasename; /* Length of "g.zLocalRoot/" */ + int useCksum; /* True if checksums should be computed and verified */ + int outputManifest; /* True to output "manifest" and "manifest.uuid" */ + int testRun; /* True for a test run. Debugging only */ const char *zBranch; /* Create a new branch with this name */ const char *zBgColor; /* Set background color when branching */ const char *zDateOvrd; /* Override date string */ const char *zUserOvrd; /* Override user name */ const char *zComFile; /* Read commit message from this file */ - Blob filename; /* complete filename */ - Blob manifest; + Blob manifest; /* Manifest in baseline form */ Blob muuid; /* Manifest uuid */ - Blob mcksum; /* Self-checksum on the manifest */ Blob cksum1, cksum2; /* Before and after commit checksums */ Blob cksum1b; /* Checksum recorded in the manifest */ + int szD; /* Size of the delta manifest */ + int szB; /* Size of the baseline manifest */ url_proxy_options(); noSign = find_option("nosign",0,0)!=0; + forceDelta = find_option("delta",0,0)!=0; + forceBaseline = find_option("baseline",0,0)!=0; + if( forceDelta && forceBaseline ){ + fossil_fatal("cannot use --delta and --baseline together"); + } + testRun = find_option("test",0,0)!=0; zComment = find_option("comment","m",1); forceFlag = find_option("force", "f", 0)!=0; zBranch = find_option("branch","b",1); zBgColor = find_option("bgcolor",0,1); zComFile = find_option("message-file", "M", 1); @@ -634,11 +787,23 @@ zDateOvrd = find_option("date-override",0,1); zUserOvrd = find_option("user-override",0,1); db_must_be_within_tree(); noSign = db_get_boolean("omitsign", 0)|noSign; if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } + useCksum = db_get_boolean("repo-cksum", 1); + outputManifest = db_get_boolean("manifest", 0); verify_all_options(); + + /* So that older versions of Fossil (that do not understand delta- + ** manifest) can continue to use this repository, do not create a new + ** delta-manifest unless this repository already contains one or more + ** delta-manifets, or unless the delta-manifest is explicitly requested + ** by the --delta option. + */ + if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){ + forceBaseline = 1; + } /* Get the ID of the parent manifest artifact */ vid = db_lget_int("checkout", 0); if( content_is_private(vid) ){ g.markPrivate = 1; @@ -685,14 +850,14 @@ */ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ fossil_fatal("no such user: %s", g.zLogin); } - rc = unsaved_changes(); + hasChanges = unsaved_changes(); db_begin_transaction(); db_record_repository_filename(0); - if( rc==0 && !isAMerge && !forceFlag ){ + if( hasChanges==0 && !isAMerge && !forceFlag ){ fossil_fatal("nothing has changed"); } /* If one or more files that were named on the command line have not ** been modified, bail out now. @@ -724,11 +889,11 @@ " WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_CLOSED, vid) ){ fossil_fatal("cannot commit against a closed leaf"); } - vfile_aggregate_checksum_disk(vid, &cksum1); + if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1); if( zComment ){ blob_zero(&comment); blob_append(&comment, zComment, -1); }else if( zComFile ){ blob_zero(&comment); @@ -779,113 +944,86 @@ db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); } db_finalize(&q); - /* Create the manifest */ - blob_zero(&manifest); + /* Create the new manifest */ if( blob_size(&comment)==0 ){ blob_append(&comment, "(no comment)", -1); } - blob_appendf(&manifest, "C %F\n", blob_str(&comment)); - zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); - blob_appendf(&manifest, "D %s\n", zDate); - zDate[10] = ' '; - db_prepare(&q, - "SELECT pathname, uuid, origname, blob.rid, isexe" - " FROM vfile JOIN blob ON vfile.mrid=blob.rid" - " WHERE NOT deleted AND vfile.vid=%d" - " ORDER BY 1", vid); - blob_zero(&filename); - blob_appendf(&filename, "%s", g.zLocalRoot); - nBasename = blob_size(&filename); - while( db_step(&q)==SQLITE_ROW ){ - const char *zName = db_column_text(&q, 0); - const char *zUuid = db_column_text(&q, 1); - const char *zOrig = db_column_text(&q, 2); - int frid = db_column_int(&q, 3); - int isexe = db_column_int(&q, 4); - const char *zPerm; - blob_append(&filename, zName, -1); -#if !defined(_WIN32) - /* For unix, extract the "executable" permission bit directly from - ** the filesystem. On windows, the "executable" bit is retained - ** unchanged from the original. */ - isexe = file_isexe(blob_str(&filename)); -#endif - if( isexe ){ - zPerm = " x"; - }else{ - zPerm = ""; - } - blob_resize(&filename, nBasename); - if( zOrig==0 || strcmp(zOrig,zName)==0 ){ - blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm); - }else{ - if( zPerm[0]==0 ){ zPerm = " w"; } - blob_appendf(&manifest, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); - } - if( !g.markPrivate ) content_make_public(frid); - } - blob_reset(&filename); - db_finalize(&q); - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); - blob_appendf(&manifest, "P %s", zUuid); - - if( !forceFlag ){ - checkin_verify_younger(vid, zUuid, zDate); - db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); - db_bind_int(&q2, ":id", 0); - while( db_step(&q2)==SQLITE_ROW ){ - int mid = db_column_int(&q2, 0); - if( !g.markPrivate && content_is_private(mid) ) continue; - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); - if( zUuid ){ - blob_appendf(&manifest, " %s", zUuid); - checkin_verify_younger(mid, zUuid, zDate); - free(zUuid); - } - } - db_finalize(&q2); - } - - blob_appendf(&manifest, "\n"); - blob_appendf(&manifest, "R %b\n", &cksum1); - if( zBranch && zBranch[0] ){ - Stmt q; - if( zBgColor && zBgColor[0] ){ - blob_appendf(&manifest, "T *bgcolor * %F\n", zBgColor); - } - blob_appendf(&manifest, "T *branch * %F\n", zBranch); - blob_appendf(&manifest, "T *sym-%F *\n", zBranch); - - /* Cancel all other symbolic tags */ - db_prepare(&q, - "SELECT tagname FROM tagxref, tag" - " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" - " AND tagtype>0 AND tagname GLOB 'sym-*'" - " AND tagname!='sym-'||%Q" - " ORDER BY tagname", - vid, zBranch); - while( db_step(&q)==SQLITE_ROW ){ - const char *zTag = db_column_text(&q, 0); - blob_appendf(&manifest, "T -%F *\n", zTag); - } - db_finalize(&q); - } - blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); - md5sum_blob(&manifest, &mcksum); - blob_appendf(&manifest, "Z %b\n", &mcksum); + if( forceDelta ){ + blob_zero(&manifest); + }else{ + create_manifest(&manifest, 0, 0, &comment, vid, + !forceFlag, useCksum ? &cksum1 : 0, + zDateOvrd, zUserOvrd, zBranch, zBgColor, &szB); + } + + /* See if a delta-manifest would be more appropriate */ + if( !forceBaseline ){ + const char *zBaselineUuid; + Manifest *pParent; + Manifest *pBaseline; + pParent = manifest_get(vid, CFTYPE_MANIFEST); + if( pParent && pParent->zBaseline ){ + zBaselineUuid = pParent->zBaseline; + pBaseline = manifest_get_by_name(zBaselineUuid, 0); + }else{ + zBaselineUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); + pBaseline = pParent; + } + if( pBaseline ){ + Blob delta; + create_manifest(&delta, zBaselineUuid, pBaseline, &comment, vid, + !forceFlag, useCksum ? &cksum1 : 0, + zDateOvrd, zUserOvrd, zBranch, zBgColor, &szD); + /* + ** At this point, two manifests have been constructed, either of + ** which would work for this checkin. The first manifest (held + ** in the "manifest" variable) is a baseline manifest and the second + ** (held in variable named "delta") is a delta manifest. The + ** question now is: which manifest should we use? + ** + ** Let B be the number of F-cards in the baseline manifest and + ** let D be the number of F-cards in the delta manifest, plus one for + ** the B-card. (B is held in the szB variable and D is held in the + ** szD variable.) Assume that all delta manifests adds X new F-cards. + ** Then to minimize the total number of F- and B-cards in the repository, + ** we should use the delta manifest if and only if: + ** + ** D*D < B*X - X*X + ** + ** X is an unknown here, but for most repositories, we will not be + ** far wrong if we assume X=3. + */ + if( forceDelta || (szD*szD)<(szB*3-9) ){ + blob_reset(&manifest); + manifest = delta; + }else{ + blob_reset(&delta); + } + }else if( forceDelta ){ + fossil_panic("unable to find a baseline-manifest for the delta"); + } + } if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ Blob ans; blob_zero(&ans); prompt_user("unable to sign manifest. continue (y/N)? ", &ans); if( blob_str(&ans)[0]!='y' ){ fossil_exit(1); } } - if( !file_exists_in_checkout(vid, "manifest") ){ + + /* If the --test option is specified, output the manifest file + ** and rollback the transaction. + */ + if( testRun ){ + blob_write_to_file(&manifest, ""); + } + + if( outputManifest ){ zManifestFile = mprintf("%smanifest", g.zLocalRoot); blob_write_to_file(&manifest, zManifestFile); blob_reset(&manifest); blob_read_from_file(&manifest, zManifestFile); free(zManifestFile); @@ -897,11 +1035,11 @@ db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid); manifest_crosslink(nvid, &manifest); content_deltify(vid, nvid, 0); zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); printf("New_Version: %s\n", zUuid); - if( !file_exists_in_checkout(vid, "manifest.uuid") ){ + if( outputManifest ){ zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot); blob_zero(&muuid); blob_appendf(&muuid, "%s\n", zUuid); blob_write_to_file(&muuid, zManifestFile); free(zManifestFile); @@ -918,45 +1056,51 @@ " WHERE file_is_selected(id);" , vid, nvid ); db_lset_int("checkout", nvid); - /* Verify that the repository checksum matches the expected checksum - ** calculated before the checkin started (and stored as the R record - ** of the manifest file). - */ - vfile_aggregate_checksum_repository(nvid, &cksum2); - if( blob_compare(&cksum1, &cksum2) ){ - fossil_panic("tree checksum does not match repository after commit"); - } - - /* Verify that the manifest checksum matches the expected checksum */ - vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); - if( blob_compare(&cksum1, &cksum1b) ){ - fossil_panic("manifest checksum does not agree with manifest: " - "%b versus %b", &cksum1, &cksum1b); - } - if( blob_compare(&cksum1, &cksum2) ){ - fossil_panic("tree checksum does not match manifest after commit: " - "%b versus %b", &cksum1, &cksum2); - } - - /* Verify that the commit did not modify any disk images. */ - vfile_aggregate_checksum_disk(nvid, &cksum2); - if( blob_compare(&cksum1, &cksum2) ){ - fossil_panic("tree checksums before and after commit do not match"); + if( useCksum ){ + /* Verify that the repository checksum matches the expected checksum + ** calculated before the checkin started (and stored as the R record + ** of the manifest file). + */ + vfile_aggregate_checksum_repository(nvid, &cksum2); + if( blob_compare(&cksum1, &cksum2) ){ + fossil_panic("tree checksum does not match repository after commit"); + } + + /* Verify that the manifest checksum matches the expected checksum */ + vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); + if( blob_compare(&cksum1, &cksum1b) ){ + fossil_panic("manifest checksum does not agree with manifest: " + "%b versus %b", &cksum1, &cksum1b); + } + if( blob_compare(&cksum1, &cksum2) ){ + fossil_panic("tree checksum does not match manifest after commit: " + "%b versus %b", &cksum1, &cksum2); + } + + /* Verify that the commit did not modify any disk images. */ + vfile_aggregate_checksum_disk(nvid, &cksum2); + if( blob_compare(&cksum1, &cksum2) ){ + fossil_panic("tree checksums before and after commit do not match"); + } } /* Clear the undo/redo stack */ undo_reset(); /* Commit */ db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); + if( testRun ){ + db_end_transaction(1); + exit(1); + } db_end_transaction(0); if( !g.markPrivate ){ autosync(AUTOSYNC_PUSH); } if( count_nonbranch_children(vid)>1 ){ printf("**** warning: a fork has occurred *****\n"); } } Index: src/checkout.c ================================================================== --- src/checkout.c +++ src/checkout.c @@ -78,87 +78,99 @@ /* ** Load a vfile from a record ID. */ void load_vfile_from_rid(int vid){ - Blob manifest; - if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){ return; } - content_get(vid, &manifest); - vfile_build(vid, &manifest); - blob_reset(&manifest); + vfile_build(vid); } /* ** Set or clear the vfile.isexe flag for a file. */ static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ - db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", - onoff, vid, zFilename); + static Stmt s; + db_static_prepare(&s, + "UPDATE vfile SET isexe=:isexe" + " WHERE vid=:vid AND pathname=:path AND isexe!=:isexe" + ); + db_bind_int(&s, ":isexe", onoff); + db_bind_int(&s, ":vid", vid); + db_bind_text(&s, ":path", zFilename); + db_step(&s); + db_reset(&s); } /* ** Set or clear the execute permission bit (as appropriate) for all ** files in the current check-out. -** -** If the checkout does not have explicit files named "manifest" and -** "manifest.uuid" then automatically generate files with those names -** containing, respectively, the text of the manifest and the artifact -** ID of the manifest. +*/ +void checkout_set_all_exe(int vid){ + Blob filename; + int baseLen; + Manifest *pManifest; + ManifestFile *pFile; + + /* Check the EXE permission status of all files + */ + pManifest = manifest_get(vid, CFTYPE_MANIFEST); + if( pManifest==0 ) return; + blob_zero(&filename); + blob_appendf(&filename, "%s/", g.zLocalRoot); + baseLen = blob_size(&filename); + manifest_file_rewind(pManifest); + while( (pFile = manifest_file_next(pManifest, 0))!=0 ){ + int isExe; + blob_append(&filename, pFile->zName, -1); + isExe = pFile->zPerm && strstr(pFile->zPerm, "x"); + file_setexe(blob_str(&filename), isExe); + set_or_clear_isexe(pFile->zName, vid, isExe); + blob_resize(&filename, baseLen); + } + blob_reset(&filename); + manifest_destroy(pManifest); +} + + +/* +** If the "manifest" setting is true, then automatically generate +** files named "manifest" and "manifest.uuid" containing, respectively, +** the text of the manifest and the artifact ID of the manifest. */ void manifest_to_disk(int vid){ char *zManFile; Blob manifest; Blob hash; - Blob filename; - int baseLen; - int i; - int seenManifest = 0; - int seenManifestUuid = 0; - Manifest m; - - /* Check the EXE permission status of all files - */ - blob_zero(&manifest); - content_get(vid, &manifest); - manifest_parse(&m, &manifest); - blob_zero(&filename); - blob_appendf(&filename, "%s/", g.zLocalRoot); - baseLen = blob_size(&filename); - for(i=0; i=nChildAlloc ){ + nChildAlloc = nChildAlloc*2 + 10; + aChild = fossil_realloc(aChild, nChildAlloc*sizeof(aChild)); + } + aChild[nChildUsed++] = child; + } + db_finalize(&q); + for(i=0; i=nChildAlloc ){ nChildAlloc = nChildAlloc*2 + 10; @@ -355,15 +400,25 @@ } db_finalize(&q); for(i=1; i0 ? aChild[0] : 0; linkFlag = 1; } free(aChild); } + +/* +** Turn dephantomization processing on or off. +*/ +void content_enable_dephantomize(int onoff){ + ignoreDephantomizations = !onoff; +} /* ** Write content into the database. Return the record ID. If the ** content is already in the database, just return the record ID. ** Index: src/db.c ================================================================== --- src/db.c +++ src/db.c @@ -1510,25 +1510,36 @@ int width; /* Width of display. 0 for boolean values */ char const *def; /* Default value */ }; #endif /* INTERFACE */ struct stControlSettings const ctrlSettings[] = { - { "auto-captcha", "autocaptcha", 0, "0" }, - { "auto-shun", 0, 0, "1" }, - { "autosync", 0, 0, "0" }, - { "binary-glob", 0, 0, "1" }, - { "clearsign", 0, 0, "0" }, - { "diff-command", 0, 16, "diff" }, - { "dont-push", 0, 0, "0" }, + { "auto-captcha", "autocaptcha", 0, "on" }, + { "auto-shun", 0, 0, "on" }, + { "autosync", 0, 0, "on" }, + { "binary-glob", 0, 32, "" }, + { "clearsign", 0, 0, "off" }, + { "diff-command", 0, 16, "" }, + { "dont-push", 0, 0, "off" }, { "editor", 0, 16, "" }, { "gdiff-command", 0, 16, "gdiff" }, { "ignore-glob", 0, 40, "" }, { "http-port", 0, 16, "8080" }, - { "localauth", 0, 0, "0" }, - { "mtime-changes", 0, 0, "0" }, + { "localauth", 0, 0, "off" }, + { "manifest", 0, 0, "off" }, + { "mtime-changes", 0, 0, "off" }, { "pgp-command", 0, 32, "gpg --clearsign -o " }, { "proxy", 0, 32, "off" }, + { "push-hook-cmd", 0, 32, "" }, + { "push-hook-force", + 0, 0, "" }, + { "push-hook-pattern-client", + 0, 32, "" }, + { "push-hook-pattern-server", + 0, 32, "" }, + { "push-hook-privilege", + 0, 1, "" }, + { "repo-cksum", 0, 0, "on" }, { "ssh-command", 0, 32, "" }, { "web-browser", 0, 32, "" }, { 0,0,0,0 } }; @@ -1586,10 +1597,14 @@ ** localauth If enabled, require that HTTP connections from ** 127.0.0.1 be authenticated by password. If ** false, all HTTP requests from localhost have ** unrestricted access to the repository. ** +** manifest If enabled, automatically create files "manifest" and +** "manifest.uuid" in every checkout. The SQLite and +** Fossil repositories both require this. Default: off. +** ** mtime-changes Use file modification times (mtimes) to detect when ** files have been modified. (Default "on".) ** ** pgp-command Command used to clear-sign manifests at check-in. ** The default is "gpg --clearsign -o ". @@ -1596,10 +1611,48 @@ ** ** proxy URL of the HTTP proxy. If undefined or "off" then ** the "http_proxy" environment variable is consulted. ** If the http_proxy environment variable is undefined ** then a direct HTTP connection is used. +** +** push-hook-cmd this is the command line, that will be activated +** as push hook. Output redirects should be added to +** this command line. +** The complete command line looks like: +** command name: the configured value for push-hook-cmd +** argument 1: timestamp followed by random-number +** argument 2: pattern sent by client +** As fallback, stdin/stderr are redirected to files +** hook-log- +** +** push-hook-force +** if this is set on the client, it will request always +** the hook activation, even if no files where pushed on +** the sync. +** if this is set on the server, it will accept hook +** activiation, even if no files where pushed. +** Default: on +** +** push-hook-pattern-client +** if set, a client push will sent this message to the +** server, to activate the push hook command. +** Default: on +** +** push-hook-pattern-server +** if set, and a client send this pattern at the end of +** a push, the push hook command will be executed. This +** might be a prefix of the pattern, sent by the client. +** +** push-hook-privilege +** if set, the user doing the push needs this privilege +** to trigger the hook. Valid privileges are: +** s (setup), a (admin), i (checkin) or o (checkout) +** +** repo-cksum Compute checksums over all files in each checkout +** as a double-check of correctness. Defaults to "on". +** Disable on large repositories for a performance +** improvement. ** ** ssh-command Command used to talk to a remote machine with ** the "ssh://" protocol. ** ** web-browser A shell command used to launch your preferred @@ -1627,24 +1680,33 @@ for(i=0; ctrlSettings[i].name; i++){ print_setting(ctrlSettings[i].name); } }else if( g.argc==3 || g.argc==4 ){ const char *zName = g.argv[2]; + int isManifest; int n = strlen(zName); for(i=0; ctrlSettings[i].name; i++){ if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break; } if( !ctrlSettings[i].name ){ fossil_fatal("no such setting: %s", zName); } + isManifest = strcmp(ctrlSettings[i].name, "manifest")==0; + if( isManifest && globalFlag ){ + fossil_fatal("cannot set 'manifest' globally"); + } if( unsetFlag ){ db_unset(ctrlSettings[i].name, globalFlag); }else if( g.argc==4 ){ db_set(ctrlSettings[i].name, g.argv[3], globalFlag); }else{ + isManifest = 0; print_setting(ctrlSettings[i].name); } + if( isManifest ){ + manifest_to_disk(db_lget_int("checkout", 0)); + } }else{ usage("?PROPERTY? ?VALUE?"); } } Index: src/delta.c ================================================================== --- src/delta.c +++ src/delta.c @@ -195,31 +195,38 @@ /* ** Compute a 32-bit checksum on the N-byte buffer. Return the result. */ static unsigned int checksum(const char *zIn, size_t N){ const unsigned char *z = (const unsigned char *)zIn; - unsigned sum = 0; + unsigned sum0 = 0; + unsigned sum1 = 0; + unsigned sum2 = 0; + unsigned sum3 = 0; while(N >= 16){ - sum += ((unsigned)z[0] + z[4] + z[8] + z[12]) << 24; - sum += ((unsigned)z[1] + z[5] + z[9] + z[13]) << 16; - sum += ((unsigned)z[2] + z[6] + z[10]+ z[14]) << 8; - sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]); + sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); + sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); + sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); + sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]); z += 16; N -= 16; } while(N >= 4){ - sum += (z[0]<<24) | (z[1]<<16) | (z[2]<<8) | z[3]; + sum0 += z[0]; + sum1 += z[1]; + sum2 += z[2]; + sum3 += z[3]; z += 4; N -= 4; } + sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); switch(N){ - case 3: sum += (z[2] << 8); - case 2: sum += (z[1] << 16); - case 1: sum += (z[0] << 24); + case 3: sum3 += (z[2] << 8); + case 2: sum3 += (z[1] << 16); + case 1: sum3 += (z[0] << 24); default: ; } - return sum; + return sum3; } /* ** Create a new delta. ** @@ -512,11 +519,13 @@ int lenDelta, /* Length of the delta */ char *zOut /* Write the output into this preallocated buffer */ ){ unsigned int limit; unsigned int total = 0; +#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST char *zOrigOut = zOut; +#endif limit = getInt(&zDelta, &lenDelta); if( *zDelta!='\n' ){ /* ERROR: size integer not terminated by "\n" */ return -1; @@ -567,14 +576,16 @@ break; } case ';': { zDelta++; lenDelta--; zOut[0] = 0; +#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST if( cnt!=checksum(zOrigOut, total) ){ /* ERROR: bad checksum */ return -1; } +#endif if( total!=limit ){ /* ERROR: generated size does not match predicted size */ return -1; } return total; Index: src/diffcmd.c ================================================================== --- src/diffcmd.c +++ src/diffcmd.c @@ -35,10 +35,11 @@ #if defined(_WIN32) /* On windows, we have to put double-quotes around the entire command. ** Who knows why - this is just the way windows works. */ char *zNewCmd = mprintf("\"%s\"", zOrigCmd); + fflush(0); rc = system(zNewCmd); free(zNewCmd); #else /* On unix, evaluate the command directly. */ @@ -355,53 +356,56 @@ const char *zFrom, const char *zTo, const char *zDiffCmd, int diffFlags ){ - Manifest mFrom, mTo; - int iFrom, iTo; + Manifest *pFrom, *pTo; + ManifestFile *pFromFile, *pToFile; int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0; int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; - manifest_from_name(zFrom, &mFrom); - manifest_from_name(zTo, &mTo); - iFrom = iTo = 0; - while( iFrom=mFrom.nFile ){ + if( pFromFile==0 ){ cmp = +1; - }else if( iTo>=mTo.nFile ){ + }else if( pToFile==0 ){ cmp = -1; }else{ - cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName); + cmp = strcmp(pFromFile->zName, pToFile->zName); } if( cmp<0 ){ - printf("DELETED %s\n", mFrom.aFile[iFrom].zName); - if( asNewFlag ){ - diff_manifest_entry(&mFrom.aFile[iFrom], 0, zDiffCmd, ignoreEolWs); - } - iFrom++; - }else if( cmp>0 ){ - printf("ADDED %s\n", mTo.aFile[iTo].zName); - if( asNewFlag ){ - diff_manifest_entry(0, &mTo.aFile[iTo], zDiffCmd, ignoreEolWs); - } - iTo++; - }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){ - /* No changes */ - iFrom++; - iTo++; - }else{ - printf("CHANGED %s\n", mFrom.aFile[iFrom].zName); - diff_manifest_entry(&mFrom.aFile[iFrom], &mTo.aFile[iTo], - zDiffCmd, ignoreEolWs); - iFrom++; - iTo++; - } - } - manifest_clear(&mFrom); - manifest_clear(&mTo); + printf("DELETED %s\n", pFromFile->zName); + if( asNewFlag ){ + diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs); + } + pFromFile = manifest_file_next(pFrom,0); + }else if( cmp>0 ){ + printf("ADDED %s\n", pToFile->zName); + if( asNewFlag ){ + diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs); + } + pToFile = manifest_file_next(pTo,0); + }else if( strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ + /* No changes */ + pFromFile = manifest_file_next(pFrom,0); + pToFile = manifest_file_next(pTo,0); + }else{ + printf("CHANGED %s\n", pFromFile->zName); + diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs); + pFromFile = manifest_file_next(pFrom,0); + pToFile = manifest_file_next(pTo,0); + } + } + manifest_destroy(pFrom); + manifest_destroy(pTo); } /* ** COMMAND: diff ** COMMAND: gdiff Index: src/doc.c ================================================================== --- src/doc.c +++ src/doc.c @@ -388,37 +388,36 @@ goto doc_not_found; } if( rid==0 ){ Stmt s; - Blob baseline; - Manifest m; + Manifest *pM; + ManifestFile *pFile; /* Add the vid baseline to the cache */ if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ db_multi_exec("DELETE FROM vcache"); } - if( content_get(vid, &baseline)==0 ){ - goto doc_not_found; - } - if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){ + pM = manifest_get(vid, CFTYPE_MANIFEST); + if( pM==0 ){ goto doc_not_found; } db_prepare(&s, "INSERT INTO vcache(vid,fname,rid)" " SELECT %d, :fname, rid FROM blob" " WHERE uuid=:uuid", vid ); - for(i=0; izName); + db_bind_text(&s, ":uuid", pFile->zUuid); db_step(&s); db_reset(&s); } db_finalize(&s); - manifest_clear(&m); + manifest_destroy(pM); /* Try again to find the file */ rid = db_int(0, "SELECT rid FROM vcache" " WHERE vid=%d AND fname=%Q", vid, zName); } Index: src/encode.c ================================================================== --- src/encode.c +++ src/encode.c @@ -267,12 +267,13 @@ /* ** Decode a fossilized string in-place. */ void defossilize(char *z){ int i, j, c; - for(i=j=0; z[i]; i++){ - c = z[i]; + for(i=0; (c=z[i])!=0 && c!='\\'; i++){} + if( c==0 ) return; + for(j=i; (c=z[i])!=0; i++){ if( c=='\\' && z[i+1] ){ i++; switch( z[i] ){ case 'n': c = '\n'; break; case 's': c = ' '; break; Index: src/event.c ================================================================== --- src/event.c +++ src/event.c @@ -63,12 +63,11 @@ const char *zEventId; /* Event identifier */ char *zETime; /* Time of the event */ char *zATime; /* Time the artifact was created */ int specRid; /* rid specified by aid= parameter */ int prevRid, nextRid; /* Previous or next edits of this event */ - Manifest m; /* Parsed event artifact */ - Blob content; /* Original event artifact content */ + Manifest *pEvent; /* Parsed event artifact */ Blob fullbody; /* Complete content of the event body */ Blob title; /* Title extracted from the event body */ Blob tail; /* Event body that comes after the title */ Stmt q1; /* Query to search for the event */ int showDetail; /* True to show details */ @@ -113,18 +112,15 @@ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); showDetail = atoi(PD("detail","0")); /* Extract the event content. */ - memset(&m, 0, sizeof(m)); - blob_zero(&m.content); - content_get(rid, &content); - manifest_parse(&m, &content); - if( m.type!=CFTYPE_EVENT ){ + pEvent = manifest_get(rid, CFTYPE_EVENT); + if( pEvent==0 ){ fossil_panic("Object #%d is not an event", rid); } - blob_init(&fullbody, m.zWiki, -1); + blob_init(&fullbody, pEvent->zWiki, -1); if( wiki_find_title(&fullbody, &title, &tail) ){ style_header(blob_str(&title)); }else{ style_header("Event %S", zEventId); tail = fullbody; @@ -131,11 +127,11 @@ } if( g.okWrWiki && g.okWrite && nextRid==0 ){ style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", g.zTop, zEventId); } - zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate); + zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); style_submenu_element("Context", "Context", "%s/timeline?c=%T", g.zTop, zETime); if( g.okHistory ){ if( showDetail ){ style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", @@ -166,37 +162,37 @@ if( showDetail && g.okHistory ){ int i; const char *zClr = 0; Blob comment; - zATime = db_text(0, "SELECT datetime(%.17g)", m.rDate); + zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); @

    Event [%S(zUuid)] at @ [%s(zETime)] - @ entered by user %h(m.zUser) on + @ entered by user %h(pEvent->zUser) on @ [%s(zATime)]:

    @
    - for(i=0; inTag; i++){ + if( strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ + zClr = pEvent->aTag[i].zValue; } } if( zClr && zClr[0]==0 ) zClr = 0; if( zClr ){ @
    }else{ @
    } - blob_init(&comment, m.zComment, -1); + blob_init(&comment, pEvent->zComment, -1); wiki_convert(&comment, 0, WIKI_INLINE); blob_reset(&comment); @
    @

    } wiki_convert(&tail, 0, 0); style_footer(); - manifest_clear(&m); + manifest_destroy(pEvent); } /* ** WEBPAGE: eventedit ** URL: /eventedit?name=EVENTID @@ -259,22 +255,18 @@ /* If editing an existing event, extract the key fields to use as ** a starting point for the edit. */ if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ - Manifest m; - Blob content; - memset(&m, 0, sizeof(m)); - blob_zero(&m.content); - content_get(rid, &content); - manifest_parse(&m, &content); - if( m.type==CFTYPE_EVENT ){ - if( zBody==0 ) zBody = m.zWiki; + Manifest *pEvent; + pEvent = manifest_get(rid, CFTYPE_EVENT); + if( pEvent && pEvent->type==CFTYPE_EVENT ){ + if( zBody==0 ) zBody = pEvent->zWiki; if( zETime==0 ){ - zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate); + zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); } - if( zComment==0 ) zComment = m.zComment; + if( zComment==0 ) zComment = pEvent->zComment; } if( zTags==0 ){ zTags = db_text(0, "SELECT group_concat(substr(tagname,5),', ')" " FROM tagxref, tag" Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -134,11 +134,11 @@ TAG_BRANCH, zFilename ); blob_zero(&title); blob_appendf(&title, "History of "); - hyperlinked_path(zFilename, &title); + hyperlinked_path(zFilename, &title, 0); @

    %b(&title)

    blob_reset(&title); pGraph = graph_init(); @
    @ Index: src/http.c ================================================================== --- src/http.c +++ src/http.c @@ -267,11 +267,11 @@ } z[j] = 0; fossil_fatal("server sends error: %s", z); } if( g.fHttpTrace ){ - printf("HTTP RECEIVE:\n%s\n=======================\n", blob_str(pReply)); + /*printf("HTTP RECEIVE:\n%s\n=======================\n",blob_str(pReply));*/ }else{ blob_uncompress(pReply, pReply); } /* Index: src/info.c ================================================================== --- src/info.c +++ src/info.c @@ -545,24 +545,20 @@ rid = 0; } db_finalize(&q); showTags(rid, "wiki-*"); if( rid ){ - Blob content; - Manifest m; - memset(&m, 0, sizeof(m)); - blob_zero(&m.content); - content_get(rid, &content); - manifest_parse(&m, &content); - if( m.type==CFTYPE_WIKI ){ + Manifest *pWiki; + pWiki = manifest_get(rid, CFTYPE_WIKI); + if( pWiki ){ Blob wiki; - blob_init(&wiki, m.zWiki, -1); + blob_init(&wiki, pWiki->zWiki, -1); @
    Content
    wiki_convert(&wiki, 0, 0); blob_reset(&wiki); } - manifest_clear(&m); + manifest_destroy(pWiki); } style_footer_cmdref("info",0); } /* @@ -582,26 +578,23 @@ /* ** Find an checkin based on query parameter zParam and parse its ** manifest. Return the number of errors. */ -static int vdiff_parse_manifest(const char *zParam, int *pRid, Manifest *pM){ +static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){ int rid; - Blob content; *pRid = rid = name_to_rid_www(zParam); if( rid==0 ){ webpage_error("Missing \"%s\" query parameter.", zParam); - return 1; + return 0; } if( !is_a_version(rid) ){ webpage_error("Artifact %s is not a checkin.", P(zParam)); - return 1; + return 0; } - content_get(rid, &content); - manifest_parse(pM, &content); - return 0; + return manifest_get(rid, CFTYPE_MANIFEST); } /* ** Output a description of a check-in */ @@ -639,12 +632,12 @@ void vdiff_page(void){ int ridFrom, ridTo; int showDetail = atoi(PD("detail","0")); const char *zFrom = P("from"); const char *zTo = P("to"); - int iFrom, iTo; - Manifest mFrom, mTo; + Manifest *pFrom, *pTo; + ManifestFile *pFileFrom, *pFileTo; login_check_credentials(); if( !g.okRead ){ login_needed(); return; } login_anonymous_available(); @@ -665,55 +658,59 @@ @ checked="checked" value="1" /> @
    @
    @ style_footer_cmdref("diff",0); - return; - }else if( vdiff_parse_manifest("from", &ridFrom, &mFrom) - || vdiff_parse_manifest("to", &ridTo, &mTo) - ){ return; } + pFrom = vdiff_parse_manifest("from", &ridFrom); + if( pFrom==0 ) return; + pTo = vdiff_parse_manifest("to", &ridTo); + if( pTo==0 ) return; + showDetail = atoi(PD("detail","0")); style_header("Check-in Differences"); @

    Difference From:

    checkin_description(ridFrom); @

    To:

    checkin_description(ridTo); @

    - iFrom = iTo = 0; - while( iFrom=mFrom.nFile ){ + if( pFileFrom==0 ){ cmp = +1; - }else if( iTo>=mTo.nFile ){ + }else if( pFileTo==0 ){ cmp = -1; }else{ - cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName); + cmp = strcmp(pFileFrom->zName, pFileTo->zName); } if( cmp<0 ){ - append_file_change_line(mFrom.aFile[iFrom].zName, - mFrom.aFile[iFrom].zUuid, 0, 0); - iFrom++; + append_file_change_line(pFileFrom->zName, + pFileFrom->zUuid, 0, 0); + pFileFrom = manifest_file_next(pFrom, 0); }else if( cmp>0 ){ - append_file_change_line(mTo.aFile[iTo].zName, - 0, mTo.aFile[iTo].zUuid, 0); - iTo++; - }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){ + append_file_change_line(pFileTo->zName, + 0, pFileTo->zUuid, 0); + pFileTo = manifest_file_next(pTo, 0); + }else if( strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ /* No changes */ - iFrom++; - iTo++; + pFileFrom = manifest_file_next(pFrom, 0); + pFileTo = manifest_file_next(pTo, 0); }else{ - append_file_change_line(mFrom.aFile[iFrom].zName, - mFrom.aFile[iFrom].zUuid, - mTo.aFile[iTo].zUuid, showDetail); - iFrom++; - iTo++; + append_file_change_line(pFileFrom->zName, + pFileFrom->zUuid, + pFileTo->zUuid, showDetail); + pFileFrom = manifest_file_next(pFrom, 0); + pFileTo = manifest_file_next(pTo, 0); } } - manifest_clear(&mFrom); - manifest_clear(&mTo); + manifest_destroy(pFrom); + manifest_destroy(pTo); style_footer_cmdref("diff",0); } /* @@ -1069,25 +1066,26 @@ */ int artifact_from_ci_and_filename(void){ const char *zFilename; const char *zCI; int cirid; - Blob content; - Manifest m; - int i; + Manifest *pManifest; + ManifestFile *pFile; zCI = P("ci"); if( zCI==0 ) return 0; zFilename = P("filename"); if( zFilename==0 ) return 0; cirid = name_to_rid_www("ci"); - if( !content_get(cirid, &content) ) return 0; - if( !manifest_parse(&m, &content) ) return 0; - if( m.type!=CFTYPE_MANIFEST ) return 0; - for(i=0; izName)==0 ){ + int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); + manifest_destroy(pManifest); + return rid; } } return 0; } @@ -1194,15 +1192,14 @@ ** ** Show the details of a ticket change control artifact. */ void tinfo_page(void){ int rid; - Blob content; char *zDate; const char *zUuid; char zTktName[20]; - Manifest m; + Manifest *pTktChng; login_check_credentials(); if( !g.okRdTkt ){ login_needed(); return; } rid = name_to_rid_www("name"); if( rid==0 ){ fossil_redirect_home(); } @@ -1214,39 +1211,37 @@ }else{ style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid); } } - content_get(rid, &content); - if( manifest_parse(&m, &content)==0 ){ - fossil_redirect_home(); - } - if( m.type!=CFTYPE_TICKET ){ + pTktChng = manifest_get(rid, CFTYPE_TICKET); + if( pTktChng==0 ){ fossil_redirect_home(); } style_header("Ticket Change Details"); - zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate); - memcpy(zTktName, m.zTicketUuid, 10); + zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); + memcpy(zTktName, pTktChng->zTicketUuid, 10); zTktName[10] = 0; if( g.okHistory ){ - @

    Changes to ticket %s(zTktName)

    + @

    Changes to ticket + @ %s(zTktName)

    @ - @

    By %h(m.zUser) on %s(zDate). See also: + @

    By %h(pTktChng->zUser) on %s(zDate). See also: @ artifact content, and - @ ticket history - @

    + @ ticket + @ history

    }else{ @

    Changes to ticket %s(zTktName)

    @ - @

    By %h(m.zUser) on %s(zDate). + @

    By %h(pTktChng->zUser) on %s(zDate). @

    } @ @
      free(zDate); - ticket_output_change_artifact(&m); - manifest_clear(&m); + ticket_output_change_artifact(pTktChng); + manifest_destroy(pTktChng); style_footer_cmdref("info",0); } /* Index: src/manifest.c ================================================================== --- src/manifest.c +++ src/manifest.c @@ -26,24 +26,39 @@ #if INTERFACE /* ** Types of control files */ +#define CFTYPE_ANY 0 #define CFTYPE_MANIFEST 1 #define CFTYPE_CLUSTER 2 #define CFTYPE_CONTROL 3 #define CFTYPE_WIKI 4 #define CFTYPE_TICKET 5 #define CFTYPE_ATTACHMENT 6 #define CFTYPE_EVENT 7 + +/* +** A single F-card within a manifest +*/ +struct ManifestFile { + char *zName; /* Name of a file */ + char *zUuid; /* UUID of the file */ + char *zPerm; /* File permissions */ + char *zPrior; /* Prior name if the name was changed */ +}; + /* ** A parsed manifest or cluster. */ struct Manifest { Blob content; /* The original content blob */ int type; /* Type of artifact. One of CFTYPE_xxxxx */ + int rid; /* The blob-id for this manifest */ + char *zBaseline; /* Baseline manifest. The B card. */ + Manifest *pBaseline; /* The actual baseline manifest */ char *zComment; /* Decoded comment. The C card. */ double rDate; /* Date and time from D card. 0.0 if no D card. */ char *zUser; /* Name of the user from the U card. */ char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ char *zWiki; /* Text of the wiki page. W card. */ @@ -54,17 +69,12 @@ char *zAttachName; /* Filename of an attachment. A card. */ char *zAttachSrc; /* UUID of document being attached. A card. */ char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ int nFile; /* Number of F cards */ int nFileAlloc; /* Slots allocated in aFile[] */ - struct ManifestFile { - char *zName; /* Name of a file */ - char *zUuid; /* UUID of the file */ - char *zPerm; /* File permissions */ - char *zPrior; /* Prior name if the name was changed */ - int iRename; /* index of renamed name in prior/next manifest */ - } *aFile; /* One entry for each F card */ + int iFile; /* Index of current file in iterator */ + ManifestFile *aFile; /* One entry for each F-card */ int nParent; /* Number of parents. */ int nParentAlloc; /* Slots allocated in azParent[] */ char **azParent; /* UUIDs of parents. One for each P card argument */ int nCChild; /* Number of cluster children */ int nCChildAlloc; /* Number of closts allocated in azCChild[] */ @@ -87,68 +97,75 @@ /* ** A cache of parsed manifests. This reduces the number of ** calls to manifest_parse() when doing a rebuild. */ -#define MX_MANIFEST_CACHE 4 +#define MX_MANIFEST_CACHE 6 static struct { int nxAge; - int aRid[MX_MANIFEST_CACHE]; int aAge[MX_MANIFEST_CACHE]; - Manifest aLine[MX_MANIFEST_CACHE]; + Manifest *apManifest[MX_MANIFEST_CACHE]; } manifestCache; /* ** Clear the memory allocated in a manifest object */ -void manifest_clear(Manifest *p){ - blob_reset(&p->content); - free(p->aFile); - free(p->azParent); - free(p->azCChild); - free(p->aTag); - free(p->aField); - memset(p, 0, sizeof(*p)); +void manifest_destroy(Manifest *p){ + if( p ){ + blob_reset(&p->content); + free(p->aFile); + free(p->azParent); + free(p->azCChild); + free(p->aTag); + free(p->aField); + if( p->pBaseline ) manifest_destroy(p->pBaseline); + fossil_free(p); + } } /* ** Add an element to the manifest cache using LRU replacement. */ -void manifest_cache_insert(int rid, Manifest *p){ - int i; - for(i=0; i=MX_MANIFEST_CACHE ){ - int oldest = 0; - int oldestAge = manifestCache.aAge[0]; - for(i=1; ipBaseline; + p->pBaseline = 0; + for(i=0; i=MX_MANIFEST_CACHE ){ + int oldest = 0; + int oldestAge = manifestCache.aAge[0]; + for(i=1; irid==rid ){ + p = manifestCache.apManifest[i]; + manifestCache.apManifest[i] = 0; + return p; } } return 0; } @@ -156,21 +173,113 @@ ** Clear the manifest cache. */ void manifest_cache_clear(void){ int i; for(i=0; i0 ){ - manifest_clear(&manifestCache.aLine[i]); + if( manifestCache.apManifest[i] ){ + manifest_destroy(manifestCache.apManifest[i]); } } memset(&manifestCache, 0, sizeof(manifestCache)); } #ifdef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM # define md5sum_init(X) # define md5sum_step_text(X,Y) #endif + +/* +** Remove the PGP signature from the artifact, if there is one. +*/ +static void remove_pgp_signature(char **pz, int *pn){ + char *z = *pz; + int n = *pn; + int i; + if( memcmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return; + for(i=34; i=n ) return; + z += i; + n -= i; + *pz = z; + for(i=n-1; i>=0; i--){ + if( z[i]=='\n' && memcmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){ + n = i+1; + break; + } + } + *pn = n; + return; +} + +/* +** Verify the Z-card checksum on the artifact, if there is such a +** checksum. Return 0 if there is no Z-card. Return 1 if the Z-card +** exists and is correct. Return 2 if the Z-card exists and has the wrong +** value. +** +** 0123456789 123456789 123456789 123456789 +** Z aea84f4f863865a8d59d0384e4d2a41c +*/ +static int verify_z_card(const char *z, int n){ + if( n<35 ) return 0; + if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0; + md5sum_init(); + md5sum_step_text(z, n-35); + if( memcmp(&z[n-33], md5sum_finish(0), 32)==0 ){ + return 1; + }else{ + return 2; + } +} + +/* +** A structure used for rapid parsing of the Manifest file +*/ +typedef struct ManifestText ManifestText; +struct ManifestText { + char *z; /* The first character of the next token */ + char *zEnd; /* One character beyond the end of the manifest */ + int atEol; /* True if z points to the start of a new line */ +}; + +/* +** Return a pointer to the next token. The token is zero-terminated. +** Return NULL if there are no more tokens on the current line. +*/ +static char *next_token(ManifestText *p, int *pLen){ + char *z; + char *zStart; + int c; + if( p->atEol ) return 0; + zStart = z = p->z; + while( (c=(*z))!=' ' && c!='\n' ){ z++; } + *z = 0; + p->z = &z[1]; + p->atEol = c=='\n'; + if( pLen ) *pLen = z - zStart; + return zStart; +} + +/* +** Return the card-type for the next card. Or, return 0 if there are no +** more cards or if we are not at the end of the current card. +*/ +static char next_card(ManifestText *p){ + char c; + if( !p->atEol || p->z>=p->zEnd ) return 0; + c = p->z[0]; + if( p->z[1]==' ' ){ + p->z += 2; + p->atEol = 0; + }else if( p->z[1]=='\n' ){ + p->z += 2; + p->atEol = 1; + }else{ + c = 0; + } + return c; +} /* ** Parse a blob into a Manifest object. The Manifest object ** takes over the input blob and will free it when the ** Manifest object is freed. Zeros are inserted into the blob @@ -195,56 +304,66 @@ ** Each card is divided into tokens by a single space character. ** The first token is a single upper-case letter which is the card type. ** The card type determines the other parameters to the card. ** Cards must occur in lexicographical order. */ -int manifest_parse(Manifest *p, Blob *pContent){ - int seenHeader = 0; +static Manifest *manifest_parse(Blob *pContent, int rid){ + Manifest *p; int seenZ = 0; int i, lineNo=0; - Blob line, token, a1, a2, a3, a4; + ManifestText x; char cPrevType = 0; + char cType; + char *z; + int n; + char *zUuid; + int sz = 0; /* Every control artifact ends with a '\n' character. Exit early - ** if that is not the case for this artifact. */ - i = blob_size(pContent); - if( i<=0 || blob_buffer(pContent)[i-1]!='\n' ){ + ** if that is not the case for this artifact. + */ + z = blob_buffer(pContent); + n = blob_size(pContent); + if( n<=0 || z[n-1]!='\n' ){ + blob_reset(pContent); + return 0; + } + + /* Strip off the PGP signature if there is one. Then verify the + ** Z-card. + */ + remove_pgp_signature(&z, &n); + if( verify_z_card(z, n)==0 ){ + blob_reset(pContent); + return 0; + } + + /* Verify that the first few characters of the artifact look like + ** a control artifact. + */ + if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){ blob_reset(pContent); return 0; } + /* Allocate a Manifest object to hold the parsed control artifact. + */ + p = fossil_malloc( sizeof(*p) ); memset(p, 0, sizeof(*p)); memcpy(&p->content, pContent, sizeof(p->content)); + p->rid = rid; blob_zero(pContent); pContent = &p->content; - blob_zero(&a1); - blob_zero(&a2); - blob_zero(&a3); - md5sum_init(); - while( blob_line(pContent, &line) ){ - char *z = blob_buffer(&line); + /* Begin parsing, card by card. + */ + x.z = z; + x.zEnd = &z[n]; + x.atEol = 1; + while( (cType = next_card(&x))!=0 && cType>=cPrevType ){ lineNo++; - if( z[0]=='-' ){ - if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){ - goto manifest_syntax_error; - } - if( seenHeader ){ - break; - } - while( blob_line(pContent, &line)>2 ){} - if( blob_line(pContent, &line)==0 ) break; - z = blob_buffer(&line); - } - if( z[0] ?? ** ** Identifies an attachment to either a wiki page or a ticket. ** is the artifact that is the attachment. @@ -251,50 +370,60 @@ ** is omitted to delete an attachment. is the name of ** a wiki page or ticket to which that attachment is connected. */ case 'A': { char *zName, *zTarget, *zSrc; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; + int nTarget = 0, nSrc = 0; + zName = next_token(&x, 0); + zTarget = next_token(&x, &nTarget); + zSrc = next_token(&x, &nSrc); + if( zName==0 || zTarget==0 ) goto manifest_syntax_error; if( p->zAttachName!=0 ) goto manifest_syntax_error; - zName = blob_terminate(&a1); - zTarget = blob_terminate(&a2); - blob_token(&line, &a3); - zSrc = blob_terminate(&a3); defossilize(zName); if( !file_is_simple_pathname(zName) ){ goto manifest_syntax_error; } defossilize(zTarget); - if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) + if( (nTarget!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){ goto manifest_syntax_error; } - if( blob_size(&a3)>0 - && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ + if( zSrc && (nSrc!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ goto manifest_syntax_error; } p->zAttachName = (char*)file_tail(zName); p->zAttachSrc = zSrc; p->zAttachTarget = zTarget; break; } + + /* + ** B + ** + ** A B-line gives the UUID for the baselinen of a delta-manifest. + */ + case 'B': { + if( p->zBaseline ) goto manifest_syntax_error; + p->zBaseline = next_token(&x, &sz); + if( p->zBaseline==0 ) goto manifest_syntax_error; + if( sz!=UUID_SIZE ) goto manifest_syntax_error; + if( !validate16(p->zBaseline, UUID_SIZE) ) goto manifest_syntax_error; + break; + } + /* ** C ** ** Comment text is fossil-encoded. There may be no more than ** one C line. C lines are required for manifests and are ** disallowed on all other control files. */ case 'C': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); if( p->zComment!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - p->zComment = blob_terminate(&a1); + p->zComment = next_token(&x, 0); + if( p->zComment==0 ) goto manifest_syntax_error; defossilize(p->zComment); break; } /* @@ -303,17 +432,13 @@ ** The timestamp should be ISO 8601. YYYY-MM-DDtHH:MM:SS ** There can be no more than 1 D line. D lines are required ** for all control files except for clusters. */ case 'D': { - char *zDate; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->rDate!=0.0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - zDate = blob_terminate(&a1); - p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); + if( p->rDate>0.0 ) goto manifest_syntax_error; + p->rDate = db_double(0.0, "SELECT julianday(%Q)", next_token(&x,0)); + if( p->rDate<=0.0 ) goto manifest_syntax_error; break; } /* ** E @@ -323,56 +448,46 @@ ** The event timestamp is distinct from the D timestamp. The D ** timestamp is when the artifact was created whereas the E timestamp ** is when the specific event is said to occur. */ case 'E': { - char *zEDate; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( p->rEventDate!=0.0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; - zEDate = blob_terminate(&a1); - p->rEventDate = db_double(0.0, "SELECT julianday(%Q)", zEDate); + if( p->rEventDate>0.0 ) goto manifest_syntax_error; + p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0)); if( p->rEventDate<=0.0 ) goto manifest_syntax_error; - if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; - p->zEventId = blob_terminate(&a2); + p->zEventId = next_token(&x, &sz); + if( sz!=UUID_SIZE ) goto manifest_syntax_error; if( !validate16(p->zEventId, UUID_SIZE) ) goto manifest_syntax_error; break; } /* - ** F ?? ?? + ** F ?? ?? ?? ** ** Identifies a file in a manifest. Multiple F lines are ** allowed in a manifest. F lines are not allowed in any ** other control file. The filename and old-name are fossil-encoded. */ case 'F': { - char *zName, *zUuid, *zPerm, *zPriorName; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; - zName = blob_terminate(&a1); - zUuid = blob_terminate(&a2); - blob_token(&line, &a3); - zPerm = blob_terminate(&a3); - if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; + char *zName, *zPerm, *zPriorName; + zName = next_token(&x,0); + if( zName==0 ) goto manifest_syntax_error; defossilize(zName); if( !file_is_simple_pathname(zName) ){ goto manifest_syntax_error; } - blob_token(&line, &a4); - zPriorName = blob_terminate(&a4); - if( zPriorName[0] ){ + zUuid = next_token(&x, &sz); + if( p->zBaseline==0 || zUuid!=0 ){ + if( sz!=UUID_SIZE ) goto manifest_syntax_error; + if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; + } + zPerm = next_token(&x,0); + zPriorName = next_token(&x,0); + if( zPriorName ){ defossilize(zPriorName); if( !file_is_simple_pathname(zPriorName) ){ goto manifest_syntax_error; } - }else{ - zPriorName = 0; } if( p->nFile>=p->nFileAlloc ){ p->nFileAlloc = p->nFileAlloc*2 + 10; p->aFile = fossil_realloc(p->aFile, p->nFileAlloc*sizeof(p->aFile[0]) ); @@ -380,11 +495,10 @@ i = p->nFile++; p->aFile[i].zName = zName; p->aFile[i].zUuid = zUuid; p->aFile[i].zPerm = zPerm; p->aFile[i].zPrior = zPriorName; - p->aFile[i].iRename = -1; if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){ goto manifest_syntax_error; } break; } @@ -397,16 +511,14 @@ ** value. If is omitted then it is understood to be an ** empty string. */ case 'J': { char *zName, *zValue; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - blob_token(&line, &a2); - if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; - zName = blob_terminate(&a1); - zValue = blob_terminate(&a2); + zName = next_token(&x,0); + zValue = next_token(&x,0); + if( zName==0 ) goto manifest_syntax_error; + if( zValue==0 ) zValue = ""; defossilize(zValue); if( p->nField>=p->nFieldAlloc ){ p->nFieldAlloc = p->nFieldAlloc*2 + 10; p->aField = fossil_realloc(p->aField, p->nFieldAlloc*sizeof(p->aField[0]) ); @@ -426,18 +538,14 @@ ** ** A K-line gives the UUID for the ticket which this control file ** is amending. */ case 'K': { - char *zUuid; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - zUuid = blob_terminate(&a1); - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; if( p->zTicketUuid!=0 ) goto manifest_syntax_error; - p->zTicketUuid = zUuid; + p->zTicketUuid = next_token(&x, &sz); + if( sz!=UUID_SIZE ) goto manifest_syntax_error; + if( !validate16(p->zTicketUuid, UUID_SIZE) ) goto manifest_syntax_error; break; } /* ** L @@ -444,15 +552,13 @@ ** ** The wiki page title is fossil-encoded. There may be no more than ** one L line. */ case 'L': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); if( p->zWikiTitle!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - p->zWikiTitle = blob_terminate(&a1); + p->zWikiTitle = next_token(&x,0); + if( p->zWikiTitle==0 ) goto manifest_syntax_error; defossilize(p->zWikiTitle); if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ goto manifest_syntax_error; } break; @@ -463,15 +569,13 @@ ** ** An M-line identifies another artifact by its UUID. M-lines ** occur in clusters only. */ case 'M': { - char *zUuid; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - zUuid = blob_terminate(&a1); - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; + zUuid = next_token(&x, &sz); + if( zUuid==0 ) goto manifest_syntax_error; + if( sz!=UUID_SIZE ) goto manifest_syntax_error; if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; if( p->nCChild>=p->nCChildAlloc ){ p->nCChildAlloc = p->nCChildAlloc*2 + 10; p->azCChild = fossil_realloc(p->azCChild , p->nCChildAlloc*sizeof(p->azCChild[0]) ); @@ -490,15 +594,12 @@ ** Specify one or more other artifacts where are the parents of ** this artifact. The first parent is the primary parent. All ** others are parents by merge. */ case 'P': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - while( blob_token(&line, &a1) ){ - char *zUuid; - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; - zUuid = blob_terminate(&a1); + while( (zUuid = next_token(&x, &sz))!=0 ){ + if( sz!=UUID_SIZE ) goto manifest_syntax_error; if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; if( p->nParent>=p->nParentAlloc ){ p->nParentAlloc = p->nParentAlloc*2 + 5; p->azParent = fossil_realloc(p->azParent, p->nParentAlloc*sizeof(char*)); @@ -514,16 +615,13 @@ ** ** Specify the MD5 checksum over the name and content of all files ** in the manifest. */ case 'R': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); if( p->zRepoCksum!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - if( blob_size(&a1)!=32 ) goto manifest_syntax_error; - p->zRepoCksum = blob_terminate(&a1); + p->zRepoCksum = next_token(&x, &sz); + if( sz!=32 ) goto manifest_syntax_error; if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; break; } /* @@ -540,29 +638,20 @@ ** the tag is really a property with the given value. ** ** Tags are not allowed in clusters. Multiple T lines are allowed. */ case 'T': { - char *zName, *zUuid, *zValue; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ){ - goto manifest_syntax_error; - } - if( blob_token(&line, &a2)==0 ){ - goto manifest_syntax_error; - } - zName = blob_terminate(&a1); - zUuid = blob_terminate(&a2); - if( blob_token(&line, &a3)==0 ){ - zValue = 0; - }else{ - zValue = blob_terminate(&a3); - defossilize(zValue); - } - if( blob_size(&a2)==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){ + char *zName, *zValue; + zName = next_token(&x, 0); + if( zName==0 ) goto manifest_syntax_error; + zUuid = next_token(&x, &sz); + if( zUuid==0 ) goto manifest_syntax_error; + zValue = next_token(&x, 0); + if( zValue ) defossilize(zValue); + if( sz==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){ /* A valid uuid */ - }else if( blob_size(&a2)==1 && zUuid[0]=='*' ){ + }else if( sz==1 && zUuid[0]=='*' ){ zUuid = 0; }else{ goto manifest_syntax_error; } defossilize(zName); @@ -593,19 +682,17 @@ ** Identify the user who created this control file by their ** login. Only one U line is allowed. Prohibited in clusters. ** If the user name is omitted, take that to be "anonymous". */ case 'U': { - md5sum_step_text(blob_buffer(&line), blob_size(&line)); if( p->zUser!=0 ) goto manifest_syntax_error; - if( blob_token(&line, &a1)==0 ){ + p->zUser = next_token(&x, 0); + if( p->zUser==0 ){ p->zUser = "anonymous"; }else{ - p->zUser = blob_terminate(&a1); defossilize(p->zUser); } - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; break; } /* ** W @@ -613,26 +700,28 @@ ** The next bytes of the file contain the text of the wiki ** page. There is always an extra \n before the start of the next ** record. */ case 'W': { - int size; + char *zSize; + int size, c; Blob wiki; - md5sum_step_text(blob_buffer(&line), blob_size(&line)); - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - if( !blob_is_int(&a1, &size) ) goto manifest_syntax_error; + zSize = next_token(&x, 0); + if( zSize==0 ) goto manifest_syntax_error; + if( x.atEol==0 ) goto manifest_syntax_error; + for(size=0; (c = zSize[0])>='0' && c<='9'; zSize++){ + size = size*10 + c - '0'; + } if( size<0 ) goto manifest_syntax_error; if( p->zWiki!=0 ) goto manifest_syntax_error; blob_zero(&wiki); - if( blob_extract(pContent, size+1, &wiki)!=size+1 ){ - goto manifest_syntax_error; - } - p->zWiki = blob_buffer(&wiki); - md5sum_step_text(p->zWiki, size+1); - if( p->zWiki[size]!='\n' ) goto manifest_syntax_error; - p->zWiki[size] = 0; + if( (&x.z[size+1])>=x.zEnd ) goto manifest_syntax_error; + p->zWiki = x.z; + x.z += size; + if( x.z[0]!='\n' ) goto manifest_syntax_error; + x.z[0] = 0; + x.z++; break; } /* @@ -645,35 +734,24 @@ ** This card is required for all control file types except for ** Manifest. It is not required for manifest only for historical ** compatibility reasons. */ case 'Z': { -#ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM - int rc; - Blob hash; -#endif - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; - if( blob_size(&a1)!=32 ) goto manifest_syntax_error; - if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error; -#ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM - md5sum_finish(&hash); - rc = blob_compare(&hash, &a1); - blob_reset(&hash); - if( rc!=0 ) goto manifest_syntax_error; -#endif + zUuid = next_token(&x, &sz); + if( sz!=32 ) goto manifest_syntax_error; + if( !validate16(zUuid, 32) ) goto manifest_syntax_error; seenZ = 1; break; } default: { goto manifest_syntax_error; } } } - if( !seenHeader ) goto manifest_syntax_error; + if( x.znFile>0 || p->zRepoCksum!=0 ){ + if( p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline ){ if( p->nCChild>0 ) goto manifest_syntax_error; if( p->rDate<=0.0 ) goto manifest_syntax_error; if( p->nField>0 ) goto manifest_syntax_error; if( p->zTicketUuid ) goto manifest_syntax_error; if( p->zWiki ) goto manifest_syntax_error; @@ -754,43 +832,194 @@ if( p->zWikiTitle ) goto manifest_syntax_error; if( p->zTicketUuid ) goto manifest_syntax_error; p->type = CFTYPE_MANIFEST; } md5sum_init(); - return 1; + return p; manifest_syntax_error: /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ md5sum_init(); - manifest_clear(p); + manifest_destroy(p); return 0; } + +/* +** Get a manifest given the rid for the control artifact. Return +** a pointer to the manifest on success or NULL if there is a failure. +*/ +Manifest *manifest_get(int rid, int cfType){ + Blob content; + Manifest *p; + p = manifest_cache_find(rid); + if( p ){ + if( cfType!=CFTYPE_ANY && cfType!=p->type ){ + manifest_cache_insert(p); + p = 0; + } + return p; + } + content_get(rid, &content); + p = manifest_parse(&content, rid); + if( p && cfType!=CFTYPE_ANY && cfType!=p->type ){ + manifest_destroy(p); + p = 0; + } + return p; +} + +/* +** Given a checkin name, load and parse the manifest for that checkin. +** Throw a fatal error if anything goes wrong. +*/ +Manifest *manifest_get_by_name(const char *zName, int *pRid){ + int rid; + Manifest *p; + + rid = name_to_rid(zName); + if( !is_a_version(rid) ){ + fossil_fatal("no such checkin: %s", zName); + } + if( pRid ) *pRid = rid; + p = manifest_get(rid, CFTYPE_MANIFEST); + if( p==0 ){ + fossil_fatal("cannot parse manifest for checkin: %s", zName); + } + return p; +} /* ** COMMAND: test-parse-manifest ** ** Usage: %fossil test-parse-manifest FILENAME ?N? ** ** Parse the manifest and discarded. Use for testing only. */ void manifest_test_parse_cmd(void){ - Manifest m; + Manifest *p; Blob b; int i; int n = 1; + sqlite3_open(":memory:", &g.db); if( g.argc!=3 && g.argc!=4 ){ usage("FILENAME"); } - db_must_be_within_tree(); blob_read_from_file(&b, g.argv[2]); if( g.argc>3 ) n = atoi(g.argv[3]); for(i=0; izBaseline!=0 && p->pBaseline==0 ){ + int rid = uuid_to_rid(p->zBaseline, 0); + if( rid==0 && !throwError ){ + rid = content_new(p->zBaseline); + db_multi_exec( + "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)", + rid, p->rid + ); + return 1; + } + p->pBaseline = manifest_get(rid, CFTYPE_MANIFEST); + if( p->pBaseline==0 ){ + if( !throwError && db_exists("SELECT 1 FROM phantom WHERE rid=%d",rid) ){ + db_multi_exec( + "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)", + rid, p->rid + ); + return 1; + } + fossil_fatal("cannot access baseline manifest %S", p->zBaseline); + } + } + return 0; +} + +/* +** Rewind a manifest-file iterator back to the beginning of the manifest. +*/ +void manifest_file_rewind(Manifest *p){ + p->iFile = 0; + fetch_baseline(p, 1); + if( p->pBaseline ){ + p->pBaseline->iFile = 0; + } +} + +/* +** Advance to the next manifest-file. +** +** Return NULL for end-of-records or if there is an error. If an error +** occurs and pErr!=0 then store 1 in *pErr. +*/ +ManifestFile *manifest_file_next( + Manifest *p, + int *pErr +){ + ManifestFile *pOut = 0; + if( pErr ) *pErr = 0; + if( p->pBaseline==0 ){ + /* Manifest p is a baseline-manifest. Just scan down the list + ** of files. */ + if( p->iFilenFile ) pOut = &p->aFile[p->iFile++]; + }else{ + /* Manifest p is a delta-manifest. Scan the baseline but amend the + ** file list in the baseline with changes described by p. + */ + Manifest *pB = p->pBaseline; + int cmp; + while(1){ + if( pB->iFile>=pB->nFile ){ + /* We have used all entries out of the baseline. Return the next + ** entry from the delta. */ + if( p->iFilenFile ) pOut = &p->aFile[p->iFile++]; + break; + }else if( p->iFile>=p->nFile ){ + /* We have used all entries from the delta. Return the next + ** entry from the baseline. */ + if( pB->iFilenFile ) pOut = &pB->aFile[pB->iFile++]; + break; + }else if( (cmp = strcmp(pB->aFile[pB->iFile].zName, + p->aFile[p->iFile].zName)) < 0 ){ + /* The next baseline entry comes before the next delta entry. + ** So return the baseline entry. */ + pOut = &pB->aFile[pB->iFile++]; + break; + }else if( cmp>0 ){ + /* The next delta entry comes before the next baseline + ** entry so return the delta entry */ + pOut = &p->aFile[p->iFile++]; + break; + }else if( p->aFile[p->iFile].zUuid ){ + /* The next delta entry is a replacement for the next baseline + ** entry. Skip the baseline entry and return the delta entry */ + pB->iFile++; + pOut = &p->aFile[p->iFile++]; + break; + }else{ + /* The next delta entry is a delete of the next baseline + ** entry. Skip them both. Repeat the loop to find the next + ** non-delete entry. */ + pB->iFile++; + p->iFile++; + continue; + } + } } + return pOut; } /* ** Translate a filename into a filename-id (fnid). Create a new fnid ** if no previously exists. @@ -818,14 +1047,14 @@ ** Add a single entry to the mlink table. Also add the filename to ** the filename table if it is not there already. */ static void add_one_mlink( int mid, /* The record ID of the manifest */ - const char *zFromUuid, /* UUID for the mlink.pid field */ - const char *zToUuid, /* UUID for the mlink.fid field */ + const char *zFromUuid, /* UUID for the mlink.pid. "" to add file */ + const char *zToUuid, /* UUID for the mlink.fid. "" to delele */ const char *zFilename, /* Filename */ - const char *zPrior /* Previous filename. NULL if unchanged */ + const char *zPrior /* Previous filename. NULL if unchanged */ ){ int fnid, pfnid, pid, fid; static Stmt s1; fnid = filename_to_fnid(zFilename); @@ -832,16 +1061,16 @@ if( zPrior==0 ){ pfnid = 0; }else{ pfnid = filename_to_fnid(zPrior); } - if( zFromUuid==0 ){ + if( zFromUuid==0 || zFromUuid[0]==0 ){ pid = 0; }else{ pid = uuid_to_rid(zFromUuid, 1); } - if( zToUuid==0 ){ + if( zToUuid==0 || zToUuid[0]==0 ){ fid = 0; }else{ fid = uuid_to_rid(zToUuid, 1); } db_static_prepare(&s1, @@ -858,33 +1087,83 @@ content_deltify(pid, fid, 0); } } /* -** Locate a file named zName in the aFile[] array of the given -** manifest. We assume that filenames are in sorted order. -** Use a binary search. Return turn the index of the matching -** entry. Or return -1 if not found. +** Do a binary search to find a file in the p->aFile[] array. +** +** As an optimization, guess that the file we seek is at index p->iFile. +** That will usually be the case. If it is not found there, then do the +** actual binary search. +** +** Update p->iFile to be the index of the file that is found. */ -static int find_file_in_manifest(Manifest *p, const char *zName){ +static ManifestFile *manifest_file_seek_base(Manifest *p, const char *zName){ int lwr, upr; int c; int i; lwr = 0; upr = p->nFile - 1; + if( p->iFile>=lwr && p->iFileaFile[p->iFile+1].zName, zName); + if( c==0 ){ + return &p->aFile[++p->iFile]; + }else if( c>0 ){ + upr = p->iFile; + }else{ + lwr = p->iFile+1; + } + } while( lwr<=upr ){ i = (lwr+upr)/2; c = strcmp(p->aFile[i].zName, zName); if( c<0 ){ lwr = i+1; }else if( c>0 ){ upr = i-1; }else{ - return i; + p->iFile = i; + return &p->aFile[i]; } } - return -1; + return 0; +} + +/* +** Locate a file named zName in the aFile[] array of the given manifest. +** Return a pointer to the appropriate ManifestFile object. Return NULL +** if not found. +** +** This routine works even if p is a delta-manifest. The pointer +** returned might be to the baseline. +** +** We assume that filenames are in sorted order and use a binary search. +*/ +ManifestFile *manifest_file_seek(Manifest *p, const char *zName){ + ManifestFile *pFile; + + pFile = manifest_file_seek_base(p, zName); + if( pFile && pFile->zUuid==0 ) return 0; + if( pFile==0 && p->zBaseline ){ + fetch_baseline(p, 1); + pFile = manifest_file_seek_base(p->pBaseline, zName); + } + return pFile; +} + +/* +** This strcmp() function handles NULL arguments. NULLs sort first. +*/ +static int strcmp_null(const char *zOne, const char *zTwo){ + if( zOne==0 ){ + if( zTwo==0 ) return 0; + return -1; + }else if( zTwo==0 ){ + return +1; + }else{ + return strcmp(zOne, zTwo); + } } /* ** Add mlink table entries associated with manifest cid. The ** parent manifest is pid. @@ -895,89 +1174,76 @@ ** Deleted files have mlink.fid=0. ** Added files have mlink.pid=0. ** Edited files have both mlink.pid!=0 and mlink.fid!=0 */ static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){ - Manifest other; Blob otherContent; int otherRid; - int i, j; + int i, rc; + ManifestFile *pChildFile, *pParentFile; + Manifest **ppOther; + static Stmt eq; - if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", cid) ){ - return; - } + db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid"); + db_bind_int(&eq, ":mid", cid); + rc = db_step(&eq); + db_reset(&eq); + if( rc==SQLITE_ROW ) return; + assert( pParent==0 || pChild==0 ); if( pParent==0 ){ - pParent = &other; + ppOther = &pParent; otherRid = pid; }else{ - pChild = &other; + ppOther = &pChild; otherRid = cid; } - if( manifest_cache_find(otherRid, &other)==0 ){ + if( (*ppOther = manifest_cache_find(otherRid))==0 ){ content_get(otherRid, &otherContent); if( blob_size(&otherContent)==0 ) return; - if( manifest_parse(&other, &otherContent)==0 ) return; - } - content_deltify(pid, cid, 0); - - /* Use the iRename fields to find the cross-linkage between - ** renamed files. */ - for(j=0; jnFile; j++){ - const char *zPrior = pChild->aFile[j].zPrior; - if( zPrior && zPrior[0] ){ - i = find_file_in_manifest(pParent, zPrior); - if( i>=0 ){ - pChild->aFile[j].iRename = i; - pParent->aFile[i].iRename = j; - } - } - } - - /* Construct the mlink entries */ - for(i=j=0; inFile && jnFile; ){ - int c; - if( pParent->aFile[i].iRename>=0 ){ - i++; - }else if( (c = strcmp(pParent->aFile[i].zName, pChild->aFile[j].zName))<0 ){ - add_one_mlink(cid, pParent->aFile[i].zUuid,0,pParent->aFile[i].zName,0); - i++; - }else if( c>0 ){ - int rn = pChild->aFile[j].iRename; - if( rn>=0 ){ - add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, - pChild->aFile[j].zName, pParent->aFile[rn].zName); - }else{ - add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); - } - j++; - }else{ - if( strcmp(pParent->aFile[i].zUuid, pChild->aFile[j].zUuid)!=0 ){ - add_one_mlink(cid, pParent->aFile[i].zUuid, pChild->aFile[j].zUuid, - pChild->aFile[j].zName, 0); - } - i++; - j++; - } - } - while( inFile ){ - if( pParent->aFile[i].iRename<0 ){ - add_one_mlink(cid, pParent->aFile[i].zUuid, 0, pParent->aFile[i].zName,0); - } - i++; - } - while( jnFile ){ - int rn = pChild->aFile[j].iRename; - if( rn>=0 ){ - add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, - pChild->aFile[j].zName, pParent->aFile[rn].zName); - }else{ - add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); - } - j++; - } - manifest_cache_insert(otherRid, &other); + *ppOther = manifest_parse(&otherContent, otherRid); + if( *ppOther==0 ) return; + } + if( fetch_baseline(pParent, 0) || fetch_baseline(pChild, 0) ){ + manifest_destroy(*ppOther); + return; + } + if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){ + content_deltify(pid, cid, 0); + }else if( pChild->zBaseline==0 && pParent->zBaseline!=0 ){ + content_deltify(pParent->pBaseline->rid, cid, 0); + } + + for(i=0, pChildFile=pChild->aFile; inFile; i++, pChildFile++){ + if( pChildFile->zPrior ){ + pParentFile = manifest_file_seek(pParent, pChildFile->zPrior); + if( pParentFile ){ + add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, + pChildFile->zName, pChildFile->zPrior); + } + }else{ + pParentFile = manifest_file_seek(pParent, pChildFile->zName); + if( pParentFile==0 ){ + if( pChildFile->zUuid ){ + add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0); + } + }else if( strcmp_null(pChildFile->zUuid, pParentFile->zUuid)!=0 ){ + add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, + pChildFile->zName, 0); + } + } + } + if( pParent->zBaseline && pChild->zBaseline ){ + for(i=0, pParentFile=pParent->aFile; inFile; i++, pParentFile++){ + if( pParentFile->zUuid ) continue; + pChildFile = manifest_file_seek(pChild, pParentFile->zName); + if( pChildFile ){ + add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0); + } + } + } + manifest_cache_insert(*ppOther); } /* ** True if manifest_crosslink_begin() has been called but ** manifest_crosslink_end() is still pending. @@ -1114,40 +1380,44 @@ ** of the routine, "manifest_crosslink", and the name of this source ** file, is a legacy of its original use. */ int manifest_crosslink(int rid, Blob *pContent){ int i; - Manifest m; + Manifest *p; Stmt q; int parentid = 0; - if( manifest_cache_find(rid, &m) ){ + if( (p = manifest_cache_find(rid))!=0 ){ blob_reset(pContent); - }else if( manifest_parse(&m, pContent)==0 ){ + }else if( (p = manifest_parse(pContent, rid))==0 ){ + return 0; + } + if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){ + manifest_destroy(p); return 0; } - if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){ - manifest_clear(&m); + if( p->type==CFTYPE_MANIFEST && fetch_baseline(p, 0) ){ + manifest_destroy(p); return 0; } db_begin_transaction(); - if( m.type==CFTYPE_MANIFEST ){ + if( p->type==CFTYPE_MANIFEST ){ if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ char *zCom; - for(i=0; inParent; i++){ + int pid = uuid_to_rid(p->azParent[i], 1); db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" - "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate); + "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate); if( i==0 ){ - add_mlink(pid, 0, rid, &m); + add_mlink(pid, 0, rid, p); parentid = pid; } } db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid); while( db_step(&q)==SQLITE_ROW ){ int cid = db_column_int(&q, 0); - add_mlink(rid, &m, cid, 0); + add_mlink(rid, p, cid, 0); } db_finalize(&q); db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment," "bgcolor,euser,ecomment)" @@ -1158,118 +1428,134 @@ " )," " %d,%Q,%Q," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", - TAG_DATE, rid, m.rDate, - rid, m.zUser, m.zComment, + TAG_DATE, rid, p->rDate, + rid, p->zUser, p->zComment, TAG_BGCOLOR, rid, TAG_USER, rid, TAG_COMMENT, rid ); zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" " WHERE rowid=last_insert_rowid()"); - wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE); - free(zCom); - } - } - if( m.type==CFTYPE_CLUSTER ){ - tag_insert("cluster", 1, 0, rid, m.rDate, rid); - for(i=0; i0 ){ - db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid); - } - } - } - if( m.type==CFTYPE_CONTROL - || m.type==CFTYPE_MANIFEST - || m.type==CFTYPE_EVENT - ){ - for(i=0; irDate, 1, WIKI_INLINE); + free(zCom); + + /* If this is a delta-manifest, record the fact that this repository + ** contains delta manifests, to free the "commit" logic to generate + ** new delta manifests. + */ + if( p->zBaseline!=0 ){ + static int once = 0; + if( !once ){ + db_set_int("seen-delta-manifest", 1, 0); + once = 0; + } + } + } + } + if( p->type==CFTYPE_CLUSTER ){ + static Stmt del1; + tag_insert("cluster", 1, 0, rid, p->rDate, rid); + db_static_prepare(&del1, "DELETE FROM unclustered WHERE rid=:rid"); + for(i=0; inCChild; i++){ + int mid; + mid = uuid_to_rid(p->azCChild[i], 1); + if( mid>0 ){ + db_bind_int(&del1, ":rid", mid); + db_step(&del1); + db_reset(&del1); + } + } + } + if( p->type==CFTYPE_CONTROL + || p->type==CFTYPE_MANIFEST + || p->type==CFTYPE_EVENT + ){ + for(i=0; inTag; i++){ + int tid; + int type; + if( p->aTag[i].zUuid ){ + tid = uuid_to_rid(p->aTag[i].zUuid, 1); }else{ tid = rid; } if( tid ){ - switch( m.aTag[i].zName[0] ){ + switch( p->aTag[i].zName[0] ){ case '-': type = 0; break; /* Cancel prior occurances */ case '+': type = 1; break; /* Apply to target only */ case '*': type = 2; break; /* Propagate to descendants */ default: - fossil_fatal("unknown tag type in manifest: %s", m.aTag); + fossil_fatal("unknown tag type in manifest: %s", p->aTag); return 0; } - tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue, - rid, m.rDate, tid); + tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, + rid, p->rDate, tid); } } if( parentid ){ tag_propagate_all(parentid); } } - if( m.type==CFTYPE_WIKI ){ - char *zTag = mprintf("wiki-%s", m.zWikiTitle); + if( p->type==CFTYPE_WIKI ){ + char *zTag = mprintf("wiki-%s", p->zWikiTitle); int tagid = tag_findid(zTag, 1); int prior; char *zComment; int nWiki; char zLength[40]; - while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; - nWiki = strlen(m.zWiki); + while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; + nWiki = strlen(p->zWiki); sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); - tag_insert(zTag, 1, zLength, rid, m.rDate, rid); + tag_insert(zTag, 1, zLength, rid, p->rDate, rid); free(zTag); prior = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=%d AND mtime<%.17g" " ORDER BY mtime DESC", - tagid, m.rDate + tagid, p->rDate ); if( prior ){ content_deltify(prior, rid, 0); } if( nWiki>0 ){ - zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); + zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); }else{ - zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle); + zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); } db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment," " bgcolor,euser,ecomment)" "VALUES('w',%.17g,%d,%Q,%Q," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", - m.rDate, rid, m.zUser, zComment, + p->rDate, rid, p->zUser, zComment, TAG_BGCOLOR, rid, TAG_BGCOLOR, rid, TAG_USER, rid, TAG_COMMENT, rid ); free(zComment); } - if( m.type==CFTYPE_EVENT ){ - char *zTag = mprintf("event-%s", m.zEventId); + if( p->type==CFTYPE_EVENT ){ + char *zTag = mprintf("event-%s", p->zEventId); int tagid = tag_findid(zTag, 1); int prior, subsequent; int nWiki; char zLength[40]; - while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; - nWiki = strlen(m.zWiki); + while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; + nWiki = strlen(p->zWiki); sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); - tag_insert(zTag, 1, zLength, rid, m.rDate, rid); + tag_insert(zTag, 1, zLength, rid, p->rDate, rid); free(zTag); prior = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=%d AND mtime<%.17g" " ORDER BY mtime DESC", - tagid, m.rDate + tagid, p->rDate ); if( prior ){ content_deltify(prior, rid, 0); db_multi_exec( "DELETE FROM event" @@ -1281,108 +1567,87 @@ } subsequent = db_int(0, "SELECT rid FROM tagxref" " WHERE tagid=%d AND mtime>%.17g" " ORDER BY mtime", - tagid, m.rDate + tagid, p->rDate ); if( subsequent ){ content_deltify(rid, subsequent, 0); }else{ db_multi_exec( "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" "VALUES('e',%.17g,%d,%d,%Q,%Q," " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", - m.rEventDate, rid, tagid, m.zUser, m.zComment, + p->rEventDate, rid, tagid, p->zUser, p->zComment, TAG_BGCOLOR, rid ); } } - if( m.type==CFTYPE_TICKET ){ + if( p->type==CFTYPE_TICKET ){ char *zTag; assert( manifest_crosslink_busy==1 ); - zTag = mprintf("tkt-%s", m.zTicketUuid); - tag_insert(zTag, 1, 0, rid, m.rDate, rid); + zTag = mprintf("tkt-%s", p->zTicketUuid); + tag_insert(zTag, 1, 0, rid, p->rDate, rid); free(zTag); db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", - m.zTicketUuid); + p->zTicketUuid); } - if( m.type==CFTYPE_ATTACHMENT ){ + if( p->type==CFTYPE_ATTACHMENT ){ db_multi_exec( "INSERT INTO attachment(attachid, mtime, src, target," "filename, comment, user)" "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", - rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName, - (m.zComment ? m.zComment : ""), m.zUser + rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName, + (p->zComment ? p->zComment : ""), p->zUser ); db_multi_exec( "UPDATE attachment SET isLatest = (mtime==" "(SELECT max(mtime) FROM attachment" " WHERE target=%Q AND filename=%Q))" " WHERE target=%Q AND filename=%Q", - m.zAttachTarget, m.zAttachName, - m.zAttachTarget, m.zAttachName + p->zAttachTarget, p->zAttachName, + p->zAttachTarget, p->zAttachName ); - if( strlen(m.zAttachTarget)!=UUID_SIZE - || !validate16(m.zAttachTarget, UUID_SIZE) + if( strlen(p->zAttachTarget)!=UUID_SIZE + || !validate16(p->zAttachTarget, UUID_SIZE) ){ char *zComment; - if( m.zAttachSrc && m.zAttachSrc[0] ){ + if( p->zAttachSrc && p->zAttachSrc[0] ){ zComment = mprintf("Add attachment \"%h\" to wiki page [%h]", - m.zAttachName, m.zAttachTarget); + p->zAttachName, p->zAttachTarget); }else{ zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", - m.zAttachName, m.zAttachTarget); + p->zAttachName, p->zAttachTarget); } db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment)" "VALUES('w',%.17g,%d,%Q,%Q)", - m.rDate, rid, m.zUser, zComment + p->rDate, rid, p->zUser, zComment ); free(zComment); }else{ char *zComment; - if( m.zAttachSrc && m.zAttachSrc[0] ){ + if( p->zAttachSrc && p->zAttachSrc[0] ){ zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]", - m.zAttachName, m.zAttachTarget); + p->zAttachName, p->zAttachTarget); }else{ zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", - m.zAttachName, m.zAttachTarget); + p->zAttachName, p->zAttachTarget); } db_multi_exec( "REPLACE INTO event(type,mtime,objid,user,comment)" "VALUES('t',%.17g,%d,%Q,%Q)", - m.rDate, rid, m.zUser, zComment + p->rDate, rid, p->zUser, zComment ); free(zComment); } } db_end_transaction(0); - if( m.type==CFTYPE_MANIFEST ){ - manifest_cache_insert(rid, &m); + if( p->type==CFTYPE_MANIFEST ){ + manifest_cache_insert(p); }else{ - manifest_clear(&m); + manifest_destroy(p); } return 1; } - -/* -** Given a checkin name, load and parse the manifest for that checkin. -** Throw a fatal error if anything goes wrong. -*/ -void manifest_from_name( - const char *zName, - Manifest *pM -){ - int rid; - Blob content; - - rid = name_to_rid(zName); - if( !is_a_version(rid) ){ - fossil_fatal("no such checkin: %s", zName); - } - content_get(rid, &content); - if( !manifest_parse(pM, &content) ){ - fossil_fatal("cannot parse manifest for checkin: %s", zName); - } -} Index: src/md5.c ================================================================== --- src/md5.c +++ src/md5.c @@ -41,12 +41,16 @@ uint32 bits[2]; unsigned char in[64]; }; typedef struct Context MD5Context; +#if defined(__i386__) || defined(__x86_64__) || defined(_WIN32) +# define byteReverse(A,B) +#else /* - * Note: this code is harmless on little-endian machines. + * Convert an array of integers to little-endian. + * Note: this code is a no-op on little-endian machines. */ static void byteReverse (unsigned char *buf, unsigned longs){ uint32 t; do { t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | @@ -53,10 +57,12 @@ ((unsigned)buf[1]<<8 | buf[0]); *(uint32 *)buf = t; buf += 4; } while (--longs); } +#endif + /* The four core functions - F1 is optimized somewhat */ /* #define F1(x, y, z) (x & y | ~x & z) */ #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) @@ -314,10 +320,30 @@ ** Add the content of a blob to the incremental MD5 checksum. */ void md5sum_step_blob(Blob *p){ md5sum_step_text(blob_buffer(p), blob_size(p)); } + +/* +** For trouble-shooting only: +** +** Report the current state of the incremental checksum. +*/ +const char *md5sum_current_state(void){ + unsigned int cksum = 0; + unsigned int *pFirst, *pLast; + static char zResult[12]; + + pFirst = (unsigned int*)&incrCtx; + pLast = (unsigned int*)((&incrCtx)+1); + while( pFirstlastOutput ){ + printf(" %d.%d%% complete...\r", permill/10, permill%10); + fflush(stdout); + lastOutput = permill; + } +} + /* ** Called after each artifact is processed */ static void rebuild_step_done(rid){ @@ -92,12 +107,11 @@ /* assert( bag_find(&bagDone, rid)==0 ); */ bag_insert(&bagDone, rid); if( ttyOutput ){ processCnt++; if (!g.fQuiet) { - printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize)); - fflush(stdout); + percent_complete((processCnt*1000)/totalSize); } } } /* @@ -167,20 +181,21 @@ rebuild_step_done(rid); /* Call all children recursively */ rid = 0; for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ - Stmt q2; + static Stmt q2; int sz; - db_prepare(&q2, "SELECT content, size FROM blob WHERE rid=%d", cid); + db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid"); + db_bind_int(&q2, ":rid", cid); if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ Blob delta, next; db_ephemeral_blob(&q2, 0, &delta); blob_uncompress(&delta, &delta); blob_delta_apply(pBase, &delta, &next); blob_reset(&delta); - db_finalize(&q2); + db_reset(&q2); if( i -#endif #include #include "config.h" #include "sha1.h" -#define SHA1HashSize 20 -#define shaSuccess 0 -#define shaInputTooLong 1 -#define shaStateError 2 - -/* - * This structure will hold context information for the SHA-1 - * hashing operation - */ -typedef struct SHA1Context SHA1Context; -struct SHA1Context { - uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ - - uint32_t Length_Low; /* Message length in bits */ - uint32_t Length_High; /* Message length in bits */ - - int Message_Block_Index; /* Index into message block array */ - uint8_t Message_Block[64]; /* 512-bit message blocks */ - - int Computed; /* Is the digest computed? */ - int Corrupted; /* Is the message digest corrupted? */ -}; - -/* - * sha1.c - * - * Description: - * This file implements the Secure Hashing Algorithm 1 as - * defined in FIPS PUB 180-1 published April 17, 1995. - * - * The SHA-1, produces a 160-bit message digest for a given - * data stream. It should take about 2**n steps to find a - * message with the same digest as a given message and - * 2**(n/2) to find any two messages with the same digest, - * when n is the digest size in bits. Therefore, this - * algorithm can serve as a means of providing a - * "fingerprint" for a message. - * - * Portability Issues: - * SHA-1 is defined in terms of 32-bit "words". This code - * uses (included via "sha1.h" to define 32 and 8 - * bit unsigned integer types. If your C compiler does not - * support 32 bit unsigned integers, this code is not - * appropriate. - * - * Caveats: - * SHA-1 is designed to work with messages less than 2^64 bits - * long. Although SHA-1 allows a message digest to be generated - * for messages of any number of bits less than 2^64, this - * implementation only works with messages with a length that is - * a multiple of the size of an 8-bit character. - * - */ - -/* - * Define the SHA1 circular left shift macro - */ -#define SHA1CircularShift(bits,word) \ - (((word) << (bits)) | ((word) >> (32-(bits)))) - -/* Local Function Prototyptes */ -static void SHA1PadMessage(SHA1Context *); -static void SHA1ProcessMessageBlock(SHA1Context *); - -/* - * SHA1Reset - * - * Description: - * This function will initialize the SHA1Context in preparation - * for computing a new SHA1 message digest. - * - * Parameters: - * context: [in/out] - * The context to reset. - * - * Returns: - * sha Error Code. - * - */ -static int SHA1Reset(SHA1Context *context) -{ - context->Length_Low = 0; - context->Length_High = 0; - context->Message_Block_Index = 0; - - context->Intermediate_Hash[0] = 0x67452301; - context->Intermediate_Hash[1] = 0xEFCDAB89; - context->Intermediate_Hash[2] = 0x98BADCFE; - context->Intermediate_Hash[3] = 0x10325476; - context->Intermediate_Hash[4] = 0xC3D2E1F0; - - context->Computed = 0; - context->Corrupted = 0; - - return shaSuccess; -} - -/* - * SHA1Result - * - * Description: - * This function will return the 160-bit message digest into the - * Message_Digest array provided by the caller. - * NOTE: The first octet of hash is stored in the 0th element, - * the last octet of hash in the 19th element. - * - * Parameters: - * context: [in/out] - * The context to use to calculate the SHA-1 hash. - * Message_Digest: [out] - * Where the digest is returned. - * - * Returns: - * sha Error Code. - * - */ -static int SHA1Result( SHA1Context *context, - uint8_t Message_Digest[SHA1HashSize]) -{ - int i; - - if (context->Corrupted) - { - return context->Corrupted; - } - - if (!context->Computed) - { - SHA1PadMessage(context); - for(i=0; i<64; ++i) - { - /* message may be sensitive, clear it out */ - context->Message_Block[i] = 0; - } - context->Length_Low = 0; /* and clear length */ - context->Length_High = 0; - context->Computed = 1; - - } - - for(i = 0; i < SHA1HashSize; ++i) - { - Message_Digest[i] = context->Intermediate_Hash[i>>2] - >> 8 * ( 3 - ( i & 0x03 ) ); - } - - return shaSuccess; -} - -/* - * SHA1Input - * - * Description: - * This function accepts an array of octets as the next portion - * of the message. - * - * Parameters: - * context: [in/out] - * The SHA context to update - * message_array: [in] - * An array of characters representing the next portion of - * the message. - * length: [in] - * The length of the message in message_array - * - * Returns: - * sha Error Code. - * - */ -static -int SHA1Input( SHA1Context *context, - const uint8_t *message_array, - unsigned length) -{ - if (!length) - { - return shaSuccess; - } - - if (context->Computed) - { - context->Corrupted = shaStateError; - - return shaStateError; - } - - if (context->Corrupted) - { - return context->Corrupted; - } - while(length-- && !context->Corrupted) - { - context->Message_Block[context->Message_Block_Index++] = - (*message_array & 0xFF); - - context->Length_Low += 8; - if (context->Length_Low == 0) - { - context->Length_High++; - if (context->Length_High == 0) - { - /* Message is too long */ - context->Corrupted = 1; - } - } - - if (context->Message_Block_Index == 64) - { - SHA1ProcessMessageBlock(context); - } - - message_array++; - } - - return shaSuccess; -} - -/* - * SHA1ProcessMessageBlock - * - * Description: - * This function will process the next 512 bits of the message - * stored in the Message_Block array. - * - * Parameters: - * None. - * - * Returns: - * Nothing. - * - * Comments: - * Many of the variable names in this code, especially the - * single character names, were used because those were the - * names used in the publication. - * - * - */ -static void SHA1ProcessMessageBlock(SHA1Context *context) -{ - const uint32_t K[] = { /* Constants defined in SHA-1 */ - 0x5A827999, - 0x6ED9EBA1, - 0x8F1BBCDC, - 0xCA62C1D6 - }; - int t; /* Loop counter */ - uint32_t temp; /* Temporary word value */ - uint32_t W[80]; /* Word sequence */ - uint32_t A, B, C, D, E; /* Word buffers */ - - /* - * Initialize the first 16 words in the array W - */ - for(t = 0; t < 16; t++) - { - W[t] = context->Message_Block[t * 4] << 24; - W[t] |= context->Message_Block[t * 4 + 1] << 16; - W[t] |= context->Message_Block[t * 4 + 2] << 8; - W[t] |= context->Message_Block[t * 4 + 3]; - } - - for(t = 16; t < 80; t++) - { - W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); - } - - A = context->Intermediate_Hash[0]; - B = context->Intermediate_Hash[1]; - C = context->Intermediate_Hash[2]; - D = context->Intermediate_Hash[3]; - E = context->Intermediate_Hash[4]; - - for(t = 0; t < 20; t++) - { - temp = SHA1CircularShift(5,A) + - ((B & C) | ((~B) & D)) + E + W[t] + K[0]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - - B = A; - A = temp; - } - - for(t = 20; t < 40; t++) - { - temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 40; t < 60; t++) - { - temp = SHA1CircularShift(5,A) + - ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - B = A; - A = temp; - } - - for(t = 60; t < 80; t++) - { - temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; - E = D; - D = C; - C = SHA1CircularShift(30,B); - B = A; - A = temp; - } - - context->Intermediate_Hash[0] += A; - context->Intermediate_Hash[1] += B; - context->Intermediate_Hash[2] += C; - context->Intermediate_Hash[3] += D; - context->Intermediate_Hash[4] += E; - - context->Message_Block_Index = 0; -} - -/* - * SHA1PadMessage - * - - * Description: - * According to the standard, the message must be padded to an even - * 512 bits. The first padding bit must be a '1'. The last 64 - * bits represent the length of the original message. All bits in - * between should be 0. This function will pad the message - * according to those rules by filling the Message_Block array - * accordingly. It will also call the ProcessMessageBlock function - * provided appropriately. When it returns, it can be assumed that - * the message digest has been computed. - * - * Parameters: - * context: [in/out] - * The context to pad - * ProcessMessageBlock: [in] - * The appropriate SHA*ProcessMessageBlock function - * Returns: - * Nothing. - * - */ -static void SHA1PadMessage(SHA1Context *context) -{ - /* - * Check to see if the current message block is too small to hold - * the initial padding bits and length. If so, we will pad the - * block, process it, and then continue padding into a second - * block. - */ - if (context->Message_Block_Index > 55) - { - context->Message_Block[context->Message_Block_Index++] = 0x80; - while(context->Message_Block_Index < 64) - { - context->Message_Block[context->Message_Block_Index++] = 0; - } - - SHA1ProcessMessageBlock(context); - - while(context->Message_Block_Index < 56) - { - context->Message_Block[context->Message_Block_Index++] = 0; - } - } - else - { - context->Message_Block[context->Message_Block_Index++] = 0x80; - while(context->Message_Block_Index < 56) - { - - context->Message_Block[context->Message_Block_Index++] = 0; - } - } - - /* - * Store the message length as the last 8 octets - */ - context->Message_Block[56] = context->Length_High >> 24; - context->Message_Block[57] = context->Length_High >> 16; - context->Message_Block[58] = context->Length_High >> 8; - context->Message_Block[59] = context->Length_High; - context->Message_Block[60] = context->Length_Low >> 24; - context->Message_Block[61] = context->Length_Low >> 16; - context->Message_Block[62] = context->Length_Low >> 8; - context->Message_Block[63] = context->Length_Low; - - SHA1ProcessMessageBlock(context); + +/* +** The SHA1 implementation below is adapted from: +** +** $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $ +** $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $ +** +** SHA-1 in C +** By Steve Reid +** 100% Public Domain +*/ +typedef struct SHA1Context SHA1Context; +struct SHA1Context { + unsigned int state[5]; + unsigned int count[2]; + unsigned char buffer[64]; +}; + +/* + * blk0() and blk() perform the initial expand. + * I got the idea of expanding during the round function from SSLeay + * + * blk0le() for little-endian and blk0be() for big-endian. + */ +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk0le(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#define blk0be(i) block->l[i] +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* + * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 + * + * Rl0() for little-endian and Rb0() for big-endian. Endianness is + * determined at run-time. + */ +#define Rl0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define Rb0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) \ + z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + +typedef union { + unsigned char c[64]; + unsigned int l[16]; +} CHAR64LONG16; + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ +void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) +{ + unsigned int a, b, c, d, e; + CHAR64LONG16 *block; + static int one = 1; + CHAR64LONG16 workspace; + + block = &workspace; + (void)memcpy(block, buffer, 64); + + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + /* 4 rounds of 20 operations each. Loop unrolled. */ + if( 1 == *(unsigned char*)&one ){ + 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); + 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); + 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); + 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); + }else{ + 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); + 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); + 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); + 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); + } + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + 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); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* + * SHA1Init - Initialize new context + */ +static void SHA1Init(SHA1Context *context){ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* + * Run your data through this. + */ +static void SHA1Update( + SHA1Context *context, + const unsigned char *data, + unsigned int len +){ + unsigned int i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1] += (len>>29)+1; + j = (j >> 3) & 63; + if ((j + len) > 63) { + (void)memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) + SHA1Transform(context->state, &data[i]); + j = 0; + } else { + i = 0; + } + (void)memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* + * Add padding and return the message digest. + */ +static void SHA1Final(SHA1Context *context, unsigned char digest[20]){ + unsigned int i; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + SHA1Update(context, (const unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) + SHA1Update(context, (const unsigned char *)"\0", 1); + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + + if (digest) { + for (i = 0; i < 20; i++) + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } } /* ** Convert a digest into base-16. digest should be declared as @@ -441,18 +214,18 @@ /* ** Add more text to the incremental SHA1 checksum. */ void sha1sum_step_text(const char *zText, int nBytes){ if( !incrInit ){ - SHA1Reset(&incrCtx); + SHA1Init(&incrCtx); incrInit = 1; } if( nBytes<=0 ){ if( nBytes==0 ) return; nBytes = strlen(zText); } - SHA1Input(&incrCtx, (unsigned char*)zText, nBytes); + SHA1Update(&incrCtx, (unsigned char*)zText, nBytes); } /* ** Add the content of a blob to the incremental SHA1 checksum. */ @@ -470,11 +243,11 @@ */ char *sha1sum_finish(Blob *pOut){ unsigned char zResult[20]; static char zOut[41]; sha1sum_step_text(0,0); - SHA1Result(&incrCtx, zResult); + SHA1Final(&incrCtx, zResult); incrInit = 0; DigestToBase16(zResult, zOut); if( pOut ){ blob_zero(pOut); blob_append(pOut, zOut, 40); @@ -497,21 +270,21 @@ in = fopen(zFilename,"rb"); if( in==0 ){ return 1; } - SHA1Reset(&ctx); + SHA1Init(&ctx); for(;;){ int n; n = fread(zBuf, 1, sizeof(zBuf), in); if( n<=0 ) break; - SHA1Input(&ctx, (unsigned char*)zBuf, (unsigned)n); + SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n); } fclose(in); blob_zero(pCksum); blob_resize(pCksum, 40); - SHA1Result(&ctx, zResult); + SHA1Final(&ctx, zResult); DigestToBase16(zResult, blob_buffer(pCksum)); return 0; } /* @@ -523,19 +296,19 @@ */ int sha1sum_blob(const Blob *pIn, Blob *pCksum){ SHA1Context ctx; unsigned char zResult[20]; - SHA1Reset(&ctx); - SHA1Input(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); + SHA1Init(&ctx); + SHA1Update(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); if( pIn==pCksum ){ blob_reset(pCksum); }else{ blob_zero(pCksum); } blob_resize(pCksum, 40); - SHA1Result(&ctx, zResult); + SHA1Final(&ctx, zResult); DigestToBase16(zResult, blob_buffer(pCksum)); return 0; } /* @@ -545,13 +318,13 @@ char *sha1sum(const char *zIn){ SHA1Context ctx; unsigned char zResult[20]; char zDigest[41]; - SHA1Reset(&ctx); - SHA1Input(&ctx, (unsigned const char*)zIn, strlen(zIn)); - SHA1Result(&ctx, zResult); + SHA1Init(&ctx); + SHA1Update(&ctx, (unsigned const char*)zIn, strlen(zIn)); + SHA1Final(&ctx, zResult); DigestToBase16(zResult, zDigest); return mprintf("%s", zDigest); } /* @@ -575,11 +348,11 @@ static char *zProjectId = 0; SHA1Context ctx; unsigned char zResult[20]; char zDigest[41]; - SHA1Reset(&ctx); + SHA1Init(&ctx); if( zProjectId==0 ){ zProjectId = db_get("project-code", 0); /* On the first xfer request of a clone, the project-code is not yet ** known. Use the cleartext password, since that is all we have. @@ -586,16 +359,16 @@ */ if( zProjectId==0 ){ return mprintf("%s", zPw); } } - SHA1Input(&ctx, (unsigned char*)zProjectId, strlen(zProjectId)); - SHA1Input(&ctx, (unsigned char*)"/", 1); - SHA1Input(&ctx, (unsigned char*)zLogin, strlen(zLogin)); - SHA1Input(&ctx, (unsigned char*)"/", 1); - SHA1Input(&ctx, (unsigned const char*)zPw, strlen(zPw)); - SHA1Result(&ctx, zResult); + SHA1Update(&ctx, (unsigned char*)zProjectId, strlen(zProjectId)); + SHA1Update(&ctx, (unsigned char*)"/", 1); + SHA1Update(&ctx, (unsigned char*)zLogin, strlen(zLogin)); + SHA1Update(&ctx, (unsigned char*)"/", 1); + SHA1Update(&ctx, (unsigned const char*)zPw, strlen(zPw)); + SHA1Final(&ctx, zResult); DigestToBase16(zResult, zDigest); return mprintf("%s", zDigest); } /* Index: src/sync.c ================================================================== --- src/sync.c +++ src/sync.c @@ -165,11 +165,14 @@ ** The URL specified normally becomes the new "remote-url" used for ** subsequent push, pull, and sync operations. However, ** the "--once" command-line option makes the URL a one-time-use URL ** that is not saved. ** -** See also: clone, pull, sync, remote-url +** If configured (setting push-hook-..), the push hook command will be +** executed on the server after pushing files. +** +** See also: callhook, clone, pull, sync, remote-url */ void push_cmd(void){ process_sync_args(); client_sync(1,0,0,0,0); } @@ -196,11 +199,14 @@ ** The URL specified normally becomes the new "remote-url" used for ** subsequent push, pull, and sync operations. However, ** the "--once" command-line option makes the URL a one-time-use URL ** that is not saved. ** -** See also: clone, push, pull, remote-url +** If configured (setting push-hook-..), the push hook command will be +** executed on the server after pushing files. +** +** See also: callhook, clone, push, pull, remote-url */ void sync_cmd(void){ int syncFlags = process_sync_args(); client_sync(1,1,0,syncFlags,0); } Index: src/tkt.c ================================================================== --- src/tkt.c +++ src/tkt.c @@ -214,25 +214,25 @@ */ void ticket_rebuild_entry(const char *zTktUuid){ char *zTag = mprintf("tkt-%s", zTktUuid); int tagid = tag_findid(zTag, 1); Stmt q; - Manifest manifest; - Blob content; + Manifest *pTicket; int createFlag = 1; db_multi_exec( "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid ); db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); - content_get(rid, &content); - manifest_parse(&manifest, &content); - ticket_insert(&manifest, createFlag, rid); - manifest_ticket_event(rid, &manifest, createFlag, tagid); - manifest_clear(&manifest); + pTicket = manifest_get(rid, CFTYPE_TICKET); + if( pTicket ){ + ticket_insert(pTicket, createFlag, rid); + manifest_ticket_event(rid, pTicket, createFlag, tagid); + manifest_destroy(pTicket); + } createFlag = 0; } db_finalize(&q); } @@ -753,12 +753,11 @@ " AND blob.rid=attachid" " ORDER BY 1 DESC", tagid, tagid ); while( db_step(&q)==SQLITE_ROW ){ - Blob content; - Manifest m; + Manifest *pTicket; char zShort[12]; const char *zDate = db_column_text(&q, 0); int rid = db_column_int(&q, 1); const char *zChngUuid = db_column_text(&q, 2); const char *zFile = db_column_text(&q, 4); @@ -777,22 +776,22 @@ @ [%s(zShort)] @ (rid %d(rid)) by hyperlink_to_user(zUser,zDate," on"); hyperlink_to_date(zDate, ".

      "); }else{ - content_get(rid, &content); - if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){ + pTicket = manifest_get(rid, CFTYPE_TICKET); + if( pTicket ){ @ @

      Ticket change @ [%s(zShort)] @ (rid %d(rid)) by - hyperlink_to_user(m.zUser,zDate," on"); + hyperlink_to_user(pTicket->zUser,zDate," on"); hyperlink_to_date(zDate, ":"); @

      - ticket_output_change_artifact(&m); + ticket_output_change_artifact(pTicket); } - manifest_clear(&m); + manifest_destroy(pTicket); } } db_finalize(&q); style_footer_cmdref("timeline",0); } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -279,10 +279,11 @@ db_end_transaction(1); /* With --nochange, rollback changes */ }else{ if( g.argc<=3 ){ /* All files updated. Shift the current checkout to the target. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid); + checkout_set_all_exe(vid); manifest_to_disk(tid); db_lset_int("checkout", tid); }else{ /* A subset of files have been checked out. Keep the current ** checkout unchanged. */ @@ -302,13 +303,13 @@ const char *revision, /* The checkin containing the file */ const char *file, /* Full treename of the file */ Blob *content, /* Put the content here */ int errCode /* Error code if file not found. Panic if 0. */ ){ - Blob mfile; - Manifest m; - int i, rid=0; + Manifest *pManifest; + ManifestFile *pFile; + int rid=0; if( revision ){ rid = name_to_rid(revision); }else{ rid = db_lget_int("checkout", 0); @@ -315,21 +316,22 @@ } if( !is_a_version(rid) ){ if( errCode>0 ) return errCode; fossil_fatal("no such checkin: %s", revision); } - content_get(rid, &mfile); + pManifest = manifest_get(rid, CFTYPE_MANIFEST); - if( manifest_parse(&m, &mfile) ){ - for(i=0; izName, file)==0 ){ + rid = uuid_to_rid(pFile->zUuid, 0); + manifest_destroy(pManifest); return content_get(rid, content); } } - manifest_clear(&m); + manifest_destroy(pManifest); if( errCode<=0 ){ fossil_fatal("file %s does not exist in checkin: %s", file, revision); } }else if( errCode<=0 ){ fossil_panic("could not parse manifest for checkin: %s", revision); @@ -386,14 +388,15 @@ }else{ int vid; vid = db_lget_int("checkout", 0); vfile_check_signature(vid, 0); db_multi_exec( + "DELETE FROM vmerge;" "INSERT INTO torevert " "SELECT pathname" " FROM vfile " - " WHERE chnged OR deleted OR rid=0 OR pathname!=origname" + " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;" ); } blob_zero(&record); db_prepare(&q, "SELECT name FROM torevert"); while( db_step(&q)==SQLITE_ROW ){ Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -85,57 +85,38 @@ } } } /* -** Build a catalog of all files in a baseline. -** We scan the baseline file for lines of the form: -** -** F NAME UUID -** -** Each such line makes an entry in the VFILE table. +** Build a catalog of all files in a checkin. */ -void vfile_build(int vid, Blob *p){ +void vfile_build(int vid){ int rid; - char *zName, *zUuid; Stmt ins; - Blob line, token, name, uuid; - int seenHeader = 0; + Manifest *p; + ManifestFile *pFile; + db_begin_transaction(); vfile_verify_not_phantom(vid, 0, 0); + p = manifest_get(vid, CFTYPE_MANIFEST); + if( p==0 ) return; db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); db_prepare(&ins, "INSERT INTO vfile(vid,rid,mrid,pathname) " " VALUES(:vid,:id,:id,:name)"); db_bind_int(&ins, ":vid", vid); - while( blob_line(p, &line) ){ - char *z = blob_buffer(&line); - if( z[0]=='-' ){ - if( seenHeader ) break; - while( blob_line(p, &line)>2 ){} - if( blob_line(p, &line)==0 ) break; - } - seenHeader = 1; - if( z[0]!='F' || z[1]!=' ' ) continue; - blob_token(&line, &token); /* Skip the "F" token */ - if( blob_token(&line, &name)==0 ) break; - if( blob_token(&line, &uuid)==0 ) break; - zName = blob_str(&name); - defossilize(zName); - zUuid = blob_str(&uuid); - rid = uuid_to_rid(zUuid, 0); - vfile_verify_not_phantom(rid, zName, zUuid); - if( rid>0 && file_is_simple_pathname(zName) ){ - db_bind_int(&ins, ":id", rid); - db_bind_text(&ins, ":name", zName); - db_step(&ins); - db_reset(&ins); - } - blob_reset(&name); - blob_reset(&uuid); + manifest_file_rewind(p); + while( (pFile = manifest_file_next(p,0))!=0 ){ + rid = uuid_to_rid(pFile->zUuid, 0); + vfile_verify_not_phantom(rid, pFile->zName, pFile->zUuid); + db_bind_int(&ins, ":id", rid); + db_bind_text(&ins, ":name", pFile->zName); + db_step(&ins); + db_reset(&ins); } db_finalize(&ins); + manifest_destroy(p); db_end_transaction(0); } /* ** Check the file signature of the disk image for every VFILE of vid. @@ -369,10 +350,11 @@ } fseek(in, 0L, SEEK_END); sprintf(zBuf, " %ld\n", ftell(in)); fseek(in, 0L, SEEK_SET); md5sum_step_text(zBuf, -1); + /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ for(;;){ int n; n = fread(zBuf, 1, sizeof(zBuf), in); if( n<=0 ) break; md5sum_step_text(zBuf, n); @@ -422,10 +404,11 @@ int rid = db_column_int(&q, 1); md5sum_step_text(zName, -1); content_get(rid, &file); sprintf(zBuf, " %d\n", blob_size(&file)); md5sum_step_text(zBuf, -1); + /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ md5sum_step_blob(&file); blob_reset(&file); } db_finalize(&q); md5sum_finish(pOut); @@ -438,41 +421,43 @@ ** ** If pManOut is not NULL then fill it with the checksum found in the ** "R" card near the end of the manifest. */ void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){ - int i, fid; - Blob file, mfile; - Manifest m; + int fid; + Blob file; + Manifest *pManifest; + ManifestFile *pFile; char zBuf[100]; blob_zero(pOut); if( pManOut ){ blob_zero(pManOut); } db_must_be_within_tree(); - content_get(vid, &mfile); - if( manifest_parse(&m, &mfile)==0 ){ + pManifest = manifest_get(vid, CFTYPE_MANIFEST); + if( pManifest==0 ){ fossil_panic("manifest file (%d) is malformed", vid); } - for(i=0; izUuid, 0); + md5sum_step_text(pFile->zName, -1); content_get(fid, &file); sprintf(zBuf, " %d\n", blob_size(&file)); md5sum_step_text(zBuf, -1); md5sum_step_blob(&file); blob_reset(&file); } if( pManOut ){ - if( m.zRepoCksum ){ - blob_append(pManOut, m.zRepoCksum, -1); + if( pManifest->zRepoCksum ){ + blob_append(pManOut, pManifest->zRepoCksum, -1); }else{ blob_zero(pManOut); } } - manifest_clear(&m); + manifest_destroy(pManifest); md5sum_finish(pOut); } /* ** COMMAND: test-agg-cksum Index: src/wiki.c ================================================================== --- src/wiki.c +++ src/wiki.c @@ -127,11 +127,11 @@ void wiki_page(void){ char *zTag; int rid = 0; int isSandbox; Blob wiki; - Manifest m; + Manifest *pWiki = 0; const char *zPageName; char *zBody = mprintf("%s","Empty Page"); Stmt q; int cnt = 0; @@ -179,20 +179,14 @@ "SELECT rid FROM tagxref" " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" " ORDER BY mtime DESC", zTag ); free(zTag); - memset(&m, 0, sizeof(m)); - blob_zero(&m.content); - if( rid ){ - Blob content; - content_get(rid, &content); - manifest_parse(&m, &content); - if( m.type==CFTYPE_WIKI && m.zWiki ){ - while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; - if( m.zWiki[0] ) zBody = m.zWiki; - } + pWiki = manifest_get(rid, CFTYPE_WIKI); + if( pWiki ){ + while( fossil_isspace(pWiki->zWiki[0]) ) pWiki->zWiki++; + if( pWiki->zWiki[0] ) zBody = pWiki->zWiki; } } if( !g.isHome ){ if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){ style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", @@ -249,13 +243,11 @@ if( cnt ){ @ } db_finalize(&q); - if( !isSandbox ){ - manifest_clear(&m); - } + manifest_destroy(pWiki); /* don'tshow the help cross reference, because we are rendering ** the wiki conten! ** style_footer_cmdref("wiki","export"); */ style_footer(); @@ -268,11 +260,11 @@ void wikiedit_page(void){ char *zTag; int rid = 0; int isSandbox; Blob wiki; - Manifest m; + Manifest *pWiki = 0; const char *zPageName; char *zHtmlPageName; int n; const char *z; char *zBody = (char*)P("w"); @@ -302,19 +294,12 @@ free(zTag); if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){ login_needed(); return; } - memset(&m, 0, sizeof(m)); - blob_zero(&m.content); - if( rid && zBody==0 ){ - Blob content; - content_get(rid, &content); - manifest_parse(&m, &content); - if( m.type==CFTYPE_WIKI ){ - zBody = m.zWiki; - } + if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ + zBody = pWiki->zWiki; } } if( P("submit")!=0 && zBody!=0 ){ char *zDate; Blob cksum; @@ -381,13 +366,11 @@ @
      @ @ @ @ - if( !isSandbox ){ - manifest_clear(&m); - } + manifest_destroy(pWiki); style_footer_cmdref("wiki"," / wiki format"); } /* ** WEBPAGE: wikinew @@ -481,27 +464,25 @@ if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){ char *zDate; Blob cksum; int nrid; Blob body; - Blob content; Blob wiki; - Manifest m; + Manifest *pWiki = 0; blob_zero(&body); if( isSandbox ){ blob_appendf(&body, db_get("sandbox","")); appendRemark(&body); db_set("sandbox", blob_str(&body), 0); }else{ login_verify_csrf_secret(); - content_get(rid, &content); - manifest_parse(&m, &content); - if( m.type==CFTYPE_WIKI ){ - blob_append(&body, m.zWiki, -1); + pWiki = manifest_get(rid, CFTYPE_WIKI); + if( pWiki ){ + blob_append(&body, pWiki->zWiki, -1); + manifest_destroy(pWiki); } - manifest_clear(&m); blob_zero(&wiki); db_begin_transaction(); zDate = db_text(0, "SELECT datetime('now')"); zDate[10] = 'T'; blob_appendf(&wiki, "D %s\n", zDate); @@ -616,12 +597,11 @@ */ void wdiff_page(void){ char *zTitle; int rid1, rid2; const char *zPageName; - Blob content1, content2; - Manifest m1, m2; + Manifest *pW1, *pW2 = 0; Blob w1, w2, d; login_check_credentials(); rid1 = atoi(PD("a","0")); if( !g.okHistory ){ login_needed(); return; } @@ -639,27 +619,24 @@ " WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)" " ORDER BY event.mtime DESC LIMIT 1", zPageName, rid1 ); } - content_get(rid1, &content1); - manifest_parse(&m1, &content1); - if( m1.type!=CFTYPE_WIKI ) fossil_redirect_home(); - blob_init(&w1, m1.zWiki, -1); + pW1 = manifest_get(rid1, CFTYPE_WIKI); + if( pW1==0 ) fossil_redirect_home(); + blob_init(&w1, pW1->zWiki, -1); blob_zero(&w2); - if( rid2 ){ - content_get(rid2, &content2); - manifest_parse(&m2, &content2); - if( m2.type==CFTYPE_WIKI ){ - blob_init(&w2, m2.zWiki, -1); - } + if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){ + blob_init(&w2, pW2->zWiki, -1); } blob_zero(&d); text_diff(&w2, &w1, &d, 5, 1); @
         @ %h(blob_str(&d))
         @ 
      + manifest_destroy(pW1); + manifest_destroy(pW2); style_footer(); } /* ** WEBPAGE: wcontent @@ -921,30 +898,26 @@ } if( strncmp(g.argv[2],"export",n)==0 ){ char const *zPageName; /* Name of the wiki page to export */ char const *zFile; /* Name of the output file (0=stdout) */ - int rid; /* Artifact ID of the wiki page */ - int i; /* Loop counter */ - char *zBody = 0; /* Wiki page content */ - Manifest m; /* Parsed wiki page content */ + int rid; /* Artifact ID of the wiki page */ + int i; /* Loop counter */ + char *zBody = 0; /* Wiki page content */ + Manifest *pWiki = 0; /* Parsed wiki page content */ + if( (g.argc!=4) && (g.argc!=5) ){ usage("export PAGENAME ?FILE?"); } zPageName = g.argv[3]; rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" " ORDER BY x.mtime DESC LIMIT 1", zPageName ); - if( rid ){ - Blob content; - content_get(rid, &content); - manifest_parse(&m, &content); - if( m.type==CFTYPE_WIKI ){ - zBody = m.zWiki; - } + if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ + zBody = pWiki->zWiki; } if( zBody==0 ){ fossil_fatal("wiki page [%s] not found",zPageName); } for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){} @@ -962,12 +935,13 @@ fossil_fatal("wiki export could not open output file for writing."); } fprintf(zF,"%.*s\n", i, zBody); if( doClose ) fclose(zF); }else{ - printf("%.*s\n", i, zBody); + printf("%.*s\n", i, zBody); } + manifest_destroy(pWiki); return; }else if( strncmp(g.argv[2],"commit",n)==0 || strncmp(g.argv[2],"create",n)==0 ){ char *zPageName; Index: src/xfer.c ================================================================== --- src/xfer.c +++ src/xfer.c @@ -40,10 +40,130 @@ int nDeltaRcvd; /* Number of deltas received */ int nDanglingFile; /* Number of dangling deltas received */ int mxSend; /* Stop sending "file" with pOut reaches this size */ }; +/* +** COMMAND: callhook +** %fossil callhook PUSHHOOKPATTERN ?--force|-f? +** +** Call the push hook command on a server, which will normally be called +** after a client push (setting push-hook-cmd). +** +** If --force is used, the given pattern is not checked against the +** configuration (setting push-hook-pattern-server). +** +** This command only works on the server side, it does not send a message +** from a client, but executes the hook directly on the server. +** +** See also push, sync, setting +*/ +void callhook_cmd(void){ + int forceFlag = find_option("force","f",0)!=0; + + db_open_config(1); + db_find_and_open_repository(0); + if( (g.argc!=3) || (!g.argv[2]) || (!g.argv[2][0]) ){ + usage("PUSHHOOKPATTERN ?--force?"); + } + if( !forceFlag ){ + const char *zPushHookPattern = db_get("push-hook-pattern-server", ""); + int lenPushHookPattern = (zPushHookPattern && zPushHookPattern[0]) + ? strlen(zPushHookPattern) : 0; + if( (!lenPushHookPattern) + || memcmp(g.argv[2], zPushHookPattern, lenPushHookPattern) + ){ + fossil_fatal("push hook pattern '%s' doesn't match configuration '%s'\n", + g.argv[2],zPushHookPattern); + } + } + post_push_hook(g.argv[2],'C'); +} + +/* +** Let a server-side external agent know that a push has completed. /fatman +** The second argument controls, how the command is called: +** P - client request with pushed files +** F - client request without pushed files(FORCE!) +** C - server side command line activation +*/ +void post_push_hook(char const * const zPushHookLine, const char requestType){ + /* + ** TO DO: get the string cmd from a config file? Or the database local + ** settings, as someone suggested? Ditto output and error logs. /fatman + */ + const char *zCmd = db_get("push-hook-cmd", ""); + int allowForced = db_get_boolean("push-hook-force", 0); + const char *zHookPriv = db_get("push-hook-privilege",""); + int privOk = 0; + + if( zHookPriv && *zHookPriv ){ + switch( *zHookPriv ){ + + case 's': + if( g.okSetup ) privOk = 1; + break; + case 'a': + if( g.okAdmin ) privOk = 1; + break; + case 'i': + if( g.okWrite ) privOk = 1; + break; + case 'o': + if( g.okRead ) privOk = 1; + break; + default: + fossil_print("Push hook wrong privilege type '%s'\n", zHookPriv); + } + }else{ + privOk = 1; + } + if( !privOk ){ + fossil_print("No privilege to activate hook!\n"); + }else if( requestType!='P' && requestType!='C' && requestType!='F' ){ + fossil_print("Push hook wrong request type '%c'\n", requestType); + }else if( requestType=='F' && !allowForced ){ + fossil_print("Forced push call from client not allowed," + " skipping call for '%s'\n", zPushHookLine); + }else if( zCmd && zCmd[0] ){ + int rc; + char * zCalledCmd; + char * zDate; + const char *zRnd; + + + zDate = db_text(0, "SELECT strftime('%%Y%%m%%d%%H%%M%%f','now')"); + zRnd = db_text(0, "SELECT lower(hex(randomblob(6)))"); + + zCalledCmd = mprintf("%s %s-%s %s >hook-log-%s-%s 2>&1",zCmd,zDate,zRnd,zPushHookLine,zDate,zRnd); + { /* remove newlines from command */ + char *zSrc, *zDest; + + for (zSrc=zDest=zCalledCmd;;zSrc++){ + switch( *zSrc ){ + case '\0': + *zDest=0; + break; + default: + *zDest++ = *zSrc; + /* fall through is intended! */ + case '\n': + continue; + } + break; + } + } + rc = system(zCalledCmd); + if (rc != 0) { + fossil_print("The post-push-hook command '%s' failed.", zCalledCmd); + } + free(zCalledCmd); + free(zDate); + }else{ + fossil_print("No push hook configured, skipping call for '%s'\n", zPushHookLine); + } +} /* ** The input blob contains a UUID. Convert it into a record ID. ** Create a phantom record if no prior record exists and ** phantomize is true. @@ -72,11 +192,17 @@ /* ** Remember that the other side of the connection already has a copy ** of the file rid. */ static void remote_has(int rid){ - if( rid ) db_multi_exec("INSERT OR IGNORE INTO onremote VALUES(%d)", rid); + if( rid ){ + static Stmt q; + db_static_prepare(&q, "INSERT OR IGNORE INTO onremote VALUES(:r)"); + db_bind_int(&q, ":r", rid); + db_step(&q); + db_reset(&q); + } } /* ** The aToken[0..nToken-1] blob array is a parse of a "file" line ** message. This routine finishes parsing that message and does @@ -95,11 +221,11 @@ ** be initialized to an empty string. ** ** Any artifact successfully received by this routine is considered to ** be public and is therefore removed from the "private" table. */ -static void xfer_accept_file(Xfer *pXfer){ +static void xfer_accept_file(Xfer *pXfer, int cloneFlag){ int n; int rid; int srcid = 0; Blob content, hash; @@ -114,13 +240,26 @@ return; } blob_zero(&content); blob_zero(&hash); blob_extract(pXfer->pIn, n, &content); - if( uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){ + if( !cloneFlag && uuid_is_shunned(blob_str(&pXfer->aToken[1])) ){ /* Ignore files that have been shunned */ return; + } + if( cloneFlag ){ + if( pXfer->nToken==4 ){ + srcid = rid_from_uuid(&pXfer->aToken[2], 1); + pXfer->nDeltaRcvd++; + }else{ + srcid = 0; + pXfer->nFileRcvd++; + } + rid = content_put(&content, blob_str(&pXfer->aToken[1]), srcid); + remote_has(rid); + blob_reset(&content); + return; } if( pXfer->nToken==4 ){ Blob src, next; srcid = rid_from_uuid(&pXfer->aToken[2], 1); if( content_get(srcid, &src)==0 ){ @@ -467,14 +606,15 @@ ** Check to see if the number of unclustered entries is greater than ** 100 and if it is, form a new cluster. Unclustered phantoms do not ** count toward the 100 total. And phantoms are never added to a new ** cluster. */ -static void create_cluster(void){ +void create_cluster(void){ Blob cluster, cksum; Stmt q; int nUncl; + int nRow = 0; /* We should not ever get any private artifacts in the unclustered table. ** But if we do (because of a bug) now is a good time to delete them. */ db_multi_exec( "DELETE FROM unclustered WHERE rid IN (SELECT rid FROM private)" @@ -481,30 +621,41 @@ ); nUncl = db_int(0, "SELECT count(*) FROM unclustered /*scan*/" " WHERE NOT EXISTS(SELECT 1 FROM phantom" " WHERE rid=unclustered.rid)"); - if( nUncl<100 ){ - return; - } - blob_zero(&cluster); - db_prepare(&q, "SELECT uuid FROM unclustered, blob" - " WHERE NOT EXISTS(SELECT 1 FROM phantom" - " WHERE rid=unclustered.rid)" - " AND unclustered.rid=blob.rid" - " AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" - " ORDER BY 1"); - while( db_step(&q)==SQLITE_ROW ){ - blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0)); - } - db_finalize(&q); - md5sum_blob(&cluster, &cksum); - blob_appendf(&cluster, "Z %b\n", &cksum); - blob_reset(&cksum); - db_multi_exec("DELETE FROM unclustered"); - content_put(&cluster, 0, 0); - blob_reset(&cluster); + if( nUncl>=100 ){ + blob_zero(&cluster); + db_prepare(&q, "SELECT uuid FROM unclustered, blob" + " WHERE NOT EXISTS(SELECT 1 FROM phantom" + " WHERE rid=unclustered.rid)" + " AND unclustered.rid=blob.rid" + " AND NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)" + " ORDER BY 1"); + while( db_step(&q)==SQLITE_ROW ){ + blob_appendf(&cluster, "M %s\n", db_column_text(&q, 0)); + nRow++; + if( nRow>=800 && nUncl>nRow+100 ){ + md5sum_blob(&cluster, &cksum); + blob_appendf(&cluster, "Z %b\n", &cksum); + blob_reset(&cksum); + content_put(&cluster, 0, 0); + blob_reset(&cluster); + nUncl -= nRow; + nRow = 0; + } + } + db_finalize(&q); + db_multi_exec("DELETE FROM unclustered"); + if( nRow>0 ){ + md5sum_blob(&cluster, &cksum); + blob_appendf(&cluster, "Z %b\n", &cksum); + blob_reset(&cksum); + content_put(&cluster, 0, 0); + blob_reset(&cluster); + } + } } /* ** Send an igot message for every entry in unclustered table. ** Return the number of cards sent. @@ -596,10 +747,13 @@ int isClone = 0; int nGimme = 0; int size; int recvConfig = 0; char *zNow; + const char *zPushHookPattern = db_get("push-hook-pattern-server", ""); + int lenPushHookPattern = (zPushHookPattern && zPushHookPattern[0]) + ? strlen(zPushHookPattern) : 0; if( strcmp(PD("REQUEST_METHOD","POST"),"POST") ){ fossil_redirect_home(); } memset(&xfer, 0, sizeof(xfer)); @@ -606,22 +760,30 @@ blobarray_zero(xfer.aToken, count(xfer.aToken)); cgi_set_content_type(g.zContentType); blob_zero(&xfer.err); xfer.pIn = &g.cgiIn; xfer.pOut = cgi_output_blob(); - xfer.mxSend = db_get_int("max-download", 5000000); + xfer.mxSend = db_get_int("max-download", 20000000); g.xferPanic = 1; db_begin_transaction(); db_multi_exec( "CREATE TEMP TABLE onremote(rid INTEGER PRIMARY KEY);" ); - zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); - @ # timestamp %s(zNow) manifest_crosslink_begin(); while( blob_line(xfer.pIn, &xfer.line) ){ - if( blob_buffer(&xfer.line)[0]=='#' ) continue; + if( blob_buffer(&xfer.line)[0]=='#' ){ + if( lenPushHookPattern + && blob_buffer(&xfer.line)[1] + && blob_buffer(&xfer.line)[2] + && (0 == memcmp(blob_buffer(&xfer.line)+2, + zPushHookPattern, lenPushHookPattern)) + ){ + post_push_hook(blob_buffer(&xfer.line)+2,blob_buffer(&xfer.line)[1]); + } + continue; + } xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken)); /* file UUID SIZE \n CONTENT ** file UUID DELTASRC SIZE \n CONTENT ** @@ -632,11 +794,11 @@ cgi_reset_content(); @ error not\sauthorized\sto\swrite nErr++; break; } - xfer_accept_file(&xfer); + xfer_accept_file(&xfer, 0); if( blob_size(&xfer.err) ){ cgi_reset_content(); @ error %T(blob_str(&xfer.err)) nErr++; break; @@ -718,26 +880,42 @@ isPush = 1; } } }else - /* clone + /* clone ?PROTOCOL-VERSION? ?SEQUENCE-NUMBER? ** ** The client knows nothing. Tell all. */ if( blob_eq(&xfer.aToken[0], "clone") ){ + int iVers; login_check_credentials(); if( !g.okClone ){ cgi_reset_content(); @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) @ error not\sauthorized\sto\sclone nErr++; break; } - isClone = 1; - isPull = 1; - deltaFlag = 1; + if( xfer.nToken==3 + && blob_is_int(&xfer.aToken[1], &iVers) + && iVers>=2 + ){ + int seqno, max; + blob_is_int(&xfer.aToken[2], &seqno); + max = db_int(0, "SELECT max(rid) FROM blob"); + while( xfer.mxSend>blob_size(xfer.pOut) && seqno<=max ){ + send_file(&xfer, seqno, 0, 1); + seqno++; + } + if( seqno>=max ) seqno = 0; + @ clone_seqno %d(seqno) + }else{ + isClone = 1; + isPull = 1; + deltaFlag = 1; + } @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x")) }else /* login USER NONCE SIGNATURE ** @@ -874,10 +1052,18 @@ } if( recvConfig ){ configure_finalize_receive(); } manifest_crosslink_end(); + + /* Send the server timestamp last, in case prior processing happened + ** to use up a significant fraction of our time window. + */ + zNow = db_text(0, "SELECT strftime('%%Y-%%m-%%dT%%H:%%M:%%S', 'now')"); + @ # timestamp %s(zNow) + free(zNow); + db_end_transaction(0); } /* ** COMMAND: test-xfer @@ -943,15 +1129,22 @@ int origConfigRcvMask; /* Original value of configRcvMask */ int nFileRecv; /* Number of files received */ int mxPhantomReq = 200; /* Max number of phantoms to request per comm */ const char *zCookie; /* Server cookie */ int nSent, nRcvd; /* Bytes sent and received (after compression) */ + int cloneSeqno = 1; /* Sequence number for clones */ Blob send; /* Text we are sending to the server */ Blob recv; /* Reply we got back from the server */ Xfer xfer; /* Transfer data */ + int pctDone; /* Percentage done with a message */ + int lastPctDone = -1; /* Last displayed pctDone */ + double rArrivalTime; /* Time at which a message arrived */ const char *zSCode = db_get("server-code", "x"); const char *zPCode = db_get("project-code", 0); + const char *zPushHookPattern = db_get("push-hook-pattern-client", ""); + int allowForced = db_get_boolean("push-hook-force", 0); + if( db_get_boolean("dont-push", 0) ) pushFlag = 0; if( pushFlag + pullFlag + cloneFlag == 0 && configRcvMask==0 && configSendMask==0 ) return; @@ -977,15 +1170,16 @@ /* ** Always begin with a clone, pull, or push message */ if( cloneFlag ){ - blob_appendf(&send, "clone\n"); + blob_appendf(&send, "clone 2 %d\n", cloneSeqno); pushFlag = 0; pullFlag = 0; nCardSent++; /* TBD: Request all transferable configuration values */ + content_enable_dephantomize(0); }else if( pullFlag ){ blob_appendf(&send, "pull %s %s\n", zSCode, zPCode); nCardSent++; } if( pushFlag ){ @@ -1009,11 +1203,11 @@ } /* Generate gimme cards for phantoms and leaf cards ** for all leaves. */ - if( pullFlag || cloneFlag ){ + if( pullFlag || (cloneFlag && cloneSeqno==1) ){ request_phantoms(&xfer, mxPhantomReq); } if( pushFlag ){ send_unsent(&xfer); nCardSent += send_unclustered(&xfer); @@ -1058,22 +1252,27 @@ blob_appendf(&send, "# %s\n", zRandomness); free(zRandomness); /* Exchange messages with the server */ nFileSend = xfer.nFileSent + xfer.nDeltaSent; - fossil_print(zValueFormat, "Send:", + fossil_print(zValueFormat, "Sent:", blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent, xfer.nFileSent, xfer.nDeltaSent); nCardSent = 0; nCardRcvd = 0; xfer.nFileSent = 0; xfer.nDeltaSent = 0; xfer.nGimmeSent = 0; xfer.nIGotSent = 0; + if( !g.cgiOutput && !g.fQuiet ){ + printf("waiting for server..."); + } fflush(stdout); http_exchange(&send, &recv, cloneFlag==0 || nCycle>0); + lastPctDone = -1; blob_reset(&send); + rArrivalTime = db_double(0.0, "SELECT julianday('now')"); /* Begin constructing the next message (which might never be ** sent) by beginning with the pull or push cards */ if( pullFlag ){ @@ -1086,18 +1285,22 @@ } go = 0; /* Process the reply that came back from the server */ while( blob_line(&recv, &xfer.line) ){ + if( g.fHttpTrace ){ + printf("\rGOT: %.*s", (int)blob_size(&xfer.line), + blob_buffer(&xfer.line)); + } if( blob_buffer(&xfer.line)[0]=='#' ){ const char *zLine = blob_buffer(&xfer.line); if( memcmp(zLine, "# timestamp ", 12)==0 ){ char zTime[20]; double rDiff; sqlite3_snprintf(sizeof(zTime), zTime, "%.19s", &zLine[12]); - rDiff = db_double(9e99, "SELECT julianday('%q') - julianday('now')", - zTime); + rDiff = db_double(9e99, "SELECT julianday('%q') - %.17g", + zTime, rArrivalTime); if( rDiff<0.0 ) rDiff = -rDiff; if( rDiff>9e98 ) rDiff = 0.0; if( (rDiff*24.0*3600.0)>=60.0 ){ fossil_warning("*** time skew *** server time differs by %s", db_timespan_name(rDiff)); @@ -1107,21 +1310,25 @@ continue; } xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken)); nCardRcvd++; if( !g.cgiOutput && !g.fQuiet ){ - printf("\r%d", nCardRcvd); - fflush(stdout); + pctDone = (recv.iCursor*100)/recv.nUsed; + if( pctDone!=lastPctDone ){ + printf("\rprocessed: %d%% ", pctDone); + lastPctDone = pctDone; + fflush(stdout); + } } /* file UUID SIZE \n CONTENT ** file UUID DELTASRC SIZE \n CONTENT ** ** Receive a file transmitted from the server. */ if( blob_eq(&xfer.aToken[0],"file") ){ - xfer_accept_file(&xfer); + xfer_accept_file(&xfer, cloneFlag); }else /* gimme UUID ** ** Server is requesting a file. If the file is a manifest, assume @@ -1177,11 +1384,11 @@ } if( zPCode==0 ){ zPCode = mprintf("%b", &xfer.aToken[2]); db_set("project-code", zPCode, 0); } - blob_appendf(&send, "clone\n"); + blob_appendf(&send, "clone 2 %d\n", cloneSeqno); nCardSent++; }else /* config NAME SIZE \n CONTENT ** @@ -1236,10 +1443,21 @@ ** same server. */ if( blob_eq(&xfer.aToken[0], "cookie") && xfer.nToken==2 ){ db_set("cookie", blob_str(&xfer.aToken[1]), 0); }else + + /* clone_seqno N + ** + ** When doing a clone, the server tries to send all of its artifacts + ** in sequence. This card indicates the sequence number of the next + ** blob that needs to be sent. If N<=0 that indicates that all blobs + ** have been sent. + */ + if( blob_eq(&xfer.aToken[0], "clone_seqno") && xfer.nToken==2 ){ + blob_is_int(&xfer.aToken[1], &cloneSeqno); + }else /* message MESSAGE ** ** Print a message. Similar to "error" but does not stop processing. ** @@ -1314,10 +1532,12 @@ nFileRecv = xfer.nFileRcvd + xfer.nDeltaRcvd + xfer.nDanglingFile; if( (nFileRecv>0 || newPhantom) && db_exists("SELECT 1 FROM phantom") ){ go = 1; mxPhantomReq = nFileRecv*2; if( mxPhantomReq<200 ) mxPhantomReq = 200; + }else if( cloneFlag && nFileRecv>0 ){ + go = 1; } nCardRcvd = 0; xfer.nFileRcvd = 0; xfer.nDeltaRcvd = 0; xfer.nDanglingFile = 0; @@ -1329,15 +1549,30 @@ go = 1; } /* If this is a clone, the go at least two rounds */ if( cloneFlag && nCycle==1 ) go = 1; + + /* Stop the cycle if the server sends a "clone_seqno 0" card */ + if( cloneSeqno<=0 ) go = 0; }; + if( pushFlag && ( (nFileSend > 0) || allowForced ) ){ + if( zPushHookPattern && zPushHookPattern[0] ){ + blob_appendf(&send, "#%s%s\n", + ((nFileSend > 0)?"P":"F"), zPushHookPattern); + fossil_print("Triggering push hook %s '%s'\n",((nFileSend > 0)?"P":"F"),zPushHookPattern); + http_exchange(&send, &recv, cloneFlag==0 || nCycle>0); + blob_reset(&send); + nCardSent++; + } + int allowForced = db_get_boolean("push-hook-force", 0); + } transport_stats(&nSent, &nRcvd, 1); fossil_print("Total network traffic: %d bytes sent, %d bytes received\n", nSent, nRcvd); transport_close(); transport_global_shutdown(); db_multi_exec("DROP TABLE onremote"); manifest_crosslink_end(); + content_enable_dephantomize(1); db_end_transaction(0); } Index: src/zip.c ================================================================== --- src/zip.c +++ src/zip.c @@ -313,64 +313,65 @@ ** politely expands into a subdir instead of filling your current dir ** with source files. For example, pass a UUID or "ProjectName". ** */ void zip_of_baseline(int rid, Blob *pZip, const char *zDir){ - int i; - Blob mfile, file, hash; - Manifest m; + Blob mfile, hash, file; + Manifest *pManifest; + ManifestFile *pFile; Blob filename; int nPrefix; content_get(rid, &mfile); if( blob_size(&mfile)==0 ){ blob_zero(pZip); return; } - blob_zero(&file); blob_zero(&hash); - blob_copy(&file, &mfile); blob_zero(&filename); zip_open(); if( zDir && zDir[0] ){ blob_appendf(&filename, "%s/", zDir); } nPrefix = blob_size(&filename); - if( manifest_parse(&m, &mfile) ){ + pManifest = manifest_get(rid, CFTYPE_MANIFEST); + if( pManifest ){ char *zName; - zip_set_timedate(m.rDate); - blob_append(&filename, "manifest", -1); - zName = blob_str(&filename); - zip_add_folders(zName); - zip_add_file(zName, &file); - sha1sum_blob(&file, &hash); - blob_reset(&file); - blob_append(&hash, "\n", 1); - blob_resize(&filename, nPrefix); - blob_append(&filename, "manifest.uuid", -1); - zName = blob_str(&filename); - zip_add_file(zName, &hash); - blob_reset(&hash); - for(i=0; irDate); + if( db_get_boolean("manifest", 0) ){ + blob_append(&filename, "manifest", -1); + zName = blob_str(&filename); + zip_add_folders(zName); + zip_add_file(zName, &mfile); + sha1sum_blob(&mfile, &hash); + blob_reset(&mfile); + blob_append(&hash, "\n", 1); + blob_resize(&filename, nPrefix); + blob_append(&filename, "manifest.uuid", -1); + zName = blob_str(&filename); + zip_add_file(zName, &hash); + blob_reset(&hash); + } + manifest_file_rewind(pManifest); + while( (pFile = manifest_file_next(pManifest,0))!=0 ){ + int fid = uuid_to_rid(pFile->zUuid, 0); if( fid ){ content_get(fid, &file); blob_resize(&filename, nPrefix); - blob_append(&filename, m.aFile[i].zName, -1); + blob_append(&filename, pFile->zName, -1); zName = blob_str(&filename); zip_add_folders(zName); zip_add_file(zName, &file); blob_reset(&file); } } - manifest_clear(&m); }else{ blob_reset(&mfile); - blob_reset(&file); } + manifest_destroy(pManifest); blob_reset(&filename); zip_close(pZip); } /* Index: win/Makefile.dmc ================================================================== --- win/Makefile.dmc +++ win/Makefile.dmc @@ -39,12 +39,12 @@ $(APPNAME) : translate$E mkindex$E headers fossil.res $(OBJ) $(OBJDIR)\link cd $(OBJDIR) $(DMDIR)\bin\link @link -fossil.res: $B\win\fossil.rc - $(RC) $(RCFLAGS) -o$@ $** +fossil.res: $B\win\fossil.rc VERSION.h + $(RC) $(RCFLAGS) -o$@ $B\win\fossil.rc $(OBJDIR)\link: $B\win\Makefile.dmc +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 > $@ +echo fossil >> $@ +echo fossil >> $@ Index: www/fileformat.wiki ================================================================== --- www/fileformat.wiki +++ www/fileformat.wiki @@ -95,19 +95,28 @@ may contain no additional text or data beyond what is described here. Allowed cards in the manifest are as follows:
      +B baseline-manifest
      C checkin-comment
      D time-and-date-stamp
      F filename SHA1-hash permissions old-name
      P SHA1-hash+
      R repository-checksum
      T (+|-|*)tag-name * ?value?
      U user-login
      Z manifest-checksum
      + +A manifest may optionally have a single B-card. The B-card specifies +another manifest that serves as the "baseline" for this manifest. A +manifest that has a B-card is called a delta-manifest and a manifest +that omits the B-card is a baseline-manifest. The other manifest +identified by the argument of the B-card must be a baseline-manifest. +A baseline-manifest records the complete contents of a checkin. +A delta-manifest records only changes from its baseline. A manifest must have exactly one C-card. The sole argument to the C-card is a check-in comment that describes the check-in that the manifest defines. The check-in comment is text. The following escape sequences are applied to the text: @@ -125,26 +134,29 @@
      YYYY-MM-DDTHH:MM:SS
      -A manifest has zero or more F-cards. Each F-card defines a file -(other than the manifest itself) which is part of the check-in that -the manifest defines. There are two, three, or four arguments. +A manifest has zero or more F-cards. Each F-card identifies a file +that is part of the check-in. There are one, two, three, or four arguments. The first argument is the pathname of the file in the check-in relative to the root of the project file hierarchy. No ".." or "." directories are allowed within the filename. Space characters are escaped as in C-card comment text. Backslash characters and newlines are not allowed within filenames. The directory separator character is a forward slash (ASCII 0x2F). The second argument to the F-card is the full 40-character lower-case hexadecimal SHA1 hash of the content -artifact. The optional 3rd argument defines any special access +artifact. The second argument is required for baseline manifests +but is optional for delta manifests. When the second argument to the +F-card is omitted, it means that the file has been deleted relative +to the baseline. The optional 3rd argument defines any special access permissions associated with the file. The only special code currently defined is "x" which means that the file is executable. All files are always readable and writable. This can be expressed by "w" permission -if desired but is optional. +if desired but is optional. The file format might be extended with +new permission letters in the future. The optional 4th argument is the name of the same file as it existed in the parent check-in. If the name of the file is unchanged from its parent, then the 4th argument is omitted. A manifest has zero or one P-cards. Most manifests have one P-card. @@ -504,10 +516,20 @@     X   + +B baseline +X +  +  +  +  +  +  + C comment-text X