Fossil

Check-in [39feb892]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Enhance the "fossil extra" and "fossil clean" commands to restrict output to files and directories named on the command-line. Enhancement request ticket [967cedbf200f7].
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:39feb8926e6c6172ce1db717530dd96d6479f49b
User & Date: drh 2013-06-20 11:46:56
Context
2013-06-20
13:18
Fixed modulo/divide by 0 in TH1, per ML bug report from Eduardo Morras. check-in: fded1d05 user: stephan tags: trunk
11:46
Enhance the "fossil extra" and "fossil clean" commands to restrict output to files and directories named on the command-line. Enhancement request ticket [967cedbf200f7]. check-in: 39feb892 user: drh tags: trunk
11:44
Factor out common parts of "fossil extra" and "fossil clean" into a subroutine. Combine vfile_scan2() into vfile_scan(). Closed-Leaf check-in: 69327d27 user: drh tags: ticket-967cedbf20
00:34
Changes to a timeline query to ensure the correct join order even if ANALYZE has not been run on the repository database. check-in: 85d960e2 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/add.c.

281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
...
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
    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_scan2(&fullName, nRoot-1, scanFlags, pClean, 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(
................................................................................
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)",
                filename_collation());
  n = strlen(g.zLocalRoot);
  blob_init(&path, g.zLocalRoot, n-1);
  /* now we read the complete file structure into a temp table */
  pClean = glob_create(zCleanFlag);
  pIgnore = glob_create(zIgnoreFlag);
  vfile_scan2(&path, blob_size(&path), scanFlags, pClean, pIgnore);
  glob_free(pIgnore);
  glob_free(pClean);
  nAdd = add_files_in_sfile(vid);

  /* step 2: search for missing files */
  db_prepare(&q,
      "SELECT pathname, %Q || pathname, deleted FROM vfile"







|







 







|







281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
...
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
    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, pClean, 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(
................................................................................
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)",
                filename_collation());
  n = strlen(g.zLocalRoot);
  blob_init(&path, g.zLocalRoot, n-1);
  /* now we read the complete file structure into a temp table */
  pClean = glob_create(zCleanFlag);
  pIgnore = glob_create(zIgnoreFlag);
  vfile_scan(&path, blob_size(&path), scanFlags, pClean, pIgnore);
  glob_free(pIgnore);
  glob_free(pClean);
  nAdd = add_files_in_sfile(vid);

  /* step 2: search for missing files */
  db_prepare(&q,
      "SELECT pathname, %Q || pathname, deleted FROM vfile"

Changes to src/checkin.c.

295
296
297
298
299
300
301
302
303
















































304
305
306
307
308

309
310
311
312
313
314
315
...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
...
375
376
377
378
379
380
381
382
383
384
385
386

387
388
389
390
391
392
393
...
415
416
417
418
419
420
421
422
423
424
425

426
427
428
429
430
431
432
...
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469

470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
    }else{
      fossil_print("UNCHANGED  %s\n", zPathname);
    }
    free(zFullName);
  }
  db_finalize(&q);
}

/*
















































** COMMAND: extras
** Usage: %fossil extras ?OPTIONS?
**
** Print a list of all files in the source tree that are not part of
** the current checkout.  See also the "clean" command.

**
** Files and subdirectories whose names begin with "." are normally
** ignored but can be included by adding the --dotfiles option.
**
** The GLOBPATTERN is a comma-separated list of GLOB expressions for
** files that are ignored.  The GLOBPATTERN specified by the "ignore-glob"
** is used if the --ignore option is omitted.
................................................................................
**    --ignore <CSG>   ignore files matching patterns from the argument
**    --rel-paths      Display pathnames relative to the current working
**                     directory.
**
** See also: changes, clean, status
*/
void extra_cmd(void){
  Blob path;
  Stmt q;
  int n;
  const char *zIgnoreFlag = find_option("ignore",0,1);
  unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0;
  int cwdRelative = 0;
  Glob *pIgnore;
  Blob rewrittenPathname;
  const char *zPathname, *zDisplayName;

  if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
  capture_case_sensitive_option();
  db_must_be_within_tree();
  cwdRelative = determine_cwd_relative_option();
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)",
                filename_collation());
  n = strlen(g.zLocalRoot);
  blob_init(&path, g.zLocalRoot, n-1);
  if( zIgnoreFlag==0 ){
    zIgnoreFlag = db_get("ignore-glob", 0);
  }
  pIgnore = glob_create(zIgnoreFlag);
  vfile_scan(&path, blob_size(&path), scanFlags, pIgnore);
  glob_free(pIgnore);
  db_prepare(&q,
      "SELECT x FROM sfile"
      " WHERE x NOT IN (%s)"
      " ORDER BY 1",
      fossil_all_reserved_names(0)
  );
