Fossil

Changes On Branch andygoth-annotation-enhancements
Login

Changes On Branch andygoth-annotation-enhancements

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

Changes In Branch andygoth-annotation-enhancements Excluding Merge-Ins

This is equivalent to a diff from d9ef474a to d2b1565b

2017-09-25
15:18
Merge miscellaneous changes that have accumulated on the annotation-enhancements branch. See a log of the whole branch for details. (check-in: ab8ed318 user: drh tags: trunk)
12:45
Simplified and improved implementation of annotate_file(). (Closed-Leaf check-in: d2b1565b user: drh tags: andygoth-annotation-enhancements)
2017-09-24
10:58
The annotate_file() function is now closer to being correct, but is still not quote right. (check-in: 54c0dab0 user: drh tags: andygoth-annotation-enhancements)
2017-09-23
21:07
Consolidated annotation file and manifest lookup code. This changes the error handling for the annotation web page to report an error rather than redirect to home. It also enables omitting the checkin query parameter when used with a server running within a checkout. (Later, by drh:) Move this code onto a branch because of build errors on Ubuntu. (check-in: 41f35ca4 user: andygoth tags: andygoth-annotation-enhancements)
19:10
Fix the value of the ANN_FILE_VERS flag so that it no longer overlaps with DIFF_SLOW_SBS. Omit the ANN_FILE_ANCEST flag which was always true. Update the annotator so that it follows check-ins in generation order according to the ancestor table. (check-in: d9ef474a user: drh tags: trunk)
18:37
Selectively revert a few changes made by the previous check-in (check-in: b83ea94e user: andygoth tags: trunk)

Changes to src/db.c.

2841
2842
2843
2844
2845
2846
2847
2848

2849
2850
2851
2852
2853
2854
2855
2841
2842
2843
2844
2845
2846
2847

2848
2849
2850
2851
2852
2853
2854
2855







-
+







** If TRUE, the files whose names differ only in case
** are considered distinct.  If FALSE files whose names
** differ only in case are the same file.  Defaults to
** TRUE for unix and FALSE for Cygwin, Mac and Windows.
*/
#endif
/*
** STTING: clean-glob       width=40 versionable block-text
** SETTING: clean-glob      width=40 versionable block-text
** The VALUE of this setting is a comma or newline-separated list of GLOB
** patterns specifying files that the "clean" command will
** delete without prompting or allowing undo.
** Example: *.a,*.lib,*.o
*/
/*
** SETTING: clearsign       boolean default=off
2955
2956
2957
2958
2959
2960
2961
2962

2963
2964
2965
2966
2967
2968
2969
2955
2956
2957
2958
2959
2960
2961

2962
2963
2964
2965
2966
2967
2968
2969







-
+







** If true, then the Fossil web server will redirect unencrypted
** login screeen requests to HTTPS.
*/
/*
** SETTING: ignore-glob      width=40 versionable block-text
** The value is a comma or newline-separated list of GLOB
** patterns specifying files that the "add", "addremove",
** "clean", and "extra" commands will ignore.
** "clean", and "extras" commands will ignore.
**
** Example:  *.log customCode.c notes.txt
*/
/*
** SETTING: keep-glob        width=40 versionable block-text
** The value is a comma or newline-separated list of GLOB
** patterns specifying files that the "clean" command will keep

Changes to src/diff.c.

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
41
42
43
44
45
46
47



48
49
50
51
52
53
54







-
-
-







#define DIFF_NOOPT        (((u64)0x01)<<32) /* Suppress optimizations (debug) */
#define DIFF_INVERT       (((u64)0x02)<<32) /* Invert the diff (debug) */
#define DIFF_CONTEXT_EX   (((u64)0x04)<<32) /* Use context even if zero */
#define DIFF_NOTTOOBIG    (((u64)0x08)<<32) /* Only display if not too big */
#define DIFF_STRIP_EOLCR  (((u64)0x10)<<32) /* Strip trailing CR */
#define DIFF_SLOW_SBS     (((u64)0x20)<<32) /* Better, but slower side-by-side */

/* Annotation flags (any DIFF flag can be used as Annotation flag as well) */
#define ANN_FILE_VERS     (((u64)0x40)<<32) /* File vers not commit vers */

/*
** These error messages are shared in multiple locations.  They are defined
** here for consistency.
*/
#define DIFF_CANNOT_COMPUTE_BINARY \
    "cannot compute difference between binary files\n"

2179
2180
2181
2182
2183
2184
2185
2186
2187


2188
2189
2190
2191
2192


2193
2194
2195
2196
2197


2198

2199
2200
2201

2202


2203

2204
2205


2206




2207
2208

2209
2210




2211
2212



2213
2214
2215
2216
2217

2218
2219
2220
2221

2222
2223
2224
2225
2226





2227
2228


2229
2230

2231
2232


2233
2234
2235
2236
2237
2238









2239
2240
2241
2242
2243
2244


2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2176
2177
2178
2179
2180
2181
2182


2183
2184
2185
2186
2187


2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199


2200
2201
2202
2203

2204


2205
2206

2207
2208
2209
2210
2211

2212


2213
2214
2215
2216


2217
2218
2219





2220

2221

