Fossil

Check-in [da9fcdc9]
Login

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

Overview
Comment:Refactor the control-artifact parser.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | experimental
Files: files | file ages | folders
SHA1: da9fcdc95a12f22394078bc12e82d422c5ae1650
User & Date: drh 2010-10-15 20:37:16.000
Context
2010-10-16
16:32
Bring over the latest bug fixes from trunk. ... (check-in: b2175857 user: drh tags: experimental)
2010-10-15
20:37
Refactor the control-artifact parser. ... (check-in: da9fcdc9 user: drh tags: experimental)
17:13
Replace all malloc() calls with fossil_malloc(). The fossil_malloc() routine panics rather than return a NULL pointer. ... (check-in: 8f41b2fa user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/branch.c.
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  char *zUuid;           /* Artifact ID of origin */
  Stmt q;                /* Generic query */
  const char *zBranch;   /* Name of the new branch */
  char *zDate;           /* Date that branch was created */
  char *zComment;        /* Check-in comment for the new branch */
  const char *zColor;    /* Color of the new branch */
  Blob branch;           /* manifest for the new branch */
  Blob parent;           /* root check-in manifest */
  Manifest mParent;      /* Parsed parent manifest */
  Blob mcksum;           /* Self-checksum on the manifest */
  const char *zDateOvrd; /* Override date string */
  const char *zUserOvrd; /* Override user name */
 
  noSign = find_option("nosign","",0)!=0;
  zColor = find_option("bgcolor","c",1);
  zDateOvrd = find_option("date-override",0,1);







<
|







33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
  char *zUuid;           /* Artifact ID of origin */
  Stmt q;                /* Generic query */
  const char *zBranch;   /* Name of the new branch */
  char *zDate;           /* Date that branch was created */
  char *zComment;        /* Check-in comment for the new branch */
  const char *zColor;    /* Color of the new branch */
  Blob branch;           /* manifest for the new branch */

  Manifest *pParent;     /* Parsed parent manifest */
  Blob mcksum;           /* Self-checksum on the manifest */
  const char *zDateOvrd; /* Override date string */
  const char *zUserOvrd; /* Override user name */
 
  noSign = find_option("nosign","",0)!=0;
  zColor = find_option("bgcolor","c",1);
  zDateOvrd = find_option("date-override",0,1);
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  zComment = mprintf("Create new branch named \"%h\"", zBranch);
  blob_appendf(&branch, "C %F\n", zComment);
  zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
  zDate[10] = 'T';
  blob_appendf(&branch, "D %s\n", zDate);

  /* Copy all of the content from the parent into the branch */
  content_get(rootid, &parent);
  manifest_parse(&mParent, &parent);
  if( mParent.type!=CFTYPE_MANIFEST ){
    fossil_fatal("%s is not a valid check-in", g.argv[4]);
  }
  for(i=0; i<mParent.nFile; ++i){
    if( mParent.aFile[i].zPerm[0] ){
      blob_appendf(&branch, "F %F %s %s\n",
                   mParent.aFile[i].zName,
                   mParent.aFile[i].zUuid,
                   mParent.aFile[i].zPerm);
    }else{
      blob_appendf(&branch, "F %F %s\n",
                   mParent.aFile[i].zName,
                   mParent.aFile[i].zUuid);
    }
  }
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
  blob_appendf(&branch, "P %s\n", zUuid);
  blob_appendf(&branch, "R %s\n", mParent.zRepoCksum);
  manifest_clear(&mParent);

  /* Add the symbolic branch name and the "branch" tag to identify
  ** this as a new branch */
  if( zColor!=0 ){
    blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
  }
  blob_appendf(&branch, "T *branch * %F\n", zBranch);







|
|
<


|
|

|
|
|


|
|




|
|







78
79
80
81
82
83
84
85
86

87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
  zComment = mprintf("Create new branch named \"%h\"", zBranch);
  blob_appendf(&branch, "C %F\n", zComment);
  zDate = date_in_standard_format(zDateOvrd ? zDateOvrd : "now");
  zDate[10] = 'T';
  blob_appendf(&branch, "D %s\n", zDate);

  /* Copy all of the content from the parent into the branch */
  pParent = manifest_get(rootid, CFTYPE_MANIFEST);
  if( pParent==0 ){

    fossil_fatal("%s is not a valid check-in", g.argv[4]);
  }
  for(i=0; i<pParent->nFile; ++i){
    if( pParent->aFile[i].zPerm[0] ){
      blob_appendf(&branch, "F %F %s %s\n",
                   pParent->aFile[i].zName,
                   pParent->aFile[i].zUuid,
                   pParent->aFile[i].zPerm);
    }else{
      blob_appendf(&branch, "F %F %s\n",
                   pParent->aFile[i].zName,
                   pParent->aFile[i].zUuid);
    }
  }
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rootid);
  blob_appendf(&branch, "P %s\n", zUuid);
  blob_appendf(&branch, "R %s\n", pParent->zRepoCksum);
  manifest_destroy(pParent);

  /* Add the symbolic branch name and the "branch" tag to identify
  ** this as a new branch */
  if( zColor!=0 ){
    blob_appendf(&branch, "T *bgcolor * %F\n", zColor);
  }
  blob_appendf(&branch, "T *branch * %F\n", zBranch);
Changes to src/browse.c.
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
  char *zPrefix;
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob content;
  Blob dirname;
  Manifest m;
  const char *zSubdirLink;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  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 ){
    if( (rid = name_to_rid(zCI))==0 || content_get(rid, &content)==0 ){
      zCI = 0;  /* No artifact named zCI exists */
    }else if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){
      zCI = 0;  /* The artifact exists but is not a manifest */
    }else{
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    }
  }









|


















|







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
  char *zPrefix;
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Blob content;
  Blob dirname;
  Manifest *pM = 0;
  const char *zSubdirLink;

  login_check_credentials();
  if( !g.okHistory ){ login_needed(); return; }
  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 ){
    if( (rid = name_to_rid(zCI))==0 || content_get(rid, &content)==0 ){
      zCI = 0;  /* No artifact named zCI exists */
    }else if( (pM = manifest_new(&content))!=0 || pM->type!=CFTYPE_MANIFEST ){
      zCI = 0;  /* The artifact exists but is not a manifest */
    }else{
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    }
  }


186
187
188
189
190
191
192

193
194
195

196
197
198
199
200
201
202
203
204
  */
  db_multi_exec(
     "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);"
     "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);"
  );
  if( zCI ){
    Stmt ins;

    int i;
    db_prepare(&ins, "INSERT INTO allfiles VALUES(:x, :u)");
    for(i=0; i<m.nFile; i++){

      db_bind_text(&ins, ":x", m.aFile[i].zName);
      db_bind_text(&ins, ":u", m.aFile[i].zUuid);
      db_step(&ins);
      db_reset(&ins);
    }
    db_finalize(&ins);
  }else{
    db_multi_exec(
      "INSERT INTO allfiles SELECT name, NULL FROM filename"







>
|

|
>
|
|







186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
  */
  db_multi_exec(
     "CREATE TEMP TABLE localfiles(x UNIQUE NOT NULL, u);"
     "CREATE TEMP TABLE allfiles(x UNIQUE NOT NULL, u);"
  );
  if( zCI ){
    Stmt ins;
    ManifestFile *pFile;

    db_prepare(&ins, "INSERT INTO allfiles VALUES(:x, :u)");
    manifest_file_rewind(pM);
    while( (pFile = manifest_file_next(pM,0))!=0 ){
      db_bind_text(&ins, ":x", pFile->zName);
      db_bind_text(&ins, ":u", pFile->zUuid);
      db_step(&ins);
      db_reset(&ins);
    }
    db_finalize(&ins);
  }else{
    db_multi_exec(
      "INSERT INTO allfiles SELECT name, NULL FROM filename"
244
245
246
247
248
249
250

251
252
253
      @ <li><a href="%s(g.zBaseURL)/artifact?name=%s(zUuid)">%h(zFN)</a></li>
    }else{
      @ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN)
      @     </a></li>
    }
  }
  db_finalize(&q);

  @ </ul></td></tr></table>
  style_footer();
}







>



246
247
248
249
250
251
252
253
254
255
256
      @ <li><a href="%s(g.zBaseURL)/artifact?name=%s(zUuid)">%h(zFN)</a></li>
    }else{
      @ <li><a href="%s(g.zBaseURL)/finfo?name=%T(zPrefix)%T(zFN)">%h(zFN)
      @     </a></li>
    }
  }
  db_finalize(&q);
  manifest_destroy(pM);
  @ </ul></td></tr></table>
  style_footer();
}
Changes to src/checkout.c.
109
110
111
112
113
114
115
116
117
118
119

