Fossil

Check-in [2b07e4e935]
Login

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

Overview
Comment:Work toward moving the file browsing closer to what typical users expect.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 2b07e4e935bfbc68f64bf5450eb771a2e8c662558fff7fb12561b8ec3964c16b
User & Date: drh 2020-05-09 13:17:53.598
Context
2020-05-09
13:34
Create a test file with spaces in its name, in a test directory with spaces in its name. Use these to help verify that fossil can handle file and directory names containing space characters. check-in: ccf6a361f3 user: drh tags: trunk
13:17
Work toward moving the file browsing closer to what typical users expect. check-in: 2b07e4e935 user: drh tags: trunk
13:16
Comment improvements and minor simplifications. Closed-Leaf check-in: aa9815ffc0 user: drh tags: browse-to-file
12:08
Update the change log. check-in: 49a06bf177 user: drh tags: trunk
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/browse.c.
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
173
174
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
  const char *zSubdirLink;
  int linkTrunk = 1;
  int linkTip = 1;
  HQuery sURI;




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

  /* 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, "dir", "");
    zPrefix = mprintf("%s/", zD);







>
>

>




<
<
<
<
<
<















>




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







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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
  const char *zSubdirLink;
  int linkTrunk = 1;
  int linkTip = 1;
  HQuery sURI;
  int isSymbolicCI = 0;   /* ci= is symbolic name, not a hash prefix */
  char *zHeader = 0;

  if( zCI && strlen(zCI)==0 ){ zCI = 0; }
  if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; }
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 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);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
    }else{
      zCI = 0;
    }
  }

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( isSymbolicCI ) {
    zHeader = mprintf("%s at %s", (zD ? zD : "Files"), zCI);
  }else if( zUuid && strlen(zUuid) ){
    zHeader = mprintf("%s at [%S]", (zD ? zD : "Files"), zUuid);
  }else{
    zHeader = mprintf("%s", (zD ? zD : "All Files"));
  }
  style_header("%s", zHeader);
  fossil_free(zHeader);
  style_adunit_config(ADUNIT_RIGHT_OK);
  sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
                          pathelementFunc, 0, 0);
  url_initialize(&sURI, "dir");
  cgi_query_parameters_to_url(&sURI);

  /* Compute the title of the page */
  blob_zero(&dirname);
  if( zD ){
    blob_append(&dirname, "in directory ", -1);
    hyperlinked_path(zD, &dirname, zCI, "dir", "");
    zPrefix = mprintf("%s/", zD);
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
    zFN = db_column_text(&q, 0);
    if( zFN[0]=='/' ){
      zFN++;
      @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
    }else{
      const char *zLink;
      if( zCI ){
        const char *zUuid = db_column_text(&q, 1);
        zLink = href("%R/artifact/%!S",zUuid);
      }else{
        zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
      }
      @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
    }
  }
  db_finalize(&q);







<
|







294
295
296
297
298
299
300

301
302
303
304
305
306
307
308
    zFN = db_column_text(&q, 0);
    if( zFN[0]=='/' ){
      zFN++;
      @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
    }else{
      const char *zLink;
      if( zCI ){

        zLink = href("%R/file/%T%T?ci=%!S",zPrefix,zFN,zCI);
      }else{
        zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
      }
      @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
    }
  }
  db_finalize(&q);
610
611
612
613
614
615
616


617

618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
  FileTreeNode *p;         /* One line of the tree */
  FileTree sTree;          /* The complete tree of files */
  HQuery sURI;             /* Hyperlink */
  int startExpanded;       /* True to start out with the tree expanded */
  int showDirOnly;         /* Show directories only.  Omit files */
  int nDir = 0;            /* Number of directories. Used for ID attributes */
  char *zProjectName = db_get("project-name", 0);




  if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
  memset(&sTree, 0, sizeof(sTree));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
  sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
                          pathelementFunc, 0, 0);
  url_initialize(&sURI, "tree");
  cgi_query_parameters_to_url(&sURI);
  if( PB("nofiles") ){
    showDirOnly = 1;
    style_header("Folder Hierarchy");
  }else{
    showDirOnly = 0;
    style_header("File Tree");
  }
  style_adunit_config(ADUNIT_RIGHT_OK);
  if( PB("expand") ){
    startExpanded = 1;
  }else{
    startExpanded = 0;
  }







