| | @@ -35,12 +35,14 @@ |
| 35 | 35 | ** We assume that vfile_check_signature has been run. |
| 36 | 36 | */ |
| 37 | 37 | static void status_report(Blob *report, const char *zPrefix){ |
| 38 | 38 | Stmt q; |
| 39 | 39 | int nPrefix = strlen(zPrefix); |
| 40 | | - db_prepare(&q, "SELECT pathname, deleted, chnged, rid FROM vfile" |
| 41 | | - " WHERE chnged OR deleted OR rid=0 ORDER BY 1"); |
| 40 | + db_prepare(&q, |
| 41 | + "SELECT pathname, deleted, chnged, rid FROM vfile " |
| 42 | + "WHERE file_is_selected(id) AND (chnged OR deleted OR rid=0) ORDER BY 1" |
| 43 | + ); |
| 42 | 44 | while( db_step(&q)==SQLITE_ROW ){ |
| 43 | 45 | const char *zPathname = db_column_text(&q,0); |
| 44 | 46 | int isDeleted = db_column_int(&q, 1); |
| 45 | 47 | int isChnged = db_column_int(&q,2); |
| 46 | 48 | int isNew = db_column_int(&q,3)==0; |
| | @@ -209,16 +211,57 @@ |
| 209 | 211 | zComment = blob_str(pComment); |
| 210 | 212 | i = strlen(zComment); |
| 211 | 213 | while( i>0 && isspace(zComment[i-1]) ){ i--; } |
| 212 | 214 | blob_resize(pComment, i); |
| 213 | 215 | } |
| 216 | + |
| 217 | +/* |
| 218 | +** Populate the Global.aCommitFile[] based on the command line arguments |
| 219 | +** to a [commit] command. Global.aCommitFile is an array of integers |
| 220 | +** sized at (N+1), where N is the number of arguments passed to [commit]. |
| 221 | +** The contents are the [id] values from the vfile table corresponding |
| 222 | +** to the filenames passed as arguments. |
| 223 | +** |
| 224 | +** The last element of aCommitFile[] is always 0 - indicating the end |
| 225 | +** of the array. |
| 226 | +** |
| 227 | +** If there were no arguments passed to [commit], aCommitFile is not |
| 228 | +** allocated and remains NULL. Other parts of the code interpret this |
| 229 | +** to mean "all files". |
| 230 | +*/ |
| 231 | +void select_commit_files(void){ |
| 232 | + if( g.argc>2 ){ |
| 233 | + int ii; |
| 234 | + Blob b; |
| 235 | + memset(&b, 0, sizeof(Blob)); |
| 236 | + g.aCommitFile = malloc(sizeof(int)*(g.argc-1)); |
| 237 | + |
| 238 | + for(ii=2; ii<g.argc; ii++){ |
| 239 | + int iId; |
| 240 | + if( !file_tree_name(g.argv[ii], &b) ){ |
| 241 | + fossil_fatal("file is not in tree: %s", g.argv[ii]); |
| 242 | + } |
| 243 | + iId = db_int(-1, "SELECT id FROM vfile WHERE pathname=%Q", blob_str(&b)); |
| 244 | + if( iId<0 ){ |
| 245 | + fossil_fatal("fossil knows nothing about: %s", g.argv[ii]); |
| 246 | + } |
| 247 | + g.aCommitFile[ii-2] = iId; |
| 248 | + } |
| 249 | + g.aCommitFile[ii-2] = 0; |
| 250 | + } |
| 251 | +} |
| 214 | 252 | |
| 215 | 253 | /* |
| 216 | 254 | ** COMMAND: commit |
| 217 | 255 | ** |
| 218 | 256 | ** Create a new version containing all of the changes in the current |
| 219 | | -** checkout. |
| 257 | +** checkout. A commit is a three step process: |
| 258 | +** |
| 259 | +** 1) Add the new content to the blob table, |
| 260 | +** 2) Create and add the new manifest to the blob table, |
| 261 | +** 3) Update the vfile table, |
| 262 | +** 4) Run checks to make sure everything is still internally consistent. |
| 220 | 263 | */ |
| 221 | 264 | void commit_cmd(void){ |
| 222 | 265 | int rc; |
| 223 | 266 | int vid, nrid, nvid; |
| 224 | 267 | Blob comment; |
| | @@ -228,37 +271,70 @@ |
| 228 | 271 | char *zManifestFile; /* Name of the manifest file */ |
| 229 | 272 | Blob manifest; |
| 230 | 273 | Blob mcksum; /* Self-checksum on the manifest */ |
| 231 | 274 | Blob cksum1, cksum2; /* Before and after commit checksums */ |
| 232 | 275 | Blob cksum1b; /* Checksum recorded in the manifest */ |
| 233 | | - |
| 276 | + |
| 234 | 277 | db_must_be_within_tree(); |
| 278 | + |
| 279 | + /* There are two ways this command may be executed. If there are |
| 280 | + ** no arguments following the word "commit", then all modified files |
| 281 | + ** in the checked out directory are committed. If one or more arguments |
| 282 | + ** follows "commit", then only those files are committed. |
| 283 | + ** |
| 284 | + ** After the following function call has returned, the Global.aCommitFile[] |
| 285 | + ** array is allocated to contain the "id" field from the vfile table |
| 286 | + ** for each file to be committed. Or, if aCommitFile is NULL, all files |
| 287 | + ** should be committed. |
| 288 | + */ |
| 289 | + select_commit_files(); |
| 290 | + |
| 235 | 291 | user_select(); |
| 236 | 292 | db_begin_transaction(); |
| 237 | 293 | rc = unsaved_changes(); |
| 238 | 294 | if( rc==0 ){ |
| 239 | 295 | fossil_panic("nothing has changed"); |
| 240 | 296 | } |
| 297 | + |
| 298 | + /* If one or more files that were named on the command line have not |
| 299 | + ** been modified, bail out now. |
| 300 | + */ |
| 301 | + if( g.aCommitFile ){ |
| 302 | + Blob unmodified; |
| 303 | + memset(&unmodified, 0, sizeof(Blob)); |
| 304 | + blob_init(&unmodified, 0, 0); |
| 305 | + db_blob(&unmodified, |
| 306 | + "SELECT pathname FROM vfile WHERE chnged = 0 AND file_is_selected(id)" |
| 307 | + ); |
| 308 | + if( strlen(blob_str(&unmodified)) ){ |
| 309 | + fossil_panic("file %s has not changed", blob_str(&unmodified)); |
| 310 | + } |
| 311 | + } |
| 312 | + |
| 241 | 313 | vid = db_lget_int("checkout", 0); |
| 242 | 314 | vfile_aggregate_checksum_disk(vid, &cksum1); |
| 243 | 315 | prepare_commit_comment(&comment); |
| 244 | 316 | |
| 317 | + /* Step 1: Insert records for all modified files into the blob |
| 318 | + ** table. If there were arguments passed to this command, only |
| 319 | + ** the identified fils are inserted (if they have been modified). |
| 320 | + */ |
| 245 | 321 | db_prepare(&q, |
| 246 | | - "SELECT id, %Q || pathname, mrid FROM vfile" |
| 247 | | - " WHERE chnged==1 AND NOT deleted", g.zLocalRoot |
| 322 | + "SELECT id, %Q || pathname, mrid FROM vfile " |
| 323 | + "WHERE chnged==1 AND NOT deleted AND file_is_selected(id)" |
| 324 | + , g.zLocalRoot |
| 248 | 325 | ); |
| 249 | | - db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); |
| 250 | 326 | while( db_step(&q)==SQLITE_ROW ){ |
| 251 | 327 | int id, rid; |
| 252 | 328 | const char *zFullname; |
| 253 | 329 | Blob content; |
| 254 | 330 | |
| 255 | 331 | id = db_column_int(&q, 0); |
| 256 | 332 | zFullname = db_column_text(&q, 1); |
| 257 | 333 | rid = db_column_int(&q, 2); |
| 258 | 334 | |
| 259 | | - blob_zero(&content); |
| 335 | + blob_zero(&content); |
| 260 | 336 | blob_read_from_file(&content, zFullname); |
| 261 | 337 | nrid = content_put(&content, 0); |
| 262 | 338 | if( rid>0 ){ |
| 263 | 339 | content_deltify(rid, nrid, 0); |
| 264 | 340 | } |
| | @@ -265,11 +341,11 @@ |
| 265 | 341 | db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); |
| 266 | 342 | } |
| 267 | 343 | db_finalize(&q); |
| 268 | 344 | |
| 269 | 345 | /* Create the manifest */ |
| 270 | | - blob_zero(&manifest); |
| 346 | + blob_zero(&manifest); |
| 271 | 347 | blob_appendf(&manifest, "C %F\n", blob_str(&comment)); |
| 272 | 348 | zDate = db_text(0, "SELECT datetime('now')"); |
| 273 | 349 | zDate[10] = 'T'; |
| 274 | 350 | blob_appendf(&manifest, "D %s\n", zDate); |
| 275 | 351 | db_prepare(&q, |
| | @@ -282,10 +358,12 @@ |
| 282 | 358 | blob_appendf(&manifest, "F %F %s\n", zName, zUuid); |
| 283 | 359 | } |
| 284 | 360 | db_finalize(&q); |
| 285 | 361 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); |
| 286 | 362 | blob_appendf(&manifest, "P %s", zUuid); |
| 363 | + |
| 364 | + db_prepare(&q2, "SELECT merge FROM vmerge WHERE id=:id"); |
| 287 | 365 | db_bind_int(&q2, ":id", 0); |
| 288 | 366 | while( db_step(&q2)==SQLITE_ROW ){ |
| 289 | 367 | int mid = db_column_int(&q2, 0); |
| 290 | 368 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); |
| 291 | 369 | if( zUuid ){ |
| | @@ -292,10 +370,11 @@ |
| 292 | 370 | blob_appendf(&manifest, " %s", zUuid); |
| 293 | 371 | free(zUuid); |
| 294 | 372 | } |
| 295 | 373 | } |
| 296 | 374 | db_reset(&q2); |
| 375 | + |
| 297 | 376 | blob_appendf(&manifest, "\n"); |
| 298 | 377 | blob_appendf(&manifest, "R %b\n", &cksum1); |
| 299 | 378 | blob_appendf(&manifest, "U %F\n", g.zLogin); |
| 300 | 379 | md5sum_blob(&manifest, &mcksum); |
| 301 | 380 | blob_appendf(&manifest, "Z %b\n", &mcksum); |
| | @@ -320,33 +399,44 @@ |
| 320 | 399 | manifest_crosslink(nvid, &manifest); |
| 321 | 400 | content_deltify(vid, nvid, 0); |
| 322 | 401 | zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid); |
| 323 | 402 | printf("New_Version: %s\n", zUuid); |
| 324 | 403 | |
| 325 | | - /* Update VFILE */ |
| 326 | | - db_multi_exec("DELETE FROM vfile WHERE vid!=%d OR deleted", vid); |
| 327 | | - db_multi_exec("DELETE FROM vmerge"); |
| 328 | | - db_multi_exec("UPDATE vfile SET vid=%d, rid=mrid, chnged=0, deleted=0", nvid); |
| 404 | + /* Update the vfile and vmerge tables */ |
| 405 | + db_multi_exec( |
| 406 | + "DELETE FROM vfile WHERE (vid!=%d OR deleted) AND file_is_selected(id);" |
| 407 | + "DELETE FROM vmerge WHERE file_is_selected(id) OR id=0;" |
| 408 | + "UPDATE vfile SET vid=%d;" |
| 409 | + "UPDATE vfile SET rid=mrid, chnged=0, deleted=0 WHERE file_is_selected(id);" |
| 410 | + , vid, nvid |
| 411 | + ); |
| 329 | 412 | db_lset_int("checkout", nvid); |
| 330 | 413 | |
| 331 | | - /* Verify that the tree checksum is unchanged */ |
| 414 | + /* Verify that the repository checksum matches the expected checksum |
| 415 | + ** calculated before the checkin started (and stored as the R record |
| 416 | + ** of the manifest file). |
| 417 | + */ |
| 332 | 418 | vfile_aggregate_checksum_repository(nvid, &cksum2); |
| 333 | 419 | if( blob_compare(&cksum1, &cksum2) ){ |
| 334 | 420 | fossil_panic("tree checksum does not match repository after commit"); |
| 335 | 421 | } |
| 422 | + |
| 423 | + /* Verify that the manifest checksum matches the expected checksum */ |
| 336 | 424 | vfile_aggregate_checksum_manifest(nvid, &cksum2, &cksum1b); |
| 337 | 425 | if( blob_compare(&cksum1, &cksum1b) ){ |
| 338 | 426 | fossil_panic("manifest checksum does not agree with manifest: " |
| 339 | 427 | "%b versus %b", &cksum1, &cksum1b); |
| 340 | 428 | } |
| 341 | 429 | if( blob_compare(&cksum1, &cksum2) ){ |
| 342 | 430 | fossil_panic("tree checksum does not match manifest after commit: " |
| 343 | 431 | "%b versus %b", &cksum1, &cksum2); |
| 344 | 432 | } |
| 433 | + |
| 434 | + /* Verify that the commit did not modify any disk images. */ |
| 345 | 435 | vfile_aggregate_checksum_disk(nvid, &cksum2); |
| 346 | 436 | if( blob_compare(&cksum1, &cksum2) ){ |
| 347 | 437 | fossil_panic("tree checksums before and after commit do not match"); |
| 348 | 438 | } |
| 349 | 439 | |
| 350 | 440 | /* Commit */ |
| 351 | 441 | db_end_transaction(0); |
| 352 | 442 | } |
| 353 | 443 | |