120
121
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
148
149
*/
void manifest_to_disk(int vid){
  char *zManFile;
  Blob manifest;
  Blob hash;
  Blob filename;
  int baseLen;
  int i;
  int seenManifest = 0;
  int seenManifestUuid = 0;
  Manifest m;


  /* Check the EXE permission status of all files
  */
  blob_zero(&manifest);
  content_get(vid, &manifest);
  manifest_parse(&m, &manifest);
  blob_zero(&filename);
  blob_appendf(&filename, "%s/", g.zLocalRoot);
  baseLen = blob_size(&filename);
  for(i=0; i<m.nFile; i++){ 

    int isExe;
    blob_append(&filename, m.aFile[i].zName, -1);
    isExe = m.aFile[i].zPerm && strstr(m.aFile[i].zPerm, "x");
    file_setexe(blob_str(&filename), isExe);
    set_or_clear_isexe(m.aFile[i].zName, vid, isExe);
    blob_resize(&filename, baseLen);
    if( memcmp(m.aFile[i].zName, "manifest", 8)==0 ){
      if( m.aFile[i].zName[8]==0 ) seenManifest = 1;
      if( strcmp(&m.aFile[i].zName[8], ".uuid")==0 ) seenManifestUuid = 1;
    }
  }
  blob_reset(&filename);
  manifest_clear(&m);

  blob_zero(&manifest);
  content_get(vid, &manifest);
  if( !seenManifest ){
    zManFile = mprintf("%smanifest", g.zLocalRoot);
    blob_write_to_file(&manifest, zManFile);
    free(zManFile);







<


|
>



<
|
|



|
>

|
|

|

|
|
|



|







109
110
111
112
113
114
115

116
117
118
119
120
121
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
148
149
*/
void manifest_to_disk(int vid){
  char *zManFile;
  Blob manifest;
  Blob hash;
  Blob filename;
  int baseLen;

  int seenManifest = 0;
  int seenManifestUuid = 0;
  Manifest *pManifest;
  ManifestFile *pFile;

  /* Check the EXE permission status of all files
  */

  pManifest = manifest_get(vid, CFTYPE_MANIFEST);
  if( pManifest==0 ) return;
  blob_zero(&filename);
  blob_appendf(&filename, "%s/", g.zLocalRoot);
  baseLen = blob_size(&filename);
  manifest_file_rewind(pManifest);
  while( (pFile = manifest_file_next(pManifest, 0))!=0 ){
    int isExe;
    blob_append(&filename, pFile->zName, -1);
    isExe = pFile->zPerm && strstr(pFile->zPerm, "x");
    file_setexe(blob_str(&filename), isExe);
    set_or_clear_isexe(pFile->zName, vid, isExe);
    blob_resize(&filename, baseLen);
    if( memcmp(pFile->zName, "manifest", 8)==0 ){
      if( pFile->zName[8]==0 ) seenManifest = 1;
      if( strcmp(&pFile->zName[8], ".uuid")==0 ) seenManifestUuid = 1;
    }
  }
  blob_reset(&filename);
  manifest_destroy(pManifest);

  blob_zero(&manifest);
  content_get(vid, &manifest);
  if( !seenManifest ){
    zManFile = mprintf("%smanifest", g.zLocalRoot);
    blob_write_to_file(&manifest, zManFile);
    free(zManFile);
Changes to src/diffcmd.c.
352
353
354
355
356
357
358
359
360
361
362
363
364


365


366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
*/
static void diff_all_two_versions(
  const char *zFrom,
  const char *zTo,
  const char *zDiffCmd,
  int diffFlags
){
  Manifest mFrom, mTo;
  int iFrom, iTo;
  int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
  int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;

  manifest_from_name(zFrom, &mFrom);


  manifest_from_name(zTo, &mTo);


  iFrom = iTo = 0;
  while( iFrom<mFrom.nFile || iTo<mTo.nFile ){
    int cmp;
    if( iFrom>=mFrom.nFile ){
      cmp = +1;
    }else if( iTo>=mTo.nFile ){
      cmp = -1;
    }else{
      cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName);
    }
    if( cmp<0 ){
      printf("DELETED %s\n", mFrom.aFile[iFrom].zName);
      if( asNewFlag ){
        diff_manifest_entry(&mFrom.aFile[iFrom], 0, zDiffCmd, ignoreEolWs);
      }
      iFrom++;
    }else if( cmp>0 ){
      printf("ADDED   %s\n", mTo.aFile[iTo].zName);
      if( asNewFlag ){
        diff_manifest_entry(0, &mTo.aFile[iTo], zDiffCmd, ignoreEolWs);
      }
      iTo++;
    }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){
      /* No changes */
      iFrom++;
      iTo++;
    }else{
      printf("CHANGED %s\n", mFrom.aFile[iFrom].zName);
      diff_manifest_entry(&mFrom.aFile[iFrom], &mTo.aFile[iTo],
                          zDiffCmd, ignoreEolWs);
      iFrom++;
      iTo++;
    }
  }
  manifest_clear(&mFrom);
  manifest_clear(&mTo);
}

/*
** COMMAND: diff
** COMMAND: gdiff
**
** Usage: %fossil diff|gdiff ?options? ?FILE?







|
|



|
>
>
|
>
>
|
|

|

|


|


|

|

|

|

|

|
|

|
|

|
<
|
|
|


|
|







352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397

398
399
400
401
402
403
404
405
406
407
408
409
410
411
*/
static void diff_all_two_versions(
  const char *zFrom,
  const char *zTo,
  const char *zDiffCmd,
  int diffFlags
){
  Manifest *pFrom, *pTo;
  ManifestFile *pFromFile, *pToFile;
  int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
  int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;

  pFrom = manifest_from_name(zFrom);
  manifest_file_rewind(pFrom);
  pFromFile = manifest_file_next(pFrom,0);
  pTo = manifest_from_name(zTo);
  manifest_file_rewind(pTo);
  pToFile = manifest_file_next(pTo,0);

  while( pFromFile || pToFile ){
    int cmp;
    if( pFromFile==0 ){
      cmp = +1;
    }else if( pToFile==0 ){
      cmp = -1;
    }else{
      cmp = strcmp(pFromFile->zName, pToFile->zName);
    }
    if( cmp<0 ){
      printf("DELETED %s\n", pFromFile->zName);
      if( asNewFlag ){
        diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs);
      }
      pFromFile = manifest_file_next(pFrom,0);
    }else if( cmp>0 ){
      printf("ADDED   %s\n", pToFile->zName);
      if( asNewFlag ){
        diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs);
      }
      pToFile = manifest_file_next(pTo,0);
    }else if( strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
      /* No changes */
      pFromFile = manifest_file_next(pFrom,0);
      pToFile = manifest_file_next(pTo,0);
    }else{
      printf("CHANGED %s\n", pFromFile->zName);

      diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs);
      pFromFile = manifest_file_next(pFrom,0);
      pToFile = manifest_file_next(pTo,0);
    }
  }
  manifest_destroy(pFrom);
  manifest_destroy(pTo);
}