................................................................................
  }
  blob_reset(&rewrittenPathname);
  db_finalize(&q);
}

/*
** COMMAND: clean
** Usage: %fossil clean ?OPTIONS?
**
** Delete all "extra" files in the source tree.  "Extra" files are
** files that are not officially part of the checkout. This operation
** cannot be undone.

**
** You will be prompted before removing each eligible file unless the
** --force flag is in use or it matches the --clean option.  The
** GLOBPATTERN specified by the "ignore-glob" setting is used if the
** --ignore option is omitted, the same with "clean-glob" and --clean
** as well as "keep-glob" and --keep.  If you are sure you wish to
** remove all "extra" files except the ones specified with --ignore
................................................................................
**
** See also: addremove, extra, status
*/
void clean_cmd(void){
  int allFlag, dryRunFlag, verboseFlag;
  unsigned scanFlags = 0;
  const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
  Blob path, repo;
  Stmt q;
  int n;
  Glob *pIgnore, *pKeep, *pClean;


  dryRunFlag = find_option("dry-run","n",0)!=0;
  if( !dryRunFlag ){
    dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
  }
  allFlag = find_option("force","f",0)!=0;
  if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
................................................................................
  if( zKeepFlag==0 ){
    zKeepFlag = db_get("keep-glob", 0);
  }
  if( zCleanFlag==0 ){
    zCleanFlag = db_get("clean-glob", 0);
  }
  verify_all_options();
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)",
                filename_collation());
  n = strlen(g.zLocalRoot);
  blob_init(&path, g.zLocalRoot, n-1);
  pIgnore = glob_create(zIgnoreFlag);
  pKeep = glob_create(zKeepFlag);
  pClean = glob_create(zCleanFlag);
  vfile_scan2(&path, blob_size(&path), scanFlags, pIgnore, pKeep);
  glob_free(pKeep);
  glob_free(pIgnore);
  db_prepare(&q,
      "SELECT %Q || x FROM sfile"
      " WHERE x NOT IN (%s)"
      " ORDER BY 1",
      g.zLocalRoot, fossil_all_reserved_names(0)
  );
  if( file_tree_name(g.zRepositoryName, &repo, 0) ){
    db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo);
  }
  db_multi_exec("DELETE FROM sfile WHERE x IN (SELECT pathname FROM vfile)");

  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    if( !allFlag && !dryRunFlag && !glob_match(pClean, zName+n) ){
      Blob ans;
      char cReply;
      char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ",
                             zName+n);
      blob_zero(&ans);
      prompt_user(prompt, &ans);
      cReply = blob_str(&ans)[0];
      if( cReply=='a' || cReply=='A' ){
        allFlag = 1;
      }else if( cReply!='y' && cReply!='Y' ){
        blob_reset(&ans);
        continue;
      }
      blob_reset(&ans);
    }
    if( verboseFlag || dryRunFlag ){
      fossil_print("Removed unmanaged file: %s\n", zName+n);
    }
    if( !dryRunFlag ){
      file_delete(zName);
    }
  }
  glob_free(pClean);
  db_finalize(&q);









>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|


|
>







 







<

<











<
<
<
<




|







 







|



|
>







 







|

<

