Fossil SCM

Merge the delta-manifest enhancement into the trunk.

drh 2010-10-26 12:51 trunk merge
Commit d13054ce84eaa950dd661a5dac11941fe4bb9376
+20 -20
--- src/branch.c
+++ src/branch.c
@@ -35,12 +35,11 @@
3535
const char *zBranch; /* Name of the new branch */
3636
char *zDate; /* Date that branch was created */
3737
char *zComment; /* Check-in comment for the new branch */
3838
const char *zColor; /* Color of the new branch */
3939
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 */
4241
Blob mcksum; /* Self-checksum on the manifest */
4342
const char *zDateOvrd; /* Override date string */
4443
const char *zUserOvrd; /* Override user name */
4544
4645
noSign = find_option("nosign","",0)!=0;
@@ -71,41 +70,42 @@
7170
db_begin_transaction();
7271
rootid = name_to_rid(g.argv[4]);
7372
if( rootid==0 ){
7473
fossil_fatal("unable to locate check-in off of which to branch");
7574
}
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
+ }
7680
7781
/* Create a manifest for the new branch */
7882
blob_zero(&branch);
83
+ if( pParent->zBaseline ){
84
+ blob_appendf(&branch, "B %s\n", pParent->zBaseline);
85
+ }
7986
zComment = mprintf("Create new branch named \"%h\"", zBranch);
8087
blob_appendf(&branch, "C %F\n", zComment);
8188
zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
8289
zDate[10] = 'T';
8390
blob_appendf(&branch, "D %s\n", zDate);
8491
8592
/* 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);
102102
}
103103
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
104104
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);
107107
108108
/* Add the symbolic branch name and the "branch" tag to identify
109109
** this as a new branch */
110110
if( zColor!=0 ){
111111
blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
112112
--- 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 @@
7171
** There is no hyperlink on the file element of the path.
7272
**
7373
** The computed string is appended to the pOut blob. pOut should
7474
** have already been initialized.
7575
*/
76
-void hyperlinked_path(const char *zPath, Blob *pOut){
76
+void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){
7777
int i, j;
7878
char *zSep = "";
7979
8080
for(i=0; zPath[i]; i=j){
8181
for(j=i; zPath[j] && zPath[j]!='/'; j++){}
8282
if( zPath[j] && g.okHistory ){
83
- blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>",
84
- zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]);
83
+ if( zCI ){
84
+ blob_appendf(pOut, "%s<a href=\"%s/dir?ci=%S&amp;name=%#T\">%#h</a>",
85
+ zSep, g.zBaseURL, zCI, j, zPath, j-i, &zPath[i]);
86
+ }else{
87
+ blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>",
88
+ zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]);
89
+ }
8590
}else{
8691
blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
8792
}
8893
zSep = "/";
8994
while( zPath[j]=='/' ){ j++; }
@@ -99,21 +104,21 @@
99104
** name=PATH Directory to display. Required.
100105
** ci=LABEL Show only files in this check-in. Optional.
101106
*/
102107
void page_dir(void){
103108
const char *zD = P("name");
109
+ int nD = zD ? strlen(zD)+1 : 0;
104110
int mxLen;
105111
int nCol, nRow;
106112
int cnt, i;
107113
char *zPrefix;
108114
Stmt q;
109115
const char *zCI = P("ci");
110116
int rid = 0;
111117
char *zUuid = 0;
112
- Blob content;
113118
Blob dirname;
114
- Manifest m;
119
+ Manifest *pM = 0;
115120
const char *zSubdirLink;
116121
117122
login_check_credentials();
118123
if( !g.okHistory ){ login_needed(); return; }
119124
style_header("File List");
@@ -126,25 +131,24 @@
126131
/* If a specific check-in is requested, fetch and parse it. If the
127132
** specific check-in does not exist, clear zCI. zCI==0 will cause all
128133
** files from all check-ins to be displayed.
129134
*/
130135
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);
135139
}else{
136
- zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
140
+ zCI = 0;
137141
}
138142
}
139143
140144
141145
/* Compute the title of the page */
142146
blob_zero(&dirname);
143147
if( zD ){
144148
blob_append(&dirname, "in directory ", -1);
145
- hyperlinked_path(zD, &dirname);
149
+ hyperlinked_path(zD, &dirname, zCI);
146150
zPrefix = mprintf("%h/", zD);
147151
}else{
148152
blob_append(&dirname, "in the top-level directory", -1);
149153
zPrefix = "";
150154
}
@@ -160,22 +164,30 @@
160164
style_submenu_element("All", "All", "%s/dir?name=%t", g.zTop, zD);
161165
}else{
162166
style_submenu_element("All", "All", "%s/dir", g.zBaseURL);
163167
}
164168
}else{
169
+ int hasTrunk;
165170
@ <h2>The union of all files from all check-ins
166171
@ %s(blob_str(&dirname))</h2>
172
+ hasTrunk = db_exists(
173
+ "SELECT 1 FROM tagxref WHERE tagid=%d AND value='trunk'",
174
+ TAG_BRANCH);
167175
zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix);
168176
if( zD ){
169177
style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL);
170178
style_submenu_element("Tip", "Tip", "%s/dir?name=%t&amp;ci=tip",
171179
g.zBaseURL, zD);
172
- style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&amp;ci=trunk",
173
- g.zBaseURL,zD);
180
+ if( hasTrunk ){
181
+ style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&amp;ci=trunk",
182
+ g.zBaseURL,zD);
183
+ }
174184
}else{
175185
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
+ }
177189
}
178190
}
179191
180192
/* Compute the temporary table "localfiles" containing the names
181193
** of all files and subdirectories in the zD[] directory.
@@ -184,39 +196,47 @@
184196
** first and it also gives us an easy way to distinguish files
185197
** from directories in the loop that follows.
186198
*/
187199
db_multi_exec(
188200
"CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);"
189
- "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);"
190201
);
191202
if( zCI ){
192203
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"
218238
);
219239
}
220240
221241
/* Generate a multi-column table listing the contents of zD[]
222242
** directory.
@@ -246,8 +266,9 @@
246266
@ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN)
247267
@ </a></li>
248268
}
249269
}
250270
db_finalize(&q);
271
+ manifest_destroy(pM);
251272
@ </ul></td></tr></table>
252273
style_footer();
253274
}
254275
--- 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&amp;ci=tip",
171 g.zBaseURL, zD);
172 style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&amp;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&amp;name=%#T\">%#h</a>",
85 zSep, g.zBaseURL, zCI, j, zPath, j-i, &zPath[i]);
86 }else{
87 blob_appendf(pOut, "%s<a href=\"%s/dir?name=%#T\">%#h</a>",
88 zSep, g.zBaseURL, j, zPath, j-i, &zPath[i]);
89 }
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&amp;ci=tip",
179 g.zBaseURL, zD);
180 if( hasTrunk ){
181 style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&amp;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 @@
259259
Blob repo;
260260
Stmt q;
261261
int n;
262262
const char *zIgnoreFlag = find_option("ignore",0,1);
263263
int allFlag = find_option("dotfiles",0,0)!=0;
264
+ int outputManifest = db_get_boolean("manifest",0);
264265
265266
db_must_be_within_tree();
266267
db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
267268
n = strlen(g.zLocalRoot);
268269
blob_init(&path, g.zLocalRoot, n-1);
@@ -270,16 +271,18 @@
270271
zIgnoreFlag = db_get("ignore-glob", 0);
271272
}
272273
vfile_scan(0, &path, blob_size(&path), allFlag);
273274
db_prepare(&q,
274275
"SELECT x FROM sfile"
275
- " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_',"
276
+ " WHERE x NOT IN ('%s','%s','_FOSSIL_',"
276277
"'_FOSSIL_-journal','.fos','.fos-journal',"
277278
"'_FOSSIL_-wal','_FOSSIL_-shm','.fos-wal',"
278279
"'.fos-shm')"
279280
" AND NOT %s"
280281
" ORDER BY 1",
282
+ outputManifest ? "manifest" : "_FOSSIL_",
283
+ outputManifest ? "manifest.uuid" : "_FOSSIL_",
281284
glob_expr("x", zIgnoreFlag)
282285
);
283286
if( file_tree_name(g.zRepositoryName, &repo, 0) ){
284287
db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo);
285288
}
@@ -537,21 +540,158 @@
537540
zDate[10] = 'T';
538541
return zDate;
539542
}
540543
541544
/*
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.
548546
*/
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;
552691
}
692
+
553693
554694
/*
555695
** COMMAND: ci
556696
** COMMAND: commit
557697
**
@@ -586,39 +726,52 @@
586726
** --branch NEW-BRANCH-NAME
587727
** --bgcolor COLOR
588728
** --nosign
589729
** --force|-f
590730
** --private
731
+** --baseline
732
+** --delta
591733
**
592734
*/
593735
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 */
601744
int noSign = 0; /* True to omit signing the manifest using GPG */
602745
int isAMerge = 0; /* True if checking in a merge */
603746
int forceFlag = 0; /* Force a fork */
747
+ int forceDelta = 0; /* Force a delta-manifest */
748
+ int forceBaseline = 0; /* Force a baseline-manifest */
604749
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 */
606753
const char *zBranch; /* Create a new branch with this name */
607754
const char *zBgColor; /* Set background color when branching */
608755
const char *zDateOvrd; /* Override date string */
609756
const char *zUserOvrd; /* Override user name */
610757
const char *zComFile; /* Read commit message from this file */
611
- Blob filename; /* complete filename */
612
- Blob manifest;
758
+ Blob manifest; /* Manifest in baseline form */
613759
Blob muuid; /* Manifest uuid */
614
- Blob mcksum; /* Self-checksum on the manifest */
615760
Blob cksum1, cksum2; /* Before and after commit checksums */
616761
Blob cksum1b; /* Checksum recorded in the manifest */
762
+ int szD; /* Size of the delta manifest */
763
+ int szB; /* Size of the baseline manifest */
617764
618765
url_proxy_options();
619766
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;
620773
zComment = find_option("comment","m",1);
621774
forceFlag = find_option("force", "f", 0)!=0;
622775
zBranch = find_option("branch","b",1);
623776
zBgColor = find_option("bgcolor",0,1);
624777
zComFile = find_option("message-file", "M", 1);
@@ -630,11 +783,23 @@
630783
zDateOvrd = find_option("date-override",0,1);
631784
zUserOvrd = find_option("user-override",0,1);
632785
db_must_be_within_tree();
633786
noSign = db_get_boolean("omitsign", 0)|noSign;
634787
if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
788
+ useCksum = db_get_boolean("repo-cksum", 1);
789
+ outputManifest = db_get_boolean("manifest", 0);
635790
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
+ }
636801
637802
/* Get the ID of the parent manifest artifact */
638803
vid = db_lget_int("checkout", 0);
639804
if( content_is_private(vid) ){
640805
g.markPrivate = 1;
@@ -681,14 +846,14 @@
681846
*/
682847
if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
683848
fossil_fatal("no such user: %s", g.zLogin);
684849
}
685850
686
- rc = unsaved_changes();
851
+ hasChanges = unsaved_changes();
687852
db_begin_transaction();
688853
db_record_repository_filename(0);
689
- if( rc==0 && !isAMerge && !forceFlag ){
854
+ if( hasChanges==0 && !isAMerge && !forceFlag ){
690855
fossil_fatal("nothing has changed");
691856
}
692857
693858
/* If one or more files that were named on the command line have not
694859
** been modified, bail out now.
@@ -720,11 +885,11 @@
720885
" WHERE tagid=%d AND rid=%d AND tagtype>0",
721886
TAG_CLOSED, vid) ){
722887
fossil_fatal("cannot commit against a closed leaf");
723888
}
724889
725
- vfile_aggregate_checksum_disk(vid, &cksum1);
890
+ if( useCksum ) vfile_aggregate_checksum_disk(vid, &cksum1);
726891
if( zComment ){
727892
blob_zero(&comment);
728893
blob_append(&comment, zComment, -1);
729894
}else if( zComFile ){
730895
blob_zero(&comment);
@@ -775,113 +940,86 @@
775940
db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id);
776941
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
777942
}
778943
db_finalize(&q);
779944
780
- /* Create the manifest */
781
- blob_zero(&manifest);
945
+ /* Create the new manifest */
782946
if( blob_size(&comment)==0 ){
783947
blob_append(&comment, "(no comment)", -1);
784948
}
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
+ }
8741004
if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){
8751005
Blob ans;
8761006
blob_zero(&ans);
8771007
prompt_user("unable to sign manifest. continue (y/N)? ", &ans);
8781008
if( blob_str(&ans)[0]!='y' ){
8791009
fossil_exit(1);
8801010
}
8811011
}
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 ){
8831021
zManifestFile = mprintf("%smanifest", g.zLocalRoot);
8841022
blob_write_to_file(&manifest, zManifestFile);
8851023
blob_reset(&manifest);
8861024
blob_read_from_file(&manifest, zManifestFile);
8871025
free(zManifestFile);
@@ -893,11 +1031,11 @@
8931031
db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nvid);
8941032
manifest_crosslink(nvid, &manifest);
8951033
content_deltify(vid, nvid, 0);
8961034
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
8971035
printf("New_Version: %s\n", zUuid);
898
- if( !file_exists_in_checkout(vid, "manifest.uuid") ){
1036
+ if( outputManifest ){
8991037
zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
9001038
blob_zero(&muuid);
9011039
blob_appendf(&muuid, "%s\n", zUuid);
9021040
blob_write_to_file(&muuid, zManifestFile);
9031041
free(zManifestFile);
@@ -914,45 +1052,51 @@
9141052
" WHERE file_is_selected(id);"
9151053
, vid, nvid
9161054
);
9171055
db_lset_int("checkout", nvid);
9181056
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
+ }
9431083
}
9441084
9451085
/* Clear the undo/redo stack */
9461086
undo_reset();
9471087
9481088
/* Commit */
9491089
db_multi_exec("DELETE FROM vvar WHERE name='ci-comment'");
1090
+ if( testRun ){
1091
+ db_end_transaction(1);
1092
+ exit(1);
1093
+ }
9501094
db_end_transaction(0);
9511095
9521096
if( !g.markPrivate ){
9531097
autosync(AUTOSYNC_PUSH);
9541098
}
9551099
if( count_nonbranch_children(vid)>1 ){
9561100
printf("**** warning: a fork has occurred *****\n");
9571101
}
9581102
}
9591103
--- 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 @@
7878
7979
/*
8080
** Load a vfile from a record ID.
8181
*/
8282
void load_vfile_from_rid(int vid){
83
- Blob manifest;
84
-
8583
if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){
8684
return;
8785
}
88
- content_get(vid, &manifest);
89
- vfile_build(vid, &manifest);
90
- blob_reset(&manifest);
86
+ vfile_build(vid);
9187
}
9288
9389
/*
9490
** Set or clear the vfile.isexe flag for a file.
9591
*/
9692
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);
99103
}
100104
101105
/*
102106
** Set or clear the execute permission bit (as appropriate) for all
103107
** 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.
109140
*/
110141
void manifest_to_disk(int vid){
111142
char *zManFile;
112143
Blob manifest;
113144
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);
147149
zManFile = mprintf("%smanifest", g.zLocalRoot);
148150
blob_write_to_file(&manifest, zManFile);
149151
free(zManFile);
150
- }
151
- if( !seenManifestUuid ){
152152
blob_zero(&hash);
153153
sha1sum_blob(&manifest, &hash);
154154
zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
155155
blob_append(&hash, "\n", 1);
156156
blob_write_to_file(&hash, zManFile);
157157
free(zManFile);
158158
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
+ }
159170
}
171
+
160172
}
161173
162174
/*
163175
** COMMAND: checkout
164176
** COMMAND: co
@@ -228,10 +240,11 @@
228240
}
229241
db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
230242
if( !keepFlag ){
231243
vfile_to_disk(vid, 0, 1, promptFlag);
232244
}
245
+ checkout_set_all_exe(vid);
233246
manifest_to_disk(vid);
234247
db_lset_int("checkout", vid);
235248
undo_reset();
236249
db_multi_exec("DELETE FROM vmerge");
237250
if( !keepFlag ){
238251
--- 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
--- src/configure.c
+++ src/configure.c
@@ -74,10 +74,11 @@
7474
{ "footer", CONFIGSET_SKIN },
7575
{ "logo-mimetype", CONFIGSET_SKIN },
7676
{ "logo-image", CONFIGSET_SKIN },
7777
{ "project-name", CONFIGSET_PROJ },
7878
{ "project-description", CONFIGSET_PROJ },
79
+ { "manifest", CONFIGSET_PROJ },
7980
{ "index-page", CONFIGSET_SKIN },
8081
{ "timeline-block-markup", CONFIGSET_SKIN },
8182
{ "timeline-max-comment", CONFIGSET_SKIN },
8283
{ "ticket-table", CONFIGSET_TKT },
8384
{ "ticket-common", CONFIGSET_TKT },
8485
--- 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
+32 -10
--- src/db.c
+++ src/db.c
@@ -1510,25 +1510,27 @@
15101510
int width; /* Width of display. 0 for boolean values */
15111511
char const *def; /* Default value */
15121512
};
15131513
#endif /* INTERFACE */
15141514
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" },
15221522
{ "editor", 0, 16, "" },
15231523
{ "gdiff-command", 0, 16, "gdiff" },
15241524
{ "ignore-glob", 0, 40, "" },
15251525
{ "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" },
15281529
{ "pgp-command", 0, 32, "gpg --clearsign -o " },
15291530
{ "proxy", 0, 32, "off" },
1531
+ { "repo-cksum", 0, 0, "on" },
15301532
{ "ssh-command", 0, 32, "" },
15311533
{ "web-browser", 0, 32, "" },
15321534
{ 0,0,0,0 }
15331535
};
15341536
@@ -1548,23 +1550,25 @@
15481550
** auto-captcha If enabled, the Login page provides a button to
15491551
** fill in the captcha password. Default: on
15501552
**
15511553
** auto-shun If enabled, automatically pull the shunning list
15521554
** from a server to which the client autosyncs.
1555
+** Default: on
15531556
**
15541557
** autosync If enabled, automatically pull prior to commit
15551558
** or update and automatically push after commit or
15561559
** tag or branch creation. If the value is "pullonly"
15571560
** then only pull operations occur automatically.
1561
+** Default: on
15581562
**
15591563
** binary-glob The VALUE is a comma-separated list of GLOB patterns
15601564
** that should be treated as binary files for merging
15611565
** purposes. Example: *.xml
15621566
**
15631567
** clearsign When enabled, fossil will attempt to sign all commits
15641568
** with gpg. When disabled (the default), commits will
1565
-** be unsigned.
1569
+** be unsigned. Default: off
15661570
**
15671571
** diff-command External command to run when performing a diff.
15681572
** If undefined, the internal text diff will be used.
15691573
**
15701574
** dont-push Prevent this repository from pushing from client to
@@ -1585,10 +1589,14 @@
15851589
** localauth If enabled, require that HTTP connections from
15861590
** 127.0.0.1 be authenticated by password. If
15871591
** false, all HTTP requests from localhost have
15881592
** unrestricted access to the repository.
15891593
**
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
+**
15901598
** mtime-changes Use file modification times (mtimes) to detect when
15911599
** files have been modified. (Default "on".)
15921600
**
15931601
** pgp-command Command used to clear-sign manifests at check-in.
15941602
** The default is "gpg --clearsign -o ".
@@ -1595,10 +1603,15 @@
15951603
**
15961604
** proxy URL of the HTTP proxy. If undefined or "off" then
15971605
** the "http_proxy" environment variable is consulted.
15981606
** If the http_proxy environment variable is undefined
15991607
** 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.
16001613
**
16011614
** ssh-command Command used to talk to a remote machine with
16021615
** the "ssh://" protocol.
16031616
**
16041617
** web-browser A shell command used to launch your preferred
@@ -1622,24 +1635,33 @@
16221635
for(i=0; ctrlSettings[i].name; i++){
16231636
print_setting(ctrlSettings[i].name);
16241637
}
16251638
}else if( g.argc==3 || g.argc==4 ){
16261639
const char *zName = g.argv[2];
1640
+ int isManifest;
16271641
int n = strlen(zName);
16281642
for(i=0; ctrlSettings[i].name; i++){
16291643
if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break;
16301644
}
16311645
if( !ctrlSettings[i].name ){
16321646
fossil_fatal("no such setting: %s", zName);
16331647
}
1648
+ isManifest = strcmp(ctrlSettings[i].name, "manifest")==0;
1649
+ if( isManifest && globalFlag ){
1650
+ fossil_fatal("cannot set 'manifest' globally");
1651
+ }
16341652
if( unsetFlag ){
16351653
db_unset(ctrlSettings[i].name, globalFlag);
16361654
}else if( g.argc==4 ){
16371655
db_set(ctrlSettings[i].name, g.argv[3], globalFlag);
16381656
}else{
1657
+ isManifest = 0;
16391658
print_setting(ctrlSettings[i].name);
16401659
}
1660
+ if( isManifest ){
1661
+ manifest_to_disk(db_lget_int("checkout", 0));
1662
+ }
16411663
}else{
16421664
usage("?PROPERTY? ?VALUE?");
16431665
}
16441666
}
16451667
16461668
--- 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 @@
195195
/*
196196
** Compute a 32-bit checksum on the N-byte buffer. Return the result.
197197
*/
198198
static unsigned int checksum(const char *zIn, size_t N){
199199
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;
201204
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]);
206209
z += 16;
207210
N -= 16;
208211
}
209212
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];
211217
z += 4;
212218
N -= 4;
213219
}
220
+ sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
214221
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);
218225
default: ;
219226
}
220
- return sum;
227
+ return sum3;
221228
}
222229
223230
/*
224231
** Create a new delta.
225232
**
@@ -512,11 +519,13 @@
512519
int lenDelta, /* Length of the delta */
513520
char *zOut /* Write the output into this preallocated buffer */
514521
){
515522
unsigned int limit;
516523
unsigned int total = 0;
524
+#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST
517525
char *zOrigOut = zOut;
526
+#endif
518527
519528
limit = getInt(&zDelta, &lenDelta);
520529
if( *zDelta!='\n' ){
521530
/* ERROR: size integer not terminated by "\n" */
522531
return -1;
@@ -567,14 +576,16 @@
567576
break;
568577
}
569578
case ';': {
570579
zDelta++; lenDelta--;
571580
zOut[0] = 0;
581
+#ifndef FOSSIL_OMIT_DELTA_CKSUM_TEST
572582
if( cnt!=checksum(zOrigOut, total) ){
573583
/* ERROR: bad checksum */
574584
return -1;
575585
}
586
+#endif
576587
if( total!=limit ){
577588
/* ERROR: generated size does not match predicted size */
578589
return -1;
579590
}
580591
return total;
581592
--- 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 @@
355355
const char *zFrom,
356356
const char *zTo,
357357
const char *zDiffCmd,
358358
int diffFlags
359359
){
360
- Manifest mFrom, mTo;
361
- int iFrom, iTo;
360
+ Manifest *pFrom, *pTo;
361
+ ManifestFile *pFromFile, *pToFile;
362362
int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
363363
int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
364364
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 ){
369373
int cmp;
370
- if( iFrom>=mFrom.nFile ){
374
+ if( pFromFile==0 ){
371375
cmp = +1;
372
- }else if( iTo>=mTo.nFile ){
376
+ }else if( pToFile==0 ){
373377
cmp = -1;
374378
}else{
375
- cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName);
379
+ cmp = strcmp(pFromFile->zName, pToFile->zName);
376380
}
377381
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);
403406
}
404407
405408
/*
406409
** COMMAND: diff
407410
** COMMAND: gdiff
408411
--- 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 @@
388388
goto doc_not_found;
389389
}
390390
391391
if( rid==0 ){
392392
Stmt s;
393
- Blob baseline;
394
- Manifest m;
393
+ Manifest *pM;
394
+ ManifestFile *pFile;
395395
396396
/* Add the vid baseline to the cache */
397397
if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
398398
db_multi_exec("DELETE FROM vcache");
399399
}
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 ){
404402
goto doc_not_found;
405403
}
406404
db_prepare(&s,
407405
"INSERT INTO vcache(vid,fname,rid)"
408406
" SELECT %d, :fname, rid FROM blob"
409407
" WHERE uuid=:uuid",
410408
vid
411409
);
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);
415414
db_step(&s);
416415
db_reset(&s);
417416
}
418417
db_finalize(&s);
419
- manifest_clear(&m);
418
+ manifest_destroy(pM);
420419
421420
/* Try again to find the file */
422421
rid = db_int(0, "SELECT rid FROM vcache"
423422
" WHERE vid=%d AND fname=%Q", vid, zName);
424423
}
425424
--- 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 @@
267267
/*
268268
** Decode a fossilized string in-place.
269269
*/
270270
void defossilize(char *z){
271271
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++){
274275
if( c=='\\' && z[i+1] ){
275276
i++;
276277
switch( z[i] ){
277278
case 'n': c = '\n'; break;
278279
case 's': c = ' '; break;
279280
--- 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 @@
6363
const char *zEventId; /* Event identifier */
6464
char *zETime; /* Time of the event */
6565
char *zATime; /* Time the artifact was created */
6666
int specRid; /* rid specified by aid= parameter */
6767
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 */
7069
Blob fullbody; /* Complete content of the event body */
7170
Blob title; /* Title extracted from the event body */
7271
Blob tail; /* Event body that comes after the title */
7372
Stmt q1; /* Query to search for the event */
7473
int showDetail; /* True to show details */
@@ -113,18 +112,15 @@
113112
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
114113
showDetail = atoi(PD("detail","0"));
115114
116115
/* Extract the event content.
117116
*/
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 ){
123119
fossil_panic("Object #%d is not an event", rid);
124120
}
125
- blob_init(&fullbody, m.zWiki, -1);
121
+ blob_init(&fullbody, pEvent->zWiki, -1);
126122
if( wiki_find_title(&fullbody, &title, &tail) ){
127123
style_header(blob_str(&title));
128124
}else{
129125
style_header("Event %S", zEventId);
130126
tail = fullbody;
@@ -131,11 +127,11 @@
131127
}
132128
if( g.okWrWiki && g.okWrite && nextRid==0 ){
133129
style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
134130
g.zTop, zEventId);
135131
}
136
- zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate);
132
+ zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
137133
style_submenu_element("Context", "Context", "%s/timeline?c=%T",
138134
g.zTop, zETime);
139135
if( g.okHistory ){
140136
if( showDetail ){
141137
style_submenu_element("Plain", "Plain", "%s/event?name=%s&amp;aid=%s",
@@ -166,37 +162,37 @@
166162
if( showDetail && g.okHistory ){
167163
int i;
168164
const char *zClr = 0;
169165
Blob comment;
170166
171
- zATime = db_text(0, "SELECT datetime(%.17g)", m.rDate);
167
+ zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
172168
@ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at
173169
@ [<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
175171
@ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p>
176172
@ <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;
180176
}
181177
}
182178
if( zClr && zClr[0]==0 ) zClr = 0;
183179
if( zClr ){
184180
@ <div style="background-color: %h(zClr);">
185181
}else{
186182
@ <div>
187183
}
188
- blob_init(&comment, m.zComment, -1);
184
+ blob_init(&comment, pEvent->zComment, -1);
189185
wiki_convert(&comment, 0, WIKI_INLINE);
190186
blob_reset(&comment);
191187
@ </div>
192188
@ </blockquote><hr />
193189
}
194190
195191
wiki_convert(&tail, 0, 0);
196192
style_footer();
197
- manifest_clear(&m);
193
+ manifest_destroy(pEvent);
198194
}
199195
200196
/*
201197
** WEBPAGE: eventedit
202198
** URL: /eventedit?name=EVENTID
@@ -259,22 +255,18 @@
259255
260256
/* If editing an existing event, extract the key fields to use as
261257
** a starting point for the edit.
262258
*/
263259
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;
272264
if( zETime==0 ){
273
- zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate);
265
+ zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
274266
}
275
- if( zComment==0 ) zComment = m.zComment;
267
+ if( zComment==0 ) zComment = pEvent->zComment;
276268
}
277269
if( zTags==0 ){
278270
zTags = db_text(0,
279271
"SELECT group_concat(substr(tagname,5),', ')"
280272
" FROM tagxref, tag"
281273
--- 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&amp;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&amp;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 @@
131131
TAG_BRANCH,
132132
zFilename
133133
);
134134
blob_zero(&title);
135135
blob_appendf(&title, "History of ");
136
- hyperlinked_path(zFilename, &title);
136
+ hyperlinked_path(zFilename, &title, 0);
137137
@ <h2>%b(&title)</h2>
138138
blob_reset(&title);
139139
pGraph = graph_init();
140140
@ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
141141
@ <table class="timelineTable">
142142
--- 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 @@
543543
rid = 0;
544544
}
545545
db_finalize(&q);
546546
showTags(rid, "wiki-*");
547547
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 ){
555551
Blob wiki;
556
- blob_init(&wiki, m.zWiki, -1);
552
+ blob_init(&wiki, pWiki->zWiki, -1);
557553
@ <div class="section">Content</div>
558554
wiki_convert(&wiki, 0, 0);
559555
blob_reset(&wiki);
560556
}
561
- manifest_clear(&m);
557
+ manifest_destroy(pWiki);
562558
}
563559
style_footer();
564560
}
565561
566562
/*
@@ -580,26 +576,23 @@
580576
581577
/*
582578
** Find an checkin based on query parameter zParam and parse its
583579
** manifest. Return the number of errors.
584580
*/
585
-static int vdiff_parse_manifest(const char *zParam, int *pRid, Manifest *pM){
581
+static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){
586582
int rid;
587
- Blob content;
588583
589584
*pRid = rid = name_to_rid_www(zParam);
590585
if( rid==0 ){
591586
webpage_error("Missing \"%s\" query parameter.", zParam);
592
- return 1;
587
+ return 0;
593588
}
594589
if( !is_a_version(rid) ){
595590
webpage_error("Artifact %s is not a checkin.", P(zParam));
596
- return 1;
591
+ return 0;
597592
}
598
- content_get(rid, &content);
599
- manifest_parse(pM, &content);
600
- return 0;
593
+ return manifest_get(rid, CFTYPE_MANIFEST);
601594
}
602595
603596
/*
604597
** Output a description of a check-in
605598
*/
@@ -635,59 +628,64 @@
635628
** Show all differences between two checkins.
636629
*/
637630
void vdiff_page(void){
638631
int ridFrom, ridTo;
639632
int showDetail = 0;
640
- int iFrom, iTo;
641
- Manifest mFrom, mTo;
633
+ Manifest *pFrom, *pTo;
634
+ ManifestFile *pFileFrom, *pFileTo;
642635
643636
login_check_credentials();
644637
if( !g.okRead ){ login_needed(); return; }
645638
login_anonymous_available();
646639
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;
649644
showDetail = atoi(PD("detail","0"));
650645
style_header("Check-in Differences");
651646
@ <h2>Difference From:</h2><blockquote>
652647
checkin_description(ridFrom);
653648
@ </blockquote><h2>To:</h2><blockquote>
654649
checkin_description(ridTo);
655650
@ </blockquote><hr /><p>
656651
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 ){
659657
int cmp;
660
- if( iFrom>=mFrom.nFile ){
658
+ if( pFileFrom==0 ){
661659
cmp = +1;
662
- }else if( iTo>=mTo.nFile ){
660
+ }else if( pFileTo==0 ){
663661
cmp = -1;
664662
}else{
665
- cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName);
663
+ cmp = strcmp(pFileFrom->zName, pFileTo->zName);
666664
}
667665
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);
671669
}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 ){
676674
/* No changes */
677
- iFrom++;
678
- iTo++;
675
+ pFileFrom = manifest_file_next(pFrom, 0);
676
+ pFileTo = manifest_file_next(pTo, 0);
679677
}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);
685683
}
686684
}
687
- manifest_clear(&mFrom);
688
- manifest_clear(&mTo);
685
+ manifest_destroy(pFrom);
686
+ manifest_destroy(pTo);
689687
690688
style_footer();
691689
}
692690
693691
/*
@@ -1043,25 +1041,26 @@
10431041
*/
10441042
int artifact_from_ci_and_filename(void){
10451043
const char *zFilename;
10461044
const char *zCI;
10471045
int cirid;
1048
- Blob content;
1049
- Manifest m;
1050
- int i;
1046
+ Manifest *pManifest;
1047
+ ManifestFile *pFile;
10511048
10521049
zCI = P("ci");
10531050
if( zCI==0 ) return 0;
10541051
zFilename = P("filename");
10551052
if( zFilename==0 ) return 0;
10561053
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;
10631062
}
10641063
}
10651064
return 0;
10661065
}
10671066
@@ -1168,15 +1167,14 @@
11681167
**
11691168
** Show the details of a ticket change control artifact.
11701169
*/
11711170
void tinfo_page(void){
11721171
int rid;
1173
- Blob content;
11741172
char *zDate;
11751173
const char *zUuid;
11761174
char zTktName[20];
1177
- Manifest m;
1175
+ Manifest *pTktChng;
11781176
11791177
login_check_credentials();
11801178
if( !g.okRdTkt ){ login_needed(); return; }
11811179
rid = name_to_rid_www("name");
11821180
if( rid==0 ){ fossil_redirect_home(); }
@@ -1188,39 +1186,37 @@
11881186
}else{
11891187
style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
11901188
g.zTop, zUuid);
11911189
}
11921190
}
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 ){
11981193
fossil_redirect_home();
11991194
}
12001195
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);
12031198
zTktName[10] = 0;
12041199
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>
12061202
@
1207
- @ <p>By %h(m.zUser) on %s(zDate). See also:
1203
+ @ <p>By %h(pTktChng->zUser) on %s(zDate). See also:
12081204
@ <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>
12111207
}else{
12121208
@ <h2>Changes to ticket %s(zTktName)</h2>
12131209
@
1214
- @ <p>By %h(m.zUser) on %s(zDate).
1210
+ @ <p>By %h(pTktChng->zUser) on %s(zDate).
12151211
@ </p>
12161212
}
12171213
@
12181214
@ <ol>
12191215
free(zDate);
1220
- ticket_output_change_artifact(&m);
1221
- manifest_clear(&m);
1216
+ ticket_output_change_artifact(pTktChng);
1217
+ manifest_destroy(pTktChng);
12221218
style_footer();
12231219
}
12241220
12251221
12261222
/*
12271223
--- 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 @@
2626
2727
#if INTERFACE
2828
/*
2929
** Types of control files
3030
*/
31
+#define CFTYPE_ANY 0
3132
#define CFTYPE_MANIFEST 1
3233
#define CFTYPE_CLUSTER 2
3334
#define CFTYPE_CONTROL 3
3435
#define CFTYPE_WIKI 4
3536
#define CFTYPE_TICKET 5
3637
#define CFTYPE_ATTACHMENT 6
3738
#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
+
3850
3951
/*
4052
** A parsed manifest or cluster.
4153
*/
4254
struct Manifest {
4355
Blob content; /* The original content blob */
4456
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 */
4560
char *zComment; /* Decoded comment. The C card. */
4661
double rDate; /* Date and time from D card. 0.0 if no D card. */
4762
char *zUser; /* Name of the user from the U card. */
4863
char *zRepoCksum; /* MD5 checksum of the baseline content. R card. */
4964
char *zWiki; /* Text of the wiki page. W card. */
@@ -54,17 +69,12 @@
5469
char *zAttachName; /* Filename of an attachment. A card. */
5570
char *zAttachSrc; /* UUID of document being attached. A card. */
5671
char *zAttachTarget; /* Ticket or wiki that attachment applies to. A card */
5772
int nFile; /* Number of F cards */
5873
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 */
6676
int nParent; /* Number of parents. */
6777
int nParentAlloc; /* Slots allocated in azParent[] */
6878
char **azParent; /* UUIDs of parents. One for each P card argument */
6979
int nCChild; /* Number of cluster children */
7080
int nCChildAlloc; /* Number of closts allocated in azCChild[] */
@@ -87,68 +97,75 @@
8797
8898
/*
8999
** A cache of parsed manifests. This reduces the number of
90100
** calls to manifest_parse() when doing a rebuild.
91101
*/
92
-#define MX_MANIFEST_CACHE 4
102
+#define MX_MANIFEST_CACHE 6
93103
static struct {
94104
int nxAge;
95
- int aRid[MX_MANIFEST_CACHE];
96105
int aAge[MX_MANIFEST_CACHE];
97
- Manifest aLine[MX_MANIFEST_CACHE];
106
+ Manifest *apManifest[MX_MANIFEST_CACHE];
98107
} manifestCache;
99108
100109
101110
/*
102111
** Clear the memory allocated in a manifest object
103112
*/
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
+ }
112124
}
113125
114126
/*
115127
** Add an element to the manifest cache using LRU replacement.
116128
*/
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
+ }
137153
}
138154
139155
/*
140156
** Try to extract a line from the manifest cache. Return 1 if found.
141157
** Return 0 if not found.
142158
*/
143
-int manifest_cache_find(int rid, Manifest *p){
159
+static Manifest *manifest_cache_find(int rid){
144160
int i;
161
+ Manifest *p;
145162
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;
150167
}
151168
}
152169
return 0;
153170
}
154171
@@ -156,21 +173,113 @@
156173
** Clear the manifest cache.
157174
*/
158175
void manifest_cache_clear(void){
159176
int i;
160177
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]);
163180
}
164181
}
165182
memset(&manifestCache, 0, sizeof(manifestCache));
166183
}
167184
168185
#ifdef FOSSIL_DONT_VERIFY_MANIFEST_MD5SUM
169186
# define md5sum_init(X)
170187
# define md5sum_step_text(X,Y)
171188
#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
+}
172281
173282
/*
174283
** Parse a blob into a Manifest object. The Manifest object
175284
** takes over the input blob and will free it when the
176285
** Manifest object is freed. Zeros are inserted into the blob
@@ -195,56 +304,66 @@
195304
** Each card is divided into tokens by a single space character.
196305
** The first token is a single upper-case letter which is the card type.
197306
** The card type determines the other parameters to the card.
198307
** Cards must occur in lexicographical order.
199308
*/
200
-int manifest_parse(Manifest *p, Blob *pContent){
201
- int seenHeader = 0;
309
+static Manifest *manifest_parse(Blob *pContent, int rid){
310
+ Manifest *p;
202311
int seenZ = 0;
203312
int i, lineNo=0;
204
- Blob line, token, a1, a2, a3, a4;
313
+ ManifestText x;
205314
char cPrevType = 0;
315
+ char cType;
316
+ char *z;
317
+ int n;
318
+ char *zUuid;
319
+ int sz;
206320
207321
/* 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]!=' ' ){
211344
blob_reset(pContent);
212345
return 0;
213346
}
214347
348
+ /* Allocate a Manifest object to hold the parsed control artifact.
349
+ */
350
+ p = fossil_malloc( sizeof(*p) );
215351
memset(p, 0, sizeof(*p));
216352
memcpy(&p->content, pContent, sizeof(p->content));
353
+ p->rid = rid;
217354
blob_zero(pContent);
218355
pContent = &p->content;
219356
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 ){
226363
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 ){
246365
/*
247366
** A <filename> <target> ?<source>?
248367
**
249368
** Identifies an attachment to either a wiki page or a ticket.
250369
** <source> is the artifact that is the attachment. <source>
@@ -251,50 +370,60 @@
251370
** is omitted to delete an attachment. <target> is the name of
252371
** a wiki page or ticket to which that attachment is connected.
253372
*/
254373
case 'A': {
255374
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;
259380
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);
264381
defossilize(zName);
265382
if( !file_is_simple_pathname(zName) ){
266383
goto manifest_syntax_error;
267384
}
268385
defossilize(zTarget);
269
- if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE))
386
+ if( (nTarget!=UUID_SIZE || !validate16(zTarget, UUID_SIZE))
270387
&& !wiki_name_is_wellformed((const unsigned char *)zTarget) ){
271388
goto manifest_syntax_error;
272389
}
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)) ){
275391
goto manifest_syntax_error;
276392
}
277393
p->zAttachName = (char*)file_tail(zName);
278394
p->zAttachSrc = zSrc;
279395
p->zAttachTarget = zTarget;
280396
break;
281397
}
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
+
282413
283414
/*
284415
** C <comment>
285416
**
286417
** Comment text is fossil-encoded. There may be no more than
287418
** one C line. C lines are required for manifests and are
288419
** disallowed on all other control files.
289420
*/
290421
case 'C': {
291
- md5sum_step_text(blob_buffer(&line), blob_size(&line));
292422
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;
296425
defossilize(p->zComment);
297426
break;
298427
}
299428
300429
/*
@@ -303,17 +432,13 @@
303432
** The timestamp should be ISO 8601. YYYY-MM-DDtHH:MM:SS
304433
** There can be no more than 1 D line. D lines are required
305434
** for all control files except for clusters.
306435
*/
307436
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;
315440
break;
316441
}
317442
318443
/*
319444
** E <timestamp> <uuid>
@@ -323,56 +448,46 @@
323448
** The event timestamp is distinct from the D timestamp. The D
324449
** timestamp is when the artifact was created whereas the E timestamp
325450
** is when the specific event is said to occur.
326451
*/
327452
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));
336455
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;
339458
if( !validate16(p->zEventId, UUID_SIZE) ) goto manifest_syntax_error;
340459
break;
341460
}
342461
343462
/*
344
- ** F <filename> <uuid> ?<permissions>? ?<old-name>?
463
+ ** F <filename> ?<uuid>? ?<permissions>? ?<old-name>?
345464
**
346465
** Identifies a file in a manifest. Multiple F lines are
347466
** allowed in a manifest. F lines are not allowed in any
348467
** other control file. The filename and old-name are fossil-encoded.
349468
*/
350469
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;
361473
defossilize(zName);
362474
if( !file_is_simple_pathname(zName) ){
363475
goto manifest_syntax_error;
364476
}
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 ){
368485
defossilize(zPriorName);
369486
if( !file_is_simple_pathname(zPriorName) ){
370487
goto manifest_syntax_error;
371488
}
372
- }else{
373
- zPriorName = 0;
374489
}
375490
if( p->nFile>=p->nFileAlloc ){
376491
p->nFileAlloc = p->nFileAlloc*2 + 10;
377492
p->aFile = fossil_realloc(p->aFile,
378493
p->nFileAlloc*sizeof(p->aFile[0]) );
@@ -380,11 +495,10 @@
380495
i = p->nFile++;
381496
p->aFile[i].zName = zName;
382497
p->aFile[i].zUuid = zUuid;
383498
p->aFile[i].zPerm = zPerm;
384499
p->aFile[i].zPrior = zPriorName;
385
- p->aFile[i].iRename = -1;
386500
if( i>0 && strcmp(p->aFile[i-1].zName, zName)>=0 ){
387501
goto manifest_syntax_error;
388502
}
389503
break;
390504
}
@@ -397,16 +511,14 @@
397511
** value. If <value> is omitted then it is understood to be an
398512
** empty string.
399513
*/
400514
case 'J': {
401515
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 = "";
408520
defossilize(zValue);
409521
if( p->nField>=p->nFieldAlloc ){
410522
p->nFieldAlloc = p->nFieldAlloc*2 + 10;
411523
p->aField = fossil_realloc(p->aField,
412524
p->nFieldAlloc*sizeof(p->aField[0]) );
@@ -426,18 +538,14 @@
426538
**
427539
** A K-line gives the UUID for the ticket which this control file
428540
** is amending.
429541
*/
430542
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;
437543
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;
439547
break;
440548
}
441549
442550
/*
443551
** L <wikititle>
@@ -444,15 +552,13 @@
444552
**
445553
** The wiki page title is fossil-encoded. There may be no more than
446554
** one L line.
447555
*/
448556
case 'L': {
449
- md5sum_step_text(blob_buffer(&line), blob_size(&line));
450557
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;
454560
defossilize(p->zWikiTitle);
455561
if( !wiki_name_is_wellformed((const unsigned char *)p->zWikiTitle) ){
456562
goto manifest_syntax_error;
457563
}
458564
break;
@@ -463,15 +569,13 @@
463569
**
464570
** An M-line identifies another artifact by its UUID. M-lines
465571
** occur in clusters only.
466572
*/
467573
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;
473577
if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
474578
if( p->nCChild>=p->nCChildAlloc ){
475579
p->nCChildAlloc = p->nCChildAlloc*2 + 10;
476580
p->azCChild = fossil_realloc(p->azCChild
477581
, p->nCChildAlloc*sizeof(p->azCChild[0]) );
@@ -490,15 +594,12 @@
490594
** Specify one or more other artifacts where are the parents of
491595
** this artifact. The first parent is the primary parent. All
492596
** others are parents by merge.
493597
*/
494598
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;
500601
if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
501602
if( p->nParent>=p->nParentAlloc ){
502603
p->nParentAlloc = p->nParentAlloc*2 + 5;
503604
p->azParent = fossil_realloc(p->azParent,
504605
p->nParentAlloc*sizeof(char*));
@@ -514,16 +615,13 @@
514615
**
515616
** Specify the MD5 checksum over the name and content of all files
516617
** in the manifest.
517618
*/
518619
case 'R': {
519
- md5sum_step_text(blob_buffer(&line), blob_size(&line));
520620
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;
525623
if( !validate16(p->zRepoCksum, 32) ) goto manifest_syntax_error;
526624
break;
527625
}
528626
529627
/*
@@ -540,29 +638,20 @@
540638
** the tag is really a property with the given value.
541639
**
542640
** Tags are not allowed in clusters. Multiple T lines are allowed.
543641
*/
544642
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) ){
562651
/* A valid uuid */
563
- }else if( blob_size(&a2)==1 && zUuid[0]=='*' ){
652
+ }else if( sz==1 && zUuid[0]=='*' ){
564653
zUuid = 0;
565654
}else{
566655
goto manifest_syntax_error;
567656
}
568657
defossilize(zName);
@@ -593,19 +682,17 @@
593682
** Identify the user who created this control file by their
594683
** login. Only one U line is allowed. Prohibited in clusters.
595684
** If the user name is omitted, take that to be "anonymous".
596685
*/
597686
case 'U': {
598
- md5sum_step_text(blob_buffer(&line), blob_size(&line));
599687
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 ){
601690
p->zUser = "anonymous";
602691
}else{
603
- p->zUser = blob_terminate(&a1);
604692
defossilize(p->zUser);
605693
}
606
- if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
607694
break;
608695
}
609696
610697
/*
611698
** W <size>
@@ -613,26 +700,28 @@
613700
** The next <size> bytes of the file contain the text of the wiki
614701
** page. There is always an extra \n before the start of the next
615702
** record.
616703
*/
617704
case 'W': {
618
- int size;
705
+ char *zSize;
706
+ int size, c;
619707
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
+ }
624714
if( size<0 ) goto manifest_syntax_error;
625715
if( p->zWiki!=0 ) goto manifest_syntax_error;
626716
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++;
634723
break;
635724
}
636725
637726
638727
/*
@@ -645,35 +734,24 @@
645734
** This card is required for all control file types except for
646735
** Manifest. It is not required for manifest only for historical
647736
** compatibility reasons.
648737
*/
649738
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;
664742
seenZ = 1;
665743
break;
666744
}
667745
default: {
668746
goto manifest_syntax_error;
669747
}
670748
}
671749
}
672
- if( !seenHeader ) goto manifest_syntax_error;
750
+ if( x.z<x.zEnd ) goto manifest_syntax_error;
673751
674
- if( p->nFile>0 || p->zRepoCksum!=0 ){
752
+ if( p->nFile>0 || p->zRepoCksum!=0 || p->zBaseline ){
675753
if( p->nCChild>0 ) goto manifest_syntax_error;
676754
if( p->rDate<=0.0 ) goto manifest_syntax_error;
677755
if( p->nField>0 ) goto manifest_syntax_error;
678756
if( p->zTicketUuid ) goto manifest_syntax_error;
679757
if( p->zWiki ) goto manifest_syntax_error;
@@ -754,28 +832,72 @@
754832
if( p->zWikiTitle ) goto manifest_syntax_error;
755833
if( p->zTicketUuid ) goto manifest_syntax_error;
756834
p->type = CFTYPE_MANIFEST;
757835
}
758836
md5sum_init();
759
- return 1;
837
+ return p;
760838
761839
manifest_syntax_error:
762840
/*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
763841
md5sum_init();
764
- manifest_clear(p);
842
+ manifest_destroy(p);
765843
return 0;
766844
}
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
+}
767889
768890
/*
769891
** COMMAND: test-parse-manifest
770892
**
771893
** Usage: %fossil test-parse-manifest FILENAME ?N?
772894
**
773895
** Parse the manifest and discarded. Use for testing only.
774896
*/
775897
void manifest_test_parse_cmd(void){
776
- Manifest m;
898
+ Manifest *p;
777899
Blob b;
778900
int i;
779901
int n = 1;
780902
if( g.argc!=3 && g.argc!=4 ){
781903
usage("FILENAME");
@@ -784,13 +906,101 @@
784906
blob_read_from_file(&b, g.argv[2]);
785907
if( g.argc>3 ) n = atoi(g.argv[3]);
786908
for(i=0; i<n; i++){
787909
Blob b2;
788910
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
+ }
7911000
}
1001
+ return pOut;
7921002
}
7931003
7941004
/*
7951005
** Translate a filename into a filename-id (fnid). Create a new fnid
7961006
** if no previously exists.
@@ -818,14 +1028,14 @@
8181028
** Add a single entry to the mlink table. Also add the filename to
8191029
** the filename table if it is not there already.
8201030
*/
8211031
static void add_one_mlink(
8221032
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 */
8251035
const char *zFilename, /* Filename */
826
- const char *zPrior /* Previous filename. NULL if unchanged */
1036
+ const char *zPrior /* Previous filename. NULL if unchanged */
8271037
){
8281038
int fnid, pfnid, pid, fid;
8291039
static Stmt s1;
8301040
8311041
fnid = filename_to_fnid(zFilename);
@@ -832,16 +1042,16 @@
8321042
if( zPrior==0 ){
8331043
pfnid = 0;
8341044
}else{
8351045
pfnid = filename_to_fnid(zPrior);
8361046
}
837
- if( zFromUuid==0 ){
1047
+ if( zFromUuid==0 || zFromUuid[0]==0 ){
8381048
pid = 0;
8391049
}else{
8401050
pid = uuid_to_rid(zFromUuid, 1);
8411051
}
842
- if( zToUuid==0 ){
1052
+ if( zToUuid==0 || zToUuid[0]==0 ){
8431053
fid = 0;
8441054
}else{
8451055
fid = uuid_to_rid(zToUuid, 1);
8461056
}
8471057
db_static_prepare(&s1,
@@ -858,33 +1068,83 @@
8581068
content_deltify(pid, fid, 0);
8591069
}
8601070
}
8611071
8621072
/*
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.
8671080
*/
868
-static int find_file_in_manifest(Manifest *p, const char *zName){
1081
+static ManifestFile *manifest_file_seek_base(Manifest *p, const char *zName){
8691082
int lwr, upr;
8701083
int c;
8711084
int i;
8721085
lwr = 0;
8731086
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
+ }
8741097
while( lwr<=upr ){
8751098
i = (lwr+upr)/2;
8761099
c = strcmp(p->aFile[i].zName, zName);
8771100
if( c<0 ){
8781101
lwr = i+1;
8791102
}else if( c>0 ){
8801103
upr = i-1;
8811104
}else{
882
- return i;
1105
+ p->iFile = i;
1106
+ return &p->aFile[i];
8831107
}
8841108
}
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
+ }
8861146
}
8871147
8881148
/*
8891149
** Add mlink table entries associated with manifest cid. The
8901150
** parent manifest is pid.
@@ -895,89 +1155,70 @@
8951155
** Deleted files have mlink.fid=0.
8961156
** Added files have mlink.pid=0.
8971157
** Edited files have both mlink.pid!=0 and mlink.fid!=0
8981158
*/
8991159
static void add_mlink(int pid, Manifest *pParent, int cid, Manifest *pChild){
900
- Manifest other;
9011160
Blob otherContent;
9021161
int otherRid;
903
- int i, j;
1162
+ int i, rc;
1163
+ ManifestFile *pChildFile, *pParentFile;
1164
+ Manifest **ppOther;
1165
+ static Stmt eq;
9041166
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
+
9081173
assert( pParent==0 || pChild==0 );
9091174
if( pParent==0 ){
910
- pParent = &other;
1175
+ ppOther = &pParent;
9111176
otherRid = pid;
9121177
}else{
913
- pChild = &other;
1178
+ ppOther = &pChild;
9141179
otherRid = cid;
9151180
}
916
- if( manifest_cache_find(otherRid, &other)==0 ){
1181
+ if( (*ppOther = manifest_cache_find(otherRid))==0 ){
9171182
content_get(otherRid, &otherContent);
9181183
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);
9791220
}
9801221
9811222
/*
9821223
** True if manifest_crosslink_begin() has been called but
9831224
** manifest_crosslink_end() is still pending.
@@ -1114,40 +1355,40 @@
11141355
** of the routine, "manifest_crosslink", and the name of this source
11151356
** file, is a legacy of its original use.
11161357
*/
11171358
int manifest_crosslink(int rid, Blob *pContent){
11181359
int i;
1119
- Manifest m;
1360
+ Manifest *p;
11201361
Stmt q;
11211362
int parentid = 0;
11221363
1123
- if( manifest_cache_find(rid, &m) ){
1364
+ if( (p = manifest_cache_find(rid))!=0 ){
11241365
blob_reset(pContent);
1125
- }else if( manifest_parse(&m, pContent)==0 ){
1366
+ }else if( (p = manifest_parse(pContent, rid))==0 ){
11261367
return 0;
11271368
}
1128
- if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){
1129
- manifest_clear(&m);
1369
+ if( g.xlinkClusterOnly && p->type!=CFTYPE_CLUSTER ){
1370
+ manifest_destroy(p);
11301371
return 0;
11311372
}
11321373
db_begin_transaction();
1133
- if( m.type==CFTYPE_MANIFEST ){
1374
+ if( p->type==CFTYPE_MANIFEST ){
11341375
if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
11351376
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);
11381379
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);
11401381
if( i==0 ){
1141
- add_mlink(pid, 0, rid, &m);
1382
+ add_mlink(pid, 0, rid, p);
11421383
parentid = pid;
11431384
}
11441385
}
11451386
db_prepare(&q, "SELECT cid FROM plink WHERE pid=%d AND isprim", rid);
11461387
while( db_step(&q)==SQLITE_ROW ){
11471388
int cid = db_column_int(&q, 0);
1148
- add_mlink(rid, &m, cid, 0);
1389
+ add_mlink(rid, p, cid, 0);
11491390
}
11501391
db_finalize(&q);
11511392
db_multi_exec(
11521393
"REPLACE INTO event(type,mtime,objid,user,comment,"
11531394
"bgcolor,euser,ecomment)"
@@ -1158,118 +1399,130 @@
11581399
" ),"
11591400
" %d,%Q,%Q,"
11601401
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0),"
11611402
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
11621403
" (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,
11651406
TAG_BGCOLOR, rid,
11661407
TAG_USER, rid,
11671408
TAG_COMMENT, rid
11681409
);
11691410
zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
11701411
" 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);
11721413
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
+ }
11731426
}
11741427
}
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++){
11781431
int mid;
1179
- mid = uuid_to_rid(m.azCChild[i], 1);
1432
+ mid = uuid_to_rid(p->azCChild[i], 1);
11801433
if( mid>0 ){
11811434
db_multi_exec("DELETE FROM unclustered WHERE rid=%d", mid);
11821435
}
11831436
}
11841437
}
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
11881441
){
1189
- for(i=0; i<m.nTag; i++){
1442
+ for(i=0; i<p->nTag; i++){
11901443
int tid;
11911444
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);
11941447
}else{
11951448
tid = rid;
11961449
}
11971450
if( tid ){
1198
- switch( m.aTag[i].zName[0] ){
1451
+ switch( p->aTag[i].zName[0] ){
11991452
case '-': type = 0; break; /* Cancel prior occurances */
12001453
case '+': type = 1; break; /* Apply to target only */
12011454
case '*': type = 2; break; /* Propagate to descendants */
12021455
default:
1203
- fossil_fatal("unknown tag type in manifest: %s", m.aTag);
1456
+ fossil_fatal("unknown tag type in manifest: %s", p->aTag);
12041457
return 0;
12051458
}
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);
12081461
}
12091462
}
12101463
if( parentid ){
12111464
tag_propagate_all(parentid);
12121465
}
12131466
}
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);
12161469
int tagid = tag_findid(zTag, 1);
12171470
int prior;
12181471
char *zComment;
12191472
int nWiki;
12201473
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);
12231476
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);
12251478
free(zTag);
12261479
prior = db_int(0,
12271480
"SELECT rid FROM tagxref"
12281481
" WHERE tagid=%d AND mtime<%.17g"
12291482
" ORDER BY mtime DESC",
1230
- tagid, m.rDate
1483
+ tagid, p->rDate
12311484
);
12321485
if( prior ){
12331486
content_deltify(prior, rid, 0);
12341487
}
12351488
if( nWiki>0 ){
1236
- zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
1489
+ zComment = mprintf("Changes to wiki page [%h]", p->zWikiTitle);
12371490
}else{
1238
- zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle);
1491
+ zComment = mprintf("Deleted wiki page [%h]", p->zWikiTitle);
12391492
}
12401493
db_multi_exec(
12411494
"REPLACE INTO event(type,mtime,objid,user,comment,"
12421495
" bgcolor,euser,ecomment)"
12431496
"VALUES('w',%.17g,%d,%Q,%Q,"
12441497
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
12451498
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
12461499
" (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
1247
- m.rDate, rid, m.zUser, zComment,
1500
+ p->rDate, rid, p->zUser, zComment,
12481501
TAG_BGCOLOR, rid,
12491502
TAG_BGCOLOR, rid,
12501503
TAG_USER, rid,
12511504
TAG_COMMENT, rid
12521505
);
12531506
free(zComment);
12541507
}
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);
12571510
int tagid = tag_findid(zTag, 1);
12581511
int prior, subsequent;
12591512
int nWiki;
12601513
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);
12631516
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);
12651518
free(zTag);
12661519
prior = db_int(0,
12671520
"SELECT rid FROM tagxref"
12681521
" WHERE tagid=%d AND mtime<%.17g"
12691522
" ORDER BY mtime DESC",
1270
- tagid, m.rDate
1523
+ tagid, p->rDate
12711524
);
12721525
if( prior ){
12731526
content_deltify(prior, rid, 0);
12741527
db_multi_exec(
12751528
"DELETE FROM event"
@@ -1281,108 +1534,87 @@
12811534
}
12821535
subsequent = db_int(0,
12831536
"SELECT rid FROM tagxref"
12841537
" WHERE tagid=%d AND mtime>%.17g"
12851538
" ORDER BY mtime",
1286
- tagid, m.rDate
1539
+ tagid, p->rDate
12871540
);
12881541
if( subsequent ){
12891542
content_deltify(rid, subsequent, 0);
12901543
}else{
12911544
db_multi_exec(
12921545
"REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)"
12931546
"VALUES('e',%.17g,%d,%d,%Q,%Q,"
12941547
" (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,
12961549
TAG_BGCOLOR, rid
12971550
);
12981551
}
12991552
}
1300
- if( m.type==CFTYPE_TICKET ){
1553
+ if( p->type==CFTYPE_TICKET ){
13011554
char *zTag;
13021555
13031556
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);
13061559
free(zTag);
13071560
db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
1308
- m.zTicketUuid);
1561
+ p->zTicketUuid);
13091562
}
1310
- if( m.type==CFTYPE_ATTACHMENT ){
1563
+ if( p->type==CFTYPE_ATTACHMENT ){
13111564
db_multi_exec(
13121565
"INSERT INTO attachment(attachid, mtime, src, target,"
13131566
"filename, comment, user)"
13141567
"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
13171570
);
13181571
db_multi_exec(
13191572
"UPDATE attachment SET isLatest = (mtime=="
13201573
"(SELECT max(mtime) FROM attachment"
13211574
" WHERE target=%Q AND filename=%Q))"
13221575
" 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
13251578
);
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)
13281581
){
13291582
char *zComment;
1330
- if( m.zAttachSrc && m.zAttachSrc[0] ){
1583
+ if( p->zAttachSrc && p->zAttachSrc[0] ){
13311584
zComment = mprintf("Add attachment \"%h\" to wiki page [%h]",
1332
- m.zAttachName, m.zAttachTarget);
1585
+ p->zAttachName, p->zAttachTarget);
13331586
}else{
13341587
zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
1335
- m.zAttachName, m.zAttachTarget);
1588
+ p->zAttachName, p->zAttachTarget);
13361589
}
13371590
db_multi_exec(
13381591
"REPLACE INTO event(type,mtime,objid,user,comment)"
13391592
"VALUES('w',%.17g,%d,%Q,%Q)",
1340
- m.rDate, rid, m.zUser, zComment
1593
+ p->rDate, rid, p->zUser, zComment
13411594
);
13421595
free(zComment);
13431596
}else{
13441597
char *zComment;
1345
- if( m.zAttachSrc && m.zAttachSrc[0] ){
1598
+ if( p->zAttachSrc && p->zAttachSrc[0] ){
13461599
zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]",
1347
- m.zAttachName, m.zAttachTarget);
1600
+ p->zAttachName, p->zAttachTarget);
13481601
}else{
13491602
zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
1350
- m.zAttachName, m.zAttachTarget);
1603
+ p->zAttachName, p->zAttachTarget);
13511604
}
13521605
db_multi_exec(
13531606
"REPLACE INTO event(type,mtime,objid,user,comment)"
13541607
"VALUES('t',%.17g,%d,%Q,%Q)",
1355
- m.rDate, rid, m.zUser, zComment
1608
+ p->rDate, rid, p->zUser, zComment
13561609
);
13571610
free(zComment);
13581611
}
13591612
}
13601613
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);
13631616
}else{
1364
- manifest_clear(&m);
1617
+ manifest_destroy(p);
13651618
}
13661619
return 1;
13671620
}
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
-}
13891621
--- 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 @@
4141
uint32 bits[2];
4242
unsigned char in[64];
4343
};
4444
typedef struct Context MD5Context;
4545
46
+#if defined(__i386__) || defined(__x86_64__) || defined(_WIN32)
47
+# define byteReverse(A,B)
48
+#else
4649
/*
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.
4852
*/
4953
static void byteReverse (unsigned char *buf, unsigned longs){
5054
uint32 t;
5155
do {
5256
t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
@@ -53,10 +57,12 @@
5357
((unsigned)buf[1]<<8 | buf[0]);
5458
*(uint32 *)buf = t;
5559
buf += 4;
5660
} while (--longs);
5761
}
62
+#endif
63
+
5864
/* The four core functions - F1 is optimized somewhat */
5965
6066
/* #define F1(x, y, z) (x & y | ~x & z) */
6167
#define F1(x, y, z) (z ^ (x & (y ^ z)))
6268
#define F2(x, y, z) F1(z, x, y)
@@ -314,10 +320,30 @@
314320
** Add the content of a blob to the incremental MD5 checksum.
315321
*/
316322
void md5sum_step_blob(Blob *p){
317323
md5sum_step_text(blob_buffer(p), blob_size(p));
318324
}
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
+}
319345
320346
/*
321347
** Finish the incremental MD5 checksum. Store the result in blob pOut
322348
** if pOut!=0. Also return a pointer to the result.
323349
**
324350
--- 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 @@
8282
static int ttyOutput; /* Do progress output */
8383
static Bag bagDone; /* Bag of records rebuilt */
8484
8585
static char *zFNameFormat; /* Format string for filenames on deconstruct */
8686
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
+
87102
88103
/*
89104
** Called after each artifact is processed
90105
*/
91106
static void rebuild_step_done(rid){
@@ -92,12 +107,11 @@
92107
/* assert( bag_find(&bagDone, rid)==0 ); */
93108
bag_insert(&bagDone, rid);
94109
if( ttyOutput ){
95110
processCnt++;
96111
if (!g.fQuiet) {
97
- printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize));
98
- fflush(stdout);
112
+ percent_complete((processCnt*1000)/totalSize);
99113
}
100114
}
101115
}
102116
103117
/*
@@ -167,20 +181,21 @@
167181
rebuild_step_done(rid);
168182
169183
/* Call all children recursively */
170184
rid = 0;
171185
for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){
172
- Stmt q2;
186
+ static Stmt q2;
173187
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);
175190
if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){
176191
Blob delta, next;
177192
db_ephemeral_blob(&q2, 0, &delta);
178193
blob_uncompress(&delta, &delta);
179194
blob_delta_apply(pBase, &delta, &next);
180195
blob_reset(&delta);
181
- db_finalize(&q2);
196
+ db_reset(&q2);
182197
if( i<nChild ){
183198
rebuild_step(cid, sz, &next);
184199
}else{
185200
/* Tail recursion */
186201
rid = cid;
@@ -187,11 +202,11 @@
187202
size = sz;
188203
blob_reset(pBase);
189204
*pBase = next;
190205
}
191206
}else{
192
- db_finalize(&q2);
207
+ db_reset(&q2);
193208
blob_reset(pBase);
194209
}
195210
}
196211
bag_clear(&children);
197212
}
@@ -237,12 +252,11 @@
237252
238253
bag_init(&bagDone);
239254
ttyOutput = doOut;
240255
processCnt = 0;
241256
if (!g.fQuiet) {
242
- printf("0 (0%%)...\r");
243
- fflush(stdout);
257
+ percent_complete(0);
244258
}
245259
db_multi_exec(zSchemaUpdates);
246260
for(;;){
247261
zTable = db_text(0,
248262
"SELECT name FROM sqlite_master /*scan*/"
249263
--- 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 @@
11
/*
2
-** This implementation of SHA1 is adapted from the example implementation
3
-** contained in RFC-3174.
2
+** This implementation of SHA1.
43
*/
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
164
#include <sys/types.h>
175
#include "config.h"
186
#include "sha1.h"
197
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
+ }
413186
}
414187
415188
416189
/*
417190
** Convert a digest into base-16. digest should be declared as
@@ -441,18 +214,18 @@
441214
/*
442215
** Add more text to the incremental SHA1 checksum.
443216
*/
444217
void sha1sum_step_text(const char *zText, int nBytes){
445218
if( !incrInit ){
446
- SHA1Reset(&incrCtx);
219
+ SHA1Init(&incrCtx);
447220
incrInit = 1;
448221
}
449222
if( nBytes<=0 ){
450223
if( nBytes==0 ) return;
451224
nBytes = strlen(zText);
452225
}
453
- SHA1Input(&incrCtx, (unsigned char*)zText, nBytes);
226
+ SHA1Update(&incrCtx, (unsigned char*)zText, nBytes);
454227
}
455228
456229
/*
457230
** Add the content of a blob to the incremental SHA1 checksum.
458231
*/
@@ -470,11 +243,11 @@
470243
*/
471244
char *sha1sum_finish(Blob *pOut){
472245
unsigned char zResult[20];
473246
static char zOut[41];
474247
sha1sum_step_text(0,0);
475
- SHA1Result(&incrCtx, zResult);
248
+ SHA1Final(&incrCtx, zResult);
476249
incrInit = 0;
477250
DigestToBase16(zResult, zOut);
478251
if( pOut ){
479252
blob_zero(pOut);
480253
blob_append(pOut, zOut, 40);
@@ -497,21 +270,21 @@
497270
498271
in = fopen(zFilename,"rb");
499272
if( in==0 ){
500273
return 1;
501274
}
502
- SHA1Reset(&ctx);
275
+ SHA1Init(&ctx);
503276
for(;;){
504277
int n;
505278
n = fread(zBuf, 1, sizeof(zBuf), in);
506279
if( n<=0 ) break;
507
- SHA1Input(&ctx, (unsigned char*)zBuf, (unsigned)n);
280
+ SHA1Update(&ctx, (unsigned char*)zBuf, (unsigned)n);
508281
}
509282
fclose(in);
510283
blob_zero(pCksum);
511284
blob_resize(pCksum, 40);
512
- SHA1Result(&ctx, zResult);
285
+ SHA1Final(&ctx, zResult);
513286
DigestToBase16(zResult, blob_buffer(pCksum));
514287
return 0;
515288
}
516289
517290
/*
@@ -523,19 +296,19 @@
523296
*/
524297
int sha1sum_blob(const Blob *pIn, Blob *pCksum){
525298
SHA1Context ctx;
526299
unsigned char zResult[20];
527300
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));
530303
if( pIn==pCksum ){
531304
blob_reset(pCksum);
532305
}else{
533306
blob_zero(pCksum);
534307
}
535308
blob_resize(pCksum, 40);
536
- SHA1Result(&ctx, zResult);
309
+ SHA1Final(&ctx, zResult);
537310
DigestToBase16(zResult, blob_buffer(pCksum));
538311
return 0;
539312
}
540313
541314
/*
@@ -545,13 +318,13 @@
545318
char *sha1sum(const char *zIn){
546319
SHA1Context ctx;
547320
unsigned char zResult[20];
548321
char zDigest[41];
549322
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);
553326
DigestToBase16(zResult, zDigest);
554327
return mprintf("%s", zDigest);
555328
}
556329
557330
/*
@@ -575,11 +348,11 @@
575348
static char *zProjectId = 0;
576349
SHA1Context ctx;
577350
unsigned char zResult[20];
578351
char zDigest[41];
579352
580
- SHA1Reset(&ctx);
353
+ SHA1Init(&ctx);
581354
if( zProjectId==0 ){
582355
zProjectId = db_get("project-code", 0);
583356
584357
/* On the first xfer request of a clone, the project-code is not yet
585358
** known. Use the cleartext password, since that is all we have.
@@ -586,16 +359,16 @@
586359
*/
587360
if( zProjectId==0 ){
588361
return mprintf("%s", zPw);
589362
}
590363
}
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);
597370
DigestToBase16(zResult, zDigest);
598371
return mprintf("%s", zDigest);
599372
}
600373
601374
/*
602375
--- 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 @@
214214
*/
215215
void ticket_rebuild_entry(const char *zTktUuid){
216216
char *zTag = mprintf("tkt-%s", zTktUuid);
217217
int tagid = tag_findid(zTag, 1);
218218
Stmt q;
219
- Manifest manifest;
220
- Blob content;
219
+ Manifest *pTicket;
221220
int createFlag = 1;
222221
223222
db_multi_exec(
224223
"DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
225224
);
226225
db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
227226
while( db_step(&q)==SQLITE_ROW ){
228227
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
+ }
234234
createFlag = 0;
235235
}
236236
db_finalize(&q);
237237
}
238238
@@ -753,12 +753,11 @@
753753
" AND blob.rid=attachid"
754754
" ORDER BY 1 DESC",
755755
tagid, tagid
756756
);
757757
while( db_step(&q)==SQLITE_ROW ){
758
- Blob content;
759
- Manifest m;
758
+ Manifest *pTicket;
760759
char zShort[12];
761760
const char *zDate = db_column_text(&q, 0);
762761
int rid = db_column_int(&q, 1);
763762
const char *zChngUuid = db_column_text(&q, 2);
764763
const char *zFile = db_column_text(&q, 4);
@@ -777,22 +776,22 @@
777776
@ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
778777
@ (rid %d(rid)) by
779778
hyperlink_to_user(zUser,zDate," on");
780779
hyperlink_to_date(zDate, ".</p>");
781780
}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 ){
784783
@
785784
@ <p>Ticket change
786785
@ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
787786
@ (rid %d(rid)) by
788
- hyperlink_to_user(m.zUser,zDate," on");
787
+ hyperlink_to_user(pTicket->zUser,zDate," on");
789788
hyperlink_to_date(zDate, ":");
790789
@ </p>
791
- ticket_output_change_artifact(&m);
790
+ ticket_output_change_artifact(pTicket);
792791
}
793
- manifest_clear(&m);
792
+ manifest_destroy(pTicket);
794793
}
795794
}
796795
db_finalize(&q);
797796
style_footer();
798797
}
799798
--- 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 @@
279279
db_end_transaction(1); /* With --nochange, rollback changes */
280280
}else{
281281
if( g.argc<=3 ){
282282
/* All files updated. Shift the current checkout to the target. */
283283
db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
284
+ checkout_set_all_exe(vid);
284285
manifest_to_disk(tid);
285286
db_lset_int("checkout", tid);
286287
}else{
287288
/* A subset of files have been checked out. Keep the current
288289
** checkout unchanged. */
@@ -302,13 +303,13 @@
302303
const char *revision, /* The checkin containing the file */
303304
const char *file, /* Full treename of the file */
304305
Blob *content, /* Put the content here */
305306
int errCode /* Error code if file not found. Panic if 0. */
306307
){
307
- Blob mfile;
308
- Manifest m;
309
- int i, rid=0;
308
+ Manifest *pManifest;
309
+ ManifestFile *pFile;
310
+ int rid=0;
310311
311312
if( revision ){
312313
rid = name_to_rid(revision);
313314
}else{
314315
rid = db_lget_int("checkout", 0);
@@ -315,21 +316,22 @@
315316
}
316317
if( !is_a_version(rid) ){
317318
if( errCode>0 ) return errCode;
318319
fossil_fatal("no such checkin: %s", revision);
319320
}
320
- content_get(rid, &mfile);
321
+ pManifest = manifest_get(rid, CFTYPE_MANIFEST);
321322
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);
327329
return content_get(rid, content);
328330
}
329331
}
330
- manifest_clear(&m);
332
+ manifest_destroy(pManifest);
331333
if( errCode<=0 ){
332334
fossil_fatal("file %s does not exist in checkin: %s", file, revision);
333335
}
334336
}else if( errCode<=0 ){
335337
fossil_panic("could not parse manifest for checkin: %s", revision);
@@ -386,14 +388,15 @@
386388
}else{
387389
int vid;
388390
vid = db_lget_int("checkout", 0);
389391
vfile_check_signature(vid, 0);
390392
db_multi_exec(
393
+ "DELETE FROM vmerge;"
391394
"INSERT INTO torevert "
392395
"SELECT pathname"
393396
" FROM vfile "
394
- " WHERE chnged OR deleted OR rid=0 OR pathname!=origname"
397
+ " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
395398
);
396399
}
397400
blob_zero(&record);
398401
db_prepare(&q, "SELECT name FROM torevert");
399402
while( db_step(&q)==SQLITE_ROW ){
400403
--- 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 @@
8585
}
8686
}
8787
}
8888
8989
/*
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.
9691
*/
97
-void vfile_build(int vid, Blob *p){
92
+void vfile_build(int vid){
9893
int rid;
99
- char *zName, *zUuid;
10094
Stmt ins;
101
- Blob line, token, name, uuid;
102
- int seenHeader = 0;
95
+ Manifest *p;
96
+ ManifestFile *pFile;
97
+
10398
db_begin_transaction();
10499
vfile_verify_not_phantom(vid, 0, 0);
100
+ p = manifest_get(vid, CFTYPE_MANIFEST);
101
+ if( p==0 ) return;
105102
db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
106103
db_prepare(&ins,
107104
"INSERT INTO vfile(vid,rid,mrid,pathname) "
108105
" VALUES(:vid,:id,:id,:name)");
109106
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);
135115
}
136116
db_finalize(&ins);
117
+ manifest_destroy(p);
137118
db_end_transaction(0);
138119
}
139120
140121
/*
141122
** Check the file signature of the disk image for every VFILE of vid.
@@ -369,10 +350,11 @@
369350
}
370351
fseek(in, 0L, SEEK_END);
371352
sprintf(zBuf, " %ld\n", ftell(in));
372353
fseek(in, 0L, SEEK_SET);
373354
md5sum_step_text(zBuf, -1);
355
+ /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/
374356
for(;;){
375357
int n;
376358
n = fread(zBuf, 1, sizeof(zBuf), in);
377359
if( n<=0 ) break;
378360
md5sum_step_text(zBuf, n);
@@ -422,10 +404,11 @@
422404
int rid = db_column_int(&q, 1);
423405
md5sum_step_text(zName, -1);
424406
content_get(rid, &file);
425407
sprintf(zBuf, " %d\n", blob_size(&file));
426408
md5sum_step_text(zBuf, -1);
409
+ /*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/
427410
md5sum_step_blob(&file);
428411
blob_reset(&file);
429412
}
430413
db_finalize(&q);
431414
md5sum_finish(pOut);
@@ -438,41 +421,43 @@
438421
**
439422
** If pManOut is not NULL then fill it with the checksum found in the
440423
** "R" card near the end of the manifest.
441424
*/
442425
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;
446430
char zBuf[100];
447431
448432
blob_zero(pOut);
449433
if( pManOut ){
450434
blob_zero(pManOut);
451435
}
452436
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 ){
455439
fossil_panic("manifest file (%d) is malformed", vid);
456440
}
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);
460445
content_get(fid, &file);
461446
sprintf(zBuf, " %d\n", blob_size(&file));
462447
md5sum_step_text(zBuf, -1);
463448
md5sum_step_blob(&file);
464449
blob_reset(&file);
465450
}
466451
if( pManOut ){
467
- if( m.zRepoCksum ){
468
- blob_append(pManOut, m.zRepoCksum, -1);
452
+ if( pManifest->zRepoCksum ){
453
+ blob_append(pManOut, pManifest->zRepoCksum, -1);
469454
}else{
470455
blob_zero(pManOut);
471456
}
472457
}
473
- manifest_clear(&m);
458
+ manifest_destroy(pManifest);
474459
md5sum_finish(pOut);
475460
}
476461
477462
/*
478463
** COMMAND: test-agg-cksum
479464
--- 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 @@
127127
void wiki_page(void){
128128
char *zTag;
129129
int rid = 0;
130130
int isSandbox;
131131
Blob wiki;
132
- Manifest m;
132
+ Manifest *pWiki = 0;
133133
const char *zPageName;
134134
char *zBody = mprintf("%s","<i>Empty Page</i>");
135135
Stmt q;
136136
int cnt = 0;
137137
@@ -179,20 +179,14 @@
179179
"SELECT rid FROM tagxref"
180180
" WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
181181
" ORDER BY mtime DESC", zTag
182182
);
183183
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;
194188
}
195189
}
196190
if( !g.isHome ){
197191
if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){
198192
style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
@@ -249,13 +243,11 @@
249243
if( cnt ){
250244
@ </ul>
251245
}
252246
db_finalize(&q);
253247
254
- if( !isSandbox ){
255
- manifest_clear(&m);
256
- }
248
+ manifest_destroy(pWiki);
257249
style_footer();
258250
}
259251
260252
/*
261253
** WEBPAGE: wikiedit
@@ -264,11 +256,11 @@
264256
void wikiedit_page(void){
265257
char *zTag;
266258
int rid = 0;
267259
int isSandbox;
268260
Blob wiki;
269
- Manifest m;
261
+ Manifest *pWiki = 0;
270262
const char *zPageName;
271263
char *zHtmlPageName;
272264
int n;
273265
const char *z;
274266
char *zBody = (char*)P("w");
@@ -298,19 +290,12 @@
298290
free(zTag);
299291
if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){
300292
login_needed();
301293
return;
302294
}
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;
312297
}
313298
}
314299
if( P("submit")!=0 && zBody!=0 ){
315300
char *zDate;
316301
Blob cksum;
@@ -377,13 +362,11 @@
377362
@ <br />
378363
@ <input type="submit" name="preview" value="Preview Your Changes" />
379364
@ <input type="submit" name="submit" value="Apply These Changes" />
380365
@ <input type="submit" name="cancel" value="Cancel" />
381366
@ </div></form>
382
- if( !isSandbox ){
383
- manifest_clear(&m);
384
- }
367
+ manifest_destroy(pWiki);
385368
style_footer();
386369
}
387370
388371
/*
389372
** WEBPAGE: wikinew
@@ -477,27 +460,25 @@
477460
if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){
478461
char *zDate;
479462
Blob cksum;
480463
int nrid;
481464
Blob body;
482
- Blob content;
483465
Blob wiki;
484
- Manifest m;
466
+ Manifest *pWiki = 0;
485467
486468
blob_zero(&body);
487469
if( isSandbox ){
488470
blob_appendf(&body, db_get("sandbox",""));
489471
appendRemark(&body);
490472
db_set("sandbox", blob_str(&body), 0);
491473
}else{
492474
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);
497479
}
498
- manifest_clear(&m);
499480
blob_zero(&wiki);
500481
db_begin_transaction();
501482
zDate = db_text(0, "SELECT datetime('now')");
502483
zDate[10] = 'T';
503484
blob_appendf(&wiki, "D %s\n", zDate);
@@ -612,12 +593,11 @@
612593
*/
613594
void wdiff_page(void){
614595
char *zTitle;
615596
int rid1, rid2;
616597
const char *zPageName;
617
- Blob content1, content2;
618
- Manifest m1, m2;
598
+ Manifest *pW1, *pW2 = 0;
619599
Blob w1, w2, d;
620600
621601
login_check_credentials();
622602
rid1 = atoi(PD("a","0"));
623603
if( !g.okHistory ){ login_needed(); return; }
@@ -635,27 +615,24 @@
635615
" WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)"
636616
" ORDER BY event.mtime DESC LIMIT 1",
637617
zPageName, rid1
638618
);
639619
}
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);
644623
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);
651626
}
652627
blob_zero(&d);
653628
text_diff(&w2, &w1, &d, 5, 1);
654629
@ <pre>
655630
@ %h(blob_str(&d))
656631
@ </pre>
632
+ manifest_destroy(pW1);
633
+ manifest_destroy(pW2);
657634
style_footer();
658635
}
659636
660637
/*
661638
** WEBPAGE: wcontent
@@ -914,30 +891,26 @@
914891
}
915892
916893
if( strncmp(g.argv[2],"export",n)==0 ){
917894
char const *zPageName; /* Name of the wiki page to export */
918895
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
+
923901
if( (g.argc!=4) && (g.argc!=5) ){
924902
usage("export PAGENAME ?FILE?");
925903
}
926904
zPageName = g.argv[3];
927905
rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
928906
" WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
929907
" ORDER BY x.mtime DESC LIMIT 1",
930908
zPageName
931909
);
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;
939912
}
940913
if( zBody==0 ){
941914
fossil_fatal("wiki page [%s] not found",zPageName);
942915
}
943916
for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
@@ -955,12 +928,13 @@
955928
fossil_fatal("wiki export could not open output file for writing.");
956929
}
957930
fprintf(zF,"%.*s\n", i, zBody);
958931
if( doClose ) fclose(zF);
959932
}else{
960
- printf("%.*s\n", i, zBody);
933
+ printf("%.*s\n", i, zBody);
961934
}
935
+ manifest_destroy(pWiki);
962936
return;
963937
}else
964938
if( strncmp(g.argv[2],"commit",n)==0
965939
|| strncmp(g.argv[2],"create",n)==0 ){
966940
char *zPageName;
967941
--- 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 @@
313313
** politely expands into a subdir instead of filling your current dir
314314
** with source files. For example, pass a UUID or "ProjectName".
315315
**
316316
*/
317317
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;
321321
Blob filename;
322322
int nPrefix;
323323
324324
content_get(rid, &mfile);
325325
if( blob_size(&mfile)==0 ){
326326
blob_zero(pZip);
327327
return;
328328
}
329
- blob_zero(&file);
330329
blob_zero(&hash);
331
- blob_copy(&file, &mfile);
332330
blob_zero(&filename);
333331
zip_open();
334332
335333
if( zDir && zDir[0] ){
336334
blob_appendf(&filename, "%s/", zDir);
337335
}
338336
nPrefix = blob_size(&filename);
339337
340
- if( manifest_parse(&m, &mfile) ){
338
+ pManifest = manifest_get(rid, CFTYPE_MANIFEST);
339
+ if( pManifest ){
341340
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);
357359
if( fid ){
358360
content_get(fid, &file);
359361
blob_resize(&filename, nPrefix);
360
- blob_append(&filename, m.aFile[i].zName, -1);
362
+ blob_append(&filename, pFile->zName, -1);
361363
zName = blob_str(&filename);
362364
zip_add_folders(zName);
363365
zip_add_file(zName, &file);
364366
blob_reset(&file);
365367
}
366368
}
367
- manifest_clear(&m);
368369
}else{
369370
blob_reset(&mfile);
370
- blob_reset(&file);
371371
}
372
+ manifest_destroy(pManifest);
372373
blob_reset(&filename);
373374
zip_close(pZip);
374375
}
375376
376377
/*
377378
--- 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
--- www/fileformat.wiki
+++ www/fileformat.wiki
@@ -95,19 +95,28 @@
9595
may contain no additional text or data beyond what is described here.
9696
9797
Allowed cards in the manifest are as follows:
9898
9999
<blockquote>
100
+<b>B</b> <i>baseline-manifest</i><br>
100101
<b>C</b> <i>checkin-comment</i><br>
101102
<b>D</b> <i>time-and-date-stamp</i><br>
102103
<b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br>
103104
<b>P</b> <i>SHA1-hash</i>+<br>
104105
<b>R</b> <i>repository-checksum</i><br>
105106
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name <b>*</b> ?value?</i><br>
106107
<b>U</b> <i>user-login</i><br>
107108
<b>Z</b> <i>manifest-checksum</i>
108109
</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.
109118
110119
A manifest must have exactly one C-card. The sole argument to
111120
the C-card is a check-in comment that describes the check-in that
112121
the manifest defines. The check-in comment is text. The following
113122
escape sequences are applied to the text:
@@ -125,26 +134,29 @@
125134
126135
<blockquote>
127136
<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>
128137
</blockquote>
129138
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.
133141
The first argument
134142
is the pathname of the file in the check-in relative to the root
135143
of the project file hierarchy. No ".." or "." directories are allowed
136144
within the filename. Space characters are escaped as in C-card
137145
comment text. Backslash characters and newlines are not allowed
138146
within filenames. The directory separator character is a forward
139147
slash (ASCII 0x2F). The second argument to the F-card is the
140148
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
142153
permissions associated with the file. The only special code currently
143154
defined is "x" which means that the file is executable. All files are
144155
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.
146158
The optional 4th argument is the name of the same file as it existed in
147159
the parent check-in. If the name of the file is unchanged from its
148160
parent, then the 4th argument is omitted.
149161
150162
A manifest has zero or one P-cards. Most manifests have one P-card.
@@ -504,10 +516,20 @@
504516
<td>&nbsp;</td>
505517
<td>&nbsp;</td>
506518
<td align=center><b>X</b></td>
507519
<td>&nbsp;</td>
508520
</tr>
521
+<tr>
522
+<td><b>B</b> <i>baseline</i></td>
523
+<td align=center><b>X</b></td>
524
+<td>&nbsp;</td>
525
+<td>&nbsp;</td>
526
+<td>&nbsp;</td>
527
+<td>&nbsp;</td>
528
+<td>&nbsp;</td>
529
+<td>&nbsp;</td>
530
+</tr>
509531
<tr>
510532
<td><b>C</b> <i>comment-text</i></td>
511533
<td align=center><b>X</b></td>
512534
<td>&nbsp;</td>
513535
<td>&nbsp;</td>
514536
--- 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>&nbsp;</td>
505 <td>&nbsp;</td>
506 <td align=center><b>X</b></td>
507 <td>&nbsp;</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>&nbsp;</td>
513 <td>&nbsp;</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>&nbsp;</td>
517 <td>&nbsp;</td>
518 <td align=center><b>X</b></td>
519 <td>&nbsp;</td>
520 </tr>
521 <tr>
522 <td><b>B</b> <i>baseline</i></td>
523 <td align=center><b>X</b></td>
524 <td>&nbsp;</td>
525 <td>&nbsp;</td>
526 <td>&nbsp;</td>
527 <td>&nbsp;</td>
528 <td>&nbsp;</td>
529 <td>&nbsp;</td>
530 </tr>
531 <tr>
532 <td><b>C</b> <i>comment-text</i></td>
533 <td align=center><b>X</b></td>
534 <td>&nbsp;</td>
535 <td>&nbsp;</td>
536

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button