/*
** COMMAND: diff
** COMMAND: gdiff
**
** Usage: %fossil diff|gdiff ?options? ?FILE?
Changes to src/doc.c.
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412

413
414
415
416
417
418
419
420
421
422
423
424
425
426
                    " WHERE vid=%d AND fname=%Q", vid, zName);
    if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
      goto doc_not_found;
    }

    if( rid==0 ){
      Stmt s;
      Blob baseline;
      Manifest m;

      /* Add the vid baseline to the cache */
      if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
        db_multi_exec("DELETE FROM vcache");
      }
      if( content_get(vid, &baseline)==0 ){
        goto doc_not_found;
      }
      if( manifest_parse(&m, &baseline)==0 || m.type!=CFTYPE_MANIFEST ){
        goto doc_not_found;
      }
      db_prepare(&s,
        "INSERT INTO vcache(vid,fname,rid)"
        " SELECT %d, :fname, rid FROM blob"
        "  WHERE uuid=:uuid",
        vid
      );
      for(i=0; i<m.nFile; i++){

        db_bind_text(&s, ":fname", m.aFile[i].zName);
        db_bind_text(&s, ":uuid", m.aFile[i].zUuid);
        db_step(&s);
        db_reset(&s);
      }
      db_finalize(&s);
      manifest_clear(&m);

      /* Try again to find the file */
      rid = db_int(0, "SELECT rid FROM vcache"
                      " WHERE vid=%d AND fname=%Q", vid, zName);
    }
    if( rid==0 ){
      goto doc_not_found;







|
|





|
<
<
|








|
>
|
|




|







386
387
388
389
390
391
392
393
394
395
396
397
398
399
400


401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
                    " WHERE vid=%d AND fname=%Q", vid, zName);
    if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
      goto doc_not_found;
    }

    if( rid==0 ){
      Stmt s;
      Manifest *pM;
      ManifestFile *pFile;

      /* Add the vid baseline to the cache */
      if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
        db_multi_exec("DELETE FROM vcache");
      }
      pM = manifest_get(vid, CFTYPE_MANIFEST);


      if( pM==0 ){
        goto doc_not_found;
      }
      db_prepare(&s,
        "INSERT INTO vcache(vid,fname,rid)"
        " SELECT %d, :fname, rid FROM blob"
        "  WHERE uuid=:uuid",
        vid
      );
      manifest_file_rewind(pM);
      while( (pFile = manifest_file_next(pM,0))!=0 ){
        db_bind_text(&s, ":fname", pFile->zName);
        db_bind_text(&s, ":uuid", pFile->zUuid);
        db_step(&s);
        db_reset(&s);
      }
      db_finalize(&s);
      manifest_destroy(pM);

      /* Try again to find the file */
      rid = db_int(0, "SELECT rid FROM vcache"
                      " WHERE vid=%d AND fname=%Q", vid, zName);
    }
    if( rid==0 ){
      goto doc_not_found;
Changes to src/event.c.
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  int rid = 0;             /* rid of the event artifact */
  char *zUuid;             /* UUID corresponding to rid */
  const char *zEventId;    /* Event identifier */
  char *zETime;            /* Time of the event */
  char *zATime;            /* Time the artifact was created */
  int specRid;             /* rid specified by aid= parameter */
  int prevRid, nextRid;    /* Previous or next edits of this event */
  Manifest m;              /* Parsed event artifact */
  Blob content;            /* Original event artifact content */
  Blob fullbody;           /* Complete content of the event body */
  Blob title;              /* Title extracted from the event body */
  Blob tail;               /* Event body that comes after the title */
  Stmt q1;                 /* Query to search for the event */
  int showDetail;          /* True to show details */









|
<







61
62
63
64
65
66
67
68

69
70
71
72
73
74
75
  int rid = 0;             /* rid of the event artifact */
  char *zUuid;             /* UUID corresponding to rid */
  const char *zEventId;    /* Event identifier */
  char *zETime;            /* Time of the event */
  char *zATime;            /* Time the artifact was created */
  int specRid;             /* rid specified by aid= parameter */
  int prevRid, nextRid;    /* Previous or next edits of this event */
  Manifest *pEvent;        /* Parsed event artifact */

  Blob fullbody;           /* Complete content of the event body */
  Blob title;              /* Title extracted from the event body */
  Blob tail;               /* Event body that comes after the title */
  Stmt q1;                 /* Query to search for the event */
  int showDetail;          /* True to show details */


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    return;
  }
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  showDetail = atoi(PD("detail","0"));

  /* Extract the event content.
  */
  memset(&m, 0, sizeof(m));
  blob_zero(&m.content);
  content_get(rid, &content);
  manifest_parse(&m, &content);
  if( m.type!=CFTYPE_EVENT ){
    fossil_panic("Object #%d is not an event", rid);
  }
  blob_init(&fullbody, m.zWiki, -1);
  if( wiki_find_title(&fullbody, &title, &tail) ){
    style_header(blob_str(&title));
  }else{
    style_header("Event %S", zEventId);
    tail = fullbody;
  }
  if( g.okWrWiki && g.okWrite && nextRid==0 ){
    style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
                          g.zTop, zEventId);
  }
  zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate);
  style_submenu_element("Context", "Context", "%s/timeline?c=%T",
                        g.zTop, zETime);
  if( g.okHistory ){
    if( showDetail ){
      style_submenu_element("Plain", "Plain", "%s/event?name=%s&amp;aid=%s",
                            g.zTop, zEventId, zUuid);
      if( nextRid ){







<
<
|
<
|


|










|







110
111
112
113
114
115
116


117

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
    return;
  }
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  showDetail = atoi(PD("detail","0"));

  /* Extract the event content.
  */


  pEvent = manifest_get(rid, CFTYPE_EVENT);

  if( pEvent==0 ){
    fossil_panic("Object #%d is not an event", rid);
  }
  blob_init(&fullbody, pEvent->zWiki, -1);
  if( wiki_find_title(&fullbody, &title, &tail) ){
    style_header(blob_str(&title));
  }else{
    style_header("Event %S", zEventId);
    tail = fullbody;
  }
  if( g.okWrWiki && g.okWrite && nextRid==0 ){
    style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
                          g.zTop, zEventId);
  }
  zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
  style_submenu_element("Context", "Context", "%s/timeline?c=%T",
                        g.zTop, zETime);
  if( g.okHistory ){
    if( showDetail ){
      style_submenu_element("Plain", "Plain", "%s/event?name=%s&amp;aid=%s",
                            g.zTop, zEventId, zUuid);
      if( nextRid ){
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
  }

  if( showDetail && g.okHistory ){
    int i;
    const char *zClr = 0;
    Blob comment;

    zATime = db_text(0, "SELECT datetime(%.17g)", m.rDate);
    @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at
    @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>]
    @ entered by user <b>%h(m.zUser)</b> on
    @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p>
    @ <blockquote>
    for(i=0; i<m.nTag; i++){
      if( strcmp(m.aTag[i].zName,"+bgcolor")==0 ){
        zClr = m.aTag[i].zValue;
      }
    }
    if( zClr && zClr[0]==0 ) zClr = 0;
    if( zClr ){
      @ <div style="background-color: %h(zClr);">
    }else{
      @ <div>
    }
    blob_init(&comment, m.zComment, -1);
    wiki_convert(&comment, 0, WIKI_INLINE);
    blob_reset(&comment);
    @ </div>
    @ </blockquote><hr />
  }  

  wiki_convert(&tail, 0, 0);
  style_footer();
  manifest_clear(&m);
}

/*
** WEBPAGE: eventedit
** URL: /eventedit?name=EVENTID
**
** Edit an event.  If name is omitted, create a new event.







|


|


|
|
|








|








|







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
  }

  if( showDetail && g.okHistory ){
    int i;
    const char *zClr = 0;
    Blob comment;

    zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
    @ <p>Event [<a href="%s(g.zTop)/artifact/%s(zUuid)">%S(zUuid)</a>] at
    @ [<a href="%s(g.zTop)/timeline?c=%T(zETime)">%s(zETime)</a>]
    @ entered by user <b>%h(pEvent->zUser)</b> on
    @ [<a href="%s(g.zTop)/timeline?c=%T(zATime)">%s(zATime)</a>]:</p>
    @ <blockquote>
    for(i=0; i<pEvent->nTag; i++){
      if( strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
        zClr = pEvent->aTag[i].zValue;
      }
    }
    if( zClr && zClr[0]==0 ) zClr = 0;
    if( zClr ){
      @ <div style="background-color: %h(zClr);">
    }else{
      @ <div>
    }
    blob_init(&comment, pEvent->zComment, -1);
    wiki_convert(&comment, 0, WIKI_INLINE);
    blob_reset(&comment);
    @ </div>
    @ </blockquote><hr />
  }  

  wiki_convert(&tail, 0, 0);
  style_footer();
  manifest_destroy(pEvent);
}

/*
** WEBPAGE: eventedit
** URL: /eventedit?name=EVENTID
**
** Edit an event.  If name is omitted, create a new event.
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
  if( strcmp(zClr,"##")==0 ) zClr = PD("cclr","");


  /* If editing an existing event, extract the key fields to use as
  ** a starting point for the edit.
  */
  if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
    Manifest m;
    Blob content;
    memset(&m, 0, sizeof(m));
    blob_zero(&m.content);
    content_get(rid, &content);
    manifest_parse(&m, &content);
    if( m.type==CFTYPE_EVENT ){
      if( zBody==0 ) zBody = m.zWiki;
      if( zETime==0 ){
        zETime = db_text(0, "SELECT datetime(%.17g)", m.rEventDate);
      }
      if( zComment==0 ) zComment = m.zComment;
    }
    if( zTags==0 ){
      zTags = db_text(0,
        "SELECT group_concat(substr(tagname,5),', ')"
        "  FROM tagxref, tag"
        " WHERE tagxref.rid=%d"
        "   AND tagxref.tagid=tag.tagid"







|
<
<
<
|
<
|
|

|

|







253
254
255
256
257
258
259
260



261

262
263
264
265
266
267
268
269
270
271
272
273
274
  if( strcmp(zClr,"##")==0 ) zClr = PD("cclr","");


  /* If editing an existing event, extract the key fields to use as
  ** a starting point for the edit.
  */
  if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
    Manifest *pEvent;



    pEvent = manifest_get(rid, CFTYPE_EVENT);

    if( pEvent && pEvent->type==CFTYPE_EVENT ){
      if( zBody==0 ) zBody = pEvent->zWiki;
      if( zETime==0 ){
        zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
      }
      if( zComment==0 ) zComment = pEvent->zComment;
    }
    if( zTags==0 ){
      zTags = db_text(0,
        "SELECT group_concat(substr(tagname,5),', ')"
        "  FROM tagxref, tag"
        " WHERE tagxref.rid=%d"
        "   AND tagxref.tagid=tag.tagid"
Changes to src/info.c.
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
  }else{
    style_header("Wiki Information");
    rid = 0;
  }
  db_finalize(&q);
  showTags(rid, "wiki-*");
  if( rid ){
    Blob content;
    Manifest m;
    memset(&m, 0, sizeof(m));
    blob_zero(&m.content);
    content_get(rid, &content);
    manifest_parse(&m, &content);
    if( m.type==CFTYPE_WIKI ){
      Blob wiki;
      blob_init(&wiki, m.zWiki, -1);
      @ <div class="section">Content</div>
      wiki_convert(&wiki, 0, 0);
      blob_reset(&wiki);
    }
    manifest_clear(&m);
  }
  style_footer();
}

/*
** Show a webpage error message
*/







<
|
<
<
|
<
|

|




|







541
542
543
544
545
546
547

548


549

550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
  }else{
    style_header("Wiki Information");
    rid = 0;
  }
  db_finalize(&q);
  showTags(rid, "wiki-*");
  if( rid ){

    Manifest *pWiki;


    pWiki = manifest_get(rid, CFTYPE_WIKI);

    if( pWiki ){
      Blob wiki;
      blob_init(&wiki, pWiki->zWiki, -1);
      @ <div class="section">Content</div>
      wiki_convert(&wiki, 0, 0);
      blob_reset(&wiki);
    }
    manifest_destroy(pWiki);
  }
  style_footer();
}

/*
** Show a webpage error message
*/
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
  style_footer();
}

/*
** Find an checkin based on query parameter zParam and parse its
** manifest.  Return the number of errors.
*/
static int vdiff_parse_manifest(const char *zParam, int *pRid, Manifest *pM){
  int rid;
  Blob content;

  *pRid = rid = name_to_rid_www(zParam);
  if( rid==0 ){
    webpage_error("Missing \"%s\" query parameter.", zParam);
    return 1;
  }
  if( !is_a_version(rid) ){
    webpage_error("Artifact %s is not a checkin.", P(zParam));
    return 1;
  }
  content_get(rid, &content);
  manifest_parse(pM, &content);
  return 0;
}

/*
** Output a description of a check-in
*/
void checkin_description(int rid){
  Stmt q;







|

<




|



|

<
<
|







574
575
576
577
578
579
580
581
582

583
584
585
586
587
588
589
590
591
592


593
594
595
596
597
598
599
600
  style_footer();
}

/*
** Find an checkin based on query parameter zParam and parse its
** manifest.  Return the number of errors.
*/
static Manifest *vdiff_parse_manifest(const char *zParam, int *pRid){
  int rid;


  *pRid = rid = name_to_rid_www(zParam);
  if( rid==0 ){
    webpage_error("Missing \"%s\" query parameter.", zParam);
    return 0;
  }
  if( !is_a_version(rid) ){
    webpage_error("Artifact %s is not a checkin.", P(zParam));
    return 0;
  }


  return manifest_get(rid, CFTYPE_MANIFEST);
}