2222
2223





2224
2225
2226
2227
2228
2229

2230
2231
2232

2233


2234
2235
2236
2237

2238


2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252

2253
2254
2255
2256
2257
2258
2259







2260
2261
2262
2263

2264
2265
2266
2267
2268
2269
2270







-
-
+
+



-
-
+
+





+
+

+

-
-
+

+
+
-
+
-
-
+
+
-
+
+
+
+

-
+
-
-
+
+
+
+
-
-
+
+
+
-
-
-
-
-
+
-

-

+
-
-
-
-
-
+
+
+
+
+

-
+
+

-
+
-
-
+
+


-

-
-
+
+
+
+
+
+
+
+
+





-
+
+





-
-
-
-
-
-
-




-








  /* Return no errors */
  return 0;
}


/*
** Compute a complete annotation on a file.  The file is identified
** by its filename number (filename.fnid) and check-in (mlink.mid).
** Compute a complete annotation on a file.  The file is identified by its
** filename and check-in name (NULL for current check-in).
*/
static void annotate_file(
  Annotator *p,        /* The annotator */
  int fnid,            /* The name of the file to be annotated */
  int mid,             /* Use the version of the file in this check-in */
  const char *zFilename,/* The name of the file to be annotated */
  const char *zRevision,/* Use the version of the file in this check-in */
  int iLimit,          /* Limit the number of levels if greater than zero */
  u64 annFlags         /* Flags to alter the annotation */
){
  Blob toAnnotate;     /* Text of the final (mid) version of the file */
  Blob step;           /* Text of previous revision */
  Blob treename;       /* FILENAME translated to canonical form */
  int cid;             /* Selected check-in ID */
  int rid;             /* Artifact ID of the file being annotated */
  int fnid;            /* Filename ID */
  Stmt q;              /* Query returning all ancestor versions */
  Stmt ins;            /* Inserts into the temporary VSEEN table */
  int cnt = 0;         /* Number of versions examined */
  int cnt = 0;         /* Number of versions analyzed */

  if( iLimit<=0 ) iLimit = 1000000000;  /* A negative limit means no limit */
  db_begin_transaction();
  /* Initialize the annotation */

  rid = db_int(0, "SELECT fid FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid);
  if( rid==0 ){
  /* Get the artificate ID for the check-in begin analyzed */
  if( zRevision ){
    fossil_fatal("file #%d is unchanged in manifest #%d", fnid, mid);
    cid = name_to_typed_rid(zRevision, "ci");
  }else{
    db_must_be_within_tree();
    cid = db_lget_int("checkout", 0);
  }
  if( !content_get(rid, &toAnnotate) ){

    fossil_fatal("unable to retrieve content of artifact #%d", rid);
  }
  /* Compute all direct ancestors of the check-in being analyzed into
  ** the "ancestor" table. */
  compute_direct_ancestors(cid);

  if( iLimit<=0 ) iLimit = 1000000000;
  blob_to_utf8_no_bom(&toAnnotate, 0);
  /* Get filename ID */
  file_tree_name(zFilename, &treename, 0, 1);
  zFilename = blob_str(&treename);
  annotation_start(p, &toAnnotate, annFlags);
  db_begin_transaction();
  db_multi_exec(
     "CREATE TEMP TABLE IF NOT EXISTS vseen(rid INTEGER PRIMARY KEY);"
     "DELETE FROM vseen;"
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  );

  db_prepare(&ins, "INSERT OR IGNORE INTO vseen(rid) VALUES(:rid)");
  db_prepare(&q,
    "SELECT"
    "SELECT (SELECT uuid FROM blob WHERE rid=mlink.fid),"
    "       (SELECT uuid FROM blob WHERE rid=mlink.mid),"
    "       date(event.mtime),"
    "       coalesce(event.euser,event.user),"
    "       mlink.pid"
    "   (SELECT uuid FROM blob WHERE rid=mlink.fid),"
    "   (SELECT uuid FROM blob WHERE rid=mlink.mid),"
    "   date(event.mtime),"
    "   coalesce(event.euser,event.user),"
    "   mlink.fid"
    "  FROM mlink, event, ancestor"
    " WHERE mlink.fid=:rid"
    " WHERE mlink.fnid=%d"
    "   AND ancestor.rid=mlink.mid"
    "   AND event.objid=mlink.mid"
    "   AND mlink.pid NOT IN vseen"
    "   AND mlink.mid!=mlink.pid"
    "   AND ancestor.rid=mlink.mid"
    " ORDER BY ancestor.generation;"
    " ORDER BY ancestor.generation;",
    fnid
  );

  db_bind_int(&q, ":rid", rid);
  if( iLimit==0 ) iLimit = 1000000000;
  while( rid && iLimit>cnt && db_step(&q)==SQLITE_ROW ){
    int prevId = db_column_int(&q, 4);
  while( iLimit>cnt && db_step(&q)==SQLITE_ROW ){
    rid = db_column_int(&q, 4);
    if( cnt==0 ){
      if( !content_get(rid, &toAnnotate) ){
        fossil_fatal("unable to retrieve content of artifact #%d", rid);
      }
      blob_to_utf8_no_bom(&toAnnotate, 0);
      annotation_start(p, &toAnnotate, annFlags);
    }
    p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0]));
    p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0));
    p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1));
    p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2));
    p->aVers[p->nVers].zUser = fossil_strdup(db_column_text(&q, 3));
    if( p->nVers ){
    p->nVers++;
    if( cnt>0 ){
      content_get(rid, &step);
      blob_to_utf8_no_bom(&step, 0);
      annotation_step(p, &step, p->nVers-1, annFlags);
      blob_reset(&step);
    }
    p->nVers++;
    db_bind_int(&ins, ":rid", rid);
    db_step(&ins);
    db_reset(&ins);
    db_reset(&q);
    rid = prevId;
    db_bind_int(&q, ":rid", prevId);
    cnt++;
  }
  p->bLimit = iLimit==cnt;
  db_finalize(&q);
  db_finalize(&ins);
  db_end_transaction(0);
}