>







 







<
<
<
<



|












>


|



|












|







295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
...
373
374
375
376
377
378
379

380

381
382
383
384
385
386
387
388
389
390
391




392
393
394
395
396
397
398
399
400
401
402
403
...
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
...
459
460
461
462
463
464
465
466
467

468
469
470
471
472
473
474
475
476
...
487
488
489
490
491
492
493




494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
    }else{
      fossil_print("UNCHANGED  %s\n", zPathname);
    }
    free(zFullName);
  }
  db_finalize(&q);
}

/*
** Create a TEMP table named SFILE and add all unmanaged files named on the command-line 
** to that table.  If directories are named, then add all unmanged files contained
** underneath those directories.  If there are no files or directories named on the
** command-line, then add all unmanaged files anywhere in the checkout.
*/
static void locate_unmanaged_files(
  int argc,              /* Number of command-line arguments to examine */
  char **argv,           /* values of command-line arguments */
  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 */
){
  Blob name;      /* Name of a candidate file or directory */
  char *zName;    /* Name of a candidate file or directory */
  int isDir;      /* 1 for a directory, 0 if doesn't exist, 2 for anything else */
  int i;          /* Loop counter */
  int nRoot;      /* length of g.zLocalRoot */

  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)",
                filename_collation());
  nRoot = (int)strlen(g.zLocalRoot);
  if( argc==0 ){
    blob_init(&name, g.zLocalRoot, nRoot - 1);
    vfile_scan(&name, blob_size(&name), scanFlags, pIgnore1, pIgnore2);
    blob_reset(&name);
  }else{
    for(i=0; i<argc; i++){
      file_canonical_name(argv[i], &name, 0);
      zName = blob_str(&name);
      isDir = file_wd_isdir(zName);
      if( isDir==1 ){
        vfile_scan(&name, nRoot-1, scanFlags, pIgnore1, pIgnore2);
      }else if( isDir==0 ){
        fossil_warning("not found: %s", zName);
      }else if( file_access(zName, R_OK) ){
        fossil_fatal("cannot open %s", zName);
      }else{
        db_multi_exec(
           "INSERT OR IGNORE INTO sfile(x) VALUES(%Q)",
           &zName[nRoot]
        );
      }
      blob_reset(&name);
    }
  }
}

/*
** COMMAND: extras
** Usage: %fossil extras ?OPTIONS? ?PATH1 ...?
**
** Print a list of all files in the source tree that are not part of
** the current checkout.  See also the "clean" command. If paths are
** specified, only files in the given directories will be listed.
**
** Files and subdirectories whose names begin with "." are normally
** ignored but can be included by adding the --dotfiles option.
**
** The GLOBPATTERN is a comma-separated list of GLOB expressions for
** files that are ignored.  The GLOBPATTERN specified by the "ignore-glob"
** is used if the --ignore option is omitted.
................................................................................
**    --ignore <CSG>   ignore files matching patterns from the argument
**    --rel-paths      Display pathnames relative to the current working
**                     directory.
**
** See also: changes, clean, status
*/
void extra_cmd(void){

  Stmt q;

  const char *zIgnoreFlag = find_option("ignore",0,1);
  unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0;
  int cwdRelative = 0;
  Glob *pIgnore;
  Blob rewrittenPathname;
  const char *zPathname, *zDisplayName;

  if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
  capture_case_sensitive_option();
  db_must_be_within_tree();
  cwdRelative = determine_cwd_relative_option();




  if( zIgnoreFlag==0 ){
    zIgnoreFlag = db_get("ignore-glob", 0);
  }
  pIgnore = glob_create(zIgnoreFlag);
  locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, 0);
  glob_free(pIgnore);
  db_prepare(&q,
      "SELECT x FROM sfile"
      " WHERE x NOT IN (%s)"
      " ORDER BY 1",
      fossil_all_reserved_names(0)
  );
................................................................................
  }
  blob_reset(&rewrittenPathname);
  db_finalize(&q);
}