>
>

>











<


<







623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644

645
646

647
648
649
650
651
652
653
  FileTreeNode *p;         /* One line of the tree */
  FileTree sTree;          /* The complete tree of files */
  HQuery sURI;             /* Hyperlink */
  int startExpanded;       /* True to start out with the tree expanded */
  int showDirOnly;         /* Show directories only.  Omit files */
  int nDir = 0;            /* Number of directories. Used for ID attributes */
  char *zProjectName = db_get("project-name", 0);
  int isSymbolicCI = 0;    /* ci= is a symbolic name, not a hash prefix */
  char *zHeader = 0;

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

  }else{
    showDirOnly = 0;

  }
  style_adunit_config(ADUNIT_RIGHT_OK);
  if( PB("expand") ){
    startExpanded = 1;
  }else{
    startExpanded = 0;
  }
658
659
660
661
662
663
664

665
666
667
668
669
670
671
672














673
674
675
676
677
678
679
      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);
      rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
      zNow = db_text("", "SELECT datetime(mtime,toLocal())"
                         " FROM event WHERE objid=%d", rid);

    }else{
      zCI = 0;
    }
  }
  if( zCI==0 ){
    rNow = db_double(0.0, "SELECT max(mtime) FROM event");
    zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
  }















  /* Compute the title of the page */
  blob_zero(&dirname);
  if( zD ){
    blob_append(&dirname, "within directory ", -1);
    hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
    if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);







>








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







672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
      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);
      rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
      zNow = db_text("", "SELECT datetime(mtime,toLocal())"
                         " FROM event WHERE objid=%d", rid);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
    }else{
      zCI = 0;
    }
  }
  if( zCI==0 ){
    rNow = db_double(0.0, "SELECT max(mtime) FROM event");
    zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
  }

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( isSymbolicCI ) {
    zHeader = mprintf("%s at %s",
      (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zCI);
  }else if( zUuid && strlen(zUuid) ){
    zHeader = mprintf("%s at [%S]",
      (zD ? zD : (showDirOnly ? "Folder Hierarchy" : "Tree-View")), zUuid);
  }else{
    zHeader = mprintf("%s",
      (zD ? zD : (showDirOnly ?"All Folders Hierarchy":"All Files Tree-View")));
  }
  style_header("%s", zHeader);
  fossil_free(zHeader);

  /* Compute the title of the page */
  blob_zero(&dirname);
  if( zD ){
    blob_append(&dirname, "within directory ", -1);
    hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
    if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
  }else{
    zObjType = "Files";
  }

  style_submenu_checkbox("nofiles", "Folders Only", 0, 0);

  if( zCI ){
    @ <h2>%s(zObjType) from
    if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
      @ "%h(zCI)"
    }
    @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
  }else{
    int n = db_int(0, "SELECT count(*) FROM plink");
    @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))







|