/*
** Return a color from a gradient.
*/
unsigned gradient_color(unsigned c1, unsigned c2, int n, int i){
2295
2296
2297
2298
2299
2300
2301
2302

2303
2304
2305

2306
2307
2308
2309
2310
2311
2312
2313
2314
2315



2316

2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333

2334

2335
2336


2337
2338
2339
2340

2341
2342
2343
2344

2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358

2359
2360
2361

2362
2363
2364
2365

2366
2367
2368
2369

2370
2371

2372
2373
2374
2375
2376
2377
2378
2379
2297
2298
2299
2300
2301
2302
2303

2304
2305
2306

2307
2308
2309
2310


2311
2312
2313


2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326

2327
2328
2329
2330

2331


2332
2333
2334


2335
2336
2337



2338

2339


2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356


2357




2358




2359


2360

2361
2362
2363
2364
2365
2366
2367







-
+


-
+



-
-



-
-
+
+
+

+








-




-

-
-
+

+
-
-
+
+

-
-
-
+
-

-
-
+














+

-
-
+
-
-
-
-
+
-
-
-
-
+
-
-
+
-







** the date of the changes and the check-in hash (with a link to the
** check-in).  /blame and /praise also show the user who made the check-in.
**
** Query parameters:
**
**    checkin=ID          The manifest ID at which to start the annotation
**    filename=FILENAME   The filename.
**    filevers            Show file versions rather than check-in versions
**    filevers=BOOLEAN    Show file versions rather than check-in versions
**    limit=N             Limit the search depth to N ancestors
**    log=BOOLEAN         Show a log of versions analyzed
**    w                   Ignore whitespace
**    w=BOOLEAN           Ignore whitespace
**
*/
void annotation_page(void){
  int mid;
  int fnid;
  int i;
  int iLimit;            /* Depth limit */
  u64 annFlags = DIFF_STRIP_EOLCR;
  int showLog = 0;       /* True to display the log */
  int ignoreWs = 0;      /* Ignore whitespace */
  int showLog;           /* True to display the log */
  int fileVers;          /* Show file version instead of check-in versions */
  int ignoreWs;          /* Ignore whitespace */
  const char *zFilename; /* Name of file to annotate */
  const char *zRevision; /* Name of check-in from which to start annotation */
  const char *zCI;       /* The check-in containing zFilename */
  Annotator ann;
  HQuery url;
  struct AnnVers *p;
  unsigned clr1, clr2, clr;
  int bBlame = g.zPath[0]!='a';/* True for BLAME output.  False for ANNOTATE. */

  /* Gather query parameters */
  showLog = atoi(PD("log","1"));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( exclude_spiders() ) return;
  load_control();
  mid = name_to_typed_rid(PD("checkin","0"),"ci");
  zFilename = P("filename");
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  if( mid==0 || fnid==0 ){ fossil_redirect_home(); }
  zRevision = PD("checkin",0);
  iLimit = atoi(PD("limit","20"));
  showLog = PB("log");
  if( P("filevers") ) annFlags |= ANN_FILE_VERS;
  ignoreWs = P("w")!=0;
  fileVers = PB("filevers");
  ignoreWs = PB("w");
  if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS;
  if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){
    fossil_redirect_home();
  }


  /* compute the annotation */
  compute_direct_ancestors(mid);
  annotate_file(&ann, fnid, mid, iLimit, annFlags);
  annotate_file(&ann, zFilename, zRevision, iLimit, annFlags);
  zCI = ann.aVers[0].zMUuid;

  /* generate the web page */
  style_header("Annotation For %h", zFilename);
  if( bBlame ){
    url_initialize(&url, "blame");
  }else{
    url_initialize(&url, "annotate");
  }
  url_add_parameter(&url, "checkin", P("checkin"));
  url_add_parameter(&url, "filename", zFilename);
  if( iLimit!=20 ){
    url_add_parameter(&url, "limit", sqlite3_mprintf("%d", iLimit));
  }
  url_add_parameter(&url, "w", ignoreWs ? "1" : "0");
  url_add_parameter(&url, "log", showLog ? "1" : "0");
  if( ignoreWs ){
    url_add_parameter(&url, "w", "");
  url_add_parameter(&url, "filevers", fileVers ? "1" : "0");
    style_submenu_element("Show Whitespace Changes", "%s",
        url_render(&url, "w", 0, 0, 0));
  }else{
    style_submenu_element("Ignore Whitespace", "%s",
  style_submenu_checkbox("w", "Ignore Whitespace", 0);
        url_render(&url, "w", "", 0, 0));
  }
  if( showLog ){
    style_submenu_element("Hide Log", "%s", url_render(&url, "log", "0", 0, 0));
  style_submenu_checkbox("log", "Log", 0);
  }else{
    style_submenu_element("Show Log", "%s", url_render(&url, "log", "1", 0, 0));
  style_submenu_checkbox("filevers", "Link to Files", 0);
  }
  if( ann.bLimit ){
    char *z1, *z2;
    style_submenu_element("All Ancestors", "%s",
       url_render(&url, "limit", "-1", 0, 0));
    z1 = sqlite3_mprintf("%d Ancestors", iLimit+20);
    z2 = sqlite3_mprintf("%d", iLimit+20);
    style_submenu_element(z1, "%s", url_render(&url, "limit", z2, 0, 0));
2439
2440
2441
2442
2443
2444
2445

2446

2447
2448
2449
2450

2451
2452
2453
2454
2455
2456
2457

2458

2459
2460
2461
2462

2463
2464
2465
2466
2467
2468
2469
2427
2428
2429
2430
2431
2432
2433
2434

2435
2436
2437
2438

2439
2440
2441
2442
2443
2444
2445
2446
2447

2448
2449
2450
2451

2452
2453
2454
2455
2456
2457
2458
2459







+
-
+



-
+







+
-
+



-
+







    char zPrefix[300];
    z[n] = 0;
    if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1;

    if( bBlame ){
      if( iVers>=0 ){
        struct AnnVers *p = ann.aVers+iVers;
        const char *zUuid = fileVers ? p->zFUuid : p->zMUuid;
        char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
        char *zLink = xhref("target='infowindow'", "%R/info/%!S", zUuid);
        sqlite3_snprintf(sizeof(zPrefix), zPrefix,
             "<span style='background-color:%s'>"
             "%s%.10s</a> %s</span> %13.13s:",
             p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser);
             p->zBgColor, zLink, zUuid, p->zDate, p->zUser);
        fossil_free(zLink);
      }else{
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", "");
      }
    }else{
      if( iVers>=0 ){
        struct AnnVers *p = ann.aVers+iVers;
        const char *zUuid = fileVers ? p->zFUuid : p->zMUuid;
        char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
        char *zLink = xhref("target='infowindow'", "%R/info/%!S", zUuid);
        sqlite3_snprintf(sizeof(zPrefix), zPrefix,
             "<span style='background-color:%s'>"
             "%s%.10s</a> %s</span> %4d:",
             p->zBgColor, zLink, p->zMUuid, p->zDate, i+1);
             p->zBgColor, zLink, zUuid, p->zDate, i+1);
        fossil_free(zLink);
      }else{
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1);
      }
    }
    @ %s(zPrefix) %h(z)