/*
** Output a description of a check-in
*/
void checkin_description(int rid){
  Stmt q;
633
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
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
** URL: /vdiff?from=UUID&amp;to=UUID&amp;detail=BOOLEAN
**
** Show all differences between two checkins.  
*/
void vdiff_page(void){
  int ridFrom, ridTo;
  int showDetail = 0;
  int iFrom, iTo;
  Manifest mFrom, mTo;

  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }
  login_anonymous_available();

  if( vdiff_parse_manifest("from", &ridFrom, &mFrom) ) return;

  if( vdiff_parse_manifest("to", &ridTo, &mTo) ) return;

  showDetail = atoi(PD("detail","0"));
  style_header("Check-in Differences");
  @ <h2>Difference From:</h2><blockquote>
  checkin_description(ridFrom);
  @ </blockquote><h2>To:</h2><blockquote>
  checkin_description(ridTo);
  @ </blockquote><hr /><p>


  iFrom = iTo = 0;


  while( iFrom<mFrom.nFile && iTo<mTo.nFile ){
    int cmp;
    if( iFrom>=mFrom.nFile ){
      cmp = +1;
    }else if( iTo>=mTo.nFile ){
      cmp = -1;
    }else{
      cmp = strcmp(mFrom.aFile[iFrom].zName, mTo.aFile[iTo].zName);
    }
    if( cmp<0 ){
      append_file_change_line(mFrom.aFile[iFrom].zName, 
                              mFrom.aFile[iFrom].zUuid, 0, 0);
      iFrom++;
    }else if( cmp>0 ){
      append_file_change_line(mTo.aFile[iTo].zName, 
                              0, mTo.aFile[iTo].zUuid, 0);
      iTo++;
    }else if( strcmp(mFrom.aFile[iFrom].zUuid, mTo.aFile[iTo].zUuid)==0 ){
      /* No changes */
      iFrom++;
      iTo++;
    }else{
      append_file_change_line(mFrom.aFile[iFrom].zName, 
                              mFrom.aFile[iFrom].zUuid,
                              mTo.aFile[iTo].zUuid, showDetail);
      iFrom++;
      iTo++;
    }
  }
  manifest_clear(&mFrom);
  manifest_clear(&mTo);

  style_footer();
}

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







|
|





|
>
|
>








>
|
>
>
|

|

|


|


|
|
|

|
|
|
|

|
|

|
|
|
|
|


|
|







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
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
** URL: /vdiff?from=UUID&amp;to=UUID&amp;detail=BOOLEAN
**
** Show all differences between two checkins.  
*/
void vdiff_page(void){
  int ridFrom, ridTo;
  int showDetail = 0;
  Manifest *pFrom, *pTo;
  ManifestFile *pFileFrom, *pFileTo;

  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }
  login_anonymous_available();

  pFrom = vdiff_parse_manifest("from", &ridFrom);
  if( pFrom==0 ) return;
  pTo = vdiff_parse_manifest("to", &ridTo);
  if( pTo==0 ) return;
  showDetail = atoi(PD("detail","0"));
  style_header("Check-in Differences");
  @ <h2>Difference From:</h2><blockquote>
  checkin_description(ridFrom);
  @ </blockquote><h2>To:</h2><blockquote>
  checkin_description(ridTo);
  @ </blockquote><hr /><p>

  manifest_file_rewind(pFrom);
  pFileFrom = manifest_file_next(pFrom, 0);
  manifest_file_rewind(pTo);
  pFileTo = manifest_file_next(pTo, 0);
  while( pFileFrom || pFileTo ){
    int cmp;
    if( pFileFrom==0 ){
      cmp = +1;
    }else if( pFileTo==0 ){
      cmp = -1;
    }else{
      cmp = strcmp(pFileFrom->zName, pFileTo->zName);
    }
    if( cmp<0 ){
      append_file_change_line(pFileFrom->zName, 
                              pFileFrom->zUuid, 0, 0);
      pFileFrom = manifest_file_next(pFrom, 0);
    }else if( cmp>0 ){
      append_file_change_line(pFileTo->zName, 
                              0, pFileTo->zUuid, 0);
      pFileTo = manifest_file_next(pTo, 0);
    }else if( strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){
      /* No changes */
      pFileFrom = manifest_file_next(pFrom, 0);
      pFileTo = manifest_file_next(pTo, 0);
    }else{
      append_file_change_line(pFileFrom->zName, 
                              pFileFrom->zUuid,
                              pFileTo->zUuid, showDetail);
      pFileFrom = manifest_file_next(pFrom, 0);
      pFileTo = manifest_file_next(pTo, 0);
    }
  }
  manifest_destroy(pFrom);
  manifest_destroy(pTo);

  style_footer();
}

/*
** Write a description of an object to the www reply.
**
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050

1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062


1063
1064
1065
1066
1067
1068
1069
** Look for "ci" and "filename" query parameters.  If found, try to
** use them to extract the record ID of an artifact for the file.
*/
int artifact_from_ci_and_filename(void){
  const char *zFilename;
  const char *zCI;
  int cirid;
  Blob content;
  Manifest m;
  int i;


  zCI = P("ci");
  if( zCI==0 ) return 0;
  zFilename = P("filename");
  if( zFilename==0 ) return 0;
  cirid = name_to_rid_www("ci");
  if( !content_get(cirid, &content) ) return 0;
  if( !manifest_parse(&m, &content) ) return 0;
  if( m.type!=CFTYPE_MANIFEST ) return 0;
  for(i=0; i<m.nFile; i++){
    if( strcmp(zFilename, m.aFile[i].zName)==0 ){
      return db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", m.aFile[i].zUuid);


    }
  }
  return 0;
}


/*







<
|
<
>






|
|
|
|
|
|
>
>







1039
1040
1041
1042
1043
1044
1045

1046

1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
** Look for "ci" and "filename" query parameters.  If found, try to
** use them to extract the record ID of an artifact for the file.
*/
int artifact_from_ci_and_filename(void){
  const char *zFilename;
  const char *zCI;
  int cirid;

  Manifest *pManifest;

  ManifestFile *pFile;

  zCI = P("ci");
  if( zCI==0 ) return 0;
  zFilename = P("filename");
  if( zFilename==0 ) return 0;
  cirid = name_to_rid_www("ci");
  pManifest = manifest_get(cirid, CFTYPE_MANIFEST);
  if( pManifest==0 ) return 0;
  manifest_file_rewind(pManifest);
  while( (pFile = manifest_file_next(pManifest,0))!=0 ){
    if( strcmp(zFilename, pFile->zName)==0 ){
      int rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", pFile->zUuid);
      manifest_destroy(pManifest);
      return rid;
    }
  }
  return 0;
}


/*
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205

1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
** WEBPAGE: tinfo
** URL: /tinfo?name=ARTIFACTID
**
** Show the details of a ticket change control artifact.
*/
void tinfo_page(void){
  int rid;
  Blob content;
  char *zDate;
  const char *zUuid;
  char zTktName[20];
  Manifest m;

  login_check_credentials();
  if( !g.okRdTkt ){ login_needed(); return; }
  rid = name_to_rid_www("name");
  if( rid==0 ){ fossil_redirect_home(); }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( g.okAdmin ){
    if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){
      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&amp;sub=1",
            g.zTop, zUuid);
    }else{
      style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
            g.zTop, zUuid);
    }
  }
  content_get(rid, &content);
  if( manifest_parse(&m, &content)==0 ){
    fossil_redirect_home();
  }
  if( m.type!=CFTYPE_TICKET ){
    fossil_redirect_home();
  }
  style_header("Ticket Change Details");
  zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate);
  memcpy(zTktName, m.zTicketUuid, 10);
  zTktName[10] = 0;
  if( g.okHistory ){
    @ <h2>Changes to ticket <a href="%s(m.zTicketUuid)">%s(zTktName)</a></h2>

    @
    @ <p>By %h(m.zUser) on %s(zDate).  See also:
    @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and
    @ <a href="%s(g.zTop)/tkthistory/%s(m.zTicketUuid)">ticket history</a>
    @ </p>
  }else{
    @ <h2>Changes to ticket %s(zTktName)</h2>
    @
    @ <p>By %h(m.zUser) on %s(zDate).
    @ </p>
  }
  @
  @ <ol>
  free(zDate);
  ticket_output_change_artifact(&m);
  manifest_clear(&m);
  style_footer();
}


/*
** WEBPAGE: info
** URL: info/ARTIFACTID







<



|















|
|
<
<
<



|
|


|
>

|

|
|



|





|
|







1165
1166
1167
1168
1169
1170
1171

1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192



1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
** WEBPAGE: tinfo
** URL: /tinfo?name=ARTIFACTID
**
** Show the details of a ticket change control artifact.
*/
void tinfo_page(void){
  int rid;

  char *zDate;
  const char *zUuid;
  char zTktName[20];
  Manifest *pTktChng;

  login_check_credentials();
  if( !g.okRdTkt ){ login_needed(); return; }
  rid = name_to_rid_www("name");
  if( rid==0 ){ fossil_redirect_home(); }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( g.okAdmin ){
    if( db_exists("SELECT 1 FROM shun WHERE uuid='%s'", zUuid) ){
      style_submenu_element("Unshun","Unshun", "%s/shun?uuid=%s&amp;sub=1",
            g.zTop, zUuid);
    }else{
      style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
            g.zTop, zUuid);
    }
  }
  pTktChng = manifest_get(rid, CFTYPE_TICKET);
  if( pTktChng==0 ){



    fossil_redirect_home();
  }
  style_header("Ticket Change Details");
  zDate = db_text(0, "SELECT datetime(%.12f)", pTktChng->rDate);
  memcpy(zTktName, pTktChng->zTicketUuid, 10);
  zTktName[10] = 0;
  if( g.okHistory ){
    @ <h2>Changes to ticket
    @ <a href="%s(pTktChng->zTicketUuid)">%s(zTktName)</a></h2>
    @
    @ <p>By %h(pTktChng->zUser) on %s(zDate).  See also:
    @ <a href="%s(g.zTop)/artifact/%T(zUuid)">artifact content</a>, and
    @ <a href="%s(g.zTop)/tkthistory/%s(pTktChng->zTicketUuid)">ticket
    @ history</a></p>
  }else{
    @ <h2>Changes to ticket %s(zTktName)</h2>
    @
    @ <p>By %h(pTktChng->zUser) on %s(zDate).
    @ </p>
  }
  @
  @ <ol>
  free(zDate);
  ticket_output_change_artifact(pTktChng);
  manifest_destroy(pTktChng);
  style_footer();
}