785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
  }else{
    zObjType = "Files";
  }

  style_submenu_checkbox("nofiles", "Folders Only", 0, 0);

  if( zCI ){
    @ <h2>%s(zObjType) of check-in
    if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
      @ "%h(zCI)"
    }
    @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
  }else{
    int n = db_int(0, "SELECT count(*) FROM plink");
    @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
        @ <ul id="dir%d(nDir)" class="collapsed">
      }
      nDir++;
    }else if( !showDirOnly ){
      const char *zFileClass = fileext_class(p->zName);
      char *zLink;
      if( zCI ){
        zLink = href("%R/artifact/%!S",p->zUuid);
      }else{
        zLink = href("%R/finfo?name=%T",p->zFullName);
      }
      @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
      @ %z(zLink)%h(p->zName)</a>
      if( p->mtime>0 ){
        char *zAge = human_readable_age(rNow - p->mtime);







|







851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
        @ <ul id="dir%d(nDir)" class="collapsed">
      }
      nDir++;
    }else if( !showDirOnly ){
      const char *zFileClass = fileext_class(p->zName);
      char *zLink;
      if( zCI ){
        zLink = href("%R/file/%T?ci=%!S",p->zFullName,zCI);
      }else{
        zLink = href("%R/finfo?name=%T",p->zFullName);
      }
      @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
      @ %z(zLink)%h(p->zName)</a>
      if( p->mtime>0 ){
        char *zAge = human_readable_age(rNow - p->mtime);
Changes to src/finfo.c.
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
        "   AND event.objid=ci.rid"
        " ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
        TAG_BRANCH, zFilename, filename_collation(),
        iLimit, iOffset
    );
    blob_zero(&line);
    if( iBrief ){
      fossil_print("History of %s\n", blob_str(&fname));
    }
    while( db_step(&q)==SQLITE_ROW ){
      const char *zFileUuid = db_column_text(&q, 0);
      const char *zCiUuid = db_column_text(&q,1);
      const char *zDate = db_column_text(&q, 2);
      const char *zCom = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 4);







|







197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
        "   AND event.objid=ci.rid"
        " ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
        TAG_BRANCH, zFilename, filename_collation(),
        iLimit, iOffset
    );
    blob_zero(&line);
    if( iBrief ){
      fossil_print("History for %s\n", blob_str(&fname));
    }
    while( db_step(&q)==SQLITE_ROW ){
      const char *zFileUuid = db_column_text(&q, 0);
      const char *zCiUuid = db_column_text(&q,1);
      const char *zDate = db_column_text(&q, 2);
      const char *zCom = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 4);
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, and it may also name a
** timezone offset from UTC as "-HH:MM" (westward) or "+HH:MM"
** (eastward). Either no timezone suffix or "Z" means UTC.
*/
void finfo_page(void){
  Stmt q;
  const char *zFilename;
  char zPrevDate[20];
  const char *zA;
  const char *zB;
  int n;
  int baseCheckin;
  int origCheckin = 0;
  int fnid;







|







294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, and it may also name a
** timezone offset from UTC as "-HH:MM" (westward) or "+HH:MM"
** (eastward). Either no timezone suffix or "Z" means UTC.
*/
void finfo_page(void){
  Stmt q;
  const char *zFilename = PD("name","");
  char zPrevDate[20];
  const char *zA;
  const char *zB;
  int n;
  int baseCheckin;
  int origCheckin = 0;
  int fnid;
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
  int tmFlags = 0;            /* Viewing mode */
  const char *zStyle;         /* Viewing mode name */
  const char *zMark;          /* Mark this version of the file */
  int selRid = 0;             /* RID of the marked file version */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }


  style_header("File History");



  login_anonymous_available();
  tmFlags = timeline_ss_submenu();
  if( tmFlags & TIMELINE_COLUMNAR ){
    zStyle = "Columnar";
  }else if( tmFlags & TIMELINE_COMPACT ){
    zStyle = "Compact";
  }else if( tmFlags & TIMELINE_VERBOSE ){
    zStyle = "Verbose";
  }else if( tmFlags & TIMELINE_CLASSIC ){
    zStyle = "Classic";
  }else{
    zStyle = "Modern";
  }
  url_initialize(&url, "finfo");
  if( brBg ) url_add_parameter(&url, "brbg", 0);
  if( uBg ) url_add_parameter(&url, "ubg", 0);
  baseCheckin = name_to_rid_www("ci");
  zPrevDate[0] = 0;
  zFilename = PD("name","");
  cookie_render();
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  if( fnid==0 ){
    @ No such file: %h(zFilename)
    style_footer();
    return;
  }
  if( g.perm.Admin ){
    style_submenu_element("MLink Table", "%R/mlink?name=%t", zFilename);







>
>
|
>
>
>


















<

<







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
  int tmFlags = 0;            /* Viewing mode */
  const char *zStyle;         /* Viewing mode name */
  const char *zMark;          /* Mark this version of the file */
  int selRid = 0;             /* RID of the marked file version */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  if( fnid==0 ){
    style_header("History unavailable");
  }else{
    style_header("History for %s", zFilename);
  }
  login_anonymous_available();
  tmFlags = timeline_ss_submenu();
  if( tmFlags & TIMELINE_COLUMNAR ){
    zStyle = "Columnar";
  }else if( tmFlags & TIMELINE_COMPACT ){
    zStyle = "Compact";
  }else if( tmFlags & TIMELINE_VERBOSE ){
    zStyle = "Verbose";
  }else if( tmFlags & TIMELINE_CLASSIC ){
    zStyle = "Classic";
  }else{
    zStyle = "Modern";
  }
  url_initialize(&url, "finfo");
  if( brBg ) url_add_parameter(&url, "brbg", 0);
  if( uBg ) url_add_parameter(&url, "ubg", 0);
  baseCheckin = name_to_rid_www("ci");
  zPrevDate[0] = 0;

  cookie_render();

  if( fnid==0 ){
    @ No such file: %h(zFilename)
    style_footer();
    return;
  }
  if( g.perm.Admin ){
    style_submenu_element("MLink Table", "%R/mlink?name=%t", zFilename);
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    if( origCheckin ){
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", origCheckin);
      zLink = href("%R/info/%!S", zUuid);
      blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid);
      fossil_free(zUuid);
    }
  }else{
    blob_appendf(&title, "History of ");
    hyperlinked_path(zFilename, &title, 0, "tree", "");
    if( fShowId ) blob_appendf(&title, " (%d)", fnid);
  }
  if( uBg ){
    blob_append(&title, " (color-coded by user)", -1);
  }
  @ <h2>%b(&title)</h2>







|







450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
    if( origCheckin ){
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", origCheckin);
      zLink = href("%R/info/%!S", zUuid);
      blob_appendf(&title, " and check-in %z%S</a>", zLink, zUuid);
      fossil_free(zUuid);
    }
  }else{
    blob_appendf(&title, "History for ");
    hyperlinked_path(zFilename, &title, 0, "tree", "");
    if( fShowId ) blob_appendf(&title, " (%d)", fnid);
  }
  if( uBg ){
    blob_append(&title, " (color-coded by user)", -1);
  }
  @ <h2>%b(&title)</h2>
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
    zTime[5] = 0;
    if( frid==selRid ){
      @ <tr class='timelineSelected'>
    }else{
      @ <tr>
    }
    @ <td class="timelineTime">\
    @ %z(href("%R/artifact/%!S",zUuid))%s(zTime)</a></td>
    @ <td class="timelineGraph"><div id="m%d(gidx)" class="tl-nodemark"></div>
    @ </td>
    if( zBgClr && zBgClr[0] ){
      @ <td class="timeline%s(zStyle)Cell" id='mc%d(gidx)'>
    }else{
      @ <td class="timeline%s(zStyle)Cell">
    }







