Fossil

Check-in [4564790e]
Login

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

Overview
Comment:Add hyperlinks to the tree-view for file listings. Other improvements to the tree-view mechanism.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:4564790ed7bcda5013725d4d1136e4fbc2372500
User & Date: drh 2013-12-31 13:59:00
Context
2014-01-01
00:48
Add TH1 variables for style-related URLs (/style.css, /logo, /background) that contain the resources' mtimes. Maintains aggressive caching without requiring users to do a hard refresh or clear their cache when the resource is changed. check-in: daff9d20 user: joel tags: trunk
2013-12-31
13:59
Add hyperlinks to the tree-view for file listings. Other improvements to the tree-view mechanism. check-in: 4564790e user: drh tags: trunk
10:43
Add the /tree URI for showing a hierarchical file listing. The URI works, but there are no hyperlinks to it yet. check-in: 7791b704 user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/browse.c.

69
70
71
72
73
74
75
76






77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
...
116
117
118
119
120
121
122
123


124

125
126
127
128
129
130

131
132
133
134
135
136
137
...
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

173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193




194
195
196
197
198
199
200
...
407
408
409
410
411
412
413
414
415



416
417
418

419

420
421
422
423
424
425
426

427
428
429

430



431
432
433
434
435
436
437
...
439
440
441
442
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
...
550
551
552
553
554
555
556



557
558
559
560
561
562
563
564
565
566
567
568
569
570
** to the "dir" page for the directory.
**
** There is no hyperlink on the file element of the path.
**
** The computed string is appended to the pOut blob.  pOut should
** have already been initialized.
*/
void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){






  int i, j;
  char *zSep = "";

  for(i=0; zPath[i]; i=j){
    for(j=i; zPath[j] && zPath[j]!='/'; j++){}
    if( zPath[j] && g.perm.Hyperlink ){
      if( zCI ){
        char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }else{
        char *zLink = href("%R/dir?name=%#T", j, zPath);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }
    }else{
      blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
    }
    zSep = "/";
................................................................................
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
  const char *zSubdirLink;
  int linkTrunk = 1, linkTip = 1;




  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
  style_header("File List");
  sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
                          pathelementFunc, 0, 0);


  /* If the name= parameter is an empty string, make it a NULL pointer */
  if( zD && strlen(zD)==0 ){ zD = 0; }

  /* If a specific check-in is requested, fetch and parse it.  If the
  ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
  ** files from all check-ins to be displayed.
................................................................................
  if( zCI ){
    pM = manifest_get_by_name(zCI, &rid);
    if( pM ){
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);

    }else{
      zCI = 0;
    }
  }

  /* Compute the title of the page */
  blob_zero(&dirname);
  if( zD ){

    blob_append(&dirname, "in directory ", -1);
    hyperlinked_path(zD, &dirname, zCI);
    zPrefix = mprintf("%s/", zD);
    if( linkTrunk ){
      style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk",
                             zD);
    }
    if( linkTip ){
      style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD);
    }
  }else{
    blob_append(&dirname, "in the top-level directory", -1);
    zPrefix = "";

    if( linkTrunk ){
      style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk");

    }
    if( linkTip ){
      style_submenu_element("Tip", "Tip", "%R/dir?ci=tip");
    }

  }
  if( zCI ){
    char zShort[20];
    memcpy(zShort, zUuid, 10);
    zShort[10] = 0;
    @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
    if( zD ){
      style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid);
      style_submenu_element("All", "All", "%R/dir?name=%t", zD);
    }else{
      style_submenu_element("All", "All", "%R/dir");
      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
                            zUuid);
    }
  }else{
    @ <h2>The union of all files from all check-ins
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
  }





  /* Compute the temporary table "localfiles" containing the names
  ** of all files and subdirectories in the zD[] directory.
  **
  ** Subdirectory names begin with "/".  This causes them to sort
  ** first and it also gives us an easy way to distinguish files
  ** from directories in the loop that follows.
................................................................................
  char *zD = fossil_strdup(P("name"));
  int nD = zD ? strlen(zD)+1 : 0;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
  int linkTrunk = 1, linkTip = 1;
  const char *zRE;



  ReCompiled *pRE = 0;
  FileTreeNode *p;
  FileTree sTree;



  memset(&sTree, 0, sizeof(sTree));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
  style_header("File List");
  sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
                          pathelementFunc, 0, 0);


  /* If a regular expression is specified, compile it */
  zRE = P("re");

  if( zRE ) re_compile(&pRE, zRE, 0);




  /* If the name= parameter is an empty string, make it a NULL pointer */
  if( zD && strlen(zD)==0 ){ zD = 0; }

  /* If a specific check-in is requested, fetch and parse it.  If the
  ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
  ** files from all check-ins to be displayed.
................................................................................
  if( zCI ){
    pM = manifest_get_by_name(zCI, &rid);
    if( pM ){
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);

    }else{
      zCI = 0;
    }
  }

  /* Compute the title of the page */
  blob_zero(&dirname);
  if( zD ){
    blob_appendf(&dirname, "in directory %h", zD);
    if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
    if( linkTrunk ){
      style_submenu_element("Trunk", "Trunk", "%R/tree?name=%t&ci=trunk",
                             zD);
    }
    if ( linkTip ){
      style_submenu_element("Tip", "Tip", "%R/tree?name=%t&ci=tip", zD);
    }
  }else{
    if( zRE ){
      blob_appendf(&dirname, "matching \"%s\"", zRE);
    }else{

      blob_append(&dirname, "in the top-level directory", -1);
    }
    if( linkTrunk ){
      style_submenu_element("Trunk", "Trunk", "%R/tree?ci=trunk");

    }
    if ( linkTip ){
      style_submenu_element("Tip", "Tip", "%R/tree?ci=tip");

    }
  }




  if( zCI ){
    char zShort[20];
    memcpy(zShort, zUuid, 10);
    zShort[10] = 0;
    @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
    @ %s(blob_str(&dirname))</h2>
    if( zD ){
      style_submenu_element("Top", "Top", "%R/tree?ci=%S", zUuid);
      style_submenu_element("All", "All", "%R/tree?name=%t", zD);
    }else{
      style_submenu_element("All", "All", "%R/tree");
    }
  }else{
    @ <h2>The union of all files from all check-ins
    @ %s(blob_str(&dirname))</h2>
  }

  /* Compute the file hierarchy.
  */