2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505

2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519

2520
2521
2522

2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594

2595
2596
2597
2598
2599
2600
2601
2483
2484
2485
2486
2487
2488
2489






2490

2491
2492
2493
2494
2495
2496
2497
2498


2499
2500

2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522





















































2523

2524
2525
2526
2527
2528
2529
2530
2531







-
-
-
-
-
-
+
-








-
-


-
+



+

















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
+







**   -n|--limit N                Only look backwards in time by N versions
**   -w|--ignore-all-space       Ignore white space when comparing lines
**   -Z|--ignore-trailing-space  Ignore whitespace at line end
**
** See also: info, finfo, timeline
*/
void annotate_cmd(void){
  int fnid;         /* Filename ID */
  int fid;          /* File instance ID */
  int mid;          /* Manifest where file was checked in */
  int cid;          /* Checkout or selected check-in ID */
  Blob treename;    /* FILENAME translated to canonical form */
  const char *zRev; /* Revision name, or NULL for current check-in */
  const char *zRevision; /* Revision name, or NULL for current check-in */
  char *zFilename;  /* Canonical filename */
  Annotator ann;    /* The annotation of the file */
  int i;            /* Loop counter */
  const char *zLimit; /* The value to the -n|--limit option */
  int iLimit;       /* How far back in time to look */
  int showLog;      /* True to show the log */
  int fileVers;     /* Show file version instead of check-in versions */
  u64 annFlags = 0; /* Flags to control annotation properties */
  int bBlame = 0;   /* True for BLAME output.  False for ANNOTATE. */
  Manifest *pManifest; /* Manifest structure */
  ManifestFile *pFile; /* Manifest file pointer */

  bBlame = g.argv[1][0]!='a';
  zRev = find_option("r","revision",1);
  zRevision = find_option("r","revision",1);
  zLimit = find_option("limit","n",1);
  if( zLimit==0 || zLimit[0]==0 ) zLimit = "-1";
  iLimit = atoi(zLimit);
  if( iLimit<=0 ) iLimit = 1000000000;
  showLog = find_option("log","l",0)!=0;
  if( find_option("ignore-trailing-space","Z",0)!=0 ){
    annFlags = DIFF_IGNORE_EOLWS;
  }
  if( find_option("ignore-all-space","w",0)!=0 ){
    annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
  }
  fileVers = find_option("filevers",0,0)!=0;
  db_must_be_within_tree();

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc<3 ) {
    usage("FILENAME");
  }

  /* Get filename ID */
  file_tree_name(g.argv[2], &treename, 0, 1);
  zFilename = blob_str(&treename);
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  if( fnid==0 ){
    fossil_fatal("no such file: %s", zFilename);
  }

  /* Get artifact IDs of selected check-in and file */
  if( zRev ){
    /* Get artifact ID of selected check-in manifest */
    cid = name_to_typed_rid(zRev, "ci");

    /* Get manifest structure for selected check-in */
    pManifest = manifest_get(cid, CFTYPE_MANIFEST, 0);
    if( !pManifest ){
      fossil_fatal("could not parse manifest for check-in: %s", zRev);
    }
    
    /* Get selected file in manifest */
    pFile = manifest_file_find(pManifest, zFilename);
    if( !pFile ){
      fossil_fatal("file %s does not exist in check-in %s", zFilename, zRev);
    }
    manifest_destroy(pManifest);

    /* Get file instance ID from manifest file record */
    fid = fast_uuid_to_rid(pFile->zUuid);
  }else{
    /* Get artifact ID of current checkout manifest */
    cid = db_lget_int("checkout", 0);
    if( cid == 0 ){
      fossil_fatal("not in a checkout");
    }

    /* Get file instance ID from current checkout file table */
    fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename);
    if( fid==0 ){
      fossil_fatal("not part of current checkout: %s", zFilename);
    }
  }

  /* Get ID of most recent manifest containing a change to the selected file */
  compute_direct_ancestors(cid);
  mid = db_int(0, "SELECT mlink.mid FROM mlink, ancestor "
          " WHERE mlink.fid=%d AND mlink.fnid=%d AND mlink.mid=ancestor.rid"
          " ORDER BY ancestor.generation ASC LIMIT 1",
          fid, fnid);
  if( mid==0 ){
    fossil_fatal("unable to find manifest");
  }

  if( iLimit<=0 ) iLimit = 1000000000;
  annFlags |= DIFF_STRIP_EOLCR;
  annotate_file(&ann, fnid, mid, iLimit, annFlags);
  annotate_file(&ann, g.argv[2], zRevision, iLimit, annFlags);
  if( showLog ){
    struct AnnVers *p;
    for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
      fossil_print("version %3d: %s %S file %S\n",
                   i+1, p->zDate, p->zMUuid, p->zFUuid);
    }
    fossil_print("---------------------------------------------------\n");