|







525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
    zTime[5] = 0;
    if( frid==selRid ){
      @ <tr class='timelineSelected'>
    }else{
      @ <tr>
    }
    @ <td class="timelineTime">\
    @ %z(href("%R/file/%T?ci=%!S",zFilename,zCkin))%s(zTime)</a></td>
    @ <td class="timelineGraph"><div id="m%d(gidx)" class="tl-nodemark"></div>
    @ </td>
    if( zBgClr && zBgClr[0] ){
      @ <td class="timeline%s(zStyle)Cell" id='mc%d(gidx)'>
    }else{
      @ <td class="timeline%s(zStyle)Cell">
    }
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
    }
    if( tmFlags & TIMELINE_COMPACT ){
      cgi_printf("<span class='clutter' id='detail-%d'>",frid);
    }
    cgi_printf("<span class='timeline%sDetail'>", zStyle);
    if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("(");
    if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){
      @ file:&nbsp;%z(href("%R/artifact/%!S",zUuid))[%S(zUuid)]</a>
      if( fShowId ){
        int srcId = delta_source_rid(frid);
        if( srcId>0 ){
          @ id:&nbsp;%d(frid)&larr;%d(srcId)
        }else{
          @ id:&nbsp;%d(frid)
        }







|







562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
    }
    if( tmFlags & TIMELINE_COMPACT ){
      cgi_printf("<span class='clutter' id='detail-%d'>",frid);
    }
    cgi_printf("<span class='timeline%sDetail'>", zStyle);
    if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("(");
    if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){
      @ file:&nbsp;%z(href("%R/file/%T?ci=%!S",zFilename,zCkin))[%S(zUuid)]</a>
      if( fShowId ){
        int srcId = delta_source_rid(frid);
        if( srcId>0 ){
          @ id:&nbsp;%d(frid)&larr;%d(srcId)
        }else{
          @ id:&nbsp;%d(frid)
        }
Changes to src/info.c.
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366

1367
1368
1369
1370
1371
1372
1373
1374
#define OBJTYPE_EXE        0x0100
#define OBJTYPE_FORUM      0x0200

/*
** Possible flags for the second parameter to
** object_description()
*/
#define OBJDESC_DETAIL      0x0001   /* more detail */
#define OBJDESC_BASE        0x0002   /* Set <base> using this object */
#endif

/*
** Write a description of an object to the www reply.
**
** If the object is a file then mention:
**
**     * It's artifact ID
**     * All its filenames
**     * The check-in it was part of, with times and users
**
** If the object is a manifest, then mention:
**
**     * It's artifact ID
**     * date of check-in
**     * Comment & user
*/
int object_description(
  int rid,                 /* The artifact ID */
  u32 objdescFlags,        /* Flags to control display */

  Blob *pDownloadName      /* Fill with an appropriate download name */
){
  Stmt q;
  int cnt = 0;
  int nWiki = 0;
  int objType = 0;
  char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  int showDetail = (objdescFlags & OBJDESC_DETAIL)!=0;







|





<
<
<
<
<
<
<
<
<
<
<
<


|

>
|







1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350












1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
#define OBJTYPE_EXE        0x0100
#define OBJTYPE_FORUM      0x0200

/*
** Possible flags for the second parameter to
** object_description()
*/
#define OBJDESC_DETAIL      0x0001   /* Show more detail */
#define OBJDESC_BASE        0x0002   /* Set <base> using this object */
#endif

/*
** Write a description of an object to the www reply.












*/
int object_description(
  int rid,                 /* The artifact ID for the object to describe */
  u32 objdescFlags,        /* Flags to control display */
  const char *zFileName,   /* For file objects, use this name.  Can be NULL */
  Blob *pDownloadName      /* Fill with a good download name.  Can be NULL */
){
  Stmt q;
  int cnt = 0;
  int nWiki = 0;
  int objType = 0;
  char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  int showDetail = (objdescFlags & OBJDESC_DETAIL)!=0;
1399
1400
1401
1402
1403
1404
1405

1406
1407
1408
1409
1410
1411
1412
    const char *zCom = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    const char *zVers = db_column_text(&q, 4);
    int mPerm = db_column_int(&q, 5);
    const char *zBr = db_column_text(&q, 6);
    int szFile = db_column_int(&q,7);
    int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;

    if( sameFilename && !showDetail ){
      if( cnt==1 ){
        @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
      }
      cnt++;
      continue;
    }







>







1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
    const char *zCom = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    const char *zVers = db_column_text(&q, 4);
    int mPerm = db_column_int(&q, 5);
    const char *zBr = db_column_text(&q, 6);
    int szFile = db_column_int(&q,7);
    int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
    if( zFileName && fossil_strcmp(zName,zFileName)!=0 ) continue;
    if( sameFilename && !showDetail ){
      if( cnt==1 ){
        @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
      }
      cnt++;
      continue;
    }
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
  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
    @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
    object_description(v1, objdescFlags, 0);
    @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
    object_description(v2, objdescFlags, 0);
  }
  if( pRe ){
    @ <b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b>
  }
  @ <hr />
  append_diff(zV1, zV2, diffFlags, pRe);







|

|







1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
  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
    @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
    object_description(v1, objdescFlags,0, 0);
    @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
    object_description(v2, objdescFlags,0, 0);
  }
  if( pRe ){
    @ <b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b>
  }
  @ <hr />
  append_diff(zV1, zV2, diffFlags, pRe);
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
  if( g.perm.Setup ){
    @  (%d(rid)):</h2>
  }else{
    @ :</h2>
  }
  blob_zero(&downloadName);
  if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  object_description(rid, objdescFlags, &downloadName);
  style_submenu_element("Download", "%s/raw/%T?name=%s",
        g.zTop, blob_str(&downloadName), zUuid);
  @ <hr />
  content_get(rid, &content);
  @ <blockquote><pre>
  hexdump(&content);
  @ </pre></blockquote>







|







1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
  if( g.perm.Setup ){
    @  (%d(rid)):</h2>
  }else{
    @ :</h2>
  }
  blob_zero(&downloadName);
  if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  object_description(rid, objdescFlags, 0, &downloadName);
  style_submenu_element("Download", "%s/raw/%T?name=%s",
        g.zTop, blob_str(&downloadName), zUuid);
  @ <hr />
  content_get(rid, &content);
  @ <blockquote><pre>
  hexdump(&content);
  @ </pre></blockquote>
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137

2138




2139


2140













2141

2142
2143
2144
2145
2146
2147
2148
  Blob content;
  const char *zMime;
  Blob downloadName;
  int renderAsWiki = 0;
  int renderAsHtml = 0;
  int objType;
  int asText;
  const char *zUuid;
  u32 objdescFlags = OBJDESC_BASE;
  int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
  int isFile = fossil_strcmp(g.zPath,"file")==0;
  const char *zLn = P("ln");
  const char *zName = P("name");

  HQuery url;







  url_initialize(&url, g.zPath);













  rid = artifact_from_ci_and_filename(&url, 0);

  if( rid==0 ){
    url_add_parameter(&url, "name", zName);
    if( isFile ){
      /* Do a top-level directory listing in /file mode if no argument
      ** specified */
      if( zName==0 || zName[0]==0 ){
        if( P("ci")==0 ) cgi_set_query_parameter("ci","tip");







|





>

>
>
>
>

>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
|
>







2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
  Blob content;
  const char *zMime;
  Blob downloadName;
  int renderAsWiki = 0;
  int renderAsHtml = 0;
  int objType;
  int asText;
  const char *zUuid = 0;
  u32 objdescFlags = OBJDESC_BASE;
  int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
  int isFile = fossil_strcmp(g.zPath,"file")==0;
  const char *zLn = P("ln");
  const char *zName = P("name");
  const char *zCI = P("ci");
  HQuery url;
  Blob dirname;
  char *zCIUuid = 0;
  int isSymbolicCI = 0;  /* ci= exists and is a symbolic name, not a hash */
  char *zHeader = 0;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  url_initialize(&url, g.zPath);
  if( zCI && strlen(zCI)==0 ){ zCI = 0; }
  if( zCI ){
    blob_zero(&dirname);
    hyperlinked_path(zName, &dirname, zCI, "dir", "");
    blob_reset(&dirname);

    if( name_to_uuid2(zCI, "ci", &zCIUuid) ){
      isSymbolicCI = (strncmp(zCIUuid, zCI, strlen(zCI)) != 0);
    }
  }
  if( isFile && zName ) {
    rid = artifact_from_ci_and_filename(0, "name");
  }else{
    rid = artifact_from_ci_and_filename(&url, 0);
  }
  if( rid==0 ){
    url_add_parameter(&url, "name", zName);
    if( isFile ){
      /* Do a top-level directory listing in /file mode if no argument
      ** specified */
      if( zName==0 || zName[0]==0 ){
        if( P("ci")==0 ) cgi_set_query_parameter("ci","tip");
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
        return;
      }
    }else{
      rid = name_to_rid_www("name");
    }
  }

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( rid==0 ){
    style_header("No such artifact");
    @ Artifact '%h(zName)' does not exist in this repository.
    style_footer();
    return;
  }
  if( descOnly || P("verbose")!=0 ){
    url_add_parameter(&url, "verbose", "1");
    objdescFlags |= OBJDESC_DETAIL;
  }
  zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);

  if( isFile ){










    @ <h2>Latest version of file '%h(zName)':</h2>

    style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
  }else{
    @ <h2>Artifact
    style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
    if( g.perm.Setup ){
      @  (%d(rid)):</h2>
    }else{
      @ :</h2>
    }
  }
  blob_zero(&downloadName);
  asText = P("txt")!=0;
  if( asText ) objdescFlags &= ~OBJDESC_BASE;
  objType = object_description(rid, objdescFlags, &downloadName);
  if( !descOnly && P("download")!=0 ){
    cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName),
          db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid));
    /*NOTREACHED*/
  }
  if( g.perm.Admin ){
    const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
      style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#accshun",
            g.zTop, zUuid);
    }else{
      style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
    }
  }














  style_header("%s", isFile ? "File Content" :
                     descOnly ? "Artifact Description" : "Artifact Content");


  if( g.perm.Admin ){
    Stmt q;
    db_prepare(&q,
      "SELECT coalesce(user.login,rcvfrom.uid),"
      "       datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr"
      "  FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid"
      " WHERE blob.rid=%d"







<
<











>

>
>
>
>
>
>
>
>
>
>
|
>













|














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







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
2271
2272
2273
2274
2275
        return;
      }
    }else{
      rid = name_to_rid_www("name");
    }
  }



  if( rid==0 ){
    style_header("No such artifact");
    @ Artifact '%h(zName)' does not exist in this repository.
    style_footer();
    return;
  }
  if( descOnly || P("verbose")!=0 ){
    url_add_parameter(&url, "verbose", "1");
    objdescFlags |= OBJDESC_DETAIL;
  }
  zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);

  if( isFile ){
    if( zCI ){
      const char *zPath;
      Blob path;
      blob_zero(&path);
      hyperlinked_path(zName, &path, zCI, "dir", "");
      zPath = blob_str(&path);
      @ <h2>File %s(zPath) of check-in [%z(href("/info/%!S",zCIUuid))%S(zCIUuid)</a>]
      @ </h2>
      blob_reset(&path);
    }else{
      @ <h2>Latest version of file '%h(zName)':</h2>
    }
    style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
  }else{
    @ <h2>Artifact
    style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
    if( g.perm.Setup ){
      @  (%d(rid)):</h2>
    }else{
      @ :</h2>
    }
  }
  blob_zero(&downloadName);
  asText = P("txt")!=0;
  if( asText ) objdescFlags &= ~OBJDESC_BASE;
  objType = object_description(rid, objdescFlags, (isFile ? zName : 0),&downloadName);
  if( !descOnly && P("download")!=0 ){
    cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName),
          db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid));
    /*NOTREACHED*/
  }
  if( g.perm.Admin ){
    const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
      style_submenu_element("Unshun", "%s/shun?accept=%s&sub=1#accshun",
            g.zTop, zUuid);
    }else{
      style_submenu_element("Shun", "%s/shun?shun=%s#addshun", g.zTop, zUuid);
    }
  }

  if( isFile ){
    if( isSymbolicCI ){
      zHeader = mprintf("%s at %s", file_tail(zName), zCI);
    }else if( zCI ){
      zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
    }else{
      zHeader = mprintf("%s", file_tail(zName));
    }
  }else if( descOnly ){
    zHeader = mprintf("Artifact Description [%S]", zUuid);
  }else{
    zHeader = mprintf("Artifact [%S]", zUuid);
  }
  style_header("%s", zHeader);

  fossil_free(zCIUuid);
  fossil_free(zHeader);
  if( g.perm.Admin ){
    Stmt q;
    db_prepare(&q,
      "SELECT coalesce(user.login,rcvfrom.uid),"
      "       datetime(rcvfrom.mtime,toLocal()), rcvfrom.ipaddr"
      "  FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid"
      " WHERE blob.rid=%d"
Changes to src/name.c.
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
  canonical16(z, strlen(z));
  db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
    @ %s(zUuid)</a> -
    object_description(rid, 0, 0);
    @ </p></li>
  }
  db_finalize(&q);
  db_prepare(&q,
    "   SELECT tkt_rid, tkt_uuid, title"
    "     FROM ticket, ticketchng"
    "    WHERE ticket.tkt_id = ticketchng.tkt_id"







|







612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
  canonical16(z, strlen(z));
  db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
    @ %s(zUuid)</a> -
    object_description(rid, 0, 0, 0);
    @ </p></li>
  }
  db_finalize(&q);
  db_prepare(&q,
    "   SELECT tkt_rid, tkt_uuid, title"
    "     FROM ticket, ticketchng"
    "    WHERE ticket.tkt_id = ticketchng.tkt_id"
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
    @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
    @ %s(zUuid)</a> -
    @ <ul></ul>
    @ Ticket
    hyperlink_to_uuid(zUuid);
    @ - %h(zTitle).
    @ <ul><li>
    object_description(rid, 0, 0);
    @ </li></ul>
    @ </p></li>
  }
  db_finalize(&q);
  db_prepare(&q,
    "SELECT rid, uuid FROM"
    "  (SELECT tagxref.rid AS rid, substr(tagname, 7) AS uuid"
    "     FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
    "      AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    const char* zUuid = db_column_text(&q, 1);
    @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
    @ %s(zUuid)</a> -
    @ <ul><li>
    object_description(rid, 0, 0);
    @ </li></ul>
    @ </p></li>
  }
  @ </ol>
  db_finalize(&q);
  style_footer();
}







|















|







634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
    @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
    @ %s(zUuid)</a> -
    @ <ul></ul>
    @ Ticket
    hyperlink_to_uuid(zUuid);
    @ - %h(zTitle).
    @ <ul><li>
    object_description(rid, 0, 0, 0);
    @ </li></ul>
    @ </p></li>
  }
  db_finalize(&q);
  db_prepare(&q,
    "SELECT rid, uuid FROM"
    "  (SELECT tagxref.rid AS rid, substr(tagname, 7) AS uuid"
    "     FROM tagxref, tag WHERE tagxref.tagid = tag.tagid"
    "      AND tagname GLOB 'event-%q*') GROUP BY uuid", z);
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    const char* zUuid = db_column_text(&q, 1);
    @ <li><p><a href="%R/%T(zSrc)/%!S(zUuid)">
    @ %s(zUuid)</a> -
    @ <ul><li>
    object_description(rid, 0, 0, 0);
    @ </li></ul>
    @ </p></li>
  }
  @ </ol>
  db_finalize(&q);
  style_footer();
}