/*
** COMMAND: clean
** Usage: %fossil clean ?OPTIONS? ?PATH1 ...?
**
** Delete all "extra" files in the source tree.  "Extra" files are
** files that are not officially part of the checkout. This operation
** cannot be undone. If paths are specified, only the directories or
** files specified will be considered for cleaning.
**
** You will be prompted before removing each eligible file unless the
** --force flag is in use or it matches the --clean option.  The
** GLOBPATTERN specified by the "ignore-glob" setting is used if the
** --ignore option is omitted, the same with "clean-glob" and --clean
** as well as "keep-glob" and --keep.  If you are sure you wish to
** remove all "extra" files except the ones specified with --ignore
................................................................................
**
** See also: addremove, extra, status
*/
void clean_cmd(void){
  int allFlag, dryRunFlag, verboseFlag;
  unsigned scanFlags = 0;
  const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
  Blob repo;
  Stmt q;

  Glob *pIgnore, *pKeep, *pClean;
  int nRoot;

  dryRunFlag = find_option("dry-run","n",0)!=0;
  if( !dryRunFlag ){
    dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
  }
  allFlag = find_option("force","f",0)!=0;
  if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
................................................................................
  if( zKeepFlag==0 ){
    zKeepFlag = db_get("keep-glob", 0);
  }
  if( zCleanFlag==0 ){
    zCleanFlag = db_get("clean-glob", 0);
  }
  verify_all_options();




  pIgnore = glob_create(zIgnoreFlag);
  pKeep = glob_create(zKeepFlag);
  pClean = glob_create(zCleanFlag);
  locate_unmanaged_files(g.argc-2, g.argv+2, scanFlags, pIgnore, pKeep);
  glob_free(pKeep);
  glob_free(pIgnore);
  db_prepare(&q,
      "SELECT %Q || x FROM sfile"
      " WHERE x NOT IN (%s)"
      " ORDER BY 1",
      g.zLocalRoot, fossil_all_reserved_names(0)
  );
  if( file_tree_name(g.zRepositoryName, &repo, 0) ){
    db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo);
  }
  db_multi_exec("DELETE FROM sfile WHERE x IN (SELECT pathname FROM vfile)");
  nRoot = (int)strlen(g.zLocalRoot);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    if( !allFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
      Blob ans;
      char cReply;
      char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ",
                             zName+nRoot);
      blob_zero(&ans);
      prompt_user(prompt, &ans);
      cReply = blob_str(&ans)[0];
      if( cReply=='a' || cReply=='A' ){
        allFlag = 1;
      }else if( cReply!='y' && cReply!='Y' ){
        blob_reset(&ans);
        continue;
      }
      blob_reset(&ans);
    }
    if( verboseFlag || dryRunFlag ){
      fossil_print("Removed unmanaged file: %s\n", zName+nRoot);
    }
    if( !dryRunFlag ){
      file_delete(zName);
    }
  }
  glob_free(pClean);
  db_finalize(&q);

Changes to src/vfile.c.

430
431
432
433
434
435
436
437
438
439
440
441
442
443

444
445
446
447
448
449
450
451
452
453
...
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
**
** 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, unsigned scanFlags, Glob *pIgnore){
  vfile_scan2(pPath, nPrefix, scanFlags, pIgnore, 0);
}

void vfile_scan2(
  Blob *pPath,
  int nPrefix,

  unsigned scanFlags,
  Glob *pIgnore1,
  Glob *pIgnore2
){
  DIR *d;
  int origSize;
  const char *zDir;
  struct dirent *pEntry;
  int skipAll = 0;
  static Stmt ins;
................................................................................
      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_scan2(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);
        }







<
<
<
<
|
|
<
>
|
|
|







 







|







430
431
432
433
434
435
436




437
438

439
440
441
442
443
444
445
446
447
448
449
...
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
**
** 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,           /* 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;
  const char *zDir;
  struct dirent *pEntry;
  int skipAll = 0;
  static Stmt ins;
................................................................................
      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);
        }