................................................................................
    tree_indentation(p->pParent);
    if( p->isLast ){
      cgi_append_content("&#x2514;&#x2500;&#x2500; ", 25);
    }else{
      cgi_append_content("&#x251c;&#x2500;&#x2500; ", 25);
    }
    if( p->isDir ){



      @ %h(p->zName)
    }else{
      char *zLink;
      if( zCI ){
        zLink = href("%R/artifact/%s",p->zUuid);
      }else{
        zLink = href("%R/finfo?name=%T",p->zFullName);
      }
      @ %z(zLink)%h(p->zName)</a>
    }
  }
  @ </pre>
  style_footer();








|
>
>
>
>
>
>







|



|







 







|
>
>

>






>







 







>








>

|

<
|
|
<
<
<
<



>
|
|
>
|
|
|
<
>








|
<
<
<
<








>
>
>
>







 







|
|
>
>
>
|
|
|
>

>







>



>
|
>
>
>







 







>








|
|
|
|
|
|
|
<
<



<
>
|
|
|
|
>
|
|
|
>
|
<
>
>
>
>






<
<
<
<
<
<







 







>
>
>
|





|







69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
...
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

169
170




171
172
173
174
175
176
177
178
179
180

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
...
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
...
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
497
498
499
500
501
502
503
504