/*
** WEBPAGE: info
** URL: info/ARTIFACTID
Changes to src/manifest.c.
24
25
26
27
28
29
30

31
32
33
34
35
36
37












38
39
40
41
42
43
44
#include "manifest.h"
#include <assert.h>

#if INTERFACE
/*
** Types of control files
*/

#define CFTYPE_MANIFEST   1
#define CFTYPE_CLUSTER    2
#define CFTYPE_CONTROL    3
#define CFTYPE_WIKI       4
#define CFTYPE_TICKET     5
#define CFTYPE_ATTACHMENT 6
#define CFTYPE_EVENT      7













/*
** A parsed manifest or cluster.
*/
struct Manifest {
  Blob content;         /* The original content blob */
  int type;             /* Type of artifact.  One of CFTYPE_xxxxx */







>







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







24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "manifest.h"
#include <assert.h>

#if INTERFACE
/*
** Types of control files
*/
#define CFTYPE_ANY        0
#define CFTYPE_MANIFEST   1
#define CFTYPE_CLUSTER    2
#define CFTYPE_CONTROL    3
#define CFTYPE_WIKI       4
#define CFTYPE_TICKET     5
#define CFTYPE_ATTACHMENT 6
#define CFTYPE_EVENT      7

/*
** A single F-card within a manifest
*/
struct ManifestFile { 
  char *zName;           /* Name of a file */
  char *zUuid;           /* UUID of the file */
  char *zPerm;           /* File permissions */
  char *zPrior;          /* Prior name if the name was changed */
  int iRename;           /* index of renamed name in prior/next manifest */
};


/*
** A parsed manifest or cluster.
*/
struct Manifest {
  Blob content;         /* The original content blob */
  int type;             /* Type of artifact.  One of CFTYPE_xxxxx */
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
  char *zEventId;       /* UUID for an event.  E card. */
  char *zTicketUuid;    /* UUID for a ticket. K card. */
  char *zAttachName;    /* Filename of an attachment. A card. */
  char *zAttachSrc;     /* UUID of document being attached. A card. */
  char *zAttachTarget;  /* Ticket or wiki that attachment applies to.  A card */
  int nFile;            /* Number of F cards */
  int nFileAlloc;       /* Slots allocated in aFile[] */
  struct ManifestFile { 
    char *zName;           /* Name of a file */
    char *zUuid;           /* UUID of the file */
    char *zPerm;           /* File permissions */
    char *zPrior;          /* Prior name if the name was changed */
    int iRename;           /* index of renamed name in prior/next manifest */
  } *aFile;             /* One entry for each F card */
  int nParent;          /* Number of parents. */
  int nParentAlloc;     /* Slots allocated in azParent[] */
  char **azParent;      /* UUIDs of parents.  One for each P card argument */
  int nCChild;          /* Number of cluster children */
  int nCChildAlloc;     /* Number of closts allocated in azCChild[] */
  char **azCChild;      /* UUIDs of referenced objects in a cluster. M cards */
  int nTag;             /* Number of T Cards */







<
|
<
<
<
<
|







65
66
67
68
69
70
71

72




73
74
75
76
77
78
79
80
  char *zEventId;       /* UUID for an event.  E card. */
  char *zTicketUuid;    /* UUID for a ticket. K card. */
  char *zAttachName;    /* Filename of an attachment. A card. */
  char *zAttachSrc;     /* UUID of document being attached. A card. */
  char *zAttachTarget;  /* Ticket or wiki that attachment applies to.  A card */
  int nFile;            /* Number of F cards */
  int nFileAlloc;       /* Slots allocated in aFile[] */

  int iFile;            /* Index of current file in iterator */




  ManifestFile *aFile;  /* One entry for each F-card */
  int nParent;          /* Number of parents. */
  int nParentAlloc;     /* Slots allocated in azParent[] */
  char **azParent;      /* UUIDs of parents.  One for each P card argument */
  int nCChild;          /* Number of cluster children */
  int nCChildAlloc;     /* Number of closts allocated in azCChild[] */
  char **azCChild;      /* UUIDs of referenced objects in a cluster. M cards */
  int nTag;             /* Number of T Cards */
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
  Manifest aLine[MX_MANIFEST_CACHE];
} manifestCache;


/*
** Clear the memory allocated in a manifest object
*/
void manifest_clear(Manifest *p){
  blob_reset(&p->content);
  free(p->aFile);
  free(p->azParent);
  free(p->azCChild);
  free(p->aTag);
  free(p->aField);
  memset(p, 0, sizeof(*p));







|







105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
  Manifest aLine[MX_MANIFEST_CACHE];
} manifestCache;


/*
** Clear the memory allocated in a manifest object
*/
static void manifest_clear(Manifest *p){
  blob_reset(&p->content);
  free(p->aFile);
  free(p->azParent);
  free(p->azCChild);
  free(p->aTag);
  free(p->aField);
  memset(p, 0, sizeof(*p));
752
753
754
755
756
757
758












































759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780

781

















782



















783
784
785
786
787
788
789

manifest_syntax_error:
  /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
  md5sum_init();
  manifest_clear(p);
  return 0;
}













































/*
** COMMAND: test-parse-manifest
**
** Usage: %fossil test-parse-manifest FILENAME ?N?
**
** Parse the manifest and discarded.  Use for testing only.
*/
void manifest_test_parse_cmd(void){
  Manifest m;
  Blob b;
  int i;
  int n = 1;
  if( g.argc!=3 && g.argc!=4 ){
    usage("FILENAME");
  }
  db_must_be_within_tree();
  blob_read_from_file(&b, g.argv[2]);
  if( g.argc>3 ) n = atoi(g.argv[3]);
  for(i=0; i<n; i++){
    Blob b2;
    blob_copy(&b2, &b);

    manifest_parse(&m, &b2);

















    manifest_clear(&m);



















  }
}

/*
** Translate a filename into a filename-id (fnid).  Create a new fnid
** if no previously exists.
*/







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









|












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







760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
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
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878

manifest_syntax_error:
  /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
  md5sum_init();
  manifest_clear(p);
  return 0;
}

/*
** Allocate space for a Manifest object and populate it with a
** parse of pContent.  Return a pointer ot the new object.
**
** If pContent is not a well-formed control artifact, return 0.
**
** pContent is reset, regardless of whether or not a Manifest object
** is returned.
*/
Manifest *manifest_new(Blob *pContent){
  Manifest *p = fossil_malloc( sizeof(*p) );
  if( manifest_parse(p, pContent)==0 ){
    fossil_free(p);
    p = 0;
  }
  return p;
}

/*
** Get a manifest given the rid for the control artifact.  Return
** a pointer to the manifest on success or NULL if there is a failure.
*/
Manifest *manifest_get(int rid, int cfType){
  Blob content;
  Manifest *p;
  content_get(rid, &content);
  p = manifest_new(&content);
  if( p && cfType!=CFTYPE_ANY && cfType!=p->type ){
    manifest_destroy(p);
    p = 0;
  }
  return p;
}

/*
** Destroy a Manifest object previously obtained from manifest_new().
*/
void manifest_destroy(Manifest *p){
  if( p ){
    manifest_clear(p);
    fossil_free(p);
  }
}

/*
** COMMAND: test-parse-manifest
**
** Usage: %fossil test-parse-manifest FILENAME ?N?
**
** Parse the manifest and discarded.  Use for testing only.
*/
void manifest_test_parse_cmd(void){
  Manifest *p;
  Blob b;
  int i;
  int n = 1;
  if( g.argc!=3 && g.argc!=4 ){
    usage("FILENAME");
  }
  db_must_be_within_tree();
  blob_read_from_file(&b, g.argv[2]);
  if( g.argc>3 ) n = atoi(g.argv[3]);
  for(i=0; i<n; i++){
    Blob b2;
    blob_copy(&b2, &b);
    p = manifest_new(&b2);
    manifest_destroy(p);
  }
}

/*
** Rewind a manifest-file iterator back to the beginning of the manifest.
*/
void manifest_file_rewind(Manifest *p){
  p->iFile = -1;
}

/*
** Advance to the next manifest-file.
**
** Return NULL for end-of-records or if there is an error.  If an error
** occurs and pErr!=0 then store 1 in *pErr.
*/
ManifestFile *manifest_file_next(
  Manifest *p,   
  int *pErr
){
  if( pErr ) *pErr = 0;
  if( p->iFile+1<p->nFile ){
    p->iFile++;
    return &p->aFile[p->iFile];
  }else{
    return 0;
  }
}

/*
** Return a pointer to the current file.
*/
ManifestFile *manifest_file_current(Manifest *p){
  if( p->iFile>=0 && p->iFile<p->nFile ){
    return &p->aFile[p->iFile];
  }else{
    return 0;
  }
}

/*
** Translate a filename into a filename-id (fnid).  Create a new fnid
** if no previously exists.
*/
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379

1380
  return 1;
}

