Index: src/add.c ================================================================== --- src/add.c +++ src/add.c @@ -148,12 +148,13 @@ db_multi_exec("UPDATE vfile SET deleted=0 WHERE pathname=%Q", zPath); } #endif else{ db_multi_exec( - "INSERT INTO vfile(vid,deleted,rid,mrid,pathname)" - "VALUES(%d,0,0,0,%Q)", vid, zPath); + "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe)" + "VALUES(%d,0,0,0,%Q,%d)", + vid, zPath,file_isexe(zName)); } printf("ADDED %s\n", zPath); } blob_reset(&pathname); } Index: src/file.c ================================================================== --- src/file.c +++ src/file.c @@ -178,26 +178,31 @@ fclose(in); fclose(out); } /* -** Set or clear the execute bit on a file. +** Set or clear the execute bit on a file. Return true if a change +** occurred and false if this routine is a no-op. */ -void file_setexe(const char *zFilename, int onoff){ +int file_setexe(const char *zFilename, int onoff){ + int rc = 0; #if !defined(_WIN32) struct stat buf; - if( stat(zFilename, &buf)!=0 ) return; + if( stat(zFilename, &buf)!=0 ) return 0; if( onoff ){ if( (buf.st_mode & 0111)!=0111 ){ chmod(zFilename, buf.st_mode | 0111); + rc = 1; } }else{ if( (buf.st_mode & 0111)!=0 ){ chmod(zFilename, buf.st_mode & ~0111); + rc = 1; } } #endif /* _WIN32 */ + return rc; } /* ** Create the directory named in the argument, if it does not already ** exist. If forceFlag is 1, delete any prior non-directory object Index: src/merge.c ================================================================== --- src/merge.c +++ src/merge.c @@ -158,20 +158,22 @@ " idm INTEGER," /* VFILE entry for version merging in */ " chnged BOOLEAN," /* True if current version has been edited */ " ridv INTEGER," /* Record ID for current version */ " ridp INTEGER," /* Record ID for pivot */ " ridm INTEGER," /* Record ID for merge */ + " isexe BOOLEAN," /* Execute permission enabled */ " fnp TEXT," /* The filename in the pivot */ " fnm TEXT" /* the filename in the merged version */ ");" ); /* Add files found in V */ db_multi_exec( - "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)" - " SELECT pathname, pathname, pathname, id, 0, 0, rid, 0, 0, chnged " + "INSERT OR IGNORE" + " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" + " SELECT pathname, pathname, pathname, id, 0, 0, rid, 0, 0, isexe, chnged " " FROM vfile WHERE vid=%d", vid ); /* @@ -194,12 +196,13 @@ } /* Add files found in P but not in V */ db_multi_exec( - "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)" - " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, 0 " + "INSERT OR IGNORE" + " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" + " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 " " FROM vfile" " WHERE vid=%d AND pathname NOT IN (SELECT fnp FROM fv)", pid ); @@ -220,12 +223,13 @@ } /* Add files found in M but not in P or V. */ db_multi_exec( - "INSERT OR IGNORE INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,chnged)" - " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, 0 " + "INSERT OR IGNORE" + " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" + " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 " " FROM vfile" " WHERE vid=%d" " AND pathname NOT IN (SELECT fnp FROM fv UNION SELECT fnm FROM fv)", mid ); @@ -242,19 +246,20 @@ pid, pid, mid, mid ); if( debugFlag ){ db_prepare(&q, - "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm FROM fv" + "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm, isexe FROM fv" ); while( db_step(&q)==SQLITE_ROW ){ - printf("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d\n", + printf("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d isexe=%d\n", db_column_int(&q, 0), db_column_int(&q, 5), db_column_int(&q, 6), db_column_int(&q, 7), - db_column_int(&q, 4)); + db_column_int(&q, 4), + db_column_int(&q, 8)); printf(" fn = [%s]\n", db_column_text(&q, 1)); printf(" fnp = [%s]\n", db_column_text(&q, 2)); printf(" fnm = [%s]\n", db_column_text(&q, 3)); } db_finalize(&q); @@ -288,12 +293,12 @@ int idm = db_column_int(&q, 0); int rowid = db_column_int(&q, 1); int idv; const char *zName; db_multi_exec( - "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,pathname)" - " SELECT %d,3,0,rid,mrid,pathname FROM vfile WHERE id=%d", + "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,isexe,pathname)" + " SELECT %d,3,0,rid,mrid,isexe,pathname FROM vfile WHERE id=%d", vid, idm ); idv = db_last_insert_rowid(); db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid); zName = db_column_text(&q, 2); @@ -332,11 +337,11 @@ /* ** Do a three-way merge on files that have changes on both P->M and P->V. */ db_prepare(&q, - "SELECT ridm, idv, ridp, ridv, %s, fn FROM fv" + "SELECT ridm, idv, ridp, ridv, %s, fn, isexe FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" " AND ridm!=ridp AND (ridv!=ridp OR chnged)", glob_expr("fv.fn", zBinGlob) ); while( db_step(&q)==SQLITE_ROW ){ @@ -344,10 +349,11 @@ int idv = db_column_int(&q, 1); int ridp = db_column_int(&q, 2); int ridv = db_column_int(&q, 3); int isBinary = db_column_int(&q, 4); const char *zName = db_column_text(&q, 5); + int isExe = db_column_int(&q, 6); int rc; char *zFullPath; Blob m, p, r; /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ if( detailFlag ){ @@ -366,10 +372,11 @@ rc = merge_3way(&p, zFullPath, &m, &r); } if( rc>=0 ){ if( !nochangeFlag ){ blob_write_to_file(&r, zFullPath); + file_setexe(zFullPath, isExe); } db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); if( rc>0 ){ printf("***** %d merge conflicts in %s\n", rc, zName); nConflict++; Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -92,13 +92,11 @@ Blob content; db_bind_int(&ins, ":rid", rid); db_bind_int(&ins, ":isadd", rid==0); db_bind_int(&ins, ":isrm", deleted); -#ifdef _WIN32 db_bind_int(&ins, ":isexe", db_column_int(&q, 1)); -#endif db_bind_text(&ins, ":orig", zOrig); db_bind_text(&ins, ":new", zName); if( rid==0 ){ /* A new file */ blob_read_from_file(&content, zPath); @@ -175,10 +173,11 @@ stashid ); while( db_step(&q)==SQLITE_ROW ){ int rid = db_column_int(&q, 0); int isRemoved = db_column_int(&q, 1); + int isExec = db_column_int(&q, 2); const char *zOrig = db_column_text(&q, 3); const char *zNew = db_column_text(&q, 4); char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig); char *zNPath = mprintf("%s%s", g.zLocalRoot, zNew); Blob delta; @@ -185,10 +184,11 @@ undo_save(zNew); blob_zero(&delta); if( rid==0 ){ db_ephemeral_blob(&q, 5, &delta); blob_write_to_file(&delta, zNPath); + file_setexe(zNPath, isExec); printf("ADD %s\n", zNew); }else if( isRemoved ){ printf("DELETE %s\n", zOrig); unlink(zOPath); }else{ @@ -197,14 +197,16 @@ blob_read_from_file(&disk, zOPath); content_get(rid, &a); blob_delta_apply(&a, &delta, &b); if( blob_compare(&disk, &a)==0 ){ blob_write_to_file(&b, zNPath); + file_setexe(zNPath, isExec); printf("UPDATE %s\n", zNew); }else{ int rc = merge_3way(&a, zOPath, &b, &out); blob_write_to_file(&out, zNPath); + file_setexe(zNPath, isExec); if( rc ){ printf("CONFLICT %s\n", zNew); nConflict++; }else{ printf("MERGE %s\n", zNew); Index: src/undo.c ================================================================== --- src/undo.c +++ src/undo.c @@ -30,27 +30,33 @@ */ static void undo_one(const char *zPathname, int redoFlag){ Stmt q; char *zFullname; db_prepare(&q, - "SELECT content, existsflag FROM undo WHERE pathname=%Q AND redoflag=%d", + "SELECT content, existsflag, isExe FROM undo" + " WHERE pathname=%Q AND redoflag=%d", zPathname, redoFlag ); if( db_step(&q)==SQLITE_ROW ){ int old_exists; int new_exists; + int old_exe; + int new_exe; Blob current; Blob new; zFullname = mprintf("%s/%s", g.zLocalRoot, zPathname); new_exists = file_size(zFullname)>=0; if( new_exists ){ blob_read_from_file(¤t, zFullname); + new_exe = file_isexe(zFullname); }else{ blob_zero(¤t); + new_exe = 0; } blob_zero(&new); old_exists = db_column_int(&q, 1); + old_exe = db_column_int(&q, 2); if( old_exists ){ db_ephemeral_blob(&q, 0, &new); } if( old_exists ){ if( new_exists ){ @@ -57,21 +63,23 @@ printf("%s %s\n", redoFlag ? "REDO" : "UNDO", zPathname); }else{ printf("NEW %s\n", zPathname); } blob_write_to_file(&new, zFullname); + file_setexe(zFullname, old_exe); }else{ printf("DELETE %s\n", zPathname); unlink(zFullname); } blob_reset(&new); free(zFullname); db_finalize(&q); db_prepare(&q, - "UPDATE undo SET content=:c, existsflag=%d, redoflag=NOT redoflag" + "UPDATE undo SET content=:c, existsflag=%d, isExe=%d," + " redoflag=NOT redoflag" " WHERE pathname=%Q", - new_exists, zPathname + new_exists, new_exe, zPathname ); if( new_exists ){ db_bind_blob(&q, ":c", ¤t); } db_step(&q); @@ -200,10 +208,11 @@ static const char zSql[] = @ CREATE TABLE %s.undo( @ pathname TEXT UNIQUE, -- Name of the file @ redoflag BOOLEAN, -- 0 for undoable. 1 for redoable @ existsflag BOOLEAN, -- True if the file exists + @ isExe BOOLEAN, -- True if the file is executable @ content BLOB -- Saved content @ ); @ CREATE TABLE %s.undo_vfile AS SELECT * FROM vfile; @ CREATE TABLE %s.undo_vmerge AS SELECT * FROM vmerge; ; @@ -246,13 +255,13 @@ if( !undoActive ) return; zFullname = mprintf("%s%s", g.zLocalRoot, zPathname); existsFlag = file_size(zFullname)>=0; db_prepare(&q, - "INSERT OR IGNORE INTO undo(pathname,redoflag,existsflag,content)" - " VALUES(%Q,0,%d,:c)", - zPathname, existsFlag + "INSERT OR IGNORE INTO undo(pathname,redoflag,existsflag,isExe,content)" + " VALUES(%Q,0,%d,%d,:c)", + zPathname, existsFlag, file_isexe(zFullname) ); if( existsFlag ){ blob_read_from_file(&content, zFullname); db_bind_blob(&q, ":c", &content); } Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -197,19 +197,21 @@ " idv INTEGER," /* VFILE entry for current version */ " idt INTEGER," /* VFILE entry for target version */ " chnged BOOLEAN," /* True if current version has been edited */ " ridv INTEGER," /* Record ID for current version */ " ridt INTEGER," /* Record ID for target */ + " isexe BOOLEAN," /* Does target have execute permission? */ " fnt TEXT" /* Filename of same file on target version */ ");" ); /* Add files found in the current version */ db_multi_exec( - "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,chnged)" - " SELECT pathname, pathname, id, 0, rid, 0, chnged FROM vfile WHERE vid=%d", + "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)" + " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged" + " FROM vfile WHERE vid=%d", vid ); /* Compute file name changes on V->T. Record name changes in files that ** have changed locally. @@ -229,12 +231,12 @@ /* Add files found in the target version T but missing from the current ** version V. */ db_multi_exec( - "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,chnged)" - " SELECT pathname, pathname, 0, 0, 0, 0, 0 FROM vfile" + "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)" + " SELECT pathname, pathname, 0, 0, 0, 0, isexe, 0 FROM vfile" " WHERE vid=%d" " AND pathname NOT IN (SELECT fnt FROM fv)", tid ); @@ -248,18 +250,19 @@ tid, tid ); if( debugFlag ){ db_prepare(&q, - "SELECT rowid, fn, fnt, chnged, ridv, ridt FROM fv" + "SELECT rowid, fn, fnt, chnged, ridv, ridt, isexe FROM fv" ); while( db_step(&q)==SQLITE_ROW ){ - printf("%3d: ridv=%-4d ridt=%-4d chnged=%d\n", + printf("%3d: ridv=%-4d ridt=%-4d chnged=%d isexe=%d\n", db_column_int(&q, 0), db_column_int(&q, 4), db_column_int(&q, 5), - db_column_int(&q, 3)); + db_column_int(&q, 3), + db_column_int(&q, 6)); printf(" fnv = [%s]\n", db_column_text(&q, 1)); printf(" fnt = [%s]\n", db_column_text(&q, 2)); } db_finalize(&q); } @@ -299,11 +302,11 @@ /* ** Alter the content of the checkout so that it conforms with the ** target */ db_prepare(&q, - "SELECT fn, idv, ridv, idt, ridt, chnged, fnt FROM fv ORDER BY 1" + "SELECT fn, idv, ridv, idt, ridt, chnged, fnt, isexe FROM fv ORDER BY 1" ); db_prepare(&mtimeXfer, "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)" " WHERE id=:idt" ); @@ -316,10 +319,11 @@ int ridv = db_column_int(&q, 2); /* RecordID for current */ int idt = db_column_int(&q, 3); /* VFILE entry for target */ int ridt = db_column_int(&q, 4); /* RecordID for target */ int chnged = db_column_int(&q, 5); /* Current is edited */ const char *zNewName = db_column_text(&q,6);/* New filename */ + int isexe = db_column_int(&q, 6); /* EXE perm for new file */ char *zFullPath; /* Full pathname of the file */ char *zFullNewPath; /* Full pathname of dest */ char nameChng; /* True if the name changed */ zFullPath = mprintf("%s%s", g.zLocalRoot, zName); @@ -374,17 +378,23 @@ undo_save(zName); content_get(ridt, &t); content_get(ridv, &v); rc = merge_3way(&v, zFullPath, &t, &r); if( rc>=0 ){ - if( !nochangeFlag ) blob_write_to_file(&r, zFullNewPath); + if( !nochangeFlag ){ + blob_write_to_file(&r, zFullNewPath); + file_setexe(zFullNewPath, isexe); + } if( rc>0 ){ printf("***** %d merge conflicts in %s\n", rc, zNewName); nConflict++; } }else{ - if( !nochangeFlag ) blob_write_to_file(&t, zFullNewPath); + if( !nochangeFlag ){ + blob_write_to_file(&t, zFullNewPath); + file_setexe(zFullNewPath, isexe); + } printf("***** Cannot merge binary file %s\n", zNewName); nConflict++; } if( nameChng && !nochangeFlag ) unlink(zFullPath); blob_reset(&v); @@ -554,33 +564,40 @@ int vid = db_lget_int("checkout", 0); zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); } while( db_step(&q)==SQLITE_ROW ){ int isExe = 0; + char *zFull; zFile = db_column_text(&q, 0); + zFull = mprintf("%/%/", g.zLocalRoot, zFile); errCode = historical_version_of_file(zRevision, zFile, &record, &isExe,2); if( errCode==2 ){ - fossil_warning("file not in repository: %s", zFile); + if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){ + printf("UNMANAGE: %s\n", zFile); + }else{ + undo_save(zFile); + unlink(zFull); + printf("DELETE: %s\n", zFile); + } + db_multi_exec("DELETE FROM vfile WHERE pathname=%Q", zFile); }else{ - char *zFull = mprintf("%/%/", g.zLocalRoot, zFile); + sqlite3_int64 mtime; undo_save(zFile); blob_write_to_file(&record, zFull); file_setexe(zFull, isExe); printf("REVERTED: %s\n", zFile); - if( zRevision==0 ){ - sqlite3_int64 mtime = file_mtime(zFull); - db_multi_exec( - "UPDATE vfile" - " SET mtime=%lld, chnged=0, deleted=0, isexe=%d," - " pathname=coalesce(origname,pathname), origname=NULL" - " WHERE pathname=%Q", - mtime, isExe, zFile - ); - } - free(zFull); + mtime = file_mtime(zFull); + db_multi_exec( + "UPDATE vfile" + " SET mtime=%lld, chnged=0, deleted=0, isexe=%d, mrid=rid," + " pathname=coalesce(origname,pathname), origname=NULL" + " WHERE pathname=%Q", + mtime, isExe, zFile + ); } blob_reset(&record); + free(zFull); } db_finalize(&q); undo_finish(); db_end_transaction(0); } Index: src/vfile.c ================================================================== --- src/vfile.c +++ src/vfile.c @@ -85,21 +85,22 @@ db_begin_transaction(); p = manifest_get(vid, CFTYPE_MANIFEST); if( p==0 ) return; db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid); db_prepare(&ins, - "INSERT INTO vfile(vid,rid,mrid,pathname) " - " VALUES(:vid,:id,:id,:name)"); + "INSERT INTO vfile(vid,isexe,rid,mrid,pathname) " + " VALUES(:vid,:isexe,:id,:id,:name)"); db_bind_int(&ins, ":vid", vid); manifest_file_rewind(p); while( (pFile = manifest_file_next(p,0))!=0 ){ if( pFile->zUuid==0 || uuid_is_shunned(pFile->zUuid) ) continue; rid = uuid_to_rid(pFile->zUuid, 0); if( rid==0 || content_size(rid, -1)<0 ){ fossil_warning("content missing for %s", pFile->zName); continue; } + db_bind_int(&ins, ":isexe", manifest_file_mperm(pFile)); db_bind_int(&ins, ":id", rid); db_bind_text(&ins, ":name", pFile->zName); db_step(&ins); db_reset(&ins); } @@ -207,31 +208,36 @@ Stmt q; Blob content; int nRepos = strlen(g.zLocalRoot); if( vid>0 && id==0 ){ - db_prepare(&q, "SELECT id, %Q || pathname, mrid" + db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe" " FROM vfile" " WHERE vid=%d AND mrid>0", g.zLocalRoot, vid); }else{ assert( vid==0 && id>0 ); - db_prepare(&q, "SELECT id, %Q || pathname, mrid" + db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe" " FROM vfile" " WHERE id=%d AND mrid>0", g.zLocalRoot, id); } while( db_step(&q)==SQLITE_ROW ){ - int id, rid; + int id, rid, isExe; const char *zName; id = db_column_int(&q, 0); zName = db_column_text(&q, 1); rid = db_column_int(&q, 2); + isExe = db_column_int(&q, 3); content_get(rid, &content); if( file_is_the_same(&content, zName) ){ blob_reset(&content); + if( file_setexe(zName, isExe) ){ + db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", + file_mtime(zName), id); + } continue; } if( promptFlag && file_size(zName)>=0 ){ Blob ans; char *zMsg; @@ -250,10 +256,11 @@ continue; } } if( verbose ) printf("%s\n", &zName[nRepos]); blob_write_to_file(&content, zName); + file_setexe(zName, isExe); blob_reset(&content); db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_mtime(zName), id); } db_finalize(&q);