Fossil

Check-in [92fe4a5441]
Login

Check-in [92fe4a5441]

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

Overview
Comment:Continue the file browsing UX enhancements into the /tree and /fileage pages
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 92fe4a5441ee3d136e237c991911b4179da99fb23614566b40454b7626d1f498
User & Date: drh 2020-05-11 22:53:07.713
Context
2020-05-11
23:57
In the /timeline, the t=TAG and m=HASH options can be used together and both all TAGs and the HASH will both be shown. Useful to get context of an historical checkin X relative to releases using "m=X&t=release". ... (check-in: 03f7bcb463 user: drh tags: trunk)
22:53
Continue the file browsing UX enhancements into the /tree and /fileage pages ... (check-in: 92fe4a5441 user: drh tags: trunk)
21:01
Widen shun fields to accommodate SHA3-256 ... (check-in: f749f203f2 user: andygoth tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/branch.c.
16
17
18
19
20
21
22














23
24
25
26
27
28
29
*******************************************************************************
**
** This file contains code used to create new branches within a repository.
*/
#include "config.h"
#include "branch.h"
#include <assert.h>















/*
** If RID refers to a check-in, return the name of the branch for that
** check-in.
**
** Space to hold the returned value is obtained from fossil_malloc()
** and should be freed by the caller.







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







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
*******************************************************************************
**
** This file contains code used to create new branches within a repository.
*/
#include "config.h"
#include "branch.h"
#include <assert.h>

/*
** Return true if zBr is the branch name associated with check-in with
** blob.uuid value of zUuid
*/
int branch_includes_uuid(const char *zBr, const char *zUuid){
  return db_exists(
    "SELECT 1 FROM tagxref, blob"
    " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid"
    "   AND tagxref.value=%Q AND tagxref.tagtype>0"
    "   AND tagxref.tagid=%d",
    zUuid, zBr, TAG_BRANCH
  );
}

/*
** If RID refers to a check-in, return the name of the branch for that
** check-in.
**
** Space to hold the returned value is obtained from fossil_malloc()
** and should be freed by the caller.
Changes to src/browse.c.
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
        zURI = "finfo";
      }else{
        blob_appendf(pOut, "/%h", zPath+i);
        break;
      }
    }
    if( zCI ){
      char *zLink = href("%R/%s?name=%#T%s&ci=%!S", zURI, j, zPath, zREx,zCI);
      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]);
    }







|







101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
        zURI = "finfo";
      }else{
        blob_appendf(pOut, "/%h", zPath+i);
        break;
      }
    }
    if( zCI ){
      char *zLink = href("%R/%s?name=%#T%s&ci=%T", zURI, j, zPath, zREx,zCI);
      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]);
    }
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
    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);
      isBranchCI = db_exists(
         "SELECT 1 FROM tagxref, blob"
         " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid"
         "   AND tagxref.value=%Q AND tagxref.tagtype>0"
         "   AND tagxref.tagid=%d",
         zUuid, zCI, TAG_BRANCH
      );
    }else{
      zCI = 0;
    }
  }

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( zD==0 ){







|
<
<
<
<
<
<







170
171
172
173
174
175
176
177






178
179
180
181
182
183
184
    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);
      isBranchCI = branch_includes_uuid(zCI, zUuid);






    }else{
      zCI = 0;
    }
  }

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( zD==0 ){
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
      @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a> \
      @ of branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2>
    }else {
      @ of check-in %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2>
    }
    zSubdirLink = mprintf("%R/dir?ci=%T&name=%T", zCI, zPrefix);
    if( nD==0 ){
      style_submenu_element("File Ages", "%R/fileage?name=%!S", zUuid);
    }
  }else{
    @ in any check-in</h2>
    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "%s",







|







223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
      @ from the %z(href("%R/info?name=%T",zCI))latest check-in</a> \
      @ of branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a></h2>
    }else {
      @ of check-in %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2>
    }
    zSubdirLink = mprintf("%R/dir?ci=%T&name=%T", zCI, zPrefix);
    if( nD==0 ){
      style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
    }
  }else{
    @ in any check-in</h2>
    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "%s",
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
    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?name=%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);







|







318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
    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?name=%T%T&ci=%T",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);
654
655
656
657
658
659
660

661
662
663
664
665
666
667
  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; }







>







648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
  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 */
  int isBranchCI = 0;      /* ci= refers to a branch name */
  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; }