/*
** Given a checkin name, load and parse the manifest for that checkin.
** Throw a fatal error if anything goes wrong.
*/
void manifest_from_name(
  const char *zName,
  Manifest *pM
){
  int rid;
  Blob content;

  rid = name_to_rid(zName);
  if( !is_a_version(rid) ){
    fossil_fatal("no such checkin: %s", zName);
  }
  content_get(rid, &content);
  if( !manifest_parse(pM, &content) ){
    fossil_fatal("cannot parse manifest for checkin: %s", zName);
  }

}







|
|
|
<
<
<





|
|


>

1447
1448
1449
1450
1451
1452
1453
1454
1455
1456



1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
  return 1;
}

/*
** Given a checkin name, load and parse the manifest for that checkin.
** Throw a fatal error if anything goes wrong.
*/
Manifest *manifest_from_name(const char *zName){
  int rid;
  Manifest *p;




  rid = name_to_rid(zName);
  if( !is_a_version(rid) ){
    fossil_fatal("no such checkin: %s", zName);
  }
  p = manifest_get(rid, CFTYPE_MANIFEST);
  if( p==0 ){
    fossil_fatal("cannot parse manifest for checkin: %s", zName);
  }
  return p;
}
Changes to src/tkt.c.
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233

234
235
236
237
238
239
240
/*
** Rebuild an entire entry in the TICKET table
*/
void ticket_rebuild_entry(const char *zTktUuid){
  char *zTag = mprintf("tkt-%s", zTktUuid);
  int tagid = tag_findid(zTag, 1);
  Stmt q;
  Manifest manifest;
  Blob content;
  int createFlag = 1;
  
  db_multi_exec(
     "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
  );
  db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    content_get(rid, &content);
    manifest_parse(&manifest, &content);
    ticket_insert(&manifest, createFlag, rid);
    manifest_ticket_event(rid, &manifest, createFlag, tagid);
    manifest_clear(&manifest);

    createFlag = 0;
  }
  db_finalize(&q);
}

/*
** Create the subscript interpreter and load the "common" code.







|
<








|
|
|
|
|
>







212
213
214
215
216
217
218
219

220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/*
** Rebuild an entire entry in the TICKET table
*/
void ticket_rebuild_entry(const char *zTktUuid){
  char *zTag = mprintf("tkt-%s", zTktUuid);
  int tagid = tag_findid(zTag, 1);
  Stmt q;
  Manifest *pTicket;

  int createFlag = 1;
  
  db_multi_exec(
     "DELETE FROM ticket WHERE tkt_uuid=%Q", zTktUuid
  );
  db_prepare(&q, "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime",tagid);
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    pTicket = manifest_get(rid, CFTYPE_TICKET);
    if( pTicket ){
      ticket_insert(pTicket, createFlag, rid);
      manifest_ticket_event(rid, pTicket, createFlag, tagid);
      manifest_destroy(pTicket);
    }
    createFlag = 0;
  }
  db_finalize(&q);
}

/*
** Create the subscript interpreter and load the "common" code.
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
    "  FROM attachment, blob"
    " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
    "   AND blob.rid=attachid"
    " ORDER BY 1 DESC",
    tagid, tagid
  );
  while( db_step(&q)==SQLITE_ROW ){
    Blob content;
    Manifest m;
    char zShort[12];
    const char *zDate = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    const char *zChngUuid = db_column_text(&q, 2);
    const char *zFile = db_column_text(&q, 4);
    memcpy(zShort, zChngUuid, 10);
    zShort[10] = 0;







<
|







751
752
753
754
755
756
757

758
759
760
761
762
763
764
765
    "  FROM attachment, blob"
    " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
    "   AND blob.rid=attachid"
    " ORDER BY 1 DESC",
    tagid, tagid
  );
  while( db_step(&q)==SQLITE_ROW ){

    Manifest *pTicket;
    char zShort[12];
    const char *zDate = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    const char *zChngUuid = db_column_text(&q, 2);
    const char *zFile = db_column_text(&q, 4);
    memcpy(zShort, zChngUuid, 10);
    zShort[10] = 0;
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
        @ <p>Add attachment "%h(zFile)"
      }
      @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
      @ (rid %d(rid)) by
      hyperlink_to_user(zUser,zDate," on");
      hyperlink_to_date(zDate, ".</p>");
    }else{
      content_get(rid, &content);
      if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){
        @
        @ <p>Ticket change
        @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
        @ (rid %d(rid)) by
        hyperlink_to_user(m.zUser,zDate," on");
        hyperlink_to_date(zDate, ":");
        @ </p>
        ticket_output_change_artifact(&m);
      }
      manifest_clear(&m);
    }
  }
  db_finalize(&q);
  style_footer();
}

/*







|
|




|


|

|







774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
        @ <p>Add attachment "%h(zFile)"
      }
      @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
      @ (rid %d(rid)) by
      hyperlink_to_user(zUser,zDate," on");
      hyperlink_to_date(zDate, ".</p>");
    }else{
      pTicket = manifest_get(rid, CFTYPE_TICKET);
      if( pTicket ){
        @
        @ <p>Ticket change
        @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zShort)</a>]
        @ (rid %d(rid)) by
        hyperlink_to_user(pTicket->zUser,zDate," on");
        hyperlink_to_date(zDate, ":");
        @ </p>
        ticket_output_change_artifact(pTicket);
      }
      manifest_destroy(pTicket);
    }
  }
  db_finalize(&q);
  style_footer();
}

/*
Changes to src/update.c.
300
301
302
303
304
305
306
307
308

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323

324
325
326
327
328
329
330
331
332
333
334
335
336
337
*/
int historical_version_of_file(
  const char *revision,    /* The checkin containing the file */
  const char *file,        /* Full treename of the file */
  Blob *content,           /* Put the content here */
  int errCode              /* Error code if file not found.  Panic if 0. */
){
  Blob mfile;
  Manifest m;

  int i, rid=0;
  
  if( revision ){
    rid = name_to_rid(revision);
  }else{
    rid = db_lget_int("checkout", 0);
  }
  if( !is_a_version(rid) ){
    if( errCode>0 ) return errCode;
    fossil_fatal("no such checkin: %s", revision);
  }
  content_get(rid, &mfile);
  
  if( manifest_parse(&m, &mfile) ){
    for(i=0; i<m.nFile; i++){

      if( strcmp(m.aFile[i].zName, file)==0 ){
        rid = uuid_to_rid(m.aFile[i].zUuid, 0);
        manifest_clear(&m);
        return content_get(rid, content);
      }
    }
    manifest_clear(&m);
    if( errCode<=0 ){
      fossil_fatal("file %s does not exist in checkin: %s", file, revision);
    }
  }else if( errCode<=0 ){
    fossil_panic("could not parse manifest for checkin: %s", revision);
  }
  return errCode;







<
|
>
|










|

|
|
>
|
|
|



|







300
301
302
303
304
305
306

307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
*/
int historical_version_of_file(
  const char *revision,    /* The checkin containing the file */
  const char *file,        /* Full treename of the file */
  Blob *content,           /* Put the content here */
  int errCode              /* Error code if file not found.  Panic if 0. */
){

  Manifest *pManifest;
  ManifestFile *pFile;
  int rid=0;
  
  if( revision ){
    rid = name_to_rid(revision);
  }else{
    rid = db_lget_int("checkout", 0);
  }
  if( !is_a_version(rid) ){
    if( errCode>0 ) return errCode;
    fossil_fatal("no such checkin: %s", revision);
  }
  pManifest = manifest_get(rid, CFTYPE_MANIFEST);
  
  if( pManifest ){
    manifest_file_rewind(pManifest);
    while( (pFile = manifest_file_next(pManifest,0))!=0 ){
      if( strcmp(pFile->zName, file)==0 ){
        rid = uuid_to_rid(pFile->zUuid, 0);
        manifest_destroy(pManifest);
        return content_get(rid, content);
      }
    }
    manifest_destroy(pManifest);
    if( errCode<=0 ){
      fossil_fatal("file %s does not exist in checkin: %s", file, revision);
    }
  }else if( errCode<=0 ){
    fossil_panic("could not parse manifest for checkin: %s", revision);
  }
  return errCode;
Changes to src/vfile.c.
436
437
438
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
** file in manifest vid.  The file names are part of the checksum.
** Return the resulting checksum in blob pOut.
**
** If pManOut is not NULL then fill it with the checksum found in the
** "R" card near the end of the manifest.  
*/
void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){
  int i, fid;
  Blob file, mfile;
  Manifest m;

  char zBuf[100];

  blob_zero(pOut);
  if( pManOut ){
    blob_zero(pManOut);
  }
  db_must_be_within_tree();
  content_get(vid, &mfile);
  if( manifest_parse(&m, &mfile)==0 ){
    fossil_panic("manifest file (%d) is malformed", vid);
  }
  for(i=0; i<m.nFile; i++){

    fid = uuid_to_rid(m.aFile[i].zUuid, 0);
    md5sum_step_text(m.aFile[i].zName, -1);
    content_get(fid, &file);
    sprintf(zBuf, " %d\n", blob_size(&file));
    md5sum_step_text(zBuf, -1);
    md5sum_step_blob(&file);
    blob_reset(&file);
  }
  if( pManOut ){
    if( m.zRepoCksum ){
      blob_append(pManOut, m.zRepoCksum, -1);
    }else{
      blob_zero(pManOut);
    }
  }
  manifest_clear(&m);
  md5sum_finish(pOut);
}

/*
** COMMAND: test-agg-cksum
*/
void test_agg_cksum_cmd(void){







|
|
|
>







|
|


|
>
|
|







|
|




|







436
437
438
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
** file in manifest vid.  The file names are part of the checksum.
** Return the resulting checksum in blob pOut.
**
** If pManOut is not NULL then fill it with the checksum found in the
** "R" card near the end of the manifest.  
*/
void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){
  int fid;
  Blob file;
  Manifest *pManifest;
  ManifestFile *pFile;
  char zBuf[100];

  blob_zero(pOut);
  if( pManOut ){
    blob_zero(pManOut);
  }
  db_must_be_within_tree();
  pManifest = manifest_get(vid, CFTYPE_MANIFEST);
  if( pManifest==0 ){
    fossil_panic("manifest file (%d) is malformed", vid);
  }
  manifest_file_rewind(pManifest);
  while( (pFile = manifest_file_next(pManifest,0))!=0 ){
    fid = uuid_to_rid(pFile->zUuid, 0);
    md5sum_step_text(pFile->zName, -1);
    content_get(fid, &file);
    sprintf(zBuf, " %d\n", blob_size(&file));
    md5sum_step_text(zBuf, -1);
    md5sum_step_blob(&file);
    blob_reset(&file);
  }
  if( pManOut ){
    if( pManifest->zRepoCksum ){
      blob_append(pManOut, pManifest->zRepoCksum, -1);
    }else{
      blob_zero(pManOut);
    }
  }
  manifest_destroy(pManifest);
  md5sum_finish(pOut);
}

/*
** COMMAND: test-agg-cksum
*/
void test_agg_cksum_cmd(void){
Changes to src/wiki.c.
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
** URL: /wiki?name=PAGENAME
*/
void wiki_page(void){
  char *zTag;
  int rid = 0;
  int isSandbox;
  Blob wiki;
  Manifest m;
  const char *zPageName;
  char *zBody = mprintf("%s","<i>Empty Page</i>");
  Stmt q;
  int cnt = 0;

  login_check_credentials();
  if( !g.okRdWiki ){ login_needed(); return; }







|







125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
** URL: /wiki?name=PAGENAME
*/
void wiki_page(void){
  char *zTag;
  int rid = 0;
  int isSandbox;
  Blob wiki;
  Manifest *pWiki = 0;
  const char *zPageName;
  char *zBody = mprintf("%s","<i>Empty Page</i>");
  Stmt q;
  int cnt = 0;

  login_check_credentials();
  if( !g.okRdWiki ){ login_needed(); return; }
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    memset(&m, 0, sizeof(m));
    blob_zero(&m.content);
    if( rid ){
      Blob content;
      content_get(rid, &content);
      manifest_parse(&m, &content);
      if( m.type==CFTYPE_WIKI && m.zWiki ){
        while( fossil_isspace(m.zWiki[0]) ) m.zWiki++;
        if( m.zWiki[0] ) zBody = m.zWiki;
      }
    }
  }
  if( !g.isHome ){
    if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){
      style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
           g.zTop, zPageName);
    }







<
<
<
<
|
<
|
|
|
<







177
178
179
180
181
182
183




184

185
186
187

188
189
190
191
192
193
194
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);




    pWiki = manifest_get(rid, CFTYPE_WIKI);

    if( pWiki ){
      while( fossil_isspace(pWiki->zWiki[0]) ) pWiki->zWiki++;
      if( pWiki->zWiki[0] ) zBody = pWiki->zWiki;

    }
  }
  if( !g.isHome ){
    if( (rid && g.okWrWiki) || (!rid && g.okNewWiki) ){
      style_submenu_element("Edit", "Edit Wiki Page", "%s/wikiedit?name=%T",
           g.zTop, zPageName);
    }
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
    @ </li>
  }
  if( cnt ){
    @ </ul>
  }
  db_finalize(&q);
 
  if( !isSandbox ){
    manifest_clear(&m);
  }
  style_footer();
}

