/* ** Make sure empty directories are created */ void ensure_empty_dirs_created(void){ /* Make empty directories? */ char *zEmptyDirs = db_get("empty-dirs", 0); if( zEmptyDirs!=0 ){ char *bc; Blob dirName; Blob dirsList; blob_zero(&dirsList); blob_init(&dirsList, zEmptyDirs, strlen(zEmptyDirs)); /* Replace commas by spaces */ bc = blob_str(&dirsList); while( (*bc)!='\0' ){ if( (*bc)==',' ) { *bc = ' '; } ++bc; } /* Make directories */ blob_zero(&dirName); while( blob_token(&dirsList, &dirName) ){ const char *zDir = blob_str(&dirName); /* Make full pathname of the directory */ Blob path; const char *zPath; blob_zero(&path); blob_appendf(&path, "%s/%s", g.zLocalRoot, zDir); zPath = blob_str(&path); /* Handle various cases of existence of the directory */ switch( file_wd_isdir(zPath) ){ case 0: { /* doesn't exist */ if( file_mkdir(zPath, 0)!=0 ) { fossil_warning("couldn't create directory %s as " "required by empty-dirs setting", zDir); } break; } case 1: { /* exists, and is a directory */ /* do nothing - required directory exists already */ break; } case 2: { /* exists, but isn't a directory */ fossil_warning("file %s found, but a directory is required " "by empty-dirs setting", zDir); } } blob_reset(&path); } } }
/* ** Create empty directories specified by the empty-dirs setting. */ void ensure_empty_dirs_created(void) { char *zEmptyDirs = db_get("empty-dirs", 0); if( zEmptyDirs!=0 ) { int i; Blob dirName; Blob dirsList; zEmptyDirs = fossil_strdup(zEmptyDirs); for(i=0; zEmptyDirs[i]; i++) { if( zEmptyDirs[i]==',' ) zEmptyDirs[i] = ' '; } blob_init(&dirsList, zEmptyDirs, -1); while( blob_token(&dirsList, &dirName) ) { char *zDir = blob_str(&dirName); char *zPath = mprintf("%s/%s", g.zLocalRoot, zDir); switch( file_wd_isdir(zPath) ) { case 0: { /* doesn't exist */ fossil_free(zPath); zPath = mprintf("%s/%s/x", g.zLocalRoot, zDir); if( file_mkfolder(zPath, 0, 1)!=0 ) { fossil_warning("couldn't create directory %s as " "required by empty-dirs setting", zDir); } break; } case 1: { /* exists, and is a directory */ /* do nothing - required directory exists already */ break; } case 2: { /* exists, but isn't a directory */ fossil_warning("file %s found, but a directory is required " "by empty-dirs setting", zDir); } } fossil_free(zPath); blob_reset(&dirName); } blob_reset(&dirsList); fossil_free(zEmptyDirs); } }
/* ** Check to see if the directory named in zPath is the top of a checkout. ** In other words, check to see if directory pPath contains a file named ** "_vcs_" or ".fslckout". Return true or false. */ int vfile_top_of_checkout(const char *zPath){ char *zFile; int fileFound = 0; zFile = mprintf("%s/_vcs_", zPath); fileFound = file_size(zFile)>=1024; vcs_free(zFile); if( !fileFound ){ zFile = mprintf("%s/.fslckout", zPath); fileFound = file_size(zFile)>=1024; vcs_free(zFile); } /* ** Load into table SFILE the name of every ordinary file in ** the directory pPath. Omit the first nPrefix characters of ** of pPath when inserting into the SFILE table. ** ** Subdirectories are scanned recursively. ** Omit files named in VFILE. ** ** Files whose names begin with "." are omitted unless allFlag is true. ** ** Any files or directories that match the glob pattern pIgnore are ** excluded from the scan. Name matching occurs after the first ** nPrefix characters are elided from the filename. */ void vfile_scan(Blob *pPath, int nPrefix, int allFlag, Glob *pIgnore){ DIR *d; int origSize; const char *zDir; struct dirent *pEntry; int skipAll = 0; static Stmt ins; static int depth = 0; char *zMbcs; origSize = blob_size(pPath); if( pIgnore ){ blob_appendf(pPath, "/"); if( glob_match(pIgnore, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1; blob_resize(pPath, origSize); } if( skipAll ) return; if( depth==0 ){ db_prepare(&ins, "INSERT OR IGNORE INTO sfile(x) SELECT :file" " WHERE NOT EXISTS(SELECT 1 FROM vfile WHERE pathname=:file)" ); } depth++; zDir = blob_str(pPath); zMbcs = vcs_utf8_to_mbcs(zDir); d = opendir(zMbcs); if( d ){ while( (pEntry=readdir(d))!=0 ){ char *zPath; char *zUtf8; if( pEntry->d_name[0]=='.' ){ if( !allFlag ) continue; if( pEntry->d_name[1]==0 ) continue; if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue; } zUtf8 = vcs_mbcs_to_utf8(pEntry->d_name); blob_appendf(pPath, "/%s", zUtf8); vcs_mbcs_free(zUtf8); zPath = blob_str(pPath); if( glob_match(pIgnore, &zPath[nPrefix+1]) ){ /* do nothing */ }else if( file_wd_isdir(zPath)==1 ){ if( !vfile_top_of_checkout(zPath) ){ vfile_scan(pPath, nPrefix, allFlag, pIgnore); } }else if( file_wd_isfile_or_link(zPath) ){ db_bind_text(&ins, ":file", &zPath[nPrefix+1]); db_step(&ins); db_reset(&ins); } blob_resize(pPath, origSize); } closedir(d); } vcs_mbcs_free(zMbcs); depth--; if( depth==0 ){ db_finalize(&ins); } }
/* ** Write all files from vid to the disk. Or if vid==0 and id!=0 ** write just the specific file where VFILE.ID=id. */ void vfile_to_disk( int vid, /* vid to write to disk */ int id, /* Write this one file, if not zero */ int verbose, /* Output progress information */ int promptFlag /* Prompt user to confirm overwrites */ ){ Stmt q; Blob content; int nRepos = strlen(g.zLocalRoot); if( vid>0 && id==0 ){ db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink" " 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, isexe, islink" " FROM vfile" " WHERE id=%d AND mrid>0", g.zLocalRoot, id); } while( db_step(&q)==SQLITE_ROW ){ int id, rid, isExe, isLink; 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); isLink = db_column_int(&q, 4); content_get(rid, &content); if( file_is_the_same(&content, zName) ){ blob_reset(&content); if( file_wd_setexe(zName, isExe) ){ db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_wd_mtime(zName), id); } continue; } if( promptFlag && file_wd_size(zName)>=0 ){ Blob ans; char *zMsg; char cReply; zMsg = mprintf("overwrite %s (a=always/y/N)? ", zName); prompt_user(zMsg, &ans); free(zMsg); cReply = blob_str(&ans)[0]; blob_reset(&ans); if( cReply=='a' || cReply=='A' ){ promptFlag = 0; cReply = 'y'; } if( cReply=='n' || cReply=='N' ){ blob_reset(&content); continue; } } if( verbose ) vcs_print("%s\n", &zName[nRepos]); if( file_wd_isdir(zName) == 1 ){ /*TODO(dchest): remove directories? */ vcs_fatal("%s is directory, cannot overwrite\n", zName); } if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){ file_delete(zName); } if( isLink ){ symlink_create(blob_str(&content), zName); }else{ blob_write_to_file(&content, zName); } file_wd_setexe(zName, isExe); blob_reset(&content); db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_wd_mtime(zName), id); } db_finalize(&q); }
/* ** COMMAND: mv ** COMMAND: rename* ** ** Usage: %fossil mv|rename OLDNAME NEWNAME ** or: %fossil mv|rename OLDNAME... DIR ** ** Move or rename one or more files or directories within the repository tree. ** You can either rename a file or directory or move it to another subdirectory. ** ** This command does NOT rename or move the files on disk. This command merely ** records the fact that filenames have changed so that appropriate notations ** can be made at the next commit/checkin. ** ** See also: changes, status */ void mv_cmd(void){ int i; int vid; char *zDest; Blob dest; Stmt q; db_must_be_within_tree(); vid = db_lget_int("checkout", 0); if( vid==0 ){ fossil_panic("no checkout rename files in"); } if( g.argc<4 ){ usage("OLDNAME NEWNAME"); } zDest = g.argv[g.argc-1]; db_begin_transaction(); file_tree_name(zDest, &dest, 1); db_multi_exec( "UPDATE vfile SET origname=pathname WHERE origname IS NULL;" ); db_multi_exec( "CREATE TEMP TABLE mv(f TEXT UNIQUE ON CONFLICT IGNORE, t TEXT);" ); if( file_wd_isdir(zDest)!=1 ){ Blob orig; if( g.argc!=4 ){ usage("OLDNAME NEWNAME"); } file_tree_name(g.argv[2], &orig, 1); db_multi_exec( "INSERT INTO mv VALUES(%B,%B)", &orig, &dest ); }else{ if( blob_eq(&dest, ".") ){ blob_reset(&dest); }else{ blob_append(&dest, "/", 1); } for(i=2; i<g.argc-1; i++){ Blob orig; char *zOrig; int nOrig; file_tree_name(g.argv[i], &orig, 1); zOrig = blob_str(&orig); nOrig = blob_size(&orig); db_prepare(&q, "SELECT pathname FROM vfile" " WHERE vid=%d" " AND (pathname='%q' OR (pathname>'%q/' AND pathname<'%q0'))" " ORDER BY 1", vid, zOrig, zOrig, zOrig ); while( db_step(&q)==SQLITE_ROW ){ const char *zPath = db_column_text(&q, 0); int nPath = db_column_bytes(&q, 0); const char *zTail; if( nPath==nOrig ){ zTail = file_tail(zPath); }else{ zTail = &zPath[nOrig+1]; } db_multi_exec( "INSERT INTO mv VALUES('%q','%q%q')", zPath, blob_str(&dest), zTail ); } db_finalize(&q); } } db_prepare(&q, "SELECT f, t FROM mv ORDER BY f"); while( db_step(&q)==SQLITE_ROW ){ const char *zFrom = db_column_text(&q, 0); const char *zTo = db_column_text(&q, 1); mv_one_file(vid, zFrom, zTo); } db_finalize(&q); db_end_transaction(0); }
/* ** COMMAND: add ** ** Usage: %fossil add ?OPTIONS? FILE1 ?FILE2 ...? ** ** Make arrangements to add one or more files or directories to the ** current checkout at the next commit. ** ** When adding files or directories recursively, filenames that begin ** with "." are excluded by default. To include such files, add ** the "--dotfiles" option to the command-line. ** ** The --ignore option is a comma-separate list of glob patterns for files ** to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore option ** does not appear on the command line then the "ignore-glob" setting is ** used. ** ** The --case-sensitive option determines whether or not filenames should ** be treated case sensitive or not. If the option is not given, the default ** depends on the global setting, or the operating system default, if not set. ** ** Options: ** ** --case-sensitive <BOOL> override case-sensitive setting ** --dotfiles include files beginning with a dot (".") ** --ignore <CSG> ignore files matching patterns from the ** comma separated list of glob patterns. ** ** See also: addremove, rm */ void add_cmd(void){ int i; /* Loop counter */ int vid; /* Currently checked out version */ int nRoot; /* Full path characters in g.zLocalRoot */ const char *zIgnoreFlag; /* The --ignore option or ignore-glob setting */ Glob *pIgnore; /* Ignore everything matching this glob pattern */ int caseSensitive; /* True if filenames are case sensitive */ unsigned scanFlags = 0; /* Flags passed to vfile_scan() */ zIgnoreFlag = find_option("ignore",0,1); if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL; capture_case_sensitive_option(); db_must_be_within_tree(); caseSensitive = filenames_are_case_sensitive(); if( zIgnoreFlag==0 ){ zIgnoreFlag = db_get("ignore-glob", 0); } vid = db_lget_int("checkout",0); if( vid==0 ){ fossil_panic("no checkout to add to"); } db_begin_transaction(); db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)"); #if defined(_WIN32) db_multi_exec( "CREATE INDEX IF NOT EXISTS vfile_pathname " " ON vfile(pathname COLLATE nocase)" ); #endif pIgnore = glob_create(zIgnoreFlag); nRoot = strlen(g.zLocalRoot); /* Load the names of all files that are to be added into sfile temp table */ for(i=2; i<g.argc; i++){ char *zName; int isDir; Blob fullName; file_canonical_name(g.argv[i], &fullName, 0); zName = blob_str(&fullName); isDir = file_wd_isdir(zName); if( isDir==1 ){ vfile_scan(&fullName, nRoot-1, scanFlags, pIgnore); }else if( isDir==0 ){ fossil_warning("not found: %s", zName); }else if( file_access(zName, R_OK) ){ fossil_fatal("cannot open %s", zName); }else{ char *zTreeName = &zName[nRoot]; db_multi_exec( "INSERT OR IGNORE INTO sfile(x) VALUES(%Q)", zTreeName ); } blob_reset(&fullName); } glob_free(pIgnore); add_files_in_sfile(vid, caseSensitive); db_end_transaction(0); }
/* ** COMMAND: update ** ** Usage: %fossil update ?OPTIONS? ?VERSION? ?FILES...? ** ** Change the version of the current checkout to VERSION. Any ** uncommitted changes are retained and applied to the new checkout. ** ** The VERSION argument can be a specific version or tag or branch ** name. If the VERSION argument is omitted, then the leaf of the ** subtree that begins at the current version is used, if there is ** only a single leaf. VERSION can also be "current" to select the ** leaf of the current version or "latest" to select the most recent ** check-in. ** ** If one or more FILES are listed after the VERSION then only the ** named files are candidates to be updated, and any updates to them ** will be treated as edits to the current version. Using a directory ** name for one of the FILES arguments is the same as using every ** subdirectory and file beneath that directory. ** ** If FILES is omitted, all files in the current checkout are subject ** to being updated and the version of the current checkout is changed ** to VERSION. Any uncommitted changes are retained and applied to the ** new checkout. ** ** The -n or --dry-run option causes this command to do a "dry run". ** It prints out what would have happened but does not actually make ** any changes to the current checkout or the repository. ** ** The -v or --verbose option prints status information about ** unchanged files in addition to those file that actually do change. ** ** Options: ** --case-sensitive <BOOL> override case-sensitive setting ** --debug print debug information on stdout ** --latest acceptable in place of VERSION, update to latest version ** --force-missing force update if missing content after sync ** -n|--dry-run If given, display instead of run actions ** -v|--verbose print status information about all files ** -W|--width <num> Width of lines (default is to auto-detect). Must be >20 ** or 0 (= no limit, resulting in a single line per entry). ** ** See also: revert */ void update_cmd(void) { int vid; /* Current version */ int tid=0; /* Target version - version we are changing to */ Stmt q; int latestFlag; /* --latest. Pick the latest version if true */ int dryRunFlag; /* -n or --dry-run. Do a dry run */ int verboseFlag; /* -v or --verbose. Output extra information */ int forceMissingFlag; /* --force-missing. Continue if missing content */ int debugFlag; /* --debug option */ int setmtimeFlag; /* --setmtime. Set mtimes on files */ int nChng; /* Number of file renames */ int *aChng; /* Array of file renames */ int i; /* Loop counter */ int nConflict = 0; /* Number of merge conflicts */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ int nUpdate = 0; /* Number of changes of any kind */ int width; /* Width of printed comment lines */ Stmt mtimeXfer; /* Statement to transfer mtimes */ const char *zWidth; /* Width option string value */ if( !internalUpdate ) { undo_capture_command_line(); url_proxy_options(); } zWidth = find_option("width","W",1); if( zWidth ) { width = atoi(zWidth); if( (width!=0) && (width<=20) ) { fossil_fatal("-W|--width value must be >20 or 0"); } } else { width = -1; } latestFlag = find_option("latest",0, 0)!=0; dryRunFlag = find_option("dry-run","n",0)!=0; if( !dryRunFlag ) { dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ } verboseFlag = find_option("verbose","v",0)!=0; forceMissingFlag = find_option("force-missing",0,0)!=0; debugFlag = find_option("debug",0,0)!=0; setmtimeFlag = find_option("setmtime",0,0)!=0; /* We should be done with options.. */ verify_all_options(); db_must_be_within_tree(); vid = db_lget_int("checkout", 0); user_select(); if( !dryRunFlag && !internalUpdate ) { if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, db_get_int("autosync-tries", 1)) ) { fossil_fatal("Cannot proceed with update"); } } /* Create any empty directories now, as well as after the update, ** so changes in settings are reflected now */ if( !dryRunFlag ) ensure_empty_dirs_created(); if( internalUpdate ) { tid = internalUpdate; } else if( g.argc>=3 ) { if( fossil_strcmp(g.argv[2], "current")==0 ) { /* If VERSION is "current", then use the same algorithm to find the ** target as if VERSION were omitted. */ } else if( fossil_strcmp(g.argv[2], "latest")==0 ) { /* If VERSION is "latest", then use the same algorithm to find the ** target as if VERSION were omitted and the --latest flag is present. */ latestFlag = 1; } else { tid = name_to_typed_rid(g.argv[2],"ci"); if( tid==0 || !is_a_version(tid) ) { fossil_fatal("no such check-in: %s", g.argv[2]); } } } /* If no VERSION is specified on the command-line, then look for a ** descendent of the current version. If there are multiple descendants, ** look for one from the same branch as the current version. If there ** are still multiple descendants, show them all and refuse to update ** until the user selects one. */ if( tid==0 ) { int closeCode = 1; compute_leaves(vid, closeCode); if( !db_exists("SELECT 1 FROM leaves") ) { closeCode = 0; compute_leaves(vid, closeCode); } if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ) { db_multi_exec( "DELETE FROM leaves WHERE rid NOT IN" " (SELECT leaves.rid FROM leaves, tagxref" " WHERE leaves.rid=tagxref.rid AND tagxref.tagid=%d" " AND tagxref.value==(SELECT value FROM tagxref" " WHERE tagid=%d AND rid=%d))", TAG_BRANCH, TAG_BRANCH, vid ); if( db_int(0, "SELECT count(*) FROM leaves")>1 ) { compute_leaves(vid, closeCode); db_prepare(&q, "%s " " AND event.objid IN leaves" " ORDER BY event.mtime DESC", timeline_query_for_tty() ); print_timeline(&q, -100, width, 0); db_finalize(&q); fossil_fatal("Multiple descendants"); } } tid = db_int(0, "SELECT rid FROM leaves, event" " WHERE event.objid=leaves.rid" " ORDER BY event.mtime DESC"); if( tid==0 ) tid = vid; } if( tid==0 ) { return; } db_begin_transaction(); vfile_check_signature(vid, CKSIG_ENOTFILE); if( !dryRunFlag && !internalUpdate ) undo_begin(); if( load_vfile_from_rid(tid) && !forceMissingFlag ) { fossil_fatal("missing content, unable to update"); }; /* ** The record.fn field is used to match files against each other. The ** FV table contains one row for each each unique filename in ** in the current checkout, the pivot, and the version being merged. */ db_multi_exec( "DROP TABLE IF EXISTS fv;" "CREATE TEMP TABLE fv(" " fn TEXT %s PRIMARY KEY," /* The filename relative to root */ " idv INTEGER," /* VFILE entry for current version */ " idt INTEGER," /* VFILE entry for target version */ " chnged BOOLEAN," /* True if current version has been edited */ " islinkv BOOLEAN," /* True if current file is a link */ " islinkt BOOLEAN," /* True if target file is a link */ " ridv INTEGER," /* Record ID for current version */ " ridt INTEGER," /* Record ID for target */ " isexe BOOLEAN," /* Does target have execute permission? */ " deleted BOOLEAN DEFAULT 0,"/* File marked by "rm" to become unmanaged */ " fnt TEXT %s" /* Filename of same file on target version */ ");", filename_collation(), filename_collation() ); /* Add files found in the current version */ db_multi_exec( "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged,deleted)" " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged, deleted" " FROM vfile WHERE vid=%d", vid ); /* Compute file name changes on V->T. Record name changes in files that ** have changed locally. */ if( vid ) { find_filename_changes(vid, tid, 1, &nChng, &aChng, debugFlag ? "V->T": 0); if( nChng ) { for(i=0; i<nChng; i++) { db_multi_exec( "UPDATE fv" " SET fnt=(SELECT name FROM filename WHERE fnid=%d)" " WHERE fn=(SELECT name FROM filename WHERE fnid=%d) AND chnged", aChng[i*2+1], aChng[i*2] ); } fossil_free(aChng); } } /* 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,isexe,chnged)" " SELECT pathname, pathname, 0, 0, 0, 0, isexe, 0 FROM vfile" " WHERE vid=%d" " AND pathname %s NOT IN (SELECT fnt FROM fv)", tid, filename_collation() ); /* ** Compute the file version ids for T */ db_multi_exec( "UPDATE fv SET" " idt=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnt=pathname),0)," " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnt=pathname),0)", tid, tid ); /* ** Add islink information */ db_multi_exec( "UPDATE fv SET" " islinkv=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnt=pathname),0)," " islinkt=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnt=pathname),0)", vid, tid ); if( debugFlag ) { db_prepare(&q, "SELECT rowid, fn, fnt, chnged, ridv, ridt, isexe," " islinkv, islinkt FROM fv" ); while( db_step(&q)==SQLITE_ROW ) { fossil_print("%3d: ridv=%-4d ridt=%-4d chnged=%d isexe=%d" " islinkv=%d islinkt=%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, 6), db_column_int(&q, 7), db_column_int(&q, 8)); fossil_print(" fnv = [%s]\n", db_column_text(&q, 1)); fossil_print(" fnt = [%s]\n", db_column_text(&q, 2)); } db_finalize(&q); } /* If FILES appear on the command-line, remove from the "fv" table ** every entry that is not named on the command-line or which is not ** in a directory named on the command-line. */ if( g.argc>=4 ) { Blob sql; /* SQL statement to purge unwanted entries */ Blob treename; /* Normalized filename */ int i; /* Loop counter */ const char *zSep; /* Term separator */ blob_zero(&sql); blob_append(&sql, "DELETE FROM fv WHERE ", -1); zSep = ""; for(i=3; i<g.argc; i++) { file_tree_name(g.argv[i], &treename, 0, 1); if( file_wd_isdir(g.argv[i])==1 ) { if( blob_size(&treename) != 1 || blob_str(&treename)[0] != '.' ) { blob_append_sql(&sql, "%sfn NOT GLOB '%q/*' ", zSep /*safe-for-%s*/, blob_str(&treename)); } else { blob_reset(&sql); break; } } else { blob_append_sql(&sql, "%sfn<>%Q ", zSep /*safe-for-%s*/, blob_str(&treename)); } zSep = "AND "; blob_reset(&treename); } db_multi_exec("%s", blob_sql_text(&sql)); blob_reset(&sql); } /* ** Alter the content of the checkout so that it conforms with the ** target */ db_prepare(&q, "SELECT fn, idv, ridv, idt, ridt, chnged, fnt," " isexe, islinkv, islinkt, deleted FROM fv ORDER BY 1" ); db_prepare(&mtimeXfer, "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)" " WHERE id=:idt" ); assert( g.zLocalRoot!=0 ); assert( strlen(g.zLocalRoot)>0 ); assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' ); while( db_step(&q)==SQLITE_ROW ) { const char *zName = db_column_text(&q, 0); /* The filename from root */ int idv = db_column_int(&q, 1); /* VFILE entry for current */ 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, 7); /* EXE perm for new file */ int islinkv = db_column_int(&q, 8); /* Is current file is a link */ int islinkt = db_column_int(&q, 9); /* Is target file is a link */ int deleted = db_column_int(&q, 10); /* Marked for deletion */ 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); zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); nameChng = fossil_strcmp(zName, zNewName); nUpdate++; if( deleted ) { db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt); } if( idv>0 && ridv==0 && idt>0 && ridt>0 ) { /* Conflict. This file has been added to the current checkout ** but also exists in the target checkout. Use the current version. */ fossil_print("CONFLICT %s\n", zName); nConflict++; } else if( idt>0 && idv==0 ) { /* File added in the target. */ if( file_wd_isfile_or_link(zFullPath) ) { fossil_print("ADD %s - overwrites an unmanaged file\n", zName); nOverwrite++; } else { fossil_print("ADD %s\n", zName); } if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); } else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ) { /* The file is unedited. Change it to the target version */ if( deleted ) { fossil_print("UPDATE %s - change to unmanaged file\n", zName); } else { fossil_print("UPDATE %s\n", zName); } if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); } else if( idt>0 && idv>0 && !deleted && file_wd_size(zFullPath)<0 ) { /* The file missing from the local check-out. Restore it to the ** version that appears in the target. */ fossil_print("UPDATE %s\n", zName); if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); } else if( idt==0 && idv>0 ) { if( ridv==0 ) { /* Added in current checkout. Continue to hold the file as ** as an addition */ db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv); } else if( chnged ) { /* Edited locally but deleted from the target. Do not track the ** file but keep the edited version around. */ fossil_print("CONFLICT %s - edited locally but deleted by update\n", zName); nConflict++; } else { fossil_print("REMOVE %s\n", zName); if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) file_delete(zFullPath); } } else if( idt>0 && idv>0 && ridt!=ridv && chnged ) { /* Merge the changes in the current tree into the target version */ Blob r, t, v; int rc; if( nameChng ) { fossil_print("MERGE %s -> %s\n", zName, zNewName); } else { fossil_print("MERGE %s\n", zName); } if( islinkv || islinkt /* || file_wd_islink(zFullPath) */ ) { fossil_print("***** Cannot merge symlink %s\n", zNewName); nConflict++; } else { unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; if( !dryRunFlag && !internalUpdate ) undo_save(zName); content_get(ridt, &t); content_get(ridv, &v); rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags); if( rc>=0 ) { if( !dryRunFlag ) { blob_write_to_file(&r, zFullNewPath); file_wd_setexe(zFullNewPath, isexe); } if( rc>0 ) { fossil_print("***** %d merge conflicts in %s\n", rc, zNewName); nConflict++; } } else { if( !dryRunFlag ) { blob_write_to_file(&t, zFullNewPath); file_wd_setexe(zFullNewPath, isexe); } fossil_print("***** Cannot merge binary file %s\n", zNewName); nConflict++; } } if( nameChng && !dryRunFlag ) file_delete(zFullPath); blob_reset(&v); blob_reset(&t); blob_reset(&r); } else { nUpdate--; if( chnged ) { if( verboseFlag ) fossil_print("EDITED %s\n", zName); } else { db_bind_int(&mtimeXfer, ":idv", idv); db_bind_int(&mtimeXfer, ":idt", idt); db_step(&mtimeXfer); db_reset(&mtimeXfer); if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName); } } free(zFullPath); free(zFullNewPath); } db_finalize(&q); db_finalize(&mtimeXfer); fossil_print("%.79c\n",'-'); if( nUpdate==0 ) { show_common_info(tid, "checkout:", 1, 0); fossil_print("%-13s None. Already up-to-date\n", "changes:"); } else { show_common_info(tid, "updated-to:", 1, 0); fossil_print("%-13s %d file%s modified.\n", "changes:", nUpdate, nUpdate>1 ? "s" : ""); } /* Report on conflicts */ if( !dryRunFlag ) { Stmt q; int nMerge = 0; db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" " WHERE id<=0"); while( db_step(&q)==SQLITE_ROW ) { const char *zLabel = "merge"; switch( db_column_int(&q, 1) ) { case -1: zLabel = "cherrypick merge"; break; case -2: zLabel = "backout merge"; break; } fossil_warning("uncommitted %s against %S.", zLabel, db_column_text(&q, 0)); nMerge++; } db_finalize(&q); leaf_ambiguity_warning(tid, tid); if( nConflict ) { if( internalUpdate ) { internalConflictCnt = nConflict; nConflict = 0; } else { fossil_warning("WARNING: %d merge conflicts", nConflict); } } if( nOverwrite ) { fossil_warning("WARNING: %d unmanaged files were overwritten", nOverwrite); } if( nMerge ) { fossil_warning("WARNING: %d uncommitted prior merges", nMerge); } } /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ if( dryRunFlag ) { db_end_transaction(1); /* With --dry-run, rollback changes */ } else { ensure_empty_dirs_created(); if( g.argc<=3 ) { /* All files updated. Shift the current checkout to the target. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid); checkout_set_all_exe(tid); manifest_to_disk(tid); db_lset_int("checkout", tid); } else { /* A subset of files have been checked out. Keep the current ** checkout unchanged. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); } if( !internalUpdate ) undo_finish(); if( setmtimeFlag ) vfile_check_signature(tid, CKSIG_SETMTIME); db_end_transaction(0); } }
/* ** Scans the specified base directory for any directories within it, while ** keeping a count of how many files they each contains, either directly or ** indirectly. ** ** Subdirectories are scanned recursively. ** Omit files named in VFILE. ** ** Directories whose names begin with "." are omitted unless the SCAN_ALL ** flag is set. ** ** Any directories that match the glob patterns pIgnore* are excluded from ** the scan. Name matching occurs after the first nPrefix characters are ** elided from the filename. ** ** Returns the total number of files found. */ int vfile_dir_scan( Blob *pPath, /* Base directory to be scanned */ int nPrefix, /* Number of bytes in base directory name */ unsigned scanFlags, /* Zero or more SCAN_xxx flags */ Glob *pIgnore1, /* Do not add directories that match this GLOB */ Glob *pIgnore2, /* Omit directories matching this GLOB too */ Glob *pIgnore3 /* Omit directories matching this GLOB too */ ){ int result = 0; DIR *d; int origSize; struct dirent *pEntry; int skipAll = 0; static Stmt ins; static Stmt upd; static int depth = 0; void *zNative; origSize = blob_size(pPath); if( pIgnore1 || pIgnore2 || pIgnore3 ){ blob_appendf(pPath, "/"); if( glob_match(pIgnore1, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1; if( glob_match(pIgnore2, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1; if( glob_match(pIgnore3, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1; blob_resize(pPath, origSize); } if( skipAll ) return result; if( depth==0 ){ db_multi_exec("DROP TABLE IF EXISTS dscan_temp;" "CREATE TEMP TABLE dscan_temp(" " x TEXT PRIMARY KEY %s, y INTEGER)", filename_collation()); db_prepare(&ins, "INSERT OR IGNORE INTO dscan_temp(x, y) SELECT :file, :count" " WHERE NOT EXISTS(SELECT 1 FROM vfile WHERE" " pathname GLOB :file || '/*' %s)", filename_collation() ); db_prepare(&upd, "UPDATE OR IGNORE dscan_temp SET y = coalesce(y, 0) + 1" " WHERE x=:file %s", filename_collation() ); } depth++; zNative = fossil_utf8_to_filename(blob_str(pPath)); d = opendir(zNative); if( d ){ while( (pEntry=readdir(d))!=0 ){ char *zOrigPath; char *zPath; char *zUtf8; if( pEntry->d_name[0]=='.' ){ if( (scanFlags & SCAN_ALL)==0 ) continue; if( pEntry->d_name[1]==0 ) continue; if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue; } zOrigPath = mprintf("%s", blob_str(pPath)); zUtf8 = fossil_filename_to_utf8(pEntry->d_name); blob_appendf(pPath, "/%s", zUtf8); zPath = blob_str(pPath); if( glob_match(pIgnore1, &zPath[nPrefix+1]) || glob_match(pIgnore2, &zPath[nPrefix+1]) || glob_match(pIgnore3, &zPath[nPrefix+1]) ){ /* do nothing */ }else if( file_wd_isdir(zPath)==1 ){ if( (scanFlags & SCAN_NESTED) || !vfile_top_of_checkout(zPath) ){ char *zSavePath = mprintf("%s", zPath); int count = vfile_dir_scan(pPath, nPrefix, scanFlags, pIgnore1, pIgnore2, pIgnore3); db_bind_text(&ins, ":file", &zSavePath[nPrefix+1]); db_bind_int(&ins, ":count", count); db_step(&ins); db_reset(&ins); fossil_free(zSavePath); result += count; /* found X normal files? */ } }else if( file_wd_isfile_or_link(zPath) ){ db_bind_text(&upd, ":file", zOrigPath); db_step(&upd); db_reset(&upd); result++; /* found 1 normal file */ } fossil_filename_free(zUtf8); blob_resize(pPath, origSize); fossil_free(zOrigPath); } closedir(d); } fossil_filename_free(zNative); depth--; if( depth==0 ){ db_finalize(&upd); db_finalize(&ins); } return result; }
/* ** Load into table SFILE the name of every ordinary file in ** the directory pPath. Omit the first nPrefix characters of ** of pPath when inserting into the SFILE table. ** ** Subdirectories are scanned recursively. ** Omit files named in VFILE. ** ** Files whose names begin with "." are omitted unless the SCAN_ALL ** flag is set. ** ** Any files or directories that match the glob patterns pIgnore* ** are excluded from the scan. Name matching occurs after the ** first nPrefix characters are elided from the filename. */ void vfile_scan( Blob *pPath, /* Directory to be scanned */ int nPrefix, /* Number of bytes in directory name */ unsigned scanFlags, /* Zero or more SCAN_xxx flags */ Glob *pIgnore1, /* Do not add files that match this GLOB */ Glob *pIgnore2 /* Omit files matching this GLOB too */ ){ DIR *d; int origSize; struct dirent *pEntry; int skipAll = 0; static Stmt ins; static int depth = 0; void *zNative; origSize = blob_size(pPath); if( pIgnore1 || pIgnore2 ){ blob_appendf(pPath, "/"); if( glob_match(pIgnore1, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1; if( glob_match(pIgnore2, &blob_str(pPath)[nPrefix+1]) ) skipAll = 1; blob_resize(pPath, origSize); } if( skipAll ) return; if( depth==0 ){ db_prepare(&ins, "INSERT OR IGNORE INTO sfile(x) SELECT :file" " WHERE NOT EXISTS(SELECT 1 FROM vfile WHERE" " pathname=:file %s)", filename_collation() ); } depth++; zNative = fossil_utf8_to_filename(blob_str(pPath)); d = opendir(zNative); if( d ){ while( (pEntry=readdir(d))!=0 ){ char *zPath; char *zUtf8; if( pEntry->d_name[0]=='.' ){ if( (scanFlags & SCAN_ALL)==0 ) continue; if( pEntry->d_name[1]==0 ) continue; if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue; } zUtf8 = fossil_filename_to_utf8(pEntry->d_name); blob_appendf(pPath, "/%s", zUtf8); zPath = blob_str(pPath); if( glob_match(pIgnore1, &zPath[nPrefix+1]) || glob_match(pIgnore2, &zPath[nPrefix+1]) ){ /* do nothing */ }else if( file_wd_isdir(zPath)==1 ){ if( !vfile_top_of_checkout(zPath) ){ vfile_scan(pPath, nPrefix, scanFlags, pIgnore1, pIgnore2); } }else if( file_wd_isfile_or_link(zPath) ){ if( (scanFlags & SCAN_TEMP)==0 || is_temporary_file(zUtf8) ){ db_bind_text(&ins, ":file", &zPath[nPrefix+1]); db_step(&ins); db_reset(&ins); } } fossil_filename_free(zUtf8); blob_resize(pPath, origSize); } closedir(d); } fossil_filename_free(zNative); depth--; if( depth==0 ){ db_finalize(&ins); } }