703
704
705
706
707
708
709

710
711
712
713
714
715
716
717
718
719

720
721
722
723
724
725

726

727

728

729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
      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, 0);
    if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
    style_submenu_element("Top-Level", "%s",
                          url_render(&sURI, "name", 0, 0, 0));
  }else{
    if( zRE ){
      blob_appendf(&dirname, "matching \"%s\"", zRE);
    }
  }
  style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
  if( zCI ){
    style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
    if( nD==0 && !showDirOnly ){
      style_submenu_element("File Ages", "%R/fileage?name=%s", zUuid);
    }
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
  if( linkTip ){







>










>
|
|
<
|
|
<
>

>
|
>
|
>












<
|
|
<





|







698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718

719
720

721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739

740
741

742
743
744
745
746
747
748
749
750
751
752
753
754
      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);
      isBranchCI = branch_includes_uuid(zCI, zUuid);
    }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( zD==0 ){
    if( zCI ){
      zHeader = mprintf("Top-level Files of %s", zCI);

    }else{
      zHeader = mprintf("All Top-level Files");

    }
  }else{
    if( zCI ){
      zHeader = mprintf("Files in %s/ of %s", zD, zCI);
    }else{
      zHeader = mprintf("All File in %s/", zD);
    }
  }
  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, 0);
    if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
    style_submenu_element("Top-Level", "%s",
                          url_render(&sURI, "name", 0, 0, 0));

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

  }
  style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
  if( zCI ){
    style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
    if( nD==0 && !showDirOnly ){
      style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
    }
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
  if( linkTip ){
802
803
804
805
806
807
808

809
810
811
812
813
814
815
816
817
818
819






820
821
822
823
824


825
826
827
828
829
830
831
832
833
      }
      if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue;
      tree_add_node(&sTree, zName, zUuid, mtime);
      nFile++;
    }
    db_finalize(&q);
  }


  if( showDirOnly ){
    for(nFile=0, p=sTree.pFirst; p; p=p->pNext){
      if( p->pChild!=0 && p->nFullName>nD ) nFile++;
    }
    zObjType = "Folders";
  }else{
    zObjType = "Files";
  }

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







  if( zCI ){
    @ <h2>%s(zObjType) at 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))
  }
  if( useMtime ){
    @ sorted by modification time</h2>
  }else{







>










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

<







799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826

827
828
829
830

831
832
833
834
835
836
837
      }
      if( pRE && re_match(pRE, (const u8*)zName, -1)==0 ) continue;
      tree_add_node(&sTree, zName, zUuid, mtime);
      nFile++;
    }
    db_finalize(&q);
  }
  style_submenu_checkbox("nofiles", "Folders Only", 0, 0);

  if( showDirOnly ){
    for(nFile=0, p=sTree.pFirst; p; p=p->pNext){
      if( p->pChild!=0 && p->nFullName>nD ) nFile++;
    }
    zObjType = "Folders";
  }else{
    zObjType = "Files";
  }

  if( zCI && strcmp(zCI,"tip")==0 ){
    @ <h2>%s(zObjType) in the %z(href("%R/info?name=tip"))latest check-in</a>
  }else if( isBranchCI ){
    @ <h2>%s(zObjType) in the %z(href("%R/info?name=%T",zCI))latest check-in\
    @ </a> for branch %z(href("%R/timeline?r=%T",zCI))%h(zCI)</a>
    if( blob_size(&dirname) ){
      @ and %s(blob_str(&dirname))</h2>
    }
  }else if( zCI ){
    @ <h2>%s(zObjType) for check-in \

    @ %z(href("%R/info?name=%T",zCI))%h(zCI)</a></h2>
    if( blob_size(&dirname) ){
      @ and %s(blob_str(&dirname))</h2>
    }

  }else{
    int n = db_int(0, "SELECT count(*) FROM plink");
    @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
  }
  if( useMtime ){
    @ sorted by modification time</h2>
  }else{
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
        @ <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?name=%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);







|







885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
        @ <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?name=%T&ci=%T",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);
1058
1059
1060
1061
1062
1063
1064

1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077

1078
1079
1080
1081
1082
1083
1084
1085
1086

1087

1088




1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133

1134