/*
** WEBPAGE: wikiedit
** URL: /wikiedit?name=PAGENAME
*/
void wikiedit_page(void){
  char *zTag;
  int rid = 0;
  int isSandbox;
  Blob wiki;
  Manifest m;
  const char *zPageName;
  char *zHtmlPageName;
  int n;
  const char *z;
  char *zBody = (char*)P("w");

  if( zBody ){







<
|
<












|







241
242
243
244
245
246
247

248

249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
    @ </li>
  }
  if( cnt ){
    @ </ul>
  }
  db_finalize(&q);
 

  manifest_destroy(pWiki);

  style_footer();
}

/*
** WEBPAGE: wikiedit
** URL: /wikiedit?name=PAGENAME
*/
void wikiedit_page(void){
  char *zTag;
  int rid = 0;
  int isSandbox;
  Blob wiki;
  Manifest *pWiki = 0;
  const char *zPageName;
  char *zHtmlPageName;
  int n;
  const char *z;
  char *zBody = (char*)P("w");

  if( zBody ){
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){
      login_needed();
      return;
    }
    memset(&m, 0, sizeof(m));
    blob_zero(&m.content);
    if( rid && zBody==0 ){
      Blob content;
      content_get(rid, &content);
      manifest_parse(&m, &content);
      if( m.type==CFTYPE_WIKI ){
        zBody = m.zWiki;
      }
    }
  }
  if( P("submit")!=0 && zBody!=0 ){
    char *zDate;
    Blob cksum;
    int nrid;
    blob_zero(&wiki);







<
<
<
<
|
<
<
|
<







288
289
290
291
292
293
294




295


296

297
298
299
300
301
302
303
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( (rid && !g.okWrWiki) || (!rid && !g.okNewWiki) ){
      login_needed();
      return;
    }




    if( zBody==0 && (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){


      zBody = pWiki->zWiki;

    }
  }
  if( P("submit")!=0 && zBody!=0 ){
    char *zDate;
    Blob cksum;
    int nrid;
    blob_zero(&wiki);
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
  @ <textarea name="w" class="wikiedit" cols="80" 
  @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
  @ <br />
  @ <input type="submit" name="preview" value="Preview Your Changes" />
  @ <input type="submit" name="submit" value="Apply These Changes" />
  @ <input type="submit" name="cancel" value="Cancel" />
  @ </div></form>
  if( !isSandbox ){
    manifest_clear(&m);
  }
  style_footer();
}

/*
** WEBPAGE: wikinew
** URL /wikinew
**







<
|
<







360
361
362
363
364
365
366

367

368
369
370
371
372
373
374
  @ <textarea name="w" class="wikiedit" cols="80" 
  @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
  @ <br />
  @ <input type="submit" name="preview" value="Preview Your Changes" />
  @ <input type="submit" name="submit" value="Apply These Changes" />
  @ <input type="submit" name="cancel" value="Cancel" />
  @ </div></form>

  manifest_destroy(pWiki);

  style_footer();
}

/*
** WEBPAGE: wikinew
** URL /wikinew
**
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
    return;
  }
  if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){
    char *zDate;
    Blob cksum;
    int nrid;
    Blob body;
    Blob content;
    Blob wiki;
    Manifest m;

    blob_zero(&body);
    if( isSandbox ){
      blob_appendf(&body, db_get("sandbox",""));
      appendRemark(&body);
      db_set("sandbox", blob_str(&body), 0);
    }else{
      login_verify_csrf_secret();
      content_get(rid, &content);
      manifest_parse(&m, &content);
      if( m.type==CFTYPE_WIKI ){
        blob_append(&body, m.zWiki, -1);

      }
      manifest_clear(&m);
      blob_zero(&wiki);
      db_begin_transaction();
      zDate = db_text(0, "SELECT datetime('now')");
      zDate[10] = 'T';
      blob_appendf(&wiki, "D %s\n", zDate);
      blob_appendf(&wiki, "L %F\n", zPageName);
      if( rid ){







<

|








|
<
|
|
>

<







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
    return;
  }
  if( P("submit")!=0 && P("r")!=0 && P("u")!=0 ){
    char *zDate;
    Blob cksum;
    int nrid;
    Blob body;

    Blob wiki;
    Manifest *pWiki = 0;

    blob_zero(&body);
    if( isSandbox ){
      blob_appendf(&body, db_get("sandbox",""));
      appendRemark(&body);
      db_set("sandbox", blob_str(&body), 0);
    }else{
      login_verify_csrf_secret();
      pWiki = manifest_get(rid, CFTYPE_WIKI);

      if( pWiki ){
        blob_append(&body, pWiki->zWiki, -1);
        manifest_destroy(pWiki);
      }

      blob_zero(&wiki);
      db_begin_transaction();
      zDate = db_text(0, "SELECT datetime('now')");
      zDate[10] = 'T';
      blob_appendf(&wiki, "D %s\n", zDate);
      blob_appendf(&wiki, "L %F\n", zPageName);
      if( rid ){
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
**
** Show the difference between two wiki pages.
*/
void wdiff_page(void){
  char *zTitle;
  int rid1, rid2;
  const char *zPageName;
  Blob content1, content2;
  Manifest m1, m2;
  Blob w1, w2, d;

  login_check_credentials();
  rid1 = atoi(PD("a","0"));
  if( !g.okHistory ){ login_needed(); return; }
  if( rid1==0 ) fossil_redirect_home();
  rid2 = atoi(PD("b","0"));







<
|







591
592
593
594
595
596
597

598
599
600
601
602
603
604
605
**
** Show the difference between two wiki pages.
*/
void wdiff_page(void){
  char *zTitle;
  int rid1, rid2;
  const char *zPageName;

  Manifest *pW1, *pW2 = 0;
  Blob w1, w2, d;

  login_check_credentials();
  rid1 = atoi(PD("a","0"));
  if( !g.okHistory ){ login_needed(); return; }
  if( rid1==0 ) fossil_redirect_home();
  rid2 = atoi(PD("b","0"));
633
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
      "SELECT objid FROM event JOIN tagxref ON objid=rid AND tagxref.tagid="
                        "(SELECT tagid FROM tag WHERE tagname='wiki-%q')"
      " WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)"
      " ORDER BY event.mtime DESC LIMIT 1",
      zPageName, rid1
    );
  }
  content_get(rid1, &content1);
  manifest_parse(&m1, &content1);
  if( m1.type!=CFTYPE_WIKI ) fossil_redirect_home();
  blob_init(&w1, m1.zWiki, -1);
  blob_zero(&w2);
  if( rid2 ){
    content_get(rid2, &content2);
    manifest_parse(&m2, &content2);
    if( m2.type==CFTYPE_WIKI ){
      blob_init(&w2, m2.zWiki, -1);
    }
  }
  blob_zero(&d);
  text_diff(&w2, &w1, &d, 5, 1);
  @ <pre>
  @ %h(blob_str(&d))
  @ </pre>


  style_footer();
}

