Fossil SCM

fossil-scm / src / vfile.c
Blame History Raw 1115 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** Procedures for managing the VFILE table.
19
*/
20
#include "config.h"
21
#include "vfile.h"
22
#include <assert.h>
23
#include <sys/types.h>
24
25
/*
26
** The input is guaranteed to be a 40- or 64-character well-formed
27
** artifact hash. Find its rid.
28
*/
29
int fast_uuid_to_rid(const char *zUuid){
30
static Stmt q;
31
int rid;
32
db_static_prepare(&q, "SELECT rid FROM blob WHERE uuid=:uuid");
33
db_bind_text(&q, ":uuid", zUuid);
34
if( db_step(&q)==SQLITE_ROW ){
35
rid = db_column_int(&q, 0);
36
}else{
37
rid = 0;
38
}
39
db_reset(&q);
40
return rid;
41
}
42
43
/*
44
** Given a UUID, return the corresponding record ID. If the UUID
45
** does not exist, then return 0.
46
**
47
** For this routine, the UUID must be exact. For a match against
48
** user input with mixed case, use resolve_uuid().
49
**
50
** If the UUID is not found and phantomize is 1 or 2, then attempt to
51
** create a phantom record. A private phantom is created for 2 and
52
** a public phantom is created for 1.
53
*/
54
int uuid_to_rid(const char *zUuid, int phantomize){
55
int rid, sz;
56
char z[HNAME_MAX+1];
57
58
sz = strlen(zUuid);
59
if( !hname_validate(zUuid, sz) ){
60
return 0; /* Not a valid hash */
61
}
62
memcpy(z, zUuid, sz+1);
63
canonical16(z, sz);
64
rid = fast_uuid_to_rid(z);
65
if( rid==0 && phantomize ){
66
rid = content_new(zUuid, phantomize-1);
67
}
68
return rid;
69
}
70
71
72
/*
73
** Load a vfile from a record ID. Return the number of files with
74
** missing content.
75
*/
76
int load_vfile_from_rid(int vid){
77
int rid, size, nMissing;
78
Stmt ins, ridq;
79
Manifest *p;
80
ManifestFile *pFile;
81
82
if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){
83
return 0;
84
}
85
86
db_begin_transaction();
87
p = manifest_get(vid, CFTYPE_MANIFEST, 0);
88
if( p==0 ) {
89
db_end_transaction(1);
90
return 0;
91
}
92
db_prepare(&ins,
93
"INSERT INTO vfile(vid,isexe,islink,rid,mrid,pathname,mhash) "
94
" VALUES(:vid,:isexe,:islink,:id,:id,:name,NULL)");
95
db_prepare(&ridq, "SELECT rid,size FROM blob WHERE uuid=:uuid");
96
db_bind_int(&ins, ":vid", vid);
97
manifest_file_rewind(p);
98
nMissing = 0;
99
while( (pFile = manifest_file_next(p,0))!=0 ){
100
if( pFile->zUuid==0 || uuid_is_shunned(pFile->zUuid) ) continue;
101
db_bind_text(&ridq, ":uuid", pFile->zUuid);
102
if( db_step(&ridq)==SQLITE_ROW ){
103
rid = db_column_int(&ridq, 0);
104
size = db_column_int(&ridq, 1);
105
}else{
106
rid = 0;
107
size = 0;
108
}
109
db_reset(&ridq);
110
if( rid==0 || size<0 ){
111
fossil_warning("content missing for %s", pFile->zName);
112
nMissing++;
113
continue;
114
}
115
db_bind_int(&ins, ":isexe", ( manifest_file_mperm(pFile)==PERM_EXE ));
116
db_bind_int(&ins, ":id", rid);
117
db_bind_text(&ins, ":name", pFile->zName);
118
db_bind_int(&ins, ":islink", ( manifest_file_mperm(pFile)==PERM_LNK ));
119
db_step(&ins);
120
db_reset(&ins);
121
}
122
db_finalize(&ridq);
123
db_finalize(&ins);
124
manifest_destroy(p);
125
db_end_transaction(0);
126
return nMissing;
127
}
128
129
#if INTERFACE
130
/*
131
** The cksigFlags parameter to vfile_check_signature() is an OR-ed
132
** combination of the following bits:
133
*/
134
#define CKSIG_ENOTFILE 0x001 /* non-file FS objects throw an error */
135
#define CKSIG_HASH 0x002 /* Verify file content using hashing */
136
#define CKSIG_SETMTIME 0x004 /* Set mtime to last check-out time */
137
138
#endif /* INTERFACE */
139
140
/*
141
** Look at every VFILE entry with the given vid and update VFILE.CHNGED field
142
** according to whether or not the file has changed.
143
** - 0 means no change.
144
** - 1 means edited.
145
** - 2 means changed due to a merge.
146
** - 3 means added by a merge.
147
** - 4 means changed due to an integrate merge.
148
** - 5 means added by an integrate merge.
149
** - 6 means became executable but has unmodified contents.
150
** - 7 means became a symlink whose target equals its old contents.
151
** - 8 means lost executable status but has unmodified contents.
152
** - 9 means lost symlink status and has contents equal to its old target.
153
**
154
** If VFILE.DELETED is true or if VFILE.RID is zero, then the file was either
155
** removed from configuration management via "fossil rm" or added via
156
** "fossil add", respectively, and in both cases we always know that
157
** the file has changed without having the check the size, mtime,
158
** or on-disk content.
159
**
160
** If the size of the file has changed, then we always know that the file
161
** changed without having to look at the mtime or on-disk content.
162
**
163
** The mtime of the file is only a factor if the mtime-changes setting
164
** is true (or undefined - it defaults to true) and the CKSIG_HASH
165
** flag is false. If the mtime-changes setting is false or if
166
** CKSIG_HASH is true, then we do not trust the mtime and will examine
167
** the on-disk content to determine if a file really is the same.
168
**
169
** If the mtime is used, it is used only to determine if files are the same.
170
** If the mtime of a file has changed, we still examine the on-disk content
171
** to see whether or not the edit was a null-edit.
172
*/
173
void vfile_check_signature(int vid, unsigned int cksigFlags){
174
int nErr = 0;
175
Stmt q;
176
int useMtime = (cksigFlags & CKSIG_HASH)==0
177
&& db_get_boolean("mtime-changes", 1);
178
179
db_begin_transaction();
180
db_prepare(&q, "SELECT id, %Q || pathname,"
181
" vfile.mrid, deleted, chnged, uuid, size, mtime,"
182
" CASE WHEN isexe THEN %d WHEN islink THEN %d ELSE %d END"
183
" FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid"
184
" WHERE vid=%d ", g.zLocalRoot, PERM_EXE, PERM_LNK, PERM_REG,
185
vid);
186
while( db_step(&q)==SQLITE_ROW ){
187
int id, rid, isDeleted;
188
const char *zName;
189
int chnged = 0;
190
int oldChnged;
191
#ifndef _WIN32
192
int origPerm;
193
int currentPerm;
194
#endif
195
i64 oldMtime;
196
i64 currentMtime;
197
i64 origSize;
198
i64 currentSize;
199
200
id = db_column_int(&q, 0);
201
zName = db_column_text(&q, 1);
202
rid = db_column_int(&q, 2);
203
isDeleted = db_column_int(&q, 3);
204
oldChnged = chnged = db_column_int(&q, 4);
205
oldMtime = db_column_int64(&q, 7);
206
origSize = db_column_int64(&q, 6);
207
currentSize = file_size(zName, RepoFILE);
208
currentMtime = file_mtime(0, 0);
209
#ifndef _WIN32
210
origPerm = db_column_int(&q, 8);
211
currentPerm = file_perm(zName, RepoFILE);
212
#endif
213
if( chnged==0 && (isDeleted || rid==0) ){
214
/* "fossil rm" or "fossil add" always change the file */
215
chnged = 1;
216
}else if( !file_isfile_or_link(0) && currentSize>=0 ){
217
if( cksigFlags & CKSIG_ENOTFILE ){
218
fossil_warning("not an ordinary file: %s", zName);
219
nErr++;
220
}
221
chnged = 1;
222
}
223
if( origSize!=currentSize ){
224
if( chnged!=1 ){
225
/* A file size change is definitive - the file has changed. No
226
** need to check the mtime or hash */
227
chnged = 1;
228
}
229
}else if( chnged==1 && rid!=0 && !isDeleted ){
230
/* File is believed to have changed but it is the same size.
231
** Double check that it really has changed by looking at content. */
232
const char *zUuid = db_column_text(&q, 5);
233
int nUuid = db_column_bytes(&q, 5);
234
assert( origSize==currentSize );
235
if( hname_verify_file_hash(zName, zUuid, nUuid) ) chnged = 0;
236
}else if( (chnged==0 || chnged==2 || chnged==4)
237
&& (useMtime==0 || currentMtime!=oldMtime) ){
238
/* For files that were formerly believed to be unchanged or that were
239
** changed by merging, if their mtime changes, or unconditionally
240
** if --hash is used, check to see if they have been edited by
241
** looking at their artifact hashes */
242
const char *zUuid = db_column_text(&q, 5);
243
int nUuid = db_column_bytes(&q, 5);
244
assert( origSize==currentSize );
245
if( !hname_verify_file_hash(zName, zUuid, nUuid) ) chnged = 1;
246
}
247
if( (cksigFlags & CKSIG_SETMTIME) && (chnged==0 || chnged==2 || chnged==4)){
248
i64 desiredMtime;
249
if( mtime_of_manifest_file(vid,rid,&desiredMtime)==0 ){
250
if( currentMtime!=desiredMtime ){
251
file_set_mtime(zName, desiredMtime);
252
currentMtime = file_mtime(zName, RepoFILE);
253
}
254
}
255
}
256
#ifndef _WIN32
257
if( origPerm!=PERM_LNK && currentPerm==PERM_LNK ){
258
/* Changing to a symlink takes priority over all other change types. */
259
chnged = 7;
260
}else if( origPerm==PERM_LNK && currentPerm!=PERM_LNK ){
261
/* Ditto, other direction */
262
chnged = 9;
263
}else if( chnged==0 || chnged==6 || chnged==7 || chnged==8 || chnged==9 ){
264
/* Confirm metadata change types. */
265
if( origPerm==currentPerm ){
266
chnged = 0;
267
}else if( currentPerm==PERM_EXE ){
268
chnged = 6;
269
}else if( origPerm==PERM_EXE ){
270
chnged = 8;
271
}else if( origPerm==PERM_LNK ){
272
chnged = 9;
273
}
274
}
275
#endif
276
if( currentMtime!=oldMtime || chnged!=oldChnged ){
277
db_multi_exec("UPDATE vfile SET mtime=%lld, chnged=%d WHERE id=%d",
278
currentMtime, chnged, id);
279
}
280
}
281
db_finalize(&q);
282
if( nErr ) fossil_fatal("abort due to prior errors");
283
db_end_transaction(0);
284
}
285
286
/*
287
** Write all files from vid to the disk. Or if vid==0 and id!=0
288
** write just the specific file where VFILE.ID=id.
289
*/
290
void vfile_to_disk(
291
int vid, /* vid to write to disk */
292
int id, /* Write this one file, if not zero */
293
int verbose, /* Output progress information */
294
int promptFlag /* Prompt user to confirm overwrites */
295
){
296
Stmt q;
297
Blob content;
298
int nRepos = strlen(g.zLocalRoot);
299
300
if( vid>0 && id==0 ){
301
db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink"
302
" FROM vfile"
303
" WHERE vid=%d AND mrid>0",
304
g.zLocalRoot, vid);
305
}else{
306
assert( vid==0 && id>0 );
307
db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink"
308
" FROM vfile"
309
" WHERE id=%d AND mrid>0",
310
g.zLocalRoot, id);
311
}
312
while( db_step(&q)==SQLITE_ROW ){
313
int id, rid, isExe, isLink;
314
const char *zName;
315
316
id = db_column_int(&q, 0);
317
zName = db_column_text(&q, 1);
318
rid = db_column_int(&q, 2);
319
isExe = db_column_int(&q, 3);
320
isLink = db_column_int(&q, 4);
321
if( file_unsafe_in_tree_path(zName) ){
322
continue;
323
}
324
content_get(rid, &content);
325
if( file_is_the_same(&content, zName) ){
326
blob_reset(&content);
327
if( file_setexe(zName, isExe) ){
328
db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
329
file_mtime(zName, RepoFILE), id);
330
}
331
continue;
332
}
333
if( promptFlag && file_size(zName, RepoFILE)>=0 ){
334
Blob ans;
335
char *zMsg;
336
char cReply;
337
zMsg = mprintf("overwrite %s (a=always/y/N)? ", zName);
338
prompt_user(zMsg, &ans);
339
free(zMsg);
340
cReply = blob_str(&ans)[0];
341
blob_reset(&ans);
342
if( cReply=='a' || cReply=='A' ){
343
promptFlag = 0;
344
} else if( cReply!='y' && cReply!='Y' ){
345
blob_reset(&content);
346
continue;
347
}
348
}
349
if( verbose ) fossil_print("%s\n", &zName[nRepos]);
350
if( file_isdir(zName, RepoFILE)==1 ){
351
/*TODO(dchest): remove directories? */
352
fossil_fatal("%s is directory, cannot overwrite", zName);
353
}
354
if( file_size(zName, RepoFILE)>=0 && (isLink || file_islink(0)) ){
355
file_delete(zName);
356
}
357
if( isLink ){
358
symlink_create(blob_str(&content), zName);
359
}else{
360
blob_write_to_file(&content, zName);
361
}
362
file_setexe(zName, isExe);
363
blob_reset(&content);
364
db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
365
file_mtime(zName, RepoFILE), id);
366
}
367
db_finalize(&q);
368
}
369
370
/*
371
** Check to see if the directory named in zPath is the top of a check-out.
372
** In other words, check to see if directory pPath contains a file named
373
** "_FOSSIL_" or ".fslckout". Return true or false.
374
*/
375
int vfile_top_of_checkout(const char *zPath){
376
char *zFile;
377
int fileFound = 0;
378
379
zFile = mprintf("%s/_FOSSIL_", zPath);
380
fileFound = file_size(zFile, ExtFILE)>=1024;
381
fossil_free(zFile);
382
if( !fileFound ){
383
zFile = mprintf("%s/.fslckout", zPath);
384
fileFound = file_size(zFile, ExtFILE)>=1024;
385
fossil_free(zFile);
386
}
387
388
/* Check for ".fos" for legacy support. But the use of ".fos" as the
389
** per-check-out database name is deprecated. At some point, all support
390
** for ".fos" will end and this code should be removed. This comment
391
** added on 2012-02-04.
392
*/
393
if( !fileFound ){
394
zFile = mprintf("%s/.fos", zPath);
395
fileFound = file_size(zFile, ExtFILE)>=1024;
396
fossil_free(zFile);
397
}
398
return fileFound;
399
}
400
401
/*
402
** Return TRUE if zFile is a temporary file. Return FALSE if not.
403
*/
404
static int is_temporary_file(const char *zName){
405
static const char *const azTemp[] = {
406
"baseline",
407
"merge",
408
"original",
409
"output",
410
};
411
int i, j, n;
412
413
if( sqlite3_strglob("ci-comment-????????????.txt", zName)==0 ) return 1;
414
for(; zName[0]!=0; zName++){
415
if( zName[0]=='/'
416
&& sqlite3_strglob("/ci-comment-????????????.txt", zName)==0 ){
417
return 1;
418
}
419
if( zName[0]!='-' ) continue;
420
for(i=0; i<count(azTemp); i++){
421
n = (int)strlen(azTemp[i]);
422
if( memcmp(azTemp[i], zName+1, n) ) continue;
423
if( zName[n+1]==0 ) return 1;
424
if( zName[n+1]=='-' ){
425
for(j=n+2; zName[j] && fossil_isdigit(zName[j]); j++){}
426
if( zName[j]==0 ) return 1;
427
}
428
}
429
}
430
return 0;
431
}
432
433
#if INTERFACE
434
/*
435
** Values for the scanFlags parameter to vfile_scan().
436
*/
437
#define SCAN_ALL 0x001 /* Includes files that begin with "." */
438
#define SCAN_TEMP 0x002 /* Only Fossil-generated files like *-baseline */
439
#define SCAN_NESTED 0x004 /* Scan for empty dirs in nested checkouts */
440
#define SCAN_MTIME 0x008 /* Populate mtime column */
441
#define SCAN_SIZE 0x010 /* Populate size column */
442
#define SCAN_ISEXE 0x020 /* Populate isexe column */
443
#endif /* INTERFACE */
444
445
/*
446
** Load into table SFILE the name of every ordinary file in
447
** the directory pPath. Omit the first nPrefix characters of
448
** of pPath when inserting into the SFILE table.
449
** Subdirectories are scanned recursively.
450
**
451
** Omit files named in VFILE if eFType==RepoFILE. Include all files
452
** if eFType==ExtFILE.
453
**
454
** Files whose names begin with "." are omitted unless the SCAN_ALL
455
** flag is set.
456
**
457
** Any files or directories that match the glob patterns pIgnore*
458
** are excluded from the scan. Name matching occurs after the
459
** first nPrefix characters are elided from the filename.
460
*/
461
void vfile_scan(
462
Blob *pPath, /* Directory to be scanned */
463
int nPrefix, /* Number of bytes in directory name */
464
unsigned scanFlags, /* Zero or more SCAN_xxx flags */
465
Glob *pIgnore1, /* Do not add files that match this GLOB */
466
Glob *pIgnore2, /* Omit files matching this GLOB too */
467
int eFType /* ExtFILE or RepoFILE */
468
){
469
DIR *d;
470
int origSize;
471
struct dirent *pEntry;
472
int skipAll = 0;
473
static Stmt ins;
474
static int depth = 0;
475
void *zNative;
476
477
origSize = blob_size(pPath);
478
if( pIgnore1 || pIgnore2 ){
479
blob_appendf(pPath, "/");
480
if( glob_match(pIgnore1, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
481
if( glob_match(pIgnore2, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
482
blob_resize(pPath, origSize);
483
}
484
if( skipAll ) return;
485
486
if( depth==0 ){
487
if( eFType==ExtFILE ){
488
db_prepare(&ins,
489
"INSERT OR IGNORE INTO sfile(pathname%s%s%s) VALUES(:file%s%s%s)",
490
scanFlags & SCAN_MTIME ? ",mtime" : "",
491
scanFlags & SCAN_SIZE ? ",size" : "",
492
scanFlags & SCAN_ISEXE ? ",isexe" : "",
493
scanFlags & SCAN_MTIME ? ",:mtime" : "",
494
scanFlags & SCAN_SIZE ? ",:size" : "",
495
scanFlags & SCAN_ISEXE ? ",:isexe" : ""
496
);
497
}else{
498
db_prepare(&ins,
499
"INSERT OR IGNORE INTO sfile(pathname%s%s%s) SELECT :file%s%s%s"
500
" WHERE NOT EXISTS(SELECT 1 FROM vfile WHERE"
501
" pathname=:file %s)",
502
scanFlags & SCAN_MTIME ? ",mtime" : "",
503
scanFlags & SCAN_SIZE ? ",size" : "",
504
scanFlags & SCAN_ISEXE ? ",isexe" : "",
505
scanFlags & SCAN_MTIME ? ",:mtime" : "",
506
scanFlags & SCAN_SIZE ? ",:size" : "",
507
scanFlags & SCAN_ISEXE ? ",:isexe" : "",
508
filename_collation()
509
);
510
}
511
}
512
depth++;
513
514
zNative = fossil_utf8_to_path(blob_str(pPath), 1);
515
d = opendir(zNative);
516
if( d ){
517
while( (pEntry=readdir(d))!=0 ){
518
char *zPath;
519
char *zUtf8;
520
if( pEntry->d_name[0]=='.' ){
521
if( (scanFlags & SCAN_ALL)==0 ) continue;
522
if( pEntry->d_name[1]==0 ) continue;
523
if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue;
524
}
525
zUtf8 = fossil_path_to_utf8(pEntry->d_name);
526
blob_appendf(pPath, "/%s", zUtf8);
527
zPath = blob_str(pPath);
528
if( glob_match(pIgnore1, &zPath[nPrefix+1]) ||
529
glob_match(pIgnore2, &zPath[nPrefix+1]) ){
530
/* do nothing */
531
#ifdef _DIRENT_HAVE_D_TYPE
532
}else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK)
533
? (file_isdir(zPath, eFType)==1) : (pEntry->d_type==DT_DIR) ){
534
#else
535
}else if( file_isdir(zPath, eFType)==1 ){
536
#endif
537
if( !vfile_top_of_checkout(zPath) ){
538
vfile_scan(pPath, nPrefix, scanFlags, pIgnore1, pIgnore2, eFType);
539
}
540
#ifdef _DIRENT_HAVE_D_TYPE
541
}else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK)
542
? (file_isfile_or_link(zPath)) : (pEntry->d_type==DT_REG) ){
543
#else
544
}else if( file_isfile_or_link(zPath) ){
545
#endif
546
if( (scanFlags & SCAN_TEMP)==0 || is_temporary_file(zUtf8) ){
547
db_bind_text(&ins, ":file", &zPath[nPrefix+1]);
548
if( scanFlags & SCAN_MTIME ){
549
db_bind_int(&ins, ":mtime", file_mtime(zPath, eFType));
550
}
551
if( scanFlags & SCAN_SIZE ){
552
db_bind_int(&ins, ":size", file_size(zPath, eFType));
553
}
554
if( scanFlags & SCAN_ISEXE ){
555
db_bind_int(&ins, ":isexe", file_isexe(zPath, eFType));
556
}
557
db_step(&ins);
558
db_reset(&ins);
559
}
560
}
561
fossil_path_free(zUtf8);
562
blob_resize(pPath, origSize);
563
}
564
closedir(d);
565
}
566
fossil_path_free(zNative);
567
568
depth--;
569
if( depth==0 ){
570
db_finalize(&ins);
571
}
572
}
573
574
/*
575
** Scans the specified base directory for any directories within it, while
576
** keeping a count of how many files they each contain, either directly or
577
** indirectly.
578
**
579
** Subdirectories are scanned recursively.
580
** Omit files named in VFILE.
581
**
582
** Directories whose names begin with "." are omitted unless the SCAN_ALL
583
** flag is set.
584
**
585
** Any directories that match the glob patterns pIgnore* are excluded from
586
** the scan. Name matching occurs after the first nPrefix characters are
587
** elided from the filename.
588
**
589
** Returns the total number of files found.
590
*/
591
int vfile_dir_scan(
592
Blob *pPath, /* Base directory to be scanned */
593
int nPrefix, /* Number of bytes in base directory name */
594
unsigned scanFlags, /* Zero or more SCAN_xxx flags */
595
Glob *pIgnore1, /* Do not add directories that match this GLOB */
596
Glob *pIgnore2, /* Omit directories matching this GLOB too */
597
int eFType /* ExtFILE or RepoFILE */
598
){
599
int result = 0;
600
DIR *d;
601
int origSize;
602
struct dirent *pEntry;
603
int skipAll = 0;
604
static Stmt ins;
605
static Stmt upd;
606
static int depth = 0;
607
void *zNative;
608
609
origSize = blob_size(pPath);
610
if( pIgnore1 || pIgnore2 ){
611
blob_appendf(pPath, "/");
612
if( glob_match(pIgnore1, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
613
if( glob_match(pIgnore2, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1;
614
blob_resize(pPath, origSize);
615
}
616
if( skipAll ) return result;
617
618
if( depth==0 ){
619
db_multi_exec("DROP TABLE IF EXISTS dscan_temp;"
620
"CREATE TEMP TABLE dscan_temp("
621
" x TEXT PRIMARY KEY %s, y INTEGER)",
622
filename_collation());
623
db_prepare(&ins,
624
"INSERT OR IGNORE INTO dscan_temp(x, y) SELECT :file, :count"
625
" WHERE NOT EXISTS(SELECT 1 FROM vfile WHERE"
626
" pathname GLOB :file || '/*' %s)", filename_collation()
627
);
628
db_prepare(&upd,
629
"UPDATE OR IGNORE dscan_temp SET y = coalesce(y, 0) + 1"
630
" WHERE x=:file %s",
631
filename_collation()
632
);
633
}
634
depth++;
635
636
zNative = fossil_utf8_to_path(blob_str(pPath), 1);
637
d = opendir(zNative);
638
if( d ){
639
while( (pEntry=readdir(d))!=0 ){
640
char *zOrigPath;
641
char *zPath;
642
char *zUtf8;
643
if( pEntry->d_name[0]=='.' ){
644
if( (scanFlags & SCAN_ALL)==0 ) continue;
645
if( pEntry->d_name[1]==0 ) continue;
646
if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue;
647
}
648
zOrigPath = fossil_strdup(blob_str(pPath));
649
zUtf8 = fossil_path_to_utf8(pEntry->d_name);
650
blob_appendf(pPath, "/%s", zUtf8);
651
zPath = blob_str(pPath);
652
if( glob_match(pIgnore1, &zPath[nPrefix+1]) ||
653
glob_match(pIgnore2, &zPath[nPrefix+1]) ){
654
/* do nothing */
655
#ifdef _DIRENT_HAVE_D_TYPE
656
}else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK)
657
? (file_isdir(zPath, eFType)==1) : (pEntry->d_type==DT_DIR) ){
658
#else
659
}else if( file_isdir(zPath, eFType)==1 ){
660
#endif
661
if( (scanFlags & SCAN_NESTED) || !vfile_top_of_checkout(zPath) ){
662
char *zSavePath = fossil_strdup(zPath);
663
int count = vfile_dir_scan(pPath, nPrefix, scanFlags, pIgnore1,
664
pIgnore2, eFType);
665
db_bind_text(&ins, ":file", &zSavePath[nPrefix+1]);
666
db_bind_int(&ins, ":count", count);
667
db_step(&ins);
668
db_reset(&ins);
669
fossil_free(zSavePath);
670
result += count; /* found X normal files? */
671
}
672
#ifdef _DIRENT_HAVE_D_TYPE
673
}else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK)
674
? (file_isfile_or_link(zPath)) : (pEntry->d_type==DT_REG) ){
675
#else
676
}else if( file_isfile_or_link(zPath) ){
677
#endif
678
db_bind_text(&upd, ":file", zOrigPath);
679
db_step(&upd);
680
db_reset(&upd);
681
result++; /* found 1 normal file */
682
}
683
fossil_path_free(zUtf8);
684
blob_resize(pPath, origSize);
685
fossil_free(zOrigPath);
686
}
687
closedir(d);
688
}
689
fossil_path_free(zNative);
690
691
depth--;
692
if( depth==0 ){
693
db_finalize(&upd);
694
db_finalize(&ins);
695
}
696
return result;
697
}
698
699
/*
700
** Compute an aggregate MD5 checksum over the disk image of every
701
** file in vid. The file names are part of the checksum. The resulting
702
** checksum is the same as is expected on the R-card of a manifest.
703
**
704
** This function operates differently if the Global.aCommitFile
705
** variable is not NULL. In that case, the disk image is used for
706
** each file in aCommitFile[] and the repository image
707
** is used for all others).
708
**
709
** Newly added files that are not contained in the repository are
710
** omitted from the checksum if they are not in Global.aCommitFile[].
711
**
712
** Newly deleted files are included in the checksum if they are not
713
** part of Global.aCommitFile[]
714
**
715
** Renamed files use their new name if they are in Global.aCommitFile[]
716
** and their original name if they are not in Global.aCommitFile[]
717
**
718
** Return the resulting checksum in blob pOut.
719
*/
720
void vfile_aggregate_checksum_disk(int vid, Blob *pOut){
721
FILE *in;
722
Stmt q;
723
char zBuf[4096];
724
725
db_must_be_within_tree();
726
db_prepare(&q,
727
"SELECT %Q || pathname, pathname, origname, is_selected(id), rid"
728
" FROM vfile"
729
" WHERE (NOT deleted OR NOT is_selected(id)) AND vid=%d"
730
" ORDER BY if_selected(id, pathname, origname) /*scan*/",
731
g.zLocalRoot, vid
732
);
733
md5sum_init();
734
while( db_step(&q)==SQLITE_ROW ){
735
const char *zFullpath = db_column_text(&q, 0);
736
const char *zName = db_column_text(&q, 1);
737
int isSelected = db_column_int(&q, 3);
738
739
if( isSelected ){
740
md5sum_step_text(zName, -1);
741
if( file_islink(zFullpath) ){
742
/* Instead of file content, use link destination path */
743
Blob pathBuf;
744
745
sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n",
746
blob_read_link(&pathBuf, zFullpath));
747
md5sum_step_text(zBuf, -1);
748
md5sum_step_text(blob_str(&pathBuf), -1);
749
blob_reset(&pathBuf);
750
}else{
751
in = fossil_fopen(zFullpath,"rb");
752
if( in==0 ){
753
md5sum_step_text(" 0\n", -1);
754
continue;
755
}
756
fseek(in, 0L, SEEK_END);
757
sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", ftell(in));
758
fseek(in, 0L, SEEK_SET);
759
md5sum_step_text(zBuf, -1);
760
/*printf("%s %s %s",md5sum_current_state(),zName,zBuf);fflush(stdout);*/
761
for(;;){
762
int n;
763
n = fread(zBuf, 1, sizeof(zBuf), in);
764
if( n<=0 ) break;
765
md5sum_step_text(zBuf, n);
766
}
767
fclose(in);
768
}
769
}else{
770
int rid = db_column_int(&q, 4);
771
const char *zOrigName = db_column_text(&q, 2);
772
char zBuf[100];
773
Blob file;
774
775
if( zOrigName ) zName = zOrigName;
776
if( rid>0 ){
777
md5sum_step_text(zName, -1);
778
blob_zero(&file);
779
content_get(rid, &file);
780
sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file));
781
md5sum_step_text(zBuf, -1);
782
md5sum_step_blob(&file);
783
blob_reset(&file);
784
}
785
}
786
}
787
db_finalize(&q);
788
md5sum_finish(pOut);
789
}
790
791
/*
792
** Write a BLOB into a random filename. Return the name of the file.
793
*/
794
char *write_blob_to_temp_file(Blob *pBlob){
795
sqlite3_uint64 r;
796
char *zOut = 0;
797
do{
798
sqlite3_free(zOut);
799
sqlite3_randomness(8, &r);
800
zOut = sqlite3_mprintf("file-%08llx", r);
801
}while( file_size(zOut, ExtFILE)>=0 );
802
blob_write_to_file(pBlob, zOut);
803
return zOut;
804
}
805
806
/*
807
** Do a file-by-file comparison of the content of the repository and
808
** the working check-out on disk. Report any errors.
809
*/
810
void vfile_compare_repository_to_disk(int vid){
811
sqlite3_int64 rc;
812
Stmt q;
813
Blob disk, repo;
814
char *zOut;
815
816
db_must_be_within_tree();
817
db_prepare(&q,
818
"SELECT %Q || pathname, pathname, rid FROM vfile"
819
" WHERE NOT deleted AND vid=%d AND is_selected(id)"
820
" ORDER BY if_selected(id, pathname, origname) /*scan*/",
821
g.zLocalRoot, vid
822
);
823
md5sum_init();
824
while( db_step(&q)==SQLITE_ROW ){
825
const char *zFullpath = db_column_text(&q, 0);
826
const char *zName = db_column_text(&q, 1);
827
int rid = db_column_int(&q, 2);
828
829
blob_zero(&disk);
830
rc = blob_read_from_file(&disk, zFullpath, RepoFILE);
831
if( rc<0 ){
832
fossil_print("ERROR: cannot read file [%s]\n", zFullpath);
833
blob_reset(&disk);
834
continue;
835
}
836
blob_zero(&repo);
837
content_get(rid, &repo);
838
if( blob_size(&repo)!=blob_size(&disk) ){
839
fossil_print("ERROR: [%s] is %d bytes on disk but %d in the repository\n",
840
zName, blob_size(&disk), blob_size(&repo));
841
zOut = write_blob_to_temp_file(&repo);
842
fossil_print("NOTICE: Repository version of [%s] stored in [%s]\n",
843
zName, zOut);
844
sqlite3_free(zOut);
845
blob_reset(&disk);
846
blob_reset(&repo);
847
continue;
848
}
849
if( blob_compare(&repo, &disk) ){
850
fossil_print(
851
"ERROR: [%s] is different on disk compared to the repository\n",
852
zName);
853
zOut = write_blob_to_temp_file(&repo);
854
fossil_print("NOTICE: Repository version of [%s] stored in [%s]\n",
855
zName, zOut);
856
sqlite3_free(zOut);
857
}
858
blob_reset(&disk);
859
blob_reset(&repo);
860
}
861
db_finalize(&q);
862
}
863
864
/*
865
** Compute an aggregate MD5 checksum over the repository image of every
866
** file in vid. The file names are part of the checksum. The resulting
867
** checksum is suitable for the R-card of a manifest.
868
**
869
** Return the resulting checksum in blob pOut.
870
*/
871
void vfile_aggregate_checksum_repository(int vid, Blob *pOut){
872
Blob file;
873
Stmt q;
874
char zBuf[100];
875
876
db_must_be_within_tree();
877
878
db_prepare(&q, "SELECT pathname, origname, rid, is_selected(id)"
879
" FROM vfile"
880
" WHERE (NOT deleted OR NOT is_selected(id))"
881
" AND rid>0 AND vid=%d"
882
" ORDER BY if_selected(id,pathname,origname) /*scan*/",
883
vid);
884
blob_zero(&file);
885
md5sum_init();
886
while( db_step(&q)==SQLITE_ROW ){
887
const char *zName = db_column_text(&q, 0);
888
const char *zOrigName = db_column_text(&q, 1);
889
int rid = db_column_int(&q, 2);
890
int isSelected = db_column_int(&q, 3);
891
if( zOrigName && !isSelected ) zName = zOrigName;
892
md5sum_step_text(zName, -1);
893
content_get(rid, &file);
894
sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file));
895
md5sum_step_text(zBuf, -1);
896
/*printf("%s %s %s",md5sum_current_state(),zName,zBuf); fflush(stdout);*/
897
md5sum_step_blob(&file);
898
blob_reset(&file);
899
}
900
db_finalize(&q);
901
md5sum_finish(pOut);
902
}
903
904
/*
905
** Compute an aggregate MD5 checksum over the repository image of every
906
** file in manifest vid. The file names are part of the checksum. The
907
** resulting checksum is suitable for use as the R-card of a manifest.
908
**
909
** Return the resulting checksum in blob pOut.
910
**
911
** If pManOut is not NULL then fill it with the checksum found in the
912
** "R" card near the end of the manifest.
913
**
914
** In a well-formed manifest, the two checksums computed here, pOut and
915
** pManOut, should be identical.
916
*/
917
void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){
918
int fid;
919
Blob file;
920
Blob err;
921
Manifest *pManifest;
922
ManifestFile *pFile;
923
char zBuf[100];
924
925
blob_zero(pOut);
926
blob_zero(&err);
927
if( pManOut ){
928
blob_zero(pManOut);
929
}
930
db_must_be_within_tree();
931
pManifest = manifest_get(vid, CFTYPE_MANIFEST, &err);
932
if( pManifest==0 ){
933
fossil_fatal("manifest file (%d) is malformed:\n%s",
934
vid, blob_str(&err));
935
}
936
manifest_file_rewind(pManifest);
937
while( (pFile = manifest_file_next(pManifest,0))!=0 ){
938
if( pFile->zUuid==0 ) continue;
939
fid = uuid_to_rid(pFile->zUuid, 0);
940
md5sum_step_text(pFile->zName, -1);
941
content_get(fid, &file);
942
sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file));
943
md5sum_step_text(zBuf, -1);
944
md5sum_step_blob(&file);
945
blob_reset(&file);
946
}
947
if( pManOut ){
948
if( pManifest->zRepoCksum ){
949
blob_append(pManOut, pManifest->zRepoCksum, -1);
950
}else{
951
blob_zero(pManOut);
952
}
953
}
954
manifest_destroy(pManifest);
955
md5sum_finish(pOut);
956
}
957
958
/*
959
** COMMAND: test-agg-cksum
960
**
961
** Display the aggregate checksum for content computed in several
962
** different ways. The aggregate checksum is used during "fossil commit"
963
** to double-check that the information about to be committed to the
964
** repository exactly matches the information currently in the check-out.
965
*/
966
void test_agg_cksum_cmd(void){
967
int vid;
968
Blob hash, hash2;
969
db_must_be_within_tree();
970
vid = db_lget_int("checkout", 0);
971
vfile_aggregate_checksum_disk(vid, &hash);
972
printf("disk: %s\n", blob_str(&hash));
973
blob_reset(&hash);
974
vfile_aggregate_checksum_repository(vid, &hash);
975
printf("archive: %s\n", blob_str(&hash));
976
blob_reset(&hash);
977
vfile_aggregate_checksum_manifest(vid, &hash, &hash2);
978
printf("manifest: %s\n", blob_str(&hash));
979
printf("recorded: %s\n", blob_str(&hash2));
980
}
981
982
/*
983
** This routine recomputes certain columns of the vfile and vmerge tables
984
** when the associated repository is swapped out for a clone of the same
985
** project, and the blob.rid value change. The following columns are
986
** updated:
987
**
988
** vmerge.merge
989
** vfile.vid
990
** vfile.rid
991
** vfile.mrid
992
**
993
** Also:
994
**
995
** vvar.value WHERE name='checkout'
996
*/
997
void vfile_rid_renumbering_event(int dryRun){
998
int oldVid;
999
int newVid;
1000
char *zUnresolved;
1001
1002
oldVid = db_lget_int("checkout", 0);
1003
newVid = db_int(0, "SELECT blob.rid FROM blob, vvar"
1004
" WHERE blob.uuid=vvar.value"
1005
" AND vvar.name='checkout-hash'");
1006
1007
/* The idMap table will make old RID values into new ones */
1008
db_multi_exec(
1009
"CREATE TEMP TABLE idMap(oldrid INTEGER PRIMARY KEY, newrid INT);\n"
1010
);
1011
1012
/* Add the RID value for the current check-out */
1013
db_multi_exec(
1014
"INSERT INTO idMap(oldrid, newrid) VALUES(%d,%d)",
1015
oldVid, newVid
1016
);
1017
1018
/* Add the RID values for any other check-ins that have been merged into
1019
** the current check-out. */
1020
db_multi_exec(
1021
"INSERT OR IGNORE INTO idMap(oldrid, newrid)"
1022
" SELECT vmerge.merge, blob.rid FROM vmerge, blob"
1023
" WHERE blob.uuid=vmerge.mhash;"
1024
);
1025
1026
/* Add RID values for files in the current check-out */
1027
db_multi_exec(
1028
"CREATE TEMP TABLE hashoffile(name TEXT PRIMARY KEY, hash TEXT)"
1029
"WITHOUT ROWID;"
1030
1031
"INSERT INTO hashoffile(name,hash)"
1032
" SELECT filename, uuid FROM vvar, files_of_checkin(vvar.value)"
1033
" WHERE vvar.name='checkout-hash';"
1034
1035
"INSERT OR IGNORE INTO idMap(oldrid, newrid)"
1036
" SELECT vfile.rid, blob.rid FROM vfile, hashoffile, blob"
1037
" WHERE hashoffile.name=coalesce(vfile.origname,vfile.pathname)"
1038
" AND blob.uuid=hashoffile.hash;"
1039
);
1040
1041
/* Add RID values for merged-in files */
1042
db_multi_exec(
1043
"INSERT OR IGNORE INTO idMap(oldrid, newrid)"
1044
" SELECT vfile.mrid, blob.rid FROM vfile, blob"
1045
" WHERE blob.uuid=vfile.mhash;"
1046
);
1047
1048
if( dryRun ){
1049
Stmt q;
1050
db_prepare(&q, "SELECT oldrid, newrid, blob.uuid"
1051
" FROM idMap, blob WHERE blob.rid=idMap.newrid");
1052
while( db_step(&q)==SQLITE_ROW ){
1053
fossil_print("%8d -> %8d %.25s\n",
1054
db_column_int(&q,0),
1055
db_column_int(&q,1),
1056
db_column_text(&q,2));
1057
}
1058
db_finalize(&q);
1059
}
1060
1061
/* Verify that all RID values in the VFILE table and VMERGE table have
1062
** been resolved. */
1063
zUnresolved = db_text("",
1064
"WITH allrid(x) AS ("
1065
" SELECT rid FROM vfile"
1066
" UNION SELECT mrid FROM vfile"
1067
" UNION SELECT merge FROM vmerge"
1068
" UNION SELECT %d"
1069
")"
1070
"SELECT group_concat(x,' ') FROM allrid"
1071
" WHERE x<>0 AND x NOT IN (SELECT oldrid FROM idMap);",
1072
oldVid
1073
);
1074
if( zUnresolved[0] ){
1075
fossil_fatal("Unresolved RID values: %s\n"
1076
"\n"
1077
"Local check-out database is out of sync with repository file:\n"
1078
"\n"
1079
" %s\n"
1080
"\n"
1081
"Has the repository file been replaced?\n",
1082
zUnresolved, db_repository_filename());
1083
}
1084
1085
/* Make the changes to the VFILE and VMERGE tables */
1086
if( !dryRun ){
1087
db_multi_exec(
1088
"UPDATE vfile"
1089
" SET rid=(SELECT newrid FROM idMap WHERE oldrid=vfile.rid)"
1090
" WHERE vid=%d AND rid>0;", oldVid);
1091
1092
db_multi_exec(
1093
"UPDATE vfile"
1094
" SET mrid=(SELECT newrid FROM idMap WHERE oldrid=vfile.mrid)"
1095
" WHERE vid=%d AND mrid>0;", oldVid);
1096
1097
db_multi_exec(
1098
"UPDATE vfile"
1099
" SET vid=%d"
1100
" WHERE vid=%d", newVid, oldVid);
1101
1102
db_multi_exec(
1103
"UPDATE vmerge"
1104
" SET merge=(SELECT newrid FROM idMap WHERE oldrid=vmerge.merge);");
1105
1106
db_lset_int("checkout",newVid);
1107
}
1108
1109
/* Clear out the TEMP tables we constructed */
1110
db_multi_exec(
1111
"DROP TABLE idMap;"
1112
"DROP TABLE hashoffile;"
1113
);
1114
}
1115

Keyboard Shortcuts

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