505
506
507
508
509
510
511
...
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
** to the "dir" page for the directory.
**
** There is no hyperlink on the file element of the path.
**
** The computed string is appended to the pOut blob.  pOut should
** have already been initialized.
*/
void hyperlinked_path(
  const char *zPath,    /* Path to render */
  Blob *pOut,           /* Write into this blob */
  const char *zCI,      /* check-in name, or NULL */
  const char *zURI,     /* "dir" or "tree" */
  const char *zREx      /* Extra query parameters */
){
  int i, j;
  char *zSep = "";

  for(i=0; zPath[i]; i=j){
    for(j=i; zPath[j] && zPath[j]!='/'; j++){}
    if( zPath[j] && g.perm.Hyperlink ){
      if( zCI ){
        char *zLink = href("%R/%s?ci=%S&name=%#T%s", zURI, zCI, j, zPath,zREx);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }else{
        char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }
    }else{
      blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
    }
    zSep = "/";
................................................................................
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
  const char *zSubdirLink;
  int linkTrunk = 1;
  int linkTip = 1;
  HQuery sURI;

  if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; }
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
  style_header("File List");
  sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
                          pathelementFunc, 0, 0);
  url_initialize(&sURI, "dir");

  /* If the name= parameter is an empty string, make it a NULL pointer */
  if( zD && strlen(zD)==0 ){ zD = 0; }

  /* If a specific check-in is requested, fetch and parse it.  If the
  ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
  ** files from all check-ins to be displayed.
................................................................................
  if( zCI ){
    pM = manifest_get_by_name(zCI, &rid);
    if( pM ){
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      url_add_parameter(&sURI, "ci", zCI);
    }else{
      zCI = 0;
    }
  }

  /* Compute the title of the page */
  blob_zero(&dirname);
  if( zD ){
    url_add_parameter(&sURI, "name", zD);
    blob_append(&dirname, "in directory ", -1);
    hyperlinked_path(zD, &dirname, zCI, "dir", "");
    zPrefix = mprintf("%s/", zD);

    style_submenu_element("Top", "Top", "%s",
                          url_render(&sURI, "name", 0, 0, 0));




  }else{
    blob_append(&dirname, "in the top-level directory", -1);
    zPrefix = "";
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
  if( linkTip ){
    style_submenu_element("Tip", "Tip", "%s",

                          url_render(&sURI, "ci", "tip", 0, 0));
  }
  if( zCI ){
    char zShort[20];
    memcpy(zShort, zUuid, 10);
    zShort[10] = 0;
    @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
    if( nD==0 ){




      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
                            zUuid);
    }
  }else{
    @ <h2>The union of all files from all check-ins
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
  }
  style_submenu_element("All", "All", "%s",
                        url_render(&sURI, "ci", 0, 0, 0));
  style_submenu_element("Tree-View", "Tree-View", "%s",
                        url_render(&sURI, "type", "tree", 0, 0));

  /* Compute the temporary table "localfiles" containing the names
  ** of all files and subdirectories in the zD[] directory.
  **
  ** Subdirectory names begin with "/".  This causes them to sort
  ** first and it also gives us an easy way to distinguish files
  ** from directories in the loop that follows.
................................................................................
  char *zD = fossil_strdup(P("name"));
  int nD = zD ? strlen(zD)+1 : 0;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
  int linkTrunk = 1;       /* include link to "trunk" */
  int linkTip = 1;         /* include link to "tip" */
  const char *zRE;         /* the value for the re=REGEXP query parameter */
  char *zPrefix;           /* Prefix on all filenames */
  char *zREx = "";         /* Extra parameters for path hyperlinks */
  ReCompiled *pRE = 0;     /* Compiled regular expression */
  FileTreeNode *p;         /* One line of the tree */
  FileTree sTree;          /* The complete tree of files */
  HQuery sURI;             /* Hyperlink */

  if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
  memset(&sTree, 0, sizeof(sTree));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
  style_header("File List");
  sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
                          pathelementFunc, 0, 0);
  url_initialize(&sURI, "tree");

  /* If a regular expression is specified, compile it */
  zRE = P("re");
  if( zRE ){
    re_compile(&pRE, zRE, 0);
    url_add_parameter(&sURI, "re", zRE);
    zREx = mprintf("&re=%T", zRE);
  }

  /* If the name= parameter is an empty string, make it a NULL pointer */
  if( zD && strlen(zD)==0 ){ zD = 0; }

  /* If a specific check-in is requested, fetch and parse it.  If the
  ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
  ** files from all check-ins to be displayed.
................................................................................
  if( zCI ){
    pM = manifest_get_by_name(zCI, &rid);
    if( pM ){
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      url_add_parameter(&sURI, "ci", zCI);
    }else{
      zCI = 0;
    }
  }

  /* Compute the title of the page */
  blob_zero(&dirname);
  if( zD ){
    url_add_parameter(&sURI, "name", zD);
    blob_append(&dirname, "within directory ", -1);
    hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
    if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
    zPrefix = mprintf("%T/", zD);
    style_submenu_element("Top", "Top", "%s",
                          url_render(&sURI, "name", 0, 0, 0));


  }else{
    if( zRE ){
      blob_appendf(&dirname, "matching \"%s\"", zRE);

    }
    zPrefix = "";
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
  if ( linkTip ){
    style_submenu_element("Tip", "Tip", "%s",
                          url_render(&sURI, "ci", "tip", 0, 0));
  }

  style_submenu_element("All", "All", "%s",
                        url_render(&sURI, "ci", 0, 0, 0));
  style_submenu_element("Flat-View", "Flat-View", "%s",
                        url_render(&sURI, "type", "flat", 0, 0));
  if( zCI ){
    char zShort[20];
    memcpy(zShort, zUuid, 10);
    zShort[10] = 0;
    @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
    @ %s(blob_str(&dirname))</h2>






  }else{
    @ <h2>The union of all files from all check-ins
    @ %s(blob_str(&dirname))</h2>
  }

  /* Compute the file hierarchy.
  */
................................................................................
    tree_indentation(p->pParent);
    if( p->isLast ){
      cgi_append_content("&#x2514;&#x2500;&#x2500; ", 25);
    }else{
      cgi_append_content("&#x251c;&#x2500;&#x2500; ", 25);
    }
    if( p->isDir ){
      char *zName = mprintf("%s%T", zPrefix, p->zFullName);
      char *zLink = href("%s", url_render(&sURI, "name", zName, 0, 0));
      fossil_free(zName);
      @ %z(zLink)%h(p->zName)</a>
    }else{
      char *zLink;
      if( zCI ){
        zLink = href("%R/artifact/%s",p->zUuid);
      }else{
        zLink = href("%R/finfo?name=%s%T",zPrefix,p->zFullName);
      }
      @ %z(zLink)%h(p->zName)</a>
    }
  }
  @ </pre>
  style_footer();