/*
** WEBPAGE: wcontent
**
**     all=1         Show deleted pages







|
<
|
|

|
<
<
<
|
<






>
>







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
640
      "SELECT objid FROM event JOIN tagxref ON objid=rid AND tagxref.tagid="
                        "(SELECT tagid FROM tag WHERE tagname='wiki-%q')"
      " WHERE event.mtime<(SELECT mtime FROM event WHERE objid=%d)"
      " ORDER BY event.mtime DESC LIMIT 1",
      zPageName, rid1
    );
  }
  pW1 = manifest_get(rid1, CFTYPE_WIKI);

  if( pW1==0 ) fossil_redirect_home();
  blob_init(&w1, pW1->zWiki, -1);
  blob_zero(&w2);
  if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){



    blob_init(&w2, pW2->zWiki, -1);

  }
  blob_zero(&d);
  text_diff(&w2, &w1, &d, 5, 1);
  @ <pre>
  @ %h(blob_str(&d))
  @ </pre>
  manifest_destroy(pW1);
  manifest_destroy(pW2);
  style_footer();
}

/*
** WEBPAGE: wcontent
**
**     all=1         Show deleted pages
912
913
914
915
916
917
918
919
920
921
922

923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
  if( n==0 ){
    goto wiki_cmd_usage;
  }

  if( strncmp(g.argv[2],"export",n)==0 ){
    char const *zPageName;        /* Name of the wiki page to export */
    char const *zFile;            /* Name of the output file (0=stdout) */
    int rid;                /* Artifact ID of the wiki page */
    int i;                  /* Loop counter */
    char *zBody = 0;        /* Wiki page content */
    Manifest m;             /* Parsed wiki page content */

    if( (g.argc!=4) && (g.argc!=5) ){
      usage("export PAGENAME ?FILE?");
    }
    zPageName = g.argv[3];
    rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
      " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
      " ORDER BY x.mtime DESC LIMIT 1",
      zPageName 
    );
    if( rid ){
      Blob content;
      content_get(rid, &content);
      manifest_parse(&m, &content);
      if( m.type==CFTYPE_WIKI ){
        zBody = m.zWiki;
      }
    }
    if( zBody==0 ){
      fossil_fatal("wiki page [%s] not found",zPageName);
    }
    for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
    zFile  = (g.argc==4) ? 0 : g.argv[4];
    if( zFile ){







|
|
|
|
>









<
<
|
<
<
|
<







889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909


910


911

912
913
914
915
916
917
918
  if( n==0 ){
    goto wiki_cmd_usage;
  }

  if( strncmp(g.argv[2],"export",n)==0 ){
    char const *zPageName;        /* Name of the wiki page to export */
    char const *zFile;            /* Name of the output file (0=stdout) */
    int rid;                      /* Artifact ID of the wiki page */
    int i;                        /* Loop counter */
    char *zBody = 0;              /* Wiki page content */
    Manifest *pWiki = 0;          /* Parsed wiki page content */

    if( (g.argc!=4) && (g.argc!=5) ){
      usage("export PAGENAME ?FILE?");
    }
    zPageName = g.argv[3];
    rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
      " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
      " ORDER BY x.mtime DESC LIMIT 1",
      zPageName 
    );


    if( (pWiki = manifest_get(rid, CFTYPE_WIKI))!=0 ){


      zBody = pWiki->zWiki;

    }
    if( zBody==0 ){
      fossil_fatal("wiki page [%s] not found",zPageName);
    }
    for(i=strlen(zBody); i>0 && fossil_isspace(zBody[i-1]); i--){}
    zFile  = (g.argc==4) ? 0 : g.argv[4];
    if( zFile ){
953
954
955
956
957
958
959
960
961

962
963
964
965
966
967
968
      }
      if( ! zF ){
        fossil_fatal("wiki export could not open output file for writing.");
      }
      fprintf(zF,"%.*s\n", i, zBody);
      if( doClose ) fclose(zF);
    }else{
	printf("%.*s\n", i, zBody);
    }

    return;
  }else
  if( strncmp(g.argv[2],"commit",n)==0
      || strncmp(g.argv[2],"create",n)==0 ){
    char *zPageName;
    Blob content;
    if( g.argc!=4 && g.argc!=5 ){







|

>







926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
      }
      if( ! zF ){
        fossil_fatal("wiki export could not open output file for writing.");
      }
      fprintf(zF,"%.*s\n", i, zBody);
      if( doClose ) fclose(zF);
    }else{
      printf("%.*s\n", i, zBody);
    }
    manifest_destroy(pWiki);
    return;
  }else
  if( strncmp(g.argv[2],"commit",n)==0
      || strncmp(g.argv[2],"create",n)==0 ){
    char *zPageName;
    Blob content;
    if( g.argc!=4 && g.argc!=5 ){
Changes to src/zip.c.
311
312
313
314
315
316
317
318
319
320

321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355

356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371

372
373
374
375
376
377
378
** added to as part of the zip file. It may be 0 or an empty string,
** in which case it is ignored. The intention is to create a zip which
** politely expands into a subdir instead of filling your current dir
** with source files. For example, pass a UUID or "ProjectName".
**
*/
void zip_of_baseline(int rid, Blob *pZip, const char *zDir){
  int i;
  Blob mfile, file, hash;
  Manifest m;

  Blob filename;
  int nPrefix;
  
  content_get(rid, &mfile);
  if( blob_size(&mfile)==0 ){
    blob_zero(pZip);
    return;
  }
  blob_zero(&file);
  blob_zero(&hash);
  blob_copy(&file, &mfile);
  blob_zero(&filename);
  zip_open();

  if( zDir && zDir[0] ){
    blob_appendf(&filename, "%s/", zDir);
  }
  nPrefix = blob_size(&filename);


  if( manifest_parse(&m, &mfile) ){
    char *zName;
    zip_set_timedate(m.rDate);
    blob_append(&filename, "manifest", -1);
    zName = blob_str(&filename);
    zip_add_folders(zName);
    zip_add_file(zName, &file);
    sha1sum_blob(&file, &hash);
    blob_reset(&file);
    blob_append(&hash, "\n", 1);
    blob_resize(&filename, nPrefix);
    blob_append(&filename, "manifest.uuid", -1);
    zName = blob_str(&filename);
    zip_add_file(zName, &hash);
    blob_reset(&hash);
    for(i=0; i<m.nFile; i++){

      int fid = uuid_to_rid(m.aFile[i].zUuid, 0);
      if( fid ){
        content_get(fid, &file);
        blob_resize(&filename, nPrefix);
        blob_append(&filename, m.aFile[i].zName, -1);
        zName = blob_str(&filename);
        zip_add_folders(zName);
        zip_add_file(zName, &file);
        blob_reset(&file);
      }
    }
    manifest_clear(&m);
  }else{
    blob_reset(&mfile);
    blob_reset(&file);
  }

  blob_reset(&filename);
  zip_close(pZip);
}

/*
** COMMAND: zip
**







<

|
>



















>
|

|












|
>
|



|






<

<


>







311
312
313
314
315
316
317

318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368

369

370
371
372
373
374
375
376
377
378
379
** added to as part of the zip file. It may be 0 or an empty string,
** in which case it is ignored. The intention is to create a zip which
** politely expands into a subdir instead of filling your current dir
** with source files. For example, pass a UUID or "ProjectName".
**
*/
void zip_of_baseline(int rid, Blob *pZip, const char *zDir){

  Blob mfile, file, hash;
  Manifest *pManifest;
  ManifestFile *pFile;
  Blob filename;
  int nPrefix;
  
  content_get(rid, &mfile);
  if( blob_size(&mfile)==0 ){
    blob_zero(pZip);
    return;
  }
  blob_zero(&file);
  blob_zero(&hash);
  blob_copy(&file, &mfile);
  blob_zero(&filename);
  zip_open();

  if( zDir && zDir[0] ){
    blob_appendf(&filename, "%s/", zDir);
  }
  nPrefix = blob_size(&filename);

  pManifest = manifest_new(&mfile);
  if( pManifest ){
    char *zName;
    zip_set_timedate(pManifest->rDate);
    blob_append(&filename, "manifest", -1);
    zName = blob_str(&filename);
    zip_add_folders(zName);
    zip_add_file(zName, &file);
    sha1sum_blob(&file, &hash);
    blob_reset(&file);
    blob_append(&hash, "\n", 1);
    blob_resize(&filename, nPrefix);
    blob_append(&filename, "manifest.uuid", -1);
    zName = blob_str(&filename);
    zip_add_file(zName, &hash);
    blob_reset(&hash);
    manifest_file_rewind(pManifest);
    while( (pFile = manifest_file_next(pManifest,0))!=0 ){
      int fid = uuid_to_rid(pFile->zUuid, 0);
      if( fid ){
        content_get(fid, &file);
        blob_resize(&filename, nPrefix);
        blob_append(&filename, pFile->zName, -1);
        zName = blob_str(&filename);
        zip_add_folders(zName);
        zip_add_file(zName, &file);
        blob_reset(&file);
      }
    }

  }else{

    blob_reset(&file);
  }
  manifest_destroy(pManifest);
  blob_reset(&filename);
  zip_close(pZip);
}

/*
** COMMAND: zip
**