Changes to src/info.c.

1069
1070
1071
1072
1073
1074
1075
1076

1077
1078
1079
1080
1081
1082
1083
1084
1085


1086
1087
1088
1089
1090
1091
1092
1069
1070
1071
1072
1073
1074
1075

1076
1077
1078
1079
1080
1081
1082
1083


1084
1085
1086
1087
1088
1089
1090
1091
1092







-
+







-
-
+
+







**   from=TAG        Left side of the comparison
**   to=TAG          Right side of the comparison
**   branch=TAG      Show all changes on a particular branch
**   v=BOOLEAN       Default true.  If false, only list files that have changed
**   sbs=BOOLEAN     Side-by-side diff if true.  Unified diff if false
**   glob=STRING     only diff files matching this glob
**   dc=N            show N lines of context around each diff
**   w               ignore whitespace when computing diffs
**   w=BOOLEAN       ignore whitespace when computing diffs
**   nohdr           omit the description at the top of the page
**
**
** Show all differences between two check-ins.
*/
void vdiff_page(void){
  int ridFrom, ridTo;
  int verboseFlag = 0;
  int sideBySide = 0;
  int verboseFlag;
  int sideBySide;
  u64 diffFlags = 0;
  Manifest *pFrom, *pTo;
  ManifestFile *pFileFrom, *pFileTo;
  const char *zBranch;
  const char *zFrom;
  const char *zTo;
  const char *zRe;
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165

1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1152
1153
1154
1155
1156
1157
1158







1159




1160
1161
1162
1163
1164
1165
1166







-
-
-
-
-
-
-
+
-
-
-
-







    style_submenu_element("Clear glob",
                          "%R/vdiff?from=%T&to=%T&sbs=%d%s%s", zFrom, zTo,
                          sideBySide, (verboseFlag && !sideBySide)?"&v":"", zW);
  }else{
    style_submenu_element("Patch", "%R/vpatch?from=%T&to=%T%s", zFrom, zTo, zW);
  }
  if( sideBySide || verboseFlag ){
    if( *zW ){
      style_submenu_element("Show Whitespace Differences",
                            "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T", zFrom, zTo,
                            sideBySide, (verboseFlag && !sideBySide)?"&v":"",
                            zGlob ? "&glob=" : "", zGlob ? zGlob : "");
    }else{
      style_submenu_element("Ignore Whitespace",
    style_submenu_checkbox("w", "Ignore Whitespace", 0);
                            "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T&w", zFrom, zTo,
                            sideBySide, (verboseFlag && !sideBySide)?"&v":"",
                            zGlob ? "&glob=" : "", zGlob ? zGlob : "");
    }
  }
  style_header("Check-in Differences");
  if( P("nohdr")==0 ){
    @ <h2>Difference From:</h2><blockquote>
    checkin_description(ridFrom);
    @ </blockquote><h2>To:</h2><blockquote>
    checkin_description(ridTo);
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556






1557
1558
1559
1560
1561



1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582

1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603

1604
1605

1606
1607
1608
1609

1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1534
1535
1536
1537
1538
1539
1540






1541
1542
1543
1544
1545
1546
1547
1548
1549


1550
1551
1552
1553
1554
1555

1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571

1572

1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585

1586
1587
1588
1589
1590

1591


1592




1593



1594
1595









1596
1597
1598
1599
1600
1601
1602







-
-
-
-
-
-
+
+
+
+
+
+



-
-
+
+
+



-
















-
+
-













-





-
+
-
-
+
-
-
-
-
+
-
-
-


-
-
-
-
-
-
-
-
-







** If the "from" and "to" query parameters are both present, then they are
** the names of two files within the check-in "ci" that are diffed.  If the
** "ci" parameter is omitted, then the most recent check-in ("tip") is
** used.
**
** Additional parameters:
**
**      dc=N         Show N lines of context around each diff
**      patch        Use the patch diff format
**      regex=REGEX  Only show differences that match REGEX
**      sbs=BOOLEAN  Turn side-by-side diffs on and off (default: on)
**      verbose      Show more detail when describing artifacts
**      w            Ignore whitespace
**      dc=N             Show N lines of context around each diff
**      patch            Use the patch diff format
**      regex=REGEX      Only show differences that match REGEX
**      sbs=BOOLEAN      Turn side-by-side diffs on and off (default: on)
**      verbose=BOOLEAN  Show more detail when describing artifacts
**      w=BOOLEAN        Ignore whitespace
*/
void diff_page(void){
  int v1, v2;
  int isPatch;
  int sideBySide;
  int isPatch = P("patch")!=0;
  int sideBySide = PB("sbs");
  int verbose = PB("verbose");
  char *zV1;
  char *zV2;
  const char *zRe;
  const char *zW;      /* URL param for ignoring whitespace */
  ReCompiled *pRe = 0;
  u64 diffFlags;
  u32 objdescFlags = 0;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( P("from") && P("to") ){
    v1 = artifact_from_ci_and_filename(0, "from");
    v2 = artifact_from_ci_and_filename(0, "to");
  }else{
    v1 = name_to_rid_www("v1");
    v2 = name_to_rid_www("v2");
  }
  if( v1==0 || v2==0 ) fossil_redirect_home();
  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
  if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  if( verbose ) objdescFlags |= OBJDESC_DETAIL;
  isPatch = P("patch")!=0;
  if( isPatch ){
    Blob c1, c2, *pOut;
    pOut = cgi_output_blob();
    cgi_set_content_type("text/plain");
    diffFlags = 4;
    content_get(v1, &c1);
    content_get(v2, &c2);
    text_diff(&c1, &c2, pOut, pRe, diffFlags);
    blob_reset(&c1);
    blob_reset(&c2);
    return;
  }

  sideBySide = !is_false(PD("sbs","1"));
  zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
  zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
  diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML;

  style_header("Diff");
  zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  style_submenu_checkbox("w", "Ignore Whitespace", 0);
  if( *zW ){
    style_submenu_element("Show Whitespace Changes",
  style_submenu_checkbox("sbs", "Side-by-Side Diff", 0);
                          "%s/fdiff?v1=%T&v2=%T&sbs=%d",
                          g.zTop, P("v1"), P("v2"), sideBySide);
  }else{
    style_submenu_element("Ignore Whitespace",
  style_submenu_checkbox("verbose", "Verbose", 0);
                          "%s/fdiff?v1=%T&v2=%T&sbs=%d&w",
                          g.zTop, P("v1"), P("v2"), sideBySide);
  }
  style_submenu_element("Patch", "%s/fdiff?v1=%T&v2=%T&patch",
                        g.zTop, P("v1"), P("v2"));
  if( !sideBySide ){
    style_submenu_element("Side-by-Side Diff",
                          "%s/fdiff?v1=%T&v2=%T&sbs=1%s",
                          g.zTop, P("v1"), P("v2"), zW);
  }else{
    style_submenu_element("Unified Diff",
                          "%s/fdiff?v1=%T&v2=%T&sbs=0%s",
                          g.zTop, P("v1"), P("v2"), zW);
  }

  if( P("smhdr")!=0 ){
    @ <h2>Differences From Artifact
    @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
    @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
  }else{
    @ <h2>Differences From

Changes to www/globs.md.

46
47
48
49
50
51
52


53
54
55
56




57
58
59
60
61
62
63
46
47
48
49
50
51
52
53
54




55
56
57
58
59
60
61
62
63
64
65







+
+
-
-
-
-
+
+
+
+







Ordinary characters consume a single character of the target and must
match it exactly.

Special characters (and special character sequences) consume zero or
more characters from the target and describe what matches. The special
characters (and sequences) are:

:Pattern |:Effect
---------------------------------------------------------------------
 *  `*` Matches any sequence of zero or more characters;
 *  `?` Matches exactly one character;
 *  `[...]` Matches one character from the enclosed list of characters; and
 *  `[^...]` Matches one character not in the enclosed list.
`*`      | Matches any sequence of zero or more characters
`?`      | Matches exactly one character
`[...]`  | Matches one character from the enclosed list of characters
`[^...]` | Matches one character not in the enclosed list

Special character sequences have some additional features:

 *  A range of characters may be specified with `-`, so `[a-d]` matches
    exactly the same characters as `[abcd]`. Ranges reflect Unicode
    code points without any locale-specific collation sequence.
 *  Include `-` in a list by placing it last, just before the `]`.
75
76
77
78
79
80
81


82
83


84
85
86
87
88
89
90






91
92
93
94
95
96
97
77
78
79
80
81
82
83
84
85


86
87







88
89
90
91
92
93
94
95
96
97
98
99
100







+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+







 *  Note that unlike typical Unix shell globs, wildcards (`*`, `?`,
    and character lists) are allowed to match `/` directory
    separators as well as the initial `.` in the name of a hidden
    file or directory.

Some examples of character lists:

:Pattern |:Effect
---------------------------------------------------------------------
 *  `[a-d]` Matches any one of `a`, `b`, `c`, or `d` but not `ä`;
 *  `[^a-d]` Matches exactly one character other than `a`, `b`, `c`,
`[a-d]`  | Matches any one of `a`, `b`, `c`, or `d` but not `ä`
`[^a-d]` | Matches exactly one character other than `a`, `b`, `c`, or `d`
    or `d`;
 *  `[0-9a-fA-F]` Matches exactly one hexadecimal digit;
 *  `[a-]` Matches either `a` or `-`;
 *  `[][]` Matches either `]` or `[`;
 *  `[^]]` Matches exactly one character other than `]`;
 *  `[]^]` Matches either `]` or `^`; and
 *  `[^-]` Matches exactly one character other than `-`.
`[0-9a-fA-F]` | Matches exactly one hexadecimal digit
`[a-]`   | Matches either `a` or `-`
`[][]`   | Matches either `]` or `[`
`[^]]`   | Matches exactly one character other than `]`
`[]^]`   | Matches either `]` or `^`
`[^-]`   | Matches exactly one character other than `-`

White space means the specific ASCII characters TAB, LF, VT, FF, CR,
and SPACE.  Note that this does not include any of the many additional
spacing characters available in Unicode, and specifically does not
include U+00A0 NO-BREAK SPACE.

Because both LF and CR are white space and leading and trailing spaces
125
126
127
128
129
130
131
132
133



134
135
136
137

138
139
140
141
142

143
144
145
146

147
148
149

150
151
152
153
154
155
156
157
158


159
160
161
162
163
164
165







166
167
168
169
170
171
172
128
129
130
131
132
133
134


135
136
137




138





139




140



141


142
143
144
145
146
147
148
149
150







151
152
153
154
155
156
157
158
159
160
161
162
163
164







-
-
+
+
+
-
-
-
-
+
-
-
-
-
-
+
-
-
-
-
+
-
-
-
+
-
-







+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+







not be a surprise on Unix where all file names are also case
sensitive. However, most Windows file systems are case preserving and
case insensitive. That is, on Windows, the names `ReadMe` and `README`
are names of the same file; on Unix they are different files.

Some example cases:

 *  The glob `README` matches only a file named `README` in the root of
    the tree. It does not match a file named `src/README` because it
:Pattern     |:Effect
--------------------------------------------------------------------------------
`README`     | Matches only a file named `README` in the root of the tree. It does not match a file named `src/README` because it does not include any characters that consume (and match) the `src/` part.
    does not include any characters that consume (and match) the
    `src/` part.
 *  The glob `*/README` does match `src/README`. Unlike Unix file
    globs, it also matches `src/library/README`. However it does not
`*/README`   | Matches `src/README`. Unlike Unix file globs, it also matches `src/library/README`. However it does not match the file `README` in the root of the tree.
    match the file `README` in the root of the tree.
 *  The glob `*README` does match `src/README` as well as the file
    `README` in the root of the tree as well as `foo/bar/README` or
    any other file named `README` in the tree. However, it also
    matches `A-DIFFERENT-README` and `src/DO-NOT-README`, or any other
`*README`    | Matches `src/README` as well as the file `README` in the root of the tree as well as `foo/bar/README` or any other file named `README` in the tree. However, it also matches `A-DIFFERENT-README` and `src/DO-NOT-README`, or any other file whose name ends with `README`.
    file whose name ends with `README`.
 *  The glob `src/README` does match the file named `src\README` on
    Windows because all directory separators are rewritten as `/` in
    the canonical name before the glob is matched. This makes it much
`src/README` | Matches `src\README` on Windows because all directory separators are rewritten as `/` in the canonical name before the glob is matched. This makes it much easier to write globs that work on both Unix and Windows.
    easier to write globs that work on both Unix and Windows.
 *  The glob `*.[ch]` matches every C source or header file in the
    tree at the root or at any depth. Again, this is (deliberately)
`*.[ch]`     | Matches every C source or header file in the tree at the root or at any depth. Again, this is (deliberately) different from Unix file globs and Windows wild cards.
    different from Unix file globs and Windows wild cards.


## Where Globs are Used

### Settings that are Globs

These settings are all lists of glob patterns:

:Setting        |:Description
--------------------------------------------------------------------------------
 *  `binary-glob`
 *  `clean-glob`
 *  `crlf-glob`
 *  `crnl-glob`
 *  `encoding-glob`
 *  `ignore-glob`
 *  `keep-glob`
`binary-glob`   | Files that should be treated as binary files for committing and merging purposes
`clean-glob`    | Files that the [`clean`][] command will delete without prompting or allowing undo
`crlf-glob`     | Files in which it is okay to have `CR`, `CR`+`LF` or mixed line endings.  Set to "`*`" to disable CR+LF checking
`crnl-glob`     | Alias for the `crlf-glob` setting
`encoding-glob` | Files that the [`commit`][] command will ignore when issuing warnings about text files that may use another encoding than ASCII or UTF-8.  Set to "`*`" to disable encoding checking
`ignore-glob`   | Files that the [`add`][], [`addremove`][], [`clean`][], and [`extras`][] commands will ignore
`keep-glob`     | Files that the [`clean`][] command will keep

All may be [versioned, local, or global](settings.wiki). Use `fossil
settings` to manage local and global settings, or a file in the
repository's `.fossil-settings/` folder at the root of the tree named
for each for versioned setting.

Using versioned settings for these not only has the advantage that
189
190
191
192
193
194
195

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215

216
217
218
219
220
221
222
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216







+




















+







usually named to correspond to the setting they override, such as
`--ignore` to override the `ignore-glob` setting. These commands are:

 *  [`add`][]
 *  [`addremove`][]
 *  [`changes`][]
 *  [`clean`][]
 *  [`commit`][]
 *  [`extras`][]
 *  [`merge`][]
 *  [`settings`][]
 *  [`status`][]
 *  [`unset`][]

The commands [`tarball`][] and [`zip`][] produce compressed archives of a
specific checkin. They may be further restricted by options that
specify glob patterns that name files to include or exclude rather
than archiving the entire checkin.

The commands [`http`][], [`cgi`][], [`server`][], and [`ui`][] that
implement or support with web servers provide a mechanism to name some
files to serve with static content where a list of glob patterns
specifies what content may be served.

[`add`]: /help?cmd=add
[`addremove`]: /help?cmd=addremove
[`changes`]: /help?cmd=changes
[`clean`]: /help?cmd=clean
[`commit`]: /help?cmd=commit
[`extras`]: /help?cmd=extras
[`merge`]: /help?cmd=merge
[`settings`]: /help?cmd=settings
[`status`]: /help?cmd=status
[`unset`]: /help?cmd=unset

[`tarball`]: /help?cmd=tarball
515
516
517
518
519
520
521
522
523
524




525
526
527
528
529


530
531
532
533
534

535
536
537
538
539
540




509
510
511
512
513
514
515



516
517
518
519

520



521
522
523
524
525
526

527






528
529
530
531







-
-
-
+
+
+
+
-

-
-
-
+
+




-
+
-
-
-
-
-
-
+
+
+
+
a glob pattern. Find commands and pages in the fossil sources by
looking for comments like `COMMAND: add` or `WEBPAGE: timeline` in
front of the function that implements the command or page in files
`src/*.c`. (Fossil's build system creates the tables used to dispatch
commands at build time by searching the sources for those comments.) A
few starting points:

 *  [`src/glob.c`][glob.c] implements glob pattern list loading,
    parsing, and matching.
 *  [`src/file.c`][file.c] implements various kinds of canonical
:File            |:Description
--------------------------------------------------------------------------------
[`src/glob.c`][] | Implementation of glob pattern list loading, parsing, and matching.
[`src/file.c`][] | Implementation of various kinds of canonical names of a file.
    names of a file.


[glob.c]: https://www.fossil-scm.org/index.html/file/src/glob.c
[file.c]: https://www.fossil-scm.org/index.html/file/src/file.c
[`src/glob.c`]: https://www.fossil-scm.org/index.html/file/src/glob.c
[`src/file.c`]: https://www.fossil-scm.org/index.html/file/src/file.c

The actual pattern matching is implemented in SQL, so the
documentation for `GLOB` and the other string matching operators in
[SQLite] (https://sqlite.org/lang_expr.html#like) is useful. Of
course, the SQLite source code and test harnesses also make
course, the SQLite [source code]
entertaining reading:

 *  `src/func.c` [lines 570-768]
    (https://www.sqlite.org/src/artifact?name=9d52522cc8ae7f5c&ln=570-768)
 *  `test/expr.test` [lines 586-673]
    (https://www.sqlite.org/src/artifact?name=66a2c9ac34f74f03&ln=586-673)
(https://www.sqlite.org/src/artifact?name=9d52522cc8ae7f5c&ln=570-768)
and [test harnesses]
(https://www.sqlite.org/src/artifact?name=66a2c9ac34f74f03&ln=586-673)
also make entertaining reading.