Changes to src/finfo.c.

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
  }
  blob_reset(&sql);
  blob_zero(&title);
  if( baseCheckin ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
    char *zLink = href("%R/info/%S", zUuid);
    blob_appendf(&title, "Ancestors of file ");
    hyperlinked_path(zFilename, &title, zUuid);
    blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
    fossil_free(zUuid);
  }else{
    blob_appendf(&title, "History of files named ");
    hyperlinked_path(zFilename, &title, 0);
  }
  @ <h2>%b(&title)</h2>
  blob_reset(&title);
  pGraph = graph_init();
  @ <div id="canvas" style="position:relative;width:1px;height:1px;"
  @  onclick="clickOnGraph(event)"></div>
  @ <table id="timelineTable" class="timelineTable">







|




|







373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
  }
  blob_reset(&sql);
  blob_zero(&title);
  if( baseCheckin ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
    char *zLink = href("%R/info/%S", zUuid);
    blob_appendf(&title, "Ancestors of file ");
    hyperlinked_path(zFilename, &title, zUuid, "dir", "");
    blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
    fossil_free(zUuid);
  }else{
    blob_appendf(&title, "History of files named ");
    hyperlinked_path(zFilename, &title, 0, "dir", "");
  }
  @ <h2>%b(&title)</h2>
  blob_reset(&title);
  pGraph = graph_init();
  @ <div id="canvas" style="position:relative;width:1px;height:1px;"
  @  onclick="clickOnGraph(event)"></div>
  @ <table id="timelineTable" class="timelineTable">