Fossil SCM
Merge the delta-manifest enhancement into the trunk.
Commit
d13054ce84eaa950dd661a5dac11941fe4bb9376
Parent
7954ccba68bd84f…
23 files changed
+20
-20
+61
-40
+285
-141
+60
-47
+1
+32
-10
+21
-10
+37
-34
+9
-10
+3
-2
+18
-26
+1
-1
+62
-66
+611
-379
+27
-1
+22
-8
+198
-425
+13
-14
+14
-11
+31
-46
+32
-58
+25
-24
+27
-5
~
src/branch.c
~
src/browse.c
~
src/checkin.c
~
src/checkout.c
~
src/configure.c
~
src/db.c
~
src/delta.c
~
src/diffcmd.c
~
src/doc.c
~
src/encode.c
~
src/event.c
~
src/finfo.c
~
src/info.c
~
src/manifest.c
~
src/md5.c
~
src/rebuild.c
~
src/sha1.c
~
src/tkt.c
~
src/update.c
~
src/vfile.c
~
src/wiki.c
~
src/zip.c
~
www/fileformat.wiki
+20
-20
| --- src/branch.c | ||
| +++ src/branch.c | ||
| @@ -35,12 +35,11 @@ | ||
| 35 | 35 | const char *zBranch; /* Name of the new branch */ |
| 36 | 36 | char *zDate; /* Date that branch was created */ |
| 37 | 37 | char *zComment; /* Check-in comment for the new branch */ |
| 38 | 38 | const char *zColor; /* Color of the new branch */ |
| 39 | 39 | Blob branch; /* manifest for the new branch */ |
| 40 | - Blob parent; /* root check-in manifest */ | |
| 41 | - Manifest mParent; /* Parsed parent manifest */ | |
| 40 | + Manifest *pParent; /* Parsed parent manifest */ | |
| 42 | 41 | Blob mcksum; /* Self-checksum on the manifest */ |
| 43 | 42 | const char *zDateOvrd; /* Override date string */ |
| 44 | 43 | const char *zUserOvrd; /* Override user name */ |
| 45 | 44 | |
| 46 | 45 | noSign = find_option("nosign","",0)!=0; |
| @@ -71,41 +70,42 @@ | ||
| 71 | 70 | db_begin_transaction(); |
| 72 | 71 | rootid = name_to_rid(g.argv[4]); |
| 73 | 72 | if( rootid==0 ){ |
| 74 | 73 | fossil_fatal("unable to locate check-in off of which to branch"); |
| 75 | 74 | } |
| 75 | + | |
| 76 | + pParent = manifest_get(rootid, CFTYPE_MANIFEST); | |
| 77 | + if( pParent==0 ){ | |
| 78 | + fossil_fatal("%s is not a valid check-in", g.argv[4]); | |
| 79 | + } | |
| 76 | 80 | |
| 77 | 81 | /* Create a manifest for the new branch */ |
| 78 | 82 | blob_zero(&branch); |
| 83 | + if( pParent->zBaseline ){ | |
| 84 | + blob_appendf(&branch, "B %s\n", pParent->zBaseline); | |
| 85 | + } | |
| 79 | 86 | zComment = mprintf("Create new branch named \"%h\"", zBranch); |
| 80 | 87 | blob_appendf(&branch, "C %F\n", zComment); |
| 81 | 88 | zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); |
| 82 | 89 | zDate[10] = 'T'; |
| 83 | 90 | blob_appendf(&branch, "D %s\n", zDate); |
| 84 | 91 | |
| 85 | 92 | /* Copy all of the content from the parent into the branch */ |
| 86 | - content_get(rootid, &parent); | |
| 87 | - manifest_parse(&mParent, &parent); | |
| 88 | - if( mParent.type!=CFTYPE_MANIFEST ){ | |
| 89 | - fossil_fatal("%s is not a valid check-in", g.argv[4]); | |
| 90 | - } | |
| 91 | - for(i=0; i<mParent.nFile; ++i){ | |
| 92 | - if( mParent.aFile[i].zPerm[0] ){ | |
| 93 | - blob_appendf(&branch, "F %F %s %s\n", | |
| 94 | - mParent.aFile[i].zName, | |
| 95 | - mParent.aFile[i].zUuid, | |
| 96 | - mParent.aFile[i].zPerm); | |
| 97 | - }else{ | |
| 98 | - blob_appendf(&branch, "F %F %s\n", | |
| 99 | - mParent.aFile[i].zName, | |
| 100 | - mParent.aFile[i].zUuid); | |
| 101 | - } | |
| 93 | + for(i=0; i<pParent->nFile; ++i){ | |
| 94 | + blob_appendf(&branch, "F %F", pParent->aFile[i].zName); | |
| 95 | + if( pParent->aFile[i].zUuid ){ | |
| 96 | + blob_appendf(&branch, " %s", pParent->aFile[i].zUuid); | |
| 97 | + if( pParent->aFile[i].zPerm[0] ){ | |
| 98 | + blob_appendf(&branch, " %s", pParent->aFile[i].zPerm); | |
| 99 | + } | |
| 100 | + } | |
| 101 | + blob_append(&branch, "\n", 1); | |
| 102 | 102 | } |
| 103 | 103 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); |
| 104 | 104 | blob_appendf(&branch, "P %s\n", zUuid); |
| 105 | - blob_appendf(&branch, "R %s\n", mParent.zRepoCksum); | |
| 106 | - manifest_clear(&mParent); | |
| 105 | + blob_appendf(&branch, "R %s\n", pParent->zRepoCksum); | |
| 106 | + manifest_destroy(pParent); | |
| 107 | 107 | |
| 108 | 108 | /* Add the symbolic branch name and the "branch" tag to identify |
| 109 | 109 | ** this as a new branch */ |
| 110 | 110 | if( zColor!=0 ){ |
| 111 | 111 | blob_appendf(&branch, "T *bgcolor * %F\n", zColor); |
| 112 | 112 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -35,12 +35,11 @@ | |
| 35 | const char *zBranch; /* Name of the new branch */ |
| 36 | char *zDate; /* Date that branch was created */ |
| 37 | char *zComment; /* Check-in comment for the new branch */ |
| 38 | const char *zColor; /* Color of the new branch */ |
| 39 | Blob branch; /* manifest for the new branch */ |
| 40 | Blob parent; /* root check-in manifest */ |
| 41 | Manifest mParent; /* Parsed parent manifest */ |
| 42 | Blob mcksum; /* Self-checksum on the manifest */ |
| 43 | const char *zDateOvrd; /* Override date string */ |
| 44 | const char *zUserOvrd; /* Override user name */ |
| 45 | |
| 46 | noSign = find_option("nosign","",0)!=0; |
| @@ -71,41 +70,42 @@ | |
| 71 | db_begin_transaction(); |
| 72 | rootid = name_to_rid(g.argv[4]); |
| 73 | if( rootid==0 ){ |
| 74 | fossil_fatal("unable to locate check-in off of which to branch"); |
| 75 | } |
| 76 | |
| 77 | /* Create a manifest for the new branch */ |
| 78 | blob_zero(&branch); |
| 79 | zComment = mprintf("Create new branch named \"%h\"", zBranch); |
| 80 | blob_appendf(&branch, "C %F\n", zComment); |
| 81 | zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); |
| 82 | zDate[10] = 'T'; |
| 83 | blob_appendf(&branch, "D %s\n", zDate); |
| 84 | |
| 85 | /* Copy all of the content from the parent into the branch */ |
| 86 | content_get(rootid, &parent); |
| 87 | manifest_parse(&mParent, &parent); |
| 88 | if( mParent.type!=CFTYPE_MANIFEST ){ |
| 89 | fossil_fatal("%s is not a valid check-in", g.argv[4]); |
| 90 | } |
| 91 | for(i=0; i<mParent.nFile; ++i){ |
| 92 | if( mParent.aFile[i].zPerm[0] ){ |
| 93 | blob_appendf(&branch, "F %F %s %s\n", |
| 94 | mParent.aFile[i].zName, |
| 95 | mParent.aFile[i].zUuid, |
| 96 | mParent.aFile[i].zPerm); |
| 97 | }else{ |
| 98 | blob_appendf(&branch, "F %F %s\n", |
| 99 | mParent.aFile[i].zName, |
| 100 | mParent.aFile[i].zUuid); |
| 101 | } |
| 102 | } |
| 103 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); |
| 104 | blob_appendf(&branch, "P %s\n", zUuid); |
| 105 | blob_appendf(&branch, "R %s\n", mParent.zRepoCksum); |
| 106 | manifest_clear(&mParent); |
| 107 | |
| 108 | /* Add the symbolic branch name and the "branch" tag to identify |
| 109 | ** this as a new branch */ |
| 110 | if( zColor!=0 ){ |
| 111 | blob_appendf(&branch, "T *bgcolor * %F\n", zColor); |
| 112 |
| --- src/branch.c | |
| +++ src/branch.c | |
| @@ -35,12 +35,11 @@ | |
| 35 | const char *zBranch; /* Name of the new branch */ |
| 36 | char *zDate; /* Date that branch was created */ |
| 37 | char *zComment; /* Check-in comment for the new branch */ |
| 38 | const char *zColor; /* Color of the new branch */ |
| 39 | Blob branch; /* manifest for the new branch */ |
| 40 | Manifest *pParent; /* Parsed parent manifest */ |
| 41 | Blob mcksum; /* Self-checksum on the manifest */ |
| 42 | const char *zDateOvrd; /* Override date string */ |
| 43 | const char *zUserOvrd; /* Override user name */ |
| 44 | |
| 45 | noSign = find_option("nosign","",0)!=0; |
| @@ -71,41 +70,42 @@ | |
| 70 | db_begin_transaction(); |
| 71 | rootid = name_to_rid(g.argv[4]); |
| 72 | if( rootid==0 ){ |
| 73 | fossil_fatal("unable to locate check-in off of which to branch"); |
| 74 | } |
| 75 | |
| 76 | pParent = manifest_get(rootid, CFTYPE_MANIFEST); |
| 77 | if( pParent==0 ){ |
| 78 | fossil_fatal("%s is not a valid check-in", g.argv[4]); |
| 79 | } |
| 80 | |
| 81 | /* Create a manifest for the new branch */ |
| 82 | blob_zero(&branch); |
| 83 | if( pParent->zBaseline ){ |
| 84 | blob_appendf(&branch, "B %s\n", pParent->zBaseline); |
| 85 | } |
| 86 | zComment = mprintf("Create new branch named \"%h\"", zBranch); |
| 87 | blob_appendf(&branch, "C %F\n", zComment); |
| 88 | zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); |
| 89 | zDate[10] = 'T'; |
| 90 | blob_appendf(&branch, "D %s\n", zDate); |
| 91 | |
| 92 | /* Copy all of the content from the parent into the branch */ |
| 93 | for(i=0; i<pParent->nFile; ++i){ |
| 94 | blob_appendf(&branch, "F %F", pParent->aFile[i].zName); |
| 95 | if( pParent->aFile[i].zUuid ){ |
| 96 | blob_appendf(&branch, " %s", pParent->aFile[i].zUuid); |
| 97 | if( pParent->aFile[i].zPerm[0] ){ |
| 98 | blob_appendf(&branch, " %s", pParent->aFile[i].zPerm); |
| 99 | } |
| 100 | } |
| 101 | blob_append(&branch, "\n", 1); |
| 102 | } |
| 103 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid); |
| 104 | blob_appendf(&branch, "P %s\n", zUuid); |
| 105 | blob_appendf(&branch, "R %s\n", pParent->zRepoCksum); |
| 106 | manifest_destroy(pParent); |
| 107 | |
| 108 | /* Add the symbolic branch name and the "branch" tag to identify |
| 109 | ** this as a new branch */ |
| 110 | if( zColor!=0 ){ |
| 111 | blob_appendf(&branch, "T *bgcolor * %F\n", zColor); |
| 112 |
+61
-40
| --- src/browse.c | ||
| +++ src/browse.c | ||
| @@ -71,19 +71,24 @@ | ||
| 71 | 71 | ** There is no hyperlink on the file element of the path. |
| 72 | 72 | ** |
| 73 | 73 | ** The computed string is appended to the pOut blob. pOut should |
| 74 | 74 | ** have already been initialized. |
| 75 | 75 | */ |
| 76 | -void hyperlinked_path(const char *zPath, Blob *pOut){ | |
| 76 | +void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){ | |
| 77 | 77 | int i, j; |
| 78 | 78 | char *zSep = ""; |
| 79 | 79 | |
| 80 | 80 | for(i=0; zPath[i]; i=j){ |
| 81 | 81 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 82 | 82 | if( zPath[j] && g.okHistory ){ |
| 83 | - blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", | |
| 84 | - zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]); | |
| 83 | + if( zCI ){ | |
| 84 | + blob_appendf(pOut, "%s<a href=\"%s/dir?ci=%S&name=%#T\">%#h</a>", | |
| 85 | + zSep, g.zBaseURL, zCI, j, zPath, j-i, &zPath[i]); | |
| 86 | + }else{ | |
| 87 | + blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", | |
| 88 | + zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]); | |
| 89 | + } | |
| 85 | 90 | }else{ |
| 86 | 91 | blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); |
| 87 | 92 | } |
| 88 | 93 | zSep = "/"; |
| 89 | 94 | while( zPath[j]=='/' ){ j++; } |
| @@ -99,21 +104,21 @@ | ||
| 99 | 104 | ** name=PATH Directory to display. Required. |
| 100 | 105 | ** ci=LABEL Show only files in this check-in. Optional. |
| 101 | 106 | */ |
| 102 | 107 | void page_dir(void){ |
| 103 | 108 | const char *zD = P("name"); |
| 109 | + int nD = zD ? strlen(zD)+1 : 0; | |
| 104 | 110 | int mxLen; |
| 105 | 111 | int nCol, nRow; |
| 106 | 112 | int cnt, i; |
| 107 | 113 | char *zPrefix; |
| 108 | 114 | Stmt q; |
| 109 | 115 | const char *zCI = P("ci"); |
| 110 | 116 | int rid = 0; |
| 111 | 117 | char *zUuid = 0; |
| 112 | - Blob content; | |
| 113 | 118 | Blob dirname; |
| 114 | - Manifest m; | |
| 119 | + Manifest *pM = 0; | |
| 115 | 120 | const char *zSubdirLink; |
| 116 | 121 | |
| 117 | 122 | login_check_credentials(); |
| 118 | 123 | if( !g.okHistory ){ login_needed(); return; } |
| 119 | 124 | style_header("File List"); |
| @@ -126,25 +131,24 @@ | ||
| 126 | 131 | /* If a specific check-in is requested, fetch and parse it. If the |
| 127 | 132 | ** specific check-in does not exist, clear zCI. zCI==0 will cause all |
| 128 | 133 | ** files from all check-ins to be displayed. |
| 129 | 134 | */ |
| 130 | 135 | if( zCI ){ |
| 131 | - if( (rid = name_to_rid(zCI))==0 || content_get(rid, &content)==0 ){ | |
| 132 | - zCI = 0; /* No artifact named zCI exists */ | |
| 133 | - }else if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){ | |
| 134 | - zCI = 0; /* The artifact exists but is not a manifest */ | |
| 136 | + pM = manifest_get_by_name(zCI, &rid); | |
| 137 | + if( pM ){ | |
| 138 | + zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); | |
| 135 | 139 | }else{ |
| 136 | - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); | |
| 140 | + zCI = 0; | |
| 137 | 141 | } |
| 138 | 142 | } |
| 139 | 143 | |
| 140 | 144 | |
| 141 | 145 | /* Compute the title of the page */ |
| 142 | 146 | blob_zero(&dirname); |
| 143 | 147 | if( zD ){ |
| 144 | 148 | blob_append(&dirname, "in directory ", -1); |
| 145 | - hyperlinked_path(zD, &dirname); | |
| 149 | + hyperlinked_path(zD, &dirname, zCI); | |
| 146 | 150 | zPrefix = mprintf("%h/", zD); |
| 147 | 151 | }else{ |
| 148 | 152 | blob_append(&dirname, "in the top-level directory", -1); |
| 149 | 153 | zPrefix = ""; |
| 150 | 154 | } |
| @@ -160,22 +164,30 @@ | ||
| 160 | 164 | style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); |
| 161 | 165 | }else{ |
| 162 | 166 | style_submenu_element("All", "All", "%s/dir", g.zBaseURL); |
| 163 | 167 | } |
| 164 | 168 | }else{ |
| 169 | + int hasTrunk; | |
| 165 | 170 | @ <h2>The union of all files from all check-ins |
| 166 | 171 | @ %s(blob_str(&dirname))</h2> |
| 172 | + hasTrunk = db_exists( | |
| 173 | + "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'", | |
| 174 | + TAG_BRANCH); | |
| 167 | 175 | zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); |
| 168 | 176 | if( zD ){ |
| 169 | 177 | style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL); |
| 170 | 178 | style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", |
| 171 | 179 | g.zBaseURL, zD); |
| 172 | - style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", | |
| 173 | - g.zBaseURL,zD); | |
| 180 | + if( hasTrunk ){ | |
| 181 | + style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", | |
| 182 | + g.zBaseURL,zD); | |
| 183 | + } | |
| 174 | 184 | }else{ |
| 175 | 185 | style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL); |
| 176 | - style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); | |
| 186 | + if( hasTrunk ){ | |
| 187 | + style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); | |
| 188 | + } | |
| 177 | 189 | } |
| 178 | 190 | } |
| 179 | 191 | |
| 180 | 192 | /* Compute the temporary table "localfiles" containing the names |
| 181 | 193 | ** of all files and subdirectories in the zD[] directory. |
| @@ -184,39 +196,47 @@ | ||
| 184 | 196 | ** first and it also gives us an easy way to distinguish files |
| 185 | 197 | ** from directories in the loop that follows. |
| 186 | 198 | */ |
| 187 | 199 | db_multi_exec( |
| 188 | 200 | "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);" |
| 189 | - "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);" | |
| 190 | 201 | ); |
| 191 | 202 | if( zCI ){ |
| 192 | 203 | Stmt ins; |
| 193 | - int i; | |
| 194 | - db_prepare(&ins, "INSERT INTO allfiles VALUES(:x, :u)"); | |
| 195 | - for(i=0; i<m.nFile; i++){ | |
| 196 | - db_bind_text(&ins, ":x", m.aFile[i].zName); | |
| 197 | - db_bind_text(&ins, ":u", m.aFile[i].zUuid); | |
| 198 | - db_step(&ins); | |
| 199 | - db_reset(&ins); | |
| 200 | - } | |
| 201 | - db_finalize(&ins); | |
| 202 | - }else{ | |
| 203 | - db_multi_exec( | |
| 204 | - "INSERT INTO allfiles SELECT name, NULL FROM filename" | |
| 205 | - ); | |
| 206 | - } | |
| 207 | - if( zD ){ | |
| 208 | - db_multi_exec( | |
| 209 | - "INSERT OR IGNORE INTO localfiles " | |
| 210 | - " SELECT pathelement(x,%d), u FROM allfiles" | |
| 211 | - " WHERE x GLOB '%q/*'", | |
| 212 | - strlen(zD)+1, zD | |
| 213 | - ); | |
| 214 | - }else{ | |
| 215 | - db_multi_exec( | |
| 216 | - "INSERT OR IGNORE INTO localfiles " | |
| 217 | - " SELECT pathelement(x,0), u FROM allfiles" | |
| 204 | + ManifestFile *pFile; | |
| 205 | + ManifestFile *pPrev = 0; | |
| 206 | + int nPrev = 0; | |
| 207 | + int c; | |
| 208 | + | |
| 209 | + db_prepare(&ins, | |
| 210 | + "INSERT OR IGNORE INTO localfiles VALUES(pathelement(:x,0), :u)" | |
| 211 | + ); | |
| 212 | + manifest_file_rewind(pM); | |
| 213 | + while( (pFile = manifest_file_next(pM,0))!=0 ){ | |
| 214 | + if( nD>0 && memcmp(pFile->zName, zD, nD-1)!=0 ) continue; | |
| 215 | + if( pPrev && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 ){ | |
| 216 | + continue; | |
| 217 | + } | |
| 218 | + db_bind_text(&ins, ":x", &pFile->zName[nD]); | |
| 219 | + db_bind_text(&ins, ":u", pFile->zUuid); | |
| 220 | + db_step(&ins); | |
| 221 | + db_reset(&ins); | |
| 222 | + pPrev = pFile; | |
| 223 | + for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} | |
| 224 | + if( c=='/' ) nPrev++; | |
| 225 | + } | |
| 226 | + db_finalize(&ins); | |
| 227 | + }else if( zD ){ | |
| 228 | + db_multi_exec( | |
| 229 | + "INSERT OR IGNORE INTO localfiles" | |
| 230 | + " SELECT pathelement(name,%d), NULL FROM filename" | |
| 231 | + " WHERE name GLOB '%q/*'", | |
| 232 | + nD, zD | |
| 233 | + ); | |
| 234 | + }else{ | |
| 235 | + db_multi_exec( | |
| 236 | + "INSERT OR IGNORE INTO localfiles" | |
| 237 | + " SELECT pathelement(name,0), NULL FROM filename" | |
| 218 | 238 | ); |
| 219 | 239 | } |
| 220 | 240 | |
| 221 | 241 | /* Generate a multi-column table listing the contents of zD[] |
| 222 | 242 | ** directory. |
| @@ -246,8 +266,9 @@ | ||
| 246 | 266 | @ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN) |
| 247 | 267 | @ </a></li> |
| 248 | 268 | } |
| 249 | 269 | } |
| 250 | 270 | db_finalize(&q); |
| 271 | + manifest_destroy(pM); | |
| 251 | 272 | @ </ul></td></tr></table> |
| 252 | 273 | style_footer(); |
| 253 | 274 | } |
| 254 | 275 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -71,19 +71,24 @@ | |
| 71 | ** There is no hyperlink on the file element of the path. |
| 72 | ** |
| 73 | ** The computed string is appended to the pOut blob. pOut should |
| 74 | ** have already been initialized. |
| 75 | */ |
| 76 | void hyperlinked_path(const char *zPath, Blob *pOut){ |
| 77 | int i, j; |
| 78 | char *zSep = ""; |
| 79 | |
| 80 | for(i=0; zPath[i]; i=j){ |
| 81 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 82 | if( zPath[j] && g.okHistory ){ |
| 83 | blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", |
| 84 | zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]); |
| 85 | }else{ |
| 86 | blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); |
| 87 | } |
| 88 | zSep = "/"; |
| 89 | while( zPath[j]=='/' ){ j++; } |
| @@ -99,21 +104,21 @@ | |
| 99 | ** name=PATH Directory to display. Required. |
| 100 | ** ci=LABEL Show only files in this check-in. Optional. |
| 101 | */ |
| 102 | void page_dir(void){ |
| 103 | const char *zD = P("name"); |
| 104 | int mxLen; |
| 105 | int nCol, nRow; |
| 106 | int cnt, i; |
| 107 | char *zPrefix; |
| 108 | Stmt q; |
| 109 | const char *zCI = P("ci"); |
| 110 | int rid = 0; |
| 111 | char *zUuid = 0; |
| 112 | Blob content; |
| 113 | Blob dirname; |
| 114 | Manifest m; |
| 115 | const char *zSubdirLink; |
| 116 | |
| 117 | login_check_credentials(); |
| 118 | if( !g.okHistory ){ login_needed(); return; } |
| 119 | style_header("File List"); |
| @@ -126,25 +131,24 @@ | |
| 126 | /* If a specific check-in is requested, fetch and parse it. If the |
| 127 | ** specific check-in does not exist, clear zCI. zCI==0 will cause all |
| 128 | ** files from all check-ins to be displayed. |
| 129 | */ |
| 130 | if( zCI ){ |
| 131 | if( (rid = name_to_rid(zCI))==0 || content_get(rid, &content)==0 ){ |
| 132 | zCI = 0; /* No artifact named zCI exists */ |
| 133 | }else if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){ |
| 134 | zCI = 0; /* The artifact exists but is not a manifest */ |
| 135 | }else{ |
| 136 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | |
| 141 | /* Compute the title of the page */ |
| 142 | blob_zero(&dirname); |
| 143 | if( zD ){ |
| 144 | blob_append(&dirname, "in directory ", -1); |
| 145 | hyperlinked_path(zD, &dirname); |
| 146 | zPrefix = mprintf("%h/", zD); |
| 147 | }else{ |
| 148 | blob_append(&dirname, "in the top-level directory", -1); |
| 149 | zPrefix = ""; |
| 150 | } |
| @@ -160,22 +164,30 @@ | |
| 160 | style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); |
| 161 | }else{ |
| 162 | style_submenu_element("All", "All", "%s/dir", g.zBaseURL); |
| 163 | } |
| 164 | }else{ |
| 165 | @ <h2>The union of all files from all check-ins |
| 166 | @ %s(blob_str(&dirname))</h2> |
| 167 | zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); |
| 168 | if( zD ){ |
| 169 | style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL); |
| 170 | style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", |
| 171 | g.zBaseURL, zD); |
| 172 | style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", |
| 173 | g.zBaseURL,zD); |
| 174 | }else{ |
| 175 | style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL); |
| 176 | style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /* Compute the temporary table "localfiles" containing the names |
| 181 | ** of all files and subdirectories in the zD[] directory. |
| @@ -184,39 +196,47 @@ | |
| 184 | ** first and it also gives us an easy way to distinguish files |
| 185 | ** from directories in the loop that follows. |
| 186 | */ |
| 187 | db_multi_exec( |
| 188 | "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);" |
| 189 | "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);" |
| 190 | ); |
| 191 | if( zCI ){ |
| 192 | Stmt ins; |
| 193 | int i; |
| 194 | db_prepare(&ins, "INSERT INTO allfiles VALUES(:x, :u)"); |
| 195 | for(i=0; i<m.nFile; i++){ |
| 196 | db_bind_text(&ins, ":x", m.aFile[i].zName); |
| 197 | db_bind_text(&ins, ":u", m.aFile[i].zUuid); |
| 198 | db_step(&ins); |
| 199 | db_reset(&ins); |
| 200 | } |
| 201 | db_finalize(&ins); |
| 202 | }else{ |
| 203 | db_multi_exec( |
| 204 | "INSERT INTO allfiles SELECT name, NULL FROM filename" |
| 205 | ); |
| 206 | } |
| 207 | if( zD ){ |
| 208 | db_multi_exec( |
| 209 | "INSERT OR IGNORE INTO localfiles " |
| 210 | " SELECT pathelement(x,%d), u FROM allfiles" |
| 211 | " WHERE x GLOB '%q/*'", |
| 212 | strlen(zD)+1, zD |
| 213 | ); |
| 214 | }else{ |
| 215 | db_multi_exec( |
| 216 | "INSERT OR IGNORE INTO localfiles " |
| 217 | " SELECT pathelement(x,0), u FROM allfiles" |
| 218 | ); |
| 219 | } |
| 220 | |
| 221 | /* Generate a multi-column table listing the contents of zD[] |
| 222 | ** directory. |
| @@ -246,8 +266,9 @@ | |
| 246 | @ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN) |
| 247 | @ </a></li> |
| 248 | } |
| 249 | } |
| 250 | db_finalize(&q); |
| 251 | @ </ul></td></tr></table> |
| 252 | style_footer(); |
| 253 | } |
| 254 |
| --- src/browse.c | |
| +++ src/browse.c | |
| @@ -71,19 +71,24 @@ | |
| 71 | ** There is no hyperlink on the file element of the path. |
| 72 | ** |
| 73 | ** The computed string is appended to the pOut blob. pOut should |
| 74 | ** have already been initialized. |
| 75 | */ |
| 76 | void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){ |
| 77 | int i, j; |
| 78 | char *zSep = ""; |
| 79 | |
| 80 | for(i=0; zPath[i]; i=j){ |
| 81 | for(j=i; zPath[j] && zPath[j]!='/'; j++){} |
| 82 | if( zPath[j] && g.okHistory ){ |
| 83 | if( zCI ){ |
| 84 | blob_appendf(pOut, "%s<a href=\"%s/dir?ci=%S&name=%#T\">%#h</a>", |
| 85 | zSep, g.zBaseURL, zCI, j, zPath, j-i, &zPath[i]); |
| 86 | }else{ |
| 87 | blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>", |
| 88 | zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]); |
| 89 | } |
| 90 | }else{ |
| 91 | blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]); |
| 92 | } |
| 93 | zSep = "/"; |
| 94 | while( zPath[j]=='/' ){ j++; } |
| @@ -99,21 +104,21 @@ | |
| 104 | ** name=PATH Directory to display. Required. |
| 105 | ** ci=LABEL Show only files in this check-in. Optional. |
| 106 | */ |
| 107 | void page_dir(void){ |
| 108 | const char *zD = P("name"); |
| 109 | int nD = zD ? strlen(zD)+1 : 0; |
| 110 | int mxLen; |
| 111 | int nCol, nRow; |
| 112 | int cnt, i; |
| 113 | char *zPrefix; |
| 114 | Stmt q; |
| 115 | const char *zCI = P("ci"); |
| 116 | int rid = 0; |
| 117 | char *zUuid = 0; |
| 118 | Blob dirname; |
| 119 | Manifest *pM = 0; |
| 120 | const char *zSubdirLink; |
| 121 | |
| 122 | login_check_credentials(); |
| 123 | if( !g.okHistory ){ login_needed(); return; } |
| 124 | style_header("File List"); |
| @@ -126,25 +131,24 @@ | |
| 131 | /* If a specific check-in is requested, fetch and parse it. If the |
| 132 | ** specific check-in does not exist, clear zCI. zCI==0 will cause all |
| 133 | ** files from all check-ins to be displayed. |
| 134 | */ |
| 135 | if( zCI ){ |
| 136 | pM = manifest_get_by_name(zCI, &rid); |
| 137 | if( pM ){ |
| 138 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 139 | }else{ |
| 140 | zCI = 0; |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | |
| 145 | /* Compute the title of the page */ |
| 146 | blob_zero(&dirname); |
| 147 | if( zD ){ |
| 148 | blob_append(&dirname, "in directory ", -1); |
| 149 | hyperlinked_path(zD, &dirname, zCI); |
| 150 | zPrefix = mprintf("%h/", zD); |
| 151 | }else{ |
| 152 | blob_append(&dirname, "in the top-level directory", -1); |
| 153 | zPrefix = ""; |
| 154 | } |
| @@ -160,22 +164,30 @@ | |
| 164 | style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD); |
| 165 | }else{ |
| 166 | style_submenu_element("All", "All", "%s/dir", g.zBaseURL); |
| 167 | } |
| 168 | }else{ |
| 169 | int hasTrunk; |
| 170 | @ <h2>The union of all files from all check-ins |
| 171 | @ %s(blob_str(&dirname))</h2> |
| 172 | hasTrunk = db_exists( |
| 173 | "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'", |
| 174 | TAG_BRANCH); |
| 175 | zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix); |
| 176 | if( zD ){ |
| 177 | style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL); |
| 178 | style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip", |
| 179 | g.zBaseURL, zD); |
| 180 | if( hasTrunk ){ |
| 181 | style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk", |
| 182 | g.zBaseURL,zD); |
| 183 | } |
| 184 | }else{ |
| 185 | style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL); |
| 186 | if( hasTrunk ){ |
| 187 | style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL); |
| 188 | } |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | /* Compute the temporary table "localfiles" containing the names |
| 193 | ** of all files and subdirectories in the zD[] directory. |
| @@ -184,39 +196,47 @@ | |
| 196 | ** first and it also gives us an easy way to distinguish files |
| 197 | ** from directories in the loop that follows. |
| 198 | */ |
| 199 | db_multi_exec( |
| 200 | "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);" |
| 201 | ); |
| 202 | if( zCI ){ |
| 203 | Stmt ins; |
| 204 | ManifestFile *pFile; |
| 205 | ManifestFile *pPrev = 0; |
| 206 | int nPrev = 0; |
| 207 | int c; |
| 208 | |
| 209 | db_prepare(&ins, |
| 210 | "INSERT OR IGNORE INTO localfiles VALUES(pathelement(:x,0), :u)" |
| 211 | ); |
| 212 | manifest_file_rewind(pM); |
| 213 | while( (pFile = manifest_file_next(pM,0))!=0 ){ |
| 214 | if( nD>0 && memcmp(pFile->zName, zD, nD-1)!=0 ) continue; |
| 215 | if( pPrev && memcmp(&pFile->zName[nD],&pPrev->zName[nD],nPrev)==0 ){ |
| 216 | continue; |
| 217 | } |
| 218 | db_bind_text(&ins, ":x", &pFile->zName[nD]); |
| 219 | db_bind_text(&ins, ":u", pFile->zUuid); |
| 220 | db_step(&ins); |
| 221 | db_reset(&ins); |
| 222 | pPrev = pFile; |
| 223 | for(nPrev=0; (c=pPrev->zName[nD+nPrev]) && c!='/'; nPrev++){} |
| 224 | if( c=='/' ) nPrev++; |
| 225 | } |
| 226 | db_finalize(&ins); |
| 227 | }else if( zD ){ |
| 228 | db_multi_exec( |
| 229 | "INSERT OR IGNORE INTO localfiles" |
| 230 | " SELECT pathelement(name,%d), NULL FROM filename" |
| 231 | " WHERE name GLOB '%q/*'", |
| 232 | nD, zD |
| 233 | ); |
| 234 | }else{ |
| 235 | db_multi_exec( |
| 236 | "INSERT OR IGNORE INTO localfiles" |
| 237 | " SELECT pathelement(name,0), NULL FROM filename" |
| 238 | ); |
| 239 | } |
| 240 | |
| 241 | /* Generate a multi-column table listing the contents of zD[] |
| 242 | ** directory. |
| @@ -246,8 +266,9 @@ | |
| 266 | @ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN) |
| 267 | @ </a></li> |
| 268 | } |
| 269 | } |
| 270 | db_finalize(&q); |
| 271 | manifest_destroy(pM); |
| 272 | @ </ul></td></tr></table> |
| 273 | style_footer(); |
| 274 | } |
| 275 |
+285
-141
| --- src/checkin.c | ||
| +++ src/checkin.c | ||
| @@ -259,10 +259,11 @@ | ||
| 259 | 259 | Blob repo; |
| 260 | 260 | Stmt q; |
| 261 | 261 | int n; |
| 262 | 262 | const char *zIgnoreFlag = find_option("ignore",0,1); |
| 263 | 263 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 264 | + int outputManifest = db_get_boolean("manifest",0); | |
| 264 | 265 | |
| 265 | 266 | db_must_be_within_tree(); |
| 266 | 267 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 267 | 268 | n = strlen(g.zLocalRoot); |
| 268 | 269 | blob_init(&path, g.zLocalRoot, n-1); |
| @@ -270,16 +271,18 @@ | ||
| 270 | 271 | zIgnoreFlag = db_get("ignore-glob", 0); |
| 271 | 272 | } |
| 272 | 273 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 273 | 274 | db_prepare(&q, |
| 274 | 275 | "SELECT x FROM sfile" |
| 275 | - " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_'," | |
| 276 | + " WHERE x NOT IN ('%s','%s','_FOSSIL_'," | |
| 276 | 277 | "'_FOSSIL_-journal','.fos','.fos-journal'," |
| 277 | 278 | "'_FOSSIL_-wal','_FOSSIL_-shm','.fos-wal'," |
| 278 | 279 | "'.fos-shm')" |
| 279 | 280 | " AND NOT %s" |
| 280 | 281 | " ORDER BY 1", |
| 282 | + outputManifest ? "manifest" : "_FOSSIL_", | |
| 283 | + outputManifest ? "manifest.uuid" : "_FOSSIL_", | |
| 281 | 284 | glob_expr("x", zIgnoreFlag) |
| 282 | 285 | ); |
| 283 | 286 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 284 | 287 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 285 | 288 | } |
| @@ -537,21 +540,158 @@ | ||
| 537 | 540 | zDate[10] = 'T'; |
| 538 | 541 | return zDate; |
| 539 | 542 | } |
| 540 | 543 | |
| 541 | 544 | /* |
| 542 | -** Return TRUE (non-zero) if a file named "zFilename" exists in | |
| 543 | -** the checkout identified by vid. | |
| 544 | -** | |
| 545 | -** The original purpose of this routine was to check for the presence of | |
| 546 | -** a "checked-in" file named "manifest" or "manifest.uuid" so as to avoid | |
| 547 | -** overwriting that file with automatically generated files. | |
| 545 | +** Create a manifest. | |
| 548 | 546 | */ |
| 549 | -int file_exists_in_checkout(int vid, const char *zFilename){ | |
| 550 | - return db_exists("SELECT 1 FROM vfile WHERE vid=%d AND pathname=%Q", | |
| 551 | - vid, zFilename); | |
| 547 | +static void create_manifest( | |
| 548 | + Blob *pOut, /* Write the manifest here */ | |
| 549 | + const char *zBaselineUuid, /* UUID of baseline, or zero */ | |
| 550 | + Manifest *pBaseline, /* Make it a delta manifest if not zero */ | |
| 551 | + Blob *pComment, /* Check-in comment text */ | |
| 552 | + int vid, /* blob-id of the parent manifest */ | |
| 553 | + int verifyDate, /* Verify that child is younger */ | |
| 554 | + Blob *pCksum, /* Repository checksum. May be 0 */ | |
| 555 | + const char *zDateOvrd, /* Date override. If 0 then use 'now' */ | |
| 556 | + const char *zUserOvrd, /* User override. If 0 then use g.zLogin */ | |
| 557 | + const char *zBranch, /* Branch name. May be 0 */ | |
| 558 | + const char *zBgColor, /* Background color. May be 0 */ | |
| 559 | + int *pnFBcard /* Number of generated B- and F-cards */ | |
| 560 | +){ | |
| 561 | + char *zDate; /* Date of the check-in */ | |
| 562 | + char *zParentUuid; /* UUID of parent check-in */ | |
| 563 | + Blob filename; /* A single filename */ | |
| 564 | + int nBasename; /* Size of base filename */ | |
| 565 | + Stmt q; /* Query of files changed */ | |
| 566 | + Stmt q2; /* Query of merge parents */ | |
| 567 | + Blob mcksum; /* Manifest checksum */ | |
| 568 | + ManifestFile *pFile; /* File from the baseline */ | |
| 569 | + int nFBcard = 0; /* Number of B-cards and F-cards */ | |
| 570 | + | |
| 571 | + assert( pBaseline==0 || pBaseline->zBaseline==0 ); | |
| 572 | + assert( pBaseline==0 || zBaselineUuid!=0 ); | |
| 573 | + blob_zero(pOut); | |
| 574 | + zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); | |
| 575 | + if( pBaseline ){ | |
| 576 | + blob_appendf(pOut, "B %s\n", zBaselineUuid); | |
| 577 | + manifest_file_rewind(pBaseline); | |
| 578 | + pFile = manifest_file_next(pBaseline, 0); | |
| 579 | + nFBcard++; | |
| 580 | + }else{ | |
| 581 | + pFile = 0; | |
| 582 | + } | |
| 583 | + blob_appendf(pOut, "C %F\n", blob_str(pComment)); | |
| 584 | + zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); | |
| 585 | + blob_appendf(pOut, "D %s\n", zDate); | |
| 586 | + zDate[10] = ' '; | |
| 587 | + db_prepare(&q, | |
| 588 | + "SELECT pathname, uuid, origname, blob.rid, isexe" | |
| 589 | + " FROM vfile JOIN blob ON vfile.mrid=blob.rid" | |
| 590 | + " WHERE NOT deleted AND vfile.vid=%d" | |
| 591 | + " ORDER BY 1", vid); | |
| 592 | + blob_zero(&filename); | |
| 593 | + blob_appendf(&filename, "%s", g.zLocalRoot); | |
| 594 | + nBasename = blob_size(&filename); | |
| 595 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 596 | + const char *zName = db_column_text(&q, 0); | |
| 597 | + const char *zUuid = db_column_text(&q, 1); | |
| 598 | + const char *zOrig = db_column_text(&q, 2); | |
| 599 | + int frid = db_column_int(&q, 3); | |
| 600 | + int isexe = db_column_int(&q, 4); | |
| 601 | + const char *zPerm; | |
| 602 | + int cmp; | |
| 603 | + blob_append(&filename, zName, -1); | |
| 604 | +#if !defined(_WIN32) | |
| 605 | + /* For unix, extract the "executable" permission bit directly from | |
| 606 | + ** the filesystem. On windows, the "executable" bit is retained | |
| 607 | + ** unchanged from the original. */ | |
| 608 | + isexe = file_isexe(blob_str(&filename)); | |
| 609 | +#endif | |
| 610 | + if( isexe ){ | |
| 611 | + zPerm = " x"; | |
| 612 | + }else{ | |
| 613 | + zPerm = ""; | |
| 614 | + } | |
| 615 | + if( !g.markPrivate ) content_make_public(frid); | |
| 616 | + while( pFile && strcmp(pFile->zName,zName)<0 ){ | |
| 617 | + blob_appendf(pOut, "F %F\n", pFile->zName); | |
| 618 | + pFile = manifest_file_next(pBaseline, 0); | |
| 619 | + nFBcard++; | |
| 620 | + } | |
| 621 | + cmp = 1; | |
| 622 | + if( pFile==0 | |
| 623 | + || (cmp = strcmp(pFile->zName,zName))!=0 | |
| 624 | + || strcmp(pFile->zUuid, zUuid)!=0 | |
| 625 | + ){ | |
| 626 | + blob_resize(&filename, nBasename); | |
| 627 | + if( zOrig==0 || strcmp(zOrig,zName)==0 ){ | |
| 628 | + blob_appendf(pOut, "F %F %s%s\n", zName, zUuid, zPerm); | |
| 629 | + }else{ | |
| 630 | + if( zPerm[0]==0 ){ zPerm = " w"; } | |
| 631 | + blob_appendf(pOut, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); | |
| 632 | + } | |
| 633 | + nFBcard++; | |
| 634 | + } | |
| 635 | + if( cmp==0 ) pFile = manifest_file_next(pBaseline,0); | |
| 636 | + } | |
| 637 | + blob_reset(&filename); | |
| 638 | + db_finalize(&q); | |
| 639 | + while( pFile ){ | |
| 640 | + blob_appendf(pOut, "F %F\n", pFile->zName); | |
| 641 | + pFile = manifest_file_next(pBaseline, 0); | |
| 642 | + nFBcard++; | |
| 643 | + } | |
| 644 | + blob_appendf(pOut, "P %s", zParentUuid); | |
| 645 | + if( verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); | |
| 646 | + free(zParentUuid); | |
| 647 | + db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); | |
| 648 | + db_bind_int(&q2, ":id", 0); | |
| 649 | + while( db_step(&q2)==SQLITE_ROW ){ | |
| 650 | + char *zMergeUuid; | |
| 651 | + int mid = db_column_int(&q2, 0); | |
| 652 | + if( !g.markPrivate && content_is_private(mid) ) continue; | |
| 653 | + zMergeUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); | |
| 654 | + if( zMergeUuid ){ | |
| 655 | + blob_appendf(pOut, " %s", zMergeUuid); | |
| 656 | + if( verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); | |
| 657 | + free(zMergeUuid); | |
| 658 | + } | |
| 659 | + } | |
| 660 | + db_finalize(&q2); | |
| 661 | + free(zDate); | |
| 662 | + | |
| 663 | + blob_appendf(pOut, "\n"); | |
| 664 | + if( pCksum ) blob_appendf(pOut, "R %b\n", pCksum); | |
| 665 | + if( zBranch && zBranch[0] ){ | |
| 666 | + Stmt q; | |
| 667 | + if( zBgColor && zBgColor[0] ){ | |
| 668 | + blob_appendf(pOut, "T *bgcolor * %F\n", zBgColor); | |
| 669 | + } | |
| 670 | + blob_appendf(pOut, "T *branch * %F\n", zBranch); | |
| 671 | + blob_appendf(pOut, "T *sym-%F *\n", zBranch); | |
| 672 | + | |
| 673 | + /* Cancel all other symbolic tags */ | |
| 674 | + db_prepare(&q, | |
| 675 | + "SELECT tagname FROM tagxref, tag" | |
| 676 | + " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" | |
| 677 | + " AND tagtype>0 AND tagname GLOB 'sym-*'" | |
| 678 | + " AND tagname!='sym-'||%Q" | |
| 679 | + " ORDER BY tagname", | |
| 680 | + vid, zBranch); | |
| 681 | + while( db_step(&q)==SQLITE_ROW ){ | |
| 682 | + const char *zTag = db_column_text(&q, 0); | |
| 683 | + blob_appendf(pOut, "T -%F *\n", zTag); | |
| 684 | + } | |
| 685 | + db_finalize(&q); | |
| 686 | + } | |
| 687 | + blob_appendf(pOut, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); | |
| 688 | + md5sum_blob(pOut, &mcksum); | |
| 689 | + blob_appendf(pOut, "Z %b\n", &mcksum); | |
| 690 | + if( pnFBcard ) *pnFBcard = nFBcard; | |
| 552 | 691 | } |
| 692 | + | |
| 553 | 693 | |
| 554 | 694 | /* |
| 555 | 695 | ** COMMAND: ci |
| 556 | 696 | ** COMMAND: commit |
| 557 | 697 | ** |
| @@ -586,39 +726,52 @@ | ||
| 586 | 726 | ** --branch NEW-BRANCH-NAME |
| 587 | 727 | ** --bgcolor COLOR |
| 588 | 728 | ** --nosign |
| 589 | 729 | ** --force|-f |
| 590 | 730 | ** --private |
| 731 | +** --baseline | |
| 732 | +** --delta | |
| 591 | 733 | ** |
| 592 | 734 | */ |
| 593 | 735 | void commit_cmd(void){ |
| 594 | - int rc; | |
| 595 | - int vid, nrid, nvid; | |
| 596 | - Blob comment; | |
| 597 | - const char *zComment; | |
| 598 | - Stmt q; | |
| 599 | - Stmt q2; | |
| 600 | - char *zUuid, *zDate; | |
| 736 | + int hasChanges; /* True if unsaved changes exist */ | |
| 737 | + int vid; /* blob-id of parent version */ | |
| 738 | + int nrid; /* blob-id of a modified file */ | |
| 739 | + int nvid; /* Blob-id of the new check-in */ | |
| 740 | + Blob comment; /* Check-in comment */ | |
| 741 | + const char *zComment; /* Check-in comment */ | |
| 742 | + Stmt q; /* Query to find files that have been modified */ | |
| 743 | + char *zUuid; /* UUID of the new check-in */ | |
| 601 | 744 | int noSign = 0; /* True to omit signing the manifest using GPG */ |
| 602 | 745 | int isAMerge = 0; /* True if checking in a merge */ |
| 603 | 746 | int forceFlag = 0; /* Force a fork */ |
| 747 | + int forceDelta = 0; /* Force a delta-manifest */ | |
| 748 | + int forceBaseline = 0; /* Force a baseline-manifest */ | |
| 604 | 749 | char *zManifestFile; /* Name of the manifest file */ |
| 605 | - int nBasename; /* Length of "g.zLocalRoot/" */ | |
| 750 | + int useCksum; /* True if checksums should be computed and verified */ | |
| 751 | + int outputManifest; /* True to output "manifest" and "manifest.uuid" */ | |
| 752 | + int testRun; /* True for a test run. Debugging only */ | |
| 606 | 753 | const char *zBranch; /* Create a new branch with this name */ |
| 607 | 754 | const char *zBgColor; /* Set background color when branching */ |
| 608 | 755 | const char *zDateOvrd; /* Override date string */ |
| 609 | 756 | const char *zUserOvrd; /* Override user name */ |
| 610 | 757 | const char *zComFile; /* Read commit message from this file */ |
| 611 | - Blob filename; /* complete filename */ | |
| 612 | - Blob manifest; | |
| 758 | + Blob manifest; /* Manifest in baseline form */ | |
| 613 | 759 | Blob muuid; /* Manifest uuid */ |
| 614 | - Blob mcksum; /* Self-checksum on the manifest */ | |
| 615 | 760 | Blob cksum1, cksum2; /* Before and after commit checksums */ |
| 616 | 761 | Blob cksum1b; /* Checksum recorded in the manifest */ |
| 762 | + int szD; /* Size of the delta manifest */ | |
| 763 | + int szB; /* Size of the baseline manifest */ | |
| 617 | 764 | |
| 618 | 765 | url_proxy_options(); |
| 619 | 766 | noSign = find_option("nosign",0,0)!=0; |
| 767 | + forceDelta = find_option("delta",0,0)!=0; | |
| 768 | + forceBaseline = find_option("baseline",0,0)!=0; | |
| 769 | + if( forceDelta && forceBaseline ){ | |
| 770 | + fossil_fatal("cannot use --delta and --baseline together"); | |
| 771 | + } | |
| 772 | + testRun = find_option("test",0,0)!=0; | |
| 620 | 773 | zComment = find_option("comment","m",1); |
| 621 | 774 | forceFlag = find_option("force", "f", 0)!=0; |
| 622 | 775 | zBranch = find_option("branch","b",1); |
| 623 | 776 | zBgColor = find_option("bgcolor",0,1); |
| 624 | 777 | zComFile = find_option("message-file", "M", 1); |
| @@ -630,11 +783,23 @@ | ||
| 630 | 783 | zDateOvrd = find_option("date-override",0,1); |
| 631 | 784 | zUserOvrd = find_option("user-override",0,1); |
| 632 | 785 | db_must_be_within_tree(); |
| 633 | 786 | noSign = db_get_boolean("omitsign", 0)|noSign; |
| 634 | 787 | if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } |
| 788 | + useCksum = db_get_boolean("repo-cksum", 1); | |
| 789 | + outputManifest = db_get_boolean("manifest", 0); | |
| 635 | 790 | verify_all_options(); |
| 791 | + | |
| 792 | + /* So that older versions of Fossil (that do not understand delta- | |
| 793 | + ** manifest) can continue to use this repository, do not create a new | |
| 794 | + ** delta-manifest unless this repository already contains one or more | |
| 795 | + ** delta-manifets, or unless the delta-manifest is explicitly requested | |
| 796 | + ** by the --delta option. | |
| 797 | + */ | |
| 798 | + if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){ | |
| 799 | + forceBaseline = 1; | |
| 800 | + } | |
| 636 | 801 | |
| 637 | 802 | /* Get the ID of the parent manifest artifact */ |
| 638 | 803 | vid = db_lget_int("checkout", 0); |
| 639 | 804 | if( content_is_private(vid) ){ |
| 640 | 805 | g.markPrivate = 1; |
| @@ -681,14 +846,14 @@ | ||
| 681 | 846 | */ |
| 682 | 847 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 683 | 848 | fossil_fatal("no such user: %s", g.zLogin); |
| 684 | 849 | } |
| 685 | 850 | |
| 686 | - rc = unsaved_changes(); | |
| 851 | + hasChanges = unsaved_changes(); | |
| 687 | 852 | db_begin_transaction(); |
| 688 | 853 | db_record_repository_filename(0); |
| 689 | - if( rc==0 && !isAMerge && !forceFlag ){ | |
| 854 | + if( hasChanges==0 && !isAMerge && !forceFlag ){ | |
| 690 | 855 | fossil_fatal("nothing has changed"); |
| 691 | 856 | } |
| 692 | 857 | |
| 693 | 858 | /* If one or more files that were named on the command line have not |
| 694 | 859 | ** been modified, bail out now. |
| @@ -720,11 +885,11 @@ | ||
| 720 | 885 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| 721 | 886 | TAG_CLOSED, vid) ){ |
| 722 | 887 | fossil_fatal("cannot commit against a closed leaf"); |
| 723 | 888 | } |
| 724 | 889 | |
| 725 | - vfile_aggregate_checksum_disk(vid, &cksum1); | |
| 890 | + if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1); | |
| 726 | 891 | if( zComment ){ |
| 727 | 892 | blob_zero(&comment); |
| 728 | 893 | blob_append(&comment, zComment, -1); |
| 729 | 894 | }else if( zComFile ){ |
| 730 | 895 | blob_zero(&comment); |
| @@ -775,113 +940,86 @@ | ||
| 775 | 940 | db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); |
| 776 | 941 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); |
| 777 | 942 | } |
| 778 | 943 | db_finalize(&q); |
| 779 | 944 | |
| 780 | - /* Create the manifest */ | |
| 781 | - blob_zero(&manifest); | |
| 945 | + /* Create the new manifest */ | |
| 782 | 946 | if( blob_size(&comment)==0 ){ |
| 783 | 947 | blob_append(&comment, "(no comment)", -1); |
| 784 | 948 | } |
| 785 | - blob_appendf(&manifest, "C %F\n", blob_str(&comment)); | |
| 786 | - zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); | |
| 787 | - blob_appendf(&manifest, "D %s\n", zDate); | |
| 788 | - zDate[10] = ' '; | |
| 789 | - db_prepare(&q, | |
| 790 | - "SELECT pathname, uuid, origname, blob.rid, isexe" | |
| 791 | - " FROM vfile JOIN blob ON vfile.mrid=blob.rid" | |
| 792 | - " WHERE NOT deleted AND vfile.vid=%d" | |
| 793 | - " ORDER BY 1", vid); | |
| 794 | - blob_zero(&filename); | |
| 795 | - blob_appendf(&filename, "%s", g.zLocalRoot); | |
| 796 | - nBasename = blob_size(&filename); | |
| 797 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 798 | - const char *zName = db_column_text(&q, 0); | |
| 799 | - const char *zUuid = db_column_text(&q, 1); | |
| 800 | - const char *zOrig = db_column_text(&q, 2); | |
| 801 | - int frid = db_column_int(&q, 3); | |
| 802 | - int isexe = db_column_int(&q, 4); | |
| 803 | - const char *zPerm; | |
| 804 | - blob_append(&filename, zName, -1); | |
| 805 | -#if !defined(_WIN32) | |
| 806 | - /* For unix, extract the "executable" permission bit directly from | |
| 807 | - ** the filesystem. On windows, the "executable" bit is retained | |
| 808 | - ** unchanged from the original. */ | |
| 809 | - isexe = file_isexe(blob_str(&filename)); | |
| 810 | -#endif | |
| 811 | - if( isexe ){ | |
| 812 | - zPerm = " x"; | |
| 813 | - }else{ | |
| 814 | - zPerm = ""; | |
| 815 | - } | |
| 816 | - blob_resize(&filename, nBasename); | |
| 817 | - if( zOrig==0 || strcmp(zOrig,zName)==0 ){ | |
| 818 | - blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm); | |
| 819 | - }else{ | |
| 820 | - if( zPerm[0]==0 ){ zPerm = " w"; } | |
| 821 | - blob_appendf(&manifest, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); | |
| 822 | - } | |
| 823 | - if( !g.markPrivate ) content_make_public(frid); | |
| 824 | - } | |
| 825 | - blob_reset(&filename); | |
| 826 | - db_finalize(&q); | |
| 827 | - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); | |
| 828 | - blob_appendf(&manifest, "P %s", zUuid); | |
| 829 | - | |
| 830 | - if( !forceFlag ){ | |
| 831 | - checkin_verify_younger(vid, zUuid, zDate); | |
| 832 | - db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); | |
| 833 | - db_bind_int(&q2, ":id", 0); | |
| 834 | - while( db_step(&q2)==SQLITE_ROW ){ | |
| 835 | - int mid = db_column_int(&q2, 0); | |
| 836 | - if( !g.markPrivate && content_is_private(mid) ) continue; | |
| 837 | - zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); | |
| 838 | - if( zUuid ){ | |
| 839 | - blob_appendf(&manifest, " %s", zUuid); | |
| 840 | - checkin_verify_younger(mid, zUuid, zDate); | |
| 841 | - free(zUuid); | |
| 842 | - } | |
| 843 | - } | |
| 844 | - db_finalize(&q2); | |
| 845 | - } | |
| 846 | - | |
| 847 | - blob_appendf(&manifest, "\n"); | |
| 848 | - blob_appendf(&manifest, "R %b\n", &cksum1); | |
| 849 | - if( zBranch && zBranch[0] ){ | |
| 850 | - Stmt q; | |
| 851 | - if( zBgColor && zBgColor[0] ){ | |
| 852 | - blob_appendf(&manifest, "T *bgcolor * %F\n", zBgColor); | |
| 853 | - } | |
| 854 | - blob_appendf(&manifest, "T *branch * %F\n", zBranch); | |
| 855 | - blob_appendf(&manifest, "T *sym-%F *\n", zBranch); | |
| 856 | - | |
| 857 | - /* Cancel all other symbolic tags */ | |
| 858 | - db_prepare(&q, | |
| 859 | - "SELECT tagname FROM tagxref, tag" | |
| 860 | - " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" | |
| 861 | - " AND tagtype>0 AND tagname GLOB 'sym-*'" | |
| 862 | - " AND tagname!='sym-'||%Q" | |
| 863 | - " ORDER BY tagname", | |
| 864 | - vid, zBranch); | |
| 865 | - while( db_step(&q)==SQLITE_ROW ){ | |
| 866 | - const char *zTag = db_column_text(&q, 0); | |
| 867 | - blob_appendf(&manifest, "T -%F *\n", zTag); | |
| 868 | - } | |
| 869 | - db_finalize(&q); | |
| 870 | - } | |
| 871 | - blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); | |
| 872 | - md5sum_blob(&manifest, &mcksum); | |
| 873 | - blob_appendf(&manifest, "Z %b\n", &mcksum); | |
| 949 | + if( forceDelta ){ | |
| 950 | + blob_zero(&manifest); | |
| 951 | + }else{ | |
| 952 | + create_manifest(&manifest, 0, 0, &comment, vid, | |
| 953 | + !forceFlag, useCksum ? &cksum1 : 0, | |
| 954 | + zDateOvrd, zUserOvrd, zBranch, zBgColor, &szB); | |
| 955 | + } | |
| 956 | + | |
| 957 | + /* See if a delta-manifest would be more appropriate */ | |
| 958 | + if( !forceBaseline ){ | |
| 959 | + const char *zBaselineUuid; | |
| 960 | + Manifest *pParent; | |
| 961 | + Manifest *pBaseline; | |
| 962 | + pParent = manifest_get(vid, CFTYPE_MANIFEST); | |
| 963 | + if( pParent && pParent->zBaseline ){ | |
| 964 | + zBaselineUuid = pParent->zBaseline; | |
| 965 | + pBaseline = manifest_get_by_name(zBaselineUuid, 0); | |
| 966 | + }else{ | |
| 967 | + zBaselineUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); | |
| 968 | + pBaseline = pParent; | |
| 969 | + } | |
| 970 | + if( pBaseline ){ | |
| 971 | + Blob delta; | |
| 972 | + create_manifest(&delta, zBaselineUuid, pBaseline, &comment, vid, | |
| 973 | + !forceFlag, useCksum ? &cksum1 : 0, | |
| 974 | + zDateOvrd, zUserOvrd, zBranch, zBgColor, &szD); | |
| 975 | + /* | |
| 976 | + ** At this point, two manifests have been constructed, either of | |
| 977 | + ** which would work for this checkin. The first manifest (held | |
| 978 | + ** in the "manifest" variable) is a baseline manifest and the second | |
| 979 | + ** (held in variable named "delta") is a delta manifest. The | |
| 980 | + ** question now is: which manifest should we use? | |
| 981 | + ** | |
| 982 | + ** Let B be the number of F-cards in the baseline manifest and | |
| 983 | + ** let D be the number of F-cards in the delta manifest, plus one for | |
| 984 | + ** the B-card. (B is held in the szB variable and D is held in the | |
| 985 | + ** szD variable.) Assume that all delta manifests adds X new F-cards. | |
| 986 | + ** Then to minimize the total number of F- and B-cards in the repository, | |
| 987 | + ** we should use the delta manifest if and only if: | |
| 988 | + ** | |
| 989 | + ** D*D < B*X - X*X | |
| 990 | + ** | |
| 991 | + ** X is an unknown here, but for most repositories, we will not be | |
| 992 | + ** far wrong if we assume X=3. | |
| 993 | + */ | |
| 994 | + if( forceDelta || (szD*szD)<(szB*3-9) ){ | |
| 995 | + blob_reset(&manifest); | |
| 996 | + manifest = delta; | |
| 997 | + }else{ | |
| 998 | + blob_reset(&delta); | |
| 999 | + } | |
| 1000 | + }else if( forceDelta ){ | |
| 1001 | + fossil_panic("unable to find a baseline-manifest for the delta"); | |
| 1002 | + } | |
| 1003 | + } | |
| 874 | 1004 | if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ |
| 875 | 1005 | Blob ans; |
| 876 | 1006 | blob_zero(&ans); |
| 877 | 1007 | prompt_user("unable to sign manifest. continue (y/N)? ", &ans); |
| 878 | 1008 | if( blob_str(&ans)[0]!='y' ){ |
| 879 | 1009 | fossil_exit(1); |
| 880 | 1010 | } |
| 881 | 1011 | } |
| 882 | - if( !file_exists_in_checkout(vid, "manifest") ){ | |
| 1012 | + | |
| 1013 | + /* If the --test option is specified, output the manifest file | |
| 1014 | + ** and rollback the transaction. | |
| 1015 | + */ | |
| 1016 | + if( testRun ){ | |
| 1017 | + blob_write_to_file(&manifest, ""); | |
| 1018 | + } | |
| 1019 | + | |
| 1020 | + if( outputManifest ){ | |
| 883 | 1021 | zManifestFile = mprintf("%smanifest", g.zLocalRoot); |
| 884 | 1022 | blob_write_to_file(&manifest, zManifestFile); |
| 885 | 1023 | blob_reset(&manifest); |
| 886 | 1024 | blob_read_from_file(&manifest, zManifestFile); |
| 887 | 1025 | free(zManifestFile); |
| @@ -893,11 +1031,11 @@ | ||
| 893 | 1031 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid); |
| 894 | 1032 | manifest_crosslink(nvid, &manifest); |
| 895 | 1033 | content_deltify(vid, nvid, 0); |
| 896 | 1034 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); |
| 897 | 1035 | printf("New_Version: %s\n", zUuid); |
| 898 | - if( !file_exists_in_checkout(vid, "manifest.uuid") ){ | |
| 1036 | + if( outputManifest ){ | |
| 899 | 1037 | zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 900 | 1038 | blob_zero(&muuid); |
| 901 | 1039 | blob_appendf(&muuid, "%s\n", zUuid); |
| 902 | 1040 | blob_write_to_file(&muuid, zManifestFile); |
| 903 | 1041 | free(zManifestFile); |
| @@ -914,45 +1052,51 @@ | ||
| 914 | 1052 | " WHERE file_is_selected(id);" |
| 915 | 1053 | , vid, nvid |
| 916 | 1054 | ); |
| 917 | 1055 | db_lset_int("checkout", nvid); |
| 918 | 1056 | |
| 919 | - /* Verify that the repository checksum matches the expected checksum | |
| 920 | - ** calculated before the checkin started (and stored as the R record | |
| 921 | - ** of the manifest file). | |
| 922 | - */ | |
| 923 | - vfile_aggregate_checksum_repository(nvid, &cksum2); | |
| 924 | - if( blob_compare(&cksum1, &cksum2) ){ | |
| 925 | - fossil_panic("tree checksum does not match repository after commit"); | |
| 926 | - } | |
| 927 | - | |
| 928 | - /* Verify that the manifest checksum matches the expected checksum */ | |
| 929 | - vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); | |
| 930 | - if( blob_compare(&cksum1, &cksum1b) ){ | |
| 931 | - fossil_panic("manifest checksum does not agree with manifest: " | |
| 932 | - "%b versus %b", &cksum1, &cksum1b); | |
| 933 | - } | |
| 934 | - if( blob_compare(&cksum1, &cksum2) ){ | |
| 935 | - fossil_panic("tree checksum does not match manifest after commit: " | |
| 936 | - "%b versus %b", &cksum1, &cksum2); | |
| 937 | - } | |
| 938 | - | |
| 939 | - /* Verify that the commit did not modify any disk images. */ | |
| 940 | - vfile_aggregate_checksum_disk(nvid, &cksum2); | |
| 941 | - if( blob_compare(&cksum1, &cksum2) ){ | |
| 942 | - fossil_panic("tree checksums before and after commit do not match"); | |
| 1057 | + if( useCksum ){ | |
| 1058 | + /* Verify that the repository checksum matches the expected checksum | |
| 1059 | + ** calculated before the checkin started (and stored as the R record | |
| 1060 | + ** of the manifest file). | |
| 1061 | + */ | |
| 1062 | + vfile_aggregate_checksum_repository(nvid, &cksum2); | |
| 1063 | + if( blob_compare(&cksum1, &cksum2) ){ | |
| 1064 | + fossil_panic("tree checksum does not match repository after commit"); | |
| 1065 | + } | |
| 1066 | + | |
| 1067 | + /* Verify that the manifest checksum matches the expected checksum */ | |
| 1068 | + vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); | |
| 1069 | + if( blob_compare(&cksum1, &cksum1b) ){ | |
| 1070 | + fossil_panic("manifest checksum does not agree with manifest: " | |
| 1071 | + "%b versus %b", &cksum1, &cksum1b); | |
| 1072 | + } | |
| 1073 | + if( blob_compare(&cksum1, &cksum2) ){ | |
| 1074 | + fossil_panic("tree checksum does not match manifest after commit: " | |
| 1075 | + "%b versus %b", &cksum1, &cksum2); | |
| 1076 | + } | |
| 1077 | + | |
| 1078 | + /* Verify that the commit did not modify any disk images. */ | |
| 1079 | + vfile_aggregate_checksum_disk(nvid, &cksum2); | |
| 1080 | + if( blob_compare(&cksum1, &cksum2) ){ | |
| 1081 | + fossil_panic("tree checksums before and after commit do not match"); | |
| 1082 | + } | |
| 943 | 1083 | } |
| 944 | 1084 | |
| 945 | 1085 | /* Clear the undo/redo stack */ |
| 946 | 1086 | undo_reset(); |
| 947 | 1087 | |
| 948 | 1088 | /* Commit */ |
| 949 | 1089 | db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); |
| 1090 | + if( testRun ){ | |
| 1091 | + db_end_transaction(1); | |
| 1092 | + exit(1); | |
| 1093 | + } | |
| 950 | 1094 | db_end_transaction(0); |
| 951 | 1095 | |
| 952 | 1096 | if( !g.markPrivate ){ |
| 953 | 1097 | autosync(AUTOSYNC_PUSH); |
| 954 | 1098 | } |
| 955 | 1099 | if( count_nonbranch_children(vid)>1 ){ |
| 956 | 1100 | printf("**** warning: a fork has occurred *****\n"); |
| 957 | 1101 | } |
| 958 | 1102 | } |
| 959 | 1103 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -259,10 +259,11 @@ | |
| 259 | Blob repo; |
| 260 | Stmt q; |
| 261 | int n; |
| 262 | const char *zIgnoreFlag = find_option("ignore",0,1); |
| 263 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 264 | |
| 265 | db_must_be_within_tree(); |
| 266 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 267 | n = strlen(g.zLocalRoot); |
| 268 | blob_init(&path, g.zLocalRoot, n-1); |
| @@ -270,16 +271,18 @@ | |
| 270 | zIgnoreFlag = db_get("ignore-glob", 0); |
| 271 | } |
| 272 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 273 | db_prepare(&q, |
| 274 | "SELECT x FROM sfile" |
| 275 | " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_'," |
| 276 | "'_FOSSIL_-journal','.fos','.fos-journal'," |
| 277 | "'_FOSSIL_-wal','_FOSSIL_-shm','.fos-wal'," |
| 278 | "'.fos-shm')" |
| 279 | " AND NOT %s" |
| 280 | " ORDER BY 1", |
| 281 | glob_expr("x", zIgnoreFlag) |
| 282 | ); |
| 283 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 284 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 285 | } |
| @@ -537,21 +540,158 @@ | |
| 537 | zDate[10] = 'T'; |
| 538 | return zDate; |
| 539 | } |
| 540 | |
| 541 | /* |
| 542 | ** Return TRUE (non-zero) if a file named "zFilename" exists in |
| 543 | ** the checkout identified by vid. |
| 544 | ** |
| 545 | ** The original purpose of this routine was to check for the presence of |
| 546 | ** a "checked-in" file named "manifest" or "manifest.uuid" so as to avoid |
| 547 | ** overwriting that file with automatically generated files. |
| 548 | */ |
| 549 | int file_exists_in_checkout(int vid, const char *zFilename){ |
| 550 | return db_exists("SELECT 1 FROM vfile WHERE vid=%d AND pathname=%Q", |
| 551 | vid, zFilename); |
| 552 | } |
| 553 | |
| 554 | /* |
| 555 | ** COMMAND: ci |
| 556 | ** COMMAND: commit |
| 557 | ** |
| @@ -586,39 +726,52 @@ | |
| 586 | ** --branch NEW-BRANCH-NAME |
| 587 | ** --bgcolor COLOR |
| 588 | ** --nosign |
| 589 | ** --force|-f |
| 590 | ** --private |
| 591 | ** |
| 592 | */ |
| 593 | void commit_cmd(void){ |
| 594 | int rc; |
| 595 | int vid, nrid, nvid; |
| 596 | Blob comment; |
| 597 | const char *zComment; |
| 598 | Stmt q; |
| 599 | Stmt q2; |
| 600 | char *zUuid, *zDate; |
| 601 | int noSign = 0; /* True to omit signing the manifest using GPG */ |
| 602 | int isAMerge = 0; /* True if checking in a merge */ |
| 603 | int forceFlag = 0; /* Force a fork */ |
| 604 | char *zManifestFile; /* Name of the manifest file */ |
| 605 | int nBasename; /* Length of "g.zLocalRoot/" */ |
| 606 | const char *zBranch; /* Create a new branch with this name */ |
| 607 | const char *zBgColor; /* Set background color when branching */ |
| 608 | const char *zDateOvrd; /* Override date string */ |
| 609 | const char *zUserOvrd; /* Override user name */ |
| 610 | const char *zComFile; /* Read commit message from this file */ |
| 611 | Blob filename; /* complete filename */ |
| 612 | Blob manifest; |
| 613 | Blob muuid; /* Manifest uuid */ |
| 614 | Blob mcksum; /* Self-checksum on the manifest */ |
| 615 | Blob cksum1, cksum2; /* Before and after commit checksums */ |
| 616 | Blob cksum1b; /* Checksum recorded in the manifest */ |
| 617 | |
| 618 | url_proxy_options(); |
| 619 | noSign = find_option("nosign",0,0)!=0; |
| 620 | zComment = find_option("comment","m",1); |
| 621 | forceFlag = find_option("force", "f", 0)!=0; |
| 622 | zBranch = find_option("branch","b",1); |
| 623 | zBgColor = find_option("bgcolor",0,1); |
| 624 | zComFile = find_option("message-file", "M", 1); |
| @@ -630,11 +783,23 @@ | |
| 630 | zDateOvrd = find_option("date-override",0,1); |
| 631 | zUserOvrd = find_option("user-override",0,1); |
| 632 | db_must_be_within_tree(); |
| 633 | noSign = db_get_boolean("omitsign", 0)|noSign; |
| 634 | if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } |
| 635 | verify_all_options(); |
| 636 | |
| 637 | /* Get the ID of the parent manifest artifact */ |
| 638 | vid = db_lget_int("checkout", 0); |
| 639 | if( content_is_private(vid) ){ |
| 640 | g.markPrivate = 1; |
| @@ -681,14 +846,14 @@ | |
| 681 | */ |
| 682 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 683 | fossil_fatal("no such user: %s", g.zLogin); |
| 684 | } |
| 685 | |
| 686 | rc = unsaved_changes(); |
| 687 | db_begin_transaction(); |
| 688 | db_record_repository_filename(0); |
| 689 | if( rc==0 && !isAMerge && !forceFlag ){ |
| 690 | fossil_fatal("nothing has changed"); |
| 691 | } |
| 692 | |
| 693 | /* If one or more files that were named on the command line have not |
| 694 | ** been modified, bail out now. |
| @@ -720,11 +885,11 @@ | |
| 720 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| 721 | TAG_CLOSED, vid) ){ |
| 722 | fossil_fatal("cannot commit against a closed leaf"); |
| 723 | } |
| 724 | |
| 725 | vfile_aggregate_checksum_disk(vid, &cksum1); |
| 726 | if( zComment ){ |
| 727 | blob_zero(&comment); |
| 728 | blob_append(&comment, zComment, -1); |
| 729 | }else if( zComFile ){ |
| 730 | blob_zero(&comment); |
| @@ -775,113 +940,86 @@ | |
| 775 | db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); |
| 776 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); |
| 777 | } |
| 778 | db_finalize(&q); |
| 779 | |
| 780 | /* Create the manifest */ |
| 781 | blob_zero(&manifest); |
| 782 | if( blob_size(&comment)==0 ){ |
| 783 | blob_append(&comment, "(no comment)", -1); |
| 784 | } |
| 785 | blob_appendf(&manifest, "C %F\n", blob_str(&comment)); |
| 786 | zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); |
| 787 | blob_appendf(&manifest, "D %s\n", zDate); |
| 788 | zDate[10] = ' '; |
| 789 | db_prepare(&q, |
| 790 | "SELECT pathname, uuid, origname, blob.rid, isexe" |
| 791 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 792 | " WHERE NOT deleted AND vfile.vid=%d" |
| 793 | " ORDER BY 1", vid); |
| 794 | blob_zero(&filename); |
| 795 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| 796 | nBasename = blob_size(&filename); |
| 797 | while( db_step(&q)==SQLITE_ROW ){ |
| 798 | const char *zName = db_column_text(&q, 0); |
| 799 | const char *zUuid = db_column_text(&q, 1); |
| 800 | const char *zOrig = db_column_text(&q, 2); |
| 801 | int frid = db_column_int(&q, 3); |
| 802 | int isexe = db_column_int(&q, 4); |
| 803 | const char *zPerm; |
| 804 | blob_append(&filename, zName, -1); |
| 805 | #if !defined(_WIN32) |
| 806 | /* For unix, extract the "executable" permission bit directly from |
| 807 | ** the filesystem. On windows, the "executable" bit is retained |
| 808 | ** unchanged from the original. */ |
| 809 | isexe = file_isexe(blob_str(&filename)); |
| 810 | #endif |
| 811 | if( isexe ){ |
| 812 | zPerm = " x"; |
| 813 | }else{ |
| 814 | zPerm = ""; |
| 815 | } |
| 816 | blob_resize(&filename, nBasename); |
| 817 | if( zOrig==0 || strcmp(zOrig,zName)==0 ){ |
| 818 | blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm); |
| 819 | }else{ |
| 820 | if( zPerm[0]==0 ){ zPerm = " w"; } |
| 821 | blob_appendf(&manifest, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); |
| 822 | } |
| 823 | if( !g.markPrivate ) content_make_public(frid); |
| 824 | } |
| 825 | blob_reset(&filename); |
| 826 | db_finalize(&q); |
| 827 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); |
| 828 | blob_appendf(&manifest, "P %s", zUuid); |
| 829 | |
| 830 | if( !forceFlag ){ |
| 831 | checkin_verify_younger(vid, zUuid, zDate); |
| 832 | db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); |
| 833 | db_bind_int(&q2, ":id", 0); |
| 834 | while( db_step(&q2)==SQLITE_ROW ){ |
| 835 | int mid = db_column_int(&q2, 0); |
| 836 | if( !g.markPrivate && content_is_private(mid) ) continue; |
| 837 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); |
| 838 | if( zUuid ){ |
| 839 | blob_appendf(&manifest, " %s", zUuid); |
| 840 | checkin_verify_younger(mid, zUuid, zDate); |
| 841 | free(zUuid); |
| 842 | } |
| 843 | } |
| 844 | db_finalize(&q2); |
| 845 | } |
| 846 | |
| 847 | blob_appendf(&manifest, "\n"); |
| 848 | blob_appendf(&manifest, "R %b\n", &cksum1); |
| 849 | if( zBranch && zBranch[0] ){ |
| 850 | Stmt q; |
| 851 | if( zBgColor && zBgColor[0] ){ |
| 852 | blob_appendf(&manifest, "T *bgcolor * %F\n", zBgColor); |
| 853 | } |
| 854 | blob_appendf(&manifest, "T *branch * %F\n", zBranch); |
| 855 | blob_appendf(&manifest, "T *sym-%F *\n", zBranch); |
| 856 | |
| 857 | /* Cancel all other symbolic tags */ |
| 858 | db_prepare(&q, |
| 859 | "SELECT tagname FROM tagxref, tag" |
| 860 | " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" |
| 861 | " AND tagtype>0 AND tagname GLOB 'sym-*'" |
| 862 | " AND tagname!='sym-'||%Q" |
| 863 | " ORDER BY tagname", |
| 864 | vid, zBranch); |
| 865 | while( db_step(&q)==SQLITE_ROW ){ |
| 866 | const char *zTag = db_column_text(&q, 0); |
| 867 | blob_appendf(&manifest, "T -%F *\n", zTag); |
| 868 | } |
| 869 | db_finalize(&q); |
| 870 | } |
| 871 | blob_appendf(&manifest, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); |
| 872 | md5sum_blob(&manifest, &mcksum); |
| 873 | blob_appendf(&manifest, "Z %b\n", &mcksum); |
| 874 | if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ |
| 875 | Blob ans; |
| 876 | blob_zero(&ans); |
| 877 | prompt_user("unable to sign manifest. continue (y/N)? ", &ans); |
| 878 | if( blob_str(&ans)[0]!='y' ){ |
| 879 | fossil_exit(1); |
| 880 | } |
| 881 | } |
| 882 | if( !file_exists_in_checkout(vid, "manifest") ){ |
| 883 | zManifestFile = mprintf("%smanifest", g.zLocalRoot); |
| 884 | blob_write_to_file(&manifest, zManifestFile); |
| 885 | blob_reset(&manifest); |
| 886 | blob_read_from_file(&manifest, zManifestFile); |
| 887 | free(zManifestFile); |
| @@ -893,11 +1031,11 @@ | |
| 893 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid); |
| 894 | manifest_crosslink(nvid, &manifest); |
| 895 | content_deltify(vid, nvid, 0); |
| 896 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); |
| 897 | printf("New_Version: %s\n", zUuid); |
| 898 | if( !file_exists_in_checkout(vid, "manifest.uuid") ){ |
| 899 | zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 900 | blob_zero(&muuid); |
| 901 | blob_appendf(&muuid, "%s\n", zUuid); |
| 902 | blob_write_to_file(&muuid, zManifestFile); |
| 903 | free(zManifestFile); |
| @@ -914,45 +1052,51 @@ | |
| 914 | " WHERE file_is_selected(id);" |
| 915 | , vid, nvid |
| 916 | ); |
| 917 | db_lset_int("checkout", nvid); |
| 918 | |
| 919 | /* Verify that the repository checksum matches the expected checksum |
| 920 | ** calculated before the checkin started (and stored as the R record |
| 921 | ** of the manifest file). |
| 922 | */ |
| 923 | vfile_aggregate_checksum_repository(nvid, &cksum2); |
| 924 | if( blob_compare(&cksum1, &cksum2) ){ |
| 925 | fossil_panic("tree checksum does not match repository after commit"); |
| 926 | } |
| 927 | |
| 928 | /* Verify that the manifest checksum matches the expected checksum */ |
| 929 | vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); |
| 930 | if( blob_compare(&cksum1, &cksum1b) ){ |
| 931 | fossil_panic("manifest checksum does not agree with manifest: " |
| 932 | "%b versus %b", &cksum1, &cksum1b); |
| 933 | } |
| 934 | if( blob_compare(&cksum1, &cksum2) ){ |
| 935 | fossil_panic("tree checksum does not match manifest after commit: " |
| 936 | "%b versus %b", &cksum1, &cksum2); |
| 937 | } |
| 938 | |
| 939 | /* Verify that the commit did not modify any disk images. */ |
| 940 | vfile_aggregate_checksum_disk(nvid, &cksum2); |
| 941 | if( blob_compare(&cksum1, &cksum2) ){ |
| 942 | fossil_panic("tree checksums before and after commit do not match"); |
| 943 | } |
| 944 | |
| 945 | /* Clear the undo/redo stack */ |
| 946 | undo_reset(); |
| 947 | |
| 948 | /* Commit */ |
| 949 | db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); |
| 950 | db_end_transaction(0); |
| 951 | |
| 952 | if( !g.markPrivate ){ |
| 953 | autosync(AUTOSYNC_PUSH); |
| 954 | } |
| 955 | if( count_nonbranch_children(vid)>1 ){ |
| 956 | printf("**** warning: a fork has occurred *****\n"); |
| 957 | } |
| 958 | } |
| 959 |
| --- src/checkin.c | |
| +++ src/checkin.c | |
| @@ -259,10 +259,11 @@ | |
| 259 | Blob repo; |
| 260 | Stmt q; |
| 261 | int n; |
| 262 | const char *zIgnoreFlag = find_option("ignore",0,1); |
| 263 | int allFlag = find_option("dotfiles",0,0)!=0; |
| 264 | int outputManifest = db_get_boolean("manifest",0); |
| 265 | |
| 266 | db_must_be_within_tree(); |
| 267 | db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); |
| 268 | n = strlen(g.zLocalRoot); |
| 269 | blob_init(&path, g.zLocalRoot, n-1); |
| @@ -270,16 +271,18 @@ | |
| 271 | zIgnoreFlag = db_get("ignore-glob", 0); |
| 272 | } |
| 273 | vfile_scan(0, &path, blob_size(&path), allFlag); |
| 274 | db_prepare(&q, |
| 275 | "SELECT x FROM sfile" |
| 276 | " WHERE x NOT IN ('%s','%s','_FOSSIL_'," |
| 277 | "'_FOSSIL_-journal','.fos','.fos-journal'," |
| 278 | "'_FOSSIL_-wal','_FOSSIL_-shm','.fos-wal'," |
| 279 | "'.fos-shm')" |
| 280 | " AND NOT %s" |
| 281 | " ORDER BY 1", |
| 282 | outputManifest ? "manifest" : "_FOSSIL_", |
| 283 | outputManifest ? "manifest.uuid" : "_FOSSIL_", |
| 284 | glob_expr("x", zIgnoreFlag) |
| 285 | ); |
| 286 | if( file_tree_name(g.zRepositoryName, &repo, 0) ){ |
| 287 | db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo); |
| 288 | } |
| @@ -537,21 +540,158 @@ | |
| 540 | zDate[10] = 'T'; |
| 541 | return zDate; |
| 542 | } |
| 543 | |
| 544 | /* |
| 545 | ** Create a manifest. |
| 546 | */ |
| 547 | static void create_manifest( |
| 548 | Blob *pOut, /* Write the manifest here */ |
| 549 | const char *zBaselineUuid, /* UUID of baseline, or zero */ |
| 550 | Manifest *pBaseline, /* Make it a delta manifest if not zero */ |
| 551 | Blob *pComment, /* Check-in comment text */ |
| 552 | int vid, /* blob-id of the parent manifest */ |
| 553 | int verifyDate, /* Verify that child is younger */ |
| 554 | Blob *pCksum, /* Repository checksum. May be 0 */ |
| 555 | const char *zDateOvrd, /* Date override. If 0 then use 'now' */ |
| 556 | const char *zUserOvrd, /* User override. If 0 then use g.zLogin */ |
| 557 | const char *zBranch, /* Branch name. May be 0 */ |
| 558 | const char *zBgColor, /* Background color. May be 0 */ |
| 559 | int *pnFBcard /* Number of generated B- and F-cards */ |
| 560 | ){ |
| 561 | char *zDate; /* Date of the check-in */ |
| 562 | char *zParentUuid; /* UUID of parent check-in */ |
| 563 | Blob filename; /* A single filename */ |
| 564 | int nBasename; /* Size of base filename */ |
| 565 | Stmt q; /* Query of files changed */ |
| 566 | Stmt q2; /* Query of merge parents */ |
| 567 | Blob mcksum; /* Manifest checksum */ |
| 568 | ManifestFile *pFile; /* File from the baseline */ |
| 569 | int nFBcard = 0; /* Number of B-cards and F-cards */ |
| 570 | |
| 571 | assert( pBaseline==0 || pBaseline->zBaseline==0 ); |
| 572 | assert( pBaseline==0 || zBaselineUuid!=0 ); |
| 573 | blob_zero(pOut); |
| 574 | zParentUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); |
| 575 | if( pBaseline ){ |
| 576 | blob_appendf(pOut, "B %s\n", zBaselineUuid); |
| 577 | manifest_file_rewind(pBaseline); |
| 578 | pFile = manifest_file_next(pBaseline, 0); |
| 579 | nFBcard++; |
| 580 | }else{ |
| 581 | pFile = 0; |
| 582 | } |
| 583 | blob_appendf(pOut, "C %F\n", blob_str(pComment)); |
| 584 | zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now"); |
| 585 | blob_appendf(pOut, "D %s\n", zDate); |
| 586 | zDate[10] = ' '; |
| 587 | db_prepare(&q, |
| 588 | "SELECT pathname, uuid, origname, blob.rid, isexe" |
| 589 | " FROM vfile JOIN blob ON vfile.mrid=blob.rid" |
| 590 | " WHERE NOT deleted AND vfile.vid=%d" |
| 591 | " ORDER BY 1", vid); |
| 592 | blob_zero(&filename); |
| 593 | blob_appendf(&filename, "%s", g.zLocalRoot); |
| 594 | nBasename = blob_size(&filename); |
| 595 | while( db_step(&q)==SQLITE_ROW ){ |
| 596 | const char *zName = db_column_text(&q, 0); |
| 597 | const char *zUuid = db_column_text(&q, 1); |
| 598 | const char *zOrig = db_column_text(&q, 2); |
| 599 | int frid = db_column_int(&q, 3); |
| 600 | int isexe = db_column_int(&q, 4); |
| 601 | const char *zPerm; |
| 602 | int cmp; |
| 603 | blob_append(&filename, zName, -1); |
| 604 | #if !defined(_WIN32) |
| 605 | /* For unix, extract the "executable" permission bit directly from |
| 606 | ** the filesystem. On windows, the "executable" bit is retained |
| 607 | ** unchanged from the original. */ |
| 608 | isexe = file_isexe(blob_str(&filename)); |
| 609 | #endif |
| 610 | if( isexe ){ |
| 611 | zPerm = " x"; |
| 612 | }else{ |
| 613 | zPerm = ""; |
| 614 | } |
| 615 | if( !g.markPrivate ) content_make_public(frid); |
| 616 | while( pFile && strcmp(pFile->zName,zName)<0 ){ |
| 617 | blob_appendf(pOut, "F %F\n", pFile->zName); |
| 618 | pFile = manifest_file_next(pBaseline, 0); |
| 619 | nFBcard++; |
| 620 | } |
| 621 | cmp = 1; |
| 622 | if( pFile==0 |
| 623 | || (cmp = strcmp(pFile->zName,zName))!=0 |
| 624 | || strcmp(pFile->zUuid, zUuid)!=0 |
| 625 | ){ |
| 626 | blob_resize(&filename, nBasename); |
| 627 | if( zOrig==0 || strcmp(zOrig,zName)==0 ){ |
| 628 | blob_appendf(pOut, "F %F %s%s\n", zName, zUuid, zPerm); |
| 629 | }else{ |
| 630 | if( zPerm[0]==0 ){ zPerm = " w"; } |
| 631 | blob_appendf(pOut, "F %F %s%s %F\n", zName, zUuid, zPerm, zOrig); |
| 632 | } |
| 633 | nFBcard++; |
| 634 | } |
| 635 | if( cmp==0 ) pFile = manifest_file_next(pBaseline,0); |
| 636 | } |
| 637 | blob_reset(&filename); |
| 638 | db_finalize(&q); |
| 639 | while( pFile ){ |
| 640 | blob_appendf(pOut, "F %F\n", pFile->zName); |
| 641 | pFile = manifest_file_next(pBaseline, 0); |
| 642 | nFBcard++; |
| 643 | } |
| 644 | blob_appendf(pOut, "P %s", zParentUuid); |
| 645 | if( verifyDate ) checkin_verify_younger(vid, zParentUuid, zDate); |
| 646 | free(zParentUuid); |
| 647 | db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); |
| 648 | db_bind_int(&q2, ":id", 0); |
| 649 | while( db_step(&q2)==SQLITE_ROW ){ |
| 650 | char *zMergeUuid; |
| 651 | int mid = db_column_int(&q2, 0); |
| 652 | if( !g.markPrivate && content_is_private(mid) ) continue; |
| 653 | zMergeUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); |
| 654 | if( zMergeUuid ){ |
| 655 | blob_appendf(pOut, " %s", zMergeUuid); |
| 656 | if( verifyDate ) checkin_verify_younger(mid, zMergeUuid, zDate); |
| 657 | free(zMergeUuid); |
| 658 | } |
| 659 | } |
| 660 | db_finalize(&q2); |
| 661 | free(zDate); |
| 662 | |
| 663 | blob_appendf(pOut, "\n"); |
| 664 | if( pCksum ) blob_appendf(pOut, "R %b\n", pCksum); |
| 665 | if( zBranch && zBranch[0] ){ |
| 666 | Stmt q; |
| 667 | if( zBgColor && zBgColor[0] ){ |
| 668 | blob_appendf(pOut, "T *bgcolor * %F\n", zBgColor); |
| 669 | } |
| 670 | blob_appendf(pOut, "T *branch * %F\n", zBranch); |
| 671 | blob_appendf(pOut, "T *sym-%F *\n", zBranch); |
| 672 | |
| 673 | /* Cancel all other symbolic tags */ |
| 674 | db_prepare(&q, |
| 675 | "SELECT tagname FROM tagxref, tag" |
| 676 | " WHERE tagxref.rid=%d AND tagxref.tagid=tag.tagid" |
| 677 | " AND tagtype>0 AND tagname GLOB 'sym-*'" |
| 678 | " AND tagname!='sym-'||%Q" |
| 679 | " ORDER BY tagname", |
| 680 | vid, zBranch); |
| 681 | while( db_step(&q)==SQLITE_ROW ){ |
| 682 | const char *zTag = db_column_text(&q, 0); |
| 683 | blob_appendf(pOut, "T -%F *\n", zTag); |
| 684 | } |
| 685 | db_finalize(&q); |
| 686 | } |
| 687 | blob_appendf(pOut, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin); |
| 688 | md5sum_blob(pOut, &mcksum); |
| 689 | blob_appendf(pOut, "Z %b\n", &mcksum); |
| 690 | if( pnFBcard ) *pnFBcard = nFBcard; |
| 691 | } |
| 692 | |
| 693 | |
| 694 | /* |
| 695 | ** COMMAND: ci |
| 696 | ** COMMAND: commit |
| 697 | ** |
| @@ -586,39 +726,52 @@ | |
| 726 | ** --branch NEW-BRANCH-NAME |
| 727 | ** --bgcolor COLOR |
| 728 | ** --nosign |
| 729 | ** --force|-f |
| 730 | ** --private |
| 731 | ** --baseline |
| 732 | ** --delta |
| 733 | ** |
| 734 | */ |
| 735 | void commit_cmd(void){ |
| 736 | int hasChanges; /* True if unsaved changes exist */ |
| 737 | int vid; /* blob-id of parent version */ |
| 738 | int nrid; /* blob-id of a modified file */ |
| 739 | int nvid; /* Blob-id of the new check-in */ |
| 740 | Blob comment; /* Check-in comment */ |
| 741 | const char *zComment; /* Check-in comment */ |
| 742 | Stmt q; /* Query to find files that have been modified */ |
| 743 | char *zUuid; /* UUID of the new check-in */ |
| 744 | int noSign = 0; /* True to omit signing the manifest using GPG */ |
| 745 | int isAMerge = 0; /* True if checking in a merge */ |
| 746 | int forceFlag = 0; /* Force a fork */ |
| 747 | int forceDelta = 0; /* Force a delta-manifest */ |
| 748 | int forceBaseline = 0; /* Force a baseline-manifest */ |
| 749 | char *zManifestFile; /* Name of the manifest file */ |
| 750 | int useCksum; /* True if checksums should be computed and verified */ |
| 751 | int outputManifest; /* True to output "manifest" and "manifest.uuid" */ |
| 752 | int testRun; /* True for a test run. Debugging only */ |
| 753 | const char *zBranch; /* Create a new branch with this name */ |
| 754 | const char *zBgColor; /* Set background color when branching */ |
| 755 | const char *zDateOvrd; /* Override date string */ |
| 756 | const char *zUserOvrd; /* Override user name */ |
| 757 | const char *zComFile; /* Read commit message from this file */ |
| 758 | Blob manifest; /* Manifest in baseline form */ |
| 759 | Blob muuid; /* Manifest uuid */ |
| 760 | Blob cksum1, cksum2; /* Before and after commit checksums */ |
| 761 | Blob cksum1b; /* Checksum recorded in the manifest */ |
| 762 | int szD; /* Size of the delta manifest */ |
| 763 | int szB; /* Size of the baseline manifest */ |
| 764 | |
| 765 | url_proxy_options(); |
| 766 | noSign = find_option("nosign",0,0)!=0; |
| 767 | forceDelta = find_option("delta",0,0)!=0; |
| 768 | forceBaseline = find_option("baseline",0,0)!=0; |
| 769 | if( forceDelta && forceBaseline ){ |
| 770 | fossil_fatal("cannot use --delta and --baseline together"); |
| 771 | } |
| 772 | testRun = find_option("test",0,0)!=0; |
| 773 | zComment = find_option("comment","m",1); |
| 774 | forceFlag = find_option("force", "f", 0)!=0; |
| 775 | zBranch = find_option("branch","b",1); |
| 776 | zBgColor = find_option("bgcolor",0,1); |
| 777 | zComFile = find_option("message-file", "M", 1); |
| @@ -630,11 +783,23 @@ | |
| 783 | zDateOvrd = find_option("date-override",0,1); |
| 784 | zUserOvrd = find_option("user-override",0,1); |
| 785 | db_must_be_within_tree(); |
| 786 | noSign = db_get_boolean("omitsign", 0)|noSign; |
| 787 | if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } |
| 788 | useCksum = db_get_boolean("repo-cksum", 1); |
| 789 | outputManifest = db_get_boolean("manifest", 0); |
| 790 | verify_all_options(); |
| 791 | |
| 792 | /* So that older versions of Fossil (that do not understand delta- |
| 793 | ** manifest) can continue to use this repository, do not create a new |
| 794 | ** delta-manifest unless this repository already contains one or more |
| 795 | ** delta-manifets, or unless the delta-manifest is explicitly requested |
| 796 | ** by the --delta option. |
| 797 | */ |
| 798 | if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){ |
| 799 | forceBaseline = 1; |
| 800 | } |
| 801 | |
| 802 | /* Get the ID of the parent manifest artifact */ |
| 803 | vid = db_lget_int("checkout", 0); |
| 804 | if( content_is_private(vid) ){ |
| 805 | g.markPrivate = 1; |
| @@ -681,14 +846,14 @@ | |
| 846 | */ |
| 847 | if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){ |
| 848 | fossil_fatal("no such user: %s", g.zLogin); |
| 849 | } |
| 850 | |
| 851 | hasChanges = unsaved_changes(); |
| 852 | db_begin_transaction(); |
| 853 | db_record_repository_filename(0); |
| 854 | if( hasChanges==0 && !isAMerge && !forceFlag ){ |
| 855 | fossil_fatal("nothing has changed"); |
| 856 | } |
| 857 | |
| 858 | /* If one or more files that were named on the command line have not |
| 859 | ** been modified, bail out now. |
| @@ -720,11 +885,11 @@ | |
| 885 | " WHERE tagid=%d AND rid=%d AND tagtype>0", |
| 886 | TAG_CLOSED, vid) ){ |
| 887 | fossil_fatal("cannot commit against a closed leaf"); |
| 888 | } |
| 889 | |
| 890 | if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1); |
| 891 | if( zComment ){ |
| 892 | blob_zero(&comment); |
| 893 | blob_append(&comment, zComment, -1); |
| 894 | }else if( zComFile ){ |
| 895 | blob_zero(&comment); |
| @@ -775,113 +940,86 @@ | |
| 940 | db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); |
| 941 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); |
| 942 | } |
| 943 | db_finalize(&q); |
| 944 | |
| 945 | /* Create the new manifest */ |
| 946 | if( blob_size(&comment)==0 ){ |
| 947 | blob_append(&comment, "(no comment)", -1); |
| 948 | } |
| 949 | if( forceDelta ){ |
| 950 | blob_zero(&manifest); |
| 951 | }else{ |
| 952 | create_manifest(&manifest, 0, 0, &comment, vid, |
| 953 | !forceFlag, useCksum ? &cksum1 : 0, |
| 954 | zDateOvrd, zUserOvrd, zBranch, zBgColor, &szB); |
| 955 | } |
| 956 | |
| 957 | /* See if a delta-manifest would be more appropriate */ |
| 958 | if( !forceBaseline ){ |
| 959 | const char *zBaselineUuid; |
| 960 | Manifest *pParent; |
| 961 | Manifest *pBaseline; |
| 962 | pParent = manifest_get(vid, CFTYPE_MANIFEST); |
| 963 | if( pParent && pParent->zBaseline ){ |
| 964 | zBaselineUuid = pParent->zBaseline; |
| 965 | pBaseline = manifest_get_by_name(zBaselineUuid, 0); |
| 966 | }else{ |
| 967 | zBaselineUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); |
| 968 | pBaseline = pParent; |
| 969 | } |
| 970 | if( pBaseline ){ |
| 971 | Blob delta; |
| 972 | create_manifest(&delta, zBaselineUuid, pBaseline, &comment, vid, |
| 973 | !forceFlag, useCksum ? &cksum1 : 0, |
| 974 | zDateOvrd, zUserOvrd, zBranch, zBgColor, &szD); |
| 975 | /* |
| 976 | ** At this point, two manifests have been constructed, either of |
| 977 | ** which would work for this checkin. The first manifest (held |
| 978 | ** in the "manifest" variable) is a baseline manifest and the second |
| 979 | ** (held in variable named "delta") is a delta manifest. The |
| 980 | ** question now is: which manifest should we use? |
| 981 | ** |
| 982 | ** Let B be the number of F-cards in the baseline manifest and |
| 983 | ** let D be the number of F-cards in the delta manifest, plus one for |
| 984 | ** the B-card. (B is held in the szB variable and D is held in the |
| 985 | ** szD variable.) Assume that all delta manifests adds X new F-cards. |
| 986 | ** Then to minimize the total number of F- and B-cards in the repository, |
| 987 | ** we should use the delta manifest if and only if: |
| 988 | ** |
| 989 | ** D*D < B*X - X*X |
| 990 | ** |
| 991 | ** X is an unknown here, but for most repositories, we will not be |
| 992 | ** far wrong if we assume X=3. |
| 993 | */ |
| 994 | if( forceDelta || (szD*szD)<(szB*3-9) ){ |
| 995 | blob_reset(&manifest); |
| 996 | manifest = delta; |
| 997 | }else{ |
| 998 | blob_reset(&delta); |
| 999 | } |
| 1000 | }else if( forceDelta ){ |
| 1001 | fossil_panic("unable to find a baseline-manifest for the delta"); |
| 1002 | } |
| 1003 | } |
| 1004 | if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){ |
| 1005 | Blob ans; |
| 1006 | blob_zero(&ans); |
| 1007 | prompt_user("unable to sign manifest. continue (y/N)? ", &ans); |
| 1008 | if( blob_str(&ans)[0]!='y' ){ |
| 1009 | fossil_exit(1); |
| 1010 | } |
| 1011 | } |
| 1012 | |
| 1013 | /* If the --test option is specified, output the manifest file |
| 1014 | ** and rollback the transaction. |
| 1015 | */ |
| 1016 | if( testRun ){ |
| 1017 | blob_write_to_file(&manifest, ""); |
| 1018 | } |
| 1019 | |
| 1020 | if( outputManifest ){ |
| 1021 | zManifestFile = mprintf("%smanifest", g.zLocalRoot); |
| 1022 | blob_write_to_file(&manifest, zManifestFile); |
| 1023 | blob_reset(&manifest); |
| 1024 | blob_read_from_file(&manifest, zManifestFile); |
| 1025 | free(zManifestFile); |
| @@ -893,11 +1031,11 @@ | |
| 1031 | db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid); |
| 1032 | manifest_crosslink(nvid, &manifest); |
| 1033 | content_deltify(vid, nvid, 0); |
| 1034 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); |
| 1035 | printf("New_Version: %s\n", zUuid); |
| 1036 | if( outputManifest ){ |
| 1037 | zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 1038 | blob_zero(&muuid); |
| 1039 | blob_appendf(&muuid, "%s\n", zUuid); |
| 1040 | blob_write_to_file(&muuid, zManifestFile); |
| 1041 | free(zManifestFile); |
| @@ -914,45 +1052,51 @@ | |
| 1052 | " WHERE file_is_selected(id);" |
| 1053 | , vid, nvid |
| 1054 | ); |
| 1055 | db_lset_int("checkout", nvid); |
| 1056 | |
| 1057 | if( useCksum ){ |
| 1058 | /* Verify that the repository checksum matches the expected checksum |
| 1059 | ** calculated before the checkin started (and stored as the R record |
| 1060 | ** of the manifest file). |
| 1061 | */ |
| 1062 | vfile_aggregate_checksum_repository(nvid, &cksum2); |
| 1063 | if( blob_compare(&cksum1, &cksum2) ){ |
| 1064 | fossil_panic("tree checksum does not match repository after commit"); |
| 1065 | } |
| 1066 | |
| 1067 | /* Verify that the manifest checksum matches the expected checksum */ |
| 1068 | vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); |
| 1069 | if( blob_compare(&cksum1, &cksum1b) ){ |
| 1070 | fossil_panic("manifest checksum does not agree with manifest: " |
| 1071 | "%b versus %b", &cksum1, &cksum1b); |
| 1072 | } |
| 1073 | if( blob_compare(&cksum1, &cksum2) ){ |
| 1074 | fossil_panic("tree checksum does not match manifest after commit: " |
| 1075 | "%b versus %b", &cksum1, &cksum2); |
| 1076 | } |
| 1077 | |
| 1078 | /* Verify that the commit did not modify any disk images. */ |
| 1079 | vfile_aggregate_checksum_disk(nvid, &cksum2); |
| 1080 | if( blob_compare(&cksum1, &cksum2) ){ |
| 1081 | fossil_panic("tree checksums before and after commit do not match"); |
| 1082 | } |
| 1083 | } |
| 1084 | |
| 1085 | /* Clear the undo/redo stack */ |
| 1086 | undo_reset(); |
| 1087 | |
| 1088 | /* Commit */ |
| 1089 | db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'"); |
| 1090 | if( testRun ){ |
| 1091 | db_end_transaction(1); |
| 1092 | exit(1); |
| 1093 | } |
| 1094 | db_end_transaction(0); |
| 1095 | |
| 1096 | if( !g.markPrivate ){ |
| 1097 | autosync(AUTOSYNC_PUSH); |
| 1098 | } |
| 1099 | if( count_nonbranch_children(vid)>1 ){ |
| 1100 | printf("**** warning: a fork has occurred *****\n"); |
| 1101 | } |
| 1102 | } |
| 1103 |
+60
-47
| --- src/checkout.c | ||
| +++ src/checkout.c | ||
| @@ -78,87 +78,99 @@ | ||
| 78 | 78 | |
| 79 | 79 | /* |
| 80 | 80 | ** Load a vfile from a record ID. |
| 81 | 81 | */ |
| 82 | 82 | void load_vfile_from_rid(int vid){ |
| 83 | - Blob manifest; | |
| 84 | - | |
| 85 | 83 | if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){ |
| 86 | 84 | return; |
| 87 | 85 | } |
| 88 | - content_get(vid, &manifest); | |
| 89 | - vfile_build(vid, &manifest); | |
| 90 | - blob_reset(&manifest); | |
| 86 | + vfile_build(vid); | |
| 91 | 87 | } |
| 92 | 88 | |
| 93 | 89 | /* |
| 94 | 90 | ** Set or clear the vfile.isexe flag for a file. |
| 95 | 91 | */ |
| 96 | 92 | static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ |
| 97 | - db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", | |
| 98 | - onoff, vid, zFilename); | |
| 93 | + static Stmt s; | |
| 94 | + db_static_prepare(&s, | |
| 95 | + "UPDATE vfile SET isexe=:isexe" | |
| 96 | + " WHERE vid=:vid AND pathname=:path AND isexe!=:isexe" | |
| 97 | + ); | |
| 98 | + db_bind_int(&s, ":isexe", onoff); | |
| 99 | + db_bind_int(&s, ":vid", vid); | |
| 100 | + db_bind_text(&s, ":path", zFilename); | |
| 101 | + db_step(&s); | |
| 102 | + db_reset(&s); | |
| 99 | 103 | } |
| 100 | 104 | |
| 101 | 105 | /* |
| 102 | 106 | ** Set or clear the execute permission bit (as appropriate) for all |
| 103 | 107 | ** files in the current check-out. |
| 104 | -** | |
| 105 | -** If the checkout does not have explicit files named "manifest" and | |
| 106 | -** "manifest.uuid" then automatically generate files with those names | |
| 107 | -** containing, respectively, the text of the manifest and the artifact | |
| 108 | -** ID of the manifest. | |
| 108 | +*/ | |
| 109 | +void checkout_set_all_exe(int vid){ | |
| 110 | + Blob filename; | |
| 111 | + int baseLen; | |
| 112 | + Manifest *pManifest; | |
| 113 | + ManifestFile *pFile; | |
| 114 | + | |
| 115 | + /* Check the EXE permission status of all files | |
| 116 | + */ | |
| 117 | + pManifest = manifest_get(vid, CFTYPE_MANIFEST); | |
| 118 | + if( pManifest==0 ) return; | |
| 119 | + blob_zero(&filename); | |
| 120 | + blob_appendf(&filename, "%s/", g.zLocalRoot); | |
| 121 | + baseLen = blob_size(&filename); | |
| 122 | + manifest_file_rewind(pManifest); | |
| 123 | + while( (pFile = manifest_file_next(pManifest, 0))!=0 ){ | |
| 124 | + int isExe; | |
| 125 | + blob_append(&filename, pFile->zName, -1); | |
| 126 | + isExe = pFile->zPerm && strstr(pFile->zPerm, "x"); | |
| 127 | + file_setexe(blob_str(&filename), isExe); | |
| 128 | + set_or_clear_isexe(pFile->zName, vid, isExe); | |
| 129 | + blob_resize(&filename, baseLen); | |
| 130 | + } | |
| 131 | + blob_reset(&filename); | |
| 132 | + manifest_destroy(pManifest); | |
| 133 | +} | |
| 134 | + | |
| 135 | + | |
| 136 | +/* | |
| 137 | +** If the "manifest" setting is true, then automatically generate | |
| 138 | +** files named "manifest" and "manifest.uuid" containing, respectively, | |
| 139 | +** the text of the manifest and the artifact ID of the manifest. | |
| 109 | 140 | */ |
| 110 | 141 | void manifest_to_disk(int vid){ |
| 111 | 142 | char *zManFile; |
| 112 | 143 | Blob manifest; |
| 113 | 144 | Blob hash; |
| 114 | - Blob filename; | |
| 115 | - int baseLen; | |
| 116 | - int i; | |
| 117 | - int seenManifest = 0; | |
| 118 | - int seenManifestUuid = 0; | |
| 119 | - Manifest m; | |
| 120 | - | |
| 121 | - /* Check the EXE permission status of all files | |
| 122 | - */ | |
| 123 | - blob_zero(&manifest); | |
| 124 | - content_get(vid, &manifest); | |
| 125 | - manifest_parse(&m, &manifest); | |
| 126 | - blob_zero(&filename); | |
| 127 | - blob_appendf(&filename, "%s/", g.zLocalRoot); | |
| 128 | - baseLen = blob_size(&filename); | |
| 129 | - for(i=0; i<m.nFile; i++){ | |
| 130 | - int isExe; | |
| 131 | - blob_append(&filename, m.aFile[i].zName, -1); | |
| 132 | - isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); | |
| 133 | - file_setexe(blob_str(&filename), isExe); | |
| 134 | - set_or_clear_isexe(m.aFile[i].zName, vid, isExe); | |
| 135 | - blob_resize(&filename, baseLen); | |
| 136 | - if( memcmp(m.aFile[i].zName, "manifest", 8)==0 ){ | |
| 137 | - if( m.aFile[i].zName[8]==0 ) seenManifest = 1; | |
| 138 | - if( strcmp(&m.aFile[i].zName[8], ".uuid")==0 ) seenManifestUuid = 1; | |
| 139 | - } | |
| 140 | - } | |
| 141 | - blob_reset(&filename); | |
| 142 | - manifest_clear(&m); | |
| 143 | - | |
| 144 | - blob_zero(&manifest); | |
| 145 | - content_get(vid, &manifest); | |
| 146 | - if( !seenManifest ){ | |
| 145 | + | |
| 146 | + if( db_get_boolean("manifest",0) ){ | |
| 147 | + blob_zero(&manifest); | |
| 148 | + content_get(vid, &manifest); | |
| 147 | 149 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 148 | 150 | blob_write_to_file(&manifest, zManFile); |
| 149 | 151 | free(zManFile); |
| 150 | - } | |
| 151 | - if( !seenManifestUuid ){ | |
| 152 | 152 | blob_zero(&hash); |
| 153 | 153 | sha1sum_blob(&manifest, &hash); |
| 154 | 154 | zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 155 | 155 | blob_append(&hash, "\n", 1); |
| 156 | 156 | blob_write_to_file(&hash, zManFile); |
| 157 | 157 | free(zManFile); |
| 158 | 158 | blob_reset(&hash); |
| 159 | + }else{ | |
| 160 | + if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){ | |
| 161 | + zManFile = mprintf("%smanifest", g.zLocalRoot); | |
| 162 | + unlink(zManFile); | |
| 163 | + free(zManFile); | |
| 164 | + } | |
| 165 | + if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){ | |
| 166 | + zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); | |
| 167 | + unlink(zManFile); | |
| 168 | + free(zManFile); | |
| 169 | + } | |
| 159 | 170 | } |
| 171 | + | |
| 160 | 172 | } |
| 161 | 173 | |
| 162 | 174 | /* |
| 163 | 175 | ** COMMAND: checkout |
| 164 | 176 | ** COMMAND: co |
| @@ -228,10 +240,11 @@ | ||
| 228 | 240 | } |
| 229 | 241 | db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); |
| 230 | 242 | if( !keepFlag ){ |
| 231 | 243 | vfile_to_disk(vid, 0, 1, promptFlag); |
| 232 | 244 | } |
| 245 | + checkout_set_all_exe(vid); | |
| 233 | 246 | manifest_to_disk(vid); |
| 234 | 247 | db_lset_int("checkout", vid); |
| 235 | 248 | undo_reset(); |
| 236 | 249 | db_multi_exec("DELETE FROM vmerge"); |
| 237 | 250 | if( !keepFlag ){ |
| 238 | 251 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -78,87 +78,99 @@ | |
| 78 | |
| 79 | /* |
| 80 | ** Load a vfile from a record ID. |
| 81 | */ |
| 82 | void load_vfile_from_rid(int vid){ |
| 83 | Blob manifest; |
| 84 | |
| 85 | if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){ |
| 86 | return; |
| 87 | } |
| 88 | content_get(vid, &manifest); |
| 89 | vfile_build(vid, &manifest); |
| 90 | blob_reset(&manifest); |
| 91 | } |
| 92 | |
| 93 | /* |
| 94 | ** Set or clear the vfile.isexe flag for a file. |
| 95 | */ |
| 96 | static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ |
| 97 | db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q", |
| 98 | onoff, vid, zFilename); |
| 99 | } |
| 100 | |
| 101 | /* |
| 102 | ** Set or clear the execute permission bit (as appropriate) for all |
| 103 | ** files in the current check-out. |
| 104 | ** |
| 105 | ** If the checkout does not have explicit files named "manifest" and |
| 106 | ** "manifest.uuid" then automatically generate files with those names |
| 107 | ** containing, respectively, the text of the manifest and the artifact |
| 108 | ** ID of the manifest. |
| 109 | */ |
| 110 | void manifest_to_disk(int vid){ |
| 111 | char *zManFile; |
| 112 | Blob manifest; |
| 113 | Blob hash; |
| 114 | Blob filename; |
| 115 | int baseLen; |
| 116 | int i; |
| 117 | int seenManifest = 0; |
| 118 | int seenManifestUuid = 0; |
| 119 | Manifest m; |
| 120 | |
| 121 | /* Check the EXE permission status of all files |
| 122 | */ |
| 123 | blob_zero(&manifest); |
| 124 | content_get(vid, &manifest); |
| 125 | manifest_parse(&m, &manifest); |
| 126 | blob_zero(&filename); |
| 127 | blob_appendf(&filename, "%s/", g.zLocalRoot); |
| 128 | baseLen = blob_size(&filename); |
| 129 | for(i=0; i<m.nFile; i++){ |
| 130 | int isExe; |
| 131 | blob_append(&filename, m.aFile[i].zName, -1); |
| 132 | isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x"); |
| 133 | file_setexe(blob_str(&filename), isExe); |
| 134 | set_or_clear_isexe(m.aFile[i].zName, vid, isExe); |
| 135 | blob_resize(&filename, baseLen); |
| 136 | if( memcmp(m.aFile[i].zName, "manifest", 8)==0 ){ |
| 137 | if( m.aFile[i].zName[8]==0 ) seenManifest = 1; |
| 138 | if( strcmp(&m.aFile[i].zName[8], ".uuid")==0 ) seenManifestUuid = 1; |
| 139 | } |
| 140 | } |
| 141 | blob_reset(&filename); |
| 142 | manifest_clear(&m); |
| 143 | |
| 144 | blob_zero(&manifest); |
| 145 | content_get(vid, &manifest); |
| 146 | if( !seenManifest ){ |
| 147 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 148 | blob_write_to_file(&manifest, zManFile); |
| 149 | free(zManFile); |
| 150 | } |
| 151 | if( !seenManifestUuid ){ |
| 152 | blob_zero(&hash); |
| 153 | sha1sum_blob(&manifest, &hash); |
| 154 | zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 155 | blob_append(&hash, "\n", 1); |
| 156 | blob_write_to_file(&hash, zManFile); |
| 157 | free(zManFile); |
| 158 | blob_reset(&hash); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | /* |
| 163 | ** COMMAND: checkout |
| 164 | ** COMMAND: co |
| @@ -228,10 +240,11 @@ | |
| 228 | } |
| 229 | db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); |
| 230 | if( !keepFlag ){ |
| 231 | vfile_to_disk(vid, 0, 1, promptFlag); |
| 232 | } |
| 233 | manifest_to_disk(vid); |
| 234 | db_lset_int("checkout", vid); |
| 235 | undo_reset(); |
| 236 | db_multi_exec("DELETE FROM vmerge"); |
| 237 | if( !keepFlag ){ |
| 238 |
| --- src/checkout.c | |
| +++ src/checkout.c | |
| @@ -78,87 +78,99 @@ | |
| 78 | |
| 79 | /* |
| 80 | ** Load a vfile from a record ID. |
| 81 | */ |
| 82 | void load_vfile_from_rid(int vid){ |
| 83 | if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){ |
| 84 | return; |
| 85 | } |
| 86 | vfile_build(vid); |
| 87 | } |
| 88 | |
| 89 | /* |
| 90 | ** Set or clear the vfile.isexe flag for a file. |
| 91 | */ |
| 92 | static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){ |
| 93 | static Stmt s; |
| 94 | db_static_prepare(&s, |
| 95 | "UPDATE vfile SET isexe=:isexe" |
| 96 | " WHERE vid=:vid AND pathname=:path AND isexe!=:isexe" |
| 97 | ); |
| 98 | db_bind_int(&s, ":isexe", onoff); |
| 99 | db_bind_int(&s, ":vid", vid); |
| 100 | db_bind_text(&s, ":path", zFilename); |
| 101 | db_step(&s); |
| 102 | db_reset(&s); |
| 103 | } |
| 104 | |
| 105 | /* |
| 106 | ** Set or clear the execute permission bit (as appropriate) for all |
| 107 | ** files in the current check-out. |
| 108 | */ |
| 109 | void checkout_set_all_exe(int vid){ |
| 110 | Blob filename; |
| 111 | int baseLen; |
| 112 | Manifest *pManifest; |
| 113 | ManifestFile *pFile; |
| 114 | |
| 115 | /* Check the EXE permission status of all files |
| 116 | */ |
| 117 | pManifest = manifest_get(vid, CFTYPE_MANIFEST); |
| 118 | if( pManifest==0 ) return; |
| 119 | blob_zero(&filename); |
| 120 | blob_appendf(&filename, "%s/", g.zLocalRoot); |
| 121 | baseLen = blob_size(&filename); |
| 122 | manifest_file_rewind(pManifest); |
| 123 | while( (pFile = manifest_file_next(pManifest, 0))!=0 ){ |
| 124 | int isExe; |
| 125 | blob_append(&filename, pFile->zName, -1); |
| 126 | isExe = pFile->zPerm && strstr(pFile->zPerm, "x"); |
| 127 | file_setexe(blob_str(&filename), isExe); |
| 128 | set_or_clear_isexe(pFile->zName, vid, isExe); |
| 129 | blob_resize(&filename, baseLen); |
| 130 | } |
| 131 | blob_reset(&filename); |
| 132 | manifest_destroy(pManifest); |
| 133 | } |
| 134 | |
| 135 | |
| 136 | /* |
| 137 | ** If the "manifest" setting is true, then automatically generate |
| 138 | ** files named "manifest" and "manifest.uuid" containing, respectively, |
| 139 | ** the text of the manifest and the artifact ID of the manifest. |
| 140 | */ |
| 141 | void manifest_to_disk(int vid){ |
| 142 | char *zManFile; |
| 143 | Blob manifest; |
| 144 | Blob hash; |
| 145 | |
| 146 | if( db_get_boolean("manifest",0) ){ |
| 147 | blob_zero(&manifest); |
| 148 | content_get(vid, &manifest); |
| 149 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 150 | blob_write_to_file(&manifest, zManFile); |
| 151 | free(zManFile); |
| 152 | blob_zero(&hash); |
| 153 | sha1sum_blob(&manifest, &hash); |
| 154 | zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 155 | blob_append(&hash, "\n", 1); |
| 156 | blob_write_to_file(&hash, zManFile); |
| 157 | free(zManFile); |
| 158 | blob_reset(&hash); |
| 159 | }else{ |
| 160 | if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){ |
| 161 | zManFile = mprintf("%smanifest", g.zLocalRoot); |
| 162 | unlink(zManFile); |
| 163 | free(zManFile); |
| 164 | } |
| 165 | if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){ |
| 166 | zManFile = mprintf("%smanifest.uuid", g.zLocalRoot); |
| 167 | unlink(zManFile); |
| 168 | free(zManFile); |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | } |
| 173 | |
| 174 | /* |
| 175 | ** COMMAND: checkout |
| 176 | ** COMMAND: co |
| @@ -228,10 +240,11 @@ | |
| 240 | } |
| 241 | db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); |
| 242 | if( !keepFlag ){ |
| 243 | vfile_to_disk(vid, 0, 1, promptFlag); |
| 244 | } |
| 245 | checkout_set_all_exe(vid); |
| 246 | manifest_to_disk(vid); |
| 247 | db_lset_int("checkout", vid); |
| 248 | undo_reset(); |
| 249 | db_multi_exec("DELETE FROM vmerge"); |
| 250 | if( !keepFlag ){ |
| 251 |
+1
| --- src/configure.c | ||
| +++ src/configure.c | ||
| @@ -74,10 +74,11 @@ | ||
| 74 | 74 | { "footer", CONFIGSET_SKIN }, |
| 75 | 75 | { "logo-mimetype", CONFIGSET_SKIN }, |
| 76 | 76 | { "logo-image", CONFIGSET_SKIN }, |
| 77 | 77 | { "project-name", CONFIGSET_PROJ }, |
| 78 | 78 | { "project-description", CONFIGSET_PROJ }, |
| 79 | + { "manifest", CONFIGSET_PROJ }, | |
| 79 | 80 | { "index-page", CONFIGSET_SKIN }, |
| 80 | 81 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 81 | 82 | { "timeline-max-comment", CONFIGSET_SKIN }, |
| 82 | 83 | { "ticket-table", CONFIGSET_TKT }, |
| 83 | 84 | { "ticket-common", CONFIGSET_TKT }, |
| 84 | 85 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -74,10 +74,11 @@ | |
| 74 | { "footer", CONFIGSET_SKIN }, |
| 75 | { "logo-mimetype", CONFIGSET_SKIN }, |
| 76 | { "logo-image", CONFIGSET_SKIN }, |
| 77 | { "project-name", CONFIGSET_PROJ }, |
| 78 | { "project-description", CONFIGSET_PROJ }, |
| 79 | { "index-page", CONFIGSET_SKIN }, |
| 80 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 81 | { "timeline-max-comment", CONFIGSET_SKIN }, |
| 82 | { "ticket-table", CONFIGSET_TKT }, |
| 83 | { "ticket-common", CONFIGSET_TKT }, |
| 84 |
| --- src/configure.c | |
| +++ src/configure.c | |
| @@ -74,10 +74,11 @@ | |
| 74 | { "footer", CONFIGSET_SKIN }, |
| 75 | { "logo-mimetype", CONFIGSET_SKIN }, |
| 76 | { "logo-image", CONFIGSET_SKIN }, |
| 77 | { "project-name", CONFIGSET_PROJ }, |
| 78 | { "project-description", CONFIGSET_PROJ }, |
| 79 | { "manifest", CONFIGSET_PROJ }, |
| 80 | { "index-page", CONFIGSET_SKIN }, |
| 81 | { "timeline-block-markup", CONFIGSET_SKIN }, |
| 82 | { "timeline-max-comment", CONFIGSET_SKIN }, |
| 83 | { "ticket-table", CONFIGSET_TKT }, |
| 84 | { "ticket-common", CONFIGSET_TKT }, |
| 85 |
M
src/db.c
+32
-10
| --- src/db.c | ||
| +++ src/db.c | ||
| @@ -1510,25 +1510,27 @@ | ||
| 1510 | 1510 | int width; /* Width of display. 0 for boolean values */ |
| 1511 | 1511 | char const *def; /* Default value */ |
| 1512 | 1512 | }; |
| 1513 | 1513 | #endif /* INTERFACE */ |
| 1514 | 1514 | struct stControlSettings const ctrlSettings[] = { |
| 1515 | - { "auto-captcha", "autocaptcha", 0, "0" }, | |
| 1516 | - { "auto-shun", 0, 0, "1" }, | |
| 1517 | - { "autosync", 0, 0, "0" }, | |
| 1518 | - { "binary-glob", 0, 0, "1" }, | |
| 1519 | - { "clearsign", 0, 0, "0" }, | |
| 1520 | - { "diff-command", 0, 16, "diff" }, | |
| 1521 | - { "dont-push", 0, 0, "0" }, | |
| 1515 | + { "auto-captcha", "autocaptcha", 0, "on" }, | |
| 1516 | + { "auto-shun", 0, 0, "on" }, | |
| 1517 | + { "autosync", 0, 0, "on" }, | |
| 1518 | + { "binary-glob", 0, 32, "" }, | |
| 1519 | + { "clearsign", 0, 0, "off" }, | |
| 1520 | + { "diff-command", 0, 16, "" }, | |
| 1521 | + { "dont-push", 0, 0, "off" }, | |
| 1522 | 1522 | { "editor", 0, 16, "" }, |
| 1523 | 1523 | { "gdiff-command", 0, 16, "gdiff" }, |
| 1524 | 1524 | { "ignore-glob", 0, 40, "" }, |
| 1525 | 1525 | { "http-port", 0, 16, "8080" }, |
| 1526 | - { "localauth", 0, 0, "0" }, | |
| 1527 | - { "mtime-changes", 0, 0, "0" }, | |
| 1526 | + { "localauth", 0, 0, "off" }, | |
| 1527 | + { "manifest", 0, 0, "off" }, | |
| 1528 | + { "mtime-changes", 0, 0, "off" }, | |
| 1528 | 1529 | { "pgp-command", 0, 32, "gpg --clearsign -o " }, |
| 1529 | 1530 | { "proxy", 0, 32, "off" }, |
| 1531 | + { "repo-cksum", 0, 0, "on" }, | |
| 1530 | 1532 | { "ssh-command", 0, 32, "" }, |
| 1531 | 1533 | { "web-browser", 0, 32, "" }, |
| 1532 | 1534 | { 0,0,0,0 } |
| 1533 | 1535 | }; |
| 1534 | 1536 | |
| @@ -1548,23 +1550,25 @@ | ||
| 1548 | 1550 | ** auto-captcha If enabled, the Login page provides a button to |
| 1549 | 1551 | ** fill in the captcha password. Default: on |
| 1550 | 1552 | ** |
| 1551 | 1553 | ** auto-shun If enabled, automatically pull the shunning list |
| 1552 | 1554 | ** from a server to which the client autosyncs. |
| 1555 | +** Default: on | |
| 1553 | 1556 | ** |
| 1554 | 1557 | ** autosync If enabled, automatically pull prior to commit |
| 1555 | 1558 | ** or update and automatically push after commit or |
| 1556 | 1559 | ** tag or branch creation. If the value is "pullonly" |
| 1557 | 1560 | ** then only pull operations occur automatically. |
| 1561 | +** Default: on | |
| 1558 | 1562 | ** |
| 1559 | 1563 | ** binary-glob The VALUE is a comma-separated list of GLOB patterns |
| 1560 | 1564 | ** that should be treated as binary files for merging |
| 1561 | 1565 | ** purposes. Example: *.xml |
| 1562 | 1566 | ** |
| 1563 | 1567 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1564 | 1568 | ** with gpg. When disabled (the default), commits will |
| 1565 | -** be unsigned. | |
| 1569 | +** be unsigned. Default: off | |
| 1566 | 1570 | ** |
| 1567 | 1571 | ** diff-command External command to run when performing a diff. |
| 1568 | 1572 | ** If undefined, the internal text diff will be used. |
| 1569 | 1573 | ** |
| 1570 | 1574 | ** dont-push Prevent this repository from pushing from client to |
| @@ -1585,10 +1589,14 @@ | ||
| 1585 | 1589 | ** localauth If enabled, require that HTTP connections from |
| 1586 | 1590 | ** 127.0.0.1 be authenticated by password. If |
| 1587 | 1591 | ** false, all HTTP requests from localhost have |
| 1588 | 1592 | ** unrestricted access to the repository. |
| 1589 | 1593 | ** |
| 1594 | +** manifest If enabled, automatically create files "manifest" and | |
| 1595 | +** "manifest.uuid" in every checkout. The SQLite and | |
| 1596 | +** Fossil repositories both require this. Default: off. | |
| 1597 | +** | |
| 1590 | 1598 | ** mtime-changes Use file modification times (mtimes) to detect when |
| 1591 | 1599 | ** files have been modified. (Default "on".) |
| 1592 | 1600 | ** |
| 1593 | 1601 | ** pgp-command Command used to clear-sign manifests at check-in. |
| 1594 | 1602 | ** The default is "gpg --clearsign -o ". |
| @@ -1595,10 +1603,15 @@ | ||
| 1595 | 1603 | ** |
| 1596 | 1604 | ** proxy URL of the HTTP proxy. If undefined or "off" then |
| 1597 | 1605 | ** the "http_proxy" environment variable is consulted. |
| 1598 | 1606 | ** If the http_proxy environment variable is undefined |
| 1599 | 1607 | ** then a direct HTTP connection is used. |
| 1608 | +** | |
| 1609 | +** repo-cksum Compute checksums over all files in each checkout | |
| 1610 | +** as a double-check of correctness. Defaults to "on". | |
| 1611 | +** Disable on large repositories for a performance | |
| 1612 | +** improvement. | |
| 1600 | 1613 | ** |
| 1601 | 1614 | ** ssh-command Command used to talk to a remote machine with |
| 1602 | 1615 | ** the "ssh://" protocol. |
| 1603 | 1616 | ** |
| 1604 | 1617 | ** web-browser A shell command used to launch your preferred |
| @@ -1622,24 +1635,33 @@ | ||
| 1622 | 1635 | for(i=0; ctrlSettings[i].name; i++){ |
| 1623 | 1636 | print_setting(ctrlSettings[i].name); |
| 1624 | 1637 | } |
| 1625 | 1638 | }else if( g.argc==3 || g.argc==4 ){ |
| 1626 | 1639 | const char *zName = g.argv[2]; |
| 1640 | + int isManifest; | |
| 1627 | 1641 | int n = strlen(zName); |
| 1628 | 1642 | for(i=0; ctrlSettings[i].name; i++){ |
| 1629 | 1643 | if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break; |
| 1630 | 1644 | } |
| 1631 | 1645 | if( !ctrlSettings[i].name ){ |
| 1632 | 1646 | fossil_fatal("no such setting: %s", zName); |
| 1633 | 1647 | } |
| 1648 | + isManifest = strcmp(ctrlSettings[i].name, "manifest")==0; | |
| 1649 | + if( isManifest && globalFlag ){ | |
| 1650 | + fossil_fatal("cannot set 'manifest' globally"); | |
| 1651 | + } | |
| 1634 | 1652 | if( unsetFlag ){ |
| 1635 | 1653 | db_unset(ctrlSettings[i].name, globalFlag); |
| 1636 | 1654 | }else if( g.argc==4 ){ |
| 1637 | 1655 | db_set(ctrlSettings[i].name, g.argv[3], globalFlag); |
| 1638 | 1656 | }else{ |
| 1657 | + isManifest = 0; | |
| 1639 | 1658 | print_setting(ctrlSettings[i].name); |
| 1640 | 1659 | } |
| 1660 | + if( isManifest ){ | |
| 1661 | + manifest_to_disk(db_lget_int("checkout", 0)); | |
| 1662 | + } | |
| 1641 | 1663 | }else{ |
| 1642 | 1664 | usage("?PROPERTY? ?VALUE?"); |
| 1643 | 1665 | } |
| 1644 | 1666 | } |
| 1645 | 1667 | |
| 1646 | 1668 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1510,25 +1510,27 @@ | |
| 1510 | int width; /* Width of display. 0 for boolean values */ |
| 1511 | char const *def; /* Default value */ |
| 1512 | }; |
| 1513 | #endif /* INTERFACE */ |
| 1514 | struct stControlSettings const ctrlSettings[] = { |
| 1515 | { "auto-captcha", "autocaptcha", 0, "0" }, |
| 1516 | { "auto-shun", 0, 0, "1" }, |
| 1517 | { "autosync", 0, 0, "0" }, |
| 1518 | { "binary-glob", 0, 0, "1" }, |
| 1519 | { "clearsign", 0, 0, "0" }, |
| 1520 | { "diff-command", 0, 16, "diff" }, |
| 1521 | { "dont-push", 0, 0, "0" }, |
| 1522 | { "editor", 0, 16, "" }, |
| 1523 | { "gdiff-command", 0, 16, "gdiff" }, |
| 1524 | { "ignore-glob", 0, 40, "" }, |
| 1525 | { "http-port", 0, 16, "8080" }, |
| 1526 | { "localauth", 0, 0, "0" }, |
| 1527 | { "mtime-changes", 0, 0, "0" }, |
| 1528 | { "pgp-command", 0, 32, "gpg --clearsign -o " }, |
| 1529 | { "proxy", 0, 32, "off" }, |
| 1530 | { "ssh-command", 0, 32, "" }, |
| 1531 | { "web-browser", 0, 32, "" }, |
| 1532 | { 0,0,0,0 } |
| 1533 | }; |
| 1534 | |
| @@ -1548,23 +1550,25 @@ | |
| 1548 | ** auto-captcha If enabled, the Login page provides a button to |
| 1549 | ** fill in the captcha password. Default: on |
| 1550 | ** |
| 1551 | ** auto-shun If enabled, automatically pull the shunning list |
| 1552 | ** from a server to which the client autosyncs. |
| 1553 | ** |
| 1554 | ** autosync If enabled, automatically pull prior to commit |
| 1555 | ** or update and automatically push after commit or |
| 1556 | ** tag or branch creation. If the value is "pullonly" |
| 1557 | ** then only pull operations occur automatically. |
| 1558 | ** |
| 1559 | ** binary-glob The VALUE is a comma-separated list of GLOB patterns |
| 1560 | ** that should be treated as binary files for merging |
| 1561 | ** purposes. Example: *.xml |
| 1562 | ** |
| 1563 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1564 | ** with gpg. When disabled (the default), commits will |
| 1565 | ** be unsigned. |
| 1566 | ** |
| 1567 | ** diff-command External command to run when performing a diff. |
| 1568 | ** If undefined, the internal text diff will be used. |
| 1569 | ** |
| 1570 | ** dont-push Prevent this repository from pushing from client to |
| @@ -1585,10 +1589,14 @@ | |
| 1585 | ** localauth If enabled, require that HTTP connections from |
| 1586 | ** 127.0.0.1 be authenticated by password. If |
| 1587 | ** false, all HTTP requests from localhost have |
| 1588 | ** unrestricted access to the repository. |
| 1589 | ** |
| 1590 | ** mtime-changes Use file modification times (mtimes) to detect when |
| 1591 | ** files have been modified. (Default "on".) |
| 1592 | ** |
| 1593 | ** pgp-command Command used to clear-sign manifests at check-in. |
| 1594 | ** The default is "gpg --clearsign -o ". |
| @@ -1595,10 +1603,15 @@ | |
| 1595 | ** |
| 1596 | ** proxy URL of the HTTP proxy. If undefined or "off" then |
| 1597 | ** the "http_proxy" environment variable is consulted. |
| 1598 | ** If the http_proxy environment variable is undefined |
| 1599 | ** then a direct HTTP connection is used. |
| 1600 | ** |
| 1601 | ** ssh-command Command used to talk to a remote machine with |
| 1602 | ** the "ssh://" protocol. |
| 1603 | ** |
| 1604 | ** web-browser A shell command used to launch your preferred |
| @@ -1622,24 +1635,33 @@ | |
| 1622 | for(i=0; ctrlSettings[i].name; i++){ |
| 1623 | print_setting(ctrlSettings[i].name); |
| 1624 | } |
| 1625 | }else if( g.argc==3 || g.argc==4 ){ |
| 1626 | const char *zName = g.argv[2]; |
| 1627 | int n = strlen(zName); |
| 1628 | for(i=0; ctrlSettings[i].name; i++){ |
| 1629 | if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break; |
| 1630 | } |
| 1631 | if( !ctrlSettings[i].name ){ |
| 1632 | fossil_fatal("no such setting: %s", zName); |
| 1633 | } |
| 1634 | if( unsetFlag ){ |
| 1635 | db_unset(ctrlSettings[i].name, globalFlag); |
| 1636 | }else if( g.argc==4 ){ |
| 1637 | db_set(ctrlSettings[i].name, g.argv[3], globalFlag); |
| 1638 | }else{ |
| 1639 | print_setting(ctrlSettings[i].name); |
| 1640 | } |
| 1641 | }else{ |
| 1642 | usage("?PROPERTY? ?VALUE?"); |
| 1643 | } |
| 1644 | } |
| 1645 | |
| 1646 |
| --- src/db.c | |
| +++ src/db.c | |
| @@ -1510,25 +1510,27 @@ | |
| 1510 | int width; /* Width of display. 0 for boolean values */ |
| 1511 | char const *def; /* Default value */ |
| 1512 | }; |
| 1513 | #endif /* INTERFACE */ |
| 1514 | struct stControlSettings const ctrlSettings[] = { |
| 1515 | { "auto-captcha", "autocaptcha", 0, "on" }, |
| 1516 | { "auto-shun", 0, 0, "on" }, |
| 1517 | { "autosync", 0, 0, "on" }, |
| 1518 | { "binary-glob", 0, 32, "" }, |
| 1519 | { "clearsign", 0, 0, "off" }, |
| 1520 | { "diff-command", 0, 16, "" }, |
| 1521 | { "dont-push", 0, 0, "off" }, |
| 1522 | { "editor", 0, 16, "" }, |
| 1523 | { "gdiff-command", 0, 16, "gdiff" }, |
| 1524 | { "ignore-glob", 0, 40, "" }, |
| 1525 | { "http-port", 0, 16, "8080" }, |
| 1526 | { "localauth", 0, 0, "off" }, |
| 1527 | { "manifest", 0, 0, "off" }, |
| 1528 | { "mtime-changes", 0, 0, "off" }, |
| 1529 | { "pgp-command", 0, 32, "gpg --clearsign -o " }, |
| 1530 | { "proxy", 0, 32, "off" }, |
| 1531 | { "repo-cksum", 0, 0, "on" }, |
| 1532 | { "ssh-command", 0, 32, "" }, |
| 1533 | { "web-browser", 0, 32, "" }, |
| 1534 | { 0,0,0,0 } |
| 1535 | }; |
| 1536 | |
| @@ -1548,23 +1550,25 @@ | |
| 1550 | ** auto-captcha If enabled, the Login page provides a button to |
| 1551 | ** fill in the captcha password. Default: on |
| 1552 | ** |
| 1553 | ** auto-shun If enabled, automatically pull the shunning list |
| 1554 | ** from a server to which the client autosyncs. |
| 1555 | ** Default: on |
| 1556 | ** |
| 1557 | ** autosync If enabled, automatically pull prior to commit |
| 1558 | ** or update and automatically push after commit or |
| 1559 | ** tag or branch creation. If the value is "pullonly" |
| 1560 | ** then only pull operations occur automatically. |
| 1561 | ** Default: on |
| 1562 | ** |
| 1563 | ** binary-glob The VALUE is a comma-separated list of GLOB patterns |
| 1564 | ** that should be treated as binary files for merging |
| 1565 | ** purposes. Example: *.xml |
| 1566 | ** |
| 1567 | ** clearsign When enabled, fossil will attempt to sign all commits |
| 1568 | ** with gpg. When disabled (the default), commits will |
| 1569 | ** be unsigned. Default: off |
| 1570 | ** |
| 1571 | ** diff-command External command to run when performing a diff. |
| 1572 | ** If undefined, the internal text diff will be used. |
| 1573 | ** |
| 1574 | ** dont-push Prevent this repository from pushing from client to |
| @@ -1585,10 +1589,14 @@ | |
| 1589 | ** localauth If enabled, require that HTTP connections from |
| 1590 | ** 127.0.0.1 be authenticated by password. If |
| 1591 | ** false, all HTTP requests from localhost have |
| 1592 | ** unrestricted access to the repository. |
| 1593 | ** |
| 1594 | ** manifest If enabled, automatically create files "manifest" and |
| 1595 | ** "manifest.uuid" in every checkout. The SQLite and |
| 1596 | ** Fossil repositories both require this. Default: off. |
| 1597 | ** |
| 1598 | ** mtime-changes Use file modification times (mtimes) to detect when |
| 1599 | ** files have been modified. (Default "on".) |
| 1600 | ** |
| 1601 | ** pgp-command Command used to clear-sign manifests at check-in. |
| 1602 | ** The default is "gpg --clearsign -o ". |
| @@ -1595,10 +1603,15 @@ | |
| 1603 | ** |
| 1604 | ** proxy URL of the HTTP proxy. If undefined or "off" then |
| 1605 | ** the "http_proxy" environment variable is consulted. |
| 1606 | ** If the http_proxy environment variable is undefined |
| 1607 | ** then a direct HTTP connection is used. |
| 1608 | ** |
| 1609 | ** repo-cksum Compute checksums over all files in each checkout |
| 1610 | ** as a double-check of correctness. Defaults to "on". |
| 1611 | ** Disable on large repositories for a performance |
| 1612 | ** improvement. |
| 1613 | ** |
| 1614 | ** ssh-command Command used to talk to a remote machine with |
| 1615 | ** the "ssh://" protocol. |
| 1616 | ** |
| 1617 | ** web-browser A shell command used to launch your preferred |
| @@ -1622,24 +1635,33 @@ | |
| 1635 | for(i=0; ctrlSettings[i].name; i++){ |
| 1636 | print_setting(ctrlSettings[i].name); |
| 1637 | } |
| 1638 | }else if( g.argc==3 || g.argc==4 ){ |
| 1639 | const char *zName = g.argv[2]; |
| 1640 | int isManifest; |
| 1641 | int n = strlen(zName); |
| 1642 | for(i=0; ctrlSettings[i].name; i++){ |
| 1643 | if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break; |
| 1644 | } |
| 1645 | if( !ctrlSettings[i].name ){ |
| 1646 | fossil_fatal("no such setting: %s", zName); |
| 1647 | } |
| 1648 | isManifest = strcmp(ctrlSettings[i].name, "manifest")==0; |
| 1649 | if( isManifest && globalFlag ){ |
| 1650 | fossil_fatal("cannot set 'manifest' globally"); |
| 1651 | } |
| 1652 | if( unsetFlag ){ |
| 1653 | db_unset(ctrlSettings[i].name, globalFlag); |
| 1654 | }else if( g.argc==4 ){ |
| 1655 | db_set(ctrlSettings[i].name, g.argv[3], globalFlag); |
| 1656 | }else{ |
| 1657 | isManifest = 0; |
| 1658 | print_setting(ctrlSettings[i].name); |
| 1659 | } |
| 1660 | if( isManifest ){ |
| 1661 | manifest_to_disk(db_lget_int("checkout", 0)); |
| 1662 | } |
| 1663 | }else{ |
| 1664 | usage("?PROPERTY? ?VALUE?"); |
| 1665 | } |
| 1666 | } |
| 1667 | |
| 1668 |
+21
-10
| --- src/delta.c | ||
| +++ src/delta.c | ||
| @@ -195,31 +195,38 @@ | ||
| 195 | 195 | /* |
| 196 | 196 | ** Compute a 32-bit checksum on the N-byte buffer. Return the result. |
| 197 | 197 | */ |
| 198 | 198 | static unsigned int checksum(const char *zIn, size_t N){ |
| 199 | 199 | const unsigned char *z = (const unsigned char *)zIn; |
| 200 | - unsigned sum = 0; | |
| 200 | + unsigned sum0 = 0; | |
| 201 | + unsigned sum1 = 0; | |
| 202 | + unsigned sum2 = 0; | |
| 203 | + unsigned sum3 = 0; | |
| 201 | 204 | while(N >= 16){ |
| 202 | - sum += ((unsigned)z[0] + z[4] + z[8] + z[12]) << 24; | |
| 203 | - sum += ((unsigned)z[1] + z[5] + z[9] + z[13]) << 16; | |
| 204 | - sum += ((unsigned)z[2] + z[6] + z[10]+ z[14]) << 8; | |
| 205 | - sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]); | |
| 205 | + sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); | |
| 206 | + sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); | |
| 207 | + sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); | |
| 208 | + sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]); | |
| 206 | 209 | z += 16; |
| 207 | 210 | N -= 16; |
| 208 | 211 | } |
| 209 | 212 | while(N >= 4){ |
| 210 | - sum += (z[0]<<24) | (z[1]<<16) | (z[2]<<8) | z[3]; | |
| 213 | + sum0 += z[0]; | |
| 214 | + sum1 += z[1]; | |
| 215 | + sum2 += z[2]; | |
| 216 | + sum3 += z[3]; | |
| 211 | 217 | z += 4; |
| 212 | 218 | N -= 4; |
| 213 | 219 | } |
| 220 | + sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); | |
| 214 | 221 | switch(N){ |
| 215 | - case 3: sum += (z[2] << 8); | |
| 216 | - case 2: sum += (z[1] << 16); | |
| 217 | - case 1: sum += (z[0] << 24); | |
| 222 | + case 3: sum3 += (z[2] << 8); | |
| 223 | + case 2: sum3 += (z[1] << 16); | |
| 224 | + case 1: sum3 += (z[0] << 24); | |
| 218 | 225 | default: ; |
| 219 | 226 | } |
| 220 | - return sum; | |
| 227 | + return sum3; | |
| 221 | 228 | } |
| 222 | 229 | |
| 223 | 230 | /* |
| 224 | 231 | ** Create a new delta. |
| 225 | 232 | ** |
| @@ -512,11 +519,13 @@ | ||
| 512 | 519 | int lenDelta, /* Length of the delta */ |
| 513 | 520 | char *zOut /* Write the output into this preallocated buffer */ |
| 514 | 521 | ){ |
| 515 | 522 | unsigned int limit; |
| 516 | 523 | unsigned int total = 0; |
| 524 | +#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST | |
| 517 | 525 | char *zOrigOut = zOut; |
| 526 | +#endif | |
| 518 | 527 | |
| 519 | 528 | limit = getInt(&zDelta, &lenDelta); |
| 520 | 529 | if( *zDelta!='\n' ){ |
| 521 | 530 | /* ERROR: size integer not terminated by "\n" */ |
| 522 | 531 | return -1; |
| @@ -567,14 +576,16 @@ | ||
| 567 | 576 | break; |
| 568 | 577 | } |
| 569 | 578 | case ';': { |
| 570 | 579 | zDelta++; lenDelta--; |
| 571 | 580 | zOut[0] = 0; |
| 581 | +#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST | |
| 572 | 582 | if( cnt!=checksum(zOrigOut, total) ){ |
| 573 | 583 | /* ERROR: bad checksum */ |
| 574 | 584 | return -1; |
| 575 | 585 | } |
| 586 | +#endif | |
| 576 | 587 | if( total!=limit ){ |
| 577 | 588 | /* ERROR: generated size does not match predicted size */ |
| 578 | 589 | return -1; |
| 579 | 590 | } |
| 580 | 591 | return total; |
| 581 | 592 |
| --- src/delta.c | |
| +++ src/delta.c | |
| @@ -195,31 +195,38 @@ | |
| 195 | /* |
| 196 | ** Compute a 32-bit checksum on the N-byte buffer. Return the result. |
| 197 | */ |
| 198 | static unsigned int checksum(const char *zIn, size_t N){ |
| 199 | const unsigned char *z = (const unsigned char *)zIn; |
| 200 | unsigned sum = 0; |
| 201 | while(N >= 16){ |
| 202 | sum += ((unsigned)z[0] + z[4] + z[8] + z[12]) << 24; |
| 203 | sum += ((unsigned)z[1] + z[5] + z[9] + z[13]) << 16; |
| 204 | sum += ((unsigned)z[2] + z[6] + z[10]+ z[14]) << 8; |
| 205 | sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]); |
| 206 | z += 16; |
| 207 | N -= 16; |
| 208 | } |
| 209 | while(N >= 4){ |
| 210 | sum += (z[0]<<24) | (z[1]<<16) | (z[2]<<8) | z[3]; |
| 211 | z += 4; |
| 212 | N -= 4; |
| 213 | } |
| 214 | switch(N){ |
| 215 | case 3: sum += (z[2] << 8); |
| 216 | case 2: sum += (z[1] << 16); |
| 217 | case 1: sum += (z[0] << 24); |
| 218 | default: ; |
| 219 | } |
| 220 | return sum; |
| 221 | } |
| 222 | |
| 223 | /* |
| 224 | ** Create a new delta. |
| 225 | ** |
| @@ -512,11 +519,13 @@ | |
| 512 | int lenDelta, /* Length of the delta */ |
| 513 | char *zOut /* Write the output into this preallocated buffer */ |
| 514 | ){ |
| 515 | unsigned int limit; |
| 516 | unsigned int total = 0; |
| 517 | char *zOrigOut = zOut; |
| 518 | |
| 519 | limit = getInt(&zDelta, &lenDelta); |
| 520 | if( *zDelta!='\n' ){ |
| 521 | /* ERROR: size integer not terminated by "\n" */ |
| 522 | return -1; |
| @@ -567,14 +576,16 @@ | |
| 567 | break; |
| 568 | } |
| 569 | case ';': { |
| 570 | zDelta++; lenDelta--; |
| 571 | zOut[0] = 0; |
| 572 | if( cnt!=checksum(zOrigOut, total) ){ |
| 573 | /* ERROR: bad checksum */ |
| 574 | return -1; |
| 575 | } |
| 576 | if( total!=limit ){ |
| 577 | /* ERROR: generated size does not match predicted size */ |
| 578 | return -1; |
| 579 | } |
| 580 | return total; |
| 581 |
| --- src/delta.c | |
| +++ src/delta.c | |
| @@ -195,31 +195,38 @@ | |
| 195 | /* |
| 196 | ** Compute a 32-bit checksum on the N-byte buffer. Return the result. |
| 197 | */ |
| 198 | static unsigned int checksum(const char *zIn, size_t N){ |
| 199 | const unsigned char *z = (const unsigned char *)zIn; |
| 200 | unsigned sum0 = 0; |
| 201 | unsigned sum1 = 0; |
| 202 | unsigned sum2 = 0; |
| 203 | unsigned sum3 = 0; |
| 204 | while(N >= 16){ |
| 205 | sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]); |
| 206 | sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]); |
| 207 | sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]); |
| 208 | sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]); |
| 209 | z += 16; |
| 210 | N -= 16; |
| 211 | } |
| 212 | while(N >= 4){ |
| 213 | sum0 += z[0]; |
| 214 | sum1 += z[1]; |
| 215 | sum2 += z[2]; |
| 216 | sum3 += z[3]; |
| 217 | z += 4; |
| 218 | N -= 4; |
| 219 | } |
| 220 | sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24); |
| 221 | switch(N){ |
| 222 | case 3: sum3 += (z[2] << 8); |
| 223 | case 2: sum3 += (z[1] << 16); |
| 224 | case 1: sum3 += (z[0] << 24); |
| 225 | default: ; |
| 226 | } |
| 227 | return sum3; |
| 228 | } |
| 229 | |
| 230 | /* |
| 231 | ** Create a new delta. |
| 232 | ** |
| @@ -512,11 +519,13 @@ | |
| 519 | int lenDelta, /* Length of the delta */ |
| 520 | char *zOut /* Write the output into this preallocated buffer */ |
| 521 | ){ |
| 522 | unsigned int limit; |
| 523 | unsigned int total = 0; |
| 524 | #ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST |
| 525 | char *zOrigOut = zOut; |
| 526 | #endif |
| 527 | |
| 528 | limit = getInt(&zDelta, &lenDelta); |
| 529 | if( *zDelta!='\n' ){ |
| 530 | /* ERROR: size integer not terminated by "\n" */ |
| 531 | return -1; |
| @@ -567,14 +576,16 @@ | |
| 576 | break; |
| 577 | } |
| 578 | case ';': { |
| 579 | zDelta++; lenDelta--; |
| 580 | zOut[0] = 0; |
| 581 | #ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST |
| 582 | if( cnt!=checksum(zOrigOut, total) ){ |
| 583 | /* ERROR: bad checksum */ |
| 584 | return -1; |
| 585 | } |
| 586 | #endif |
| 587 | if( total!=limit ){ |
| 588 | /* ERROR: generated size does not match predicted size */ |
| 589 | return -1; |
| 590 | } |
| 591 | return total; |
| 592 |
+37
-34
| --- src/diffcmd.c | ||
| +++ src/diffcmd.c | ||
| @@ -355,53 +355,56 @@ | ||
| 355 | 355 | const char *zFrom, |
| 356 | 356 | const char *zTo, |
| 357 | 357 | const char *zDiffCmd, |
| 358 | 358 | int diffFlags |
| 359 | 359 | ){ |
| 360 | - Manifest mFrom, mTo; | |
| 361 | - int iFrom, iTo; | |
| 360 | + Manifest *pFrom, *pTo; | |
| 361 | + ManifestFile *pFromFile, *pToFile; | |
| 362 | 362 | int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0; |
| 363 | 363 | int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; |
| 364 | 364 | |
| 365 | - manifest_from_name(zFrom, &mFrom); | |
| 366 | - manifest_from_name(zTo, &mTo); | |
| 367 | - iFrom = iTo = 0; | |
| 368 | - while( iFrom<mFrom.nFile || iTo<mTo.nFile ){ | |
| 365 | + pFrom = manifest_get_by_name(zFrom, 0); | |
| 366 | + manifest_file_rewind(pFrom); | |
| 367 | + pFromFile = manifest_file_next(pFrom,0); | |
| 368 | + pTo = manifest_get_by_name(zTo, 0); | |
| 369 | + manifest_file_rewind(pTo); | |
| 370 | + pToFile = manifest_file_next(pTo,0); | |
| 371 | + | |
| 372 | + while( pFromFile || pToFile ){ | |
| 369 | 373 | int cmp; |
| 370 | - if( iFrom>=mFrom.nFile ){ | |
| 374 | + if( pFromFile==0 ){ | |
| 371 | 375 | cmp = +1; |
| 372 | - }else if( iTo>=mTo.nFile ){ | |
| 376 | + }else if( pToFile==0 ){ | |
| 373 | 377 | cmp = -1; |
| 374 | 378 | }else{ |
| 375 | - cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName); | |
| 379 | + cmp = strcmp(pFromFile->zName, pToFile->zName); | |
| 376 | 380 | } |
| 377 | 381 | if( cmp<0 ){ |
| 378 | - printf("DELETED %s\n", mFrom.aFile[iFrom].zName); | |
| 379 | - if( asNewFlag ){ | |
| 380 | - diff_manifest_entry(&mFrom.aFile[iFrom], 0, zDiffCmd, ignoreEolWs); | |
| 381 | - } | |
| 382 | - iFrom++; | |
| 383 | - }else if( cmp>0 ){ | |
| 384 | - printf("ADDED %s\n", mTo.aFile[iTo].zName); | |
| 385 | - if( asNewFlag ){ | |
| 386 | - diff_manifest_entry(0, &mTo.aFile[iTo], zDiffCmd, ignoreEolWs); | |
| 387 | - } | |
| 388 | - iTo++; | |
| 389 | - }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){ | |
| 390 | - /* No changes */ | |
| 391 | - iFrom++; | |
| 392 | - iTo++; | |
| 393 | - }else{ | |
| 394 | - printf("CHANGED %s\n", mFrom.aFile[iFrom].zName); | |
| 395 | - diff_manifest_entry(&mFrom.aFile[iFrom], &mTo.aFile[iTo], | |
| 396 | - zDiffCmd, ignoreEolWs); | |
| 397 | - iFrom++; | |
| 398 | - iTo++; | |
| 399 | - } | |
| 400 | - } | |
| 401 | - manifest_clear(&mFrom); | |
| 402 | - manifest_clear(&mTo); | |
| 382 | + printf("DELETED %s\n", pFromFile->zName); | |
| 383 | + if( asNewFlag ){ | |
| 384 | + diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs); | |
| 385 | + } | |
| 386 | + pFromFile = manifest_file_next(pFrom,0); | |
| 387 | + }else if( cmp>0 ){ | |
| 388 | + printf("ADDED %s\n", pToFile->zName); | |
| 389 | + if( asNewFlag ){ | |
| 390 | + diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs); | |
| 391 | + } | |
| 392 | + pToFile = manifest_file_next(pTo,0); | |
| 393 | + }else if( strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ | |
| 394 | + /* No changes */ | |
| 395 | + pFromFile = manifest_file_next(pFrom,0); | |
| 396 | + pToFile = manifest_file_next(pTo,0); | |
| 397 | + }else{ | |
| 398 | + printf("CHANGED %s\n", pFromFile->zName); | |
| 399 | + diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs); | |
| 400 | + pFromFile = manifest_file_next(pFrom,0); | |
| 401 | + pToFile = manifest_file_next(pTo,0); | |
| 402 | + } | |
| 403 | + } | |
| 404 | + manifest_destroy(pFrom); | |
| 405 | + manifest_destroy(pTo); | |
| 403 | 406 | } |
| 404 | 407 | |
| 405 | 408 | /* |
| 406 | 409 | ** COMMAND: diff |
| 407 | 410 | ** COMMAND: gdiff |
| 408 | 411 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -355,53 +355,56 @@ | |
| 355 | const char *zFrom, |
| 356 | const char *zTo, |
| 357 | const char *zDiffCmd, |
| 358 | int diffFlags |
| 359 | ){ |
| 360 | Manifest mFrom, mTo; |
| 361 | int iFrom, iTo; |
| 362 | int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0; |
| 363 | int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; |
| 364 | |
| 365 | manifest_from_name(zFrom, &mFrom); |
| 366 | manifest_from_name(zTo, &mTo); |
| 367 | iFrom = iTo = 0; |
| 368 | while( iFrom<mFrom.nFile || iTo<mTo.nFile ){ |
| 369 | int cmp; |
| 370 | if( iFrom>=mFrom.nFile ){ |
| 371 | cmp = +1; |
| 372 | }else if( iTo>=mTo.nFile ){ |
| 373 | cmp = -1; |
| 374 | }else{ |
| 375 | cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName); |
| 376 | } |
| 377 | if( cmp<0 ){ |
| 378 | printf("DELETED %s\n", mFrom.aFile[iFrom].zName); |
| 379 | if( asNewFlag ){ |
| 380 | diff_manifest_entry(&mFrom.aFile[iFrom], 0, zDiffCmd, ignoreEolWs); |
| 381 | } |
| 382 | iFrom++; |
| 383 | }else if( cmp>0 ){ |
| 384 | printf("ADDED %s\n", mTo.aFile[iTo].zName); |
| 385 | if( asNewFlag ){ |
| 386 | diff_manifest_entry(0, &mTo.aFile[iTo], zDiffCmd, ignoreEolWs); |
| 387 | } |
| 388 | iTo++; |
| 389 | }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){ |
| 390 | /* No changes */ |
| 391 | iFrom++; |
| 392 | iTo++; |
| 393 | }else{ |
| 394 | printf("CHANGED %s\n", mFrom.aFile[iFrom].zName); |
| 395 | diff_manifest_entry(&mFrom.aFile[iFrom], &mTo.aFile[iTo], |
| 396 | zDiffCmd, ignoreEolWs); |
| 397 | iFrom++; |
| 398 | iTo++; |
| 399 | } |
| 400 | } |
| 401 | manifest_clear(&mFrom); |
| 402 | manifest_clear(&mTo); |
| 403 | } |
| 404 | |
| 405 | /* |
| 406 | ** COMMAND: diff |
| 407 | ** COMMAND: gdiff |
| 408 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -355,53 +355,56 @@ | |
| 355 | const char *zFrom, |
| 356 | const char *zTo, |
| 357 | const char *zDiffCmd, |
| 358 | int diffFlags |
| 359 | ){ |
| 360 | Manifest *pFrom, *pTo; |
| 361 | ManifestFile *pFromFile, *pToFile; |
| 362 | int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0; |
| 363 | int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0; |
| 364 | |
| 365 | pFrom = manifest_get_by_name(zFrom, 0); |
| 366 | manifest_file_rewind(pFrom); |
| 367 | pFromFile = manifest_file_next(pFrom,0); |
| 368 | pTo = manifest_get_by_name(zTo, 0); |
| 369 | manifest_file_rewind(pTo); |
| 370 | pToFile = manifest_file_next(pTo,0); |
| 371 | |
| 372 | while( pFromFile || pToFile ){ |
| 373 | int cmp; |
| 374 | if( pFromFile==0 ){ |
| 375 | cmp = +1; |
| 376 | }else if( pToFile==0 ){ |
| 377 | cmp = -1; |
| 378 | }else{ |
| 379 | cmp = strcmp(pFromFile->zName, pToFile->zName); |
| 380 | } |
| 381 | if( cmp<0 ){ |
| 382 | printf("DELETED %s\n", pFromFile->zName); |
| 383 | if( asNewFlag ){ |
| 384 | diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs); |
| 385 | } |
| 386 | pFromFile = manifest_file_next(pFrom,0); |
| 387 | }else if( cmp>0 ){ |
| 388 | printf("ADDED %s\n", pToFile->zName); |
| 389 | if( asNewFlag ){ |
| 390 | diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs); |
| 391 | } |
| 392 | pToFile = manifest_file_next(pTo,0); |
| 393 | }else if( strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){ |
| 394 | /* No changes */ |
| 395 | pFromFile = manifest_file_next(pFrom,0); |
| 396 | pToFile = manifest_file_next(pTo,0); |
| 397 | }else{ |
| 398 | printf("CHANGED %s\n", pFromFile->zName); |
| 399 | diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs); |
| 400 | pFromFile = manifest_file_next(pFrom,0); |
| 401 | pToFile = manifest_file_next(pTo,0); |
| 402 | } |
| 403 | } |
| 404 | manifest_destroy(pFrom); |
| 405 | manifest_destroy(pTo); |
| 406 | } |
| 407 | |
| 408 | /* |
| 409 | ** COMMAND: diff |
| 410 | ** COMMAND: gdiff |
| 411 |
+9
-10
| --- src/doc.c | ||
| +++ src/doc.c | ||
| @@ -388,37 +388,36 @@ | ||
| 388 | 388 | goto doc_not_found; |
| 389 | 389 | } |
| 390 | 390 | |
| 391 | 391 | if( rid==0 ){ |
| 392 | 392 | Stmt s; |
| 393 | - Blob baseline; | |
| 394 | - Manifest m; | |
| 393 | + Manifest *pM; | |
| 394 | + ManifestFile *pFile; | |
| 395 | 395 | |
| 396 | 396 | /* Add the vid baseline to the cache */ |
| 397 | 397 | if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ |
| 398 | 398 | db_multi_exec("DELETE FROM vcache"); |
| 399 | 399 | } |
| 400 | - if( content_get(vid, &baseline)==0 ){ | |
| 401 | - goto doc_not_found; | |
| 402 | - } | |
| 403 | - if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){ | |
| 400 | + pM = manifest_get(vid, CFTYPE_MANIFEST); | |
| 401 | + if( pM==0 ){ | |
| 404 | 402 | goto doc_not_found; |
| 405 | 403 | } |
| 406 | 404 | db_prepare(&s, |
| 407 | 405 | "INSERT INTO vcache(vid,fname,rid)" |
| 408 | 406 | " SELECT %d, :fname, rid FROM blob" |
| 409 | 407 | " WHERE uuid=:uuid", |
| 410 | 408 | vid |
| 411 | 409 | ); |
| 412 | - for(i=0; i<m.nFile; i++){ | |
| 413 | - db_bind_text(&s, ":fname", m.aFile[i].zName); | |
| 414 | - db_bind_text(&s, ":uuid", m.aFile[i].zUuid); | |
| 410 | + manifest_file_rewind(pM); | |
| 411 | + while( (pFile = manifest_file_next(pM,0))!=0 ){ | |
| 412 | + db_bind_text(&s, ":fname", pFile->zName); | |
| 413 | + db_bind_text(&s, ":uuid", pFile->zUuid); | |
| 415 | 414 | db_step(&s); |
| 416 | 415 | db_reset(&s); |
| 417 | 416 | } |
| 418 | 417 | db_finalize(&s); |
| 419 | - manifest_clear(&m); | |
| 418 | + manifest_destroy(pM); | |
| 420 | 419 | |
| 421 | 420 | /* Try again to find the file */ |
| 422 | 421 | rid = db_int(0, "SELECT rid FROM vcache" |
| 423 | 422 | " WHERE vid=%d AND fname=%Q", vid, zName); |
| 424 | 423 | } |
| 425 | 424 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -388,37 +388,36 @@ | |
| 388 | goto doc_not_found; |
| 389 | } |
| 390 | |
| 391 | if( rid==0 ){ |
| 392 | Stmt s; |
| 393 | Blob baseline; |
| 394 | Manifest m; |
| 395 | |
| 396 | /* Add the vid baseline to the cache */ |
| 397 | if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ |
| 398 | db_multi_exec("DELETE FROM vcache"); |
| 399 | } |
| 400 | if( content_get(vid, &baseline)==0 ){ |
| 401 | goto doc_not_found; |
| 402 | } |
| 403 | if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){ |
| 404 | goto doc_not_found; |
| 405 | } |
| 406 | db_prepare(&s, |
| 407 | "INSERT INTO vcache(vid,fname,rid)" |
| 408 | " SELECT %d, :fname, rid FROM blob" |
| 409 | " WHERE uuid=:uuid", |
| 410 | vid |
| 411 | ); |
| 412 | for(i=0; i<m.nFile; i++){ |
| 413 | db_bind_text(&s, ":fname", m.aFile[i].zName); |
| 414 | db_bind_text(&s, ":uuid", m.aFile[i].zUuid); |
| 415 | db_step(&s); |
| 416 | db_reset(&s); |
| 417 | } |
| 418 | db_finalize(&s); |
| 419 | manifest_clear(&m); |
| 420 | |
| 421 | /* Try again to find the file */ |
| 422 | rid = db_int(0, "SELECT rid FROM vcache" |
| 423 | " WHERE vid=%d AND fname=%Q", vid, zName); |
| 424 | } |
| 425 |
| --- src/doc.c | |
| +++ src/doc.c | |
| @@ -388,37 +388,36 @@ | |
| 388 | goto doc_not_found; |
| 389 | } |
| 390 | |
| 391 | if( rid==0 ){ |
| 392 | Stmt s; |
| 393 | Manifest *pM; |
| 394 | ManifestFile *pFile; |
| 395 | |
| 396 | /* Add the vid baseline to the cache */ |
| 397 | if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){ |
| 398 | db_multi_exec("DELETE FROM vcache"); |
| 399 | } |
| 400 | pM = manifest_get(vid, CFTYPE_MANIFEST); |
| 401 | if( pM==0 ){ |
| 402 | goto doc_not_found; |
| 403 | } |
| 404 | db_prepare(&s, |
| 405 | "INSERT INTO vcache(vid,fname,rid)" |
| 406 | " SELECT %d, :fname, rid FROM blob" |
| 407 | " WHERE uuid=:uuid", |
| 408 | vid |
| 409 | ); |
| 410 | manifest_file_rewind(pM); |
| 411 | while( (pFile = manifest_file_next(pM,0))!=0 ){ |
| 412 | db_bind_text(&s, ":fname", pFile->zName); |
| 413 | db_bind_text(&s, ":uuid", pFile->zUuid); |
| 414 | db_step(&s); |
| 415 | db_reset(&s); |
| 416 | } |
| 417 | db_finalize(&s); |
| 418 | manifest_destroy(pM); |
| 419 | |
| 420 | /* Try again to find the file */ |
| 421 | rid = db_int(0, "SELECT rid FROM vcache" |
| 422 | " WHERE vid=%d AND fname=%Q", vid, zName); |
| 423 | } |
| 424 |
+3
-2
| --- src/encode.c | ||
| +++ src/encode.c | ||
| @@ -267,12 +267,13 @@ | ||
| 267 | 267 | /* |
| 268 | 268 | ** Decode a fossilized string in-place. |
| 269 | 269 | */ |
| 270 | 270 | void defossilize(char *z){ |
| 271 | 271 | int i, j, c; |
| 272 | - for(i=j=0; z[i]; i++){ | |
| 273 | - c = z[i]; | |
| 272 | + for(i=0; (c=z[i])!=0 && c!='\\'; i++){} | |
| 273 | + if( c==0 ) return; | |
| 274 | + for(j=i; (c=z[i])!=0; i++){ | |
| 274 | 275 | if( c=='\\' && z[i+1] ){ |
| 275 | 276 | i++; |
| 276 | 277 | switch( z[i] ){ |
| 277 | 278 | case 'n': c = '\n'; break; |
| 278 | 279 | case 's': c = ' '; break; |
| 279 | 280 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -267,12 +267,13 @@ | |
| 267 | /* |
| 268 | ** Decode a fossilized string in-place. |
| 269 | */ |
| 270 | void defossilize(char *z){ |
| 271 | int i, j, c; |
| 272 | for(i=j=0; z[i]; i++){ |
| 273 | c = z[i]; |
| 274 | if( c=='\\' && z[i+1] ){ |
| 275 | i++; |
| 276 | switch( z[i] ){ |
| 277 | case 'n': c = '\n'; break; |
| 278 | case 's': c = ' '; break; |
| 279 |
| --- src/encode.c | |
| +++ src/encode.c | |
| @@ -267,12 +267,13 @@ | |
| 267 | /* |
| 268 | ** Decode a fossilized string in-place. |
| 269 | */ |
| 270 | void defossilize(char *z){ |
| 271 | int i, j, c; |
| 272 | for(i=0; (c=z[i])!=0 && c!='\\'; i++){} |
| 273 | if( c==0 ) return; |
| 274 | for(j=i; (c=z[i])!=0; i++){ |
| 275 | if( c=='\\' && z[i+1] ){ |
| 276 | i++; |
| 277 | switch( z[i] ){ |
| 278 | case 'n': c = '\n'; break; |
| 279 | case 's': c = ' '; break; |
| 280 |
+18
-26
| --- src/event.c | ||
| +++ src/event.c | ||
| @@ -63,12 +63,11 @@ | ||
| 63 | 63 | const char *zEventId; /* Event identifier */ |
| 64 | 64 | char *zETime; /* Time of the event */ |
| 65 | 65 | char *zATime; /* Time the artifact was created */ |
| 66 | 66 | int specRid; /* rid specified by aid= parameter */ |
| 67 | 67 | int prevRid, nextRid; /* Previous or next edits of this event */ |
| 68 | - Manifest m; /* Parsed event artifact */ | |
| 69 | - Blob content; /* Original event artifact content */ | |
| 68 | + Manifest *pEvent; /* Parsed event artifact */ | |
| 70 | 69 | Blob fullbody; /* Complete content of the event body */ |
| 71 | 70 | Blob title; /* Title extracted from the event body */ |
| 72 | 71 | Blob tail; /* Event body that comes after the title */ |
| 73 | 72 | Stmt q1; /* Query to search for the event */ |
| 74 | 73 | int showDetail; /* True to show details */ |
| @@ -113,18 +112,15 @@ | ||
| 113 | 112 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 114 | 113 | showDetail = atoi(PD("detail","0")); |
| 115 | 114 | |
| 116 | 115 | /* Extract the event content. |
| 117 | 116 | */ |
| 118 | - memset(&m, 0, sizeof(m)); | |
| 119 | - blob_zero(&m.content); | |
| 120 | - content_get(rid, &content); | |
| 121 | - manifest_parse(&m, &content); | |
| 122 | - if( m.type!=CFTYPE_EVENT ){ | |
| 117 | + pEvent = manifest_get(rid, CFTYPE_EVENT); | |
| 118 | + if( pEvent==0 ){ | |
| 123 | 119 | fossil_panic("Object #%d is not an event", rid); |
| 124 | 120 | } |
| 125 | - blob_init(&fullbody, m.zWiki, -1); | |
| 121 | + blob_init(&fullbody, pEvent->zWiki, -1); | |
| 126 | 122 | if( wiki_find_title(&fullbody, &title, &tail) ){ |
| 127 | 123 | style_header(blob_str(&title)); |
| 128 | 124 | }else{ |
| 129 | 125 | style_header("Event %S", zEventId); |
| 130 | 126 | tail = fullbody; |
| @@ -131,11 +127,11 @@ | ||
| 131 | 127 | } |
| 132 | 128 | if( g.okWrWiki && g.okWrite && nextRid==0 ){ |
| 133 | 129 | style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", |
| 134 | 130 | g.zTop, zEventId); |
| 135 | 131 | } |
| 136 | - zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate); | |
| 132 | + zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); | |
| 137 | 133 | style_submenu_element("Context", "Context", "%s/timeline?c=%T", |
| 138 | 134 | g.zTop, zETime); |
| 139 | 135 | if( g.okHistory ){ |
| 140 | 136 | if( showDetail ){ |
| 141 | 137 | style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", |
| @@ -166,37 +162,37 @@ | ||
| 166 | 162 | if( showDetail && g.okHistory ){ |
| 167 | 163 | int i; |
| 168 | 164 | const char *zClr = 0; |
| 169 | 165 | Blob comment; |
| 170 | 166 | |
| 171 | - zATime = db_text(0, "SELECT datetime(%.17g)", m.rDate); | |
| 167 | + zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); | |
| 172 | 168 | @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at |
| 173 | 169 | @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>] |
| 174 | - @ entered by user <b>%h(m.zUser)</b> on | |
| 170 | + @ entered by user <b>%h(pEvent->zUser)</b> on | |
| 175 | 171 | @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p> |
| 176 | 172 | @ <blockquote> |
| 177 | - for(i=0; i<m.nTag; i++){ | |
| 178 | - if( strcmp(m.aTag[i].zName,"+bgcolor")==0 ){ | |
| 179 | - zClr = m.aTag[i].zValue; | |
| 173 | + for(i=0; i<pEvent->nTag; i++){ | |
| 174 | + if( strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ | |
| 175 | + zClr = pEvent->aTag[i].zValue; | |
| 180 | 176 | } |
| 181 | 177 | } |
| 182 | 178 | if( zClr && zClr[0]==0 ) zClr = 0; |
| 183 | 179 | if( zClr ){ |
| 184 | 180 | @ <div style="background-color: %h(zClr);"> |
| 185 | 181 | }else{ |
| 186 | 182 | @ <div> |
| 187 | 183 | } |
| 188 | - blob_init(&comment, m.zComment, -1); | |
| 184 | + blob_init(&comment, pEvent->zComment, -1); | |
| 189 | 185 | wiki_convert(&comment, 0, WIKI_INLINE); |
| 190 | 186 | blob_reset(&comment); |
| 191 | 187 | @ </div> |
| 192 | 188 | @ </blockquote><hr /> |
| 193 | 189 | } |
| 194 | 190 | |
| 195 | 191 | wiki_convert(&tail, 0, 0); |
| 196 | 192 | style_footer(); |
| 197 | - manifest_clear(&m); | |
| 193 | + manifest_destroy(pEvent); | |
| 198 | 194 | } |
| 199 | 195 | |
| 200 | 196 | /* |
| 201 | 197 | ** WEBPAGE: eventedit |
| 202 | 198 | ** URL: /eventedit?name=EVENTID |
| @@ -259,22 +255,18 @@ | ||
| 259 | 255 | |
| 260 | 256 | /* If editing an existing event, extract the key fields to use as |
| 261 | 257 | ** a starting point for the edit. |
| 262 | 258 | */ |
| 263 | 259 | if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ |
| 264 | - Manifest m; | |
| 265 | - Blob content; | |
| 266 | - memset(&m, 0, sizeof(m)); | |
| 267 | - blob_zero(&m.content); | |
| 268 | - content_get(rid, &content); | |
| 269 | - manifest_parse(&m, &content); | |
| 270 | - if( m.type==CFTYPE_EVENT ){ | |
| 271 | - if( zBody==0 ) zBody = m.zWiki; | |
| 260 | + Manifest *pEvent; | |
| 261 | + pEvent = manifest_get(rid, CFTYPE_EVENT); | |
| 262 | + if( pEvent && pEvent->type==CFTYPE_EVENT ){ | |
| 263 | + if( zBody==0 ) zBody = pEvent->zWiki; | |
| 272 | 264 | if( zETime==0 ){ |
| 273 | - zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate); | |
| 265 | + zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); | |
| 274 | 266 | } |
| 275 | - if( zComment==0 ) zComment = m.zComment; | |
| 267 | + if( zComment==0 ) zComment = pEvent->zComment; | |
| 276 | 268 | } |
| 277 | 269 | if( zTags==0 ){ |
| 278 | 270 | zTags = db_text(0, |
| 279 | 271 | "SELECT group_concat(substr(tagname,5),', ')" |
| 280 | 272 | " FROM tagxref, tag" |
| 281 | 273 |
| --- src/event.c | |
| +++ src/event.c | |
| @@ -63,12 +63,11 @@ | |
| 63 | const char *zEventId; /* Event identifier */ |
| 64 | char *zETime; /* Time of the event */ |
| 65 | char *zATime; /* Time the artifact was created */ |
| 66 | int specRid; /* rid specified by aid= parameter */ |
| 67 | int prevRid, nextRid; /* Previous or next edits of this event */ |
| 68 | Manifest m; /* Parsed event artifact */ |
| 69 | Blob content; /* Original event artifact content */ |
| 70 | Blob fullbody; /* Complete content of the event body */ |
| 71 | Blob title; /* Title extracted from the event body */ |
| 72 | Blob tail; /* Event body that comes after the title */ |
| 73 | Stmt q1; /* Query to search for the event */ |
| 74 | int showDetail; /* True to show details */ |
| @@ -113,18 +112,15 @@ | |
| 113 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 114 | showDetail = atoi(PD("detail","0")); |
| 115 | |
| 116 | /* Extract the event content. |
| 117 | */ |
| 118 | memset(&m, 0, sizeof(m)); |
| 119 | blob_zero(&m.content); |
| 120 | content_get(rid, &content); |
| 121 | manifest_parse(&m, &content); |
| 122 | if( m.type!=CFTYPE_EVENT ){ |
| 123 | fossil_panic("Object #%d is not an event", rid); |
| 124 | } |
| 125 | blob_init(&fullbody, m.zWiki, -1); |
| 126 | if( wiki_find_title(&fullbody, &title, &tail) ){ |
| 127 | style_header(blob_str(&title)); |
| 128 | }else{ |
| 129 | style_header("Event %S", zEventId); |
| 130 | tail = fullbody; |
| @@ -131,11 +127,11 @@ | |
| 131 | } |
| 132 | if( g.okWrWiki && g.okWrite && nextRid==0 ){ |
| 133 | style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", |
| 134 | g.zTop, zEventId); |
| 135 | } |
| 136 | zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate); |
| 137 | style_submenu_element("Context", "Context", "%s/timeline?c=%T", |
| 138 | g.zTop, zETime); |
| 139 | if( g.okHistory ){ |
| 140 | if( showDetail ){ |
| 141 | style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", |
| @@ -166,37 +162,37 @@ | |
| 166 | if( showDetail && g.okHistory ){ |
| 167 | int i; |
| 168 | const char *zClr = 0; |
| 169 | Blob comment; |
| 170 | |
| 171 | zATime = db_text(0, "SELECT datetime(%.17g)", m.rDate); |
| 172 | @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at |
| 173 | @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>] |
| 174 | @ entered by user <b>%h(m.zUser)</b> on |
| 175 | @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p> |
| 176 | @ <blockquote> |
| 177 | for(i=0; i<m.nTag; i++){ |
| 178 | if( strcmp(m.aTag[i].zName,"+bgcolor")==0 ){ |
| 179 | zClr = m.aTag[i].zValue; |
| 180 | } |
| 181 | } |
| 182 | if( zClr && zClr[0]==0 ) zClr = 0; |
| 183 | if( zClr ){ |
| 184 | @ <div style="background-color: %h(zClr);"> |
| 185 | }else{ |
| 186 | @ <div> |
| 187 | } |
| 188 | blob_init(&comment, m.zComment, -1); |
| 189 | wiki_convert(&comment, 0, WIKI_INLINE); |
| 190 | blob_reset(&comment); |
| 191 | @ </div> |
| 192 | @ </blockquote><hr /> |
| 193 | } |
| 194 | |
| 195 | wiki_convert(&tail, 0, 0); |
| 196 | style_footer(); |
| 197 | manifest_clear(&m); |
| 198 | } |
| 199 | |
| 200 | /* |
| 201 | ** WEBPAGE: eventedit |
| 202 | ** URL: /eventedit?name=EVENTID |
| @@ -259,22 +255,18 @@ | |
| 259 | |
| 260 | /* If editing an existing event, extract the key fields to use as |
| 261 | ** a starting point for the edit. |
| 262 | */ |
| 263 | if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ |
| 264 | Manifest m; |
| 265 | Blob content; |
| 266 | memset(&m, 0, sizeof(m)); |
| 267 | blob_zero(&m.content); |
| 268 | content_get(rid, &content); |
| 269 | manifest_parse(&m, &content); |
| 270 | if( m.type==CFTYPE_EVENT ){ |
| 271 | if( zBody==0 ) zBody = m.zWiki; |
| 272 | if( zETime==0 ){ |
| 273 | zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate); |
| 274 | } |
| 275 | if( zComment==0 ) zComment = m.zComment; |
| 276 | } |
| 277 | if( zTags==0 ){ |
| 278 | zTags = db_text(0, |
| 279 | "SELECT group_concat(substr(tagname,5),', ')" |
| 280 | " FROM tagxref, tag" |
| 281 |
| --- src/event.c | |
| +++ src/event.c | |
| @@ -63,12 +63,11 @@ | |
| 63 | const char *zEventId; /* Event identifier */ |
| 64 | char *zETime; /* Time of the event */ |
| 65 | char *zATime; /* Time the artifact was created */ |
| 66 | int specRid; /* rid specified by aid= parameter */ |
| 67 | int prevRid, nextRid; /* Previous or next edits of this event */ |
| 68 | Manifest *pEvent; /* Parsed event artifact */ |
| 69 | Blob fullbody; /* Complete content of the event body */ |
| 70 | Blob title; /* Title extracted from the event body */ |
| 71 | Blob tail; /* Event body that comes after the title */ |
| 72 | Stmt q1; /* Query to search for the event */ |
| 73 | int showDetail; /* True to show details */ |
| @@ -113,18 +112,15 @@ | |
| 112 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 113 | showDetail = atoi(PD("detail","0")); |
| 114 | |
| 115 | /* Extract the event content. |
| 116 | */ |
| 117 | pEvent = manifest_get(rid, CFTYPE_EVENT); |
| 118 | if( pEvent==0 ){ |
| 119 | fossil_panic("Object #%d is not an event", rid); |
| 120 | } |
| 121 | blob_init(&fullbody, pEvent->zWiki, -1); |
| 122 | if( wiki_find_title(&fullbody, &title, &tail) ){ |
| 123 | style_header(blob_str(&title)); |
| 124 | }else{ |
| 125 | style_header("Event %S", zEventId); |
| 126 | tail = fullbody; |
| @@ -131,11 +127,11 @@ | |
| 127 | } |
| 128 | if( g.okWrWiki && g.okWrite && nextRid==0 ){ |
| 129 | style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s", |
| 130 | g.zTop, zEventId); |
| 131 | } |
| 132 | zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); |
| 133 | style_submenu_element("Context", "Context", "%s/timeline?c=%T", |
| 134 | g.zTop, zETime); |
| 135 | if( g.okHistory ){ |
| 136 | if( showDetail ){ |
| 137 | style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s", |
| @@ -166,37 +162,37 @@ | |
| 162 | if( showDetail && g.okHistory ){ |
| 163 | int i; |
| 164 | const char *zClr = 0; |
| 165 | Blob comment; |
| 166 | |
| 167 | zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate); |
| 168 | @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at |
| 169 | @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>] |
| 170 | @ entered by user <b>%h(pEvent->zUser)</b> on |
| 171 | @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p> |
| 172 | @ <blockquote> |
| 173 | for(i=0; i<pEvent->nTag; i++){ |
| 174 | if( strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){ |
| 175 | zClr = pEvent->aTag[i].zValue; |
| 176 | } |
| 177 | } |
| 178 | if( zClr && zClr[0]==0 ) zClr = 0; |
| 179 | if( zClr ){ |
| 180 | @ <div style="background-color: %h(zClr);"> |
| 181 | }else{ |
| 182 | @ <div> |
| 183 | } |
| 184 | blob_init(&comment, pEvent->zComment, -1); |
| 185 | wiki_convert(&comment, 0, WIKI_INLINE); |
| 186 | blob_reset(&comment); |
| 187 | @ </div> |
| 188 | @ </blockquote><hr /> |
| 189 | } |
| 190 | |
| 191 | wiki_convert(&tail, 0, 0); |
| 192 | style_footer(); |
| 193 | manifest_destroy(pEvent); |
| 194 | } |
| 195 | |
| 196 | /* |
| 197 | ** WEBPAGE: eventedit |
| 198 | ** URL: /eventedit?name=EVENTID |
| @@ -259,22 +255,18 @@ | |
| 255 | |
| 256 | /* If editing an existing event, extract the key fields to use as |
| 257 | ** a starting point for the edit. |
| 258 | */ |
| 259 | if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){ |
| 260 | Manifest *pEvent; |
| 261 | pEvent = manifest_get(rid, CFTYPE_EVENT); |
| 262 | if( pEvent && pEvent->type==CFTYPE_EVENT ){ |
| 263 | if( zBody==0 ) zBody = pEvent->zWiki; |
| 264 | if( zETime==0 ){ |
| 265 | zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate); |
| 266 | } |
| 267 | if( zComment==0 ) zComment = pEvent->zComment; |
| 268 | } |
| 269 | if( zTags==0 ){ |
| 270 | zTags = db_text(0, |
| 271 | "SELECT group_concat(substr(tagname,5),', ')" |
| 272 | " FROM tagxref, tag" |
| 273 |
+1
-1
| --- src/finfo.c | ||
| +++ src/finfo.c | ||
| @@ -131,11 +131,11 @@ | ||
| 131 | 131 | TAG_BRANCH, |
| 132 | 132 | zFilename |
| 133 | 133 | ); |
| 134 | 134 | blob_zero(&title); |
| 135 | 135 | blob_appendf(&title, "History of "); |
| 136 | - hyperlinked_path(zFilename, &title); | |
| 136 | + hyperlinked_path(zFilename, &title, 0); | |
| 137 | 137 | @ <h2>%b(&title)</h2> |
| 138 | 138 | blob_reset(&title); |
| 139 | 139 | pGraph = graph_init(); |
| 140 | 140 | @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> |
| 141 | 141 | @ <table class="timelineTable"> |
| 142 | 142 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -131,11 +131,11 @@ | |
| 131 | TAG_BRANCH, |
| 132 | zFilename |
| 133 | ); |
| 134 | blob_zero(&title); |
| 135 | blob_appendf(&title, "History of "); |
| 136 | hyperlinked_path(zFilename, &title); |
| 137 | @ <h2>%b(&title)</h2> |
| 138 | blob_reset(&title); |
| 139 | pGraph = graph_init(); |
| 140 | @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> |
| 141 | @ <table class="timelineTable"> |
| 142 |
| --- src/finfo.c | |
| +++ src/finfo.c | |
| @@ -131,11 +131,11 @@ | |
| 131 | TAG_BRANCH, |
| 132 | zFilename |
| 133 | ); |
| 134 | blob_zero(&title); |
| 135 | blob_appendf(&title, "History of "); |
| 136 | hyperlinked_path(zFilename, &title, 0); |
| 137 | @ <h2>%b(&title)</h2> |
| 138 | blob_reset(&title); |
| 139 | pGraph = graph_init(); |
| 140 | @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div> |
| 141 | @ <table class="timelineTable"> |
| 142 |
+62
-66
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -543,24 +543,20 @@ | ||
| 543 | 543 | rid = 0; |
| 544 | 544 | } |
| 545 | 545 | db_finalize(&q); |
| 546 | 546 | showTags(rid, "wiki-*"); |
| 547 | 547 | if( rid ){ |
| 548 | - Blob content; | |
| 549 | - Manifest m; | |
| 550 | - memset(&m, 0, sizeof(m)); | |
| 551 | - blob_zero(&m.content); | |
| 552 | - content_get(rid, &content); | |
| 553 | - manifest_parse(&m, &content); | |
| 554 | - if( m.type==CFTYPE_WIKI ){ | |
| 548 | + Manifest *pWiki; | |
| 549 | + pWiki = manifest_get(rid, CFTYPE_WIKI); | |
| 550 | + if( pWiki ){ | |
| 555 | 551 | Blob wiki; |
| 556 | - blob_init(&wiki, m.zWiki, -1); | |
| 552 | + blob_init(&wiki, pWiki->zWiki, -1); | |
| 557 | 553 | @ <div class="section">Content</div> |
| 558 | 554 | wiki_convert(&wiki, 0, 0); |
| 559 | 555 | blob_reset(&wiki); |
| 560 | 556 | } |
| 561 | - manifest_clear(&m); | |
| 557 | + manifest_destroy(pWiki); | |
| 562 | 558 | } |
| 563 | 559 | style_footer(); |
| 564 | 560 | } |
| 565 | 561 | |
| 566 | 562 | /* |
| @@ -580,26 +576,23 @@ | ||
| 580 | 576 | |
| 581 | 577 | /* |
| 582 | 578 | ** Find an checkin based on query parameter zParam and parse its |
| 583 | 579 | ** manifest. Return the number of errors. |
| 584 | 580 | */ |
| 585 | -static int vdiff_parse_manifest(const char *zParam, int *pRid, Manifest *pM){ | |
| 581 | +static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){ | |
| 586 | 582 | int rid; |
| 587 | - Blob content; | |
| 588 | 583 | |
| 589 | 584 | *pRid = rid = name_to_rid_www(zParam); |
| 590 | 585 | if( rid==0 ){ |
| 591 | 586 | webpage_error("Missing \"%s\" query parameter.", zParam); |
| 592 | - return 1; | |
| 587 | + return 0; | |
| 593 | 588 | } |
| 594 | 589 | if( !is_a_version(rid) ){ |
| 595 | 590 | webpage_error("Artifact %s is not a checkin.", P(zParam)); |
| 596 | - return 1; | |
| 591 | + return 0; | |
| 597 | 592 | } |
| 598 | - content_get(rid, &content); | |
| 599 | - manifest_parse(pM, &content); | |
| 600 | - return 0; | |
| 593 | + return manifest_get(rid, CFTYPE_MANIFEST); | |
| 601 | 594 | } |
| 602 | 595 | |
| 603 | 596 | /* |
| 604 | 597 | ** Output a description of a check-in |
| 605 | 598 | */ |
| @@ -635,59 +628,64 @@ | ||
| 635 | 628 | ** Show all differences between two checkins. |
| 636 | 629 | */ |
| 637 | 630 | void vdiff_page(void){ |
| 638 | 631 | int ridFrom, ridTo; |
| 639 | 632 | int showDetail = 0; |
| 640 | - int iFrom, iTo; | |
| 641 | - Manifest mFrom, mTo; | |
| 633 | + Manifest *pFrom, *pTo; | |
| 634 | + ManifestFile *pFileFrom, *pFileTo; | |
| 642 | 635 | |
| 643 | 636 | login_check_credentials(); |
| 644 | 637 | if( !g.okRead ){ login_needed(); return; } |
| 645 | 638 | login_anonymous_available(); |
| 646 | 639 | |
| 647 | - if( vdiff_parse_manifest("from", &ridFrom, &mFrom) ) return; | |
| 648 | - if( vdiff_parse_manifest("to", &ridTo, &mTo) ) return; | |
| 640 | + pFrom = vdiff_parse_manifest("from", &ridFrom); | |
| 641 | + if( pFrom==0 ) return; | |
| 642 | + pTo = vdiff_parse_manifest("to", &ridTo); | |
| 643 | + if( pTo==0 ) return; | |
| 649 | 644 | showDetail = atoi(PD("detail","0")); |
| 650 | 645 | style_header("Check-in Differences"); |
| 651 | 646 | @ <h2>Difference From:</h2><blockquote> |
| 652 | 647 | checkin_description(ridFrom); |
| 653 | 648 | @ </blockquote><h2>To:</h2><blockquote> |
| 654 | 649 | checkin_description(ridTo); |
| 655 | 650 | @ </blockquote><hr /><p> |
| 656 | 651 | |
| 657 | - iFrom = iTo = 0; | |
| 658 | - while( iFrom<mFrom.nFile && iTo<mTo.nFile ){ | |
| 652 | + manifest_file_rewind(pFrom); | |
| 653 | + pFileFrom = manifest_file_next(pFrom, 0); | |
| 654 | + manifest_file_rewind(pTo); | |
| 655 | + pFileTo = manifest_file_next(pTo, 0); | |
| 656 | + while( pFileFrom || pFileTo ){ | |
| 659 | 657 | int cmp; |
| 660 | - if( iFrom>=mFrom.nFile ){ | |
| 658 | + if( pFileFrom==0 ){ | |
| 661 | 659 | cmp = +1; |
| 662 | - }else if( iTo>=mTo.nFile ){ | |
| 660 | + }else if( pFileTo==0 ){ | |
| 663 | 661 | cmp = -1; |
| 664 | 662 | }else{ |
| 665 | - cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName); | |
| 663 | + cmp = strcmp(pFileFrom->zName, pFileTo->zName); | |
| 666 | 664 | } |
| 667 | 665 | if( cmp<0 ){ |
| 668 | - append_file_change_line(mFrom.aFile[iFrom].zName, | |
| 669 | - mFrom.aFile[iFrom].zUuid, 0, 0); | |
| 670 | - iFrom++; | |
| 666 | + append_file_change_line(pFileFrom->zName, | |
| 667 | + pFileFrom->zUuid, 0, 0); | |
| 668 | + pFileFrom = manifest_file_next(pFrom, 0); | |
| 671 | 669 | }else if( cmp>0 ){ |
| 672 | - append_file_change_line(mTo.aFile[iTo].zName, | |
| 673 | - 0, mTo.aFile[iTo].zUuid, 0); | |
| 674 | - iTo++; | |
| 675 | - }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){ | |
| 670 | + append_file_change_line(pFileTo->zName, | |
| 671 | + 0, pFileTo->zUuid, 0); | |
| 672 | + pFileTo = manifest_file_next(pTo, 0); | |
| 673 | + }else if( strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ | |
| 676 | 674 | /* No changes */ |
| 677 | - iFrom++; | |
| 678 | - iTo++; | |
| 675 | + pFileFrom = manifest_file_next(pFrom, 0); | |
| 676 | + pFileTo = manifest_file_next(pTo, 0); | |
| 679 | 677 | }else{ |
| 680 | - append_file_change_line(mFrom.aFile[iFrom].zName, | |
| 681 | - mFrom.aFile[iFrom].zUuid, | |
| 682 | - mTo.aFile[iTo].zUuid, showDetail); | |
| 683 | - iFrom++; | |
| 684 | - iTo++; | |
| 678 | + append_file_change_line(pFileFrom->zName, | |
| 679 | + pFileFrom->zUuid, | |
| 680 | + pFileTo->zUuid, showDetail); | |
| 681 | + pFileFrom = manifest_file_next(pFrom, 0); | |
| 682 | + pFileTo = manifest_file_next(pTo, 0); | |
| 685 | 683 | } |
| 686 | 684 | } |
| 687 | - manifest_clear(&mFrom); | |
| 688 | - manifest_clear(&mTo); | |
| 685 | + manifest_destroy(pFrom); | |
| 686 | + manifest_destroy(pTo); | |
| 689 | 687 | |
| 690 | 688 | style_footer(); |
| 691 | 689 | } |
| 692 | 690 | |
| 693 | 691 | /* |
| @@ -1043,25 +1041,26 @@ | ||
| 1043 | 1041 | */ |
| 1044 | 1042 | int artifact_from_ci_and_filename(void){ |
| 1045 | 1043 | const char *zFilename; |
| 1046 | 1044 | const char *zCI; |
| 1047 | 1045 | int cirid; |
| 1048 | - Blob content; | |
| 1049 | - Manifest m; | |
| 1050 | - int i; | |
| 1046 | + Manifest *pManifest; | |
| 1047 | + ManifestFile *pFile; | |
| 1051 | 1048 | |
| 1052 | 1049 | zCI = P("ci"); |
| 1053 | 1050 | if( zCI==0 ) return 0; |
| 1054 | 1051 | zFilename = P("filename"); |
| 1055 | 1052 | if( zFilename==0 ) return 0; |
| 1056 | 1053 | cirid = name_to_rid_www("ci"); |
| 1057 | - if( !content_get(cirid, &content) ) return 0; | |
| 1058 | - if( !manifest_parse(&m, &content) ) return 0; | |
| 1059 | - if( m.type!=CFTYPE_MANIFEST ) return 0; | |
| 1060 | - for(i=0; i<m.nFile; i++){ | |
| 1061 | - if( strcmp(zFilename, m.aFile[i].zName)==0 ){ | |
| 1062 | - return db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", m.aFile[i].zUuid); | |
| 1054 | + pManifest = manifest_get(cirid, CFTYPE_MANIFEST); | |
| 1055 | + if( pManifest==0 ) return 0; | |
| 1056 | + manifest_file_rewind(pManifest); | |
| 1057 | + while( (pFile = manifest_file_next(pManifest,0))!=0 ){ | |
| 1058 | + if( strcmp(zFilename, pFile->zName)==0 ){ | |
| 1059 | + int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); | |
| 1060 | + manifest_destroy(pManifest); | |
| 1061 | + return rid; | |
| 1063 | 1062 | } |
| 1064 | 1063 | } |
| 1065 | 1064 | return 0; |
| 1066 | 1065 | } |
| 1067 | 1066 | |
| @@ -1168,15 +1167,14 @@ | ||
| 1168 | 1167 | ** |
| 1169 | 1168 | ** Show the details of a ticket change control artifact. |
| 1170 | 1169 | */ |
| 1171 | 1170 | void tinfo_page(void){ |
| 1172 | 1171 | int rid; |
| 1173 | - Blob content; | |
| 1174 | 1172 | char *zDate; |
| 1175 | 1173 | const char *zUuid; |
| 1176 | 1174 | char zTktName[20]; |
| 1177 | - Manifest m; | |
| 1175 | + Manifest *pTktChng; | |
| 1178 | 1176 | |
| 1179 | 1177 | login_check_credentials(); |
| 1180 | 1178 | if( !g.okRdTkt ){ login_needed(); return; } |
| 1181 | 1179 | rid = name_to_rid_www("name"); |
| 1182 | 1180 | if( rid==0 ){ fossil_redirect_home(); } |
| @@ -1188,39 +1186,37 @@ | ||
| 1188 | 1186 | }else{ |
| 1189 | 1187 | style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", |
| 1190 | 1188 | g.zTop, zUuid); |
| 1191 | 1189 | } |
| 1192 | 1190 | } |
| 1193 | - content_get(rid, &content); | |
| 1194 | - if( manifest_parse(&m, &content)==0 ){ | |
| 1195 | - fossil_redirect_home(); | |
| 1196 | - } | |
| 1197 | - if( m.type!=CFTYPE_TICKET ){ | |
| 1191 | + pTktChng = manifest_get(rid, CFTYPE_TICKET); | |
| 1192 | + if( pTktChng==0 ){ | |
| 1198 | 1193 | fossil_redirect_home(); |
| 1199 | 1194 | } |
| 1200 | 1195 | style_header("Ticket Change Details"); |
| 1201 | - zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate); | |
| 1202 | - memcpy(zTktName, m.zTicketUuid, 10); | |
| 1196 | + zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); | |
| 1197 | + memcpy(zTktName, pTktChng->zTicketUuid, 10); | |
| 1203 | 1198 | zTktName[10] = 0; |
| 1204 | 1199 | if( g.okHistory ){ |
| 1205 | - @ <h2>Changes to ticket <a href="%s(m.zTicketUuid)">%s(zTktName)</a></h2> | |
| 1200 | + @ <h2>Changes to ticket | |
| 1201 | + @ <a href="%s(pTktChng->zTicketUuid)">%s(zTktName)</a></h2> | |
| 1206 | 1202 | @ |
| 1207 | - @ <p>By %h(m.zUser) on %s(zDate). See also: | |
| 1203 | + @ <p>By %h(pTktChng->zUser) on %s(zDate). See also: | |
| 1208 | 1204 | @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and |
| 1209 | - @ <a href="%s(g.zTop)/tkthistory/%s(m.zTicketUuid)">ticket history</a> | |
| 1210 | - @ </p> | |
| 1205 | + @ <a href="%s(g.zTop)/tkthistory/%s(pTktChng->zTicketUuid)">ticket | |
| 1206 | + @ history</a></p> | |
| 1211 | 1207 | }else{ |
| 1212 | 1208 | @ <h2>Changes to ticket %s(zTktName)</h2> |
| 1213 | 1209 | @ |
| 1214 | - @ <p>By %h(m.zUser) on %s(zDate). | |
| 1210 | + @ <p>By %h(pTktChng->zUser) on %s(zDate). | |
| 1215 | 1211 | @ </p> |
| 1216 | 1212 | } |
| 1217 | 1213 | @ |
| 1218 | 1214 | @ <ol> |
| 1219 | 1215 | free(zDate); |
| 1220 | - ticket_output_change_artifact(&m); | |
| 1221 | - manifest_clear(&m); | |
| 1216 | + ticket_output_change_artifact(pTktChng); | |
| 1217 | + manifest_destroy(pTktChng); | |
| 1222 | 1218 | style_footer(); |
| 1223 | 1219 | } |
| 1224 | 1220 | |
| 1225 | 1221 | |
| 1226 | 1222 | /* |
| 1227 | 1223 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -543,24 +543,20 @@ | |
| 543 | rid = 0; |
| 544 | } |
| 545 | db_finalize(&q); |
| 546 | showTags(rid, "wiki-*"); |
| 547 | if( rid ){ |
| 548 | Blob content; |
| 549 | Manifest m; |
| 550 | memset(&m, 0, sizeof(m)); |
| 551 | blob_zero(&m.content); |
| 552 | content_get(rid, &content); |
| 553 | manifest_parse(&m, &content); |
| 554 | if( m.type==CFTYPE_WIKI ){ |
| 555 | Blob wiki; |
| 556 | blob_init(&wiki, m.zWiki, -1); |
| 557 | @ <div class="section">Content</div> |
| 558 | wiki_convert(&wiki, 0, 0); |
| 559 | blob_reset(&wiki); |
| 560 | } |
| 561 | manifest_clear(&m); |
| 562 | } |
| 563 | style_footer(); |
| 564 | } |
| 565 | |
| 566 | /* |
| @@ -580,26 +576,23 @@ | |
| 580 | |
| 581 | /* |
| 582 | ** Find an checkin based on query parameter zParam and parse its |
| 583 | ** manifest. Return the number of errors. |
| 584 | */ |
| 585 | static int vdiff_parse_manifest(const char *zParam, int *pRid, Manifest *pM){ |
| 586 | int rid; |
| 587 | Blob content; |
| 588 | |
| 589 | *pRid = rid = name_to_rid_www(zParam); |
| 590 | if( rid==0 ){ |
| 591 | webpage_error("Missing \"%s\" query parameter.", zParam); |
| 592 | return 1; |
| 593 | } |
| 594 | if( !is_a_version(rid) ){ |
| 595 | webpage_error("Artifact %s is not a checkin.", P(zParam)); |
| 596 | return 1; |
| 597 | } |
| 598 | content_get(rid, &content); |
| 599 | manifest_parse(pM, &content); |
| 600 | return 0; |
| 601 | } |
| 602 | |
| 603 | /* |
| 604 | ** Output a description of a check-in |
| 605 | */ |
| @@ -635,59 +628,64 @@ | |
| 635 | ** Show all differences between two checkins. |
| 636 | */ |
| 637 | void vdiff_page(void){ |
| 638 | int ridFrom, ridTo; |
| 639 | int showDetail = 0; |
| 640 | int iFrom, iTo; |
| 641 | Manifest mFrom, mTo; |
| 642 | |
| 643 | login_check_credentials(); |
| 644 | if( !g.okRead ){ login_needed(); return; } |
| 645 | login_anonymous_available(); |
| 646 | |
| 647 | if( vdiff_parse_manifest("from", &ridFrom, &mFrom) ) return; |
| 648 | if( vdiff_parse_manifest("to", &ridTo, &mTo) ) return; |
| 649 | showDetail = atoi(PD("detail","0")); |
| 650 | style_header("Check-in Differences"); |
| 651 | @ <h2>Difference From:</h2><blockquote> |
| 652 | checkin_description(ridFrom); |
| 653 | @ </blockquote><h2>To:</h2><blockquote> |
| 654 | checkin_description(ridTo); |
| 655 | @ </blockquote><hr /><p> |
| 656 | |
| 657 | iFrom = iTo = 0; |
| 658 | while( iFrom<mFrom.nFile && iTo<mTo.nFile ){ |
| 659 | int cmp; |
| 660 | if( iFrom>=mFrom.nFile ){ |
| 661 | cmp = +1; |
| 662 | }else if( iTo>=mTo.nFile ){ |
| 663 | cmp = -1; |
| 664 | }else{ |
| 665 | cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName); |
| 666 | } |
| 667 | if( cmp<0 ){ |
| 668 | append_file_change_line(mFrom.aFile[iFrom].zName, |
| 669 | mFrom.aFile[iFrom].zUuid, 0, 0); |
| 670 | iFrom++; |
| 671 | }else if( cmp>0 ){ |
| 672 | append_file_change_line(mTo.aFile[iTo].zName, |
| 673 | 0, mTo.aFile[iTo].zUuid, 0); |
| 674 | iTo++; |
| 675 | }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){ |
| 676 | /* No changes */ |
| 677 | iFrom++; |
| 678 | iTo++; |
| 679 | }else{ |
| 680 | append_file_change_line(mFrom.aFile[iFrom].zName, |
| 681 | mFrom.aFile[iFrom].zUuid, |
| 682 | mTo.aFile[iTo].zUuid, showDetail); |
| 683 | iFrom++; |
| 684 | iTo++; |
| 685 | } |
| 686 | } |
| 687 | manifest_clear(&mFrom); |
| 688 | manifest_clear(&mTo); |
| 689 | |
| 690 | style_footer(); |
| 691 | } |
| 692 | |
| 693 | /* |
| @@ -1043,25 +1041,26 @@ | |
| 1043 | */ |
| 1044 | int artifact_from_ci_and_filename(void){ |
| 1045 | const char *zFilename; |
| 1046 | const char *zCI; |
| 1047 | int cirid; |
| 1048 | Blob content; |
| 1049 | Manifest m; |
| 1050 | int i; |
| 1051 | |
| 1052 | zCI = P("ci"); |
| 1053 | if( zCI==0 ) return 0; |
| 1054 | zFilename = P("filename"); |
| 1055 | if( zFilename==0 ) return 0; |
| 1056 | cirid = name_to_rid_www("ci"); |
| 1057 | if( !content_get(cirid, &content) ) return 0; |
| 1058 | if( !manifest_parse(&m, &content) ) return 0; |
| 1059 | if( m.type!=CFTYPE_MANIFEST ) return 0; |
| 1060 | for(i=0; i<m.nFile; i++){ |
| 1061 | if( strcmp(zFilename, m.aFile[i].zName)==0 ){ |
| 1062 | return db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", m.aFile[i].zUuid); |
| 1063 | } |
| 1064 | } |
| 1065 | return 0; |
| 1066 | } |
| 1067 | |
| @@ -1168,15 +1167,14 @@ | |
| 1168 | ** |
| 1169 | ** Show the details of a ticket change control artifact. |
| 1170 | */ |
| 1171 | void tinfo_page(void){ |
| 1172 | int rid; |
| 1173 | Blob content; |
| 1174 | char *zDate; |
| 1175 | const char *zUuid; |
| 1176 | char zTktName[20]; |
| 1177 | Manifest m; |
| 1178 | |
| 1179 | login_check_credentials(); |
| 1180 | if( !g.okRdTkt ){ login_needed(); return; } |
| 1181 | rid = name_to_rid_www("name"); |
| 1182 | if( rid==0 ){ fossil_redirect_home(); } |
| @@ -1188,39 +1186,37 @@ | |
| 1188 | }else{ |
| 1189 | style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", |
| 1190 | g.zTop, zUuid); |
| 1191 | } |
| 1192 | } |
| 1193 | content_get(rid, &content); |
| 1194 | if( manifest_parse(&m, &content)==0 ){ |
| 1195 | fossil_redirect_home(); |
| 1196 | } |
| 1197 | if( m.type!=CFTYPE_TICKET ){ |
| 1198 | fossil_redirect_home(); |
| 1199 | } |
| 1200 | style_header("Ticket Change Details"); |
| 1201 | zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate); |
| 1202 | memcpy(zTktName, m.zTicketUuid, 10); |
| 1203 | zTktName[10] = 0; |
| 1204 | if( g.okHistory ){ |
| 1205 | @ <h2>Changes to ticket <a href="%s(m.zTicketUuid)">%s(zTktName)</a></h2> |
| 1206 | @ |
| 1207 | @ <p>By %h(m.zUser) on %s(zDate). See also: |
| 1208 | @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and |
| 1209 | @ <a href="%s(g.zTop)/tkthistory/%s(m.zTicketUuid)">ticket history</a> |
| 1210 | @ </p> |
| 1211 | }else{ |
| 1212 | @ <h2>Changes to ticket %s(zTktName)</h2> |
| 1213 | @ |
| 1214 | @ <p>By %h(m.zUser) on %s(zDate). |
| 1215 | @ </p> |
| 1216 | } |
| 1217 | @ |
| 1218 | @ <ol> |
| 1219 | free(zDate); |
| 1220 | ticket_output_change_artifact(&m); |
| 1221 | manifest_clear(&m); |
| 1222 | style_footer(); |
| 1223 | } |
| 1224 | |
| 1225 | |
| 1226 | /* |
| 1227 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -543,24 +543,20 @@ | |
| 543 | rid = 0; |
| 544 | } |
| 545 | db_finalize(&q); |
| 546 | showTags(rid, "wiki-*"); |
| 547 | if( rid ){ |
| 548 | Manifest *pWiki; |
| 549 | pWiki = manifest_get(rid, CFTYPE_WIKI); |
| 550 | if( pWiki ){ |
| 551 | Blob wiki; |
| 552 | blob_init(&wiki, pWiki->zWiki, -1); |
| 553 | @ <div class="section">Content</div> |
| 554 | wiki_convert(&wiki, 0, 0); |
| 555 | blob_reset(&wiki); |
| 556 | } |
| 557 | manifest_destroy(pWiki); |
| 558 | } |
| 559 | style_footer(); |
| 560 | } |
| 561 | |
| 562 | /* |
| @@ -580,26 +576,23 @@ | |
| 576 | |
| 577 | /* |
| 578 | ** Find an checkin based on query parameter zParam and parse its |
| 579 | ** manifest. Return the number of errors. |
| 580 | */ |
| 581 | static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){ |
| 582 | int rid; |
| 583 | |
| 584 | *pRid = rid = name_to_rid_www(zParam); |
| 585 | if( rid==0 ){ |
| 586 | webpage_error("Missing \"%s\" query parameter.", zParam); |
| 587 | return 0; |
| 588 | } |
| 589 | if( !is_a_version(rid) ){ |
| 590 | webpage_error("Artifact %s is not a checkin.", P(zParam)); |
| 591 | return 0; |
| 592 | } |
| 593 | return manifest_get(rid, CFTYPE_MANIFEST); |
| 594 | } |
| 595 | |
| 596 | /* |
| 597 | ** Output a description of a check-in |
| 598 | */ |
| @@ -635,59 +628,64 @@ | |
| 628 | ** Show all differences between two checkins. |
| 629 | */ |
| 630 | void vdiff_page(void){ |
| 631 | int ridFrom, ridTo; |
| 632 | int showDetail = 0; |
| 633 | Manifest *pFrom, *pTo; |
| 634 | ManifestFile *pFileFrom, *pFileTo; |
| 635 | |
| 636 | login_check_credentials(); |
| 637 | if( !g.okRead ){ login_needed(); return; } |
| 638 | login_anonymous_available(); |
| 639 | |
| 640 | pFrom = vdiff_parse_manifest("from", &ridFrom); |
| 641 | if( pFrom==0 ) return; |
| 642 | pTo = vdiff_parse_manifest("to", &ridTo); |
| 643 | if( pTo==0 ) return; |
| 644 | showDetail = atoi(PD("detail","0")); |
| 645 | style_header("Check-in Differences"); |
| 646 | @ <h2>Difference From:</h2><blockquote> |
| 647 | checkin_description(ridFrom); |
| 648 | @ </blockquote><h2>To:</h2><blockquote> |
| 649 | checkin_description(ridTo); |
| 650 | @ </blockquote><hr /><p> |
| 651 | |
| 652 | manifest_file_rewind(pFrom); |
| 653 | pFileFrom = manifest_file_next(pFrom, 0); |
| 654 | manifest_file_rewind(pTo); |
| 655 | pFileTo = manifest_file_next(pTo, 0); |
| 656 | while( pFileFrom || pFileTo ){ |
| 657 | int cmp; |
| 658 | if( pFileFrom==0 ){ |
| 659 | cmp = +1; |
| 660 | }else if( pFileTo==0 ){ |
| 661 | cmp = -1; |
| 662 | }else{ |
| 663 | cmp = strcmp(pFileFrom->zName, pFileTo->zName); |
| 664 | } |
| 665 | if( cmp<0 ){ |
| 666 | append_file_change_line(pFileFrom->zName, |
| 667 | pFileFrom->zUuid, 0, 0); |
| 668 | pFileFrom = manifest_file_next(pFrom, 0); |
| 669 | }else if( cmp>0 ){ |
| 670 | append_file_change_line(pFileTo->zName, |
| 671 | 0, pFileTo->zUuid, 0); |
| 672 | pFileTo = manifest_file_next(pTo, 0); |
| 673 | }else if( strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){ |
| 674 | /* No changes */ |
| 675 | pFileFrom = manifest_file_next(pFrom, 0); |
| 676 | pFileTo = manifest_file_next(pTo, 0); |
| 677 | }else{ |
| 678 | append_file_change_line(pFileFrom->zName, |
| 679 | pFileFrom->zUuid, |
| 680 | pFileTo->zUuid, showDetail); |
| 681 | pFileFrom = manifest_file_next(pFrom, 0); |
| 682 | pFileTo = manifest_file_next(pTo, 0); |
| 683 | } |
| 684 | } |
| 685 | manifest_destroy(pFrom); |
| 686 | manifest_destroy(pTo); |
| 687 | |
| 688 | style_footer(); |
| 689 | } |
| 690 | |
| 691 | /* |
| @@ -1043,25 +1041,26 @@ | |
| 1041 | */ |
| 1042 | int artifact_from_ci_and_filename(void){ |
| 1043 | const char *zFilename; |
| 1044 | const char *zCI; |
| 1045 | int cirid; |
| 1046 | Manifest *pManifest; |
| 1047 | ManifestFile *pFile; |
| 1048 | |
| 1049 | zCI = P("ci"); |
| 1050 | if( zCI==0 ) return 0; |
| 1051 | zFilename = P("filename"); |
| 1052 | if( zFilename==0 ) return 0; |
| 1053 | cirid = name_to_rid_www("ci"); |
| 1054 | pManifest = manifest_get(cirid, CFTYPE_MANIFEST); |
| 1055 | if( pManifest==0 ) return 0; |
| 1056 | manifest_file_rewind(pManifest); |
| 1057 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 1058 | if( strcmp(zFilename, pFile->zName)==0 ){ |
| 1059 | int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid); |
| 1060 | manifest_destroy(pManifest); |
| 1061 | return rid; |
| 1062 | } |
| 1063 | } |
| 1064 | return 0; |
| 1065 | } |
| 1066 | |
| @@ -1168,15 +1167,14 @@ | |
| 1167 | ** |
| 1168 | ** Show the details of a ticket change control artifact. |
| 1169 | */ |
| 1170 | void tinfo_page(void){ |
| 1171 | int rid; |
| 1172 | char *zDate; |
| 1173 | const char *zUuid; |
| 1174 | char zTktName[20]; |
| 1175 | Manifest *pTktChng; |
| 1176 | |
| 1177 | login_check_credentials(); |
| 1178 | if( !g.okRdTkt ){ login_needed(); return; } |
| 1179 | rid = name_to_rid_www("name"); |
| 1180 | if( rid==0 ){ fossil_redirect_home(); } |
| @@ -1188,39 +1186,37 @@ | |
| 1186 | }else{ |
| 1187 | style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun", |
| 1188 | g.zTop, zUuid); |
| 1189 | } |
| 1190 | } |
| 1191 | pTktChng = manifest_get(rid, CFTYPE_TICKET); |
| 1192 | if( pTktChng==0 ){ |
| 1193 | fossil_redirect_home(); |
| 1194 | } |
| 1195 | style_header("Ticket Change Details"); |
| 1196 | zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate); |
| 1197 | memcpy(zTktName, pTktChng->zTicketUuid, 10); |
| 1198 | zTktName[10] = 0; |
| 1199 | if( g.okHistory ){ |
| 1200 | @ <h2>Changes to ticket |
| 1201 | @ <a href="%s(pTktChng->zTicketUuid)">%s(zTktName)</a></h2> |
| 1202 | @ |
| 1203 | @ <p>By %h(pTktChng->zUser) on %s(zDate). See also: |
| 1204 | @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and |
| 1205 | @ <a href="%s(g.zTop)/tkthistory/%s(pTktChng->zTicketUuid)">ticket |
| 1206 | @ history</a></p> |
| 1207 | }else{ |
| 1208 | @ <h2>Changes to ticket %s(zTktName)</h2> |
| 1209 | @ |
| 1210 | @ <p>By %h(pTktChng->zUser) on %s(zDate). |
| 1211 | @ </p> |
| 1212 | } |
| 1213 | @ |
| 1214 | @ <ol> |
| 1215 | free(zDate); |
| 1216 | ticket_output_change_artifact(pTktChng); |
| 1217 | manifest_destroy(pTktChng); |
| 1218 | style_footer(); |
| 1219 | } |
| 1220 | |
| 1221 | |
| 1222 | /* |
| 1223 |
+611
-379
| --- src/manifest.c | ||
| +++ src/manifest.c | ||
| @@ -26,24 +26,39 @@ | ||
| 26 | 26 | |
| 27 | 27 | #if INTERFACE |
| 28 | 28 | /* |
| 29 | 29 | ** Types of control files |
| 30 | 30 | */ |
| 31 | +#define CFTYPE_ANY 0 | |
| 31 | 32 | #define CFTYPE_MANIFEST 1 |
| 32 | 33 | #define CFTYPE_CLUSTER 2 |
| 33 | 34 | #define CFTYPE_CONTROL 3 |
| 34 | 35 | #define CFTYPE_WIKI 4 |
| 35 | 36 | #define CFTYPE_TICKET 5 |
| 36 | 37 | #define CFTYPE_ATTACHMENT 6 |
| 37 | 38 | #define CFTYPE_EVENT 7 |
| 39 | + | |
| 40 | +/* | |
| 41 | +** A single F-card within a manifest | |
| 42 | +*/ | |
| 43 | +struct ManifestFile { | |
| 44 | + char *zName; /* Name of a file */ | |
| 45 | + char *zUuid; /* UUID of the file */ | |
| 46 | + char *zPerm; /* File permissions */ | |
| 47 | + char *zPrior; /* Prior name if the name was changed */ | |
| 48 | +}; | |
| 49 | + | |
| 38 | 50 | |
| 39 | 51 | /* |
| 40 | 52 | ** A parsed manifest or cluster. |
| 41 | 53 | */ |
| 42 | 54 | struct Manifest { |
| 43 | 55 | Blob content; /* The original content blob */ |
| 44 | 56 | int type; /* Type of artifact. One of CFTYPE_xxxxx */ |
| 57 | + int rid; /* The blob-id for this manifest */ | |
| 58 | + char *zBaseline; /* Baseline manifest. The B card. */ | |
| 59 | + Manifest *pBaseline; /* The actual baseline manifest */ | |
| 45 | 60 | char *zComment; /* Decoded comment. The C card. */ |
| 46 | 61 | double rDate; /* Date and time from D card. 0.0 if no D card. */ |
| 47 | 62 | char *zUser; /* Name of the user from the U card. */ |
| 48 | 63 | char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ |
| 49 | 64 | char *zWiki; /* Text of the wiki page. W card. */ |
| @@ -54,17 +69,12 @@ | ||
| 54 | 69 | char *zAttachName; /* Filename of an attachment. A card. */ |
| 55 | 70 | char *zAttachSrc; /* UUID of document being attached. A card. */ |
| 56 | 71 | char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ |
| 57 | 72 | int nFile; /* Number of F cards */ |
| 58 | 73 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 59 | - struct ManifestFile { | |
| 60 | - char *zName; /* Name of a file */ | |
| 61 | - char *zUuid; /* UUID of the file */ | |
| 62 | - char *zPerm; /* File permissions */ | |
| 63 | - char *zPrior; /* Prior name if the name was changed */ | |
| 64 | - int iRename; /* index of renamed name in prior/next manifest */ | |
| 65 | - } *aFile; /* One entry for each F card */ | |
| 74 | + int iFile; /* Index of current file in iterator */ | |
| 75 | + ManifestFile *aFile; /* One entry for each F-card */ | |
| 66 | 76 | int nParent; /* Number of parents. */ |
| 67 | 77 | int nParentAlloc; /* Slots allocated in azParent[] */ |
| 68 | 78 | char **azParent; /* UUIDs of parents. One for each P card argument */ |
| 69 | 79 | int nCChild; /* Number of cluster children */ |
| 70 | 80 | int nCChildAlloc; /* Number of closts allocated in azCChild[] */ |
| @@ -87,68 +97,75 @@ | ||
| 87 | 97 | |
| 88 | 98 | /* |
| 89 | 99 | ** A cache of parsed manifests. This reduces the number of |
| 90 | 100 | ** calls to manifest_parse() when doing a rebuild. |
| 91 | 101 | */ |
| 92 | -#define MX_MANIFEST_CACHE 4 | |
| 102 | +#define MX_MANIFEST_CACHE 6 | |
| 93 | 103 | static struct { |
| 94 | 104 | int nxAge; |
| 95 | - int aRid[MX_MANIFEST_CACHE]; | |
| 96 | 105 | int aAge[MX_MANIFEST_CACHE]; |
| 97 | - Manifest aLine[MX_MANIFEST_CACHE]; | |
| 106 | + Manifest *apManifest[MX_MANIFEST_CACHE]; | |
| 98 | 107 | } manifestCache; |
| 99 | 108 | |
| 100 | 109 | |
| 101 | 110 | /* |
| 102 | 111 | ** Clear the memory allocated in a manifest object |
| 103 | 112 | */ |
| 104 | -void manifest_clear(Manifest *p){ | |
| 105 | - blob_reset(&p->content); | |
| 106 | - free(p->aFile); | |
| 107 | - free(p->azParent); | |
| 108 | - free(p->azCChild); | |
| 109 | - free(p->aTag); | |
| 110 | - free(p->aField); | |
| 111 | - memset(p, 0, sizeof(*p)); | |
| 113 | +void manifest_destroy(Manifest *p){ | |
| 114 | + if( p ){ | |
| 115 | + blob_reset(&p->content); | |
| 116 | + free(p->aFile); | |
| 117 | + free(p->azParent); | |
| 118 | + free(p->azCChild); | |
| 119 | + free(p->aTag); | |
| 120 | + free(p->aField); | |
| 121 | + if( p->pBaseline ) manifest_destroy(p->pBaseline); | |
| 122 | + fossil_free(p); | |
| 123 | + } | |
| 112 | 124 | } |
| 113 | 125 | |
| 114 | 126 | /* |
| 115 | 127 | ** Add an element to the manifest cache using LRU replacement. |
| 116 | 128 | */ |
| 117 | -void manifest_cache_insert(int rid, Manifest *p){ | |
| 118 | - int i; | |
| 119 | - for(i=0; i<MX_MANIFEST_CACHE; i++){ | |
| 120 | - if( manifestCache.aRid[i]==0 ) break; | |
| 121 | - } | |
| 122 | - if( i>=MX_MANIFEST_CACHE ){ | |
| 123 | - int oldest = 0; | |
| 124 | - int oldestAge = manifestCache.aAge[0]; | |
| 125 | - for(i=1; i<MX_MANIFEST_CACHE; i++){ | |
| 126 | - if( manifestCache.aAge[i]<oldestAge ){ | |
| 127 | - oldest = i; | |
| 128 | - oldestAge = manifestCache.aAge[i]; | |
| 129 | - } | |
| 130 | - } | |
| 131 | - manifest_clear(&manifestCache.aLine[oldest]); | |
| 132 | - i = oldest; | |
| 133 | - } | |
| 134 | - manifestCache.aAge[i] = ++manifestCache.nxAge; | |
| 135 | - manifestCache.aRid[i] = rid; | |
| 136 | - manifestCache.aLine[i] = *p; | |
| 129 | +void manifest_cache_insert(Manifest *p){ | |
| 130 | + while( p ){ | |
| 131 | + int i; | |
| 132 | + Manifest *pBaseline = p->pBaseline; | |
| 133 | + p->pBaseline = 0; | |
| 134 | + for(i=0; i<MX_MANIFEST_CACHE; i++){ | |
| 135 | + if( manifestCache.apManifest[i]==0 ) break; | |
| 136 | + } | |
| 137 | + if( i>=MX_MANIFEST_CACHE ){ | |
| 138 | + int oldest = 0; | |
| 139 | + int oldestAge = manifestCache.aAge[0]; | |
| 140 | + for(i=1; i<MX_MANIFEST_CACHE; i++){ | |
| 141 | + if( manifestCache.aAge[i]<oldestAge ){ | |
| 142 | + oldest = i; | |
| 143 | + oldestAge = manifestCache.aAge[i]; | |
| 144 | + } | |
| 145 | + } | |
| 146 | + manifest_destroy(manifestCache.apManifest[oldest]); | |
| 147 | + i = oldest; | |
| 148 | + } | |
| 149 | + manifestCache.aAge[i] = ++manifestCache.nxAge; | |
| 150 | + manifestCache.apManifest[i] = p; | |
| 151 | + p = pBaseline; | |
| 152 | + } | |
| 137 | 153 | } |
| 138 | 154 | |
| 139 | 155 | /* |
| 140 | 156 | ** Try to extract a line from the manifest cache. Return 1 if found. |
| 141 | 157 | ** Return 0 if not found. |
| 142 | 158 | */ |
| 143 | -int manifest_cache_find(int rid, Manifest *p){ | |
| 159 | +static Manifest *manifest_cache_find(int rid){ | |
| 144 | 160 | int i; |
| 161 | + Manifest *p; | |
| 145 | 162 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 146 | - if( manifestCache.aRid[i]==rid ){ | |
| 147 | - *p = manifestCache.aLine[i]; | |
| 148 | - manifestCache.aRid[i] = 0; | |
| 149 | - return 1; | |
| 163 | + if( manifestCache.apManifest[i] && manifestCache.apManifest[i]->rid==rid ){ | |
| 164 | + p = manifestCache.apManifest[i]; | |
| 165 | + manifestCache.apManifest[i] = 0; | |
| 166 | + return p; | |
| 150 | 167 | } |
| 151 | 168 | } |
| 152 | 169 | return 0; |
| 153 | 170 | } |
| 154 | 171 | |
| @@ -156,21 +173,113 @@ | ||
| 156 | 173 | ** Clear the manifest cache. |
| 157 | 174 | */ |
| 158 | 175 | void manifest_cache_clear(void){ |
| 159 | 176 | int i; |
| 160 | 177 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 161 | - if( manifestCache.aRid[i]>0 ){ | |
| 162 | - manifest_clear(&manifestCache.aLine[i]); | |
| 178 | + if( manifestCache.apManifest[i] ){ | |
| 179 | + manifest_destroy(manifestCache.apManifest[i]); | |
| 163 | 180 | } |
| 164 | 181 | } |
| 165 | 182 | memset(&manifestCache, 0, sizeof(manifestCache)); |
| 166 | 183 | } |
| 167 | 184 | |
| 168 | 185 | #ifdef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM |
| 169 | 186 | # define md5sum_init(X) |
| 170 | 187 | # define md5sum_step_text(X,Y) |
| 171 | 188 | #endif |
| 189 | + | |
| 190 | +/* | |
| 191 | +** Remove the PGP signature from the artifact, if there is one. | |
| 192 | +*/ | |
| 193 | +static void remove_pgp_signature(char **pz, int *pn){ | |
| 194 | + char *z = *pz; | |
| 195 | + int n = *pn; | |
| 196 | + int i; | |
| 197 | + if( memcmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return; | |
| 198 | + for(i=34; i<n && (z[i-1]!='\n' || z[i-2]!='\n'); i++){} | |
| 199 | + if( i>=n ) return; | |
| 200 | + z += i; | |
| 201 | + n -= i; | |
| 202 | + *pz = z; | |
| 203 | + for(i=n-1; i>=0; i--){ | |
| 204 | + if( z[i]=='\n' && memcmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){ | |
| 205 | + n = i+1; | |
| 206 | + break; | |
| 207 | + } | |
| 208 | + } | |
| 209 | + *pn = n; | |
| 210 | + return; | |
| 211 | +} | |
| 212 | + | |
| 213 | +/* | |
| 214 | +** Verify the Z-card checksum on the artifact, if there is such a | |
| 215 | +** checksum. Return 0 if there is no Z-card. Return 1 if the Z-card | |
| 216 | +** exists and is correct. Return 2 if the Z-card exists and has the wrong | |
| 217 | +** value. | |
| 218 | +** | |
| 219 | +** 0123456789 123456789 123456789 123456789 | |
| 220 | +** Z aea84f4f863865a8d59d0384e4d2a41c | |
| 221 | +*/ | |
| 222 | +static int verify_z_card(const char *z, int n){ | |
| 223 | + if( n<35 ) return 0; | |
| 224 | + if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0; | |
| 225 | + md5sum_init(); | |
| 226 | + md5sum_step_text(z, n-35); | |
| 227 | + if( memcmp(&z[n-33], md5sum_finish(0), 32)==0 ){ | |
| 228 | + return 1; | |
| 229 | + }else{ | |
| 230 | + return 2; | |
| 231 | + } | |
| 232 | +} | |
| 233 | + | |
| 234 | +/* | |
| 235 | +** A structure used for rapid parsing of the Manifest file | |
| 236 | +*/ | |
| 237 | +typedef struct ManifestText ManifestText; | |
| 238 | +struct ManifestText { | |
| 239 | + char *z; /* The first character of the next token */ | |
| 240 | + char *zEnd; /* One character beyond the end of the manifest */ | |
| 241 | + int atEol; /* True if z points to the start of a new line */ | |
| 242 | +}; | |
| 243 | + | |
| 244 | +/* | |
| 245 | +** Return a pointer to the next token. The token is zero-terminated. | |
| 246 | +** Return NULL if there are no more tokens on the current line. | |
| 247 | +*/ | |
| 248 | +static char *next_token(ManifestText *p, int *pLen){ | |
| 249 | + char *z; | |
| 250 | + char *zStart; | |
| 251 | + int c; | |
| 252 | + if( p->atEol ) return 0; | |
| 253 | + zStart = z = p->z; | |
| 254 | + while( (c=(*z))!=' ' && c!='\n' ){ z++; } | |
| 255 | + *z = 0; | |
| 256 | + p->z = &z[1]; | |
| 257 | + p->atEol = c=='\n'; | |
| 258 | + if( pLen ) *pLen = z - zStart; | |
| 259 | + return zStart; | |
| 260 | +} | |
| 261 | + | |
| 262 | +/* | |
| 263 | +** Return the card-type for the next card. Or, return 0 if there are no | |
| 264 | +** more cards or if we are not at the end of the current card. | |
| 265 | +*/ | |
| 266 | +static char next_card(ManifestText *p){ | |
| 267 | + char c; | |
| 268 | + if( !p->atEol || p->z>=p->zEnd ) return 0; | |
| 269 | + c = p->z[0]; | |
| 270 | + if( p->z[1]==' ' ){ | |
| 271 | + p->z += 2; | |
| 272 | + p->atEol = 0; | |
| 273 | + }else if( p->z[1]=='\n' ){ | |
| 274 | + p->z += 2; | |
| 275 | + p->atEol = 1; | |
| 276 | + }else{ | |
| 277 | + c = 0; | |
| 278 | + } | |
| 279 | + return c; | |
| 280 | +} | |
| 172 | 281 | |
| 173 | 282 | /* |
| 174 | 283 | ** Parse a blob into a Manifest object. The Manifest object |
| 175 | 284 | ** takes over the input blob and will free it when the |
| 176 | 285 | ** Manifest object is freed. Zeros are inserted into the blob |
| @@ -195,56 +304,66 @@ | ||
| 195 | 304 | ** Each card is divided into tokens by a single space character. |
| 196 | 305 | ** The first token is a single upper-case letter which is the card type. |
| 197 | 306 | ** The card type determines the other parameters to the card. |
| 198 | 307 | ** Cards must occur in lexicographical order. |
| 199 | 308 | */ |
| 200 | -int manifest_parse(Manifest *p, Blob *pContent){ | |
| 201 | - int seenHeader = 0; | |
| 309 | +static Manifest *manifest_parse(Blob *pContent, int rid){ | |
| 310 | + Manifest *p; | |
| 202 | 311 | int seenZ = 0; |
| 203 | 312 | int i, lineNo=0; |
| 204 | - Blob line, token, a1, a2, a3, a4; | |
| 313 | + ManifestText x; | |
| 205 | 314 | char cPrevType = 0; |
| 315 | + char cType; | |
| 316 | + char *z; | |
| 317 | + int n; | |
| 318 | + char *zUuid; | |
| 319 | + int sz; | |
| 206 | 320 | |
| 207 | 321 | /* Every control artifact ends with a '\n' character. Exit early |
| 208 | - ** if that is not the case for this artifact. */ | |
| 209 | - i = blob_size(pContent); | |
| 210 | - if( i<=0 || blob_buffer(pContent)[i-1]!='\n' ){ | |
| 322 | + ** if that is not the case for this artifact. | |
| 323 | + */ | |
| 324 | + z = blob_buffer(pContent); | |
| 325 | + n = blob_size(pContent); | |
| 326 | + if( n<=0 || z[n-1]!='\n' ){ | |
| 327 | + blob_reset(pContent); | |
| 328 | + return 0; | |
| 329 | + } | |
| 330 | + | |
| 331 | + /* Strip off the PGP signature if there is one. Then verify the | |
| 332 | + ** Z-card. | |
| 333 | + */ | |
| 334 | + remove_pgp_signature(&z, &n); | |
| 335 | + if( verify_z_card(z, n)==0 ){ | |
| 336 | + blob_reset(pContent); | |
| 337 | + return 0; | |
| 338 | + } | |
| 339 | + | |
| 340 | + /* Verify that the first few characters of the artifact look like | |
| 341 | + ** a control artifact. | |
| 342 | + */ | |
| 343 | + if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){ | |
| 211 | 344 | blob_reset(pContent); |
| 212 | 345 | return 0; |
| 213 | 346 | } |
| 214 | 347 | |
| 348 | + /* Allocate a Manifest object to hold the parsed control artifact. | |
| 349 | + */ | |
| 350 | + p = fossil_malloc( sizeof(*p) ); | |
| 215 | 351 | memset(p, 0, sizeof(*p)); |
| 216 | 352 | memcpy(&p->content, pContent, sizeof(p->content)); |
| 353 | + p->rid = rid; | |
| 217 | 354 | blob_zero(pContent); |
| 218 | 355 | pContent = &p->content; |
| 219 | 356 | |
| 220 | - blob_zero(&a1); | |
| 221 | - blob_zero(&a2); | |
| 222 | - blob_zero(&a3); | |
| 223 | - md5sum_init(); | |
| 224 | - while( blob_line(pContent, &line) ){ | |
| 225 | - char *z = blob_buffer(&line); | |
| 357 | + /* Begin parsing, card by card. | |
| 358 | + */ | |
| 359 | + x.z = z; | |
| 360 | + x.zEnd = &z[n]; | |
| 361 | + x.atEol = 1; | |
| 362 | + while( (cType = next_card(&x))!=0 && cType>=cPrevType ){ | |
| 226 | 363 | lineNo++; |
| 227 | - if( z[0]=='-' ){ | |
| 228 | - if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){ | |
| 229 | - goto manifest_syntax_error; | |
| 230 | - } | |
| 231 | - if( seenHeader ){ | |
| 232 | - break; | |
| 233 | - } | |
| 234 | - while( blob_line(pContent, &line)>2 ){} | |
| 235 | - if( blob_line(pContent, &line)==0 ) break; | |
| 236 | - z = blob_buffer(&line); | |
| 237 | - } | |
| 238 | - if( z[0]<cPrevType ){ | |
| 239 | - /* Lines of a manifest must occur in lexicographical order */ | |
| 240 | - goto manifest_syntax_error; | |
| 241 | - } | |
| 242 | - cPrevType = z[0]; | |
| 243 | - seenHeader = 1; | |
| 244 | - if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error; | |
| 245 | - switch( z[0] ){ | |
| 364 | + switch( cType ){ | |
| 246 | 365 | /* |
| 247 | 366 | ** A <filename> <target> ?<source>? |
| 248 | 367 | ** |
| 249 | 368 | ** Identifies an attachment to either a wiki page or a ticket. |
| 250 | 369 | ** <source> is the artifact that is the attachment. <source> |
| @@ -251,50 +370,60 @@ | ||
| 251 | 370 | ** is omitted to delete an attachment. <target> is the name of |
| 252 | 371 | ** a wiki page or ticket to which that attachment is connected. |
| 253 | 372 | */ |
| 254 | 373 | case 'A': { |
| 255 | 374 | char *zName, *zTarget, *zSrc; |
| 256 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 257 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 258 | - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; | |
| 375 | + int nTarget, nSrc; | |
| 376 | + zName = next_token(&x, 0); | |
| 377 | + zTarget = next_token(&x, &nTarget); | |
| 378 | + zSrc = next_token(&x, &nSrc); | |
| 379 | + if( zName==0 || zTarget==0 ) goto manifest_syntax_error; | |
| 259 | 380 | if( p->zAttachName!=0 ) goto manifest_syntax_error; |
| 260 | - zName = blob_terminate(&a1); | |
| 261 | - zTarget = blob_terminate(&a2); | |
| 262 | - blob_token(&line, &a3); | |
| 263 | - zSrc = blob_terminate(&a3); | |
| 264 | 381 | defossilize(zName); |
| 265 | 382 | if( !file_is_simple_pathname(zName) ){ |
| 266 | 383 | goto manifest_syntax_error; |
| 267 | 384 | } |
| 268 | 385 | defossilize(zTarget); |
| 269 | - if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) | |
| 386 | + if( (nTarget!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) | |
| 270 | 387 | && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){ |
| 271 | 388 | goto manifest_syntax_error; |
| 272 | 389 | } |
| 273 | - if( blob_size(&a3)>0 | |
| 274 | - && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ | |
| 390 | + if( zSrc && (nSrc!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ | |
| 275 | 391 | goto manifest_syntax_error; |
| 276 | 392 | } |
| 277 | 393 | p->zAttachName = (char*)file_tail(zName); |
| 278 | 394 | p->zAttachSrc = zSrc; |
| 279 | 395 | p->zAttachTarget = zTarget; |
| 280 | 396 | break; |
| 281 | 397 | } |
| 398 | + | |
| 399 | + /* | |
| 400 | + ** B <uuid> | |
| 401 | + ** | |
| 402 | + ** A B-line gives the UUID for the baselinen of a delta-manifest. | |
| 403 | + */ | |
| 404 | + case 'B': { | |
| 405 | + if( p->zBaseline ) goto manifest_syntax_error; | |
| 406 | + p->zBaseline = next_token(&x, &sz); | |
| 407 | + if( p->zBaseline==0 ) goto manifest_syntax_error; | |
| 408 | + if( sz!=UUID_SIZE ) goto manifest_syntax_error; | |
| 409 | + if( !validate16(p->zBaseline, UUID_SIZE) ) goto manifest_syntax_error; | |
| 410 | + break; | |
| 411 | + } | |
| 412 | + | |
| 282 | 413 | |
| 283 | 414 | /* |
| 284 | 415 | ** C <comment> |
| 285 | 416 | ** |
| 286 | 417 | ** Comment text is fossil-encoded. There may be no more than |
| 287 | 418 | ** one C line. C lines are required for manifests and are |
| 288 | 419 | ** disallowed on all other control files. |
| 289 | 420 | */ |
| 290 | 421 | case 'C': { |
| 291 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 292 | 422 | if( p->zComment!=0 ) goto manifest_syntax_error; |
| 293 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 294 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 295 | - p->zComment = blob_terminate(&a1); | |
| 423 | + p->zComment = next_token(&x, 0); | |
| 424 | + if( p->zComment==0 ) goto manifest_syntax_error; | |
| 296 | 425 | defossilize(p->zComment); |
| 297 | 426 | break; |
| 298 | 427 | } |
| 299 | 428 | |
| 300 | 429 | /* |
| @@ -303,17 +432,13 @@ | ||
| 303 | 432 | ** The timestamp should be ISO 8601. YYYY-MM-DDtHH:MM:SS |
| 304 | 433 | ** There can be no more than 1 D line. D lines are required |
| 305 | 434 | ** for all control files except for clusters. |
| 306 | 435 | */ |
| 307 | 436 | case 'D': { |
| 308 | - char *zDate; | |
| 309 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 310 | - if( p->rDate!=0.0 ) goto manifest_syntax_error; | |
| 311 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 312 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 313 | - zDate = blob_terminate(&a1); | |
| 314 | - p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); | |
| 437 | + if( p->rDate>0.0 ) goto manifest_syntax_error; | |
| 438 | + p->rDate = db_double(0.0, "SELECT julianday(%Q)", next_token(&x,0)); | |
| 439 | + if( p->rDate<=0.0 ) goto manifest_syntax_error; | |
| 315 | 440 | break; |
| 316 | 441 | } |
| 317 | 442 | |
| 318 | 443 | /* |
| 319 | 444 | ** E <timestamp> <uuid> |
| @@ -323,56 +448,46 @@ | ||
| 323 | 448 | ** The event timestamp is distinct from the D timestamp. The D |
| 324 | 449 | ** timestamp is when the artifact was created whereas the E timestamp |
| 325 | 450 | ** is when the specific event is said to occur. |
| 326 | 451 | */ |
| 327 | 452 | case 'E': { |
| 328 | - char *zEDate; | |
| 329 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 330 | - if( p->rEventDate!=0.0 ) goto manifest_syntax_error; | |
| 331 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 332 | - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; | |
| 333 | - if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; | |
| 334 | - zEDate = blob_terminate(&a1); | |
| 335 | - p->rEventDate = db_double(0.0, "SELECT julianday(%Q)", zEDate); | |
| 453 | + if( p->rEventDate>0.0 ) goto manifest_syntax_error; | |
| 454 | + p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0)); | |
| 336 | 455 | if( p->rEventDate<=0.0 ) goto manifest_syntax_error; |
| 337 | - if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; | |
| 338 | - p->zEventId = blob_terminate(&a2); | |
| 456 | + p->zEventId = next_token(&x, &sz); | |
| 457 | + if( sz!=UUID_SIZE ) goto manifest_syntax_error; | |
| 339 | 458 | if( !validate16(p->zEventId, UUID_SIZE) ) goto manifest_syntax_error; |
| 340 | 459 | break; |
| 341 | 460 | } |
| 342 | 461 | |
| 343 | 462 | /* |
| 344 | - ** F <filename> <uuid> ?<permissions>? ?<old-name>? | |
| 463 | + ** F <filename> ?<uuid>? ?<permissions>? ?<old-name>? | |
| 345 | 464 | ** |
| 346 | 465 | ** Identifies a file in a manifest. Multiple F lines are |
| 347 | 466 | ** allowed in a manifest. F lines are not allowed in any |
| 348 | 467 | ** other control file. The filename and old-name are fossil-encoded. |
| 349 | 468 | */ |
| 350 | 469 | case 'F': { |
| 351 | - char *zName, *zUuid, *zPerm, *zPriorName; | |
| 352 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 353 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 354 | - if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; | |
| 355 | - zName = blob_terminate(&a1); | |
| 356 | - zUuid = blob_terminate(&a2); | |
| 357 | - blob_token(&line, &a3); | |
| 358 | - zPerm = blob_terminate(&a3); | |
| 359 | - if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; | |
| 360 | - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; | |
| 470 | + char *zName, *zPerm, *zPriorName; | |
| 471 | + zName = next_token(&x,0); | |
| 472 | + if( zName==0 ) goto manifest_syntax_error; | |
| 361 | 473 | defossilize(zName); |
| 362 | 474 | if( !file_is_simple_pathname(zName) ){ |
| 363 | 475 | goto manifest_syntax_error; |
| 364 | 476 | } |
| 365 | - blob_token(&line, &a4); | |
| 366 | - zPriorName = blob_terminate(&a4); | |
| 367 | - if( zPriorName[0] ){ | |
| 477 | + zUuid = next_token(&x, &sz); | |
| 478 | + if( p->zBaseline==0 || zUuid!=0 ){ | |
| 479 | + if( sz!=UUID_SIZE ) goto manifest_syntax_error; | |
| 480 | + if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; | |
| 481 | + } | |
| 482 | + zPerm = next_token(&x,0); | |
| 483 | + zPriorName = next_token(&x,0); | |
| 484 | + if( zPriorName ){ | |
| 368 | 485 | defossilize(zPriorName); |
| 369 | 486 | if( !file_is_simple_pathname(zPriorName) ){ |
| 370 | 487 | goto manifest_syntax_error; |
| 371 | 488 | } |
| 372 | - }else{ | |
| 373 | - zPriorName = 0; | |
| 374 | 489 | } |
| 375 | 490 | if( p->nFile>=p->nFileAlloc ){ |
| 376 | 491 | p->nFileAlloc = p->nFileAlloc*2 + 10; |
| 377 | 492 | p->aFile = fossil_realloc(p->aFile, |
| 378 | 493 | p->nFileAlloc*sizeof(p->aFile[0]) ); |
| @@ -380,11 +495,10 @@ | ||
| 380 | 495 | i = p->nFile++; |
| 381 | 496 | p->aFile[i].zName = zName; |
| 382 | 497 | p->aFile[i].zUuid = zUuid; |
| 383 | 498 | p->aFile[i].zPerm = zPerm; |
| 384 | 499 | p->aFile[i].zPrior = zPriorName; |
| 385 | - p->aFile[i].iRename = -1; | |
| 386 | 500 | if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){ |
| 387 | 501 | goto manifest_syntax_error; |
| 388 | 502 | } |
| 389 | 503 | break; |
| 390 | 504 | } |
| @@ -397,16 +511,14 @@ | ||
| 397 | 511 | ** value. If <value> is omitted then it is understood to be an |
| 398 | 512 | ** empty string. |
| 399 | 513 | */ |
| 400 | 514 | case 'J': { |
| 401 | 515 | char *zName, *zValue; |
| 402 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 403 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 404 | - blob_token(&line, &a2); | |
| 405 | - if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; | |
| 406 | - zName = blob_terminate(&a1); | |
| 407 | - zValue = blob_terminate(&a2); | |
| 516 | + zName = next_token(&x,0); | |
| 517 | + zValue = next_token(&x,0); | |
| 518 | + if( zName==0 ) goto manifest_syntax_error; | |
| 519 | + if( zValue==0 ) zValue = ""; | |
| 408 | 520 | defossilize(zValue); |
| 409 | 521 | if( p->nField>=p->nFieldAlloc ){ |
| 410 | 522 | p->nFieldAlloc = p->nFieldAlloc*2 + 10; |
| 411 | 523 | p->aField = fossil_realloc(p->aField, |
| 412 | 524 | p->nFieldAlloc*sizeof(p->aField[0]) ); |
| @@ -426,18 +538,14 @@ | ||
| 426 | 538 | ** |
| 427 | 539 | ** A K-line gives the UUID for the ticket which this control file |
| 428 | 540 | ** is amending. |
| 429 | 541 | */ |
| 430 | 542 | case 'K': { |
| 431 | - char *zUuid; | |
| 432 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 433 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 434 | - zUuid = blob_terminate(&a1); | |
| 435 | - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; | |
| 436 | - if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; | |
| 437 | 543 | if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 438 | - p->zTicketUuid = zUuid; | |
| 544 | + p->zTicketUuid = next_token(&x, &sz); | |
| 545 | + if( sz!=UUID_SIZE ) goto manifest_syntax_error; | |
| 546 | + if( !validate16(p->zTicketUuid, UUID_SIZE) ) goto manifest_syntax_error; | |
| 439 | 547 | break; |
| 440 | 548 | } |
| 441 | 549 | |
| 442 | 550 | /* |
| 443 | 551 | ** L <wikititle> |
| @@ -444,15 +552,13 @@ | ||
| 444 | 552 | ** |
| 445 | 553 | ** The wiki page title is fossil-encoded. There may be no more than |
| 446 | 554 | ** one L line. |
| 447 | 555 | */ |
| 448 | 556 | case 'L': { |
| 449 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 450 | 557 | if( p->zWikiTitle!=0 ) goto manifest_syntax_error; |
| 451 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 452 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 453 | - p->zWikiTitle = blob_terminate(&a1); | |
| 558 | + p->zWikiTitle = next_token(&x,0); | |
| 559 | + if( p->zWikiTitle==0 ) goto manifest_syntax_error; | |
| 454 | 560 | defossilize(p->zWikiTitle); |
| 455 | 561 | if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ |
| 456 | 562 | goto manifest_syntax_error; |
| 457 | 563 | } |
| 458 | 564 | break; |
| @@ -463,15 +569,13 @@ | ||
| 463 | 569 | ** |
| 464 | 570 | ** An M-line identifies another artifact by its UUID. M-lines |
| 465 | 571 | ** occur in clusters only. |
| 466 | 572 | */ |
| 467 | 573 | case 'M': { |
| 468 | - char *zUuid; | |
| 469 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 470 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 471 | - zUuid = blob_terminate(&a1); | |
| 472 | - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; | |
| 574 | + zUuid = next_token(&x, &sz); | |
| 575 | + if( zUuid==0 ) goto manifest_syntax_error; | |
| 576 | + if( sz!=UUID_SIZE ) goto manifest_syntax_error; | |
| 473 | 577 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 474 | 578 | if( p->nCChild>=p->nCChildAlloc ){ |
| 475 | 579 | p->nCChildAlloc = p->nCChildAlloc*2 + 10; |
| 476 | 580 | p->azCChild = fossil_realloc(p->azCChild |
| 477 | 581 | , p->nCChildAlloc*sizeof(p->azCChild[0]) ); |
| @@ -490,15 +594,12 @@ | ||
| 490 | 594 | ** Specify one or more other artifacts where are the parents of |
| 491 | 595 | ** this artifact. The first parent is the primary parent. All |
| 492 | 596 | ** others are parents by merge. |
| 493 | 597 | */ |
| 494 | 598 | case 'P': { |
| 495 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 496 | - while( blob_token(&line, &a1) ){ | |
| 497 | - char *zUuid; | |
| 498 | - if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; | |
| 499 | - zUuid = blob_terminate(&a1); | |
| 599 | + while( (zUuid = next_token(&x, &sz))!=0 ){ | |
| 600 | + if( sz!=UUID_SIZE ) goto manifest_syntax_error; | |
| 500 | 601 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 501 | 602 | if( p->nParent>=p->nParentAlloc ){ |
| 502 | 603 | p->nParentAlloc = p->nParentAlloc*2 + 5; |
| 503 | 604 | p->azParent = fossil_realloc(p->azParent, |
| 504 | 605 | p->nParentAlloc*sizeof(char*)); |
| @@ -514,16 +615,13 @@ | ||
| 514 | 615 | ** |
| 515 | 616 | ** Specify the MD5 checksum over the name and content of all files |
| 516 | 617 | ** in the manifest. |
| 517 | 618 | */ |
| 518 | 619 | case 'R': { |
| 519 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 520 | 620 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 521 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 522 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 523 | - if( blob_size(&a1)!=32 ) goto manifest_syntax_error; | |
| 524 | - p->zRepoCksum = blob_terminate(&a1); | |
| 621 | + p->zRepoCksum = next_token(&x, &sz); | |
| 622 | + if( sz!=32 ) goto manifest_syntax_error; | |
| 525 | 623 | if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; |
| 526 | 624 | break; |
| 527 | 625 | } |
| 528 | 626 | |
| 529 | 627 | /* |
| @@ -540,29 +638,20 @@ | ||
| 540 | 638 | ** the tag is really a property with the given value. |
| 541 | 639 | ** |
| 542 | 640 | ** Tags are not allowed in clusters. Multiple T lines are allowed. |
| 543 | 641 | */ |
| 544 | 642 | case 'T': { |
| 545 | - char *zName, *zUuid, *zValue; | |
| 546 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 547 | - if( blob_token(&line, &a1)==0 ){ | |
| 548 | - goto manifest_syntax_error; | |
| 549 | - } | |
| 550 | - if( blob_token(&line, &a2)==0 ){ | |
| 551 | - goto manifest_syntax_error; | |
| 552 | - } | |
| 553 | - zName = blob_terminate(&a1); | |
| 554 | - zUuid = blob_terminate(&a2); | |
| 555 | - if( blob_token(&line, &a3)==0 ){ | |
| 556 | - zValue = 0; | |
| 557 | - }else{ | |
| 558 | - zValue = blob_terminate(&a3); | |
| 559 | - defossilize(zValue); | |
| 560 | - } | |
| 561 | - if( blob_size(&a2)==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){ | |
| 643 | + char *zName, *zValue; | |
| 644 | + zName = next_token(&x, 0); | |
| 645 | + if( zName==0 ) goto manifest_syntax_error; | |
| 646 | + zUuid = next_token(&x, &sz); | |
| 647 | + if( zUuid==0 ) goto manifest_syntax_error; | |
| 648 | + zValue = next_token(&x, 0); | |
| 649 | + if( zValue ) defossilize(zValue); | |
| 650 | + if( sz==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){ | |
| 562 | 651 | /* A valid uuid */ |
| 563 | - }else if( blob_size(&a2)==1 && zUuid[0]=='*' ){ | |
| 652 | + }else if( sz==1 && zUuid[0]=='*' ){ | |
| 564 | 653 | zUuid = 0; |
| 565 | 654 | }else{ |
| 566 | 655 | goto manifest_syntax_error; |
| 567 | 656 | } |
| 568 | 657 | defossilize(zName); |
| @@ -593,19 +682,17 @@ | ||
| 593 | 682 | ** Identify the user who created this control file by their |
| 594 | 683 | ** login. Only one U line is allowed. Prohibited in clusters. |
| 595 | 684 | ** If the user name is omitted, take that to be "anonymous". |
| 596 | 685 | */ |
| 597 | 686 | case 'U': { |
| 598 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 599 | 687 | if( p->zUser!=0 ) goto manifest_syntax_error; |
| 600 | - if( blob_token(&line, &a1)==0 ){ | |
| 688 | + p->zUser = next_token(&x, 0); | |
| 689 | + if( p->zUser==0 ){ | |
| 601 | 690 | p->zUser = "anonymous"; |
| 602 | 691 | }else{ |
| 603 | - p->zUser = blob_terminate(&a1); | |
| 604 | 692 | defossilize(p->zUser); |
| 605 | 693 | } |
| 606 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 607 | 694 | break; |
| 608 | 695 | } |
| 609 | 696 | |
| 610 | 697 | /* |
| 611 | 698 | ** W <size> |
| @@ -613,26 +700,28 @@ | ||
| 613 | 700 | ** The next <size> bytes of the file contain the text of the wiki |
| 614 | 701 | ** page. There is always an extra \n before the start of the next |
| 615 | 702 | ** record. |
| 616 | 703 | */ |
| 617 | 704 | case 'W': { |
| 618 | - int size; | |
| 705 | + char *zSize; | |
| 706 | + int size, c; | |
| 619 | 707 | Blob wiki; |
| 620 | - md5sum_step_text(blob_buffer(&line), blob_size(&line)); | |
| 621 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 622 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 623 | - if( !blob_is_int(&a1, &size) ) goto manifest_syntax_error; | |
| 708 | + zSize = next_token(&x, 0); | |
| 709 | + if( zSize==0 ) goto manifest_syntax_error; | |
| 710 | + if( x.atEol==0 ) goto manifest_syntax_error; | |
| 711 | + for(size=0; (c = zSize[0])>='0' && c<='9'; zSize++){ | |
| 712 | + size = size*10 + c - '0'; | |
| 713 | + } | |
| 624 | 714 | if( size<0 ) goto manifest_syntax_error; |
| 625 | 715 | if( p->zWiki!=0 ) goto manifest_syntax_error; |
| 626 | 716 | blob_zero(&wiki); |
| 627 | - if( blob_extract(pContent, size+1, &wiki)!=size+1 ){ | |
| 628 | - goto manifest_syntax_error; | |
| 629 | - } | |
| 630 | - p->zWiki = blob_buffer(&wiki); | |
| 631 | - md5sum_step_text(p->zWiki, size+1); | |
| 632 | - if( p->zWiki[size]!='\n' ) goto manifest_syntax_error; | |
| 633 | - p->zWiki[size] = 0; | |
| 717 | + if( (&x.z[size+1])>=x.zEnd ) goto manifest_syntax_error; | |
| 718 | + p->zWiki = x.z; | |
| 719 | + x.z += size; | |
| 720 | + if( x.z[0]!='\n' ) goto manifest_syntax_error; | |
| 721 | + x.z[0] = 0; | |
| 722 | + x.z++; | |
| 634 | 723 | break; |
| 635 | 724 | } |
| 636 | 725 | |
| 637 | 726 | |
| 638 | 727 | /* |
| @@ -645,35 +734,24 @@ | ||
| 645 | 734 | ** This card is required for all control file types except for |
| 646 | 735 | ** Manifest. It is not required for manifest only for historical |
| 647 | 736 | ** compatibility reasons. |
| 648 | 737 | */ |
| 649 | 738 | case 'Z': { |
| 650 | -#ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM | |
| 651 | - int rc; | |
| 652 | - Blob hash; | |
| 653 | -#endif | |
| 654 | - if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; | |
| 655 | - if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; | |
| 656 | - if( blob_size(&a1)!=32 ) goto manifest_syntax_error; | |
| 657 | - if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error; | |
| 658 | -#ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM | |
| 659 | - md5sum_finish(&hash); | |
| 660 | - rc = blob_compare(&hash, &a1); | |
| 661 | - blob_reset(&hash); | |
| 662 | - if( rc!=0 ) goto manifest_syntax_error; | |
| 663 | -#endif | |
| 739 | + zUuid = next_token(&x, &sz); | |
| 740 | + if( sz!=32 ) goto manifest_syntax_error; | |
| 741 | + if( !validate16(zUuid, 32) ) goto manifest_syntax_error; | |
| 664 | 742 | seenZ = 1; |
| 665 | 743 | break; |
| 666 | 744 | } |
| 667 | 745 | default: { |
| 668 | 746 | goto manifest_syntax_error; |
| 669 | 747 | } |
| 670 | 748 | } |
| 671 | 749 | } |
| 672 | - if( !seenHeader ) goto manifest_syntax_error; | |
| 750 | + if( x.z<x.zEnd ) goto manifest_syntax_error; | |
| 673 | 751 | |
| 674 | - if( p->nFile>0 || p->zRepoCksum!=0 ){ | |
| 752 | + if( p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline ){ | |
| 675 | 753 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 676 | 754 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 677 | 755 | if( p->nField>0 ) goto manifest_syntax_error; |
| 678 | 756 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 679 | 757 | if( p->zWiki ) goto manifest_syntax_error; |
| @@ -754,28 +832,72 @@ | ||
| 754 | 832 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 755 | 833 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 756 | 834 | p->type = CFTYPE_MANIFEST; |
| 757 | 835 | } |
| 758 | 836 | md5sum_init(); |
| 759 | - return 1; | |
| 837 | + return p; | |
| 760 | 838 | |
| 761 | 839 | manifest_syntax_error: |
| 762 | 840 | /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ |
| 763 | 841 | md5sum_init(); |
| 764 | - manifest_clear(p); | |
| 842 | + manifest_destroy(p); | |
| 765 | 843 | return 0; |
| 766 | 844 | } |
| 845 | + | |
| 846 | +/* | |
| 847 | +** Get a manifest given the rid for the control artifact. Return | |
| 848 | +** a pointer to the manifest on success or NULL if there is a failure. | |
| 849 | +*/ | |
| 850 | +Manifest *manifest_get(int rid, int cfType){ | |
| 851 | + Blob content; | |
| 852 | + Manifest *p; | |
| 853 | + p = manifest_cache_find(rid); | |
| 854 | + if( p ){ | |
| 855 | + if( cfType!=CFTYPE_ANY && cfType!=p->type ){ | |
| 856 | + manifest_cache_insert(p); | |
| 857 | + p = 0; | |
| 858 | + } | |
| 859 | + return p; | |
| 860 | + } | |
| 861 | + content_get(rid, &content); | |
| 862 | + p = manifest_parse(&content, rid); | |
| 863 | + if( p && cfType!=CFTYPE_ANY && cfType!=p->type ){ | |
| 864 | + manifest_destroy(p); | |
| 865 | + p = 0; | |
| 866 | + } | |
| 867 | + return p; | |
| 868 | +} | |
| 869 | + | |
| 870 | +/* | |
| 871 | +** Given a checkin name, load and parse the manifest for that checkin. | |
| 872 | +** Throw a fatal error if anything goes wrong. | |
| 873 | +*/ | |
| 874 | +Manifest *manifest_get_by_name(const char *zName, int *pRid){ | |
| 875 | + int rid; | |
| 876 | + Manifest *p; | |
| 877 | + | |
| 878 | + rid = name_to_rid(zName); | |
| 879 | + if( !is_a_version(rid) ){ | |
| 880 | + fossil_fatal("no such checkin: %s", zName); | |
| 881 | + } | |
| 882 | + if( pRid ) *pRid = rid; | |
| 883 | + p = manifest_get(rid, CFTYPE_MANIFEST); | |
| 884 | + if( p==0 ){ | |
| 885 | + fossil_fatal("cannot parse manifest for checkin: %s", zName); | |
| 886 | + } | |
| 887 | + return p; | |
| 888 | +} | |
| 767 | 889 | |
| 768 | 890 | /* |
| 769 | 891 | ** COMMAND: test-parse-manifest |
| 770 | 892 | ** |
| 771 | 893 | ** Usage: %fossil test-parse-manifest FILENAME ?N? |
| 772 | 894 | ** |
| 773 | 895 | ** Parse the manifest and discarded. Use for testing only. |
| 774 | 896 | */ |
| 775 | 897 | void manifest_test_parse_cmd(void){ |
| 776 | - Manifest m; | |
| 898 | + Manifest *p; | |
| 777 | 899 | Blob b; |
| 778 | 900 | int i; |
| 779 | 901 | int n = 1; |
| 780 | 902 | if( g.argc!=3 && g.argc!=4 ){ |
| 781 | 903 | usage("FILENAME"); |
| @@ -784,13 +906,101 @@ | ||
| 784 | 906 | blob_read_from_file(&b, g.argv[2]); |
| 785 | 907 | if( g.argc>3 ) n = atoi(g.argv[3]); |
| 786 | 908 | for(i=0; i<n; i++){ |
| 787 | 909 | Blob b2; |
| 788 | 910 | blob_copy(&b2, &b); |
| 789 | - manifest_parse(&m, &b2); | |
| 790 | - manifest_clear(&m); | |
| 911 | + p = manifest_parse(&b2, 0); | |
| 912 | + manifest_destroy(p); | |
| 913 | + } | |
| 914 | +} | |
| 915 | + | |
| 916 | +/* | |
| 917 | +** Fetch the baseline associated with the delta-manifest p. | |
| 918 | +** Print a fatal-error and quit if unable to load the baseline. | |
| 919 | +*/ | |
| 920 | +static void fetch_baseline(Manifest *p){ | |
| 921 | + if( p->zBaseline!=0 && p->pBaseline==0 ){ | |
| 922 | + int rid = uuid_to_rid(p->zBaseline, 0); | |
| 923 | + p->pBaseline = manifest_get(rid, CFTYPE_MANIFEST); | |
| 924 | + if( p->pBaseline==0 ){ | |
| 925 | + fossil_fatal("cannot access baseline manifest %S", p->zBaseline); | |
| 926 | + } | |
| 927 | + } | |
| 928 | +} | |
| 929 | + | |
| 930 | +/* | |
| 931 | +** Rewind a manifest-file iterator back to the beginning of the manifest. | |
| 932 | +*/ | |
| 933 | +void manifest_file_rewind(Manifest *p){ | |
| 934 | + p->iFile = 0; | |
| 935 | + fetch_baseline(p); | |
| 936 | + if( p->pBaseline ){ | |
| 937 | + p->pBaseline->iFile = 0; | |
| 938 | + } | |
| 939 | +} | |
| 940 | + | |
| 941 | +/* | |
| 942 | +** Advance to the next manifest-file. | |
| 943 | +** | |
| 944 | +** Return NULL for end-of-records or if there is an error. If an error | |
| 945 | +** occurs and pErr!=0 then store 1 in *pErr. | |
| 946 | +*/ | |
| 947 | +ManifestFile *manifest_file_next( | |
| 948 | + Manifest *p, | |
| 949 | + int *pErr | |
| 950 | +){ | |
| 951 | + ManifestFile *pOut = 0; | |
| 952 | + if( pErr ) *pErr = 0; | |
| 953 | + if( p->pBaseline==0 ){ | |
| 954 | + /* Manifest p is a baseline-manifest. Just scan down the list | |
| 955 | + ** of files. */ | |
| 956 | + if( p->iFile<p->nFile ) pOut = &p->aFile[p->iFile++]; | |
| 957 | + }else{ | |
| 958 | + /* Manifest p is a delta-manifest. Scan the baseline but amend the | |
| 959 | + ** file list in the baseline with changes described by p. | |
| 960 | + */ | |
| 961 | + Manifest *pB = p->pBaseline; | |
| 962 | + int cmp; | |
| 963 | + while(1){ | |
| 964 | + if( pB->iFile>=pB->nFile ){ | |
| 965 | + /* We have used all entries out of the baseline. Return the next | |
| 966 | + ** entry from the delta. */ | |
| 967 | + if( p->iFile<p->nFile ) pOut = &p->aFile[p->iFile++]; | |
| 968 | + break; | |
| 969 | + }else if( p->iFile>=p->nFile ){ | |
| 970 | + /* We have used all entries from the delta. Return the next | |
| 971 | + ** entry from the baseline. */ | |
| 972 | + if( pB->iFile<pB->nFile ) pOut = &pB->aFile[pB->iFile++]; | |
| 973 | + break; | |
| 974 | + }else if( (cmp = strcmp(pB->aFile[pB->iFile].zName, | |
| 975 | + p->aFile[p->iFile].zName)) < 0 ){ | |
| 976 | + /* The next baseline entry comes before the next delta entry. | |
| 977 | + ** So return the baseline entry. */ | |
| 978 | + pOut = &pB->aFile[pB->iFile++]; | |
| 979 | + break; | |
| 980 | + }else if( cmp>0 ){ | |
| 981 | + /* The next delta entry comes before the next baseline | |
| 982 | + ** entry so return the delta entry */ | |
| 983 | + pOut = &p->aFile[p->iFile++]; | |
| 984 | + break; | |
| 985 | + }else if( p->aFile[p->iFile].zUuid ){ | |
| 986 | + /* The next delta entry is a replacement for the next baseline | |
| 987 | + ** entry. Skip the baseline entry and return the delta entry */ | |
| 988 | + pB->iFile++; | |
| 989 | + pOut = &p->aFile[p->iFile++]; | |
| 990 | + break; | |
| 991 | + }else{ | |
| 992 | + /* The next delta entry is a delete of the next baseline | |
| 993 | + ** entry. Skip them both. Repeat the loop to find the next | |
| 994 | + ** non-delete entry. */ | |
| 995 | + pB->iFile++; | |
| 996 | + p->iFile++; | |
| 997 | + continue; | |
| 998 | + } | |
| 999 | + } | |
| 791 | 1000 | } |
| 1001 | + return pOut; | |
| 792 | 1002 | } |
| 793 | 1003 | |
| 794 | 1004 | /* |
| 795 | 1005 | ** Translate a filename into a filename-id (fnid). Create a new fnid |
| 796 | 1006 | ** if no previously exists. |
| @@ -818,14 +1028,14 @@ | ||
| 818 | 1028 | ** Add a single entry to the mlink table. Also add the filename to |
| 819 | 1029 | ** the filename table if it is not there already. |
| 820 | 1030 | */ |
| 821 | 1031 | static void add_one_mlink( |
| 822 | 1032 | int mid, /* The record ID of the manifest */ |
| 823 | - const char *zFromUuid, /* UUID for the mlink.pid field */ | |
| 824 | - const char *zToUuid, /* UUID for the mlink.fid field */ | |
| 1033 | + const char *zFromUuid, /* UUID for the mlink.pid. "" to add file */ | |
| 1034 | + const char *zToUuid, /* UUID for the mlink.fid. "" to delele */ | |
| 825 | 1035 | const char *zFilename, /* Filename */ |
| 826 | - const char *zPrior /* Previous filename. NULL if unchanged */ | |
| 1036 | + const char *zPrior /* Previous filename. NULL if unchanged */ | |
| 827 | 1037 | ){ |
| 828 | 1038 | int fnid, pfnid, pid, fid; |
| 829 | 1039 | static Stmt s1; |
| 830 | 1040 | |
| 831 | 1041 | fnid = filename_to_fnid(zFilename); |
| @@ -832,16 +1042,16 @@ | ||
| 832 | 1042 | if( zPrior==0 ){ |
| 833 | 1043 | pfnid = 0; |
| 834 | 1044 | }else{ |
| 835 | 1045 | pfnid = filename_to_fnid(zPrior); |
| 836 | 1046 | } |
| 837 | - if( zFromUuid==0 ){ | |
| 1047 | + if( zFromUuid==0 || zFromUuid[0]==0 ){ | |
| 838 | 1048 | pid = 0; |
| 839 | 1049 | }else{ |
| 840 | 1050 | pid = uuid_to_rid(zFromUuid, 1); |
| 841 | 1051 | } |
| 842 | - if( zToUuid==0 ){ | |
| 1052 | + if( zToUuid==0 || zToUuid[0]==0 ){ | |
| 843 | 1053 | fid = 0; |
| 844 | 1054 | }else{ |
| 845 | 1055 | fid = uuid_to_rid(zToUuid, 1); |
| 846 | 1056 | } |
| 847 | 1057 | db_static_prepare(&s1, |
| @@ -858,33 +1068,83 @@ | ||
| 858 | 1068 | content_deltify(pid, fid, 0); |
| 859 | 1069 | } |
| 860 | 1070 | } |
| 861 | 1071 | |
| 862 | 1072 | /* |
| 863 | -** Locate a file named zName in the aFile[] array of the given | |
| 864 | -** manifest. We assume that filenames are in sorted order. | |
| 865 | -** Use a binary search. Return turn the index of the matching | |
| 866 | -** entry. Or return -1 if not found. | |
| 1073 | +** Do a binary search to find a file in the p->aFile[] array. | |
| 1074 | +** | |
| 1075 | +** As an optimization, guess that the file we seek is at index p->iFile. | |
| 1076 | +** That will usually be the case. If it is not found there, then do the | |
| 1077 | +** actual binary search. | |
| 1078 | +** | |
| 1079 | +** Update p->iFile to be the index of the file that is found. | |
| 867 | 1080 | */ |
| 868 | -static int find_file_in_manifest(Manifest *p, const char *zName){ | |
| 1081 | +static ManifestFile *manifest_file_seek_base(Manifest *p, const char *zName){ | |
| 869 | 1082 | int lwr, upr; |
| 870 | 1083 | int c; |
| 871 | 1084 | int i; |
| 872 | 1085 | lwr = 0; |
| 873 | 1086 | upr = p->nFile - 1; |
| 1087 | + if( p->iFile>=lwr && p->iFile<upr ){ | |
| 1088 | + c = strcmp(p->aFile[p->iFile+1].zName, zName); | |
| 1089 | + if( c==0 ){ | |
| 1090 | + return &p->aFile[++p->iFile]; | |
| 1091 | + }else if( c>0 ){ | |
| 1092 | + upr = p->iFile; | |
| 1093 | + }else{ | |
| 1094 | + lwr = p->iFile+1; | |
| 1095 | + } | |
| 1096 | + } | |
| 874 | 1097 | while( lwr<=upr ){ |
| 875 | 1098 | i = (lwr+upr)/2; |
| 876 | 1099 | c = strcmp(p->aFile[i].zName, zName); |
| 877 | 1100 | if( c<0 ){ |
| 878 | 1101 | lwr = i+1; |
| 879 | 1102 | }else if( c>0 ){ |
| 880 | 1103 | upr = i-1; |
| 881 | 1104 | }else{ |
| 882 | - return i; | |
| 1105 | + p->iFile = i; | |
| 1106 | + return &p->aFile[i]; | |
| 883 | 1107 | } |
| 884 | 1108 | } |
| 885 | - return -1; | |
| 1109 | + return 0; | |
| 1110 | +} | |
| 1111 | + | |
| 1112 | +/* | |
| 1113 | +** Locate a file named zName in the aFile[] array of the given manifest. | |
| 1114 | +** Return a pointer to the appropriate ManifestFile object. Return NULL | |
| 1115 | +** if not found. | |
| 1116 | +** | |
| 1117 | +** This routine works even if p is a delta-manifest. The pointer | |
| 1118 | +** returned might be to the baseline. | |
| 1119 | +** | |
| 1120 | +** We assume that filenames are in sorted order and use a binary search. | |
| 1121 | +*/ | |
| 1122 | +ManifestFile *manifest_file_seek(Manifest *p, const char *zName){ | |
| 1123 | + ManifestFile *pFile; | |
| 1124 | + | |
| 1125 | + pFile = manifest_file_seek_base(p, zName); | |
| 1126 | + if( pFile && pFile->zUuid==0 ) return 0; | |
| 1127 | + if( pFile==0 && p->zBaseline ){ | |
| 1128 | + fetch_baseline(p); | |
| 1129 | + pFile = manifest_file_seek_base(p->pBaseline, zName); | |
| 1130 | + } | |
| 1131 | + return pFile; | |
| 1132 | +} | |
| 1133 | + | |
| 1134 | +/* | |
| 1135 | +** This strcmp() function handles NULL arguments. NULLs sort first. | |
| 1136 | +*/ | |
| 1137 | +static int strcmp_null(const char *zOne, const char *zTwo){ | |
| 1138 | + if( zOne==0 ){ | |
| 1139 | + if( zTwo==0 ) return 0; | |
| 1140 | + return -1; | |
| 1141 | + }else if( zTwo==0 ){ | |
| 1142 | + return +1; | |
| 1143 | + }else{ | |
| 1144 | + return strcmp(zOne, zTwo); | |
| 1145 | + } | |
| 886 | 1146 | } |
| 887 | 1147 | |
| 888 | 1148 | /* |
| 889 | 1149 | ** Add mlink table entries associated with manifest cid. The |
| 890 | 1150 | ** parent manifest is pid. |
| @@ -895,89 +1155,70 @@ | ||
| 895 | 1155 | ** Deleted files have mlink.fid=0. |
| 896 | 1156 | ** Added files have mlink.pid=0. |
| 897 | 1157 | ** Edited files have both mlink.pid!=0 and mlink.fid!=0 |
| 898 | 1158 | */ |
| 899 | 1159 | static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){ |
| 900 | - Manifest other; | |
| 901 | 1160 | Blob otherContent; |
| 902 | 1161 | int otherRid; |
| 903 | - int i, j; | |
| 1162 | + int i, rc; | |
| 1163 | + ManifestFile *pChildFile, *pParentFile; | |
| 1164 | + Manifest **ppOther; | |
| 1165 | + static Stmt eq; | |
| 904 | 1166 | |
| 905 | - if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", cid) ){ | |
| 906 | - return; | |
| 907 | - } | |
| 1167 | + db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid"); | |
| 1168 | + db_bind_int(&eq, ":mid", cid); | |
| 1169 | + rc = db_step(&eq); | |
| 1170 | + db_reset(&eq); | |
| 1171 | + if( rc==SQLITE_ROW ) return; | |
| 1172 | + | |
| 908 | 1173 | assert( pParent==0 || pChild==0 ); |
| 909 | 1174 | if( pParent==0 ){ |
| 910 | - pParent = &other; | |
| 1175 | + ppOther = &pParent; | |
| 911 | 1176 | otherRid = pid; |
| 912 | 1177 | }else{ |
| 913 | - pChild = &other; | |
| 1178 | + ppOther = &pChild; | |
| 914 | 1179 | otherRid = cid; |
| 915 | 1180 | } |
| 916 | - if( manifest_cache_find(otherRid, &other)==0 ){ | |
| 1181 | + if( (*ppOther = manifest_cache_find(otherRid))==0 ){ | |
| 917 | 1182 | content_get(otherRid, &otherContent); |
| 918 | 1183 | if( blob_size(&otherContent)==0 ) return; |
| 919 | - if( manifest_parse(&other, &otherContent)==0 ) return; | |
| 920 | - } | |
| 921 | - content_deltify(pid, cid, 0); | |
| 922 | - | |
| 923 | - /* Use the iRename fields to find the cross-linkage between | |
| 924 | - ** renamed files. */ | |
| 925 | - for(j=0; j<pChild->nFile; j++){ | |
| 926 | - const char *zPrior = pChild->aFile[j].zPrior; | |
| 927 | - if( zPrior && zPrior[0] ){ | |
| 928 | - i = find_file_in_manifest(pParent, zPrior); | |
| 929 | - if( i>=0 ){ | |
| 930 | - pChild->aFile[j].iRename = i; | |
| 931 | - pParent->aFile[i].iRename = j; | |
| 932 | - } | |
| 933 | - } | |
| 934 | - } | |
| 935 | - | |
| 936 | - /* Construct the mlink entries */ | |
| 937 | - for(i=j=0; i<pParent->nFile && j<pChild->nFile; ){ | |
| 938 | - int c; | |
| 939 | - if( pParent->aFile[i].iRename>=0 ){ | |
| 940 | - i++; | |
| 941 | - }else if( (c = strcmp(pParent->aFile[i].zName, pChild->aFile[j].zName))<0 ){ | |
| 942 | - add_one_mlink(cid, pParent->aFile[i].zUuid,0,pParent->aFile[i].zName,0); | |
| 943 | - i++; | |
| 944 | - }else if( c>0 ){ | |
| 945 | - int rn = pChild->aFile[j].iRename; | |
| 946 | - if( rn>=0 ){ | |
| 947 | - add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, | |
| 948 | - pChild->aFile[j].zName, pParent->aFile[rn].zName); | |
| 949 | - }else{ | |
| 950 | - add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); | |
| 951 | - } | |
| 952 | - j++; | |
| 953 | - }else{ | |
| 954 | - if( strcmp(pParent->aFile[i].zUuid, pChild->aFile[j].zUuid)!=0 ){ | |
| 955 | - add_one_mlink(cid, pParent->aFile[i].zUuid, pChild->aFile[j].zUuid, | |
| 956 | - pChild->aFile[j].zName, 0); | |
| 957 | - } | |
| 958 | - i++; | |
| 959 | - j++; | |
| 960 | - } | |
| 961 | - } | |
| 962 | - while( i<pParent->nFile ){ | |
| 963 | - if( pParent->aFile[i].iRename<0 ){ | |
| 964 | - add_one_mlink(cid, pParent->aFile[i].zUuid, 0, pParent->aFile[i].zName,0); | |
| 965 | - } | |
| 966 | - i++; | |
| 967 | - } | |
| 968 | - while( j<pChild->nFile ){ | |
| 969 | - int rn = pChild->aFile[j].iRename; | |
| 970 | - if( rn>=0 ){ | |
| 971 | - add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, | |
| 972 | - pChild->aFile[j].zName, pParent->aFile[rn].zName); | |
| 973 | - }else{ | |
| 974 | - add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); | |
| 975 | - } | |
| 976 | - j++; | |
| 977 | - } | |
| 978 | - manifest_cache_insert(otherRid, &other); | |
| 1184 | + *ppOther = manifest_parse(&otherContent, otherRid); | |
| 1185 | + if( *ppOther==0 ) return; | |
| 1186 | + } | |
| 1187 | + if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){ | |
| 1188 | + content_deltify(pid, cid, 0); | |
| 1189 | + } | |
| 1190 | + | |
| 1191 | + for(i=0, pChildFile=pChild->aFile; i<pChild->nFile; i++, pChildFile++){ | |
| 1192 | + if( pChildFile->zPrior ){ | |
| 1193 | + pParentFile = manifest_file_seek(pParent, pChildFile->zPrior); | |
| 1194 | + if( pParentFile ){ | |
| 1195 | + add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, | |
| 1196 | + pChildFile->zName, pChildFile->zPrior); | |
| 1197 | + } | |
| 1198 | + }else{ | |
| 1199 | + pParentFile = manifest_file_seek(pParent, pChildFile->zName); | |
| 1200 | + if( pParentFile==0 ){ | |
| 1201 | + if( pChildFile->zUuid ){ | |
| 1202 | + add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0); | |
| 1203 | + } | |
| 1204 | + }else if( strcmp_null(pChildFile->zUuid, pParentFile->zUuid)!=0 ){ | |
| 1205 | + add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, | |
| 1206 | + pChildFile->zName, 0); | |
| 1207 | + } | |
| 1208 | + } | |
| 1209 | + } | |
| 1210 | + if( pParent->zBaseline && pChild->zBaseline ){ | |
| 1211 | + for(i=0, pParentFile=pParent->aFile; i<pParent->nFile; i++, pParentFile++){ | |
| 1212 | + if( pParentFile->zUuid ) continue; | |
| 1213 | + pChildFile = manifest_file_seek(pChild, pParentFile->zName); | |
| 1214 | + if( pChildFile ){ | |
| 1215 | + add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0); | |
| 1216 | + } | |
| 1217 | + } | |
| 1218 | + } | |
| 1219 | + manifest_cache_insert(*ppOther); | |
| 979 | 1220 | } |
| 980 | 1221 | |
| 981 | 1222 | /* |
| 982 | 1223 | ** True if manifest_crosslink_begin() has been called but |
| 983 | 1224 | ** manifest_crosslink_end() is still pending. |
| @@ -1114,40 +1355,40 @@ | ||
| 1114 | 1355 | ** of the routine, "manifest_crosslink", and the name of this source |
| 1115 | 1356 | ** file, is a legacy of its original use. |
| 1116 | 1357 | */ |
| 1117 | 1358 | int manifest_crosslink(int rid, Blob *pContent){ |
| 1118 | 1359 | int i; |
| 1119 | - Manifest m; | |
| 1360 | + Manifest *p; | |
| 1120 | 1361 | Stmt q; |
| 1121 | 1362 | int parentid = 0; |
| 1122 | 1363 | |
| 1123 | - if( manifest_cache_find(rid, &m) ){ | |
| 1364 | + if( (p = manifest_cache_find(rid))!=0 ){ | |
| 1124 | 1365 | blob_reset(pContent); |
| 1125 | - }else if( manifest_parse(&m, pContent)==0 ){ | |
| 1366 | + }else if( (p = manifest_parse(pContent, rid))==0 ){ | |
| 1126 | 1367 | return 0; |
| 1127 | 1368 | } |
| 1128 | - if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){ | |
| 1129 | - manifest_clear(&m); | |
| 1369 | + if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){ | |
| 1370 | + manifest_destroy(p); | |
| 1130 | 1371 | return 0; |
| 1131 | 1372 | } |
| 1132 | 1373 | db_begin_transaction(); |
| 1133 | - if( m.type==CFTYPE_MANIFEST ){ | |
| 1374 | + if( p->type==CFTYPE_MANIFEST ){ | |
| 1134 | 1375 | if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ |
| 1135 | 1376 | char *zCom; |
| 1136 | - for(i=0; i<m.nParent; i++){ | |
| 1137 | - int pid = uuid_to_rid(m.azParent[i], 1); | |
| 1377 | + for(i=0; i<p->nParent; i++){ | |
| 1378 | + int pid = uuid_to_rid(p->azParent[i], 1); | |
| 1138 | 1379 | db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" |
| 1139 | - "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate); | |
| 1380 | + "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate); | |
| 1140 | 1381 | if( i==0 ){ |
| 1141 | - add_mlink(pid, 0, rid, &m); | |
| 1382 | + add_mlink(pid, 0, rid, p); | |
| 1142 | 1383 | parentid = pid; |
| 1143 | 1384 | } |
| 1144 | 1385 | } |
| 1145 | 1386 | db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid); |
| 1146 | 1387 | while( db_step(&q)==SQLITE_ROW ){ |
| 1147 | 1388 | int cid = db_column_int(&q, 0); |
| 1148 | - add_mlink(rid, &m, cid, 0); | |
| 1389 | + add_mlink(rid, p, cid, 0); | |
| 1149 | 1390 | } |
| 1150 | 1391 | db_finalize(&q); |
| 1151 | 1392 | db_multi_exec( |
| 1152 | 1393 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1153 | 1394 | "bgcolor,euser,ecomment)" |
| @@ -1158,118 +1399,130 @@ | ||
| 1158 | 1399 | " )," |
| 1159 | 1400 | " %d,%Q,%Q," |
| 1160 | 1401 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0)," |
| 1161 | 1402 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," |
| 1162 | 1403 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1163 | - TAG_DATE, rid, m.rDate, | |
| 1164 | - rid, m.zUser, m.zComment, | |
| 1404 | + TAG_DATE, rid, p->rDate, | |
| 1405 | + rid, p->zUser, p->zComment, | |
| 1165 | 1406 | TAG_BGCOLOR, rid, |
| 1166 | 1407 | TAG_USER, rid, |
| 1167 | 1408 | TAG_COMMENT, rid |
| 1168 | 1409 | ); |
| 1169 | 1410 | zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" |
| 1170 | 1411 | " WHERE rowid=last_insert_rowid()"); |
| 1171 | - wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE); | |
| 1412 | + wiki_extract_links(zCom, rid, 0, p->rDate, 1, WIKI_INLINE); | |
| 1172 | 1413 | free(zCom); |
| 1414 | + | |
| 1415 | + /* If this is a delta-manifest, record the fact that this repository | |
| 1416 | + ** contains delta manifests, to free the "commit" logic to generate | |
| 1417 | + ** new delta manifests. | |
| 1418 | + */ | |
| 1419 | + if( p->zBaseline!=0 ){ | |
| 1420 | + static int once = 0; | |
| 1421 | + if( !once ){ | |
| 1422 | + db_set_int("seen-delta-manifest", 1, 0); | |
| 1423 | + once = 0; | |
| 1424 | + } | |
| 1425 | + } | |
| 1173 | 1426 | } |
| 1174 | 1427 | } |
| 1175 | - if( m.type==CFTYPE_CLUSTER ){ | |
| 1176 | - tag_insert("cluster", 1, 0, rid, m.rDate, rid); | |
| 1177 | - for(i=0; i<m.nCChild; i++){ | |
| 1428 | + if( p->type==CFTYPE_CLUSTER ){ | |
| 1429 | + tag_insert("cluster", 1, 0, rid, p->rDate, rid); | |
| 1430 | + for(i=0; i<p->nCChild; i++){ | |
| 1178 | 1431 | int mid; |
| 1179 | - mid = uuid_to_rid(m.azCChild[i], 1); | |
| 1432 | + mid = uuid_to_rid(p->azCChild[i], 1); | |
| 1180 | 1433 | if( mid>0 ){ |
| 1181 | 1434 | db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid); |
| 1182 | 1435 | } |
| 1183 | 1436 | } |
| 1184 | 1437 | } |
| 1185 | - if( m.type==CFTYPE_CONTROL | |
| 1186 | - || m.type==CFTYPE_MANIFEST | |
| 1187 | - || m.type==CFTYPE_EVENT | |
| 1438 | + if( p->type==CFTYPE_CONTROL | |
| 1439 | + || p->type==CFTYPE_MANIFEST | |
| 1440 | + || p->type==CFTYPE_EVENT | |
| 1188 | 1441 | ){ |
| 1189 | - for(i=0; i<m.nTag; i++){ | |
| 1442 | + for(i=0; i<p->nTag; i++){ | |
| 1190 | 1443 | int tid; |
| 1191 | 1444 | int type; |
| 1192 | - if( m.aTag[i].zUuid ){ | |
| 1193 | - tid = uuid_to_rid(m.aTag[i].zUuid, 1); | |
| 1445 | + if( p->aTag[i].zUuid ){ | |
| 1446 | + tid = uuid_to_rid(p->aTag[i].zUuid, 1); | |
| 1194 | 1447 | }else{ |
| 1195 | 1448 | tid = rid; |
| 1196 | 1449 | } |
| 1197 | 1450 | if( tid ){ |
| 1198 | - switch( m.aTag[i].zName[0] ){ | |
| 1451 | + switch( p->aTag[i].zName[0] ){ | |
| 1199 | 1452 | case '-': type = 0; break; /* Cancel prior occurances */ |
| 1200 | 1453 | case '+': type = 1; break; /* Apply to target only */ |
| 1201 | 1454 | case '*': type = 2; break; /* Propagate to descendants */ |
| 1202 | 1455 | default: |
| 1203 | - fossil_fatal("unknown tag type in manifest: %s", m.aTag); | |
| 1456 | + fossil_fatal("unknown tag type in manifest: %s", p->aTag); | |
| 1204 | 1457 | return 0; |
| 1205 | 1458 | } |
| 1206 | - tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue, | |
| 1207 | - rid, m.rDate, tid); | |
| 1459 | + tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, | |
| 1460 | + rid, p->rDate, tid); | |
| 1208 | 1461 | } |
| 1209 | 1462 | } |
| 1210 | 1463 | if( parentid ){ |
| 1211 | 1464 | tag_propagate_all(parentid); |
| 1212 | 1465 | } |
| 1213 | 1466 | } |
| 1214 | - if( m.type==CFTYPE_WIKI ){ | |
| 1215 | - char *zTag = mprintf("wiki-%s", m.zWikiTitle); | |
| 1467 | + if( p->type==CFTYPE_WIKI ){ | |
| 1468 | + char *zTag = mprintf("wiki-%s", p->zWikiTitle); | |
| 1216 | 1469 | int tagid = tag_findid(zTag, 1); |
| 1217 | 1470 | int prior; |
| 1218 | 1471 | char *zComment; |
| 1219 | 1472 | int nWiki; |
| 1220 | 1473 | char zLength[40]; |
| 1221 | - while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; | |
| 1222 | - nWiki = strlen(m.zWiki); | |
| 1474 | + while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; | |
| 1475 | + nWiki = strlen(p->zWiki); | |
| 1223 | 1476 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 1224 | - tag_insert(zTag, 1, zLength, rid, m.rDate, rid); | |
| 1477 | + tag_insert(zTag, 1, zLength, rid, p->rDate, rid); | |
| 1225 | 1478 | free(zTag); |
| 1226 | 1479 | prior = db_int(0, |
| 1227 | 1480 | "SELECT rid FROM tagxref" |
| 1228 | 1481 | " WHERE tagid=%d AND mtime<%.17g" |
| 1229 | 1482 | " ORDER BY mtime DESC", |
| 1230 | - tagid, m.rDate | |
| 1483 | + tagid, p->rDate | |
| 1231 | 1484 | ); |
| 1232 | 1485 | if( prior ){ |
| 1233 | 1486 | content_deltify(prior, rid, 0); |
| 1234 | 1487 | } |
| 1235 | 1488 | if( nWiki>0 ){ |
| 1236 | - zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); | |
| 1489 | + zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); | |
| 1237 | 1490 | }else{ |
| 1238 | - zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle); | |
| 1491 | + zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); | |
| 1239 | 1492 | } |
| 1240 | 1493 | db_multi_exec( |
| 1241 | 1494 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1242 | 1495 | " bgcolor,euser,ecomment)" |
| 1243 | 1496 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1244 | 1497 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| 1245 | 1498 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," |
| 1246 | 1499 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1247 | - m.rDate, rid, m.zUser, zComment, | |
| 1500 | + p->rDate, rid, p->zUser, zComment, | |
| 1248 | 1501 | TAG_BGCOLOR, rid, |
| 1249 | 1502 | TAG_BGCOLOR, rid, |
| 1250 | 1503 | TAG_USER, rid, |
| 1251 | 1504 | TAG_COMMENT, rid |
| 1252 | 1505 | ); |
| 1253 | 1506 | free(zComment); |
| 1254 | 1507 | } |
| 1255 | - if( m.type==CFTYPE_EVENT ){ | |
| 1256 | - char *zTag = mprintf("event-%s", m.zEventId); | |
| 1508 | + if( p->type==CFTYPE_EVENT ){ | |
| 1509 | + char *zTag = mprintf("event-%s", p->zEventId); | |
| 1257 | 1510 | int tagid = tag_findid(zTag, 1); |
| 1258 | 1511 | int prior, subsequent; |
| 1259 | 1512 | int nWiki; |
| 1260 | 1513 | char zLength[40]; |
| 1261 | - while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; | |
| 1262 | - nWiki = strlen(m.zWiki); | |
| 1514 | + while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; | |
| 1515 | + nWiki = strlen(p->zWiki); | |
| 1263 | 1516 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 1264 | - tag_insert(zTag, 1, zLength, rid, m.rDate, rid); | |
| 1517 | + tag_insert(zTag, 1, zLength, rid, p->rDate, rid); | |
| 1265 | 1518 | free(zTag); |
| 1266 | 1519 | prior = db_int(0, |
| 1267 | 1520 | "SELECT rid FROM tagxref" |
| 1268 | 1521 | " WHERE tagid=%d AND mtime<%.17g" |
| 1269 | 1522 | " ORDER BY mtime DESC", |
| 1270 | - tagid, m.rDate | |
| 1523 | + tagid, p->rDate | |
| 1271 | 1524 | ); |
| 1272 | 1525 | if( prior ){ |
| 1273 | 1526 | content_deltify(prior, rid, 0); |
| 1274 | 1527 | db_multi_exec( |
| 1275 | 1528 | "DELETE FROM event" |
| @@ -1281,108 +1534,87 @@ | ||
| 1281 | 1534 | } |
| 1282 | 1535 | subsequent = db_int(0, |
| 1283 | 1536 | "SELECT rid FROM tagxref" |
| 1284 | 1537 | " WHERE tagid=%d AND mtime>%.17g" |
| 1285 | 1538 | " ORDER BY mtime", |
| 1286 | - tagid, m.rDate | |
| 1539 | + tagid, p->rDate | |
| 1287 | 1540 | ); |
| 1288 | 1541 | if( subsequent ){ |
| 1289 | 1542 | content_deltify(rid, subsequent, 0); |
| 1290 | 1543 | }else{ |
| 1291 | 1544 | db_multi_exec( |
| 1292 | 1545 | "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" |
| 1293 | 1546 | "VALUES('e',%.17g,%d,%d,%Q,%Q," |
| 1294 | 1547 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1295 | - m.rEventDate, rid, tagid, m.zUser, m.zComment, | |
| 1548 | + p->rEventDate, rid, tagid, p->zUser, p->zComment, | |
| 1296 | 1549 | TAG_BGCOLOR, rid |
| 1297 | 1550 | ); |
| 1298 | 1551 | } |
| 1299 | 1552 | } |
| 1300 | - if( m.type==CFTYPE_TICKET ){ | |
| 1553 | + if( p->type==CFTYPE_TICKET ){ | |
| 1301 | 1554 | char *zTag; |
| 1302 | 1555 | |
| 1303 | 1556 | assert( manifest_crosslink_busy==1 ); |
| 1304 | - zTag = mprintf("tkt-%s", m.zTicketUuid); | |
| 1305 | - tag_insert(zTag, 1, 0, rid, m.rDate, rid); | |
| 1557 | + zTag = mprintf("tkt-%s", p->zTicketUuid); | |
| 1558 | + tag_insert(zTag, 1, 0, rid, p->rDate, rid); | |
| 1306 | 1559 | free(zTag); |
| 1307 | 1560 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 1308 | - m.zTicketUuid); | |
| 1561 | + p->zTicketUuid); | |
| 1309 | 1562 | } |
| 1310 | - if( m.type==CFTYPE_ATTACHMENT ){ | |
| 1563 | + if( p->type==CFTYPE_ATTACHMENT ){ | |
| 1311 | 1564 | db_multi_exec( |
| 1312 | 1565 | "INSERT INTO attachment(attachid, mtime, src, target," |
| 1313 | 1566 | "filename, comment, user)" |
| 1314 | 1567 | "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", |
| 1315 | - rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName, | |
| 1316 | - (m.zComment ? m.zComment : ""), m.zUser | |
| 1568 | + rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName, | |
| 1569 | + (p->zComment ? p->zComment : ""), p->zUser | |
| 1317 | 1570 | ); |
| 1318 | 1571 | db_multi_exec( |
| 1319 | 1572 | "UPDATE attachment SET isLatest = (mtime==" |
| 1320 | 1573 | "(SELECT max(mtime) FROM attachment" |
| 1321 | 1574 | " WHERE target=%Q AND filename=%Q))" |
| 1322 | 1575 | " WHERE target=%Q AND filename=%Q", |
| 1323 | - m.zAttachTarget, m.zAttachName, | |
| 1324 | - m.zAttachTarget, m.zAttachName | |
| 1576 | + p->zAttachTarget, p->zAttachName, | |
| 1577 | + p->zAttachTarget, p->zAttachName | |
| 1325 | 1578 | ); |
| 1326 | - if( strlen(m.zAttachTarget)!=UUID_SIZE | |
| 1327 | - || !validate16(m.zAttachTarget, UUID_SIZE) | |
| 1579 | + if( strlen(p->zAttachTarget)!=UUID_SIZE | |
| 1580 | + || !validate16(p->zAttachTarget, UUID_SIZE) | |
| 1328 | 1581 | ){ |
| 1329 | 1582 | char *zComment; |
| 1330 | - if( m.zAttachSrc && m.zAttachSrc[0] ){ | |
| 1583 | + if( p->zAttachSrc && p->zAttachSrc[0] ){ | |
| 1331 | 1584 | zComment = mprintf("Add attachment \"%h\" to wiki page [%h]", |
| 1332 | - m.zAttachName, m.zAttachTarget); | |
| 1585 | + p->zAttachName, p->zAttachTarget); | |
| 1333 | 1586 | }else{ |
| 1334 | 1587 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 1335 | - m.zAttachName, m.zAttachTarget); | |
| 1588 | + p->zAttachName, p->zAttachTarget); | |
| 1336 | 1589 | } |
| 1337 | 1590 | db_multi_exec( |
| 1338 | 1591 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1339 | 1592 | "VALUES('w',%.17g,%d,%Q,%Q)", |
| 1340 | - m.rDate, rid, m.zUser, zComment | |
| 1593 | + p->rDate, rid, p->zUser, zComment | |
| 1341 | 1594 | ); |
| 1342 | 1595 | free(zComment); |
| 1343 | 1596 | }else{ |
| 1344 | 1597 | char *zComment; |
| 1345 | - if( m.zAttachSrc && m.zAttachSrc[0] ){ | |
| 1598 | + if( p->zAttachSrc && p->zAttachSrc[0] ){ | |
| 1346 | 1599 | zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]", |
| 1347 | - m.zAttachName, m.zAttachTarget); | |
| 1600 | + p->zAttachName, p->zAttachTarget); | |
| 1348 | 1601 | }else{ |
| 1349 | 1602 | zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", |
| 1350 | - m.zAttachName, m.zAttachTarget); | |
| 1603 | + p->zAttachName, p->zAttachTarget); | |
| 1351 | 1604 | } |
| 1352 | 1605 | db_multi_exec( |
| 1353 | 1606 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1354 | 1607 | "VALUES('t',%.17g,%d,%Q,%Q)", |
| 1355 | - m.rDate, rid, m.zUser, zComment | |
| 1608 | + p->rDate, rid, p->zUser, zComment | |
| 1356 | 1609 | ); |
| 1357 | 1610 | free(zComment); |
| 1358 | 1611 | } |
| 1359 | 1612 | } |
| 1360 | 1613 | db_end_transaction(0); |
| 1361 | - if( m.type==CFTYPE_MANIFEST ){ | |
| 1362 | - manifest_cache_insert(rid, &m); | |
| 1614 | + if( p->type==CFTYPE_MANIFEST ){ | |
| 1615 | + manifest_cache_insert(p); | |
| 1363 | 1616 | }else{ |
| 1364 | - manifest_clear(&m); | |
| 1617 | + manifest_destroy(p); | |
| 1365 | 1618 | } |
| 1366 | 1619 | return 1; |
| 1367 | 1620 | } |
| 1368 | - | |
| 1369 | -/* | |
| 1370 | -** Given a checkin name, load and parse the manifest for that checkin. | |
| 1371 | -** Throw a fatal error if anything goes wrong. | |
| 1372 | -*/ | |
| 1373 | -void manifest_from_name( | |
| 1374 | - const char *zName, | |
| 1375 | - Manifest *pM | |
| 1376 | -){ | |
| 1377 | - int rid; | |
| 1378 | - Blob content; | |
| 1379 | - | |
| 1380 | - rid = name_to_rid(zName); | |
| 1381 | - if( !is_a_version(rid) ){ | |
| 1382 | - fossil_fatal("no such checkin: %s", zName); | |
| 1383 | - } | |
| 1384 | - content_get(rid, &content); | |
| 1385 | - if( !manifest_parse(pM, &content) ){ | |
| 1386 | - fossil_fatal("cannot parse manifest for checkin: %s", zName); | |
| 1387 | - } | |
| 1388 | -} | |
| 1389 | 1621 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -26,24 +26,39 @@ | |
| 26 | |
| 27 | #if INTERFACE |
| 28 | /* |
| 29 | ** Types of control files |
| 30 | */ |
| 31 | #define CFTYPE_MANIFEST 1 |
| 32 | #define CFTYPE_CLUSTER 2 |
| 33 | #define CFTYPE_CONTROL 3 |
| 34 | #define CFTYPE_WIKI 4 |
| 35 | #define CFTYPE_TICKET 5 |
| 36 | #define CFTYPE_ATTACHMENT 6 |
| 37 | #define CFTYPE_EVENT 7 |
| 38 | |
| 39 | /* |
| 40 | ** A parsed manifest or cluster. |
| 41 | */ |
| 42 | struct Manifest { |
| 43 | Blob content; /* The original content blob */ |
| 44 | int type; /* Type of artifact. One of CFTYPE_xxxxx */ |
| 45 | char *zComment; /* Decoded comment. The C card. */ |
| 46 | double rDate; /* Date and time from D card. 0.0 if no D card. */ |
| 47 | char *zUser; /* Name of the user from the U card. */ |
| 48 | char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ |
| 49 | char *zWiki; /* Text of the wiki page. W card. */ |
| @@ -54,17 +69,12 @@ | |
| 54 | char *zAttachName; /* Filename of an attachment. A card. */ |
| 55 | char *zAttachSrc; /* UUID of document being attached. A card. */ |
| 56 | char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ |
| 57 | int nFile; /* Number of F cards */ |
| 58 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 59 | struct ManifestFile { |
| 60 | char *zName; /* Name of a file */ |
| 61 | char *zUuid; /* UUID of the file */ |
| 62 | char *zPerm; /* File permissions */ |
| 63 | char *zPrior; /* Prior name if the name was changed */ |
| 64 | int iRename; /* index of renamed name in prior/next manifest */ |
| 65 | } *aFile; /* One entry for each F card */ |
| 66 | int nParent; /* Number of parents. */ |
| 67 | int nParentAlloc; /* Slots allocated in azParent[] */ |
| 68 | char **azParent; /* UUIDs of parents. One for each P card argument */ |
| 69 | int nCChild; /* Number of cluster children */ |
| 70 | int nCChildAlloc; /* Number of closts allocated in azCChild[] */ |
| @@ -87,68 +97,75 @@ | |
| 87 | |
| 88 | /* |
| 89 | ** A cache of parsed manifests. This reduces the number of |
| 90 | ** calls to manifest_parse() when doing a rebuild. |
| 91 | */ |
| 92 | #define MX_MANIFEST_CACHE 4 |
| 93 | static struct { |
| 94 | int nxAge; |
| 95 | int aRid[MX_MANIFEST_CACHE]; |
| 96 | int aAge[MX_MANIFEST_CACHE]; |
| 97 | Manifest aLine[MX_MANIFEST_CACHE]; |
| 98 | } manifestCache; |
| 99 | |
| 100 | |
| 101 | /* |
| 102 | ** Clear the memory allocated in a manifest object |
| 103 | */ |
| 104 | void manifest_clear(Manifest *p){ |
| 105 | blob_reset(&p->content); |
| 106 | free(p->aFile); |
| 107 | free(p->azParent); |
| 108 | free(p->azCChild); |
| 109 | free(p->aTag); |
| 110 | free(p->aField); |
| 111 | memset(p, 0, sizeof(*p)); |
| 112 | } |
| 113 | |
| 114 | /* |
| 115 | ** Add an element to the manifest cache using LRU replacement. |
| 116 | */ |
| 117 | void manifest_cache_insert(int rid, Manifest *p){ |
| 118 | int i; |
| 119 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 120 | if( manifestCache.aRid[i]==0 ) break; |
| 121 | } |
| 122 | if( i>=MX_MANIFEST_CACHE ){ |
| 123 | int oldest = 0; |
| 124 | int oldestAge = manifestCache.aAge[0]; |
| 125 | for(i=1; i<MX_MANIFEST_CACHE; i++){ |
| 126 | if( manifestCache.aAge[i]<oldestAge ){ |
| 127 | oldest = i; |
| 128 | oldestAge = manifestCache.aAge[i]; |
| 129 | } |
| 130 | } |
| 131 | manifest_clear(&manifestCache.aLine[oldest]); |
| 132 | i = oldest; |
| 133 | } |
| 134 | manifestCache.aAge[i] = ++manifestCache.nxAge; |
| 135 | manifestCache.aRid[i] = rid; |
| 136 | manifestCache.aLine[i] = *p; |
| 137 | } |
| 138 | |
| 139 | /* |
| 140 | ** Try to extract a line from the manifest cache. Return 1 if found. |
| 141 | ** Return 0 if not found. |
| 142 | */ |
| 143 | int manifest_cache_find(int rid, Manifest *p){ |
| 144 | int i; |
| 145 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 146 | if( manifestCache.aRid[i]==rid ){ |
| 147 | *p = manifestCache.aLine[i]; |
| 148 | manifestCache.aRid[i] = 0; |
| 149 | return 1; |
| 150 | } |
| 151 | } |
| 152 | return 0; |
| 153 | } |
| 154 | |
| @@ -156,21 +173,113 @@ | |
| 156 | ** Clear the manifest cache. |
| 157 | */ |
| 158 | void manifest_cache_clear(void){ |
| 159 | int i; |
| 160 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 161 | if( manifestCache.aRid[i]>0 ){ |
| 162 | manifest_clear(&manifestCache.aLine[i]); |
| 163 | } |
| 164 | } |
| 165 | memset(&manifestCache, 0, sizeof(manifestCache)); |
| 166 | } |
| 167 | |
| 168 | #ifdef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM |
| 169 | # define md5sum_init(X) |
| 170 | # define md5sum_step_text(X,Y) |
| 171 | #endif |
| 172 | |
| 173 | /* |
| 174 | ** Parse a blob into a Manifest object. The Manifest object |
| 175 | ** takes over the input blob and will free it when the |
| 176 | ** Manifest object is freed. Zeros are inserted into the blob |
| @@ -195,56 +304,66 @@ | |
| 195 | ** Each card is divided into tokens by a single space character. |
| 196 | ** The first token is a single upper-case letter which is the card type. |
| 197 | ** The card type determines the other parameters to the card. |
| 198 | ** Cards must occur in lexicographical order. |
| 199 | */ |
| 200 | int manifest_parse(Manifest *p, Blob *pContent){ |
| 201 | int seenHeader = 0; |
| 202 | int seenZ = 0; |
| 203 | int i, lineNo=0; |
| 204 | Blob line, token, a1, a2, a3, a4; |
| 205 | char cPrevType = 0; |
| 206 | |
| 207 | /* Every control artifact ends with a '\n' character. Exit early |
| 208 | ** if that is not the case for this artifact. */ |
| 209 | i = blob_size(pContent); |
| 210 | if( i<=0 || blob_buffer(pContent)[i-1]!='\n' ){ |
| 211 | blob_reset(pContent); |
| 212 | return 0; |
| 213 | } |
| 214 | |
| 215 | memset(p, 0, sizeof(*p)); |
| 216 | memcpy(&p->content, pContent, sizeof(p->content)); |
| 217 | blob_zero(pContent); |
| 218 | pContent = &p->content; |
| 219 | |
| 220 | blob_zero(&a1); |
| 221 | blob_zero(&a2); |
| 222 | blob_zero(&a3); |
| 223 | md5sum_init(); |
| 224 | while( blob_line(pContent, &line) ){ |
| 225 | char *z = blob_buffer(&line); |
| 226 | lineNo++; |
| 227 | if( z[0]=='-' ){ |
| 228 | if( strncmp(z, "-----BEGIN PGP ", 15)!=0 ){ |
| 229 | goto manifest_syntax_error; |
| 230 | } |
| 231 | if( seenHeader ){ |
| 232 | break; |
| 233 | } |
| 234 | while( blob_line(pContent, &line)>2 ){} |
| 235 | if( blob_line(pContent, &line)==0 ) break; |
| 236 | z = blob_buffer(&line); |
| 237 | } |
| 238 | if( z[0]<cPrevType ){ |
| 239 | /* Lines of a manifest must occur in lexicographical order */ |
| 240 | goto manifest_syntax_error; |
| 241 | } |
| 242 | cPrevType = z[0]; |
| 243 | seenHeader = 1; |
| 244 | if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error; |
| 245 | switch( z[0] ){ |
| 246 | /* |
| 247 | ** A <filename> <target> ?<source>? |
| 248 | ** |
| 249 | ** Identifies an attachment to either a wiki page or a ticket. |
| 250 | ** <source> is the artifact that is the attachment. <source> |
| @@ -251,50 +370,60 @@ | |
| 251 | ** is omitted to delete an attachment. <target> is the name of |
| 252 | ** a wiki page or ticket to which that attachment is connected. |
| 253 | */ |
| 254 | case 'A': { |
| 255 | char *zName, *zTarget, *zSrc; |
| 256 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 257 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 258 | if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 259 | if( p->zAttachName!=0 ) goto manifest_syntax_error; |
| 260 | zName = blob_terminate(&a1); |
| 261 | zTarget = blob_terminate(&a2); |
| 262 | blob_token(&line, &a3); |
| 263 | zSrc = blob_terminate(&a3); |
| 264 | defossilize(zName); |
| 265 | if( !file_is_simple_pathname(zName) ){ |
| 266 | goto manifest_syntax_error; |
| 267 | } |
| 268 | defossilize(zTarget); |
| 269 | if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) |
| 270 | && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){ |
| 271 | goto manifest_syntax_error; |
| 272 | } |
| 273 | if( blob_size(&a3)>0 |
| 274 | && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ |
| 275 | goto manifest_syntax_error; |
| 276 | } |
| 277 | p->zAttachName = (char*)file_tail(zName); |
| 278 | p->zAttachSrc = zSrc; |
| 279 | p->zAttachTarget = zTarget; |
| 280 | break; |
| 281 | } |
| 282 | |
| 283 | /* |
| 284 | ** C <comment> |
| 285 | ** |
| 286 | ** Comment text is fossil-encoded. There may be no more than |
| 287 | ** one C line. C lines are required for manifests and are |
| 288 | ** disallowed on all other control files. |
| 289 | */ |
| 290 | case 'C': { |
| 291 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 292 | if( p->zComment!=0 ) goto manifest_syntax_error; |
| 293 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 294 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 295 | p->zComment = blob_terminate(&a1); |
| 296 | defossilize(p->zComment); |
| 297 | break; |
| 298 | } |
| 299 | |
| 300 | /* |
| @@ -303,17 +432,13 @@ | |
| 303 | ** The timestamp should be ISO 8601. YYYY-MM-DDtHH:MM:SS |
| 304 | ** There can be no more than 1 D line. D lines are required |
| 305 | ** for all control files except for clusters. |
| 306 | */ |
| 307 | case 'D': { |
| 308 | char *zDate; |
| 309 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 310 | if( p->rDate!=0.0 ) goto manifest_syntax_error; |
| 311 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 312 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 313 | zDate = blob_terminate(&a1); |
| 314 | p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate); |
| 315 | break; |
| 316 | } |
| 317 | |
| 318 | /* |
| 319 | ** E <timestamp> <uuid> |
| @@ -323,56 +448,46 @@ | |
| 323 | ** The event timestamp is distinct from the D timestamp. The D |
| 324 | ** timestamp is when the artifact was created whereas the E timestamp |
| 325 | ** is when the specific event is said to occur. |
| 326 | */ |
| 327 | case 'E': { |
| 328 | char *zEDate; |
| 329 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 330 | if( p->rEventDate!=0.0 ) goto manifest_syntax_error; |
| 331 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 332 | if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 333 | if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; |
| 334 | zEDate = blob_terminate(&a1); |
| 335 | p->rEventDate = db_double(0.0, "SELECT julianday(%Q)", zEDate); |
| 336 | if( p->rEventDate<=0.0 ) goto manifest_syntax_error; |
| 337 | if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; |
| 338 | p->zEventId = blob_terminate(&a2); |
| 339 | if( !validate16(p->zEventId, UUID_SIZE) ) goto manifest_syntax_error; |
| 340 | break; |
| 341 | } |
| 342 | |
| 343 | /* |
| 344 | ** F <filename> <uuid> ?<permissions>? ?<old-name>? |
| 345 | ** |
| 346 | ** Identifies a file in a manifest. Multiple F lines are |
| 347 | ** allowed in a manifest. F lines are not allowed in any |
| 348 | ** other control file. The filename and old-name are fossil-encoded. |
| 349 | */ |
| 350 | case 'F': { |
| 351 | char *zName, *zUuid, *zPerm, *zPriorName; |
| 352 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 353 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 354 | if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error; |
| 355 | zName = blob_terminate(&a1); |
| 356 | zUuid = blob_terminate(&a2); |
| 357 | blob_token(&line, &a3); |
| 358 | zPerm = blob_terminate(&a3); |
| 359 | if( blob_size(&a2)!=UUID_SIZE ) goto manifest_syntax_error; |
| 360 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 361 | defossilize(zName); |
| 362 | if( !file_is_simple_pathname(zName) ){ |
| 363 | goto manifest_syntax_error; |
| 364 | } |
| 365 | blob_token(&line, &a4); |
| 366 | zPriorName = blob_terminate(&a4); |
| 367 | if( zPriorName[0] ){ |
| 368 | defossilize(zPriorName); |
| 369 | if( !file_is_simple_pathname(zPriorName) ){ |
| 370 | goto manifest_syntax_error; |
| 371 | } |
| 372 | }else{ |
| 373 | zPriorName = 0; |
| 374 | } |
| 375 | if( p->nFile>=p->nFileAlloc ){ |
| 376 | p->nFileAlloc = p->nFileAlloc*2 + 10; |
| 377 | p->aFile = fossil_realloc(p->aFile, |
| 378 | p->nFileAlloc*sizeof(p->aFile[0]) ); |
| @@ -380,11 +495,10 @@ | |
| 380 | i = p->nFile++; |
| 381 | p->aFile[i].zName = zName; |
| 382 | p->aFile[i].zUuid = zUuid; |
| 383 | p->aFile[i].zPerm = zPerm; |
| 384 | p->aFile[i].zPrior = zPriorName; |
| 385 | p->aFile[i].iRename = -1; |
| 386 | if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){ |
| 387 | goto manifest_syntax_error; |
| 388 | } |
| 389 | break; |
| 390 | } |
| @@ -397,16 +511,14 @@ | |
| 397 | ** value. If <value> is omitted then it is understood to be an |
| 398 | ** empty string. |
| 399 | */ |
| 400 | case 'J': { |
| 401 | char *zName, *zValue; |
| 402 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 403 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 404 | blob_token(&line, &a2); |
| 405 | if( blob_token(&line, &a3)!=0 ) goto manifest_syntax_error; |
| 406 | zName = blob_terminate(&a1); |
| 407 | zValue = blob_terminate(&a2); |
| 408 | defossilize(zValue); |
| 409 | if( p->nField>=p->nFieldAlloc ){ |
| 410 | p->nFieldAlloc = p->nFieldAlloc*2 + 10; |
| 411 | p->aField = fossil_realloc(p->aField, |
| 412 | p->nFieldAlloc*sizeof(p->aField[0]) ); |
| @@ -426,18 +538,14 @@ | |
| 426 | ** |
| 427 | ** A K-line gives the UUID for the ticket which this control file |
| 428 | ** is amending. |
| 429 | */ |
| 430 | case 'K': { |
| 431 | char *zUuid; |
| 432 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 433 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 434 | zUuid = blob_terminate(&a1); |
| 435 | if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 436 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 437 | if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 438 | p->zTicketUuid = zUuid; |
| 439 | break; |
| 440 | } |
| 441 | |
| 442 | /* |
| 443 | ** L <wikititle> |
| @@ -444,15 +552,13 @@ | |
| 444 | ** |
| 445 | ** The wiki page title is fossil-encoded. There may be no more than |
| 446 | ** one L line. |
| 447 | */ |
| 448 | case 'L': { |
| 449 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 450 | if( p->zWikiTitle!=0 ) goto manifest_syntax_error; |
| 451 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 452 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 453 | p->zWikiTitle = blob_terminate(&a1); |
| 454 | defossilize(p->zWikiTitle); |
| 455 | if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ |
| 456 | goto manifest_syntax_error; |
| 457 | } |
| 458 | break; |
| @@ -463,15 +569,13 @@ | |
| 463 | ** |
| 464 | ** An M-line identifies another artifact by its UUID. M-lines |
| 465 | ** occur in clusters only. |
| 466 | */ |
| 467 | case 'M': { |
| 468 | char *zUuid; |
| 469 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 470 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 471 | zUuid = blob_terminate(&a1); |
| 472 | if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 473 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 474 | if( p->nCChild>=p->nCChildAlloc ){ |
| 475 | p->nCChildAlloc = p->nCChildAlloc*2 + 10; |
| 476 | p->azCChild = fossil_realloc(p->azCChild |
| 477 | , p->nCChildAlloc*sizeof(p->azCChild[0]) ); |
| @@ -490,15 +594,12 @@ | |
| 490 | ** Specify one or more other artifacts where are the parents of |
| 491 | ** this artifact. The first parent is the primary parent. All |
| 492 | ** others are parents by merge. |
| 493 | */ |
| 494 | case 'P': { |
| 495 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 496 | while( blob_token(&line, &a1) ){ |
| 497 | char *zUuid; |
| 498 | if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error; |
| 499 | zUuid = blob_terminate(&a1); |
| 500 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 501 | if( p->nParent>=p->nParentAlloc ){ |
| 502 | p->nParentAlloc = p->nParentAlloc*2 + 5; |
| 503 | p->azParent = fossil_realloc(p->azParent, |
| 504 | p->nParentAlloc*sizeof(char*)); |
| @@ -514,16 +615,13 @@ | |
| 514 | ** |
| 515 | ** Specify the MD5 checksum over the name and content of all files |
| 516 | ** in the manifest. |
| 517 | */ |
| 518 | case 'R': { |
| 519 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 520 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 521 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 522 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 523 | if( blob_size(&a1)!=32 ) goto manifest_syntax_error; |
| 524 | p->zRepoCksum = blob_terminate(&a1); |
| 525 | if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; |
| 526 | break; |
| 527 | } |
| 528 | |
| 529 | /* |
| @@ -540,29 +638,20 @@ | |
| 540 | ** the tag is really a property with the given value. |
| 541 | ** |
| 542 | ** Tags are not allowed in clusters. Multiple T lines are allowed. |
| 543 | */ |
| 544 | case 'T': { |
| 545 | char *zName, *zUuid, *zValue; |
| 546 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 547 | if( blob_token(&line, &a1)==0 ){ |
| 548 | goto manifest_syntax_error; |
| 549 | } |
| 550 | if( blob_token(&line, &a2)==0 ){ |
| 551 | goto manifest_syntax_error; |
| 552 | } |
| 553 | zName = blob_terminate(&a1); |
| 554 | zUuid = blob_terminate(&a2); |
| 555 | if( blob_token(&line, &a3)==0 ){ |
| 556 | zValue = 0; |
| 557 | }else{ |
| 558 | zValue = blob_terminate(&a3); |
| 559 | defossilize(zValue); |
| 560 | } |
| 561 | if( blob_size(&a2)==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){ |
| 562 | /* A valid uuid */ |
| 563 | }else if( blob_size(&a2)==1 && zUuid[0]=='*' ){ |
| 564 | zUuid = 0; |
| 565 | }else{ |
| 566 | goto manifest_syntax_error; |
| 567 | } |
| 568 | defossilize(zName); |
| @@ -593,19 +682,17 @@ | |
| 593 | ** Identify the user who created this control file by their |
| 594 | ** login. Only one U line is allowed. Prohibited in clusters. |
| 595 | ** If the user name is omitted, take that to be "anonymous". |
| 596 | */ |
| 597 | case 'U': { |
| 598 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 599 | if( p->zUser!=0 ) goto manifest_syntax_error; |
| 600 | if( blob_token(&line, &a1)==0 ){ |
| 601 | p->zUser = "anonymous"; |
| 602 | }else{ |
| 603 | p->zUser = blob_terminate(&a1); |
| 604 | defossilize(p->zUser); |
| 605 | } |
| 606 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 607 | break; |
| 608 | } |
| 609 | |
| 610 | /* |
| 611 | ** W <size> |
| @@ -613,26 +700,28 @@ | |
| 613 | ** The next <size> bytes of the file contain the text of the wiki |
| 614 | ** page. There is always an extra \n before the start of the next |
| 615 | ** record. |
| 616 | */ |
| 617 | case 'W': { |
| 618 | int size; |
| 619 | Blob wiki; |
| 620 | md5sum_step_text(blob_buffer(&line), blob_size(&line)); |
| 621 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 622 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 623 | if( !blob_is_int(&a1, &size) ) goto manifest_syntax_error; |
| 624 | if( size<0 ) goto manifest_syntax_error; |
| 625 | if( p->zWiki!=0 ) goto manifest_syntax_error; |
| 626 | blob_zero(&wiki); |
| 627 | if( blob_extract(pContent, size+1, &wiki)!=size+1 ){ |
| 628 | goto manifest_syntax_error; |
| 629 | } |
| 630 | p->zWiki = blob_buffer(&wiki); |
| 631 | md5sum_step_text(p->zWiki, size+1); |
| 632 | if( p->zWiki[size]!='\n' ) goto manifest_syntax_error; |
| 633 | p->zWiki[size] = 0; |
| 634 | break; |
| 635 | } |
| 636 | |
| 637 | |
| 638 | /* |
| @@ -645,35 +734,24 @@ | |
| 645 | ** This card is required for all control file types except for |
| 646 | ** Manifest. It is not required for manifest only for historical |
| 647 | ** compatibility reasons. |
| 648 | */ |
| 649 | case 'Z': { |
| 650 | #ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM |
| 651 | int rc; |
| 652 | Blob hash; |
| 653 | #endif |
| 654 | if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error; |
| 655 | if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error; |
| 656 | if( blob_size(&a1)!=32 ) goto manifest_syntax_error; |
| 657 | if( !validate16(blob_buffer(&a1), 32) ) goto manifest_syntax_error; |
| 658 | #ifndef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM |
| 659 | md5sum_finish(&hash); |
| 660 | rc = blob_compare(&hash, &a1); |
| 661 | blob_reset(&hash); |
| 662 | if( rc!=0 ) goto manifest_syntax_error; |
| 663 | #endif |
| 664 | seenZ = 1; |
| 665 | break; |
| 666 | } |
| 667 | default: { |
| 668 | goto manifest_syntax_error; |
| 669 | } |
| 670 | } |
| 671 | } |
| 672 | if( !seenHeader ) goto manifest_syntax_error; |
| 673 | |
| 674 | if( p->nFile>0 || p->zRepoCksum!=0 ){ |
| 675 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 676 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 677 | if( p->nField>0 ) goto manifest_syntax_error; |
| 678 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 679 | if( p->zWiki ) goto manifest_syntax_error; |
| @@ -754,28 +832,72 @@ | |
| 754 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 755 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 756 | p->type = CFTYPE_MANIFEST; |
| 757 | } |
| 758 | md5sum_init(); |
| 759 | return 1; |
| 760 | |
| 761 | manifest_syntax_error: |
| 762 | /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ |
| 763 | md5sum_init(); |
| 764 | manifest_clear(p); |
| 765 | return 0; |
| 766 | } |
| 767 | |
| 768 | /* |
| 769 | ** COMMAND: test-parse-manifest |
| 770 | ** |
| 771 | ** Usage: %fossil test-parse-manifest FILENAME ?N? |
| 772 | ** |
| 773 | ** Parse the manifest and discarded. Use for testing only. |
| 774 | */ |
| 775 | void manifest_test_parse_cmd(void){ |
| 776 | Manifest m; |
| 777 | Blob b; |
| 778 | int i; |
| 779 | int n = 1; |
| 780 | if( g.argc!=3 && g.argc!=4 ){ |
| 781 | usage("FILENAME"); |
| @@ -784,13 +906,101 @@ | |
| 784 | blob_read_from_file(&b, g.argv[2]); |
| 785 | if( g.argc>3 ) n = atoi(g.argv[3]); |
| 786 | for(i=0; i<n; i++){ |
| 787 | Blob b2; |
| 788 | blob_copy(&b2, &b); |
| 789 | manifest_parse(&m, &b2); |
| 790 | manifest_clear(&m); |
| 791 | } |
| 792 | } |
| 793 | |
| 794 | /* |
| 795 | ** Translate a filename into a filename-id (fnid). Create a new fnid |
| 796 | ** if no previously exists. |
| @@ -818,14 +1028,14 @@ | |
| 818 | ** Add a single entry to the mlink table. Also add the filename to |
| 819 | ** the filename table if it is not there already. |
| 820 | */ |
| 821 | static void add_one_mlink( |
| 822 | int mid, /* The record ID of the manifest */ |
| 823 | const char *zFromUuid, /* UUID for the mlink.pid field */ |
| 824 | const char *zToUuid, /* UUID for the mlink.fid field */ |
| 825 | const char *zFilename, /* Filename */ |
| 826 | const char *zPrior /* Previous filename. NULL if unchanged */ |
| 827 | ){ |
| 828 | int fnid, pfnid, pid, fid; |
| 829 | static Stmt s1; |
| 830 | |
| 831 | fnid = filename_to_fnid(zFilename); |
| @@ -832,16 +1042,16 @@ | |
| 832 | if( zPrior==0 ){ |
| 833 | pfnid = 0; |
| 834 | }else{ |
| 835 | pfnid = filename_to_fnid(zPrior); |
| 836 | } |
| 837 | if( zFromUuid==0 ){ |
| 838 | pid = 0; |
| 839 | }else{ |
| 840 | pid = uuid_to_rid(zFromUuid, 1); |
| 841 | } |
| 842 | if( zToUuid==0 ){ |
| 843 | fid = 0; |
| 844 | }else{ |
| 845 | fid = uuid_to_rid(zToUuid, 1); |
| 846 | } |
| 847 | db_static_prepare(&s1, |
| @@ -858,33 +1068,83 @@ | |
| 858 | content_deltify(pid, fid, 0); |
| 859 | } |
| 860 | } |
| 861 | |
| 862 | /* |
| 863 | ** Locate a file named zName in the aFile[] array of the given |
| 864 | ** manifest. We assume that filenames are in sorted order. |
| 865 | ** Use a binary search. Return turn the index of the matching |
| 866 | ** entry. Or return -1 if not found. |
| 867 | */ |
| 868 | static int find_file_in_manifest(Manifest *p, const char *zName){ |
| 869 | int lwr, upr; |
| 870 | int c; |
| 871 | int i; |
| 872 | lwr = 0; |
| 873 | upr = p->nFile - 1; |
| 874 | while( lwr<=upr ){ |
| 875 | i = (lwr+upr)/2; |
| 876 | c = strcmp(p->aFile[i].zName, zName); |
| 877 | if( c<0 ){ |
| 878 | lwr = i+1; |
| 879 | }else if( c>0 ){ |
| 880 | upr = i-1; |
| 881 | }else{ |
| 882 | return i; |
| 883 | } |
| 884 | } |
| 885 | return -1; |
| 886 | } |
| 887 | |
| 888 | /* |
| 889 | ** Add mlink table entries associated with manifest cid. The |
| 890 | ** parent manifest is pid. |
| @@ -895,89 +1155,70 @@ | |
| 895 | ** Deleted files have mlink.fid=0. |
| 896 | ** Added files have mlink.pid=0. |
| 897 | ** Edited files have both mlink.pid!=0 and mlink.fid!=0 |
| 898 | */ |
| 899 | static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){ |
| 900 | Manifest other; |
| 901 | Blob otherContent; |
| 902 | int otherRid; |
| 903 | int i, j; |
| 904 | |
| 905 | if( db_exists("SELECT 1 FROM mlink WHERE mid=%d", cid) ){ |
| 906 | return; |
| 907 | } |
| 908 | assert( pParent==0 || pChild==0 ); |
| 909 | if( pParent==0 ){ |
| 910 | pParent = &other; |
| 911 | otherRid = pid; |
| 912 | }else{ |
| 913 | pChild = &other; |
| 914 | otherRid = cid; |
| 915 | } |
| 916 | if( manifest_cache_find(otherRid, &other)==0 ){ |
| 917 | content_get(otherRid, &otherContent); |
| 918 | if( blob_size(&otherContent)==0 ) return; |
| 919 | if( manifest_parse(&other, &otherContent)==0 ) return; |
| 920 | } |
| 921 | content_deltify(pid, cid, 0); |
| 922 | |
| 923 | /* Use the iRename fields to find the cross-linkage between |
| 924 | ** renamed files. */ |
| 925 | for(j=0; j<pChild->nFile; j++){ |
| 926 | const char *zPrior = pChild->aFile[j].zPrior; |
| 927 | if( zPrior && zPrior[0] ){ |
| 928 | i = find_file_in_manifest(pParent, zPrior); |
| 929 | if( i>=0 ){ |
| 930 | pChild->aFile[j].iRename = i; |
| 931 | pParent->aFile[i].iRename = j; |
| 932 | } |
| 933 | } |
| 934 | } |
| 935 | |
| 936 | /* Construct the mlink entries */ |
| 937 | for(i=j=0; i<pParent->nFile && j<pChild->nFile; ){ |
| 938 | int c; |
| 939 | if( pParent->aFile[i].iRename>=0 ){ |
| 940 | i++; |
| 941 | }else if( (c = strcmp(pParent->aFile[i].zName, pChild->aFile[j].zName))<0 ){ |
| 942 | add_one_mlink(cid, pParent->aFile[i].zUuid,0,pParent->aFile[i].zName,0); |
| 943 | i++; |
| 944 | }else if( c>0 ){ |
| 945 | int rn = pChild->aFile[j].iRename; |
| 946 | if( rn>=0 ){ |
| 947 | add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, |
| 948 | pChild->aFile[j].zName, pParent->aFile[rn].zName); |
| 949 | }else{ |
| 950 | add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); |
| 951 | } |
| 952 | j++; |
| 953 | }else{ |
| 954 | if( strcmp(pParent->aFile[i].zUuid, pChild->aFile[j].zUuid)!=0 ){ |
| 955 | add_one_mlink(cid, pParent->aFile[i].zUuid, pChild->aFile[j].zUuid, |
| 956 | pChild->aFile[j].zName, 0); |
| 957 | } |
| 958 | i++; |
| 959 | j++; |
| 960 | } |
| 961 | } |
| 962 | while( i<pParent->nFile ){ |
| 963 | if( pParent->aFile[i].iRename<0 ){ |
| 964 | add_one_mlink(cid, pParent->aFile[i].zUuid, 0, pParent->aFile[i].zName,0); |
| 965 | } |
| 966 | i++; |
| 967 | } |
| 968 | while( j<pChild->nFile ){ |
| 969 | int rn = pChild->aFile[j].iRename; |
| 970 | if( rn>=0 ){ |
| 971 | add_one_mlink(cid, pParent->aFile[rn].zUuid, pChild->aFile[j].zUuid, |
| 972 | pChild->aFile[j].zName, pParent->aFile[rn].zName); |
| 973 | }else{ |
| 974 | add_one_mlink(cid, 0, pChild->aFile[j].zUuid, pChild->aFile[j].zName,0); |
| 975 | } |
| 976 | j++; |
| 977 | } |
| 978 | manifest_cache_insert(otherRid, &other); |
| 979 | } |
| 980 | |
| 981 | /* |
| 982 | ** True if manifest_crosslink_begin() has been called but |
| 983 | ** manifest_crosslink_end() is still pending. |
| @@ -1114,40 +1355,40 @@ | |
| 1114 | ** of the routine, "manifest_crosslink", and the name of this source |
| 1115 | ** file, is a legacy of its original use. |
| 1116 | */ |
| 1117 | int manifest_crosslink(int rid, Blob *pContent){ |
| 1118 | int i; |
| 1119 | Manifest m; |
| 1120 | Stmt q; |
| 1121 | int parentid = 0; |
| 1122 | |
| 1123 | if( manifest_cache_find(rid, &m) ){ |
| 1124 | blob_reset(pContent); |
| 1125 | }else if( manifest_parse(&m, pContent)==0 ){ |
| 1126 | return 0; |
| 1127 | } |
| 1128 | if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){ |
| 1129 | manifest_clear(&m); |
| 1130 | return 0; |
| 1131 | } |
| 1132 | db_begin_transaction(); |
| 1133 | if( m.type==CFTYPE_MANIFEST ){ |
| 1134 | if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ |
| 1135 | char *zCom; |
| 1136 | for(i=0; i<m.nParent; i++){ |
| 1137 | int pid = uuid_to_rid(m.azParent[i], 1); |
| 1138 | db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" |
| 1139 | "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate); |
| 1140 | if( i==0 ){ |
| 1141 | add_mlink(pid, 0, rid, &m); |
| 1142 | parentid = pid; |
| 1143 | } |
| 1144 | } |
| 1145 | db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid); |
| 1146 | while( db_step(&q)==SQLITE_ROW ){ |
| 1147 | int cid = db_column_int(&q, 0); |
| 1148 | add_mlink(rid, &m, cid, 0); |
| 1149 | } |
| 1150 | db_finalize(&q); |
| 1151 | db_multi_exec( |
| 1152 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1153 | "bgcolor,euser,ecomment)" |
| @@ -1158,118 +1399,130 @@ | |
| 1158 | " )," |
| 1159 | " %d,%Q,%Q," |
| 1160 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0)," |
| 1161 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," |
| 1162 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1163 | TAG_DATE, rid, m.rDate, |
| 1164 | rid, m.zUser, m.zComment, |
| 1165 | TAG_BGCOLOR, rid, |
| 1166 | TAG_USER, rid, |
| 1167 | TAG_COMMENT, rid |
| 1168 | ); |
| 1169 | zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" |
| 1170 | " WHERE rowid=last_insert_rowid()"); |
| 1171 | wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE); |
| 1172 | free(zCom); |
| 1173 | } |
| 1174 | } |
| 1175 | if( m.type==CFTYPE_CLUSTER ){ |
| 1176 | tag_insert("cluster", 1, 0, rid, m.rDate, rid); |
| 1177 | for(i=0; i<m.nCChild; i++){ |
| 1178 | int mid; |
| 1179 | mid = uuid_to_rid(m.azCChild[i], 1); |
| 1180 | if( mid>0 ){ |
| 1181 | db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid); |
| 1182 | } |
| 1183 | } |
| 1184 | } |
| 1185 | if( m.type==CFTYPE_CONTROL |
| 1186 | || m.type==CFTYPE_MANIFEST |
| 1187 | || m.type==CFTYPE_EVENT |
| 1188 | ){ |
| 1189 | for(i=0; i<m.nTag; i++){ |
| 1190 | int tid; |
| 1191 | int type; |
| 1192 | if( m.aTag[i].zUuid ){ |
| 1193 | tid = uuid_to_rid(m.aTag[i].zUuid, 1); |
| 1194 | }else{ |
| 1195 | tid = rid; |
| 1196 | } |
| 1197 | if( tid ){ |
| 1198 | switch( m.aTag[i].zName[0] ){ |
| 1199 | case '-': type = 0; break; /* Cancel prior occurances */ |
| 1200 | case '+': type = 1; break; /* Apply to target only */ |
| 1201 | case '*': type = 2; break; /* Propagate to descendants */ |
| 1202 | default: |
| 1203 | fossil_fatal("unknown tag type in manifest: %s", m.aTag); |
| 1204 | return 0; |
| 1205 | } |
| 1206 | tag_insert(&m.aTag[i].zName[1], type, m.aTag[i].zValue, |
| 1207 | rid, m.rDate, tid); |
| 1208 | } |
| 1209 | } |
| 1210 | if( parentid ){ |
| 1211 | tag_propagate_all(parentid); |
| 1212 | } |
| 1213 | } |
| 1214 | if( m.type==CFTYPE_WIKI ){ |
| 1215 | char *zTag = mprintf("wiki-%s", m.zWikiTitle); |
| 1216 | int tagid = tag_findid(zTag, 1); |
| 1217 | int prior; |
| 1218 | char *zComment; |
| 1219 | int nWiki; |
| 1220 | char zLength[40]; |
| 1221 | while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; |
| 1222 | nWiki = strlen(m.zWiki); |
| 1223 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 1224 | tag_insert(zTag, 1, zLength, rid, m.rDate, rid); |
| 1225 | free(zTag); |
| 1226 | prior = db_int(0, |
| 1227 | "SELECT rid FROM tagxref" |
| 1228 | " WHERE tagid=%d AND mtime<%.17g" |
| 1229 | " ORDER BY mtime DESC", |
| 1230 | tagid, m.rDate |
| 1231 | ); |
| 1232 | if( prior ){ |
| 1233 | content_deltify(prior, rid, 0); |
| 1234 | } |
| 1235 | if( nWiki>0 ){ |
| 1236 | zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle); |
| 1237 | }else{ |
| 1238 | zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle); |
| 1239 | } |
| 1240 | db_multi_exec( |
| 1241 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1242 | " bgcolor,euser,ecomment)" |
| 1243 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1244 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| 1245 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," |
| 1246 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1247 | m.rDate, rid, m.zUser, zComment, |
| 1248 | TAG_BGCOLOR, rid, |
| 1249 | TAG_BGCOLOR, rid, |
| 1250 | TAG_USER, rid, |
| 1251 | TAG_COMMENT, rid |
| 1252 | ); |
| 1253 | free(zComment); |
| 1254 | } |
| 1255 | if( m.type==CFTYPE_EVENT ){ |
| 1256 | char *zTag = mprintf("event-%s", m.zEventId); |
| 1257 | int tagid = tag_findid(zTag, 1); |
| 1258 | int prior, subsequent; |
| 1259 | int nWiki; |
| 1260 | char zLength[40]; |
| 1261 | while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; |
| 1262 | nWiki = strlen(m.zWiki); |
| 1263 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 1264 | tag_insert(zTag, 1, zLength, rid, m.rDate, rid); |
| 1265 | free(zTag); |
| 1266 | prior = db_int(0, |
| 1267 | "SELECT rid FROM tagxref" |
| 1268 | " WHERE tagid=%d AND mtime<%.17g" |
| 1269 | " ORDER BY mtime DESC", |
| 1270 | tagid, m.rDate |
| 1271 | ); |
| 1272 | if( prior ){ |
| 1273 | content_deltify(prior, rid, 0); |
| 1274 | db_multi_exec( |
| 1275 | "DELETE FROM event" |
| @@ -1281,108 +1534,87 @@ | |
| 1281 | } |
| 1282 | subsequent = db_int(0, |
| 1283 | "SELECT rid FROM tagxref" |
| 1284 | " WHERE tagid=%d AND mtime>%.17g" |
| 1285 | " ORDER BY mtime", |
| 1286 | tagid, m.rDate |
| 1287 | ); |
| 1288 | if( subsequent ){ |
| 1289 | content_deltify(rid, subsequent, 0); |
| 1290 | }else{ |
| 1291 | db_multi_exec( |
| 1292 | "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" |
| 1293 | "VALUES('e',%.17g,%d,%d,%Q,%Q," |
| 1294 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1295 | m.rEventDate, rid, tagid, m.zUser, m.zComment, |
| 1296 | TAG_BGCOLOR, rid |
| 1297 | ); |
| 1298 | } |
| 1299 | } |
| 1300 | if( m.type==CFTYPE_TICKET ){ |
| 1301 | char *zTag; |
| 1302 | |
| 1303 | assert( manifest_crosslink_busy==1 ); |
| 1304 | zTag = mprintf("tkt-%s", m.zTicketUuid); |
| 1305 | tag_insert(zTag, 1, 0, rid, m.rDate, rid); |
| 1306 | free(zTag); |
| 1307 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 1308 | m.zTicketUuid); |
| 1309 | } |
| 1310 | if( m.type==CFTYPE_ATTACHMENT ){ |
| 1311 | db_multi_exec( |
| 1312 | "INSERT INTO attachment(attachid, mtime, src, target," |
| 1313 | "filename, comment, user)" |
| 1314 | "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", |
| 1315 | rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName, |
| 1316 | (m.zComment ? m.zComment : ""), m.zUser |
| 1317 | ); |
| 1318 | db_multi_exec( |
| 1319 | "UPDATE attachment SET isLatest = (mtime==" |
| 1320 | "(SELECT max(mtime) FROM attachment" |
| 1321 | " WHERE target=%Q AND filename=%Q))" |
| 1322 | " WHERE target=%Q AND filename=%Q", |
| 1323 | m.zAttachTarget, m.zAttachName, |
| 1324 | m.zAttachTarget, m.zAttachName |
| 1325 | ); |
| 1326 | if( strlen(m.zAttachTarget)!=UUID_SIZE |
| 1327 | || !validate16(m.zAttachTarget, UUID_SIZE) |
| 1328 | ){ |
| 1329 | char *zComment; |
| 1330 | if( m.zAttachSrc && m.zAttachSrc[0] ){ |
| 1331 | zComment = mprintf("Add attachment \"%h\" to wiki page [%h]", |
| 1332 | m.zAttachName, m.zAttachTarget); |
| 1333 | }else{ |
| 1334 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 1335 | m.zAttachName, m.zAttachTarget); |
| 1336 | } |
| 1337 | db_multi_exec( |
| 1338 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1339 | "VALUES('w',%.17g,%d,%Q,%Q)", |
| 1340 | m.rDate, rid, m.zUser, zComment |
| 1341 | ); |
| 1342 | free(zComment); |
| 1343 | }else{ |
| 1344 | char *zComment; |
| 1345 | if( m.zAttachSrc && m.zAttachSrc[0] ){ |
| 1346 | zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]", |
| 1347 | m.zAttachName, m.zAttachTarget); |
| 1348 | }else{ |
| 1349 | zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", |
| 1350 | m.zAttachName, m.zAttachTarget); |
| 1351 | } |
| 1352 | db_multi_exec( |
| 1353 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1354 | "VALUES('t',%.17g,%d,%Q,%Q)", |
| 1355 | m.rDate, rid, m.zUser, zComment |
| 1356 | ); |
| 1357 | free(zComment); |
| 1358 | } |
| 1359 | } |
| 1360 | db_end_transaction(0); |
| 1361 | if( m.type==CFTYPE_MANIFEST ){ |
| 1362 | manifest_cache_insert(rid, &m); |
| 1363 | }else{ |
| 1364 | manifest_clear(&m); |
| 1365 | } |
| 1366 | return 1; |
| 1367 | } |
| 1368 | |
| 1369 | /* |
| 1370 | ** Given a checkin name, load and parse the manifest for that checkin. |
| 1371 | ** Throw a fatal error if anything goes wrong. |
| 1372 | */ |
| 1373 | void manifest_from_name( |
| 1374 | const char *zName, |
| 1375 | Manifest *pM |
| 1376 | ){ |
| 1377 | int rid; |
| 1378 | Blob content; |
| 1379 | |
| 1380 | rid = name_to_rid(zName); |
| 1381 | if( !is_a_version(rid) ){ |
| 1382 | fossil_fatal("no such checkin: %s", zName); |
| 1383 | } |
| 1384 | content_get(rid, &content); |
| 1385 | if( !manifest_parse(pM, &content) ){ |
| 1386 | fossil_fatal("cannot parse manifest for checkin: %s", zName); |
| 1387 | } |
| 1388 | } |
| 1389 |
| --- src/manifest.c | |
| +++ src/manifest.c | |
| @@ -26,24 +26,39 @@ | |
| 26 | |
| 27 | #if INTERFACE |
| 28 | /* |
| 29 | ** Types of control files |
| 30 | */ |
| 31 | #define CFTYPE_ANY 0 |
| 32 | #define CFTYPE_MANIFEST 1 |
| 33 | #define CFTYPE_CLUSTER 2 |
| 34 | #define CFTYPE_CONTROL 3 |
| 35 | #define CFTYPE_WIKI 4 |
| 36 | #define CFTYPE_TICKET 5 |
| 37 | #define CFTYPE_ATTACHMENT 6 |
| 38 | #define CFTYPE_EVENT 7 |
| 39 | |
| 40 | /* |
| 41 | ** A single F-card within a manifest |
| 42 | */ |
| 43 | struct ManifestFile { |
| 44 | char *zName; /* Name of a file */ |
| 45 | char *zUuid; /* UUID of the file */ |
| 46 | char *zPerm; /* File permissions */ |
| 47 | char *zPrior; /* Prior name if the name was changed */ |
| 48 | }; |
| 49 | |
| 50 | |
| 51 | /* |
| 52 | ** A parsed manifest or cluster. |
| 53 | */ |
| 54 | struct Manifest { |
| 55 | Blob content; /* The original content blob */ |
| 56 | int type; /* Type of artifact. One of CFTYPE_xxxxx */ |
| 57 | int rid; /* The blob-id for this manifest */ |
| 58 | char *zBaseline; /* Baseline manifest. The B card. */ |
| 59 | Manifest *pBaseline; /* The actual baseline manifest */ |
| 60 | char *zComment; /* Decoded comment. The C card. */ |
| 61 | double rDate; /* Date and time from D card. 0.0 if no D card. */ |
| 62 | char *zUser; /* Name of the user from the U card. */ |
| 63 | char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */ |
| 64 | char *zWiki; /* Text of the wiki page. W card. */ |
| @@ -54,17 +69,12 @@ | |
| 69 | char *zAttachName; /* Filename of an attachment. A card. */ |
| 70 | char *zAttachSrc; /* UUID of document being attached. A card. */ |
| 71 | char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */ |
| 72 | int nFile; /* Number of F cards */ |
| 73 | int nFileAlloc; /* Slots allocated in aFile[] */ |
| 74 | int iFile; /* Index of current file in iterator */ |
| 75 | ManifestFile *aFile; /* One entry for each F-card */ |
| 76 | int nParent; /* Number of parents. */ |
| 77 | int nParentAlloc; /* Slots allocated in azParent[] */ |
| 78 | char **azParent; /* UUIDs of parents. One for each P card argument */ |
| 79 | int nCChild; /* Number of cluster children */ |
| 80 | int nCChildAlloc; /* Number of closts allocated in azCChild[] */ |
| @@ -87,68 +97,75 @@ | |
| 97 | |
| 98 | /* |
| 99 | ** A cache of parsed manifests. This reduces the number of |
| 100 | ** calls to manifest_parse() when doing a rebuild. |
| 101 | */ |
| 102 | #define MX_MANIFEST_CACHE 6 |
| 103 | static struct { |
| 104 | int nxAge; |
| 105 | int aAge[MX_MANIFEST_CACHE]; |
| 106 | Manifest *apManifest[MX_MANIFEST_CACHE]; |
| 107 | } manifestCache; |
| 108 | |
| 109 | |
| 110 | /* |
| 111 | ** Clear the memory allocated in a manifest object |
| 112 | */ |
| 113 | void manifest_destroy(Manifest *p){ |
| 114 | if( p ){ |
| 115 | blob_reset(&p->content); |
| 116 | free(p->aFile); |
| 117 | free(p->azParent); |
| 118 | free(p->azCChild); |
| 119 | free(p->aTag); |
| 120 | free(p->aField); |
| 121 | if( p->pBaseline ) manifest_destroy(p->pBaseline); |
| 122 | fossil_free(p); |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | /* |
| 127 | ** Add an element to the manifest cache using LRU replacement. |
| 128 | */ |
| 129 | void manifest_cache_insert(Manifest *p){ |
| 130 | while( p ){ |
| 131 | int i; |
| 132 | Manifest *pBaseline = p->pBaseline; |
| 133 | p->pBaseline = 0; |
| 134 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 135 | if( manifestCache.apManifest[i]==0 ) break; |
| 136 | } |
| 137 | if( i>=MX_MANIFEST_CACHE ){ |
| 138 | int oldest = 0; |
| 139 | int oldestAge = manifestCache.aAge[0]; |
| 140 | for(i=1; i<MX_MANIFEST_CACHE; i++){ |
| 141 | if( manifestCache.aAge[i]<oldestAge ){ |
| 142 | oldest = i; |
| 143 | oldestAge = manifestCache.aAge[i]; |
| 144 | } |
| 145 | } |
| 146 | manifest_destroy(manifestCache.apManifest[oldest]); |
| 147 | i = oldest; |
| 148 | } |
| 149 | manifestCache.aAge[i] = ++manifestCache.nxAge; |
| 150 | manifestCache.apManifest[i] = p; |
| 151 | p = pBaseline; |
| 152 | } |
| 153 | } |
| 154 | |
| 155 | /* |
| 156 | ** Try to extract a line from the manifest cache. Return 1 if found. |
| 157 | ** Return 0 if not found. |
| 158 | */ |
| 159 | static Manifest *manifest_cache_find(int rid){ |
| 160 | int i; |
| 161 | Manifest *p; |
| 162 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 163 | if( manifestCache.apManifest[i] && manifestCache.apManifest[i]->rid==rid ){ |
| 164 | p = manifestCache.apManifest[i]; |
| 165 | manifestCache.apManifest[i] = 0; |
| 166 | return p; |
| 167 | } |
| 168 | } |
| 169 | return 0; |
| 170 | } |
| 171 | |
| @@ -156,21 +173,113 @@ | |
| 173 | ** Clear the manifest cache. |
| 174 | */ |
| 175 | void manifest_cache_clear(void){ |
| 176 | int i; |
| 177 | for(i=0; i<MX_MANIFEST_CACHE; i++){ |
| 178 | if( manifestCache.apManifest[i] ){ |
| 179 | manifest_destroy(manifestCache.apManifest[i]); |
| 180 | } |
| 181 | } |
| 182 | memset(&manifestCache, 0, sizeof(manifestCache)); |
| 183 | } |
| 184 | |
| 185 | #ifdef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM |
| 186 | # define md5sum_init(X) |
| 187 | # define md5sum_step_text(X,Y) |
| 188 | #endif |
| 189 | |
| 190 | /* |
| 191 | ** Remove the PGP signature from the artifact, if there is one. |
| 192 | */ |
| 193 | static void remove_pgp_signature(char **pz, int *pn){ |
| 194 | char *z = *pz; |
| 195 | int n = *pn; |
| 196 | int i; |
| 197 | if( memcmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return; |
| 198 | for(i=34; i<n && (z[i-1]!='\n' || z[i-2]!='\n'); i++){} |
| 199 | if( i>=n ) return; |
| 200 | z += i; |
| 201 | n -= i; |
| 202 | *pz = z; |
| 203 | for(i=n-1; i>=0; i--){ |
| 204 | if( z[i]=='\n' && memcmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){ |
| 205 | n = i+1; |
| 206 | break; |
| 207 | } |
| 208 | } |
| 209 | *pn = n; |
| 210 | return; |
| 211 | } |
| 212 | |
| 213 | /* |
| 214 | ** Verify the Z-card checksum on the artifact, if there is such a |
| 215 | ** checksum. Return 0 if there is no Z-card. Return 1 if the Z-card |
| 216 | ** exists and is correct. Return 2 if the Z-card exists and has the wrong |
| 217 | ** value. |
| 218 | ** |
| 219 | ** 0123456789 123456789 123456789 123456789 |
| 220 | ** Z aea84f4f863865a8d59d0384e4d2a41c |
| 221 | */ |
| 222 | static int verify_z_card(const char *z, int n){ |
| 223 | if( n<35 ) return 0; |
| 224 | if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0; |
| 225 | md5sum_init(); |
| 226 | md5sum_step_text(z, n-35); |
| 227 | if( memcmp(&z[n-33], md5sum_finish(0), 32)==0 ){ |
| 228 | return 1; |
| 229 | }else{ |
| 230 | return 2; |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | /* |
| 235 | ** A structure used for rapid parsing of the Manifest file |
| 236 | */ |
| 237 | typedef struct ManifestText ManifestText; |
| 238 | struct ManifestText { |
| 239 | char *z; /* The first character of the next token */ |
| 240 | char *zEnd; /* One character beyond the end of the manifest */ |
| 241 | int atEol; /* True if z points to the start of a new line */ |
| 242 | }; |
| 243 | |
| 244 | /* |
| 245 | ** Return a pointer to the next token. The token is zero-terminated. |
| 246 | ** Return NULL if there are no more tokens on the current line. |
| 247 | */ |
| 248 | static char *next_token(ManifestText *p, int *pLen){ |
| 249 | char *z; |
| 250 | char *zStart; |
| 251 | int c; |
| 252 | if( p->atEol ) return 0; |
| 253 | zStart = z = p->z; |
| 254 | while( (c=(*z))!=' ' && c!='\n' ){ z++; } |
| 255 | *z = 0; |
| 256 | p->z = &z[1]; |
| 257 | p->atEol = c=='\n'; |
| 258 | if( pLen ) *pLen = z - zStart; |
| 259 | return zStart; |
| 260 | } |
| 261 | |
| 262 | /* |
| 263 | ** Return the card-type for the next card. Or, return 0 if there are no |
| 264 | ** more cards or if we are not at the end of the current card. |
| 265 | */ |
| 266 | static char next_card(ManifestText *p){ |
| 267 | char c; |
| 268 | if( !p->atEol || p->z>=p->zEnd ) return 0; |
| 269 | c = p->z[0]; |
| 270 | if( p->z[1]==' ' ){ |
| 271 | p->z += 2; |
| 272 | p->atEol = 0; |
| 273 | }else if( p->z[1]=='\n' ){ |
| 274 | p->z += 2; |
| 275 | p->atEol = 1; |
| 276 | }else{ |
| 277 | c = 0; |
| 278 | } |
| 279 | return c; |
| 280 | } |
| 281 | |
| 282 | /* |
| 283 | ** Parse a blob into a Manifest object. The Manifest object |
| 284 | ** takes over the input blob and will free it when the |
| 285 | ** Manifest object is freed. Zeros are inserted into the blob |
| @@ -195,56 +304,66 @@ | |
| 304 | ** Each card is divided into tokens by a single space character. |
| 305 | ** The first token is a single upper-case letter which is the card type. |
| 306 | ** The card type determines the other parameters to the card. |
| 307 | ** Cards must occur in lexicographical order. |
| 308 | */ |
| 309 | static Manifest *manifest_parse(Blob *pContent, int rid){ |
| 310 | Manifest *p; |
| 311 | int seenZ = 0; |
| 312 | int i, lineNo=0; |
| 313 | ManifestText x; |
| 314 | char cPrevType = 0; |
| 315 | char cType; |
| 316 | char *z; |
| 317 | int n; |
| 318 | char *zUuid; |
| 319 | int sz; |
| 320 | |
| 321 | /* Every control artifact ends with a '\n' character. Exit early |
| 322 | ** if that is not the case for this artifact. |
| 323 | */ |
| 324 | z = blob_buffer(pContent); |
| 325 | n = blob_size(pContent); |
| 326 | if( n<=0 || z[n-1]!='\n' ){ |
| 327 | blob_reset(pContent); |
| 328 | return 0; |
| 329 | } |
| 330 | |
| 331 | /* Strip off the PGP signature if there is one. Then verify the |
| 332 | ** Z-card. |
| 333 | */ |
| 334 | remove_pgp_signature(&z, &n); |
| 335 | if( verify_z_card(z, n)==0 ){ |
| 336 | blob_reset(pContent); |
| 337 | return 0; |
| 338 | } |
| 339 | |
| 340 | /* Verify that the first few characters of the artifact look like |
| 341 | ** a control artifact. |
| 342 | */ |
| 343 | if( n<10 || z[0]<'A' || z[0]>'Z' || z[1]!=' ' ){ |
| 344 | blob_reset(pContent); |
| 345 | return 0; |
| 346 | } |
| 347 | |
| 348 | /* Allocate a Manifest object to hold the parsed control artifact. |
| 349 | */ |
| 350 | p = fossil_malloc( sizeof(*p) ); |
| 351 | memset(p, 0, sizeof(*p)); |
| 352 | memcpy(&p->content, pContent, sizeof(p->content)); |
| 353 | p->rid = rid; |
| 354 | blob_zero(pContent); |
| 355 | pContent = &p->content; |
| 356 | |
| 357 | /* Begin parsing, card by card. |
| 358 | */ |
| 359 | x.z = z; |
| 360 | x.zEnd = &z[n]; |
| 361 | x.atEol = 1; |
| 362 | while( (cType = next_card(&x))!=0 && cType>=cPrevType ){ |
| 363 | lineNo++; |
| 364 | switch( cType ){ |
| 365 | /* |
| 366 | ** A <filename> <target> ?<source>? |
| 367 | ** |
| 368 | ** Identifies an attachment to either a wiki page or a ticket. |
| 369 | ** <source> is the artifact that is the attachment. <source> |
| @@ -251,50 +370,60 @@ | |
| 370 | ** is omitted to delete an attachment. <target> is the name of |
| 371 | ** a wiki page or ticket to which that attachment is connected. |
| 372 | */ |
| 373 | case 'A': { |
| 374 | char *zName, *zTarget, *zSrc; |
| 375 | int nTarget, nSrc; |
| 376 | zName = next_token(&x, 0); |
| 377 | zTarget = next_token(&x, &nTarget); |
| 378 | zSrc = next_token(&x, &nSrc); |
| 379 | if( zName==0 || zTarget==0 ) goto manifest_syntax_error; |
| 380 | if( p->zAttachName!=0 ) goto manifest_syntax_error; |
| 381 | defossilize(zName); |
| 382 | if( !file_is_simple_pathname(zName) ){ |
| 383 | goto manifest_syntax_error; |
| 384 | } |
| 385 | defossilize(zTarget); |
| 386 | if( (nTarget!=UUID_SIZE || !validate16(zTarget, UUID_SIZE)) |
| 387 | && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){ |
| 388 | goto manifest_syntax_error; |
| 389 | } |
| 390 | if( zSrc && (nSrc!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){ |
| 391 | goto manifest_syntax_error; |
| 392 | } |
| 393 | p->zAttachName = (char*)file_tail(zName); |
| 394 | p->zAttachSrc = zSrc; |
| 395 | p->zAttachTarget = zTarget; |
| 396 | break; |
| 397 | } |
| 398 | |
| 399 | /* |
| 400 | ** B <uuid> |
| 401 | ** |
| 402 | ** A B-line gives the UUID for the baselinen of a delta-manifest. |
| 403 | */ |
| 404 | case 'B': { |
| 405 | if( p->zBaseline ) goto manifest_syntax_error; |
| 406 | p->zBaseline = next_token(&x, &sz); |
| 407 | if( p->zBaseline==0 ) goto manifest_syntax_error; |
| 408 | if( sz!=UUID_SIZE ) goto manifest_syntax_error; |
| 409 | if( !validate16(p->zBaseline, UUID_SIZE) ) goto manifest_syntax_error; |
| 410 | break; |
| 411 | } |
| 412 | |
| 413 | |
| 414 | /* |
| 415 | ** C <comment> |
| 416 | ** |
| 417 | ** Comment text is fossil-encoded. There may be no more than |
| 418 | ** one C line. C lines are required for manifests and are |
| 419 | ** disallowed on all other control files. |
| 420 | */ |
| 421 | case 'C': { |
| 422 | if( p->zComment!=0 ) goto manifest_syntax_error; |
| 423 | p->zComment = next_token(&x, 0); |
| 424 | if( p->zComment==0 ) goto manifest_syntax_error; |
| 425 | defossilize(p->zComment); |
| 426 | break; |
| 427 | } |
| 428 | |
| 429 | /* |
| @@ -303,17 +432,13 @@ | |
| 432 | ** The timestamp should be ISO 8601. YYYY-MM-DDtHH:MM:SS |
| 433 | ** There can be no more than 1 D line. D lines are required |
| 434 | ** for all control files except for clusters. |
| 435 | */ |
| 436 | case 'D': { |
| 437 | if( p->rDate>0.0 ) goto manifest_syntax_error; |
| 438 | p->rDate = db_double(0.0, "SELECT julianday(%Q)", next_token(&x,0)); |
| 439 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 440 | break; |
| 441 | } |
| 442 | |
| 443 | /* |
| 444 | ** E <timestamp> <uuid> |
| @@ -323,56 +448,46 @@ | |
| 448 | ** The event timestamp is distinct from the D timestamp. The D |
| 449 | ** timestamp is when the artifact was created whereas the E timestamp |
| 450 | ** is when the specific event is said to occur. |
| 451 | */ |
| 452 | case 'E': { |
| 453 | if( p->rEventDate>0.0 ) goto manifest_syntax_error; |
| 454 | p->rEventDate = db_double(0.0,"SELECT julianday(%Q)", next_token(&x,0)); |
| 455 | if( p->rEventDate<=0.0 ) goto manifest_syntax_error; |
| 456 | p->zEventId = next_token(&x, &sz); |
| 457 | if( sz!=UUID_SIZE ) goto manifest_syntax_error; |
| 458 | if( !validate16(p->zEventId, UUID_SIZE) ) goto manifest_syntax_error; |
| 459 | break; |
| 460 | } |
| 461 | |
| 462 | /* |
| 463 | ** F <filename> ?<uuid>? ?<permissions>? ?<old-name>? |
| 464 | ** |
| 465 | ** Identifies a file in a manifest. Multiple F lines are |
| 466 | ** allowed in a manifest. F lines are not allowed in any |
| 467 | ** other control file. The filename and old-name are fossil-encoded. |
| 468 | */ |
| 469 | case 'F': { |
| 470 | char *zName, *zPerm, *zPriorName; |
| 471 | zName = next_token(&x,0); |
| 472 | if( zName==0 ) goto manifest_syntax_error; |
| 473 | defossilize(zName); |
| 474 | if( !file_is_simple_pathname(zName) ){ |
| 475 | goto manifest_syntax_error; |
| 476 | } |
| 477 | zUuid = next_token(&x, &sz); |
| 478 | if( p->zBaseline==0 || zUuid!=0 ){ |
| 479 | if( sz!=UUID_SIZE ) goto manifest_syntax_error; |
| 480 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 481 | } |
| 482 | zPerm = next_token(&x,0); |
| 483 | zPriorName = next_token(&x,0); |
| 484 | if( zPriorName ){ |
| 485 | defossilize(zPriorName); |
| 486 | if( !file_is_simple_pathname(zPriorName) ){ |
| 487 | goto manifest_syntax_error; |
| 488 | } |
| 489 | } |
| 490 | if( p->nFile>=p->nFileAlloc ){ |
| 491 | p->nFileAlloc = p->nFileAlloc*2 + 10; |
| 492 | p->aFile = fossil_realloc(p->aFile, |
| 493 | p->nFileAlloc*sizeof(p->aFile[0]) ); |
| @@ -380,11 +495,10 @@ | |
| 495 | i = p->nFile++; |
| 496 | p->aFile[i].zName = zName; |
| 497 | p->aFile[i].zUuid = zUuid; |
| 498 | p->aFile[i].zPerm = zPerm; |
| 499 | p->aFile[i].zPrior = zPriorName; |
| 500 | if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){ |
| 501 | goto manifest_syntax_error; |
| 502 | } |
| 503 | break; |
| 504 | } |
| @@ -397,16 +511,14 @@ | |
| 511 | ** value. If <value> is omitted then it is understood to be an |
| 512 | ** empty string. |
| 513 | */ |
| 514 | case 'J': { |
| 515 | char *zName, *zValue; |
| 516 | zName = next_token(&x,0); |
| 517 | zValue = next_token(&x,0); |
| 518 | if( zName==0 ) goto manifest_syntax_error; |
| 519 | if( zValue==0 ) zValue = ""; |
| 520 | defossilize(zValue); |
| 521 | if( p->nField>=p->nFieldAlloc ){ |
| 522 | p->nFieldAlloc = p->nFieldAlloc*2 + 10; |
| 523 | p->aField = fossil_realloc(p->aField, |
| 524 | p->nFieldAlloc*sizeof(p->aField[0]) ); |
| @@ -426,18 +538,14 @@ | |
| 538 | ** |
| 539 | ** A K-line gives the UUID for the ticket which this control file |
| 540 | ** is amending. |
| 541 | */ |
| 542 | case 'K': { |
| 543 | if( p->zTicketUuid!=0 ) goto manifest_syntax_error; |
| 544 | p->zTicketUuid = next_token(&x, &sz); |
| 545 | if( sz!=UUID_SIZE ) goto manifest_syntax_error; |
| 546 | if( !validate16(p->zTicketUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 547 | break; |
| 548 | } |
| 549 | |
| 550 | /* |
| 551 | ** L <wikititle> |
| @@ -444,15 +552,13 @@ | |
| 552 | ** |
| 553 | ** The wiki page title is fossil-encoded. There may be no more than |
| 554 | ** one L line. |
| 555 | */ |
| 556 | case 'L': { |
| 557 | if( p->zWikiTitle!=0 ) goto manifest_syntax_error; |
| 558 | p->zWikiTitle = next_token(&x,0); |
| 559 | if( p->zWikiTitle==0 ) goto manifest_syntax_error; |
| 560 | defossilize(p->zWikiTitle); |
| 561 | if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){ |
| 562 | goto manifest_syntax_error; |
| 563 | } |
| 564 | break; |
| @@ -463,15 +569,13 @@ | |
| 569 | ** |
| 570 | ** An M-line identifies another artifact by its UUID. M-lines |
| 571 | ** occur in clusters only. |
| 572 | */ |
| 573 | case 'M': { |
| 574 | zUuid = next_token(&x, &sz); |
| 575 | if( zUuid==0 ) goto manifest_syntax_error; |
| 576 | if( sz!=UUID_SIZE ) goto manifest_syntax_error; |
| 577 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 578 | if( p->nCChild>=p->nCChildAlloc ){ |
| 579 | p->nCChildAlloc = p->nCChildAlloc*2 + 10; |
| 580 | p->azCChild = fossil_realloc(p->azCChild |
| 581 | , p->nCChildAlloc*sizeof(p->azCChild[0]) ); |
| @@ -490,15 +594,12 @@ | |
| 594 | ** Specify one or more other artifacts where are the parents of |
| 595 | ** this artifact. The first parent is the primary parent. All |
| 596 | ** others are parents by merge. |
| 597 | */ |
| 598 | case 'P': { |
| 599 | while( (zUuid = next_token(&x, &sz))!=0 ){ |
| 600 | if( sz!=UUID_SIZE ) goto manifest_syntax_error; |
| 601 | if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error; |
| 602 | if( p->nParent>=p->nParentAlloc ){ |
| 603 | p->nParentAlloc = p->nParentAlloc*2 + 5; |
| 604 | p->azParent = fossil_realloc(p->azParent, |
| 605 | p->nParentAlloc*sizeof(char*)); |
| @@ -514,16 +615,13 @@ | |
| 615 | ** |
| 616 | ** Specify the MD5 checksum over the name and content of all files |
| 617 | ** in the manifest. |
| 618 | */ |
| 619 | case 'R': { |
| 620 | if( p->zRepoCksum!=0 ) goto manifest_syntax_error; |
| 621 | p->zRepoCksum = next_token(&x, &sz); |
| 622 | if( sz!=32 ) goto manifest_syntax_error; |
| 623 | if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error; |
| 624 | break; |
| 625 | } |
| 626 | |
| 627 | /* |
| @@ -540,29 +638,20 @@ | |
| 638 | ** the tag is really a property with the given value. |
| 639 | ** |
| 640 | ** Tags are not allowed in clusters. Multiple T lines are allowed. |
| 641 | */ |
| 642 | case 'T': { |
| 643 | char *zName, *zValue; |
| 644 | zName = next_token(&x, 0); |
| 645 | if( zName==0 ) goto manifest_syntax_error; |
| 646 | zUuid = next_token(&x, &sz); |
| 647 | if( zUuid==0 ) goto manifest_syntax_error; |
| 648 | zValue = next_token(&x, 0); |
| 649 | if( zValue ) defossilize(zValue); |
| 650 | if( sz==UUID_SIZE && validate16(zUuid, UUID_SIZE) ){ |
| 651 | /* A valid uuid */ |
| 652 | }else if( sz==1 && zUuid[0]=='*' ){ |
| 653 | zUuid = 0; |
| 654 | }else{ |
| 655 | goto manifest_syntax_error; |
| 656 | } |
| 657 | defossilize(zName); |
| @@ -593,19 +682,17 @@ | |
| 682 | ** Identify the user who created this control file by their |
| 683 | ** login. Only one U line is allowed. Prohibited in clusters. |
| 684 | ** If the user name is omitted, take that to be "anonymous". |
| 685 | */ |
| 686 | case 'U': { |
| 687 | if( p->zUser!=0 ) goto manifest_syntax_error; |
| 688 | p->zUser = next_token(&x, 0); |
| 689 | if( p->zUser==0 ){ |
| 690 | p->zUser = "anonymous"; |
| 691 | }else{ |
| 692 | defossilize(p->zUser); |
| 693 | } |
| 694 | break; |
| 695 | } |
| 696 | |
| 697 | /* |
| 698 | ** W <size> |
| @@ -613,26 +700,28 @@ | |
| 700 | ** The next <size> bytes of the file contain the text of the wiki |
| 701 | ** page. There is always an extra \n before the start of the next |
| 702 | ** record. |
| 703 | */ |
| 704 | case 'W': { |
| 705 | char *zSize; |
| 706 | int size, c; |
| 707 | Blob wiki; |
| 708 | zSize = next_token(&x, 0); |
| 709 | if( zSize==0 ) goto manifest_syntax_error; |
| 710 | if( x.atEol==0 ) goto manifest_syntax_error; |
| 711 | for(size=0; (c = zSize[0])>='0' && c<='9'; zSize++){ |
| 712 | size = size*10 + c - '0'; |
| 713 | } |
| 714 | if( size<0 ) goto manifest_syntax_error; |
| 715 | if( p->zWiki!=0 ) goto manifest_syntax_error; |
| 716 | blob_zero(&wiki); |
| 717 | if( (&x.z[size+1])>=x.zEnd ) goto manifest_syntax_error; |
| 718 | p->zWiki = x.z; |
| 719 | x.z += size; |
| 720 | if( x.z[0]!='\n' ) goto manifest_syntax_error; |
| 721 | x.z[0] = 0; |
| 722 | x.z++; |
| 723 | break; |
| 724 | } |
| 725 | |
| 726 | |
| 727 | /* |
| @@ -645,35 +734,24 @@ | |
| 734 | ** This card is required for all control file types except for |
| 735 | ** Manifest. It is not required for manifest only for historical |
| 736 | ** compatibility reasons. |
| 737 | */ |
| 738 | case 'Z': { |
| 739 | zUuid = next_token(&x, &sz); |
| 740 | if( sz!=32 ) goto manifest_syntax_error; |
| 741 | if( !validate16(zUuid, 32) ) goto manifest_syntax_error; |
| 742 | seenZ = 1; |
| 743 | break; |
| 744 | } |
| 745 | default: { |
| 746 | goto manifest_syntax_error; |
| 747 | } |
| 748 | } |
| 749 | } |
| 750 | if( x.z<x.zEnd ) goto manifest_syntax_error; |
| 751 | |
| 752 | if( p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline ){ |
| 753 | if( p->nCChild>0 ) goto manifest_syntax_error; |
| 754 | if( p->rDate<=0.0 ) goto manifest_syntax_error; |
| 755 | if( p->nField>0 ) goto manifest_syntax_error; |
| 756 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 757 | if( p->zWiki ) goto manifest_syntax_error; |
| @@ -754,28 +832,72 @@ | |
| 832 | if( p->zWikiTitle ) goto manifest_syntax_error; |
| 833 | if( p->zTicketUuid ) goto manifest_syntax_error; |
| 834 | p->type = CFTYPE_MANIFEST; |
| 835 | } |
| 836 | md5sum_init(); |
| 837 | return p; |
| 838 | |
| 839 | manifest_syntax_error: |
| 840 | /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/ |
| 841 | md5sum_init(); |
| 842 | manifest_destroy(p); |
| 843 | return 0; |
| 844 | } |
| 845 | |
| 846 | /* |
| 847 | ** Get a manifest given the rid for the control artifact. Return |
| 848 | ** a pointer to the manifest on success or NULL if there is a failure. |
| 849 | */ |
| 850 | Manifest *manifest_get(int rid, int cfType){ |
| 851 | Blob content; |
| 852 | Manifest *p; |
| 853 | p = manifest_cache_find(rid); |
| 854 | if( p ){ |
| 855 | if( cfType!=CFTYPE_ANY && cfType!=p->type ){ |
| 856 | manifest_cache_insert(p); |
| 857 | p = 0; |
| 858 | } |
| 859 | return p; |
| 860 | } |
| 861 | content_get(rid, &content); |
| 862 | p = manifest_parse(&content, rid); |
| 863 | if( p && cfType!=CFTYPE_ANY && cfType!=p->type ){ |
| 864 | manifest_destroy(p); |
| 865 | p = 0; |
| 866 | } |
| 867 | return p; |
| 868 | } |
| 869 | |
| 870 | /* |
| 871 | ** Given a checkin name, load and parse the manifest for that checkin. |
| 872 | ** Throw a fatal error if anything goes wrong. |
| 873 | */ |
| 874 | Manifest *manifest_get_by_name(const char *zName, int *pRid){ |
| 875 | int rid; |
| 876 | Manifest *p; |
| 877 | |
| 878 | rid = name_to_rid(zName); |
| 879 | if( !is_a_version(rid) ){ |
| 880 | fossil_fatal("no such checkin: %s", zName); |
| 881 | } |
| 882 | if( pRid ) *pRid = rid; |
| 883 | p = manifest_get(rid, CFTYPE_MANIFEST); |
| 884 | if( p==0 ){ |
| 885 | fossil_fatal("cannot parse manifest for checkin: %s", zName); |
| 886 | } |
| 887 | return p; |
| 888 | } |
| 889 | |
| 890 | /* |
| 891 | ** COMMAND: test-parse-manifest |
| 892 | ** |
| 893 | ** Usage: %fossil test-parse-manifest FILENAME ?N? |
| 894 | ** |
| 895 | ** Parse the manifest and discarded. Use for testing only. |
| 896 | */ |
| 897 | void manifest_test_parse_cmd(void){ |
| 898 | Manifest *p; |
| 899 | Blob b; |
| 900 | int i; |
| 901 | int n = 1; |
| 902 | if( g.argc!=3 && g.argc!=4 ){ |
| 903 | usage("FILENAME"); |
| @@ -784,13 +906,101 @@ | |
| 906 | blob_read_from_file(&b, g.argv[2]); |
| 907 | if( g.argc>3 ) n = atoi(g.argv[3]); |
| 908 | for(i=0; i<n; i++){ |
| 909 | Blob b2; |
| 910 | blob_copy(&b2, &b); |
| 911 | p = manifest_parse(&b2, 0); |
| 912 | manifest_destroy(p); |
| 913 | } |
| 914 | } |
| 915 | |
| 916 | /* |
| 917 | ** Fetch the baseline associated with the delta-manifest p. |
| 918 | ** Print a fatal-error and quit if unable to load the baseline. |
| 919 | */ |
| 920 | static void fetch_baseline(Manifest *p){ |
| 921 | if( p->zBaseline!=0 && p->pBaseline==0 ){ |
| 922 | int rid = uuid_to_rid(p->zBaseline, 0); |
| 923 | p->pBaseline = manifest_get(rid, CFTYPE_MANIFEST); |
| 924 | if( p->pBaseline==0 ){ |
| 925 | fossil_fatal("cannot access baseline manifest %S", p->zBaseline); |
| 926 | } |
| 927 | } |
| 928 | } |
| 929 | |
| 930 | /* |
| 931 | ** Rewind a manifest-file iterator back to the beginning of the manifest. |
| 932 | */ |
| 933 | void manifest_file_rewind(Manifest *p){ |
| 934 | p->iFile = 0; |
| 935 | fetch_baseline(p); |
| 936 | if( p->pBaseline ){ |
| 937 | p->pBaseline->iFile = 0; |
| 938 | } |
| 939 | } |
| 940 | |
| 941 | /* |
| 942 | ** Advance to the next manifest-file. |
| 943 | ** |
| 944 | ** Return NULL for end-of-records or if there is an error. If an error |
| 945 | ** occurs and pErr!=0 then store 1 in *pErr. |
| 946 | */ |
| 947 | ManifestFile *manifest_file_next( |
| 948 | Manifest *p, |
| 949 | int *pErr |
| 950 | ){ |
| 951 | ManifestFile *pOut = 0; |
| 952 | if( pErr ) *pErr = 0; |
| 953 | if( p->pBaseline==0 ){ |
| 954 | /* Manifest p is a baseline-manifest. Just scan down the list |
| 955 | ** of files. */ |
| 956 | if( p->iFile<p->nFile ) pOut = &p->aFile[p->iFile++]; |
| 957 | }else{ |
| 958 | /* Manifest p is a delta-manifest. Scan the baseline but amend the |
| 959 | ** file list in the baseline with changes described by p. |
| 960 | */ |
| 961 | Manifest *pB = p->pBaseline; |
| 962 | int cmp; |
| 963 | while(1){ |
| 964 | if( pB->iFile>=pB->nFile ){ |
| 965 | /* We have used all entries out of the baseline. Return the next |
| 966 | ** entry from the delta. */ |
| 967 | if( p->iFile<p->nFile ) pOut = &p->aFile[p->iFile++]; |
| 968 | break; |
| 969 | }else if( p->iFile>=p->nFile ){ |
| 970 | /* We have used all entries from the delta. Return the next |
| 971 | ** entry from the baseline. */ |
| 972 | if( pB->iFile<pB->nFile ) pOut = &pB->aFile[pB->iFile++]; |
| 973 | break; |
| 974 | }else if( (cmp = strcmp(pB->aFile[pB->iFile].zName, |
| 975 | p->aFile[p->iFile].zName)) < 0 ){ |
| 976 | /* The next baseline entry comes before the next delta entry. |
| 977 | ** So return the baseline entry. */ |
| 978 | pOut = &pB->aFile[pB->iFile++]; |
| 979 | break; |
| 980 | }else if( cmp>0 ){ |
| 981 | /* The next delta entry comes before the next baseline |
| 982 | ** entry so return the delta entry */ |
| 983 | pOut = &p->aFile[p->iFile++]; |
| 984 | break; |
| 985 | }else if( p->aFile[p->iFile].zUuid ){ |
| 986 | /* The next delta entry is a replacement for the next baseline |
| 987 | ** entry. Skip the baseline entry and return the delta entry */ |
| 988 | pB->iFile++; |
| 989 | pOut = &p->aFile[p->iFile++]; |
| 990 | break; |
| 991 | }else{ |
| 992 | /* The next delta entry is a delete of the next baseline |
| 993 | ** entry. Skip them both. Repeat the loop to find the next |
| 994 | ** non-delete entry. */ |
| 995 | pB->iFile++; |
| 996 | p->iFile++; |
| 997 | continue; |
| 998 | } |
| 999 | } |
| 1000 | } |
| 1001 | return pOut; |
| 1002 | } |
| 1003 | |
| 1004 | /* |
| 1005 | ** Translate a filename into a filename-id (fnid). Create a new fnid |
| 1006 | ** if no previously exists. |
| @@ -818,14 +1028,14 @@ | |
| 1028 | ** Add a single entry to the mlink table. Also add the filename to |
| 1029 | ** the filename table if it is not there already. |
| 1030 | */ |
| 1031 | static void add_one_mlink( |
| 1032 | int mid, /* The record ID of the manifest */ |
| 1033 | const char *zFromUuid, /* UUID for the mlink.pid. "" to add file */ |
| 1034 | const char *zToUuid, /* UUID for the mlink.fid. "" to delele */ |
| 1035 | const char *zFilename, /* Filename */ |
| 1036 | const char *zPrior /* Previous filename. NULL if unchanged */ |
| 1037 | ){ |
| 1038 | int fnid, pfnid, pid, fid; |
| 1039 | static Stmt s1; |
| 1040 | |
| 1041 | fnid = filename_to_fnid(zFilename); |
| @@ -832,16 +1042,16 @@ | |
| 1042 | if( zPrior==0 ){ |
| 1043 | pfnid = 0; |
| 1044 | }else{ |
| 1045 | pfnid = filename_to_fnid(zPrior); |
| 1046 | } |
| 1047 | if( zFromUuid==0 || zFromUuid[0]==0 ){ |
| 1048 | pid = 0; |
| 1049 | }else{ |
| 1050 | pid = uuid_to_rid(zFromUuid, 1); |
| 1051 | } |
| 1052 | if( zToUuid==0 || zToUuid[0]==0 ){ |
| 1053 | fid = 0; |
| 1054 | }else{ |
| 1055 | fid = uuid_to_rid(zToUuid, 1); |
| 1056 | } |
| 1057 | db_static_prepare(&s1, |
| @@ -858,33 +1068,83 @@ | |
| 1068 | content_deltify(pid, fid, 0); |
| 1069 | } |
| 1070 | } |
| 1071 | |
| 1072 | /* |
| 1073 | ** Do a binary search to find a file in the p->aFile[] array. |
| 1074 | ** |
| 1075 | ** As an optimization, guess that the file we seek is at index p->iFile. |
| 1076 | ** That will usually be the case. If it is not found there, then do the |
| 1077 | ** actual binary search. |
| 1078 | ** |
| 1079 | ** Update p->iFile to be the index of the file that is found. |
| 1080 | */ |
| 1081 | static ManifestFile *manifest_file_seek_base(Manifest *p, const char *zName){ |
| 1082 | int lwr, upr; |
| 1083 | int c; |
| 1084 | int i; |
| 1085 | lwr = 0; |
| 1086 | upr = p->nFile - 1; |
| 1087 | if( p->iFile>=lwr && p->iFile<upr ){ |
| 1088 | c = strcmp(p->aFile[p->iFile+1].zName, zName); |
| 1089 | if( c==0 ){ |
| 1090 | return &p->aFile[++p->iFile]; |
| 1091 | }else if( c>0 ){ |
| 1092 | upr = p->iFile; |
| 1093 | }else{ |
| 1094 | lwr = p->iFile+1; |
| 1095 | } |
| 1096 | } |
| 1097 | while( lwr<=upr ){ |
| 1098 | i = (lwr+upr)/2; |
| 1099 | c = strcmp(p->aFile[i].zName, zName); |
| 1100 | if( c<0 ){ |
| 1101 | lwr = i+1; |
| 1102 | }else if( c>0 ){ |
| 1103 | upr = i-1; |
| 1104 | }else{ |
| 1105 | p->iFile = i; |
| 1106 | return &p->aFile[i]; |
| 1107 | } |
| 1108 | } |
| 1109 | return 0; |
| 1110 | } |
| 1111 | |
| 1112 | /* |
| 1113 | ** Locate a file named zName in the aFile[] array of the given manifest. |
| 1114 | ** Return a pointer to the appropriate ManifestFile object. Return NULL |
| 1115 | ** if not found. |
| 1116 | ** |
| 1117 | ** This routine works even if p is a delta-manifest. The pointer |
| 1118 | ** returned might be to the baseline. |
| 1119 | ** |
| 1120 | ** We assume that filenames are in sorted order and use a binary search. |
| 1121 | */ |
| 1122 | ManifestFile *manifest_file_seek(Manifest *p, const char *zName){ |
| 1123 | ManifestFile *pFile; |
| 1124 | |
| 1125 | pFile = manifest_file_seek_base(p, zName); |
| 1126 | if( pFile && pFile->zUuid==0 ) return 0; |
| 1127 | if( pFile==0 && p->zBaseline ){ |
| 1128 | fetch_baseline(p); |
| 1129 | pFile = manifest_file_seek_base(p->pBaseline, zName); |
| 1130 | } |
| 1131 | return pFile; |
| 1132 | } |
| 1133 | |
| 1134 | /* |
| 1135 | ** This strcmp() function handles NULL arguments. NULLs sort first. |
| 1136 | */ |
| 1137 | static int strcmp_null(const char *zOne, const char *zTwo){ |
| 1138 | if( zOne==0 ){ |
| 1139 | if( zTwo==0 ) return 0; |
| 1140 | return -1; |
| 1141 | }else if( zTwo==0 ){ |
| 1142 | return +1; |
| 1143 | }else{ |
| 1144 | return strcmp(zOne, zTwo); |
| 1145 | } |
| 1146 | } |
| 1147 | |
| 1148 | /* |
| 1149 | ** Add mlink table entries associated with manifest cid. The |
| 1150 | ** parent manifest is pid. |
| @@ -895,89 +1155,70 @@ | |
| 1155 | ** Deleted files have mlink.fid=0. |
| 1156 | ** Added files have mlink.pid=0. |
| 1157 | ** Edited files have both mlink.pid!=0 and mlink.fid!=0 |
| 1158 | */ |
| 1159 | static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){ |
| 1160 | Blob otherContent; |
| 1161 | int otherRid; |
| 1162 | int i, rc; |
| 1163 | ManifestFile *pChildFile, *pParentFile; |
| 1164 | Manifest **ppOther; |
| 1165 | static Stmt eq; |
| 1166 | |
| 1167 | db_static_prepare(&eq, "SELECT 1 FROM mlink WHERE mid=:mid"); |
| 1168 | db_bind_int(&eq, ":mid", cid); |
| 1169 | rc = db_step(&eq); |
| 1170 | db_reset(&eq); |
| 1171 | if( rc==SQLITE_ROW ) return; |
| 1172 | |
| 1173 | assert( pParent==0 || pChild==0 ); |
| 1174 | if( pParent==0 ){ |
| 1175 | ppOther = &pParent; |
| 1176 | otherRid = pid; |
| 1177 | }else{ |
| 1178 | ppOther = &pChild; |
| 1179 | otherRid = cid; |
| 1180 | } |
| 1181 | if( (*ppOther = manifest_cache_find(otherRid))==0 ){ |
| 1182 | content_get(otherRid, &otherContent); |
| 1183 | if( blob_size(&otherContent)==0 ) return; |
| 1184 | *ppOther = manifest_parse(&otherContent, otherRid); |
| 1185 | if( *ppOther==0 ) return; |
| 1186 | } |
| 1187 | if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){ |
| 1188 | content_deltify(pid, cid, 0); |
| 1189 | } |
| 1190 | |
| 1191 | for(i=0, pChildFile=pChild->aFile; i<pChild->nFile; i++, pChildFile++){ |
| 1192 | if( pChildFile->zPrior ){ |
| 1193 | pParentFile = manifest_file_seek(pParent, pChildFile->zPrior); |
| 1194 | if( pParentFile ){ |
| 1195 | add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, |
| 1196 | pChildFile->zName, pChildFile->zPrior); |
| 1197 | } |
| 1198 | }else{ |
| 1199 | pParentFile = manifest_file_seek(pParent, pChildFile->zName); |
| 1200 | if( pParentFile==0 ){ |
| 1201 | if( pChildFile->zUuid ){ |
| 1202 | add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0); |
| 1203 | } |
| 1204 | }else if( strcmp_null(pChildFile->zUuid, pParentFile->zUuid)!=0 ){ |
| 1205 | add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid, |
| 1206 | pChildFile->zName, 0); |
| 1207 | } |
| 1208 | } |
| 1209 | } |
| 1210 | if( pParent->zBaseline && pChild->zBaseline ){ |
| 1211 | for(i=0, pParentFile=pParent->aFile; i<pParent->nFile; i++, pParentFile++){ |
| 1212 | if( pParentFile->zUuid ) continue; |
| 1213 | pChildFile = manifest_file_seek(pChild, pParentFile->zName); |
| 1214 | if( pChildFile ){ |
| 1215 | add_one_mlink(cid, 0, pChildFile->zUuid, pChildFile->zName, 0); |
| 1216 | } |
| 1217 | } |
| 1218 | } |
| 1219 | manifest_cache_insert(*ppOther); |
| 1220 | } |
| 1221 | |
| 1222 | /* |
| 1223 | ** True if manifest_crosslink_begin() has been called but |
| 1224 | ** manifest_crosslink_end() is still pending. |
| @@ -1114,40 +1355,40 @@ | |
| 1355 | ** of the routine, "manifest_crosslink", and the name of this source |
| 1356 | ** file, is a legacy of its original use. |
| 1357 | */ |
| 1358 | int manifest_crosslink(int rid, Blob *pContent){ |
| 1359 | int i; |
| 1360 | Manifest *p; |
| 1361 | Stmt q; |
| 1362 | int parentid = 0; |
| 1363 | |
| 1364 | if( (p = manifest_cache_find(rid))!=0 ){ |
| 1365 | blob_reset(pContent); |
| 1366 | }else if( (p = manifest_parse(pContent, rid))==0 ){ |
| 1367 | return 0; |
| 1368 | } |
| 1369 | if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){ |
| 1370 | manifest_destroy(p); |
| 1371 | return 0; |
| 1372 | } |
| 1373 | db_begin_transaction(); |
| 1374 | if( p->type==CFTYPE_MANIFEST ){ |
| 1375 | if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){ |
| 1376 | char *zCom; |
| 1377 | for(i=0; i<p->nParent; i++){ |
| 1378 | int pid = uuid_to_rid(p->azParent[i], 1); |
| 1379 | db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)" |
| 1380 | "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate); |
| 1381 | if( i==0 ){ |
| 1382 | add_mlink(pid, 0, rid, p); |
| 1383 | parentid = pid; |
| 1384 | } |
| 1385 | } |
| 1386 | db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid); |
| 1387 | while( db_step(&q)==SQLITE_ROW ){ |
| 1388 | int cid = db_column_int(&q, 0); |
| 1389 | add_mlink(rid, p, cid, 0); |
| 1390 | } |
| 1391 | db_finalize(&q); |
| 1392 | db_multi_exec( |
| 1393 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1394 | "bgcolor,euser,ecomment)" |
| @@ -1158,118 +1399,130 @@ | |
| 1399 | " )," |
| 1400 | " %d,%Q,%Q," |
| 1401 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0)," |
| 1402 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," |
| 1403 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1404 | TAG_DATE, rid, p->rDate, |
| 1405 | rid, p->zUser, p->zComment, |
| 1406 | TAG_BGCOLOR, rid, |
| 1407 | TAG_USER, rid, |
| 1408 | TAG_COMMENT, rid |
| 1409 | ); |
| 1410 | zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event" |
| 1411 | " WHERE rowid=last_insert_rowid()"); |
| 1412 | wiki_extract_links(zCom, rid, 0, p->rDate, 1, WIKI_INLINE); |
| 1413 | free(zCom); |
| 1414 | |
| 1415 | /* If this is a delta-manifest, record the fact that this repository |
| 1416 | ** contains delta manifests, to free the "commit" logic to generate |
| 1417 | ** new delta manifests. |
| 1418 | */ |
| 1419 | if( p->zBaseline!=0 ){ |
| 1420 | static int once = 0; |
| 1421 | if( !once ){ |
| 1422 | db_set_int("seen-delta-manifest", 1, 0); |
| 1423 | once = 0; |
| 1424 | } |
| 1425 | } |
| 1426 | } |
| 1427 | } |
| 1428 | if( p->type==CFTYPE_CLUSTER ){ |
| 1429 | tag_insert("cluster", 1, 0, rid, p->rDate, rid); |
| 1430 | for(i=0; i<p->nCChild; i++){ |
| 1431 | int mid; |
| 1432 | mid = uuid_to_rid(p->azCChild[i], 1); |
| 1433 | if( mid>0 ){ |
| 1434 | db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid); |
| 1435 | } |
| 1436 | } |
| 1437 | } |
| 1438 | if( p->type==CFTYPE_CONTROL |
| 1439 | || p->type==CFTYPE_MANIFEST |
| 1440 | || p->type==CFTYPE_EVENT |
| 1441 | ){ |
| 1442 | for(i=0; i<p->nTag; i++){ |
| 1443 | int tid; |
| 1444 | int type; |
| 1445 | if( p->aTag[i].zUuid ){ |
| 1446 | tid = uuid_to_rid(p->aTag[i].zUuid, 1); |
| 1447 | }else{ |
| 1448 | tid = rid; |
| 1449 | } |
| 1450 | if( tid ){ |
| 1451 | switch( p->aTag[i].zName[0] ){ |
| 1452 | case '-': type = 0; break; /* Cancel prior occurances */ |
| 1453 | case '+': type = 1; break; /* Apply to target only */ |
| 1454 | case '*': type = 2; break; /* Propagate to descendants */ |
| 1455 | default: |
| 1456 | fossil_fatal("unknown tag type in manifest: %s", p->aTag); |
| 1457 | return 0; |
| 1458 | } |
| 1459 | tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, |
| 1460 | rid, p->rDate, tid); |
| 1461 | } |
| 1462 | } |
| 1463 | if( parentid ){ |
| 1464 | tag_propagate_all(parentid); |
| 1465 | } |
| 1466 | } |
| 1467 | if( p->type==CFTYPE_WIKI ){ |
| 1468 | char *zTag = mprintf("wiki-%s", p->zWikiTitle); |
| 1469 | int tagid = tag_findid(zTag, 1); |
| 1470 | int prior; |
| 1471 | char *zComment; |
| 1472 | int nWiki; |
| 1473 | char zLength[40]; |
| 1474 | while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; |
| 1475 | nWiki = strlen(p->zWiki); |
| 1476 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 1477 | tag_insert(zTag, 1, zLength, rid, p->rDate, rid); |
| 1478 | free(zTag); |
| 1479 | prior = db_int(0, |
| 1480 | "SELECT rid FROM tagxref" |
| 1481 | " WHERE tagid=%d AND mtime<%.17g" |
| 1482 | " ORDER BY mtime DESC", |
| 1483 | tagid, p->rDate |
| 1484 | ); |
| 1485 | if( prior ){ |
| 1486 | content_deltify(prior, rid, 0); |
| 1487 | } |
| 1488 | if( nWiki>0 ){ |
| 1489 | zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle); |
| 1490 | }else{ |
| 1491 | zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle); |
| 1492 | } |
| 1493 | db_multi_exec( |
| 1494 | "REPLACE INTO event(type,mtime,objid,user,comment," |
| 1495 | " bgcolor,euser,ecomment)" |
| 1496 | "VALUES('w',%.17g,%d,%Q,%Q," |
| 1497 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1)," |
| 1498 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d)," |
| 1499 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1500 | p->rDate, rid, p->zUser, zComment, |
| 1501 | TAG_BGCOLOR, rid, |
| 1502 | TAG_BGCOLOR, rid, |
| 1503 | TAG_USER, rid, |
| 1504 | TAG_COMMENT, rid |
| 1505 | ); |
| 1506 | free(zComment); |
| 1507 | } |
| 1508 | if( p->type==CFTYPE_EVENT ){ |
| 1509 | char *zTag = mprintf("event-%s", p->zEventId); |
| 1510 | int tagid = tag_findid(zTag, 1); |
| 1511 | int prior, subsequent; |
| 1512 | int nWiki; |
| 1513 | char zLength[40]; |
| 1514 | while( fossil_isspace(p->zWiki[0]) ) p->zWiki++; |
| 1515 | nWiki = strlen(p->zWiki); |
| 1516 | sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki); |
| 1517 | tag_insert(zTag, 1, zLength, rid, p->rDate, rid); |
| 1518 | free(zTag); |
| 1519 | prior = db_int(0, |
| 1520 | "SELECT rid FROM tagxref" |
| 1521 | " WHERE tagid=%d AND mtime<%.17g" |
| 1522 | " ORDER BY mtime DESC", |
| 1523 | tagid, p->rDate |
| 1524 | ); |
| 1525 | if( prior ){ |
| 1526 | content_deltify(prior, rid, 0); |
| 1527 | db_multi_exec( |
| 1528 | "DELETE FROM event" |
| @@ -1281,108 +1534,87 @@ | |
| 1534 | } |
| 1535 | subsequent = db_int(0, |
| 1536 | "SELECT rid FROM tagxref" |
| 1537 | " WHERE tagid=%d AND mtime>%.17g" |
| 1538 | " ORDER BY mtime", |
| 1539 | tagid, p->rDate |
| 1540 | ); |
| 1541 | if( subsequent ){ |
| 1542 | content_deltify(rid, subsequent, 0); |
| 1543 | }else{ |
| 1544 | db_multi_exec( |
| 1545 | "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)" |
| 1546 | "VALUES('e',%.17g,%d,%d,%Q,%Q," |
| 1547 | " (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));", |
| 1548 | p->rEventDate, rid, tagid, p->zUser, p->zComment, |
| 1549 | TAG_BGCOLOR, rid |
| 1550 | ); |
| 1551 | } |
| 1552 | } |
| 1553 | if( p->type==CFTYPE_TICKET ){ |
| 1554 | char *zTag; |
| 1555 | |
| 1556 | assert( manifest_crosslink_busy==1 ); |
| 1557 | zTag = mprintf("tkt-%s", p->zTicketUuid); |
| 1558 | tag_insert(zTag, 1, 0, rid, p->rDate, rid); |
| 1559 | free(zTag); |
| 1560 | db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)", |
| 1561 | p->zTicketUuid); |
| 1562 | } |
| 1563 | if( p->type==CFTYPE_ATTACHMENT ){ |
| 1564 | db_multi_exec( |
| 1565 | "INSERT INTO attachment(attachid, mtime, src, target," |
| 1566 | "filename, comment, user)" |
| 1567 | "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);", |
| 1568 | rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName, |
| 1569 | (p->zComment ? p->zComment : ""), p->zUser |
| 1570 | ); |
| 1571 | db_multi_exec( |
| 1572 | "UPDATE attachment SET isLatest = (mtime==" |
| 1573 | "(SELECT max(mtime) FROM attachment" |
| 1574 | " WHERE target=%Q AND filename=%Q))" |
| 1575 | " WHERE target=%Q AND filename=%Q", |
| 1576 | p->zAttachTarget, p->zAttachName, |
| 1577 | p->zAttachTarget, p->zAttachName |
| 1578 | ); |
| 1579 | if( strlen(p->zAttachTarget)!=UUID_SIZE |
| 1580 | || !validate16(p->zAttachTarget, UUID_SIZE) |
| 1581 | ){ |
| 1582 | char *zComment; |
| 1583 | if( p->zAttachSrc && p->zAttachSrc[0] ){ |
| 1584 | zComment = mprintf("Add attachment \"%h\" to wiki page [%h]", |
| 1585 | p->zAttachName, p->zAttachTarget); |
| 1586 | }else{ |
| 1587 | zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]", |
| 1588 | p->zAttachName, p->zAttachTarget); |
| 1589 | } |
| 1590 | db_multi_exec( |
| 1591 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1592 | "VALUES('w',%.17g,%d,%Q,%Q)", |
| 1593 | p->rDate, rid, p->zUser, zComment |
| 1594 | ); |
| 1595 | free(zComment); |
| 1596 | }else{ |
| 1597 | char *zComment; |
| 1598 | if( p->zAttachSrc && p->zAttachSrc[0] ){ |
| 1599 | zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]", |
| 1600 | p->zAttachName, p->zAttachTarget); |
| 1601 | }else{ |
| 1602 | zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]", |
| 1603 | p->zAttachName, p->zAttachTarget); |
| 1604 | } |
| 1605 | db_multi_exec( |
| 1606 | "REPLACE INTO event(type,mtime,objid,user,comment)" |
| 1607 | "VALUES('t',%.17g,%d,%Q,%Q)", |
| 1608 | p->rDate, rid, p->zUser, zComment |
| 1609 | ); |
| 1610 | free(zComment); |
| 1611 | } |
| 1612 | } |
| 1613 | db_end_transaction(0); |
| 1614 | if( p->type==CFTYPE_MANIFEST ){ |
| 1615 | manifest_cache_insert(p); |
| 1616 | }else{ |
| 1617 | manifest_destroy(p); |
| 1618 | } |
| 1619 | return 1; |
| 1620 | } |
| 1621 |
+27
-1
| --- src/md5.c | ||
| +++ src/md5.c | ||
| @@ -41,12 +41,16 @@ | ||
| 41 | 41 | uint32 bits[2]; |
| 42 | 42 | unsigned char in[64]; |
| 43 | 43 | }; |
| 44 | 44 | typedef struct Context MD5Context; |
| 45 | 45 | |
| 46 | +#if defined(__i386__) || defined(__x86_64__) || defined(_WIN32) | |
| 47 | +# define byteReverse(A,B) | |
| 48 | +#else | |
| 46 | 49 | /* |
| 47 | - * Note: this code is harmless on little-endian machines. | |
| 50 | + * Convert an array of integers to little-endian. | |
| 51 | + * Note: this code is a no-op on little-endian machines. | |
| 48 | 52 | */ |
| 49 | 53 | static void byteReverse (unsigned char *buf, unsigned longs){ |
| 50 | 54 | uint32 t; |
| 51 | 55 | do { |
| 52 | 56 | t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | |
| @@ -53,10 +57,12 @@ | ||
| 53 | 57 | ((unsigned)buf[1]<<8 | buf[0]); |
| 54 | 58 | *(uint32 *)buf = t; |
| 55 | 59 | buf += 4; |
| 56 | 60 | } while (--longs); |
| 57 | 61 | } |
| 62 | +#endif | |
| 63 | + | |
| 58 | 64 | /* The four core functions - F1 is optimized somewhat */ |
| 59 | 65 | |
| 60 | 66 | /* #define F1(x, y, z) (x & y | ~x & z) */ |
| 61 | 67 | #define F1(x, y, z) (z ^ (x & (y ^ z))) |
| 62 | 68 | #define F2(x, y, z) F1(z, x, y) |
| @@ -314,10 +320,30 @@ | ||
| 314 | 320 | ** Add the content of a blob to the incremental MD5 checksum. |
| 315 | 321 | */ |
| 316 | 322 | void md5sum_step_blob(Blob *p){ |
| 317 | 323 | md5sum_step_text(blob_buffer(p), blob_size(p)); |
| 318 | 324 | } |
| 325 | + | |
| 326 | +/* | |
| 327 | +** For trouble-shooting only: | |
| 328 | +** | |
| 329 | +** Report the current state of the incremental checksum. | |
| 330 | +*/ | |
| 331 | +const char *md5sum_current_state(void){ | |
| 332 | + unsigned int cksum = 0; | |
| 333 | + unsigned int *pFirst, *pLast; | |
| 334 | + static char zResult[12]; | |
| 335 | + | |
| 336 | + pFirst = (unsigned int*)&incrCtx; | |
| 337 | + pLast = (unsigned int*)((&incrCtx)+1); | |
| 338 | + while( pFirst<pLast ){ | |
| 339 | + cksum += *pFirst; | |
| 340 | + pFirst++; | |
| 341 | + } | |
| 342 | + sqlite3_snprintf(sizeof(zResult), zResult, "%08x", cksum); | |
| 343 | + return zResult; | |
| 344 | +} | |
| 319 | 345 | |
| 320 | 346 | /* |
| 321 | 347 | ** Finish the incremental MD5 checksum. Store the result in blob pOut |
| 322 | 348 | ** if pOut!=0. Also return a pointer to the result. |
| 323 | 349 | ** |
| 324 | 350 |
| --- src/md5.c | |
| +++ src/md5.c | |
| @@ -41,12 +41,16 @@ | |
| 41 | uint32 bits[2]; |
| 42 | unsigned char in[64]; |
| 43 | }; |
| 44 | typedef struct Context MD5Context; |
| 45 | |
| 46 | /* |
| 47 | * Note: this code is harmless on little-endian machines. |
| 48 | */ |
| 49 | static void byteReverse (unsigned char *buf, unsigned longs){ |
| 50 | uint32 t; |
| 51 | do { |
| 52 | t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | |
| @@ -53,10 +57,12 @@ | |
| 53 | ((unsigned)buf[1]<<8 | buf[0]); |
| 54 | *(uint32 *)buf = t; |
| 55 | buf += 4; |
| 56 | } while (--longs); |
| 57 | } |
| 58 | /* The four core functions - F1 is optimized somewhat */ |
| 59 | |
| 60 | /* #define F1(x, y, z) (x & y | ~x & z) */ |
| 61 | #define F1(x, y, z) (z ^ (x & (y ^ z))) |
| 62 | #define F2(x, y, z) F1(z, x, y) |
| @@ -314,10 +320,30 @@ | |
| 314 | ** Add the content of a blob to the incremental MD5 checksum. |
| 315 | */ |
| 316 | void md5sum_step_blob(Blob *p){ |
| 317 | md5sum_step_text(blob_buffer(p), blob_size(p)); |
| 318 | } |
| 319 | |
| 320 | /* |
| 321 | ** Finish the incremental MD5 checksum. Store the result in blob pOut |
| 322 | ** if pOut!=0. Also return a pointer to the result. |
| 323 | ** |
| 324 |
| --- src/md5.c | |
| +++ src/md5.c | |
| @@ -41,12 +41,16 @@ | |
| 41 | uint32 bits[2]; |
| 42 | unsigned char in[64]; |
| 43 | }; |
| 44 | typedef struct Context MD5Context; |
| 45 | |
| 46 | #if defined(__i386__) || defined(__x86_64__) || defined(_WIN32) |
| 47 | # define byteReverse(A,B) |
| 48 | #else |
| 49 | /* |
| 50 | * Convert an array of integers to little-endian. |
| 51 | * Note: this code is a no-op on little-endian machines. |
| 52 | */ |
| 53 | static void byteReverse (unsigned char *buf, unsigned longs){ |
| 54 | uint32 t; |
| 55 | do { |
| 56 | t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 | |
| @@ -53,10 +57,12 @@ | |
| 57 | ((unsigned)buf[1]<<8 | buf[0]); |
| 58 | *(uint32 *)buf = t; |
| 59 | buf += 4; |
| 60 | } while (--longs); |
| 61 | } |
| 62 | #endif |
| 63 | |
| 64 | /* The four core functions - F1 is optimized somewhat */ |
| 65 | |
| 66 | /* #define F1(x, y, z) (x & y | ~x & z) */ |
| 67 | #define F1(x, y, z) (z ^ (x & (y ^ z))) |
| 68 | #define F2(x, y, z) F1(z, x, y) |
| @@ -314,10 +320,30 @@ | |
| 320 | ** Add the content of a blob to the incremental MD5 checksum. |
| 321 | */ |
| 322 | void md5sum_step_blob(Blob *p){ |
| 323 | md5sum_step_text(blob_buffer(p), blob_size(p)); |
| 324 | } |
| 325 | |
| 326 | /* |
| 327 | ** For trouble-shooting only: |
| 328 | ** |
| 329 | ** Report the current state of the incremental checksum. |
| 330 | */ |
| 331 | const char *md5sum_current_state(void){ |
| 332 | unsigned int cksum = 0; |
| 333 | unsigned int *pFirst, *pLast; |
| 334 | static char zResult[12]; |
| 335 | |
| 336 | pFirst = (unsigned int*)&incrCtx; |
| 337 | pLast = (unsigned int*)((&incrCtx)+1); |
| 338 | while( pFirst<pLast ){ |
| 339 | cksum += *pFirst; |
| 340 | pFirst++; |
| 341 | } |
| 342 | sqlite3_snprintf(sizeof(zResult), zResult, "%08x", cksum); |
| 343 | return zResult; |
| 344 | } |
| 345 | |
| 346 | /* |
| 347 | ** Finish the incremental MD5 checksum. Store the result in blob pOut |
| 348 | ** if pOut!=0. Also return a pointer to the result. |
| 349 | ** |
| 350 |
+22
-8
| --- src/rebuild.c | ||
| +++ src/rebuild.c | ||
| @@ -82,10 +82,25 @@ | ||
| 82 | 82 | static int ttyOutput; /* Do progress output */ |
| 83 | 83 | static Bag bagDone; /* Bag of records rebuilt */ |
| 84 | 84 | |
| 85 | 85 | static char *zFNameFormat; /* Format string for filenames on deconstruct */ |
| 86 | 86 | static int prefixLength; /* Length of directory prefix for deconstruct */ |
| 87 | + | |
| 88 | + | |
| 89 | +/* | |
| 90 | +** Draw the percent-complete message. | |
| 91 | +** The input is actually the permill complete. | |
| 92 | +*/ | |
| 93 | +static void percent_complete(int permill){ | |
| 94 | + static int lastOutput = -1; | |
| 95 | + if( permill>lastOutput ){ | |
| 96 | + printf(" %d.%d%% complete...\r", permill/10, permill%10); | |
| 97 | + fflush(stdout); | |
| 98 | + lastOutput = permill; | |
| 99 | + } | |
| 100 | +} | |
| 101 | + | |
| 87 | 102 | |
| 88 | 103 | /* |
| 89 | 104 | ** Called after each artifact is processed |
| 90 | 105 | */ |
| 91 | 106 | static void rebuild_step_done(rid){ |
| @@ -92,12 +107,11 @@ | ||
| 92 | 107 | /* assert( bag_find(&bagDone, rid)==0 ); */ |
| 93 | 108 | bag_insert(&bagDone, rid); |
| 94 | 109 | if( ttyOutput ){ |
| 95 | 110 | processCnt++; |
| 96 | 111 | if (!g.fQuiet) { |
| 97 | - printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize)); | |
| 98 | - fflush(stdout); | |
| 112 | + percent_complete((processCnt*1000)/totalSize); | |
| 99 | 113 | } |
| 100 | 114 | } |
| 101 | 115 | } |
| 102 | 116 | |
| 103 | 117 | /* |
| @@ -167,20 +181,21 @@ | ||
| 167 | 181 | rebuild_step_done(rid); |
| 168 | 182 | |
| 169 | 183 | /* Call all children recursively */ |
| 170 | 184 | rid = 0; |
| 171 | 185 | for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ |
| 172 | - Stmt q2; | |
| 186 | + static Stmt q2; | |
| 173 | 187 | int sz; |
| 174 | - db_prepare(&q2, "SELECT content, size FROM blob WHERE rid=%d", cid); | |
| 188 | + db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid"); | |
| 189 | + db_bind_int(&q2, ":rid", cid); | |
| 175 | 190 | if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ |
| 176 | 191 | Blob delta, next; |
| 177 | 192 | db_ephemeral_blob(&q2, 0, &delta); |
| 178 | 193 | blob_uncompress(&delta, &delta); |
| 179 | 194 | blob_delta_apply(pBase, &delta, &next); |
| 180 | 195 | blob_reset(&delta); |
| 181 | - db_finalize(&q2); | |
| 196 | + db_reset(&q2); | |
| 182 | 197 | if( i<nChild ){ |
| 183 | 198 | rebuild_step(cid, sz, &next); |
| 184 | 199 | }else{ |
| 185 | 200 | /* Tail recursion */ |
| 186 | 201 | rid = cid; |
| @@ -187,11 +202,11 @@ | ||
| 187 | 202 | size = sz; |
| 188 | 203 | blob_reset(pBase); |
| 189 | 204 | *pBase = next; |
| 190 | 205 | } |
| 191 | 206 | }else{ |
| 192 | - db_finalize(&q2); | |
| 207 | + db_reset(&q2); | |
| 193 | 208 | blob_reset(pBase); |
| 194 | 209 | } |
| 195 | 210 | } |
| 196 | 211 | bag_clear(&children); |
| 197 | 212 | } |
| @@ -237,12 +252,11 @@ | ||
| 237 | 252 | |
| 238 | 253 | bag_init(&bagDone); |
| 239 | 254 | ttyOutput = doOut; |
| 240 | 255 | processCnt = 0; |
| 241 | 256 | if (!g.fQuiet) { |
| 242 | - printf("0 (0%%)...\r"); | |
| 243 | - fflush(stdout); | |
| 257 | + percent_complete(0); | |
| 244 | 258 | } |
| 245 | 259 | db_multi_exec(zSchemaUpdates); |
| 246 | 260 | for(;;){ |
| 247 | 261 | zTable = db_text(0, |
| 248 | 262 | "SELECT name FROM sqlite_master /*scan*/" |
| 249 | 263 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -82,10 +82,25 @@ | |
| 82 | static int ttyOutput; /* Do progress output */ |
| 83 | static Bag bagDone; /* Bag of records rebuilt */ |
| 84 | |
| 85 | static char *zFNameFormat; /* Format string for filenames on deconstruct */ |
| 86 | static int prefixLength; /* Length of directory prefix for deconstruct */ |
| 87 | |
| 88 | /* |
| 89 | ** Called after each artifact is processed |
| 90 | */ |
| 91 | static void rebuild_step_done(rid){ |
| @@ -92,12 +107,11 @@ | |
| 92 | /* assert( bag_find(&bagDone, rid)==0 ); */ |
| 93 | bag_insert(&bagDone, rid); |
| 94 | if( ttyOutput ){ |
| 95 | processCnt++; |
| 96 | if (!g.fQuiet) { |
| 97 | printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize)); |
| 98 | fflush(stdout); |
| 99 | } |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | /* |
| @@ -167,20 +181,21 @@ | |
| 167 | rebuild_step_done(rid); |
| 168 | |
| 169 | /* Call all children recursively */ |
| 170 | rid = 0; |
| 171 | for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ |
| 172 | Stmt q2; |
| 173 | int sz; |
| 174 | db_prepare(&q2, "SELECT content, size FROM blob WHERE rid=%d", cid); |
| 175 | if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ |
| 176 | Blob delta, next; |
| 177 | db_ephemeral_blob(&q2, 0, &delta); |
| 178 | blob_uncompress(&delta, &delta); |
| 179 | blob_delta_apply(pBase, &delta, &next); |
| 180 | blob_reset(&delta); |
| 181 | db_finalize(&q2); |
| 182 | if( i<nChild ){ |
| 183 | rebuild_step(cid, sz, &next); |
| 184 | }else{ |
| 185 | /* Tail recursion */ |
| 186 | rid = cid; |
| @@ -187,11 +202,11 @@ | |
| 187 | size = sz; |
| 188 | blob_reset(pBase); |
| 189 | *pBase = next; |
| 190 | } |
| 191 | }else{ |
| 192 | db_finalize(&q2); |
| 193 | blob_reset(pBase); |
| 194 | } |
| 195 | } |
| 196 | bag_clear(&children); |
| 197 | } |
| @@ -237,12 +252,11 @@ | |
| 237 | |
| 238 | bag_init(&bagDone); |
| 239 | ttyOutput = doOut; |
| 240 | processCnt = 0; |
| 241 | if (!g.fQuiet) { |
| 242 | printf("0 (0%%)...\r"); |
| 243 | fflush(stdout); |
| 244 | } |
| 245 | db_multi_exec(zSchemaUpdates); |
| 246 | for(;;){ |
| 247 | zTable = db_text(0, |
| 248 | "SELECT name FROM sqlite_master /*scan*/" |
| 249 |
| --- src/rebuild.c | |
| +++ src/rebuild.c | |
| @@ -82,10 +82,25 @@ | |
| 82 | static int ttyOutput; /* Do progress output */ |
| 83 | static Bag bagDone; /* Bag of records rebuilt */ |
| 84 | |
| 85 | static char *zFNameFormat; /* Format string for filenames on deconstruct */ |
| 86 | static int prefixLength; /* Length of directory prefix for deconstruct */ |
| 87 | |
| 88 | |
| 89 | /* |
| 90 | ** Draw the percent-complete message. |
| 91 | ** The input is actually the permill complete. |
| 92 | */ |
| 93 | static void percent_complete(int permill){ |
| 94 | static int lastOutput = -1; |
| 95 | if( permill>lastOutput ){ |
| 96 | printf(" %d.%d%% complete...\r", permill/10, permill%10); |
| 97 | fflush(stdout); |
| 98 | lastOutput = permill; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | |
| 103 | /* |
| 104 | ** Called after each artifact is processed |
| 105 | */ |
| 106 | static void rebuild_step_done(rid){ |
| @@ -92,12 +107,11 @@ | |
| 107 | /* assert( bag_find(&bagDone, rid)==0 ); */ |
| 108 | bag_insert(&bagDone, rid); |
| 109 | if( ttyOutput ){ |
| 110 | processCnt++; |
| 111 | if (!g.fQuiet) { |
| 112 | percent_complete((processCnt*1000)/totalSize); |
| 113 | } |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | /* |
| @@ -167,20 +181,21 @@ | |
| 181 | rebuild_step_done(rid); |
| 182 | |
| 183 | /* Call all children recursively */ |
| 184 | rid = 0; |
| 185 | for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ |
| 186 | static Stmt q2; |
| 187 | int sz; |
| 188 | db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid"); |
| 189 | db_bind_int(&q2, ":rid", cid); |
| 190 | if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ |
| 191 | Blob delta, next; |
| 192 | db_ephemeral_blob(&q2, 0, &delta); |
| 193 | blob_uncompress(&delta, &delta); |
| 194 | blob_delta_apply(pBase, &delta, &next); |
| 195 | blob_reset(&delta); |
| 196 | db_reset(&q2); |
| 197 | if( i<nChild ){ |
| 198 | rebuild_step(cid, sz, &next); |
| 199 | }else{ |
| 200 | /* Tail recursion */ |
| 201 | rid = cid; |
| @@ -187,11 +202,11 @@ | |
| 202 | size = sz; |
| 203 | blob_reset(pBase); |
| 204 | *pBase = next; |
| 205 | } |
| 206 | }else{ |
| 207 | db_reset(&q2); |
| 208 | blob_reset(pBase); |
| 209 | } |
| 210 | } |
| 211 | bag_clear(&children); |
| 212 | } |
| @@ -237,12 +252,11 @@ | |
| 252 | |
| 253 | bag_init(&bagDone); |
| 254 | ttyOutput = doOut; |
| 255 | processCnt = 0; |
| 256 | if (!g.fQuiet) { |
| 257 | percent_complete(0); |
| 258 | } |
| 259 | db_multi_exec(zSchemaUpdates); |
| 260 | for(;;){ |
| 261 | zTable = db_text(0, |
| 262 | "SELECT name FROM sqlite_master /*scan*/" |
| 263 |
+198
-425
| --- src/sha1.c | ||
| +++ src/sha1.c | ||
| @@ -1,417 +1,190 @@ | ||
| 1 | 1 | /* |
| 2 | -** This implementation of SHA1 is adapted from the example implementation | |
| 3 | -** contained in RFC-3174. | |
| 2 | +** This implementation of SHA1. | |
| 4 | 3 | */ |
| 5 | -/* | |
| 6 | - * If you do not have the ISO standard stdint.h header file, then you | |
| 7 | - * must typdef the following: | |
| 8 | - * name meaning | |
| 9 | - * */ | |
| 10 | -#if defined(__DMC__) || defined(_MSC_VER) | |
| 11 | - typedef unsigned long uint32_t; //unsigned 32 bit integer | |
| 12 | - typedef unsigned char uint8_t; //unsigned 8 bit integer (i.e., unsigned char) | |
| 13 | -#else | |
| 14 | -# include <stdint.h> | |
| 15 | -#endif | |
| 16 | 4 | #include <sys/types.h> |
| 17 | 5 | #include "config.h" |
| 18 | 6 | #include "sha1.h" |
| 19 | 7 | |
| 20 | -#define SHA1HashSize 20 | |
| 21 | -#define shaSuccess 0 | |
| 22 | -#define shaInputTooLong 1 | |
| 23 | -#define shaStateError 2 | |
| 24 | - | |
| 25 | -/* | |
| 26 | - * This structure will hold context information for the SHA-1 | |
| 27 | - * hashing operation | |
| 28 | - */ | |
| 29 | -typedef struct SHA1Context SHA1Context; | |
| 30 | -struct SHA1Context { | |
| 31 | - uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ | |
| 32 | - | |
| 33 | - uint32_t Length_Low; /* Message length in bits */ | |
| 34 | - uint32_t Length_High; /* Message length in bits */ | |
| 35 | - | |
| 36 | - int Message_Block_Index; /* Index into message block array */ | |
| 37 | - uint8_t Message_Block[64]; /* 512-bit message blocks */ | |
| 38 | - | |
| 39 | - int Computed; /* Is the digest computed? */ | |
| 40 | - int Corrupted; /* Is the message digest corrupted? */ | |
| 41 | -}; | |
| 42 | - | |
| 43 | -/* | |
| 44 | - * sha1.c | |
| 45 | - * | |
| 46 | - * Description: | |
| 47 | - * This file implements the Secure Hashing Algorithm 1 as | |
| 48 | - * defined in FIPS PUB 180-1 published April 17, 1995. | |
| 49 | - * | |
| 50 | - * The SHA-1, produces a 160-bit message digest for a given | |
| 51 | - * data stream. It should take about 2**n steps to find a | |
| 52 | - * message with the same digest as a given message and | |
| 53 | - * 2**(n/2) to find any two messages with the same digest, | |
| 54 | - * when n is the digest size in bits. Therefore, this | |
| 55 | - * algorithm can serve as a means of providing a | |
| 56 | - * "fingerprint" for a message. | |
| 57 | - * | |
| 58 | - * Portability Issues: | |
| 59 | - * SHA-1 is defined in terms of 32-bit "words". This code | |
| 60 | - * uses <stdint.h> (included via "sha1.h" to define 32 and 8 | |
| 61 | - * bit unsigned integer types. If your C compiler does not | |
| 62 | - * support 32 bit unsigned integers, this code is not | |
| 63 | - * appropriate. | |
| 64 | - * | |
| 65 | - * Caveats: | |
| 66 | - * SHA-1 is designed to work with messages less than 2^64 bits | |
| 67 | - * long. Although SHA-1 allows a message digest to be generated | |
| 68 | - * for messages of any number of bits less than 2^64, this | |
| 69 | - * implementation only works with messages with a length that is | |
| 70 | - * a multiple of the size of an 8-bit character. | |
| 71 | - * | |
| 72 | - */ | |
| 73 | - | |
| 74 | -/* | |
| 75 | - * Define the SHA1 circular left shift macro | |
| 76 | - */ | |
| 77 | -#define SHA1CircularShift(bits,word) \ | |
| 78 | - (((word) << (bits)) | ((word) >> (32-(bits)))) | |
| 79 | - | |
| 80 | -/* Local Function Prototyptes */ | |
| 81 | -static void SHA1PadMessage(SHA1Context *); | |
| 82 | -static void SHA1ProcessMessageBlock(SHA1Context *); | |
| 83 | - | |
| 84 | -/* | |
| 85 | - * SHA1Reset | |
| 86 | - * | |
| 87 | - * Description: | |
| 88 | - * This function will initialize the SHA1Context in preparation | |
| 89 | - * for computing a new SHA1 message digest. | |
| 90 | - * | |
| 91 | - * Parameters: | |
| 92 | - * context: [in/out] | |
| 93 | - * The context to reset. | |
| 94 | - * | |
| 95 | - * Returns: | |
| 96 | - * sha Error Code. | |
| 97 | - * | |
| 98 | - */ | |
| 99 | -static int SHA1Reset(SHA1Context *context) | |
| 100 | -{ | |
| 101 | - context->Length_Low = 0; | |
| 102 | - context->Length_High = 0; | |
| 103 | - context->Message_Block_Index = 0; | |
| 104 | - | |
| 105 | - context->Intermediate_Hash[0] = 0x67452301; | |
| 106 | - context->Intermediate_Hash[1] = 0xEFCDAB89; | |
| 107 | - context->Intermediate_Hash[2] = 0x98BADCFE; | |
| 108 | - context->Intermediate_Hash[3] = 0x10325476; | |
| 109 | - context->Intermediate_Hash[4] = 0xC3D2E1F0; | |
| 110 | - | |
| 111 | - context->Computed = 0; | |
| 112 | - context->Corrupted = 0; | |
| 113 | - | |
| 114 | - return shaSuccess; | |
| 115 | -} | |
| 116 | - | |
| 117 | -/* | |
| 118 | - * SHA1Result | |
| 119 | - * | |
| 120 | - * Description: | |
| 121 | - * This function will return the 160-bit message digest into the | |
| 122 | - * Message_Digest array provided by the caller. | |
| 123 | - * NOTE: The first octet of hash is stored in the 0th element, | |
| 124 | - * the last octet of hash in the 19th element. | |
| 125 | - * | |
| 126 | - * Parameters: | |
| 127 | - * context: [in/out] | |
| 128 | - * The context to use to calculate the SHA-1 hash. | |
| 129 | - * Message_Digest: [out] | |
| 130 | - * Where the digest is returned. | |
| 131 | - * | |
| 132 | - * Returns: | |
| 133 | - * sha Error Code. | |
| 134 | - * | |
| 135 | - */ | |
| 136 | -static int SHA1Result( SHA1Context *context, | |
| 137 | - uint8_t Message_Digest[SHA1HashSize]) | |
| 138 | -{ | |
| 139 | - int i; | |
| 140 | - | |
| 141 | - if (context->Corrupted) | |
| 142 | - { | |
| 143 | - return context->Corrupted; | |
| 144 | - } | |
| 145 | - | |
| 146 | - if (!context->Computed) | |
| 147 | - { | |
| 148 | - SHA1PadMessage(context); | |
| 149 | - for(i=0; i<64; ++i) | |
| 150 | - { | |
| 151 | - /* message may be sensitive, clear it out */ | |
| 152 | - context->Message_Block[i] = 0; | |
| 153 | - } | |
| 154 | - context->Length_Low = 0; /* and clear length */ | |
| 155 | - context->Length_High = 0; | |
| 156 | - context->Computed = 1; | |
| 157 | - | |
| 158 | - } | |
| 159 | - | |
| 160 | - for(i = 0; i < SHA1HashSize; ++i) | |
| 161 | - { | |
| 162 | - Message_Digest[i] = context->Intermediate_Hash[i>>2] | |
| 163 | - >> 8 * ( 3 - ( i & 0x03 ) ); | |
| 164 | - } | |
| 165 | - | |
| 166 | - return shaSuccess; | |
| 167 | -} | |
| 168 | - | |
| 169 | -/* | |
| 170 | - * SHA1Input | |
| 171 | - * | |
| 172 | - * Description: | |
| 173 | - * This function accepts an array of octets as the next portion | |
| 174 | - * of the message. | |
| 175 | - * | |
| 176 | - * Parameters: | |
| 177 | - * context: [in/out] | |
| 178 | - * The SHA context to update | |
| 179 | - * message_array: [in] | |
| 180 | - * An array of characters representing the next portion of | |
| 181 | - * the message. | |
| 182 | - * length: [in] | |
| 183 | - * The length of the message in message_array | |
| 184 | - * | |
| 185 | - * Returns: | |
| 186 | - * sha Error Code. | |
| 187 | - * | |
| 188 | - */ | |
| 189 | -static | |
| 190 | -int SHA1Input( SHA1Context *context, | |
| 191 | - const uint8_t *message_array, | |
| 192 | - unsigned length) | |
| 193 | -{ | |
| 194 | - if (!length) | |
| 195 | - { | |
| 196 | - return shaSuccess; | |
| 197 | - } | |
| 198 | - | |
| 199 | - if (context->Computed) | |
| 200 | - { | |
| 201 | - context->Corrupted = shaStateError; | |
| 202 | - | |
| 203 | - return shaStateError; | |
| 204 | - } | |
| 205 | - | |
| 206 | - if (context->Corrupted) | |
| 207 | - { | |
| 208 | - return context->Corrupted; | |
| 209 | - } | |
| 210 | - while(length-- && !context->Corrupted) | |
| 211 | - { | |
| 212 | - context->Message_Block[context->Message_Block_Index++] = | |
| 213 | - (*message_array & 0xFF); | |
| 214 | - | |
| 215 | - context->Length_Low += 8; | |
| 216 | - if (context->Length_Low == 0) | |
| 217 | - { | |
| 218 | - context->Length_High++; | |
| 219 | - if (context->Length_High == 0) | |
| 220 | - { | |
| 221 | - /* Message is too long */ | |
| 222 | - context->Corrupted = 1; | |
| 223 | - } | |
| 224 | - } | |
| 225 | - | |
| 226 | - if (context->Message_Block_Index == 64) | |
| 227 | - { | |
| 228 | - SHA1ProcessMessageBlock(context); | |
| 229 | - } | |
| 230 | - | |
| 231 | - message_array++; | |
| 232 | - } | |
| 233 | - | |
| 234 | - return shaSuccess; | |
| 235 | -} | |
| 236 | - | |
| 237 | -/* | |
| 238 | - * SHA1ProcessMessageBlock | |
| 239 | - * | |
| 240 | - * Description: | |
| 241 | - * This function will process the next 512 bits of the message | |
| 242 | - * stored in the Message_Block array. | |
| 243 | - * | |
| 244 | - * Parameters: | |
| 245 | - * None. | |
| 246 | - * | |
| 247 | - * Returns: | |
| 248 | - * Nothing. | |
| 249 | - * | |
| 250 | - * Comments: | |
| 251 | - * Many of the variable names in this code, especially the | |
| 252 | - * single character names, were used because those were the | |
| 253 | - * names used in the publication. | |
| 254 | - * | |
| 255 | - * | |
| 256 | - */ | |
| 257 | -static void SHA1ProcessMessageBlock(SHA1Context *context) | |
| 258 | -{ | |
| 259 | - const uint32_t K[] = { /* Constants defined in SHA-1 */ | |
| 260 | - 0x5A827999, | |
| 261 | - 0x6ED9EBA1, | |
| 262 | - 0x8F1BBCDC, | |
| 263 | - 0xCA62C1D6 | |
| 264 | - }; | |
| 265 | - int t; /* Loop counter */ | |
| 266 | - uint32_t temp; /* Temporary word value */ | |
| 267 | - uint32_t W[80]; /* Word sequence */ | |
| 268 | - uint32_t A, B, C, D, E; /* Word buffers */ | |
| 269 | - | |
| 270 | - /* | |
| 271 | - * Initialize the first 16 words in the array W | |
| 272 | - */ | |
| 273 | - for(t = 0; t < 16; t++) | |
| 274 | - { | |
| 275 | - W[t] = context->Message_Block[t * 4] << 24; | |
| 276 | - W[t] |= context->Message_Block[t * 4 + 1] << 16; | |
| 277 | - W[t] |= context->Message_Block[t * 4 + 2] << 8; | |
| 278 | - W[t] |= context->Message_Block[t * 4 + 3]; | |
| 279 | - } | |
| 280 | - | |
| 281 | - for(t = 16; t < 80; t++) | |
| 282 | - { | |
| 283 | - W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); | |
| 284 | - } | |
| 285 | - | |
| 286 | - A = context->Intermediate_Hash[0]; | |
| 287 | - B = context->Intermediate_Hash[1]; | |
| 288 | - C = context->Intermediate_Hash[2]; | |
| 289 | - D = context->Intermediate_Hash[3]; | |
| 290 | - E = context->Intermediate_Hash[4]; | |
| 291 | - | |
| 292 | - for(t = 0; t < 20; t++) | |
| 293 | - { | |
| 294 | - temp = SHA1CircularShift(5,A) + | |
| 295 | - ((B & C) | ((~B) & D)) + E + W[t] + K[0]; | |
| 296 | - E = D; | |
| 297 | - D = C; | |
| 298 | - C = SHA1CircularShift(30,B); | |
| 299 | - | |
| 300 | - B = A; | |
| 301 | - A = temp; | |
| 302 | - } | |
| 303 | - | |
| 304 | - for(t = 20; t < 40; t++) | |
| 305 | - { | |
| 306 | - temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; | |
| 307 | - E = D; | |
| 308 | - D = C; | |
| 309 | - C = SHA1CircularShift(30,B); | |
| 310 | - B = A; | |
| 311 | - A = temp; | |
| 312 | - } | |
| 313 | - | |
| 314 | - for(t = 40; t < 60; t++) | |
| 315 | - { | |
| 316 | - temp = SHA1CircularShift(5,A) + | |
| 317 | - ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; | |
| 318 | - E = D; | |
| 319 | - D = C; | |
| 320 | - C = SHA1CircularShift(30,B); | |
| 321 | - B = A; | |
| 322 | - A = temp; | |
| 323 | - } | |
| 324 | - | |
| 325 | - for(t = 60; t < 80; t++) | |
| 326 | - { | |
| 327 | - temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; | |
| 328 | - E = D; | |
| 329 | - D = C; | |
| 330 | - C = SHA1CircularShift(30,B); | |
| 331 | - B = A; | |
| 332 | - A = temp; | |
| 333 | - } | |
| 334 | - | |
| 335 | - context->Intermediate_Hash[0] += A; | |
| 336 | - context->Intermediate_Hash[1] += B; | |
| 337 | - context->Intermediate_Hash[2] += C; | |
| 338 | - context->Intermediate_Hash[3] += D; | |
| 339 | - context->Intermediate_Hash[4] += E; | |
| 340 | - | |
| 341 | - context->Message_Block_Index = 0; | |
| 342 | -} | |
| 343 | - | |
| 344 | -/* | |
| 345 | - * SHA1PadMessage | |
| 346 | - * | |
| 347 | - | |
| 348 | - * Description: | |
| 349 | - * According to the standard, the message must be padded to an even | |
| 350 | - * 512 bits. The first padding bit must be a '1'. The last 64 | |
| 351 | - * bits represent the length of the original message. All bits in | |
| 352 | - * between should be 0. This function will pad the message | |
| 353 | - * according to those rules by filling the Message_Block array | |
| 354 | - * accordingly. It will also call the ProcessMessageBlock function | |
| 355 | - * provided appropriately. When it returns, it can be assumed that | |
| 356 | - * the message digest has been computed. | |
| 357 | - * | |
| 358 | - * Parameters: | |
| 359 | - * context: [in/out] | |
| 360 | - * The context to pad | |
| 361 | - * ProcessMessageBlock: [in] | |
| 362 | - * The appropriate SHA*ProcessMessageBlock function | |
| 363 | - * Returns: | |
| 364 | - * Nothing. | |
| 365 | - * | |
| 366 | - */ | |
| 367 | -static void SHA1PadMessage(SHA1Context *context) | |
| 368 | -{ | |
| 369 | - /* | |
| 370 | - * Check to see if the current message block is too small to hold | |
| 371 | - * the initial padding bits and length. If so, we will pad the | |
| 372 | - * block, process it, and then continue padding into a second | |
| 373 | - * block. | |
| 374 | - */ | |
| 375 | - if (context->Message_Block_Index > 55) | |
| 376 | - { | |
| 377 | - context->Message_Block[context->Message_Block_Index++] = 0x80; | |
| 378 | - while(context->Message_Block_Index < 64) | |
| 379 | - { | |
| 380 | - context->Message_Block[context->Message_Block_Index++] = 0; | |
| 381 | - } | |
| 382 | - | |
| 383 | - SHA1ProcessMessageBlock(context); | |
| 384 | - | |
| 385 | - while(context->Message_Block_Index < 56) | |
| 386 | - { | |
| 387 | - context->Message_Block[context->Message_Block_Index++] = 0; | |
| 388 | - } | |
| 389 | - } | |
| 390 | - else | |
| 391 | - { | |
| 392 | - context->Message_Block[context->Message_Block_Index++] = 0x80; | |
| 393 | - while(context->Message_Block_Index < 56) | |
| 394 | - { | |
| 395 | - | |
| 396 | - context->Message_Block[context->Message_Block_Index++] = 0; | |
| 397 | - } | |
| 398 | - } | |
| 399 | - | |
| 400 | - /* | |
| 401 | - * Store the message length as the last 8 octets | |
| 402 | - */ | |
| 403 | - context->Message_Block[56] = context->Length_High >> 24; | |
| 404 | - context->Message_Block[57] = context->Length_High >> 16; | |
| 405 | - context->Message_Block[58] = context->Length_High >> 8; | |
| 406 | - context->Message_Block[59] = context->Length_High; | |
| 407 | - context->Message_Block[60] = context->Length_Low >> 24; | |
| 408 | - context->Message_Block[61] = context->Length_Low >> 16; | |
| 409 | - context->Message_Block[62] = context->Length_Low >> 8; | |
| 410 | - context->Message_Block[63] = context->Length_Low; | |
| 411 | - | |
| 412 | - SHA1ProcessMessageBlock(context); | |
| 8 | + | |
| 9 | +/* | |
| 10 | +** The SHA1 implementation below is adapted from: | |
| 11 | +** | |
| 12 | +** $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $ | |
| 13 | +** $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $ | |
| 14 | +** | |
| 15 | +** SHA-1 in C | |
| 16 | +** By Steve Reid <[email protected]> | |
| 17 | +** 100% Public Domain | |
| 18 | +*/ | |
| 19 | +typedef struct SHA1Context SHA1Context; | |
| 20 | +struct SHA1Context { | |
| 21 | + unsigned int state[5]; | |
| 22 | + unsigned int count[2]; | |
| 23 | + unsigned char buffer[64]; | |
| 24 | +}; | |
| 25 | + | |
| 26 | +/* | |
| 27 | + * blk0() and blk() perform the initial expand. | |
| 28 | + * I got the idea of expanding during the round function from SSLeay | |
| 29 | + * | |
| 30 | + * blk0le() for little-endian and blk0be() for big-endian. | |
| 31 | + */ | |
| 32 | +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) | |
| 33 | +#define blk0le(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ | |
| 34 | + |(rol(block->l[i],8)&0x00FF00FF)) | |
| 35 | +#define blk0be(i) block->l[i] | |
| 36 | +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ | |
| 37 | + ^block->l[(i+2)&15]^block->l[i&15],1)) | |
| 38 | + | |
| 39 | +/* | |
| 40 | + * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 | |
| 41 | + * | |
| 42 | + * Rl0() for little-endian and Rb0() for big-endian. Endianness is | |
| 43 | + * determined at run-time. | |
| 44 | + */ | |
| 45 | +#define Rl0(v,w,x,y,z,i) \ | |
| 46 | + z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=rol(w,30); | |
| 47 | +#define Rb0(v,w,x,y,z,i) \ | |
| 48 | + z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=rol(w,30); | |
| 49 | +#define R1(v,w,x,y,z,i) \ | |
| 50 | + z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); | |
| 51 | +#define R2(v,w,x,y,z,i) \ | |
| 52 | + z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); | |
| 53 | +#define R3(v,w,x,y,z,i) \ | |
| 54 | + z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); | |
| 55 | +#define R4(v,w,x,y,z,i) \ | |
| 56 | + z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); | |
| 57 | + | |
| 58 | +typedef union { | |
| 59 | + unsigned char c[64]; | |
| 60 | + unsigned int l[16]; | |
| 61 | +} CHAR64LONG16; | |
| 62 | + | |
| 63 | +/* | |
| 64 | + * Hash a single 512-bit block. This is the core of the algorithm. | |
| 65 | + */ | |
| 66 | +void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) | |
| 67 | +{ | |
| 68 | + unsigned int a, b, c, d, e; | |
| 69 | + CHAR64LONG16 *block; | |
| 70 | + static int one = 1; | |
| 71 | + CHAR64LONG16 workspace; | |
| 72 | + | |
| 73 | + block = &workspace; | |
| 74 | + (void)memcpy(block, buffer, 64); | |
| 75 | + | |
| 76 | + /* Copy context->state[] to working vars */ | |
| 77 | + a = state[0]; | |
| 78 | + b = state[1]; | |
| 79 | + c = state[2]; | |
| 80 | + d = state[3]; | |
| 81 | + e = state[4]; | |
| 82 | + | |
| 83 | + /* 4 rounds of 20 operations each. Loop unrolled. */ | |
| 84 | + if( 1 == *(unsigned char*)&one ){ | |
| 85 | + Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3); | |
| 86 | + Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7); | |
| 87 | + Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11); | |
| 88 | + Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15); | |
| 89 | + }else{ | |
| 90 | + Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3); | |
| 91 | + Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7); | |
| 92 | + Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11); | |
| 93 | + Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15); | |
| 94 | + } | |
| 95 | + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); | |
| 96 | + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); | |
| 97 | + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); | |
| 98 | + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); | |
| 99 | + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); | |
| 100 | + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); | |
| 101 | + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); | |
| 102 | + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); | |
| 103 | + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); | |
| 104 | + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); | |
| 105 | + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); | |
| 106 | + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); | |
| 107 | + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); | |
| 108 | + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); | |
| 109 | + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); | |
| 110 | + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); | |
| 111 | + | |
| 112 | + /* Add the working vars back into context.state[] */ | |
| 113 | + state[0] += a; | |
| 114 | + state[1] += b; | |
| 115 | + state[2] += c; | |
| 116 | + state[3] += d; | |
| 117 | + state[4] += e; | |
| 118 | + | |
| 119 | + /* Wipe variables */ | |
| 120 | + a = b = c = d = e = 0; | |
| 121 | +} | |
| 122 | + | |
| 123 | + | |
| 124 | +/* | |
| 125 | + * SHA1Init - Initialize new context | |
| 126 | + */ | |
| 127 | +static void SHA1Init(SHA1Context *context){ | |
| 128 | + /* SHA1 initialization constants */ | |
| 129 | + context->state[0] = 0x67452301; | |
| 130 | + context->state[1] = 0xEFCDAB89; | |
| 131 | + context->state[2] = 0x98BADCFE; | |
| 132 | + context->state[3] = 0x10325476; | |
| 133 | + context->state[4] = 0xC3D2E1F0; | |
| 134 | + context->count[0] = context->count[1] = 0; | |
| 135 | +} | |
| 136 | + | |
| 137 | + | |
| 138 | +/* | |
| 139 | + * Run your data through this. | |
| 140 | + */ | |
| 141 | +static void SHA1Update( | |
| 142 | + SHA1Context *context, | |
| 143 | + const unsigned char *data, | |
| 144 | + unsigned int len | |
| 145 | +){ | |
| 146 | + unsigned int i, j; | |
| 147 | + | |
| 148 | + j = context->count[0]; | |
| 149 | + if ((context->count[0] += len << 3) < j) | |
| 150 | + context->count[1] += (len>>29)+1; | |
| 151 | + j = (j >> 3) & 63; | |
| 152 | + if ((j + len) > 63) { | |
| 153 | + (void)memcpy(&context->buffer[j], data, (i = 64-j)); | |
| 154 | + SHA1Transform(context->state, context->buffer); | |
| 155 | + for ( ; i + 63 < len; i += 64) | |
| 156 | + SHA1Transform(context->state, &data[i]); | |
| 157 | + j = 0; | |
| 158 | + } else { | |
| 159 | + i = 0; | |
| 160 | + } | |
| 161 | + (void)memcpy(&context->buffer[j], &data[i], len - i); | |
| 162 | +} | |
| 163 | + | |
| 164 | + | |
| 165 | +/* | |
| 166 | + * Add padding and return the message digest. | |
| 167 | + */ | |
| 168 | +static void SHA1Final(SHA1Context *context, unsigned char digest[20]){ | |
| 169 | + unsigned int i; | |
| 170 | + unsigned char finalcount[8]; | |
| 171 | + | |
| 172 | + for (i = 0; i < 8; i++) { | |
| 173 | + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] | |
| 174 | + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ | |
| 175 | + } | |
| 176 | + SHA1Update(context, (const unsigned char *)"\200", 1); | |
| 177 | + while ((context->count[0] & 504) != 448) | |
| 178 | + SHA1Update(context, (const unsigned char *)"\0", 1); | |
| 179 | + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ | |
| 180 | + | |
| 181 | + if (digest) { | |
| 182 | + for (i = 0; i < 20; i++) | |
| 183 | + digest[i] = (unsigned char) | |
| 184 | + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); | |
| 185 | + } | |
| 413 | 186 | } |
| 414 | 187 | |
| 415 | 188 | |
| 416 | 189 | /* |
| 417 | 190 | ** Convert a digest into base-16. digest should be declared as |
| @@ -441,18 +214,18 @@ | ||
| 441 | 214 | /* |
| 442 | 215 | ** Add more text to the incremental SHA1 checksum. |
| 443 | 216 | */ |
| 444 | 217 | void sha1sum_step_text(const char *zText, int nBytes){ |
| 445 | 218 | if( !incrInit ){ |
| 446 | - SHA1Reset(&incrCtx); | |
| 219 | + SHA1Init(&incrCtx); | |
| 447 | 220 | incrInit = 1; |
| 448 | 221 | } |
| 449 | 222 | if( nBytes<=0 ){ |
| 450 | 223 | if( nBytes==0 ) return; |
| 451 | 224 | nBytes = strlen(zText); |
| 452 | 225 | } |
| 453 | - SHA1Input(&incrCtx, (unsigned char*)zText, nBytes); | |
| 226 | + SHA1Update(&incrCtx, (unsigned char*)zText, nBytes); | |
| 454 | 227 | } |
| 455 | 228 | |
| 456 | 229 | /* |
| 457 | 230 | ** Add the content of a blob to the incremental SHA1 checksum. |
| 458 | 231 | */ |
| @@ -470,11 +243,11 @@ | ||
| 470 | 243 | */ |
| 471 | 244 | char *sha1sum_finish(Blob *pOut){ |
| 472 | 245 | unsigned char zResult[20]; |
| 473 | 246 | static char zOut[41]; |
| 474 | 247 | sha1sum_step_text(0,0); |
| 475 | - SHA1Result(&incrCtx, zResult); | |
| 248 | + SHA1Final(&incrCtx, zResult); | |
| 476 | 249 | incrInit = 0; |
| 477 | 250 | DigestToBase16(zResult, zOut); |
| 478 | 251 | if( pOut ){ |
| 479 | 252 | blob_zero(pOut); |
| 480 | 253 | blob_append(pOut, zOut, 40); |
| @@ -497,21 +270,21 @@ | ||
| 497 | 270 | |
| 498 | 271 | in = fopen(zFilename,"rb"); |
| 499 | 272 | if( in==0 ){ |
| 500 | 273 | return 1; |
| 501 | 274 | } |
| 502 | - SHA1Reset(&ctx); | |
| 275 | + SHA1Init(&ctx); | |
| 503 | 276 | for(;;){ |
| 504 | 277 | int n; |
| 505 | 278 | n = fread(zBuf, 1, sizeof(zBuf), in); |
| 506 | 279 | if( n<=0 ) break; |
| 507 | - SHA1Input(&ctx, (unsigned char*)zBuf, (unsigned)n); | |
| 280 | + SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n); | |
| 508 | 281 | } |
| 509 | 282 | fclose(in); |
| 510 | 283 | blob_zero(pCksum); |
| 511 | 284 | blob_resize(pCksum, 40); |
| 512 | - SHA1Result(&ctx, zResult); | |
| 285 | + SHA1Final(&ctx, zResult); | |
| 513 | 286 | DigestToBase16(zResult, blob_buffer(pCksum)); |
| 514 | 287 | return 0; |
| 515 | 288 | } |
| 516 | 289 | |
| 517 | 290 | /* |
| @@ -523,19 +296,19 @@ | ||
| 523 | 296 | */ |
| 524 | 297 | int sha1sum_blob(const Blob *pIn, Blob *pCksum){ |
| 525 | 298 | SHA1Context ctx; |
| 526 | 299 | unsigned char zResult[20]; |
| 527 | 300 | |
| 528 | - SHA1Reset(&ctx); | |
| 529 | - SHA1Input(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); | |
| 301 | + SHA1Init(&ctx); | |
| 302 | + SHA1Update(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); | |
| 530 | 303 | if( pIn==pCksum ){ |
| 531 | 304 | blob_reset(pCksum); |
| 532 | 305 | }else{ |
| 533 | 306 | blob_zero(pCksum); |
| 534 | 307 | } |
| 535 | 308 | blob_resize(pCksum, 40); |
| 536 | - SHA1Result(&ctx, zResult); | |
| 309 | + SHA1Final(&ctx, zResult); | |
| 537 | 310 | DigestToBase16(zResult, blob_buffer(pCksum)); |
| 538 | 311 | return 0; |
| 539 | 312 | } |
| 540 | 313 | |
| 541 | 314 | /* |
| @@ -545,13 +318,13 @@ | ||
| 545 | 318 | char *sha1sum(const char *zIn){ |
| 546 | 319 | SHA1Context ctx; |
| 547 | 320 | unsigned char zResult[20]; |
| 548 | 321 | char zDigest[41]; |
| 549 | 322 | |
| 550 | - SHA1Reset(&ctx); | |
| 551 | - SHA1Input(&ctx, (unsigned const char*)zIn, strlen(zIn)); | |
| 552 | - SHA1Result(&ctx, zResult); | |
| 323 | + SHA1Init(&ctx); | |
| 324 | + SHA1Update(&ctx, (unsigned const char*)zIn, strlen(zIn)); | |
| 325 | + SHA1Final(&ctx, zResult); | |
| 553 | 326 | DigestToBase16(zResult, zDigest); |
| 554 | 327 | return mprintf("%s", zDigest); |
| 555 | 328 | } |
| 556 | 329 | |
| 557 | 330 | /* |
| @@ -575,11 +348,11 @@ | ||
| 575 | 348 | static char *zProjectId = 0; |
| 576 | 349 | SHA1Context ctx; |
| 577 | 350 | unsigned char zResult[20]; |
| 578 | 351 | char zDigest[41]; |
| 579 | 352 | |
| 580 | - SHA1Reset(&ctx); | |
| 353 | + SHA1Init(&ctx); | |
| 581 | 354 | if( zProjectId==0 ){ |
| 582 | 355 | zProjectId = db_get("project-code", 0); |
| 583 | 356 | |
| 584 | 357 | /* On the first xfer request of a clone, the project-code is not yet |
| 585 | 358 | ** known. Use the cleartext password, since that is all we have. |
| @@ -586,16 +359,16 @@ | ||
| 586 | 359 | */ |
| 587 | 360 | if( zProjectId==0 ){ |
| 588 | 361 | return mprintf("%s", zPw); |
| 589 | 362 | } |
| 590 | 363 | } |
| 591 | - SHA1Input(&ctx, (unsigned char*)zProjectId, strlen(zProjectId)); | |
| 592 | - SHA1Input(&ctx, (unsigned char*)"/", 1); | |
| 593 | - SHA1Input(&ctx, (unsigned char*)zLogin, strlen(zLogin)); | |
| 594 | - SHA1Input(&ctx, (unsigned char*)"/", 1); | |
| 595 | - SHA1Input(&ctx, (unsigned const char*)zPw, strlen(zPw)); | |
| 596 | - SHA1Result(&ctx, zResult); | |
| 364 | + SHA1Update(&ctx, (unsigned char*)zProjectId, strlen(zProjectId)); | |
| 365 | + SHA1Update(&ctx, (unsigned char*)"/", 1); | |
| 366 | + SHA1Update(&ctx, (unsigned char*)zLogin, strlen(zLogin)); | |
| 367 | + SHA1Update(&ctx, (unsigned char*)"/", 1); | |
| 368 | + SHA1Update(&ctx, (unsigned const char*)zPw, strlen(zPw)); | |
| 369 | + SHA1Final(&ctx, zResult); | |
| 597 | 370 | DigestToBase16(zResult, zDigest); |
| 598 | 371 | return mprintf("%s", zDigest); |
| 599 | 372 | } |
| 600 | 373 | |
| 601 | 374 | /* |
| 602 | 375 |
| --- src/sha1.c | |
| +++ src/sha1.c | |
| @@ -1,417 +1,190 @@ | |
| 1 | /* |
| 2 | ** This implementation of SHA1 is adapted from the example implementation |
| 3 | ** contained in RFC-3174. |
| 4 | */ |
| 5 | /* |
| 6 | * If you do not have the ISO standard stdint.h header file, then you |
| 7 | * must typdef the following: |
| 8 | * name meaning |
| 9 | * */ |
| 10 | #if defined(__DMC__) || defined(_MSC_VER) |
| 11 | typedef unsigned long uint32_t; //unsigned 32 bit integer |
| 12 | typedef unsigned char uint8_t; //unsigned 8 bit integer (i.e., unsigned char) |
| 13 | #else |
| 14 | # include <stdint.h> |
| 15 | #endif |
| 16 | #include <sys/types.h> |
| 17 | #include "config.h" |
| 18 | #include "sha1.h" |
| 19 | |
| 20 | #define SHA1HashSize 20 |
| 21 | #define shaSuccess 0 |
| 22 | #define shaInputTooLong 1 |
| 23 | #define shaStateError 2 |
| 24 | |
| 25 | /* |
| 26 | * This structure will hold context information for the SHA-1 |
| 27 | * hashing operation |
| 28 | */ |
| 29 | typedef struct SHA1Context SHA1Context; |
| 30 | struct SHA1Context { |
| 31 | uint32_t Intermediate_Hash[SHA1HashSize/4]; /* Message Digest */ |
| 32 | |
| 33 | uint32_t Length_Low; /* Message length in bits */ |
| 34 | uint32_t Length_High; /* Message length in bits */ |
| 35 | |
| 36 | int Message_Block_Index; /* Index into message block array */ |
| 37 | uint8_t Message_Block[64]; /* 512-bit message blocks */ |
| 38 | |
| 39 | int Computed; /* Is the digest computed? */ |
| 40 | int Corrupted; /* Is the message digest corrupted? */ |
| 41 | }; |
| 42 | |
| 43 | /* |
| 44 | * sha1.c |
| 45 | * |
| 46 | * Description: |
| 47 | * This file implements the Secure Hashing Algorithm 1 as |
| 48 | * defined in FIPS PUB 180-1 published April 17, 1995. |
| 49 | * |
| 50 | * The SHA-1, produces a 160-bit message digest for a given |
| 51 | * data stream. It should take about 2**n steps to find a |
| 52 | * message with the same digest as a given message and |
| 53 | * 2**(n/2) to find any two messages with the same digest, |
| 54 | * when n is the digest size in bits. Therefore, this |
| 55 | * algorithm can serve as a means of providing a |
| 56 | * "fingerprint" for a message. |
| 57 | * |
| 58 | * Portability Issues: |
| 59 | * SHA-1 is defined in terms of 32-bit "words". This code |
| 60 | * uses <stdint.h> (included via "sha1.h" to define 32 and 8 |
| 61 | * bit unsigned integer types. If your C compiler does not |
| 62 | * support 32 bit unsigned integers, this code is not |
| 63 | * appropriate. |
| 64 | * |
| 65 | * Caveats: |
| 66 | * SHA-1 is designed to work with messages less than 2^64 bits |
| 67 | * long. Although SHA-1 allows a message digest to be generated |
| 68 | * for messages of any number of bits less than 2^64, this |
| 69 | * implementation only works with messages with a length that is |
| 70 | * a multiple of the size of an 8-bit character. |
| 71 | * |
| 72 | */ |
| 73 | |
| 74 | /* |
| 75 | * Define the SHA1 circular left shift macro |
| 76 | */ |
| 77 | #define SHA1CircularShift(bits,word) \ |
| 78 | (((word) << (bits)) | ((word) >> (32-(bits)))) |
| 79 | |
| 80 | /* Local Function Prototyptes */ |
| 81 | static void SHA1PadMessage(SHA1Context *); |
| 82 | static void SHA1ProcessMessageBlock(SHA1Context *); |
| 83 | |
| 84 | /* |
| 85 | * SHA1Reset |
| 86 | * |
| 87 | * Description: |
| 88 | * This function will initialize the SHA1Context in preparation |
| 89 | * for computing a new SHA1 message digest. |
| 90 | * |
| 91 | * Parameters: |
| 92 | * context: [in/out] |
| 93 | * The context to reset. |
| 94 | * |
| 95 | * Returns: |
| 96 | * sha Error Code. |
| 97 | * |
| 98 | */ |
| 99 | static int SHA1Reset(SHA1Context *context) |
| 100 | { |
| 101 | context->Length_Low = 0; |
| 102 | context->Length_High = 0; |
| 103 | context->Message_Block_Index = 0; |
| 104 | |
| 105 | context->Intermediate_Hash[0] = 0x67452301; |
| 106 | context->Intermediate_Hash[1] = 0xEFCDAB89; |
| 107 | context->Intermediate_Hash[2] = 0x98BADCFE; |
| 108 | context->Intermediate_Hash[3] = 0x10325476; |
| 109 | context->Intermediate_Hash[4] = 0xC3D2E1F0; |
| 110 | |
| 111 | context->Computed = 0; |
| 112 | context->Corrupted = 0; |
| 113 | |
| 114 | return shaSuccess; |
| 115 | } |
| 116 | |
| 117 | /* |
| 118 | * SHA1Result |
| 119 | * |
| 120 | * Description: |
| 121 | * This function will return the 160-bit message digest into the |
| 122 | * Message_Digest array provided by the caller. |
| 123 | * NOTE: The first octet of hash is stored in the 0th element, |
| 124 | * the last octet of hash in the 19th element. |
| 125 | * |
| 126 | * Parameters: |
| 127 | * context: [in/out] |
| 128 | * The context to use to calculate the SHA-1 hash. |
| 129 | * Message_Digest: [out] |
| 130 | * Where the digest is returned. |
| 131 | * |
| 132 | * Returns: |
| 133 | * sha Error Code. |
| 134 | * |
| 135 | */ |
| 136 | static int SHA1Result( SHA1Context *context, |
| 137 | uint8_t Message_Digest[SHA1HashSize]) |
| 138 | { |
| 139 | int i; |
| 140 | |
| 141 | if (context->Corrupted) |
| 142 | { |
| 143 | return context->Corrupted; |
| 144 | } |
| 145 | |
| 146 | if (!context->Computed) |
| 147 | { |
| 148 | SHA1PadMessage(context); |
| 149 | for(i=0; i<64; ++i) |
| 150 | { |
| 151 | /* message may be sensitive, clear it out */ |
| 152 | context->Message_Block[i] = 0; |
| 153 | } |
| 154 | context->Length_Low = 0; /* and clear length */ |
| 155 | context->Length_High = 0; |
| 156 | context->Computed = 1; |
| 157 | |
| 158 | } |
| 159 | |
| 160 | for(i = 0; i < SHA1HashSize; ++i) |
| 161 | { |
| 162 | Message_Digest[i] = context->Intermediate_Hash[i>>2] |
| 163 | >> 8 * ( 3 - ( i & 0x03 ) ); |
| 164 | } |
| 165 | |
| 166 | return shaSuccess; |
| 167 | } |
| 168 | |
| 169 | /* |
| 170 | * SHA1Input |
| 171 | * |
| 172 | * Description: |
| 173 | * This function accepts an array of octets as the next portion |
| 174 | * of the message. |
| 175 | * |
| 176 | * Parameters: |
| 177 | * context: [in/out] |
| 178 | * The SHA context to update |
| 179 | * message_array: [in] |
| 180 | * An array of characters representing the next portion of |
| 181 | * the message. |
| 182 | * length: [in] |
| 183 | * The length of the message in message_array |
| 184 | * |
| 185 | * Returns: |
| 186 | * sha Error Code. |
| 187 | * |
| 188 | */ |
| 189 | static |
| 190 | int SHA1Input( SHA1Context *context, |
| 191 | const uint8_t *message_array, |
| 192 | unsigned length) |
| 193 | { |
| 194 | if (!length) |
| 195 | { |
| 196 | return shaSuccess; |
| 197 | } |
| 198 | |
| 199 | if (context->Computed) |
| 200 | { |
| 201 | context->Corrupted = shaStateError; |
| 202 | |
| 203 | return shaStateError; |
| 204 | } |
| 205 | |
| 206 | if (context->Corrupted) |
| 207 | { |
| 208 | return context->Corrupted; |
| 209 | } |
| 210 | while(length-- && !context->Corrupted) |
| 211 | { |
| 212 | context->Message_Block[context->Message_Block_Index++] = |
| 213 | (*message_array & 0xFF); |
| 214 | |
| 215 | context->Length_Low += 8; |
| 216 | if (context->Length_Low == 0) |
| 217 | { |
| 218 | context->Length_High++; |
| 219 | if (context->Length_High == 0) |
| 220 | { |
| 221 | /* Message is too long */ |
| 222 | context->Corrupted = 1; |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | if (context->Message_Block_Index == 64) |
| 227 | { |
| 228 | SHA1ProcessMessageBlock(context); |
| 229 | } |
| 230 | |
| 231 | message_array++; |
| 232 | } |
| 233 | |
| 234 | return shaSuccess; |
| 235 | } |
| 236 | |
| 237 | /* |
| 238 | * SHA1ProcessMessageBlock |
| 239 | * |
| 240 | * Description: |
| 241 | * This function will process the next 512 bits of the message |
| 242 | * stored in the Message_Block array. |
| 243 | * |
| 244 | * Parameters: |
| 245 | * None. |
| 246 | * |
| 247 | * Returns: |
| 248 | * Nothing. |
| 249 | * |
| 250 | * Comments: |
| 251 | * Many of the variable names in this code, especially the |
| 252 | * single character names, were used because those were the |
| 253 | * names used in the publication. |
| 254 | * |
| 255 | * |
| 256 | */ |
| 257 | static void SHA1ProcessMessageBlock(SHA1Context *context) |
| 258 | { |
| 259 | const uint32_t K[] = { /* Constants defined in SHA-1 */ |
| 260 | 0x5A827999, |
| 261 | 0x6ED9EBA1, |
| 262 | 0x8F1BBCDC, |
| 263 | 0xCA62C1D6 |
| 264 | }; |
| 265 | int t; /* Loop counter */ |
| 266 | uint32_t temp; /* Temporary word value */ |
| 267 | uint32_t W[80]; /* Word sequence */ |
| 268 | uint32_t A, B, C, D, E; /* Word buffers */ |
| 269 | |
| 270 | /* |
| 271 | * Initialize the first 16 words in the array W |
| 272 | */ |
| 273 | for(t = 0; t < 16; t++) |
| 274 | { |
| 275 | W[t] = context->Message_Block[t * 4] << 24; |
| 276 | W[t] |= context->Message_Block[t * 4 + 1] << 16; |
| 277 | W[t] |= context->Message_Block[t * 4 + 2] << 8; |
| 278 | W[t] |= context->Message_Block[t * 4 + 3]; |
| 279 | } |
| 280 | |
| 281 | for(t = 16; t < 80; t++) |
| 282 | { |
| 283 | W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); |
| 284 | } |
| 285 | |
| 286 | A = context->Intermediate_Hash[0]; |
| 287 | B = context->Intermediate_Hash[1]; |
| 288 | C = context->Intermediate_Hash[2]; |
| 289 | D = context->Intermediate_Hash[3]; |
| 290 | E = context->Intermediate_Hash[4]; |
| 291 | |
| 292 | for(t = 0; t < 20; t++) |
| 293 | { |
| 294 | temp = SHA1CircularShift(5,A) + |
| 295 | ((B & C) | ((~B) & D)) + E + W[t] + K[0]; |
| 296 | E = D; |
| 297 | D = C; |
| 298 | C = SHA1CircularShift(30,B); |
| 299 | |
| 300 | B = A; |
| 301 | A = temp; |
| 302 | } |
| 303 | |
| 304 | for(t = 20; t < 40; t++) |
| 305 | { |
| 306 | temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; |
| 307 | E = D; |
| 308 | D = C; |
| 309 | C = SHA1CircularShift(30,B); |
| 310 | B = A; |
| 311 | A = temp; |
| 312 | } |
| 313 | |
| 314 | for(t = 40; t < 60; t++) |
| 315 | { |
| 316 | temp = SHA1CircularShift(5,A) + |
| 317 | ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; |
| 318 | E = D; |
| 319 | D = C; |
| 320 | C = SHA1CircularShift(30,B); |
| 321 | B = A; |
| 322 | A = temp; |
| 323 | } |
| 324 | |
| 325 | for(t = 60; t < 80; t++) |
| 326 | { |
| 327 | temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; |
| 328 | E = D; |
| 329 | D = C; |
| 330 | C = SHA1CircularShift(30,B); |
| 331 | B = A; |
| 332 | A = temp; |
| 333 | } |
| 334 | |
| 335 | context->Intermediate_Hash[0] += A; |
| 336 | context->Intermediate_Hash[1] += B; |
| 337 | context->Intermediate_Hash[2] += C; |
| 338 | context->Intermediate_Hash[3] += D; |
| 339 | context->Intermediate_Hash[4] += E; |
| 340 | |
| 341 | context->Message_Block_Index = 0; |
| 342 | } |
| 343 | |
| 344 | /* |
| 345 | * SHA1PadMessage |
| 346 | * |
| 347 | |
| 348 | * Description: |
| 349 | * According to the standard, the message must be padded to an even |
| 350 | * 512 bits. The first padding bit must be a '1'. The last 64 |
| 351 | * bits represent the length of the original message. All bits in |
| 352 | * between should be 0. This function will pad the message |
| 353 | * according to those rules by filling the Message_Block array |
| 354 | * accordingly. It will also call the ProcessMessageBlock function |
| 355 | * provided appropriately. When it returns, it can be assumed that |
| 356 | * the message digest has been computed. |
| 357 | * |
| 358 | * Parameters: |
| 359 | * context: [in/out] |
| 360 | * The context to pad |
| 361 | * ProcessMessageBlock: [in] |
| 362 | * The appropriate SHA*ProcessMessageBlock function |
| 363 | * Returns: |
| 364 | * Nothing. |
| 365 | * |
| 366 | */ |
| 367 | static void SHA1PadMessage(SHA1Context *context) |
| 368 | { |
| 369 | /* |
| 370 | * Check to see if the current message block is too small to hold |
| 371 | * the initial padding bits and length. If so, we will pad the |
| 372 | * block, process it, and then continue padding into a second |
| 373 | * block. |
| 374 | */ |
| 375 | if (context->Message_Block_Index > 55) |
| 376 | { |
| 377 | context->Message_Block[context->Message_Block_Index++] = 0x80; |
| 378 | while(context->Message_Block_Index < 64) |
| 379 | { |
| 380 | context->Message_Block[context->Message_Block_Index++] = 0; |
| 381 | } |
| 382 | |
| 383 | SHA1ProcessMessageBlock(context); |
| 384 | |
| 385 | while(context->Message_Block_Index < 56) |
| 386 | { |
| 387 | context->Message_Block[context->Message_Block_Index++] = 0; |
| 388 | } |
| 389 | } |
| 390 | else |
| 391 | { |
| 392 | context->Message_Block[context->Message_Block_Index++] = 0x80; |
| 393 | while(context->Message_Block_Index < 56) |
| 394 | { |
| 395 | |
| 396 | context->Message_Block[context->Message_Block_Index++] = 0; |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | /* |
| 401 | * Store the message length as the last 8 octets |
| 402 | */ |
| 403 | context->Message_Block[56] = context->Length_High >> 24; |
| 404 | context->Message_Block[57] = context->Length_High >> 16; |
| 405 | context->Message_Block[58] = context->Length_High >> 8; |
| 406 | context->Message_Block[59] = context->Length_High; |
| 407 | context->Message_Block[60] = context->Length_Low >> 24; |
| 408 | context->Message_Block[61] = context->Length_Low >> 16; |
| 409 | context->Message_Block[62] = context->Length_Low >> 8; |
| 410 | context->Message_Block[63] = context->Length_Low; |
| 411 | |
| 412 | SHA1ProcessMessageBlock(context); |
| 413 | } |
| 414 | |
| 415 | |
| 416 | /* |
| 417 | ** Convert a digest into base-16. digest should be declared as |
| @@ -441,18 +214,18 @@ | |
| 441 | /* |
| 442 | ** Add more text to the incremental SHA1 checksum. |
| 443 | */ |
| 444 | void sha1sum_step_text(const char *zText, int nBytes){ |
| 445 | if( !incrInit ){ |
| 446 | SHA1Reset(&incrCtx); |
| 447 | incrInit = 1; |
| 448 | } |
| 449 | if( nBytes<=0 ){ |
| 450 | if( nBytes==0 ) return; |
| 451 | nBytes = strlen(zText); |
| 452 | } |
| 453 | SHA1Input(&incrCtx, (unsigned char*)zText, nBytes); |
| 454 | } |
| 455 | |
| 456 | /* |
| 457 | ** Add the content of a blob to the incremental SHA1 checksum. |
| 458 | */ |
| @@ -470,11 +243,11 @@ | |
| 470 | */ |
| 471 | char *sha1sum_finish(Blob *pOut){ |
| 472 | unsigned char zResult[20]; |
| 473 | static char zOut[41]; |
| 474 | sha1sum_step_text(0,0); |
| 475 | SHA1Result(&incrCtx, zResult); |
| 476 | incrInit = 0; |
| 477 | DigestToBase16(zResult, zOut); |
| 478 | if( pOut ){ |
| 479 | blob_zero(pOut); |
| 480 | blob_append(pOut, zOut, 40); |
| @@ -497,21 +270,21 @@ | |
| 497 | |
| 498 | in = fopen(zFilename,"rb"); |
| 499 | if( in==0 ){ |
| 500 | return 1; |
| 501 | } |
| 502 | SHA1Reset(&ctx); |
| 503 | for(;;){ |
| 504 | int n; |
| 505 | n = fread(zBuf, 1, sizeof(zBuf), in); |
| 506 | if( n<=0 ) break; |
| 507 | SHA1Input(&ctx, (unsigned char*)zBuf, (unsigned)n); |
| 508 | } |
| 509 | fclose(in); |
| 510 | blob_zero(pCksum); |
| 511 | blob_resize(pCksum, 40); |
| 512 | SHA1Result(&ctx, zResult); |
| 513 | DigestToBase16(zResult, blob_buffer(pCksum)); |
| 514 | return 0; |
| 515 | } |
| 516 | |
| 517 | /* |
| @@ -523,19 +296,19 @@ | |
| 523 | */ |
| 524 | int sha1sum_blob(const Blob *pIn, Blob *pCksum){ |
| 525 | SHA1Context ctx; |
| 526 | unsigned char zResult[20]; |
| 527 | |
| 528 | SHA1Reset(&ctx); |
| 529 | SHA1Input(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); |
| 530 | if( pIn==pCksum ){ |
| 531 | blob_reset(pCksum); |
| 532 | }else{ |
| 533 | blob_zero(pCksum); |
| 534 | } |
| 535 | blob_resize(pCksum, 40); |
| 536 | SHA1Result(&ctx, zResult); |
| 537 | DigestToBase16(zResult, blob_buffer(pCksum)); |
| 538 | return 0; |
| 539 | } |
| 540 | |
| 541 | /* |
| @@ -545,13 +318,13 @@ | |
| 545 | char *sha1sum(const char *zIn){ |
| 546 | SHA1Context ctx; |
| 547 | unsigned char zResult[20]; |
| 548 | char zDigest[41]; |
| 549 | |
| 550 | SHA1Reset(&ctx); |
| 551 | SHA1Input(&ctx, (unsigned const char*)zIn, strlen(zIn)); |
| 552 | SHA1Result(&ctx, zResult); |
| 553 | DigestToBase16(zResult, zDigest); |
| 554 | return mprintf("%s", zDigest); |
| 555 | } |
| 556 | |
| 557 | /* |
| @@ -575,11 +348,11 @@ | |
| 575 | static char *zProjectId = 0; |
| 576 | SHA1Context ctx; |
| 577 | unsigned char zResult[20]; |
| 578 | char zDigest[41]; |
| 579 | |
| 580 | SHA1Reset(&ctx); |
| 581 | if( zProjectId==0 ){ |
| 582 | zProjectId = db_get("project-code", 0); |
| 583 | |
| 584 | /* On the first xfer request of a clone, the project-code is not yet |
| 585 | ** known. Use the cleartext password, since that is all we have. |
| @@ -586,16 +359,16 @@ | |
| 586 | */ |
| 587 | if( zProjectId==0 ){ |
| 588 | return mprintf("%s", zPw); |
| 589 | } |
| 590 | } |
| 591 | SHA1Input(&ctx, (unsigned char*)zProjectId, strlen(zProjectId)); |
| 592 | SHA1Input(&ctx, (unsigned char*)"/", 1); |
| 593 | SHA1Input(&ctx, (unsigned char*)zLogin, strlen(zLogin)); |
| 594 | SHA1Input(&ctx, (unsigned char*)"/", 1); |
| 595 | SHA1Input(&ctx, (unsigned const char*)zPw, strlen(zPw)); |
| 596 | SHA1Result(&ctx, zResult); |
| 597 | DigestToBase16(zResult, zDigest); |
| 598 | return mprintf("%s", zDigest); |
| 599 | } |
| 600 | |
| 601 | /* |
| 602 |
| --- src/sha1.c | |
| +++ src/sha1.c | |
| @@ -1,417 +1,190 @@ | |
| 1 | /* |
| 2 | ** This implementation of SHA1. |
| 3 | */ |
| 4 | #include <sys/types.h> |
| 5 | #include "config.h" |
| 6 | #include "sha1.h" |
| 7 | |
| 8 | |
| 9 | /* |
| 10 | ** The SHA1 implementation below is adapted from: |
| 11 | ** |
| 12 | ** $NetBSD: sha1.c,v 1.6 2009/11/06 20:31:18 joerg Exp $ |
| 13 | ** $OpenBSD: sha1.c,v 1.9 1997/07/23 21:12:32 kstailey Exp $ |
| 14 | ** |
| 15 | ** SHA-1 in C |
| 16 | ** By Steve Reid <[email protected]> |
| 17 | ** 100% Public Domain |
| 18 | */ |
| 19 | typedef struct SHA1Context SHA1Context; |
| 20 | struct SHA1Context { |
| 21 | unsigned int state[5]; |
| 22 | unsigned int count[2]; |
| 23 | unsigned char buffer[64]; |
| 24 | }; |
| 25 | |
| 26 | /* |
| 27 | * blk0() and blk() perform the initial expand. |
| 28 | * I got the idea of expanding during the round function from SSLeay |
| 29 | * |
| 30 | * blk0le() for little-endian and blk0be() for big-endian. |
| 31 | */ |
| 32 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) |
| 33 | #define blk0le(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ |
| 34 | |(rol(block->l[i],8)&0x00FF00FF)) |
| 35 | #define blk0be(i) block->l[i] |
| 36 | #define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ |
| 37 | ^block->l[(i+2)&15]^block->l[i&15],1)) |
| 38 | |
| 39 | /* |
| 40 | * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 |
| 41 | * |
| 42 | * Rl0() for little-endian and Rb0() for big-endian. Endianness is |
| 43 | * determined at run-time. |
| 44 | */ |
| 45 | #define Rl0(v,w,x,y,z,i) \ |
| 46 | z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=rol(w,30); |
| 47 | #define Rb0(v,w,x,y,z,i) \ |
| 48 | z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=rol(w,30); |
| 49 | #define R1(v,w,x,y,z,i) \ |
| 50 | z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); |
| 51 | #define R2(v,w,x,y,z,i) \ |
| 52 | z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); |
| 53 | #define R3(v,w,x,y,z,i) \ |
| 54 | z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); |
| 55 | #define R4(v,w,x,y,z,i) \ |
| 56 | z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); |
| 57 | |
| 58 | typedef union { |
| 59 | unsigned char c[64]; |
| 60 | unsigned int l[16]; |
| 61 | } CHAR64LONG16; |
| 62 | |
| 63 | /* |
| 64 | * Hash a single 512-bit block. This is the core of the algorithm. |
| 65 | */ |
| 66 | void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]) |
| 67 | { |
| 68 | unsigned int a, b, c, d, e; |
| 69 | CHAR64LONG16 *block; |
| 70 | static int one = 1; |
| 71 | CHAR64LONG16 workspace; |
| 72 | |
| 73 | block = &workspace; |
| 74 | (void)memcpy(block, buffer, 64); |
| 75 | |
| 76 | /* Copy context->state[] to working vars */ |
| 77 | a = state[0]; |
| 78 | b = state[1]; |
| 79 | c = state[2]; |
| 80 | d = state[3]; |
| 81 | e = state[4]; |
| 82 | |
| 83 | /* 4 rounds of 20 operations each. Loop unrolled. */ |
| 84 | if( 1 == *(unsigned char*)&one ){ |
| 85 | Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3); |
| 86 | Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7); |
| 87 | Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11); |
| 88 | Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15); |
| 89 | }else{ |
| 90 | Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3); |
| 91 | Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7); |
| 92 | Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11); |
| 93 | Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15); |
| 94 | } |
| 95 | R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); |
| 96 | R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); |
| 97 | R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); |
| 98 | R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); |
| 99 | R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); |
| 100 | R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); |
| 101 | R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); |
| 102 | R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); |
| 103 | R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); |
| 104 | R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); |
| 105 | R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); |
| 106 | R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); |
| 107 | R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); |
| 108 | R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); |
| 109 | R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); |
| 110 | R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); |
| 111 | |
| 112 | /* Add the working vars back into context.state[] */ |
| 113 | state[0] += a; |
| 114 | state[1] += b; |
| 115 | state[2] += c; |
| 116 | state[3] += d; |
| 117 | state[4] += e; |
| 118 | |
| 119 | /* Wipe variables */ |
| 120 | a = b = c = d = e = 0; |
| 121 | } |
| 122 | |
| 123 | |
| 124 | /* |
| 125 | * SHA1Init - Initialize new context |
| 126 | */ |
| 127 | static void SHA1Init(SHA1Context *context){ |
| 128 | /* SHA1 initialization constants */ |
| 129 | context->state[0] = 0x67452301; |
| 130 | context->state[1] = 0xEFCDAB89; |
| 131 | context->state[2] = 0x98BADCFE; |
| 132 | context->state[3] = 0x10325476; |
| 133 | context->state[4] = 0xC3D2E1F0; |
| 134 | context->count[0] = context->count[1] = 0; |
| 135 | } |
| 136 | |
| 137 | |
| 138 | /* |
| 139 | * Run your data through this. |
| 140 | */ |
| 141 | static void SHA1Update( |
| 142 | SHA1Context *context, |
| 143 | const unsigned char *data, |
| 144 | unsigned int len |
| 145 | ){ |
| 146 | unsigned int i, j; |
| 147 | |
| 148 | j = context->count[0]; |
| 149 | if ((context->count[0] += len << 3) < j) |
| 150 | context->count[1] += (len>>29)+1; |
| 151 | j = (j >> 3) & 63; |
| 152 | if ((j + len) > 63) { |
| 153 | (void)memcpy(&context->buffer[j], data, (i = 64-j)); |
| 154 | SHA1Transform(context->state, context->buffer); |
| 155 | for ( ; i + 63 < len; i += 64) |
| 156 | SHA1Transform(context->state, &data[i]); |
| 157 | j = 0; |
| 158 | } else { |
| 159 | i = 0; |
| 160 | } |
| 161 | (void)memcpy(&context->buffer[j], &data[i], len - i); |
| 162 | } |
| 163 | |
| 164 | |
| 165 | /* |
| 166 | * Add padding and return the message digest. |
| 167 | */ |
| 168 | static void SHA1Final(SHA1Context *context, unsigned char digest[20]){ |
| 169 | unsigned int i; |
| 170 | unsigned char finalcount[8]; |
| 171 | |
| 172 | for (i = 0; i < 8; i++) { |
| 173 | finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] |
| 174 | >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ |
| 175 | } |
| 176 | SHA1Update(context, (const unsigned char *)"\200", 1); |
| 177 | while ((context->count[0] & 504) != 448) |
| 178 | SHA1Update(context, (const unsigned char *)"\0", 1); |
| 179 | SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ |
| 180 | |
| 181 | if (digest) { |
| 182 | for (i = 0; i < 20; i++) |
| 183 | digest[i] = (unsigned char) |
| 184 | ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | |
| 189 | /* |
| 190 | ** Convert a digest into base-16. digest should be declared as |
| @@ -441,18 +214,18 @@ | |
| 214 | /* |
| 215 | ** Add more text to the incremental SHA1 checksum. |
| 216 | */ |
| 217 | void sha1sum_step_text(const char *zText, int nBytes){ |
| 218 | if( !incrInit ){ |
| 219 | SHA1Init(&incrCtx); |
| 220 | incrInit = 1; |
| 221 | } |
| 222 | if( nBytes<=0 ){ |
| 223 | if( nBytes==0 ) return; |
| 224 | nBytes = strlen(zText); |
| 225 | } |
| 226 | SHA1Update(&incrCtx, (unsigned char*)zText, nBytes); |
| 227 | } |
| 228 | |
| 229 | /* |
| 230 | ** Add the content of a blob to the incremental SHA1 checksum. |
| 231 | */ |
| @@ -470,11 +243,11 @@ | |
| 243 | */ |
| 244 | char *sha1sum_finish(Blob *pOut){ |
| 245 | unsigned char zResult[20]; |
| 246 | static char zOut[41]; |
| 247 | sha1sum_step_text(0,0); |
| 248 | SHA1Final(&incrCtx, zResult); |
| 249 | incrInit = 0; |
| 250 | DigestToBase16(zResult, zOut); |
| 251 | if( pOut ){ |
| 252 | blob_zero(pOut); |
| 253 | blob_append(pOut, zOut, 40); |
| @@ -497,21 +270,21 @@ | |
| 270 | |
| 271 | in = fopen(zFilename,"rb"); |
| 272 | if( in==0 ){ |
| 273 | return 1; |
| 274 | } |
| 275 | SHA1Init(&ctx); |
| 276 | for(;;){ |
| 277 | int n; |
| 278 | n = fread(zBuf, 1, sizeof(zBuf), in); |
| 279 | if( n<=0 ) break; |
| 280 | SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n); |
| 281 | } |
| 282 | fclose(in); |
| 283 | blob_zero(pCksum); |
| 284 | blob_resize(pCksum, 40); |
| 285 | SHA1Final(&ctx, zResult); |
| 286 | DigestToBase16(zResult, blob_buffer(pCksum)); |
| 287 | return 0; |
| 288 | } |
| 289 | |
| 290 | /* |
| @@ -523,19 +296,19 @@ | |
| 296 | */ |
| 297 | int sha1sum_blob(const Blob *pIn, Blob *pCksum){ |
| 298 | SHA1Context ctx; |
| 299 | unsigned char zResult[20]; |
| 300 | |
| 301 | SHA1Init(&ctx); |
| 302 | SHA1Update(&ctx, (unsigned char*)blob_buffer(pIn), blob_size(pIn)); |
| 303 | if( pIn==pCksum ){ |
| 304 | blob_reset(pCksum); |
| 305 | }else{ |
| 306 | blob_zero(pCksum); |
| 307 | } |
| 308 | blob_resize(pCksum, 40); |
| 309 | SHA1Final(&ctx, zResult); |
| 310 | DigestToBase16(zResult, blob_buffer(pCksum)); |
| 311 | return 0; |
| 312 | } |
| 313 | |
| 314 | /* |
| @@ -545,13 +318,13 @@ | |
| 318 | char *sha1sum(const char *zIn){ |
| 319 | SHA1Context ctx; |
| 320 | unsigned char zResult[20]; |
| 321 | char zDigest[41]; |
| 322 | |
| 323 | SHA1Init(&ctx); |
| 324 | SHA1Update(&ctx, (unsigned const char*)zIn, strlen(zIn)); |
| 325 | SHA1Final(&ctx, zResult); |
| 326 | DigestToBase16(zResult, zDigest); |
| 327 | return mprintf("%s", zDigest); |
| 328 | } |
| 329 | |
| 330 | /* |
| @@ -575,11 +348,11 @@ | |
| 348 | static char *zProjectId = 0; |
| 349 | SHA1Context ctx; |
| 350 | unsigned char zResult[20]; |
| 351 | char zDigest[41]; |
| 352 | |
| 353 | SHA1Init(&ctx); |
| 354 | if( zProjectId==0 ){ |
| 355 | zProjectId = db_get("project-code", 0); |
| 356 | |
| 357 | /* On the first xfer request of a clone, the project-code is not yet |
| 358 | ** known. Use the cleartext password, since that is all we have. |
| @@ -586,16 +359,16 @@ | |
| 359 | */ |
| 360 | if( zProjectId==0 ){ |
| 361 | return mprintf("%s", zPw); |
| 362 | } |
| 363 | } |
| 364 | SHA1Update(&ctx, (unsigned char*)zProjectId, strlen(zProjectId)); |
| 365 | SHA1Update(&ctx, (unsigned char*)"/", 1); |
| 366 | SHA1Update(&ctx, (unsigned char*)zLogin, strlen(zLogin)); |
| 367 | SHA1Update(&ctx, (unsigned char*)"/", 1); |
| 368 | SHA1Update(&ctx, (unsigned const char*)zPw, strlen(zPw)); |
| 369 | SHA1Final(&ctx, zResult); |
| 370 | DigestToBase16(zResult, zDigest); |
| 371 | return mprintf("%s", zDigest); |
| 372 | } |
| 373 | |
| 374 | /* |
| 375 |
+13
-14
| --- src/tkt.c | ||
| +++ src/tkt.c | ||
| @@ -214,25 +214,25 @@ | ||
| 214 | 214 | */ |
| 215 | 215 | void ticket_rebuild_entry(const char *zTktUuid){ |
| 216 | 216 | char *zTag = mprintf("tkt-%s", zTktUuid); |
| 217 | 217 | int tagid = tag_findid(zTag, 1); |
| 218 | 218 | Stmt q; |
| 219 | - Manifest manifest; | |
| 220 | - Blob content; | |
| 219 | + Manifest *pTicket; | |
| 221 | 220 | int createFlag = 1; |
| 222 | 221 | |
| 223 | 222 | db_multi_exec( |
| 224 | 223 | "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid |
| 225 | 224 | ); |
| 226 | 225 | db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); |
| 227 | 226 | while( db_step(&q)==SQLITE_ROW ){ |
| 228 | 227 | int rid = db_column_int(&q, 0); |
| 229 | - content_get(rid, &content); | |
| 230 | - manifest_parse(&manifest, &content); | |
| 231 | - ticket_insert(&manifest, createFlag, rid); | |
| 232 | - manifest_ticket_event(rid, &manifest, createFlag, tagid); | |
| 233 | - manifest_clear(&manifest); | |
| 228 | + pTicket = manifest_get(rid, CFTYPE_TICKET); | |
| 229 | + if( pTicket ){ | |
| 230 | + ticket_insert(pTicket, createFlag, rid); | |
| 231 | + manifest_ticket_event(rid, pTicket, createFlag, tagid); | |
| 232 | + manifest_destroy(pTicket); | |
| 233 | + } | |
| 234 | 234 | createFlag = 0; |
| 235 | 235 | } |
| 236 | 236 | db_finalize(&q); |
| 237 | 237 | } |
| 238 | 238 | |
| @@ -753,12 +753,11 @@ | ||
| 753 | 753 | " AND blob.rid=attachid" |
| 754 | 754 | " ORDER BY 1 DESC", |
| 755 | 755 | tagid, tagid |
| 756 | 756 | ); |
| 757 | 757 | while( db_step(&q)==SQLITE_ROW ){ |
| 758 | - Blob content; | |
| 759 | - Manifest m; | |
| 758 | + Manifest *pTicket; | |
| 760 | 759 | char zShort[12]; |
| 761 | 760 | const char *zDate = db_column_text(&q, 0); |
| 762 | 761 | int rid = db_column_int(&q, 1); |
| 763 | 762 | const char *zChngUuid = db_column_text(&q, 2); |
| 764 | 763 | const char *zFile = db_column_text(&q, 4); |
| @@ -777,22 +776,22 @@ | ||
| 777 | 776 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 778 | 777 | @ (rid %d(rid)) by |
| 779 | 778 | hyperlink_to_user(zUser,zDate," on"); |
| 780 | 779 | hyperlink_to_date(zDate, ".</p>"); |
| 781 | 780 | }else{ |
| 782 | - content_get(rid, &content); | |
| 783 | - if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){ | |
| 781 | + pTicket = manifest_get(rid, CFTYPE_TICKET); | |
| 782 | + if( pTicket ){ | |
| 784 | 783 | @ |
| 785 | 784 | @ <p>Ticket change |
| 786 | 785 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 787 | 786 | @ (rid %d(rid)) by |
| 788 | - hyperlink_to_user(m.zUser,zDate," on"); | |
| 787 | + hyperlink_to_user(pTicket->zUser,zDate," on"); | |
| 789 | 788 | hyperlink_to_date(zDate, ":"); |
| 790 | 789 | @ </p> |
| 791 | - ticket_output_change_artifact(&m); | |
| 790 | + ticket_output_change_artifact(pTicket); | |
| 792 | 791 | } |
| 793 | - manifest_clear(&m); | |
| 792 | + manifest_destroy(pTicket); | |
| 794 | 793 | } |
| 795 | 794 | } |
| 796 | 795 | db_finalize(&q); |
| 797 | 796 | style_footer(); |
| 798 | 797 | } |
| 799 | 798 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -214,25 +214,25 @@ | |
| 214 | */ |
| 215 | void ticket_rebuild_entry(const char *zTktUuid){ |
| 216 | char *zTag = mprintf("tkt-%s", zTktUuid); |
| 217 | int tagid = tag_findid(zTag, 1); |
| 218 | Stmt q; |
| 219 | Manifest manifest; |
| 220 | Blob content; |
| 221 | int createFlag = 1; |
| 222 | |
| 223 | db_multi_exec( |
| 224 | "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid |
| 225 | ); |
| 226 | db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); |
| 227 | while( db_step(&q)==SQLITE_ROW ){ |
| 228 | int rid = db_column_int(&q, 0); |
| 229 | content_get(rid, &content); |
| 230 | manifest_parse(&manifest, &content); |
| 231 | ticket_insert(&manifest, createFlag, rid); |
| 232 | manifest_ticket_event(rid, &manifest, createFlag, tagid); |
| 233 | manifest_clear(&manifest); |
| 234 | createFlag = 0; |
| 235 | } |
| 236 | db_finalize(&q); |
| 237 | } |
| 238 | |
| @@ -753,12 +753,11 @@ | |
| 753 | " AND blob.rid=attachid" |
| 754 | " ORDER BY 1 DESC", |
| 755 | tagid, tagid |
| 756 | ); |
| 757 | while( db_step(&q)==SQLITE_ROW ){ |
| 758 | Blob content; |
| 759 | Manifest m; |
| 760 | char zShort[12]; |
| 761 | const char *zDate = db_column_text(&q, 0); |
| 762 | int rid = db_column_int(&q, 1); |
| 763 | const char *zChngUuid = db_column_text(&q, 2); |
| 764 | const char *zFile = db_column_text(&q, 4); |
| @@ -777,22 +776,22 @@ | |
| 777 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 778 | @ (rid %d(rid)) by |
| 779 | hyperlink_to_user(zUser,zDate," on"); |
| 780 | hyperlink_to_date(zDate, ".</p>"); |
| 781 | }else{ |
| 782 | content_get(rid, &content); |
| 783 | if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){ |
| 784 | @ |
| 785 | @ <p>Ticket change |
| 786 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 787 | @ (rid %d(rid)) by |
| 788 | hyperlink_to_user(m.zUser,zDate," on"); |
| 789 | hyperlink_to_date(zDate, ":"); |
| 790 | @ </p> |
| 791 | ticket_output_change_artifact(&m); |
| 792 | } |
| 793 | manifest_clear(&m); |
| 794 | } |
| 795 | } |
| 796 | db_finalize(&q); |
| 797 | style_footer(); |
| 798 | } |
| 799 |
| --- src/tkt.c | |
| +++ src/tkt.c | |
| @@ -214,25 +214,25 @@ | |
| 214 | */ |
| 215 | void ticket_rebuild_entry(const char *zTktUuid){ |
| 216 | char *zTag = mprintf("tkt-%s", zTktUuid); |
| 217 | int tagid = tag_findid(zTag, 1); |
| 218 | Stmt q; |
| 219 | Manifest *pTicket; |
| 220 | int createFlag = 1; |
| 221 | |
| 222 | db_multi_exec( |
| 223 | "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid |
| 224 | ); |
| 225 | db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid); |
| 226 | while( db_step(&q)==SQLITE_ROW ){ |
| 227 | int rid = db_column_int(&q, 0); |
| 228 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 229 | if( pTicket ){ |
| 230 | ticket_insert(pTicket, createFlag, rid); |
| 231 | manifest_ticket_event(rid, pTicket, createFlag, tagid); |
| 232 | manifest_destroy(pTicket); |
| 233 | } |
| 234 | createFlag = 0; |
| 235 | } |
| 236 | db_finalize(&q); |
| 237 | } |
| 238 | |
| @@ -753,12 +753,11 @@ | |
| 753 | " AND blob.rid=attachid" |
| 754 | " ORDER BY 1 DESC", |
| 755 | tagid, tagid |
| 756 | ); |
| 757 | while( db_step(&q)==SQLITE_ROW ){ |
| 758 | Manifest *pTicket; |
| 759 | char zShort[12]; |
| 760 | const char *zDate = db_column_text(&q, 0); |
| 761 | int rid = db_column_int(&q, 1); |
| 762 | const char *zChngUuid = db_column_text(&q, 2); |
| 763 | const char *zFile = db_column_text(&q, 4); |
| @@ -777,22 +776,22 @@ | |
| 776 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 777 | @ (rid %d(rid)) by |
| 778 | hyperlink_to_user(zUser,zDate," on"); |
| 779 | hyperlink_to_date(zDate, ".</p>"); |
| 780 | }else{ |
| 781 | pTicket = manifest_get(rid, CFTYPE_TICKET); |
| 782 | if( pTicket ){ |
| 783 | @ |
| 784 | @ <p>Ticket change |
| 785 | @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>] |
| 786 | @ (rid %d(rid)) by |
| 787 | hyperlink_to_user(pTicket->zUser,zDate," on"); |
| 788 | hyperlink_to_date(zDate, ":"); |
| 789 | @ </p> |
| 790 | ticket_output_change_artifact(pTicket); |
| 791 | } |
| 792 | manifest_destroy(pTicket); |
| 793 | } |
| 794 | } |
| 795 | db_finalize(&q); |
| 796 | style_footer(); |
| 797 | } |
| 798 |
+14
-11
| --- src/update.c | ||
| +++ src/update.c | ||
| @@ -279,10 +279,11 @@ | ||
| 279 | 279 | db_end_transaction(1); /* With --nochange, rollback changes */ |
| 280 | 280 | }else{ |
| 281 | 281 | if( g.argc<=3 ){ |
| 282 | 282 | /* All files updated. Shift the current checkout to the target. */ |
| 283 | 283 | db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid); |
| 284 | + checkout_set_all_exe(vid); | |
| 284 | 285 | manifest_to_disk(tid); |
| 285 | 286 | db_lset_int("checkout", tid); |
| 286 | 287 | }else{ |
| 287 | 288 | /* A subset of files have been checked out. Keep the current |
| 288 | 289 | ** checkout unchanged. */ |
| @@ -302,13 +303,13 @@ | ||
| 302 | 303 | const char *revision, /* The checkin containing the file */ |
| 303 | 304 | const char *file, /* Full treename of the file */ |
| 304 | 305 | Blob *content, /* Put the content here */ |
| 305 | 306 | int errCode /* Error code if file not found. Panic if 0. */ |
| 306 | 307 | ){ |
| 307 | - Blob mfile; | |
| 308 | - Manifest m; | |
| 309 | - int i, rid=0; | |
| 308 | + Manifest *pManifest; | |
| 309 | + ManifestFile *pFile; | |
| 310 | + int rid=0; | |
| 310 | 311 | |
| 311 | 312 | if( revision ){ |
| 312 | 313 | rid = name_to_rid(revision); |
| 313 | 314 | }else{ |
| 314 | 315 | rid = db_lget_int("checkout", 0); |
| @@ -315,21 +316,22 @@ | ||
| 315 | 316 | } |
| 316 | 317 | if( !is_a_version(rid) ){ |
| 317 | 318 | if( errCode>0 ) return errCode; |
| 318 | 319 | fossil_fatal("no such checkin: %s", revision); |
| 319 | 320 | } |
| 320 | - content_get(rid, &mfile); | |
| 321 | + pManifest = manifest_get(rid, CFTYPE_MANIFEST); | |
| 321 | 322 | |
| 322 | - if( manifest_parse(&m, &mfile) ){ | |
| 323 | - for(i=0; i<m.nFile; i++){ | |
| 324 | - if( strcmp(m.aFile[i].zName, file)==0 ){ | |
| 325 | - rid = uuid_to_rid(m.aFile[i].zUuid, 0); | |
| 326 | - manifest_clear(&m); | |
| 323 | + if( pManifest ){ | |
| 324 | + manifest_file_rewind(pManifest); | |
| 325 | + while( (pFile = manifest_file_next(pManifest,0))!=0 ){ | |
| 326 | + if( strcmp(pFile->zName, file)==0 ){ | |
| 327 | + rid = uuid_to_rid(pFile->zUuid, 0); | |
| 328 | + manifest_destroy(pManifest); | |
| 327 | 329 | return content_get(rid, content); |
| 328 | 330 | } |
| 329 | 331 | } |
| 330 | - manifest_clear(&m); | |
| 332 | + manifest_destroy(pManifest); | |
| 331 | 333 | if( errCode<=0 ){ |
| 332 | 334 | fossil_fatal("file %s does not exist in checkin: %s", file, revision); |
| 333 | 335 | } |
| 334 | 336 | }else if( errCode<=0 ){ |
| 335 | 337 | fossil_panic("could not parse manifest for checkin: %s", revision); |
| @@ -386,14 +388,15 @@ | ||
| 386 | 388 | }else{ |
| 387 | 389 | int vid; |
| 388 | 390 | vid = db_lget_int("checkout", 0); |
| 389 | 391 | vfile_check_signature(vid, 0); |
| 390 | 392 | db_multi_exec( |
| 393 | + "DELETE FROM vmerge;" | |
| 391 | 394 | "INSERT INTO torevert " |
| 392 | 395 | "SELECT pathname" |
| 393 | 396 | " FROM vfile " |
| 394 | - " WHERE chnged OR deleted OR rid=0 OR pathname!=origname" | |
| 397 | + " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;" | |
| 395 | 398 | ); |
| 396 | 399 | } |
| 397 | 400 | blob_zero(&record); |
| 398 | 401 | db_prepare(&q, "SELECT name FROM torevert"); |
| 399 | 402 | while( db_step(&q)==SQLITE_ROW ){ |
| 400 | 403 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -279,10 +279,11 @@ | |
| 279 | db_end_transaction(1); /* With --nochange, rollback changes */ |
| 280 | }else{ |
| 281 | if( g.argc<=3 ){ |
| 282 | /* All files updated. Shift the current checkout to the target. */ |
| 283 | db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid); |
| 284 | manifest_to_disk(tid); |
| 285 | db_lset_int("checkout", tid); |
| 286 | }else{ |
| 287 | /* A subset of files have been checked out. Keep the current |
| 288 | ** checkout unchanged. */ |
| @@ -302,13 +303,13 @@ | |
| 302 | const char *revision, /* The checkin containing the file */ |
| 303 | const char *file, /* Full treename of the file */ |
| 304 | Blob *content, /* Put the content here */ |
| 305 | int errCode /* Error code if file not found. Panic if 0. */ |
| 306 | ){ |
| 307 | Blob mfile; |
| 308 | Manifest m; |
| 309 | int i, rid=0; |
| 310 | |
| 311 | if( revision ){ |
| 312 | rid = name_to_rid(revision); |
| 313 | }else{ |
| 314 | rid = db_lget_int("checkout", 0); |
| @@ -315,21 +316,22 @@ | |
| 315 | } |
| 316 | if( !is_a_version(rid) ){ |
| 317 | if( errCode>0 ) return errCode; |
| 318 | fossil_fatal("no such checkin: %s", revision); |
| 319 | } |
| 320 | content_get(rid, &mfile); |
| 321 | |
| 322 | if( manifest_parse(&m, &mfile) ){ |
| 323 | for(i=0; i<m.nFile; i++){ |
| 324 | if( strcmp(m.aFile[i].zName, file)==0 ){ |
| 325 | rid = uuid_to_rid(m.aFile[i].zUuid, 0); |
| 326 | manifest_clear(&m); |
| 327 | return content_get(rid, content); |
| 328 | } |
| 329 | } |
| 330 | manifest_clear(&m); |
| 331 | if( errCode<=0 ){ |
| 332 | fossil_fatal("file %s does not exist in checkin: %s", file, revision); |
| 333 | } |
| 334 | }else if( errCode<=0 ){ |
| 335 | fossil_panic("could not parse manifest for checkin: %s", revision); |
| @@ -386,14 +388,15 @@ | |
| 386 | }else{ |
| 387 | int vid; |
| 388 | vid = db_lget_int("checkout", 0); |
| 389 | vfile_check_signature(vid, 0); |
| 390 | db_multi_exec( |
| 391 | "INSERT INTO torevert " |
| 392 | "SELECT pathname" |
| 393 | " FROM vfile " |
| 394 | " WHERE chnged OR deleted OR rid=0 OR pathname!=origname" |
| 395 | ); |
| 396 | } |
| 397 | blob_zero(&record); |
| 398 | db_prepare(&q, "SELECT name FROM torevert"); |
| 399 | while( db_step(&q)==SQLITE_ROW ){ |
| 400 |
| --- src/update.c | |
| +++ src/update.c | |
| @@ -279,10 +279,11 @@ | |
| 279 | db_end_transaction(1); /* With --nochange, rollback changes */ |
| 280 | }else{ |
| 281 | if( g.argc<=3 ){ |
| 282 | /* All files updated. Shift the current checkout to the target. */ |
| 283 | db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid); |
| 284 | checkout_set_all_exe(vid); |
| 285 | manifest_to_disk(tid); |
| 286 | db_lset_int("checkout", tid); |
| 287 | }else{ |
| 288 | /* A subset of files have been checked out. Keep the current |
| 289 | ** checkout unchanged. */ |
| @@ -302,13 +303,13 @@ | |
| 303 | const char *revision, /* The checkin containing the file */ |
| 304 | const char *file, /* Full treename of the file */ |
| 305 | Blob *content, /* Put the content here */ |
| 306 | int errCode /* Error code if file not found. Panic if 0. */ |
| 307 | ){ |
| 308 | Manifest *pManifest; |
| 309 | ManifestFile *pFile; |
| 310 | int rid=0; |
| 311 | |
| 312 | if( revision ){ |
| 313 | rid = name_to_rid(revision); |
| 314 | }else{ |
| 315 | rid = db_lget_int("checkout", 0); |
| @@ -315,21 +316,22 @@ | |
| 316 | } |
| 317 | if( !is_a_version(rid) ){ |
| 318 | if( errCode>0 ) return errCode; |
| 319 | fossil_fatal("no such checkin: %s", revision); |
| 320 | } |
| 321 | pManifest = manifest_get(rid, CFTYPE_MANIFEST); |
| 322 | |
| 323 | if( pManifest ){ |
| 324 | manifest_file_rewind(pManifest); |
| 325 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 326 | if( strcmp(pFile->zName, file)==0 ){ |
| 327 | rid = uuid_to_rid(pFile->zUuid, 0); |
| 328 | manifest_destroy(pManifest); |
| 329 | return content_get(rid, content); |
| 330 | } |
| 331 | } |
| 332 | manifest_destroy(pManifest); |
| 333 | if( errCode<=0 ){ |
| 334 | fossil_fatal("file %s does not exist in checkin: %s", file, revision); |
| 335 | } |
| 336 | }else if( errCode<=0 ){ |
| 337 | fossil_panic("could not parse manifest for checkin: %s", revision); |
| @@ -386,14 +388,15 @@ | |
| 388 | }else{ |
| 389 | int vid; |
| 390 | vid = db_lget_int("checkout", 0); |
| 391 | vfile_check_signature(vid, 0); |
| 392 | db_multi_exec( |
| 393 | "DELETE FROM vmerge;" |
| 394 | "INSERT INTO torevert " |
| 395 | "SELECT pathname" |
| 396 | " FROM vfile " |
| 397 | " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;" |
| 398 | ); |
| 399 | } |
| 400 | blob_zero(&record); |
| 401 | db_prepare(&q, "SELECT name FROM torevert"); |
| 402 | while( db_step(&q)==SQLITE_ROW ){ |
| 403 |
+31
-46
| --- src/vfile.c | ||
| +++ src/vfile.c | ||
| @@ -85,57 +85,38 @@ | ||
| 85 | 85 | } |
| 86 | 86 | } |
| 87 | 87 | } |
| 88 | 88 | |
| 89 | 89 | /* |
| 90 | -** Build a catalog of all files in a baseline. | |
| 91 | -** We scan the baseline file for lines of the form: | |
| 92 | -** | |
| 93 | -** F NAME UUID | |
| 94 | -** | |
| 95 | -** Each such line makes an entry in the VFILE table. | |
| 90 | +** Build a catalog of all files in a checkin. | |
| 96 | 91 | */ |
| 97 | -void vfile_build(int vid, Blob *p){ | |
| 92 | +void vfile_build(int vid){ | |
| 98 | 93 | int rid; |
| 99 | - char *zName, *zUuid; | |
| 100 | 94 | Stmt ins; |
| 101 | - Blob line, token, name, uuid; | |
| 102 | - int seenHeader = 0; | |
| 95 | + Manifest *p; | |
| 96 | + ManifestFile *pFile; | |
| 97 | + | |
| 103 | 98 | db_begin_transaction(); |
| 104 | 99 | vfile_verify_not_phantom(vid, 0, 0); |
| 100 | + p = manifest_get(vid, CFTYPE_MANIFEST); | |
| 101 | + if( p==0 ) return; | |
| 105 | 102 | db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); |
| 106 | 103 | db_prepare(&ins, |
| 107 | 104 | "INSERT INTO vfile(vid,rid,mrid,pathname) " |
| 108 | 105 | " VALUES(:vid,:id,:id,:name)"); |
| 109 | 106 | db_bind_int(&ins, ":vid", vid); |
| 110 | - while( blob_line(p, &line) ){ | |
| 111 | - char *z = blob_buffer(&line); | |
| 112 | - if( z[0]=='-' ){ | |
| 113 | - if( seenHeader ) break; | |
| 114 | - while( blob_line(p, &line)>2 ){} | |
| 115 | - if( blob_line(p, &line)==0 ) break; | |
| 116 | - } | |
| 117 | - seenHeader = 1; | |
| 118 | - if( z[0]!='F' || z[1]!=' ' ) continue; | |
| 119 | - blob_token(&line, &token); /* Skip the "F" token */ | |
| 120 | - if( blob_token(&line, &name)==0 ) break; | |
| 121 | - if( blob_token(&line, &uuid)==0 ) break; | |
| 122 | - zName = blob_str(&name); | |
| 123 | - defossilize(zName); | |
| 124 | - zUuid = blob_str(&uuid); | |
| 125 | - rid = uuid_to_rid(zUuid, 0); | |
| 126 | - vfile_verify_not_phantom(rid, zName, zUuid); | |
| 127 | - if( rid>0 && file_is_simple_pathname(zName) ){ | |
| 128 | - db_bind_int(&ins, ":id", rid); | |
| 129 | - db_bind_text(&ins, ":name", zName); | |
| 130 | - db_step(&ins); | |
| 131 | - db_reset(&ins); | |
| 132 | - } | |
| 133 | - blob_reset(&name); | |
| 134 | - blob_reset(&uuid); | |
| 107 | + manifest_file_rewind(p); | |
| 108 | + while( (pFile = manifest_file_next(p,0))!=0 ){ | |
| 109 | + rid = uuid_to_rid(pFile->zUuid, 0); | |
| 110 | + vfile_verify_not_phantom(rid, pFile->zName, pFile->zUuid); | |
| 111 | + db_bind_int(&ins, ":id", rid); | |
| 112 | + db_bind_text(&ins, ":name", pFile->zName); | |
| 113 | + db_step(&ins); | |
| 114 | + db_reset(&ins); | |
| 135 | 115 | } |
| 136 | 116 | db_finalize(&ins); |
| 117 | + manifest_destroy(p); | |
| 137 | 118 | db_end_transaction(0); |
| 138 | 119 | } |
| 139 | 120 | |
| 140 | 121 | /* |
| 141 | 122 | ** Check the file signature of the disk image for every VFILE of vid. |
| @@ -369,10 +350,11 @@ | ||
| 369 | 350 | } |
| 370 | 351 | fseek(in, 0L, SEEK_END); |
| 371 | 352 | sprintf(zBuf, " %ld\n", ftell(in)); |
| 372 | 353 | fseek(in, 0L, SEEK_SET); |
| 373 | 354 | md5sum_step_text(zBuf, -1); |
| 355 | + /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ | |
| 374 | 356 | for(;;){ |
| 375 | 357 | int n; |
| 376 | 358 | n = fread(zBuf, 1, sizeof(zBuf), in); |
| 377 | 359 | if( n<=0 ) break; |
| 378 | 360 | md5sum_step_text(zBuf, n); |
| @@ -422,10 +404,11 @@ | ||
| 422 | 404 | int rid = db_column_int(&q, 1); |
| 423 | 405 | md5sum_step_text(zName, -1); |
| 424 | 406 | content_get(rid, &file); |
| 425 | 407 | sprintf(zBuf, " %d\n", blob_size(&file)); |
| 426 | 408 | md5sum_step_text(zBuf, -1); |
| 409 | + /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ | |
| 427 | 410 | md5sum_step_blob(&file); |
| 428 | 411 | blob_reset(&file); |
| 429 | 412 | } |
| 430 | 413 | db_finalize(&q); |
| 431 | 414 | md5sum_finish(pOut); |
| @@ -438,41 +421,43 @@ | ||
| 438 | 421 | ** |
| 439 | 422 | ** If pManOut is not NULL then fill it with the checksum found in the |
| 440 | 423 | ** "R" card near the end of the manifest. |
| 441 | 424 | */ |
| 442 | 425 | void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){ |
| 443 | - int i, fid; | |
| 444 | - Blob file, mfile; | |
| 445 | - Manifest m; | |
| 426 | + int fid; | |
| 427 | + Blob file; | |
| 428 | + Manifest *pManifest; | |
| 429 | + ManifestFile *pFile; | |
| 446 | 430 | char zBuf[100]; |
| 447 | 431 | |
| 448 | 432 | blob_zero(pOut); |
| 449 | 433 | if( pManOut ){ |
| 450 | 434 | blob_zero(pManOut); |
| 451 | 435 | } |
| 452 | 436 | db_must_be_within_tree(); |
| 453 | - content_get(vid, &mfile); | |
| 454 | - if( manifest_parse(&m, &mfile)==0 ){ | |
| 437 | + pManifest = manifest_get(vid, CFTYPE_MANIFEST); | |
| 438 | + if( pManifest==0 ){ | |
| 455 | 439 | fossil_panic("manifest file (%d) is malformed", vid); |
| 456 | 440 | } |
| 457 | - for(i=0; i<m.nFile; i++){ | |
| 458 | - fid = uuid_to_rid(m.aFile[i].zUuid, 0); | |
| 459 | - md5sum_step_text(m.aFile[i].zName, -1); | |
| 441 | + manifest_file_rewind(pManifest); | |
| 442 | + while( (pFile = manifest_file_next(pManifest,0))!=0 ){ | |
| 443 | + fid = uuid_to_rid(pFile->zUuid, 0); | |
| 444 | + md5sum_step_text(pFile->zName, -1); | |
| 460 | 445 | content_get(fid, &file); |
| 461 | 446 | sprintf(zBuf, " %d\n", blob_size(&file)); |
| 462 | 447 | md5sum_step_text(zBuf, -1); |
| 463 | 448 | md5sum_step_blob(&file); |
| 464 | 449 | blob_reset(&file); |
| 465 | 450 | } |
| 466 | 451 | if( pManOut ){ |
| 467 | - if( m.zRepoCksum ){ | |
| 468 | - blob_append(pManOut, m.zRepoCksum, -1); | |
| 452 | + if( pManifest->zRepoCksum ){ | |
| 453 | + blob_append(pManOut, pManifest->zRepoCksum, -1); | |
| 469 | 454 | }else{ |
| 470 | 455 | blob_zero(pManOut); |
| 471 | 456 | } |
| 472 | 457 | } |
| 473 | - manifest_clear(&m); | |
| 458 | + manifest_destroy(pManifest); | |
| 474 | 459 | md5sum_finish(pOut); |
| 475 | 460 | } |
| 476 | 461 | |
| 477 | 462 | /* |
| 478 | 463 | ** COMMAND: test-agg-cksum |
| 479 | 464 |
| --- src/vfile.c | |
| +++ src/vfile.c | |
| @@ -85,57 +85,38 @@ | |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | /* |
| 90 | ** Build a catalog of all files in a baseline. |
| 91 | ** We scan the baseline file for lines of the form: |
| 92 | ** |
| 93 | ** F NAME UUID |
| 94 | ** |
| 95 | ** Each such line makes an entry in the VFILE table. |
| 96 | */ |
| 97 | void vfile_build(int vid, Blob *p){ |
| 98 | int rid; |
| 99 | char *zName, *zUuid; |
| 100 | Stmt ins; |
| 101 | Blob line, token, name, uuid; |
| 102 | int seenHeader = 0; |
| 103 | db_begin_transaction(); |
| 104 | vfile_verify_not_phantom(vid, 0, 0); |
| 105 | db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); |
| 106 | db_prepare(&ins, |
| 107 | "INSERT INTO vfile(vid,rid,mrid,pathname) " |
| 108 | " VALUES(:vid,:id,:id,:name)"); |
| 109 | db_bind_int(&ins, ":vid", vid); |
| 110 | while( blob_line(p, &line) ){ |
| 111 | char *z = blob_buffer(&line); |
| 112 | if( z[0]=='-' ){ |
| 113 | if( seenHeader ) break; |
| 114 | while( blob_line(p, &line)>2 ){} |
| 115 | if( blob_line(p, &line)==0 ) break; |
| 116 | } |
| 117 | seenHeader = 1; |
| 118 | if( z[0]!='F' || z[1]!=' ' ) continue; |
| 119 | blob_token(&line, &token); /* Skip the "F" token */ |
| 120 | if( blob_token(&line, &name)==0 ) break; |
| 121 | if( blob_token(&line, &uuid)==0 ) break; |
| 122 | zName = blob_str(&name); |
| 123 | defossilize(zName); |
| 124 | zUuid = blob_str(&uuid); |
| 125 | rid = uuid_to_rid(zUuid, 0); |
| 126 | vfile_verify_not_phantom(rid, zName, zUuid); |
| 127 | if( rid>0 && file_is_simple_pathname(zName) ){ |
| 128 | db_bind_int(&ins, ":id", rid); |
| 129 | db_bind_text(&ins, ":name", zName); |
| 130 | db_step(&ins); |
| 131 | db_reset(&ins); |
| 132 | } |
| 133 | blob_reset(&name); |
| 134 | blob_reset(&uuid); |
| 135 | } |
| 136 | db_finalize(&ins); |
| 137 | db_end_transaction(0); |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | ** Check the file signature of the disk image for every VFILE of vid. |
| @@ -369,10 +350,11 @@ | |
| 369 | } |
| 370 | fseek(in, 0L, SEEK_END); |
| 371 | sprintf(zBuf, " %ld\n", ftell(in)); |
| 372 | fseek(in, 0L, SEEK_SET); |
| 373 | md5sum_step_text(zBuf, -1); |
| 374 | for(;;){ |
| 375 | int n; |
| 376 | n = fread(zBuf, 1, sizeof(zBuf), in); |
| 377 | if( n<=0 ) break; |
| 378 | md5sum_step_text(zBuf, n); |
| @@ -422,10 +404,11 @@ | |
| 422 | int rid = db_column_int(&q, 1); |
| 423 | md5sum_step_text(zName, -1); |
| 424 | content_get(rid, &file); |
| 425 | sprintf(zBuf, " %d\n", blob_size(&file)); |
| 426 | md5sum_step_text(zBuf, -1); |
| 427 | md5sum_step_blob(&file); |
| 428 | blob_reset(&file); |
| 429 | } |
| 430 | db_finalize(&q); |
| 431 | md5sum_finish(pOut); |
| @@ -438,41 +421,43 @@ | |
| 438 | ** |
| 439 | ** If pManOut is not NULL then fill it with the checksum found in the |
| 440 | ** "R" card near the end of the manifest. |
| 441 | */ |
| 442 | void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){ |
| 443 | int i, fid; |
| 444 | Blob file, mfile; |
| 445 | Manifest m; |
| 446 | char zBuf[100]; |
| 447 | |
| 448 | blob_zero(pOut); |
| 449 | if( pManOut ){ |
| 450 | blob_zero(pManOut); |
| 451 | } |
| 452 | db_must_be_within_tree(); |
| 453 | content_get(vid, &mfile); |
| 454 | if( manifest_parse(&m, &mfile)==0 ){ |
| 455 | fossil_panic("manifest file (%d) is malformed", vid); |
| 456 | } |
| 457 | for(i=0; i<m.nFile; i++){ |
| 458 | fid = uuid_to_rid(m.aFile[i].zUuid, 0); |
| 459 | md5sum_step_text(m.aFile[i].zName, -1); |
| 460 | content_get(fid, &file); |
| 461 | sprintf(zBuf, " %d\n", blob_size(&file)); |
| 462 | md5sum_step_text(zBuf, -1); |
| 463 | md5sum_step_blob(&file); |
| 464 | blob_reset(&file); |
| 465 | } |
| 466 | if( pManOut ){ |
| 467 | if( m.zRepoCksum ){ |
| 468 | blob_append(pManOut, m.zRepoCksum, -1); |
| 469 | }else{ |
| 470 | blob_zero(pManOut); |
| 471 | } |
| 472 | } |
| 473 | manifest_clear(&m); |
| 474 | md5sum_finish(pOut); |
| 475 | } |
| 476 | |
| 477 | /* |
| 478 | ** COMMAND: test-agg-cksum |
| 479 |
| --- src/vfile.c | |
| +++ src/vfile.c | |
| @@ -85,57 +85,38 @@ | |
| 85 | } |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | /* |
| 90 | ** Build a catalog of all files in a checkin. |
| 91 | */ |
| 92 | void vfile_build(int vid){ |
| 93 | int rid; |
| 94 | Stmt ins; |
| 95 | Manifest *p; |
| 96 | ManifestFile *pFile; |
| 97 | |
| 98 | db_begin_transaction(); |
| 99 | vfile_verify_not_phantom(vid, 0, 0); |
| 100 | p = manifest_get(vid, CFTYPE_MANIFEST); |
| 101 | if( p==0 ) return; |
| 102 | db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); |
| 103 | db_prepare(&ins, |
| 104 | "INSERT INTO vfile(vid,rid,mrid,pathname) " |
| 105 | " VALUES(:vid,:id,:id,:name)"); |
| 106 | db_bind_int(&ins, ":vid", vid); |
| 107 | manifest_file_rewind(p); |
| 108 | while( (pFile = manifest_file_next(p,0))!=0 ){ |
| 109 | rid = uuid_to_rid(pFile->zUuid, 0); |
| 110 | vfile_verify_not_phantom(rid, pFile->zName, pFile->zUuid); |
| 111 | db_bind_int(&ins, ":id", rid); |
| 112 | db_bind_text(&ins, ":name", pFile->zName); |
| 113 | db_step(&ins); |
| 114 | db_reset(&ins); |
| 115 | } |
| 116 | db_finalize(&ins); |
| 117 | manifest_destroy(p); |
| 118 | db_end_transaction(0); |
| 119 | } |
| 120 | |
| 121 | /* |
| 122 | ** Check the file signature of the disk image for every VFILE of vid. |
| @@ -369,10 +350,11 @@ | |
| 350 | } |
| 351 | fseek(in, 0L, SEEK_END); |
| 352 | sprintf(zBuf, " %ld\n", ftell(in)); |
| 353 | fseek(in, 0L, SEEK_SET); |
| 354 | md5sum_step_text(zBuf, -1); |
| 355 | /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ |
| 356 | for(;;){ |
| 357 | int n; |
| 358 | n = fread(zBuf, 1, sizeof(zBuf), in); |
| 359 | if( n<=0 ) break; |
| 360 | md5sum_step_text(zBuf, n); |
| @@ -422,10 +404,11 @@ | |
| 404 | int rid = db_column_int(&q, 1); |
| 405 | md5sum_step_text(zName, -1); |
| 406 | content_get(rid, &file); |
| 407 | sprintf(zBuf, " %d\n", blob_size(&file)); |
| 408 | md5sum_step_text(zBuf, -1); |
| 409 | /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/ |
| 410 | md5sum_step_blob(&file); |
| 411 | blob_reset(&file); |
| 412 | } |
| 413 | db_finalize(&q); |
| 414 | md5sum_finish(pOut); |
| @@ -438,41 +421,43 @@ | |
| 421 | ** |
| 422 | ** If pManOut is not NULL then fill it with the checksum found in the |
| 423 | ** "R" card near the end of the manifest. |
| 424 | */ |
| 425 | void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){ |
| 426 | int fid; |
| 427 | Blob file; |
| 428 | Manifest *pManifest; |
| 429 | ManifestFile *pFile; |
| 430 | char zBuf[100]; |
| 431 | |
| 432 | blob_zero(pOut); |
| 433 | if( pManOut ){ |
| 434 | blob_zero(pManOut); |
| 435 | } |
| 436 | db_must_be_within_tree(); |
| 437 | pManifest = manifest_get(vid, CFTYPE_MANIFEST); |
| 438 | if( pManifest==0 ){ |
| 439 | fossil_panic("manifest file (%d) is malformed", vid); |
| 440 | } |
| 441 | manifest_file_rewind(pManifest); |
| 442 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 443 | fid = uuid_to_rid(pFile->zUuid, 0); |
| 444 | md5sum_step_text(pFile->zName, -1); |
| 445 | content_get(fid, &file); |
| 446 | sprintf(zBuf, " %d\n", blob_size(&file)); |
| 447 | md5sum_step_text(zBuf, -1); |
| 448 | md5sum_step_blob(&file); |
| 449 | blob_reset(&file); |
| 450 | } |
| 451 | if( pManOut ){ |
| 452 | if( pManifest->zRepoCksum ){ |
| 453 | blob_append(pManOut, pManifest->zRepoCksum, -1); |
| 454 | }else{ |
| 455 | blob_zero(pManOut); |
| 456 | } |
| 457 | } |
| 458 | manifest_destroy(pManifest); |
| 459 | md5sum_finish(pOut); |
| 460 | } |
| 461 | |
| 462 | /* |
| 463 | ** COMMAND: test-agg-cksum |
| 464 |
+32
-58
| --- src/wiki.c | ||
| +++ src/wiki.c | ||
| @@ -127,11 +127,11 @@ | ||
| 127 | 127 | void wiki_page(void){ |
| 128 | 128 | char *zTag; |
| 129 | 129 | int rid = 0; |
| 130 | 130 | int isSandbox; |
| 131 | 131 | Blob wiki; |
| 132 | - Manifest m; | |
| 132 | + Manifest *pWiki = 0; | |
| 133 | 133 | const char *zPageName; |
| 134 | 134 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 135 | 135 | Stmt q; |
| 136 | 136 | int cnt = 0; |
| 137 | 137 | |
| @@ -179,20 +179,14 @@ | ||
| 179 | 179 | "SELECT rid FROM tagxref" |
| 180 | 180 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 181 | 181 | " ORDER BY mtime DESC", zTag |
| 182 | 182 | ); |
| 183 | 183 | free(zTag); |
| 184 | - memset(&m, 0, sizeof(m)); | |
| 185 | - blob_zero(&m.content); | |
| 186 | - if( rid ){ | |
| 187 | - Blob content; | |
| 188 | - content_get(rid, &content); | |
| 189 | - manifest_parse(&m, &content); | |
| 190 | - if( m.type==CFTYPE_WIKI && m.zWiki ){ | |
| 191 | - while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; | |
| 192 | - if( m.zWiki[0] ) zBody = m.zWiki; | |
| 193 | - } | |
| 184 | + pWiki = manifest_get(rid, CFTYPE_WIKI); | |
| 185 | + if( pWiki ){ | |
| 186 | + while( fossil_isspace(pWiki->zWiki[0]) ) pWiki->zWiki++; | |
| 187 | + if( pWiki->zWiki[0] ) zBody = pWiki->zWiki; | |
| 194 | 188 | } |
| 195 | 189 | } |
| 196 | 190 | if( !g.isHome ){ |
| 197 | 191 | if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){ |
| 198 | 192 | style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", |
| @@ -249,13 +243,11 @@ | ||
| 249 | 243 | if( cnt ){ |
| 250 | 244 | @ </ul> |
| 251 | 245 | } |
| 252 | 246 | db_finalize(&q); |
| 253 | 247 | |
| 254 | - if( !isSandbox ){ | |
| 255 | - manifest_clear(&m); | |
| 256 | - } | |
| 248 | + manifest_destroy(pWiki); | |
| 257 | 249 | style_footer(); |
| 258 | 250 | } |
| 259 | 251 | |
| 260 | 252 | /* |
| 261 | 253 | ** WEBPAGE: wikiedit |
| @@ -264,11 +256,11 @@ | ||
| 264 | 256 | void wikiedit_page(void){ |
| 265 | 257 | char *zTag; |
| 266 | 258 | int rid = 0; |
| 267 | 259 | int isSandbox; |
| 268 | 260 | Blob wiki; |
| 269 | - Manifest m; | |
| 261 | + Manifest *pWiki = 0; | |
| 270 | 262 | const char *zPageName; |
| 271 | 263 | char *zHtmlPageName; |
| 272 | 264 | int n; |
| 273 | 265 | const char *z; |
| 274 | 266 | char *zBody = (char*)P("w"); |
| @@ -298,19 +290,12 @@ | ||
| 298 | 290 | free(zTag); |
| 299 | 291 | if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){ |
| 300 | 292 | login_needed(); |
| 301 | 293 | return; |
| 302 | 294 | } |
| 303 | - memset(&m, 0, sizeof(m)); | |
| 304 | - blob_zero(&m.content); | |
| 305 | - if( rid && zBody==0 ){ | |
| 306 | - Blob content; | |
| 307 | - content_get(rid, &content); | |
| 308 | - manifest_parse(&m, &content); | |
| 309 | - if( m.type==CFTYPE_WIKI ){ | |
| 310 | - zBody = m.zWiki; | |
| 311 | - } | |
| 295 | + if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ | |
| 296 | + zBody = pWiki->zWiki; | |
| 312 | 297 | } |
| 313 | 298 | } |
| 314 | 299 | if( P("submit")!=0 && zBody!=0 ){ |
| 315 | 300 | char *zDate; |
| 316 | 301 | Blob cksum; |
| @@ -377,13 +362,11 @@ | ||
| 377 | 362 | @ <br /> |
| 378 | 363 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 379 | 364 | @ <input type="submit" name="submit" value="Apply These Changes" /> |
| 380 | 365 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 381 | 366 | @ </div></form> |
| 382 | - if( !isSandbox ){ | |
| 383 | - manifest_clear(&m); | |
| 384 | - } | |
| 367 | + manifest_destroy(pWiki); | |
| 385 | 368 | style_footer(); |
| 386 | 369 | } |
| 387 | 370 | |
| 388 | 371 | /* |
| 389 | 372 | ** WEBPAGE: wikinew |
| @@ -477,27 +460,25 @@ | ||
| 477 | 460 | if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){ |
| 478 | 461 | char *zDate; |
| 479 | 462 | Blob cksum; |
| 480 | 463 | int nrid; |
| 481 | 464 | Blob body; |
| 482 | - Blob content; | |
| 483 | 465 | Blob wiki; |
| 484 | - Manifest m; | |
| 466 | + Manifest *pWiki = 0; | |
| 485 | 467 | |
| 486 | 468 | blob_zero(&body); |
| 487 | 469 | if( isSandbox ){ |
| 488 | 470 | blob_appendf(&body, db_get("sandbox","")); |
| 489 | 471 | appendRemark(&body); |
| 490 | 472 | db_set("sandbox", blob_str(&body), 0); |
| 491 | 473 | }else{ |
| 492 | 474 | login_verify_csrf_secret(); |
| 493 | - content_get(rid, &content); | |
| 494 | - manifest_parse(&m, &content); | |
| 495 | - if( m.type==CFTYPE_WIKI ){ | |
| 496 | - blob_append(&body, m.zWiki, -1); | |
| 475 | + pWiki = manifest_get(rid, CFTYPE_WIKI); | |
| 476 | + if( pWiki ){ | |
| 477 | + blob_append(&body, pWiki->zWiki, -1); | |
| 478 | + manifest_destroy(pWiki); | |
| 497 | 479 | } |
| 498 | - manifest_clear(&m); | |
| 499 | 480 | blob_zero(&wiki); |
| 500 | 481 | db_begin_transaction(); |
| 501 | 482 | zDate = db_text(0, "SELECT datetime('now')"); |
| 502 | 483 | zDate[10] = 'T'; |
| 503 | 484 | blob_appendf(&wiki, "D %s\n", zDate); |
| @@ -612,12 +593,11 @@ | ||
| 612 | 593 | */ |
| 613 | 594 | void wdiff_page(void){ |
| 614 | 595 | char *zTitle; |
| 615 | 596 | int rid1, rid2; |
| 616 | 597 | const char *zPageName; |
| 617 | - Blob content1, content2; | |
| 618 | - Manifest m1, m2; | |
| 598 | + Manifest *pW1, *pW2 = 0; | |
| 619 | 599 | Blob w1, w2, d; |
| 620 | 600 | |
| 621 | 601 | login_check_credentials(); |
| 622 | 602 | rid1 = atoi(PD("a","0")); |
| 623 | 603 | if( !g.okHistory ){ login_needed(); return; } |
| @@ -635,27 +615,24 @@ | ||
| 635 | 615 | " WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)" |
| 636 | 616 | " ORDER BY event.mtime DESC LIMIT 1", |
| 637 | 617 | zPageName, rid1 |
| 638 | 618 | ); |
| 639 | 619 | } |
| 640 | - content_get(rid1, &content1); | |
| 641 | - manifest_parse(&m1, &content1); | |
| 642 | - if( m1.type!=CFTYPE_WIKI ) fossil_redirect_home(); | |
| 643 | - blob_init(&w1, m1.zWiki, -1); | |
| 620 | + pW1 = manifest_get(rid1, CFTYPE_WIKI); | |
| 621 | + if( pW1==0 ) fossil_redirect_home(); | |
| 622 | + blob_init(&w1, pW1->zWiki, -1); | |
| 644 | 623 | blob_zero(&w2); |
| 645 | - if( rid2 ){ | |
| 646 | - content_get(rid2, &content2); | |
| 647 | - manifest_parse(&m2, &content2); | |
| 648 | - if( m2.type==CFTYPE_WIKI ){ | |
| 649 | - blob_init(&w2, m2.zWiki, -1); | |
| 650 | - } | |
| 624 | + if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){ | |
| 625 | + blob_init(&w2, pW2->zWiki, -1); | |
| 651 | 626 | } |
| 652 | 627 | blob_zero(&d); |
| 653 | 628 | text_diff(&w2, &w1, &d, 5, 1); |
| 654 | 629 | @ <pre> |
| 655 | 630 | @ %h(blob_str(&d)) |
| 656 | 631 | @ </pre> |
| 632 | + manifest_destroy(pW1); | |
| 633 | + manifest_destroy(pW2); | |
| 657 | 634 | style_footer(); |
| 658 | 635 | } |
| 659 | 636 | |
| 660 | 637 | /* |
| 661 | 638 | ** WEBPAGE: wcontent |
| @@ -914,30 +891,26 @@ | ||
| 914 | 891 | } |
| 915 | 892 | |
| 916 | 893 | if( strncmp(g.argv[2],"export",n)==0 ){ |
| 917 | 894 | char const *zPageName; /* Name of the wiki page to export */ |
| 918 | 895 | char const *zFile; /* Name of the output file (0=stdout) */ |
| 919 | - int rid; /* Artifact ID of the wiki page */ | |
| 920 | - int i; /* Loop counter */ | |
| 921 | - char *zBody = 0; /* Wiki page content */ | |
| 922 | - Manifest m; /* Parsed wiki page content */ | |
| 896 | + int rid; /* Artifact ID of the wiki page */ | |
| 897 | + int i; /* Loop counter */ | |
| 898 | + char *zBody = 0; /* Wiki page content */ | |
| 899 | + Manifest *pWiki = 0; /* Parsed wiki page content */ | |
| 900 | + | |
| 923 | 901 | if( (g.argc!=4) && (g.argc!=5) ){ |
| 924 | 902 | usage("export PAGENAME ?FILE?"); |
| 925 | 903 | } |
| 926 | 904 | zPageName = g.argv[3]; |
| 927 | 905 | rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" |
| 928 | 906 | " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" |
| 929 | 907 | " ORDER BY x.mtime DESC LIMIT 1", |
| 930 | 908 | zPageName |
| 931 | 909 | ); |
| 932 | - if( rid ){ | |
| 933 | - Blob content; | |
| 934 | - content_get(rid, &content); | |
| 935 | - manifest_parse(&m, &content); | |
| 936 | - if( m.type==CFTYPE_WIKI ){ | |
| 937 | - zBody = m.zWiki; | |
| 938 | - } | |
| 910 | + if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ | |
| 911 | + zBody = pWiki->zWiki; | |
| 939 | 912 | } |
| 940 | 913 | if( zBody==0 ){ |
| 941 | 914 | fossil_fatal("wiki page [%s] not found",zPageName); |
| 942 | 915 | } |
| 943 | 916 | for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){} |
| @@ -955,12 +928,13 @@ | ||
| 955 | 928 | fossil_fatal("wiki export could not open output file for writing."); |
| 956 | 929 | } |
| 957 | 930 | fprintf(zF,"%.*s\n", i, zBody); |
| 958 | 931 | if( doClose ) fclose(zF); |
| 959 | 932 | }else{ |
| 960 | - printf("%.*s\n", i, zBody); | |
| 933 | + printf("%.*s\n", i, zBody); | |
| 961 | 934 | } |
| 935 | + manifest_destroy(pWiki); | |
| 962 | 936 | return; |
| 963 | 937 | }else |
| 964 | 938 | if( strncmp(g.argv[2],"commit",n)==0 |
| 965 | 939 | || strncmp(g.argv[2],"create",n)==0 ){ |
| 966 | 940 | char *zPageName; |
| 967 | 941 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -127,11 +127,11 @@ | |
| 127 | void wiki_page(void){ |
| 128 | char *zTag; |
| 129 | int rid = 0; |
| 130 | int isSandbox; |
| 131 | Blob wiki; |
| 132 | Manifest m; |
| 133 | const char *zPageName; |
| 134 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 135 | Stmt q; |
| 136 | int cnt = 0; |
| 137 | |
| @@ -179,20 +179,14 @@ | |
| 179 | "SELECT rid FROM tagxref" |
| 180 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 181 | " ORDER BY mtime DESC", zTag |
| 182 | ); |
| 183 | free(zTag); |
| 184 | memset(&m, 0, sizeof(m)); |
| 185 | blob_zero(&m.content); |
| 186 | if( rid ){ |
| 187 | Blob content; |
| 188 | content_get(rid, &content); |
| 189 | manifest_parse(&m, &content); |
| 190 | if( m.type==CFTYPE_WIKI && m.zWiki ){ |
| 191 | while( fossil_isspace(m.zWiki[0]) ) m.zWiki++; |
| 192 | if( m.zWiki[0] ) zBody = m.zWiki; |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | if( !g.isHome ){ |
| 197 | if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){ |
| 198 | style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", |
| @@ -249,13 +243,11 @@ | |
| 249 | if( cnt ){ |
| 250 | @ </ul> |
| 251 | } |
| 252 | db_finalize(&q); |
| 253 | |
| 254 | if( !isSandbox ){ |
| 255 | manifest_clear(&m); |
| 256 | } |
| 257 | style_footer(); |
| 258 | } |
| 259 | |
| 260 | /* |
| 261 | ** WEBPAGE: wikiedit |
| @@ -264,11 +256,11 @@ | |
| 264 | void wikiedit_page(void){ |
| 265 | char *zTag; |
| 266 | int rid = 0; |
| 267 | int isSandbox; |
| 268 | Blob wiki; |
| 269 | Manifest m; |
| 270 | const char *zPageName; |
| 271 | char *zHtmlPageName; |
| 272 | int n; |
| 273 | const char *z; |
| 274 | char *zBody = (char*)P("w"); |
| @@ -298,19 +290,12 @@ | |
| 298 | free(zTag); |
| 299 | if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){ |
| 300 | login_needed(); |
| 301 | return; |
| 302 | } |
| 303 | memset(&m, 0, sizeof(m)); |
| 304 | blob_zero(&m.content); |
| 305 | if( rid && zBody==0 ){ |
| 306 | Blob content; |
| 307 | content_get(rid, &content); |
| 308 | manifest_parse(&m, &content); |
| 309 | if( m.type==CFTYPE_WIKI ){ |
| 310 | zBody = m.zWiki; |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | if( P("submit")!=0 && zBody!=0 ){ |
| 315 | char *zDate; |
| 316 | Blob cksum; |
| @@ -377,13 +362,11 @@ | |
| 377 | @ <br /> |
| 378 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 379 | @ <input type="submit" name="submit" value="Apply These Changes" /> |
| 380 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 381 | @ </div></form> |
| 382 | if( !isSandbox ){ |
| 383 | manifest_clear(&m); |
| 384 | } |
| 385 | style_footer(); |
| 386 | } |
| 387 | |
| 388 | /* |
| 389 | ** WEBPAGE: wikinew |
| @@ -477,27 +460,25 @@ | |
| 477 | if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){ |
| 478 | char *zDate; |
| 479 | Blob cksum; |
| 480 | int nrid; |
| 481 | Blob body; |
| 482 | Blob content; |
| 483 | Blob wiki; |
| 484 | Manifest m; |
| 485 | |
| 486 | blob_zero(&body); |
| 487 | if( isSandbox ){ |
| 488 | blob_appendf(&body, db_get("sandbox","")); |
| 489 | appendRemark(&body); |
| 490 | db_set("sandbox", blob_str(&body), 0); |
| 491 | }else{ |
| 492 | login_verify_csrf_secret(); |
| 493 | content_get(rid, &content); |
| 494 | manifest_parse(&m, &content); |
| 495 | if( m.type==CFTYPE_WIKI ){ |
| 496 | blob_append(&body, m.zWiki, -1); |
| 497 | } |
| 498 | manifest_clear(&m); |
| 499 | blob_zero(&wiki); |
| 500 | db_begin_transaction(); |
| 501 | zDate = db_text(0, "SELECT datetime('now')"); |
| 502 | zDate[10] = 'T'; |
| 503 | blob_appendf(&wiki, "D %s\n", zDate); |
| @@ -612,12 +593,11 @@ | |
| 612 | */ |
| 613 | void wdiff_page(void){ |
| 614 | char *zTitle; |
| 615 | int rid1, rid2; |
| 616 | const char *zPageName; |
| 617 | Blob content1, content2; |
| 618 | Manifest m1, m2; |
| 619 | Blob w1, w2, d; |
| 620 | |
| 621 | login_check_credentials(); |
| 622 | rid1 = atoi(PD("a","0")); |
| 623 | if( !g.okHistory ){ login_needed(); return; } |
| @@ -635,27 +615,24 @@ | |
| 635 | " WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)" |
| 636 | " ORDER BY event.mtime DESC LIMIT 1", |
| 637 | zPageName, rid1 |
| 638 | ); |
| 639 | } |
| 640 | content_get(rid1, &content1); |
| 641 | manifest_parse(&m1, &content1); |
| 642 | if( m1.type!=CFTYPE_WIKI ) fossil_redirect_home(); |
| 643 | blob_init(&w1, m1.zWiki, -1); |
| 644 | blob_zero(&w2); |
| 645 | if( rid2 ){ |
| 646 | content_get(rid2, &content2); |
| 647 | manifest_parse(&m2, &content2); |
| 648 | if( m2.type==CFTYPE_WIKI ){ |
| 649 | blob_init(&w2, m2.zWiki, -1); |
| 650 | } |
| 651 | } |
| 652 | blob_zero(&d); |
| 653 | text_diff(&w2, &w1, &d, 5, 1); |
| 654 | @ <pre> |
| 655 | @ %h(blob_str(&d)) |
| 656 | @ </pre> |
| 657 | style_footer(); |
| 658 | } |
| 659 | |
| 660 | /* |
| 661 | ** WEBPAGE: wcontent |
| @@ -914,30 +891,26 @@ | |
| 914 | } |
| 915 | |
| 916 | if( strncmp(g.argv[2],"export",n)==0 ){ |
| 917 | char const *zPageName; /* Name of the wiki page to export */ |
| 918 | char const *zFile; /* Name of the output file (0=stdout) */ |
| 919 | int rid; /* Artifact ID of the wiki page */ |
| 920 | int i; /* Loop counter */ |
| 921 | char *zBody = 0; /* Wiki page content */ |
| 922 | Manifest m; /* Parsed wiki page content */ |
| 923 | if( (g.argc!=4) && (g.argc!=5) ){ |
| 924 | usage("export PAGENAME ?FILE?"); |
| 925 | } |
| 926 | zPageName = g.argv[3]; |
| 927 | rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" |
| 928 | " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" |
| 929 | " ORDER BY x.mtime DESC LIMIT 1", |
| 930 | zPageName |
| 931 | ); |
| 932 | if( rid ){ |
| 933 | Blob content; |
| 934 | content_get(rid, &content); |
| 935 | manifest_parse(&m, &content); |
| 936 | if( m.type==CFTYPE_WIKI ){ |
| 937 | zBody = m.zWiki; |
| 938 | } |
| 939 | } |
| 940 | if( zBody==0 ){ |
| 941 | fossil_fatal("wiki page [%s] not found",zPageName); |
| 942 | } |
| 943 | for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){} |
| @@ -955,12 +928,13 @@ | |
| 955 | fossil_fatal("wiki export could not open output file for writing."); |
| 956 | } |
| 957 | fprintf(zF,"%.*s\n", i, zBody); |
| 958 | if( doClose ) fclose(zF); |
| 959 | }else{ |
| 960 | printf("%.*s\n", i, zBody); |
| 961 | } |
| 962 | return; |
| 963 | }else |
| 964 | if( strncmp(g.argv[2],"commit",n)==0 |
| 965 | || strncmp(g.argv[2],"create",n)==0 ){ |
| 966 | char *zPageName; |
| 967 |
| --- src/wiki.c | |
| +++ src/wiki.c | |
| @@ -127,11 +127,11 @@ | |
| 127 | void wiki_page(void){ |
| 128 | char *zTag; |
| 129 | int rid = 0; |
| 130 | int isSandbox; |
| 131 | Blob wiki; |
| 132 | Manifest *pWiki = 0; |
| 133 | const char *zPageName; |
| 134 | char *zBody = mprintf("%s","<i>Empty Page</i>"); |
| 135 | Stmt q; |
| 136 | int cnt = 0; |
| 137 | |
| @@ -179,20 +179,14 @@ | |
| 179 | "SELECT rid FROM tagxref" |
| 180 | " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)" |
| 181 | " ORDER BY mtime DESC", zTag |
| 182 | ); |
| 183 | free(zTag); |
| 184 | pWiki = manifest_get(rid, CFTYPE_WIKI); |
| 185 | if( pWiki ){ |
| 186 | while( fossil_isspace(pWiki->zWiki[0]) ) pWiki->zWiki++; |
| 187 | if( pWiki->zWiki[0] ) zBody = pWiki->zWiki; |
| 188 | } |
| 189 | } |
| 190 | if( !g.isHome ){ |
| 191 | if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){ |
| 192 | style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T", |
| @@ -249,13 +243,11 @@ | |
| 243 | if( cnt ){ |
| 244 | @ </ul> |
| 245 | } |
| 246 | db_finalize(&q); |
| 247 | |
| 248 | manifest_destroy(pWiki); |
| 249 | style_footer(); |
| 250 | } |
| 251 | |
| 252 | /* |
| 253 | ** WEBPAGE: wikiedit |
| @@ -264,11 +256,11 @@ | |
| 256 | void wikiedit_page(void){ |
| 257 | char *zTag; |
| 258 | int rid = 0; |
| 259 | int isSandbox; |
| 260 | Blob wiki; |
| 261 | Manifest *pWiki = 0; |
| 262 | const char *zPageName; |
| 263 | char *zHtmlPageName; |
| 264 | int n; |
| 265 | const char *z; |
| 266 | char *zBody = (char*)P("w"); |
| @@ -298,19 +290,12 @@ | |
| 290 | free(zTag); |
| 291 | if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){ |
| 292 | login_needed(); |
| 293 | return; |
| 294 | } |
| 295 | if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ |
| 296 | zBody = pWiki->zWiki; |
| 297 | } |
| 298 | } |
| 299 | if( P("submit")!=0 && zBody!=0 ){ |
| 300 | char *zDate; |
| 301 | Blob cksum; |
| @@ -377,13 +362,11 @@ | |
| 362 | @ <br /> |
| 363 | @ <input type="submit" name="preview" value="Preview Your Changes" /> |
| 364 | @ <input type="submit" name="submit" value="Apply These Changes" /> |
| 365 | @ <input type="submit" name="cancel" value="Cancel" /> |
| 366 | @ </div></form> |
| 367 | manifest_destroy(pWiki); |
| 368 | style_footer(); |
| 369 | } |
| 370 | |
| 371 | /* |
| 372 | ** WEBPAGE: wikinew |
| @@ -477,27 +460,25 @@ | |
| 460 | if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){ |
| 461 | char *zDate; |
| 462 | Blob cksum; |
| 463 | int nrid; |
| 464 | Blob body; |
| 465 | Blob wiki; |
| 466 | Manifest *pWiki = 0; |
| 467 | |
| 468 | blob_zero(&body); |
| 469 | if( isSandbox ){ |
| 470 | blob_appendf(&body, db_get("sandbox","")); |
| 471 | appendRemark(&body); |
| 472 | db_set("sandbox", blob_str(&body), 0); |
| 473 | }else{ |
| 474 | login_verify_csrf_secret(); |
| 475 | pWiki = manifest_get(rid, CFTYPE_WIKI); |
| 476 | if( pWiki ){ |
| 477 | blob_append(&body, pWiki->zWiki, -1); |
| 478 | manifest_destroy(pWiki); |
| 479 | } |
| 480 | blob_zero(&wiki); |
| 481 | db_begin_transaction(); |
| 482 | zDate = db_text(0, "SELECT datetime('now')"); |
| 483 | zDate[10] = 'T'; |
| 484 | blob_appendf(&wiki, "D %s\n", zDate); |
| @@ -612,12 +593,11 @@ | |
| 593 | */ |
| 594 | void wdiff_page(void){ |
| 595 | char *zTitle; |
| 596 | int rid1, rid2; |
| 597 | const char *zPageName; |
| 598 | Manifest *pW1, *pW2 = 0; |
| 599 | Blob w1, w2, d; |
| 600 | |
| 601 | login_check_credentials(); |
| 602 | rid1 = atoi(PD("a","0")); |
| 603 | if( !g.okHistory ){ login_needed(); return; } |
| @@ -635,27 +615,24 @@ | |
| 615 | " WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)" |
| 616 | " ORDER BY event.mtime DESC LIMIT 1", |
| 617 | zPageName, rid1 |
| 618 | ); |
| 619 | } |
| 620 | pW1 = manifest_get(rid1, CFTYPE_WIKI); |
| 621 | if( pW1==0 ) fossil_redirect_home(); |
| 622 | blob_init(&w1, pW1->zWiki, -1); |
| 623 | blob_zero(&w2); |
| 624 | if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){ |
| 625 | blob_init(&w2, pW2->zWiki, -1); |
| 626 | } |
| 627 | blob_zero(&d); |
| 628 | text_diff(&w2, &w1, &d, 5, 1); |
| 629 | @ <pre> |
| 630 | @ %h(blob_str(&d)) |
| 631 | @ </pre> |
| 632 | manifest_destroy(pW1); |
| 633 | manifest_destroy(pW2); |
| 634 | style_footer(); |
| 635 | } |
| 636 | |
| 637 | /* |
| 638 | ** WEBPAGE: wcontent |
| @@ -914,30 +891,26 @@ | |
| 891 | } |
| 892 | |
| 893 | if( strncmp(g.argv[2],"export",n)==0 ){ |
| 894 | char const *zPageName; /* Name of the wiki page to export */ |
| 895 | char const *zFile; /* Name of the output file (0=stdout) */ |
| 896 | int rid; /* Artifact ID of the wiki page */ |
| 897 | int i; /* Loop counter */ |
| 898 | char *zBody = 0; /* Wiki page content */ |
| 899 | Manifest *pWiki = 0; /* Parsed wiki page content */ |
| 900 | |
| 901 | if( (g.argc!=4) && (g.argc!=5) ){ |
| 902 | usage("export PAGENAME ?FILE?"); |
| 903 | } |
| 904 | zPageName = g.argv[3]; |
| 905 | rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x" |
| 906 | " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'" |
| 907 | " ORDER BY x.mtime DESC LIMIT 1", |
| 908 | zPageName |
| 909 | ); |
| 910 | if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){ |
| 911 | zBody = pWiki->zWiki; |
| 912 | } |
| 913 | if( zBody==0 ){ |
| 914 | fossil_fatal("wiki page [%s] not found",zPageName); |
| 915 | } |
| 916 | for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){} |
| @@ -955,12 +928,13 @@ | |
| 928 | fossil_fatal("wiki export could not open output file for writing."); |
| 929 | } |
| 930 | fprintf(zF,"%.*s\n", i, zBody); |
| 931 | if( doClose ) fclose(zF); |
| 932 | }else{ |
| 933 | printf("%.*s\n", i, zBody); |
| 934 | } |
| 935 | manifest_destroy(pWiki); |
| 936 | return; |
| 937 | }else |
| 938 | if( strncmp(g.argv[2],"commit",n)==0 |
| 939 | || strncmp(g.argv[2],"create",n)==0 ){ |
| 940 | char *zPageName; |
| 941 |
+25
-24
| --- src/zip.c | ||
| +++ src/zip.c | ||
| @@ -313,64 +313,65 @@ | ||
| 313 | 313 | ** politely expands into a subdir instead of filling your current dir |
| 314 | 314 | ** with source files. For example, pass a UUID or "ProjectName". |
| 315 | 315 | ** |
| 316 | 316 | */ |
| 317 | 317 | void zip_of_baseline(int rid, Blob *pZip, const char *zDir){ |
| 318 | - int i; | |
| 319 | - Blob mfile, file, hash; | |
| 320 | - Manifest m; | |
| 318 | + Blob mfile, hash, file; | |
| 319 | + Manifest *pManifest; | |
| 320 | + ManifestFile *pFile; | |
| 321 | 321 | Blob filename; |
| 322 | 322 | int nPrefix; |
| 323 | 323 | |
| 324 | 324 | content_get(rid, &mfile); |
| 325 | 325 | if( blob_size(&mfile)==0 ){ |
| 326 | 326 | blob_zero(pZip); |
| 327 | 327 | return; |
| 328 | 328 | } |
| 329 | - blob_zero(&file); | |
| 330 | 329 | blob_zero(&hash); |
| 331 | - blob_copy(&file, &mfile); | |
| 332 | 330 | blob_zero(&filename); |
| 333 | 331 | zip_open(); |
| 334 | 332 | |
| 335 | 333 | if( zDir && zDir[0] ){ |
| 336 | 334 | blob_appendf(&filename, "%s/", zDir); |
| 337 | 335 | } |
| 338 | 336 | nPrefix = blob_size(&filename); |
| 339 | 337 | |
| 340 | - if( manifest_parse(&m, &mfile) ){ | |
| 338 | + pManifest = manifest_get(rid, CFTYPE_MANIFEST); | |
| 339 | + if( pManifest ){ | |
| 341 | 340 | char *zName; |
| 342 | - zip_set_timedate(m.rDate); | |
| 343 | - blob_append(&filename, "manifest", -1); | |
| 344 | - zName = blob_str(&filename); | |
| 345 | - zip_add_folders(zName); | |
| 346 | - zip_add_file(zName, &file); | |
| 347 | - sha1sum_blob(&file, &hash); | |
| 348 | - blob_reset(&file); | |
| 349 | - blob_append(&hash, "\n", 1); | |
| 350 | - blob_resize(&filename, nPrefix); | |
| 351 | - blob_append(&filename, "manifest.uuid", -1); | |
| 352 | - zName = blob_str(&filename); | |
| 353 | - zip_add_file(zName, &hash); | |
| 354 | - blob_reset(&hash); | |
| 355 | - for(i=0; i<m.nFile; i++){ | |
| 356 | - int fid = uuid_to_rid(m.aFile[i].zUuid, 0); | |
| 341 | + zip_set_timedate(pManifest->rDate); | |
| 342 | + if( db_get_boolean("manifest", 0) ){ | |
| 343 | + blob_append(&filename, "manifest", -1); | |
| 344 | + zName = blob_str(&filename); | |
| 345 | + zip_add_folders(zName); | |
| 346 | + zip_add_file(zName, &mfile); | |
| 347 | + sha1sum_blob(&mfile, &hash); | |
| 348 | + blob_reset(&mfile); | |
| 349 | + blob_append(&hash, "\n", 1); | |
| 350 | + blob_resize(&filename, nPrefix); | |
| 351 | + blob_append(&filename, "manifest.uuid", -1); | |
| 352 | + zName = blob_str(&filename); | |
| 353 | + zip_add_file(zName, &hash); | |
| 354 | + blob_reset(&hash); | |
| 355 | + } | |
| 356 | + manifest_file_rewind(pManifest); | |
| 357 | + while( (pFile = manifest_file_next(pManifest,0))!=0 ){ | |
| 358 | + int fid = uuid_to_rid(pFile->zUuid, 0); | |
| 357 | 359 | if( fid ){ |
| 358 | 360 | content_get(fid, &file); |
| 359 | 361 | blob_resize(&filename, nPrefix); |
| 360 | - blob_append(&filename, m.aFile[i].zName, -1); | |
| 362 | + blob_append(&filename, pFile->zName, -1); | |
| 361 | 363 | zName = blob_str(&filename); |
| 362 | 364 | zip_add_folders(zName); |
| 363 | 365 | zip_add_file(zName, &file); |
| 364 | 366 | blob_reset(&file); |
| 365 | 367 | } |
| 366 | 368 | } |
| 367 | - manifest_clear(&m); | |
| 368 | 369 | }else{ |
| 369 | 370 | blob_reset(&mfile); |
| 370 | - blob_reset(&file); | |
| 371 | 371 | } |
| 372 | + manifest_destroy(pManifest); | |
| 372 | 373 | blob_reset(&filename); |
| 373 | 374 | zip_close(pZip); |
| 374 | 375 | } |
| 375 | 376 | |
| 376 | 377 | /* |
| 377 | 378 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -313,64 +313,65 @@ | |
| 313 | ** politely expands into a subdir instead of filling your current dir |
| 314 | ** with source files. For example, pass a UUID or "ProjectName". |
| 315 | ** |
| 316 | */ |
| 317 | void zip_of_baseline(int rid, Blob *pZip, const char *zDir){ |
| 318 | int i; |
| 319 | Blob mfile, file, hash; |
| 320 | Manifest m; |
| 321 | Blob filename; |
| 322 | int nPrefix; |
| 323 | |
| 324 | content_get(rid, &mfile); |
| 325 | if( blob_size(&mfile)==0 ){ |
| 326 | blob_zero(pZip); |
| 327 | return; |
| 328 | } |
| 329 | blob_zero(&file); |
| 330 | blob_zero(&hash); |
| 331 | blob_copy(&file, &mfile); |
| 332 | blob_zero(&filename); |
| 333 | zip_open(); |
| 334 | |
| 335 | if( zDir && zDir[0] ){ |
| 336 | blob_appendf(&filename, "%s/", zDir); |
| 337 | } |
| 338 | nPrefix = blob_size(&filename); |
| 339 | |
| 340 | if( manifest_parse(&m, &mfile) ){ |
| 341 | char *zName; |
| 342 | zip_set_timedate(m.rDate); |
| 343 | blob_append(&filename, "manifest", -1); |
| 344 | zName = blob_str(&filename); |
| 345 | zip_add_folders(zName); |
| 346 | zip_add_file(zName, &file); |
| 347 | sha1sum_blob(&file, &hash); |
| 348 | blob_reset(&file); |
| 349 | blob_append(&hash, "\n", 1); |
| 350 | blob_resize(&filename, nPrefix); |
| 351 | blob_append(&filename, "manifest.uuid", -1); |
| 352 | zName = blob_str(&filename); |
| 353 | zip_add_file(zName, &hash); |
| 354 | blob_reset(&hash); |
| 355 | for(i=0; i<m.nFile; i++){ |
| 356 | int fid = uuid_to_rid(m.aFile[i].zUuid, 0); |
| 357 | if( fid ){ |
| 358 | content_get(fid, &file); |
| 359 | blob_resize(&filename, nPrefix); |
| 360 | blob_append(&filename, m.aFile[i].zName, -1); |
| 361 | zName = blob_str(&filename); |
| 362 | zip_add_folders(zName); |
| 363 | zip_add_file(zName, &file); |
| 364 | blob_reset(&file); |
| 365 | } |
| 366 | } |
| 367 | manifest_clear(&m); |
| 368 | }else{ |
| 369 | blob_reset(&mfile); |
| 370 | blob_reset(&file); |
| 371 | } |
| 372 | blob_reset(&filename); |
| 373 | zip_close(pZip); |
| 374 | } |
| 375 | |
| 376 | /* |
| 377 |
| --- src/zip.c | |
| +++ src/zip.c | |
| @@ -313,64 +313,65 @@ | |
| 313 | ** politely expands into a subdir instead of filling your current dir |
| 314 | ** with source files. For example, pass a UUID or "ProjectName". |
| 315 | ** |
| 316 | */ |
| 317 | void zip_of_baseline(int rid, Blob *pZip, const char *zDir){ |
| 318 | Blob mfile, hash, file; |
| 319 | Manifest *pManifest; |
| 320 | ManifestFile *pFile; |
| 321 | Blob filename; |
| 322 | int nPrefix; |
| 323 | |
| 324 | content_get(rid, &mfile); |
| 325 | if( blob_size(&mfile)==0 ){ |
| 326 | blob_zero(pZip); |
| 327 | return; |
| 328 | } |
| 329 | blob_zero(&hash); |
| 330 | blob_zero(&filename); |
| 331 | zip_open(); |
| 332 | |
| 333 | if( zDir && zDir[0] ){ |
| 334 | blob_appendf(&filename, "%s/", zDir); |
| 335 | } |
| 336 | nPrefix = blob_size(&filename); |
| 337 | |
| 338 | pManifest = manifest_get(rid, CFTYPE_MANIFEST); |
| 339 | if( pManifest ){ |
| 340 | char *zName; |
| 341 | zip_set_timedate(pManifest->rDate); |
| 342 | if( db_get_boolean("manifest", 0) ){ |
| 343 | blob_append(&filename, "manifest", -1); |
| 344 | zName = blob_str(&filename); |
| 345 | zip_add_folders(zName); |
| 346 | zip_add_file(zName, &mfile); |
| 347 | sha1sum_blob(&mfile, &hash); |
| 348 | blob_reset(&mfile); |
| 349 | blob_append(&hash, "\n", 1); |
| 350 | blob_resize(&filename, nPrefix); |
| 351 | blob_append(&filename, "manifest.uuid", -1); |
| 352 | zName = blob_str(&filename); |
| 353 | zip_add_file(zName, &hash); |
| 354 | blob_reset(&hash); |
| 355 | } |
| 356 | manifest_file_rewind(pManifest); |
| 357 | while( (pFile = manifest_file_next(pManifest,0))!=0 ){ |
| 358 | int fid = uuid_to_rid(pFile->zUuid, 0); |
| 359 | if( fid ){ |
| 360 | content_get(fid, &file); |
| 361 | blob_resize(&filename, nPrefix); |
| 362 | blob_append(&filename, pFile->zName, -1); |
| 363 | zName = blob_str(&filename); |
| 364 | zip_add_folders(zName); |
| 365 | zip_add_file(zName, &file); |
| 366 | blob_reset(&file); |
| 367 | } |
| 368 | } |
| 369 | }else{ |
| 370 | blob_reset(&mfile); |
| 371 | } |
| 372 | manifest_destroy(pManifest); |
| 373 | blob_reset(&filename); |
| 374 | zip_close(pZip); |
| 375 | } |
| 376 | |
| 377 | /* |
| 378 |
+27
-5
| --- www/fileformat.wiki | ||
| +++ www/fileformat.wiki | ||
| @@ -95,19 +95,28 @@ | ||
| 95 | 95 | may contain no additional text or data beyond what is described here. |
| 96 | 96 | |
| 97 | 97 | Allowed cards in the manifest are as follows: |
| 98 | 98 | |
| 99 | 99 | <blockquote> |
| 100 | +<b>B</b> <i>baseline-manifest</i><br> | |
| 100 | 101 | <b>C</b> <i>checkin-comment</i><br> |
| 101 | 102 | <b>D</b> <i>time-and-date-stamp</i><br> |
| 102 | 103 | <b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br> |
| 103 | 104 | <b>P</b> <i>SHA1-hash</i>+<br> |
| 104 | 105 | <b>R</b> <i>repository-checksum</i><br> |
| 105 | 106 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name <b>*</b> ?value?</i><br> |
| 106 | 107 | <b>U</b> <i>user-login</i><br> |
| 107 | 108 | <b>Z</b> <i>manifest-checksum</i> |
| 108 | 109 | </blockquote> |
| 110 | + | |
| 111 | +A manifest may optionally have a single B-card. The B-card specifies | |
| 112 | +another manifest that serves as the "baseline" for this manifest. A | |
| 113 | +manifest that has a B-card is called a delta-manifest and a manifest | |
| 114 | +that omits the B-card is a baseline-manifest. The other manifest | |
| 115 | +identified by the argument of the B-card must be a baseline-manifest. | |
| 116 | +A baseline-manifest records the complete contents of a checkin. | |
| 117 | +A delta-manifest records only changes from its baseline. | |
| 109 | 118 | |
| 110 | 119 | A manifest must have exactly one C-card. The sole argument to |
| 111 | 120 | the C-card is a check-in comment that describes the check-in that |
| 112 | 121 | the manifest defines. The check-in comment is text. The following |
| 113 | 122 | escape sequences are applied to the text: |
| @@ -125,26 +134,29 @@ | ||
| 125 | 134 | |
| 126 | 135 | <blockquote> |
| 127 | 136 | <i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i> |
| 128 | 137 | </blockquote> |
| 129 | 138 | |
| 130 | -A manifest has zero or more F-cards. Each F-card defines a file | |
| 131 | -(other than the manifest itself) which is part of the check-in that | |
| 132 | -the manifest defines. There are two, three, or four arguments. | |
| 139 | +A manifest has zero or more F-cards. Each F-card identifies a file | |
| 140 | +that is part of the check-in. There are one, two, three, or four arguments. | |
| 133 | 141 | The first argument |
| 134 | 142 | is the pathname of the file in the check-in relative to the root |
| 135 | 143 | of the project file hierarchy. No ".." or "." directories are allowed |
| 136 | 144 | within the filename. Space characters are escaped as in C-card |
| 137 | 145 | comment text. Backslash characters and newlines are not allowed |
| 138 | 146 | within filenames. The directory separator character is a forward |
| 139 | 147 | slash (ASCII 0x2F). The second argument to the F-card is the |
| 140 | 148 | full 40-character lower-case hexadecimal SHA1 hash of the content |
| 141 | -artifact. The optional 3rd argument defines any special access | |
| 149 | +artifact. The second argument is required for baseline manifests | |
| 150 | +but is optional for delta manifests. When the second argument to the | |
| 151 | +F-card is omitted, it means that the file has been deleted relative | |
| 152 | +to the baseline. The optional 3rd argument defines any special access | |
| 142 | 153 | permissions associated with the file. The only special code currently |
| 143 | 154 | defined is "x" which means that the file is executable. All files are |
| 144 | 155 | always readable and writable. This can be expressed by "w" permission |
| 145 | -if desired but is optional. | |
| 156 | +if desired but is optional. The file format might be extended with | |
| 157 | +new permission letters in the future. | |
| 146 | 158 | The optional 4th argument is the name of the same file as it existed in |
| 147 | 159 | the parent check-in. If the name of the file is unchanged from its |
| 148 | 160 | parent, then the 4th argument is omitted. |
| 149 | 161 | |
| 150 | 162 | A manifest has zero or one P-cards. Most manifests have one P-card. |
| @@ -504,10 +516,20 @@ | ||
| 504 | 516 | <td> </td> |
| 505 | 517 | <td> </td> |
| 506 | 518 | <td align=center><b>X</b></td> |
| 507 | 519 | <td> </td> |
| 508 | 520 | </tr> |
| 521 | +<tr> | |
| 522 | +<td><b>B</b> <i>baseline</i></td> | |
| 523 | +<td align=center><b>X</b></td> | |
| 524 | +<td> </td> | |
| 525 | +<td> </td> | |
| 526 | +<td> </td> | |
| 527 | +<td> </td> | |
| 528 | +<td> </td> | |
| 529 | +<td> </td> | |
| 530 | +</tr> | |
| 509 | 531 | <tr> |
| 510 | 532 | <td><b>C</b> <i>comment-text</i></td> |
| 511 | 533 | <td align=center><b>X</b></td> |
| 512 | 534 | <td> </td> |
| 513 | 535 | <td> </td> |
| 514 | 536 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -95,19 +95,28 @@ | |
| 95 | may contain no additional text or data beyond what is described here. |
| 96 | |
| 97 | Allowed cards in the manifest are as follows: |
| 98 | |
| 99 | <blockquote> |
| 100 | <b>C</b> <i>checkin-comment</i><br> |
| 101 | <b>D</b> <i>time-and-date-stamp</i><br> |
| 102 | <b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br> |
| 103 | <b>P</b> <i>SHA1-hash</i>+<br> |
| 104 | <b>R</b> <i>repository-checksum</i><br> |
| 105 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name <b>*</b> ?value?</i><br> |
| 106 | <b>U</b> <i>user-login</i><br> |
| 107 | <b>Z</b> <i>manifest-checksum</i> |
| 108 | </blockquote> |
| 109 | |
| 110 | A manifest must have exactly one C-card. The sole argument to |
| 111 | the C-card is a check-in comment that describes the check-in that |
| 112 | the manifest defines. The check-in comment is text. The following |
| 113 | escape sequences are applied to the text: |
| @@ -125,26 +134,29 @@ | |
| 125 | |
| 126 | <blockquote> |
| 127 | <i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i> |
| 128 | </blockquote> |
| 129 | |
| 130 | A manifest has zero or more F-cards. Each F-card defines a file |
| 131 | (other than the manifest itself) which is part of the check-in that |
| 132 | the manifest defines. There are two, three, or four arguments. |
| 133 | The first argument |
| 134 | is the pathname of the file in the check-in relative to the root |
| 135 | of the project file hierarchy. No ".." or "." directories are allowed |
| 136 | within the filename. Space characters are escaped as in C-card |
| 137 | comment text. Backslash characters and newlines are not allowed |
| 138 | within filenames. The directory separator character is a forward |
| 139 | slash (ASCII 0x2F). The second argument to the F-card is the |
| 140 | full 40-character lower-case hexadecimal SHA1 hash of the content |
| 141 | artifact. The optional 3rd argument defines any special access |
| 142 | permissions associated with the file. The only special code currently |
| 143 | defined is "x" which means that the file is executable. All files are |
| 144 | always readable and writable. This can be expressed by "w" permission |
| 145 | if desired but is optional. |
| 146 | The optional 4th argument is the name of the same file as it existed in |
| 147 | the parent check-in. If the name of the file is unchanged from its |
| 148 | parent, then the 4th argument is omitted. |
| 149 | |
| 150 | A manifest has zero or one P-cards. Most manifests have one P-card. |
| @@ -504,10 +516,20 @@ | |
| 504 | <td> </td> |
| 505 | <td> </td> |
| 506 | <td align=center><b>X</b></td> |
| 507 | <td> </td> |
| 508 | </tr> |
| 509 | <tr> |
| 510 | <td><b>C</b> <i>comment-text</i></td> |
| 511 | <td align=center><b>X</b></td> |
| 512 | <td> </td> |
| 513 | <td> </td> |
| 514 |
| --- www/fileformat.wiki | |
| +++ www/fileformat.wiki | |
| @@ -95,19 +95,28 @@ | |
| 95 | may contain no additional text or data beyond what is described here. |
| 96 | |
| 97 | Allowed cards in the manifest are as follows: |
| 98 | |
| 99 | <blockquote> |
| 100 | <b>B</b> <i>baseline-manifest</i><br> |
| 101 | <b>C</b> <i>checkin-comment</i><br> |
| 102 | <b>D</b> <i>time-and-date-stamp</i><br> |
| 103 | <b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br> |
| 104 | <b>P</b> <i>SHA1-hash</i>+<br> |
| 105 | <b>R</b> <i>repository-checksum</i><br> |
| 106 | <b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name <b>*</b> ?value?</i><br> |
| 107 | <b>U</b> <i>user-login</i><br> |
| 108 | <b>Z</b> <i>manifest-checksum</i> |
| 109 | </blockquote> |
| 110 | |
| 111 | A manifest may optionally have a single B-card. The B-card specifies |
| 112 | another manifest that serves as the "baseline" for this manifest. A |
| 113 | manifest that has a B-card is called a delta-manifest and a manifest |
| 114 | that omits the B-card is a baseline-manifest. The other manifest |
| 115 | identified by the argument of the B-card must be a baseline-manifest. |
| 116 | A baseline-manifest records the complete contents of a checkin. |
| 117 | A delta-manifest records only changes from its baseline. |
| 118 | |
| 119 | A manifest must have exactly one C-card. The sole argument to |
| 120 | the C-card is a check-in comment that describes the check-in that |
| 121 | the manifest defines. The check-in comment is text. The following |
| 122 | escape sequences are applied to the text: |
| @@ -125,26 +134,29 @@ | |
| 134 | |
| 135 | <blockquote> |
| 136 | <i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i> |
| 137 | </blockquote> |
| 138 | |
| 139 | A manifest has zero or more F-cards. Each F-card identifies a file |
| 140 | that is part of the check-in. There are one, two, three, or four arguments. |
| 141 | The first argument |
| 142 | is the pathname of the file in the check-in relative to the root |
| 143 | of the project file hierarchy. No ".." or "." directories are allowed |
| 144 | within the filename. Space characters are escaped as in C-card |
| 145 | comment text. Backslash characters and newlines are not allowed |
| 146 | within filenames. The directory separator character is a forward |
| 147 | slash (ASCII 0x2F). The second argument to the F-card is the |
| 148 | full 40-character lower-case hexadecimal SHA1 hash of the content |
| 149 | artifact. The second argument is required for baseline manifests |
| 150 | but is optional for delta manifests. When the second argument to the |
| 151 | F-card is omitted, it means that the file has been deleted relative |
| 152 | to the baseline. The optional 3rd argument defines any special access |
| 153 | permissions associated with the file. The only special code currently |
| 154 | defined is "x" which means that the file is executable. All files are |
| 155 | always readable and writable. This can be expressed by "w" permission |
| 156 | if desired but is optional. The file format might be extended with |
| 157 | new permission letters in the future. |
| 158 | The optional 4th argument is the name of the same file as it existed in |
| 159 | the parent check-in. If the name of the file is unchanged from its |
| 160 | parent, then the 4th argument is omitted. |
| 161 | |
| 162 | A manifest has zero or one P-cards. Most manifests have one P-card. |
| @@ -504,10 +516,20 @@ | |
| 516 | <td> </td> |
| 517 | <td> </td> |
| 518 | <td align=center><b>X</b></td> |
| 519 | <td> </td> |
| 520 | </tr> |
| 521 | <tr> |
| 522 | <td><b>B</b> <i>baseline</i></td> |
| 523 | <td align=center><b>X</b></td> |
| 524 | <td> </td> |
| 525 | <td> </td> |
| 526 | <td> </td> |
| 527 | <td> </td> |
| 528 | <td> </td> |
| 529 | <td> </td> |
| 530 | </tr> |
| 531 | <tr> |
| 532 | <td><b>C</b> <i>comment-text</i></td> |
| 533 | <td align=center><b>X</b></td> |
| 534 | <td> </td> |
| 535 | <td> </td> |
| 536 |