1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
*/
void fileage_page(void){
  int rid;
  const char *zName;
  const char *zGlob;
  const char *zUuid;
  const char *zNow;            /* Time of check-in */

  int showId = PB("showid");
  Stmt q1, q2;
  double baseTime;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( exclude_spiders() ) return;
  zName = P("name");
  if( zName==0 ) zName = "tip";
  rid = symbolic_name_to_rid(zName, "ci");
  if( rid==0 ){
    fossil_fatal("not a valid check-in: %s", zName);
  }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);

  baseTime = 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);
  style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName);
  style_header("File Ages");
  zGlob = P("glob");
  compute_fileage(rid,zGlob);
  db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");


  @ <h1>Files in

  @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>




  if( zGlob && zGlob[0] ){
    @ that match "%h(zGlob)"
  }
  @ ordered by age</h1>
  @
  @ <p>File ages are expressed relative to the
  @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> check-in time of
  @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
  @
  @ <div class='fileage'><table>
  @ <tr><th>Age</th><th>Files</th><th>Check-in</th></tr>
  db_prepare(&q1,
    "SELECT event.mtime, event.objid, blob.uuid,\n"
    "       coalesce(event.ecomment,event.comment),\n"
    "       coalesce(event.euser,event.user),\n"
    "       coalesce((SELECT value FROM tagxref\n"
    "                  WHERE tagtype>0 AND tagid=%d\n"
    "                    AND rid=event.objid),'trunk')\n"
    "  FROM event, blob\n"
    " WHERE event.objid IN (SELECT mid FROM fileage)\n"
    "   AND blob.rid=event.objid\n"
    " ORDER BY event.mtime DESC;",
    TAG_BRANCH
  );
  db_prepare(&q2,
    "SELECT blob.uuid, filename.name, fileage.fid\n"
    "  FROM fileage, blob, filename\n"
    " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid"
    "   AND blob.rid=fileage.fid;"
  );
  while( db_step(&q1)==SQLITE_ROW ){
    double age = baseTime - db_column_double(&q1, 0);
    int mid = db_column_int(&q1, 1);
    const char *zUuid = db_column_text(&q1, 2);
    const char *zComment = db_column_text(&q1, 3);
    const char *zUser = db_column_text(&q1, 4);
    const char *zBranch = db_column_text(&q1, 5);
    char *zAge = human_readable_age(age);
    @ <tr><td>%s(zAge)</td>
    @ <td>
    db_bind_int(&q2, ":mid", mid);
    while( db_step(&q2)==SQLITE_ROW ){
      const char *zFUuid = db_column_text(&q2,0);
      const char *zFile = db_column_text(&q2,1);
      int fid = db_column_int(&q2,2);

      if( showId ){

        @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a> (%d(fid))<br />
      }else{
        @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a><br />
      }
    }
    db_reset(&q2);
    @ </td>
    @ <td>
    @ %W(zComment)
    @ (check-in:&nbsp;%z(href("%R/ci/%!S",zUuid))%S(zUuid)</a>,
    if( showId ){
      @ id: %d(mid)
    }
    @ user:&nbsp;%z(href("%R/timeline?u=%t&c=%!S&nd",zUser,zUuid))%h(zUser)</a>,
    @ branch:&nbsp;\
    @ %z(href("%R/timeline?r=%t&c=%!S&nd",zBranch,zUuid))%h(zBranch)</a>)
    @ </td></tr>







>













>









>
|
>
|
>
>
>
>





|
<


















|
|

<













<
|
<
>

>
|

|






|







1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106

1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127

1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140

1141

1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
*/
void fileage_page(void){
  int rid;
  const char *zName;
  const char *zGlob;
  const char *zUuid;
  const char *zNow;            /* Time of check-in */
  int isBranchCI;              /* name= is a branch name */
  int showId = PB("showid");
  Stmt q1, q2;
  double baseTime;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( exclude_spiders() ) return;
  zName = P("name");
  if( zName==0 ) zName = "tip";
  rid = symbolic_name_to_rid(zName, "ci");
  if( rid==0 ){
    fossil_fatal("not a valid check-in: %s", zName);
  }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  isBranchCI = branch_includes_uuid(zName,zUuid);
  baseTime = 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);
  style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName);
  style_header("File Ages");
  zGlob = P("glob");
  compute_fileage(rid,zGlob);
  db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");

  if( fossil_strcmp(zName,"tip")==0 ){
    @ <h1>Files in the %z(href("%R/info?name=tip"))latest check-in</a>
  }else if( isBranchCI ){
    @ <h1>Files in the %z(href("%R/info?name=%T",zName))latest check-in</a>
    @ of branch %z(href("%R/timeline?r=%T",zName))%h(zName)</a>
  }else{
    @ <h1>Files in check-in %z(href("%R/info?name=%T",zName))%h(zName)</a>
  }
  if( zGlob && zGlob[0] ){
    @ that match "%h(zGlob)"
  }
  @ ordered by age</h1>
  @
  @ <p>File ages are expressed relative to the check-in time of

  @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
  @
  @ <div class='fileage'><table>
  @ <tr><th>Age</th><th>Files</th><th>Check-in</th></tr>
  db_prepare(&q1,
    "SELECT event.mtime, event.objid, blob.uuid,\n"
    "       coalesce(event.ecomment,event.comment),\n"
    "       coalesce(event.euser,event.user),\n"
    "       coalesce((SELECT value FROM tagxref\n"
    "                  WHERE tagtype>0 AND tagid=%d\n"
    "                    AND rid=event.objid),'trunk')\n"
    "  FROM event, blob\n"
    " WHERE event.objid IN (SELECT mid FROM fileage)\n"
    "   AND blob.rid=event.objid\n"
    " ORDER BY event.mtime DESC;",
    TAG_BRANCH
  );
  db_prepare(&q2,
    "SELECT filename.name, fileage.fid\n"
    "  FROM fileage, filename\n"
    " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid"

  );
  while( db_step(&q1)==SQLITE_ROW ){
    double age = baseTime - db_column_double(&q1, 0);
    int mid = db_column_int(&q1, 1);
    const char *zUuid = db_column_text(&q1, 2);
    const char *zComment = db_column_text(&q1, 3);
    const char *zUser = db_column_text(&q1, 4);
    const char *zBranch = db_column_text(&q1, 5);
    char *zAge = human_readable_age(age);
    @ <tr><td>%s(zAge)</td>
    @ <td>
    db_bind_int(&q2, ":mid", mid);
    while( db_step(&q2)==SQLITE_ROW ){

      const char *zFile = db_column_text(&q2,0);

      @ %z(href("%R/file?name=%T&ci=%!S",zFile,zUuid))%h(zFile)</a> \
      if( showId ){
        int fid = db_column_int(&q2,1);
        @ (%d(fid))<br />
      }else{
        @ </a><br />
      }
    }
    db_reset(&q2);
    @ </td>
    @ <td>
    @ %W(zComment)
    @ (check-in:&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>,
    if( showId ){
      @ id: %d(mid)
    }
    @ user:&nbsp;%z(href("%R/timeline?u=%t&c=%!S&nd",zUser,zUuid))%h(zUser)</a>,
    @ branch:&nbsp;\
    @ %z(href("%R/timeline?r=%t&c=%!S&nd",zBranch,zUuid))%h(zBranch)</a>)
    @ </td></tr>
Changes to src/info.c.
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
  }
  if( zCI && strlen(zCI)==0 ){ zCI = 0; }
  if( zCI
   && name_to_uuid2(zCI, "ci", &zCIUuid)
   && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0
  ){
    isSymbolicCI = 1;
    isBranchCI = db_exists(
       "SELECT 1 FROM tagxref, blob"
       " WHERE blob.uuid=%Q AND tagxref.rid=blob.rid"
       "   AND tagxref.value=%Q AND tagxref.tagtype>0"
       "   AND tagxref.tagid=%d",
       zCIUuid, zCI, TAG_BRANCH
    );
  }

  /* The name= query parameter (or at least one of its alternative
  ** spellings) is required.  Except for /file, show a top-level
  ** directory listing if name= is omitted.
  */
  if( zName==0 ){







|
<
<
<
<
<
<







2148
2149
2150
2151
2152
2153
2154
2155






2156
2157
2158
2159
2160
2161
2162
  }
  if( zCI && strlen(zCI)==0 ){ zCI = 0; }
  if( zCI
   && name_to_uuid2(zCI, "ci", &zCIUuid)
   && sqlite3_strnicmp(zCIUuid, zCI, strlen(zCI))!=0
  ){
    isSymbolicCI = 1;
    isBranchCI = branch_includes_uuid(zCI, zCIUuid);






  }

  /* The name= query parameter (or at least one of its alternative
  ** spellings) is required.  Except for /file, show a top-level
  ** directory listing if name= is omitted.
  */
  if( zName==0 ){