Fossil

Check-in [7c2577bd]
Login

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

Overview
Comment:Merge in all of the latest clear-title changes from the trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | clear-title
Files: files | file ages | folders
SHA1: 7c2577bd637e91b0d3649f75555644cd8b46189d
User & Date: drh 2010-03-18 14:40:46
Context
2010-04-02
20:09
Merge all recent trunk changes into the clear-title branch (since we hold clear title on all recent changes.) check-in: 64541535 user: drh tags: clear-title
2010-03-18
14:40
Merge in all of the latest clear-title changes from the trunk. check-in: 7c2577bd user: drh tags: clear-title
14:28
Update the script that builds the download page. check-in: d5f6a272 user: drh tags: trunk
2010-03-05
23:14
Pull in all clear-title changes from trunk. check-in: dfb5fd64 user: drh tags: clear-title
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to COPYRIGHT-GPL2.txt.















1
2
3
4
5
6
7














		    GNU GENERAL PUBLIC LICENSE
		       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Fossil is licensed under the terms of the GPLv2 shown below.  In 
addition, permission is granted to link Fossil against the OpenSSL
project's "OpenSSL" library (or with modified versions of that 
library that use the same license), and distribute the linked
executables.  If you modify Fossil, you may extend the exception
described in this paragraph to your modifications, or not, at your
discretion.  

The "clear-title" branch of Fossil is available for more liberal
licenses such as Berkeley-style licenses or the Apache license.

The original text of GPLv2 follows.
--------------------------------------------------------------------------

		    GNU GENERAL PUBLIC LICENSE
		       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

Added src/attach.c.











































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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
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
/*
** Copyright (c) 2010 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public
** License version 2 as published by the Free Software Foundation.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** General Public License for more details.
** 
** You should have received a copy of the GNU General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA  02111-1307, USA.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code for dealing with attachments.
*/
#include "config.h"
#include "attach.h"
#include <assert.h>

/*
** WEBPAGE: attachlist
**
**    tkt=TICKETUUID
**    page=WIKIPAGE
**    all
**
** List attachments.
*/
void attachlist_page(void){
  const char *zPage = P("page");
  const char *zTkt = P("tkt");
  Blob sql;
  Stmt q;

  if( zPage && zTkt ) zTkt = 0;
  login_check_credentials();
  blob_zero(&sql);
  blob_append(&sql,
     "SELECT datetime(mtime,'localtime'), src, target, filename, comment, user"
     "  FROM attachment",
     -1
  );
  if( zPage ){
    if( g.okRdWiki==0 ) login_needed();
    style_header("Attachments To %h", zPage);
    blob_appendf(&sql, " WHERE target=%Q", zPage);
  }else if( zTkt ){
    if( g.okRdTkt==0 ) login_needed();
    style_header("Attachments To Ticket %.10s", zTkt);
    blob_appendf(&sql, " WHERE target GLOB '%q*'", zTkt);
  }else{
    if( g.okRdTkt==0 && g.okRdWiki==0 ) login_needed();
    style_header("All Attachments");
  }
  blob_appendf(&sql, " ORDER BY mtime DESC");
  db_prepare(&q, "%s", blob_str(&sql));
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDate = db_column_text(&q, 0);
    const char *zSrc = db_column_text(&q, 1);
    const char *zTarget = db_column_text(&q, 2);
    const char *zFilename = db_column_text(&q, 3);
    const char *zComment = db_column_text(&q, 4);
    const char *zUser = db_column_text(&q, 5);
    int i;
    char *zUrlTail;
    for(i=0; zFilename[i]; i++){
      if( zFilename[i]=='/' && zFilename[i+1]!=0 ){ 
        zFilename = &zFilename[i+1];
        i = -1;
      }
    }
    if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
      zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
    }else{
      zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
    }
    @
    @ <p><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a>
    @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br>
    if( zComment ) while( isspace(zComment[0]) ) zComment++;
    if( zComment && zComment[0] ){
      @ %w(zComment)<br>
    }
    if( zPage==0 && zTkt==0 ){
      if( zSrc==0 || zSrc[0]==0 ){
        zSrc = "Deleted from";
      }else {
        zSrc = "Added to";
      }
      if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
        char zShort[20];
        memcpy(zShort, zTarget, 10);
        zShort[10] = 0;
        @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)">
        @ %s(zShort)</a>
      }else{
        @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)">
        @ %h(zTarget)</a>
      }
    }else{
      if( zSrc==0 || zSrc[0]==0 ){
        @ Deleted
      }else {
        @ Added
      }
    }
    @ by %h(zUser) on
    hyperlink_to_date(zDate, ".");
    free(zUrlTail);
  }
  db_finalize(&q);
  style_footer();
  return;
}

/*
** WEBPAGE: attachdownload
** WEBPAGE: attachimage
** WEBPAGE: attachview
**
**    tkt=TICKETUUID
**    page=WIKIPAGE
**    file=FILENAME
**    attachid=ID
**
** List attachments.
*/
void attachview_page(void){
  const char *zPage = P("page");
  const char *zTkt = P("tkt");
  const char *zFile = P("file");
  const char *zTarget = 0;
  int attachid = atoi(PD("attachid","0"));
  char *zUUID;

  if( zPage && zTkt ) zTkt = 0;
  if( zFile==0 ) fossil_redirect_home();
  login_check_credentials();
  if( zPage ){
    if( g.okRdWiki==0 ) login_needed();
    zTarget = zPage;
  }else if( zTkt ){
    if( g.okRdTkt==0 ) login_needed();
    zTarget = zTkt;
  }else{
    fossil_redirect_home();
  }
  if( attachid>0 ){
    zUUID = db_text(0,
       "SELECT coalesce(src,'x') FROM attachment"
       " WHERE target=%Q AND attachid=%d",
       zTarget, attachid
    );
  }else{
    zUUID = db_text(0,
       "SELECT coalesce(src,'x') FROM attachment"
       " WHERE target=%Q AND filename=%Q"
       " ORDER BY mtime DESC LIMIT 1",
       zTarget, zFile
    );
  }
  if( zUUID==0 || zUUID[0]==0 ){
    style_header("No Such Attachment");
    @ No such attachment....
    style_footer();
    return;
  }else if( zUUID[0]=='x' ){
    style_header("Missing");
    @ Attachment has been deleted
    style_footer();
    return;
  }
  g.okRead = 1;
  cgi_replace_parameter("name",zUUID);
  if( strcmp(g.zPath,"attachview")==0 ){
    artifact_page();
  }else{
    cgi_replace_parameter("m", mimetype_from_name(zFile));
    rawartifact_page();
  }
}


/*
** WEBPAGE: attachadd
**
**    tkt=TICKETUUID
**    page=WIKIPAGE
**    from=URL
**
** Add a new attachment.
*/
void attachadd_page(void){
  const char *zPage = P("page");
  const char *zTkt = P("tkt");
  const char *zFrom = P("from");
  const char *aContent = P("f");
  const char *zName = PD("f:filename","unknown");
  const char *zTarget;
  const char *zTargetType;
  int szContent = atoi(PD("f:bytes","0"));

  if( P("cancel") ) cgi_redirect(zFrom);
  if( zPage && zTkt ) fossil_redirect_home();
  if( zPage==0 && zTkt==0 ) fossil_redirect_home();
  login_check_credentials();
  if( zPage ){
    if( g.okApndWiki==0 || g.okAttach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
      fossil_redirect_home();
    }
    zTarget = zPage;
    zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>",
                           g.zTop, zPage, zPage);
  }else{
    if( g.okApndTkt==0 || g.okAttach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
      fossil_redirect_home();
    }
    zTarget = zTkt;
    zTargetType = mprintf("Ticket <a href=\"%s/tktview?name=%.10s\">%.10s</a>",
                          g.zTop, zTkt, zTkt);
  }
  if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
  if( P("cancel") ){
    cgi_redirect(zFrom);
  }
  if( P("ok") && szContent>0 ){
    Blob content;
    Blob manifest;
    Blob cksum;
    char *zUUID;
    const char *zComment;
    char *zDate;
    int rid;
    int i, n;

    db_begin_transaction();
    blob_init(&content, aContent, szContent);
    rid = content_put(&content, 0, 0);
    zUUID = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    blob_zero(&manifest);
    for(i=n=0; zName[i]; i++){
      if( zName[i]=='/' || zName[i]=='\\' ) n = i;
    }
    zName += n;
    if( zName[0]==0 ) zName = "unknown";
    blob_appendf(&manifest, "A %F %F %s\n", zName, zTarget, zUUID);
    zComment = PD("comment", "");
    while( isspace(zComment[0]) ) zComment++;
    n = strlen(zComment);
    while( n>0 && isspace(zComment[n-1]) ){ n--; }
    if( n>0 ){
      blob_appendf(&manifest, "C %F\n", zComment);
    }
    zDate = db_text(0, "SELECT datetime('now')");
    zDate[10] = 'T';
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    rid = content_put(&manifest, 0, 0);
    manifest_crosslink(rid, &manifest);
    db_end_transaction(0);
    cgi_redirect(zFrom);
  }
  style_header("Add Attachment");
  @ <h2>Add Attachment To %s(zTargetType)</h2>
  @ <form action="%s(g.zBaseURL)/attachadd" method="POST"
  @  enctype="multipart/form-data">
  @ File to Attach:
  @ <input type="file" name="f" size="60"><br>
  @ Description:<br>
  @ <textarea name="comment" cols=80 rows=5 wrap="virtual"></textarea><br>
  if( zTkt ){
    @ <input type="hidden" name="tkt" value="%h(zTkt)">
  }else{
    @ <input type="hidden" name="page" value="%h(zPage)">
  }
  @ <input type="hidden" name="from" value="%h(zFrom)">
  @ <input type="submit" name="ok" value="Add Attachment">
  @ <input type="submit" name="cancel" value="Cancel">
  @ </form>
  style_footer();
}


/*
** WEBPAGE: attachdelete
**
**    tkt=TICKETUUID
**    page=WIKIPAGE
**    file=FILENAME
**
** "Delete" an attachment.  Because objects in Fossil are immutable
** the attachment isn't really deleted.  Instead, we change the content
** of the attachment to NULL, which the system understands as being
** deleted.  Historical values of the attachment are preserved.
*/
void attachdel_page(void){
  const char *zPage = P("page");
  const char *zTkt = P("tkt");
  const char *zFile = P("file");
  const char *zFrom = P("from");
  const char *zTarget;

  if( zPage && zTkt ) fossil_redirect_home();
  if( zPage==0 && zTkt==0 ) fossil_redirect_home();
  if( zFile==0 ) fossil_redirect_home();
  login_check_credentials();
  if( zPage ){
    if( g.okWrWiki==0 || g.okAttach==0 ) login_needed();
    zTarget = zPage;
  }else{
    if( g.okWrTkt==0 || g.okAttach==0 ) login_needed();
    zTarget = zTkt;
  }
  if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
  if( P("cancel") ){
    cgi_redirect(zFrom);
  }
  if( P("confirm") ){
    int i, n, rid;
    char *zDate;
    Blob manifest;
    Blob cksum;

    db_begin_transaction();
    blob_zero(&manifest);
    for(i=n=0; zFile[i]; i++){
      if( zFile[i]=='/' || zFile[i]=='\\' ) n = i;
    }
    zFile += n;
    if( zFile[0]==0 ) zFile = "unknown";
    blob_appendf(&manifest, "A %F %F\n", zFile, zTarget);
    zDate = db_text(0, "SELECT datetime('now')");
    zDate[10] = 'T';
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    rid = content_put(&manifest, 0, 0);
    manifest_crosslink(rid, &manifest);
    db_end_transaction(0);
    cgi_redirect(zFrom);
  }    
  style_header("Delete Attachment");
  @ <form action="%s(g.zBaseURL)/attachdelete" method="POST">
  @ <p>Confirm that you want to delete the attachment named
  @ "%h(zFile)" on %s(zTkt?"ticket":"wiki page") %h(zTarget):<br>
  if( zTkt ){
    @ <input type="hidden" name="tkt" value="%h(zTkt)">
  }else{
    @ <input type="hidden" name="page" value="%h(zPage)">
  }
  @ <input type="hidden" name="file" value="%h(zFile)">
  @ <input type="hidden" name="from" value="%h(zFrom)">
  @ <input type="submit" name="confirm" value="Delete">
  @ <input type="submit" name="cancel" value="Cancel">
  @ </form>
  style_footer();

}

Changes to src/browse.c.

111
112
113
114
115
116
117

118
119
120
121
122
123
124
...
131
132
133
134
135
136
137

138
139
140
141
142

143
144
145
146
147
148
149
150
151
152
153
154
155
156

157
158
159



160
161
162


163










164
165
166
167
168
169
170
  int nCol, nRow;
  int cnt, i;
  char *zPrefix;
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  Blob content;

  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,
................................................................................
  if( zCI && (rid = name_to_rid(zCI))!=0 && content_get(rid, &content) ){
    if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){
      zCI = 0;
    }
  }

  /* Compute the title of the page */  

  if( zD ){
    Blob title;

    blob_zero(&title);
    blob_appendf(&title, "Files in directory ");

    hyperlinked_path(zD, &title);
    @ <h2>%s(blob_str(&title))
    blob_reset(&title);
    zPrefix = mprintf("%h/", zD);
  }else{
    @ <h2>Files in the top-level directory
    zPrefix = "";
  }
  if( zCI ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    char zShort[20];
    memcpy(zShort, zUuid, 10);
    zShort[10] = 0;
    @ of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>]</h2>

    zSubdirLink = mprintf("%s/dir?ci=%s&name=%T", g.zBaseURL, zUuid, zPrefix);
    if( zD ){
      style_submenu_element("Top", "Top", "%s/dir?ci=%s", g.zBaseURL, zUuid);



    }
  }else{
    @ </h2>


    zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix);










  }

  /* Compute the temporary table "localfiles" containing the names
  ** of all files and subdirectories in the zD[] directory.  
  **
  ** Subdirectory names begin with "/".  This causes them to sort
  ** first and it also gives us an easy way to distinguish files







>







 







>

<
<
<
<
>
|
<
<


|







|
>



>
>
>


<
>
>

>
>
>
>
>
>
>
>
>
>







111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
...
132
133
134
135
136
137
138
139
140




141
142


143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162

163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
  int nCol, nRow;
  int cnt, i;
  char *zPrefix;
  Stmt q;
  const char *zCI = P("ci");
  int rid = 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,
................................................................................
  if( zCI && (rid = name_to_rid(zCI))!=0 && content_get(rid, &content) ){
    if( !manifest_parse(&m, &content) || m.type!=CFTYPE_MANIFEST ){
      zCI = 0;
    }
  }

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




    blob_append(&dirname, "in directory ", -1);
    hyperlinked_path(zD, &dirname);


    zPrefix = mprintf("%h/", zD);
  }else{
    blob_append(&dirname, "in the top-level directory", -1);
    zPrefix = "";
  }
  if( zCI ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    char zShort[20];
    memcpy(zShort, zUuid, 10);
    zShort[10] = 0;
    @ <h2>Files of check-in [<a href="vinfo?name=%T(zUuid)">%s(zShort)</a>]
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%s/dir?ci=%s&name=%T", g.zBaseURL, zUuid, zPrefix);
    if( zD ){
      style_submenu_element("Top", "Top", "%s/dir?ci=%s", g.zBaseURL, zUuid);
      style_submenu_element("All", "All", "%s/dir?name=%t", g.zBaseURL, zD);
    }else{
      style_submenu_element("All", "All", "%s/dir", g.zBaseURL);
    }
  }else{

    @ <h2>The union of all files from all check-ins
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%s/dir?name=%T", g.zBaseURL, zPrefix);
    if( zD ){
      style_submenu_element("Top", "Top", "%s/dir", g.zBaseURL);
      style_submenu_element("Tip", "Tip", "%s/dir?name=%t&ci=tip",
                            g.zBaseURL, zD);
      style_submenu_element("Trunk", "Trunk", "%s/dir?name=%t&ci=trunk",
                             g.zBaseURL,zD);
    }else{
      style_submenu_element("Tip", "Tip", "%s/dir?ci=tip", g.zBaseURL);
      style_submenu_element("Trunk", "Trunk", "%s/dir?ci=trunk", g.zBaseURL);
    }
  }

  /* Compute the temporary table "localfiles" containing the names
  ** of all files and subdirectories in the zD[] directory.  
  **
  ** Subdirectory names begin with "/".  This causes them to sort
  ** first and it also gives us an easy way to distinguish files

Changes to src/checkin.c.

191
192
193
194
195
196
197
198
199






















































200
201
202
203
204
205
206
207
208
209
210
211
212
213

214

215
216
217
218



219
220
221
222

223


224
225
226
227
228
229
230
...
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699

700
701




702


703
704
705
706
707
708
709
    }else{
      printf("UNCHANGED  %s\n", zPathname);
    }
    free(zFullName);
  }
  db_finalize(&q);
}

/*






















































** COMMAND: extras
** Usage: %fossil extras ?--dotfiles?
**
** Print a list of all files in the source tree that are not part of
** the current checkout.  See also the "clean" command.
**
** Files and subdirectories whose names begin with "." are normally
** ignored but can be included by adding the --dotfiles option.
*/
void extra_cmd(void){
  Blob path;
  Blob repo;
  Stmt q;
  int n;

  int allFlag = find_option("dotfiles",0,0)!=0;

  db_must_be_within_tree();
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
  n = strlen(g.zLocalRoot);
  blob_init(&path, g.zLocalRoot, n-1);



  vfile_scan(0, &path, blob_size(&path), allFlag);
  db_prepare(&q, 
      "SELECT x FROM sfile"
      " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')"

      " ORDER BY 1");


  if( file_tree_name(g.zRepositoryName, &repo, 0) ){
    db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo);
  }
  while( db_step(&q)==SQLITE_ROW ){
    printf("%s\n", db_column_text(&q, 0));
  }
  db_finalize(&q);
................................................................................
  }
  blob_appendf(&manifest, "C %F\n", blob_str(&comment));
  zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now");
  zDate[10] = 'T';
  blob_appendf(&manifest, "D %s\n", zDate);
  zDate[10] = ' ';
  db_prepare(&q,
    "SELECT pathname, uuid, origname, blob.rid"
    "  FROM vfile JOIN blob ON vfile.mrid=blob.rid"
    " WHERE NOT deleted AND vfile.vid=%d"
    " ORDER BY 1", vid);
  blob_zero(&filename);
  blob_appendf(&filename, "%s", g.zLocalRoot);
  nBasename = blob_size(&filename);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zOrig = db_column_text(&q, 2);
    int frid = db_column_int(&q, 3);

    const char *zPerm;
    blob_append(&filename, zName, -1);




    if( file_isexe(blob_str(&filename)) ){


      zPerm = " x";
    }else{
      zPerm = "";
    }
    blob_resize(&filename, nBasename);
    if( zOrig==0 || strcmp(zOrig,zName)==0 ){
      blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm);









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

|












>

>




>
>
>




>
|
>
>







 







|











>


>
>
>
>
|
>
>







191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
...
743
744
745
746
747
748
749
750
751
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
    }else{
      printf("UNCHANGED  %s\n", zPathname);
    }
    free(zFullName);
  }
  db_finalize(&q);
}

/*
** Construct and return a string which is an SQL expression that will
** be TRUE if value zVal matches any of the GLOB expressions in the list
** zGlobList.  For example:
**
**    zVal:       "x"
**    zGlobList:  "*.o,*.obj"
**
**    Result:     "(x GLOB '*.o' OR x GLOB '*.obj')"
**
** Each element of the GLOB list may optionally be enclosed in either '...'
** or "...".  This allows commas in the expression.  Whitespace at the
** beginning and end of each GLOB pattern is ignored, except when enclosed
** within '...' or "...".
**
** This routine makes no effort to free the memory space it uses.
*/
char *glob_expr(const char *zVal, const char *zGlobList){
  Blob expr;
  char *zSep = "(";
  int nTerm = 0;
  int i;
  int cTerm;

  if( zGlobList==0 || zGlobList[0]==0 ) return "0";
  blob_zero(&expr);
  while( zGlobList[0] ){
    while( isspace(zGlobList[0]) || zGlobList[0]==',' ) zGlobList++;
    if( zGlobList[0]==0 ) break;
    if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){
      cTerm = zGlobList[0];
      zGlobList++;
    }else{
      cTerm = ',';
    }
    for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){}
    if( cTerm==',' ){
      while( i>0 && isspace(zGlobList[i-1]) ){ i--; }
    }
    blob_appendf(&expr, "%s%s GLOB '%.*q'", zSep, zVal, i, zGlobList);
    zSep = " OR ";
    if( cTerm!=',' && zGlobList[i] ) i++;
    zGlobList += i;
    if( zGlobList[0] ) zGlobList++;
    nTerm++;
  }
  if( nTerm ){
    blob_appendf(&expr, ")");
    return blob_str(&expr);
  }else{
    return "0";
  }
}

/*
** COMMAND: extras
** Usage: %fossil extras ?--dotfiles? ?--ignore GLOBPATTERN?
**
** Print a list of all files in the source tree that are not part of
** the current checkout.  See also the "clean" command.
**
** Files and subdirectories whose names begin with "." are normally
** ignored but can be included by adding the --dotfiles option.
*/
void extra_cmd(void){
  Blob path;
  Blob repo;
  Stmt q;
  int n;
  const char *zIgnoreFlag = find_option("ignore",0,1);
  int allFlag = find_option("dotfiles",0,0)!=0;

  db_must_be_within_tree();
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY)");
  n = strlen(g.zLocalRoot);
  blob_init(&path, g.zLocalRoot, n-1);
  if( zIgnoreFlag==0 ){
    zIgnoreFlag = db_get("ignore-glob", 0);
  }
  vfile_scan(0, &path, blob_size(&path), allFlag);
  db_prepare(&q, 
      "SELECT x FROM sfile"
      " WHERE x NOT IN ('manifest','manifest.uuid','_FOSSIL_')"
      "   AND NOT %s"
      " ORDER BY 1",
      glob_expr("x", zIgnoreFlag)
  );
  if( file_tree_name(g.zRepositoryName, &repo, 0) ){
    db_multi_exec("DELETE FROM sfile WHERE x=%B", &repo);
  }
  while( db_step(&q)==SQLITE_ROW ){
    printf("%s\n", db_column_text(&q, 0));
  }
  db_finalize(&q);
................................................................................
  }
  blob_appendf(&manifest, "C %F\n", blob_str(&comment));
  zDate = db_text(0, "SELECT datetime('%q')", zDateOvrd ? zDateOvrd : "now");
  zDate[10] = 'T';
  blob_appendf(&manifest, "D %s\n", zDate);
  zDate[10] = ' ';
  db_prepare(&q,
    "SELECT pathname, uuid, origname, blob.rid, isexe"
    "  FROM vfile JOIN blob ON vfile.mrid=blob.rid"
    " WHERE NOT deleted AND vfile.vid=%d"
    " ORDER BY 1", vid);
  blob_zero(&filename);
  blob_appendf(&filename, "%s", g.zLocalRoot);
  nBasename = blob_size(&filename);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zOrig = db_column_text(&q, 2);
    int frid = db_column_int(&q, 3);
    int isexe = db_column_int(&q, 4);
    const char *zPerm;
    blob_append(&filename, zName, -1);
#ifndef __MINGW32__
    /* For unix, extract the "executable" permission bit directly from
    ** the filesystem.  On windows, the "executable" bit is retained
    ** unchanged from the original. */
    isexe = file_isexe(blob_str(&filename));
#endif
    if( isexe ){
      zPerm = " x";
    }else{
      zPerm = "";
    }
    blob_resize(&filename, nBasename);
    if( zOrig==0 || strcmp(zOrig,zName)==0 ){
      blob_appendf(&manifest, "F %F %s%s\n", zName, zUuid, zPerm);

Changes to src/checkout.c.

91
92
93
94
95
96
97








98
99
100
101
102
103
104
...
126
127
128
129
130
131
132

133
134
135
136
137
138
139
  if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){
    return;
  }
  content_get(vid, &manifest);
  vfile_build(vid, &manifest);
  blob_reset(&manifest);
}









/*
** Read the manifest file given by vid out of the repository
** and store it in the root of the local check-out.
*/
void manifest_to_disk(int vid){
  char *zManFile;
................................................................................
  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);

    blob_resize(&filename, baseLen);
  }
  blob_reset(&filename);
  manifest_clear(&m);
}

/*







>
>
>
>
>
>
>
>







 







>







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
...
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
  if( db_exists("SELECT 1 FROM vfile WHERE vid=%d", vid) ){
    return;
  }
  content_get(vid, &manifest);
  vfile_build(vid, &manifest);
  blob_reset(&manifest);
}

/*
** Set or clear the vfile.isexe flag for a file.
*/
static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){
  db_multi_exec("UPDATE vfile SET isexe=%d WHERE vid=%d and pathname=%Q",
                onoff, vid, zFilename);
}

/*
** Read the manifest file given by vid out of the repository
** and store it in the root of the local check-out.
*/
void manifest_to_disk(int vid){
  char *zManFile;
................................................................................
  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);
  }
  blob_reset(&filename);
  manifest_clear(&m);
}

/*

Changes to src/db.c.

494
495
496
497
498
499
500
501
502
503
504
505
506




507
508
509
510




511
512
513
514
515
516
517
...
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
...
883
884
885
886
887
888
889
890




891
892
893
894
895
896
897
...
940
941
942
943
944
945
946

947
948
949
950
951
952
953
....
1403
1404
1405
1406
1407
1408
1409




1410
1411
1412
1413
1414
1415
1416
....
1421
1422
1423
1424
1425
1426
1427




1428
1429
1430
1431
1432
1433
1434
....
1446
1447
1448
1449
1450
1451
1452

1453
1454
1455
1456
1457

1458
1459
1460
1461
1462
1463
1464
** of the result set as a string.  Space to hold the string is
** obtained from malloc().  If the result set is empty, return
** zDefault instead.
*/
char *db_text(char *zDefault, const char *zSql, ...){
  va_list ap;
  Stmt s;
  char *z = zDefault;
  va_start(ap, zSql);
  db_vprepare(&s, zSql, ap);
  va_end(ap);
  if( db_step(&s)==SQLITE_ROW ){
    z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));




  }
  db_finalize(&s);
  return z;
}





/*
** Initialize a new database file with the given schema.  If anything
** goes wrong, call db_err() to exit.
*/
void db_init_database(
  const char *zFileName,   /* Name of database file to create */
................................................................................
  lsize = file_size(zDbName);
  if( lsize%1024!=0 || lsize<4096 ) return 0;
  db_open_or_attach(zDbName, "localdb");
  g.localOpen = 1;
  db_open_config(0);
  db_open_repository(0);












  /* If the "mtime" column is missing from the vfile table, then
  ** add it now.   This code added on 2008-12-06.  After all users have
  ** upgraded, this code can be safely deleted. 
  */
  rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0);
  sqlite3_finalize(pStmt);
  if( rc==SQLITE_ERROR ){
    sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0);
  }



  /* If the "origname" column is missing from the vfile table, then
  ** add it now.   This code added on 2008-11-09.  After all users have
  ** upgraded, this code can be safely deleted. 
  */
  rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0);
  sqlite3_finalize(pStmt);
  if( rc==SQLITE_ERROR ){
    sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0);
  }


  return 1;
}

/*
** Locate the root directory of the local repository tree.  The root
** directory is found by searching for a file named "_FOSSIL_" or ".fos"
................................................................................
** new repositories.
**
** The zInitialDate parameter determines the date of the initial check-in
** that is automatically created.  If zInitialDate is 0 then no initial
** check-in is created. The makeServerCodes flag determines whether or
** not server and project codes are invented for this repository.
*/
void db_initial_setup (const char *zInitialDate, const char *zDefaultUser, int makeServerCodes){




  char *zDate;
  Blob hash;
  Blob manifest;

  db_set("content-schema", CONTENT_SCHEMA, 0);
  db_set("aux-schema", AUX_SCHEMA, 0);
  if( makeServerCodes ){
................................................................................
** By default, your current login name is used to create the default
** admin user. This can be overridden using the -A|--admin-user
** parameter.
**
** Options:
**
**    --admin-user|-A USERNAME

**
*/
void create_repository_cmd(void){
  char *zPassword;
  const char *zDate;          /* Date of the initial check-in */
  const char *zDefaultUser;   /* Optional name of the default user */

................................................................................
** The "unset" command clears a property setting.
**
**
**    autosync         If enabled, automatically pull prior to commit
**                     or update and automatically push after commit or
**                     tag or branch creation.  If the the value is "pullonly"
**                     then only pull operations occur automatically.




**
**    clearsign        When enabled, fossil will attempt to sign all commits
**                     with gpg.  When disabled (the default), commits will
**                     be unsigned.
**
**    diff-command     External command to run when performing a diff.
**                     If undefined, the internal text diff will be used.
................................................................................
**    editor           Text editor command used for check-in comments.
**
**    gdiff-command    External command to run when performing a graphical
**                     diff. If undefined, text diff will be used.
**
**    http-port        The TCP/IP port number to use by the "server"
**                     and "ui" commands.  Default: 8080




**
**    localauth        If enabled, require that HTTP connections from
**                     127.0.0.1 be authenticated by password.  If
**                     false, all HTTP requests from localhost have
**                     unrestricted access to the repository.
**
**    mtime-changes    Use file modification times (mtimes) to detect when
................................................................................
**                     web browser when given a URL as an argument.
**                     Defaults to "start" on windows, "open" on Mac,
**                     and "firefox" on Unix.
*/
void setting_cmd(void){
  static const char *azName[] = {
    "autosync",

    "clearsign",
    "diff-command",
    "dont-push",
    "editor",
    "gdiff-command",

    "http-port",
    "localauth",
    "mtime-changes",
    "pgp-command",
    "proxy",
    "web-browser",
  };







|





>
>
>
>




>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>









>

>









>







 







|
>
>
>
>







 







>







 







>
>
>
>







 







>
>
>
>







 







>





>







494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
...
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
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
...
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
...
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
....
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
....
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
....
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
** of the result set as a string.  Space to hold the string is
** obtained from malloc().  If the result set is empty, return
** zDefault instead.
*/
char *db_text(char *zDefault, const char *zSql, ...){
  va_list ap;
  Stmt s;
  char *z;
  va_start(ap, zSql);
  db_vprepare(&s, zSql, ap);
  va_end(ap);
  if( db_step(&s)==SQLITE_ROW ){
    z = mprintf("%s", sqlite3_column_text(s.pStmt, 0));
  }else if( zDefault ){
    z = mprintf("%s", zDefault);
  }else{
    z = 0;
  }
  db_finalize(&s);
  return z;
}

#ifdef __MINGW32__
extern char *sqlite3_win32_mbcs_to_utf8(const char*);
#endif

/*
** Initialize a new database file with the given schema.  If anything
** goes wrong, call db_err() to exit.
*/
void db_init_database(
  const char *zFileName,   /* Name of database file to create */
................................................................................
  lsize = file_size(zDbName);
  if( lsize%1024!=0 || lsize<4096 ) return 0;
  db_open_or_attach(zDbName, "localdb");
  g.localOpen = 1;
  db_open_config(0);
  db_open_repository(0);

  /* If the "isexe" column is missing from the vfile table, then
  ** add it now.   This code added on 2010-03-06.  After all users have
  ** upgraded, this code can be safely deleted. 
  */
  rc = sqlite3_prepare(g.db, "SELECT isexe FROM vfile", -1, &pStmt, 0);
  sqlite3_finalize(pStmt);
  if( rc==SQLITE_ERROR ){
    sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN isexe BOOLEAN", 0, 0, 0);
  }

#if 0
  /* If the "mtime" column is missing from the vfile table, then
  ** add it now.   This code added on 2008-12-06.  After all users have
  ** upgraded, this code can be safely deleted. 
  */
  rc = sqlite3_prepare(g.db, "SELECT mtime FROM vfile", -1, &pStmt, 0);
  sqlite3_finalize(pStmt);
  if( rc==SQLITE_ERROR ){
    sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN mtime INTEGER", 0, 0, 0);
  }
#endif

#if 0
  /* If the "origname" column is missing from the vfile table, then
  ** add it now.   This code added on 2008-11-09.  After all users have
  ** upgraded, this code can be safely deleted. 
  */
  rc = sqlite3_prepare(g.db, "SELECT origname FROM vfile", -1, &pStmt, 0);
  sqlite3_finalize(pStmt);
  if( rc==SQLITE_ERROR ){
    sqlite3_exec(g.db, "ALTER TABLE vfile ADD COLUMN origname TEXT", 0, 0, 0);
  }
#endif

  return 1;
}

/*
** Locate the root directory of the local repository tree.  The root
** directory is found by searching for a file named "_FOSSIL_" or ".fos"
................................................................................
** new repositories.
**
** The zInitialDate parameter determines the date of the initial check-in
** that is automatically created.  If zInitialDate is 0 then no initial
** check-in is created. The makeServerCodes flag determines whether or
** not server and project codes are invented for this repository.
*/
void db_initial_setup(
  const char *zInitialDate,    /* Initial date of repository. (ex: "now") */
  const char *zDefaultUser,    /* Default user for the repository */
  int makeServerCodes          /* True to make new server & project codes */
){
  char *zDate;
  Blob hash;
  Blob manifest;

  db_set("content-schema", CONTENT_SCHEMA, 0);
  db_set("aux-schema", AUX_SCHEMA, 0);
  if( makeServerCodes ){
................................................................................
** By default, your current login name is used to create the default
** admin user. This can be overridden using the -A|--admin-user
** parameter.
**
** Options:
**
**    --admin-user|-A USERNAME
**    --date-override DATETIME
**
*/
void create_repository_cmd(void){
  char *zPassword;
  const char *zDate;          /* Date of the initial check-in */
  const char *zDefaultUser;   /* Optional name of the default user */

................................................................................
** The "unset" command clears a property setting.
**
**
**    autosync         If enabled, automatically pull prior to commit
**                     or update and automatically push after commit or
**                     tag or branch creation.  If the the value is "pullonly"
**                     then only pull operations occur automatically.
**
**    binary-glob      The VALUE is a comma-separated list of GLOB patterns
**                     that should be treated as binary files for merging
**                     purposes.  Example:   *.xml
**
**    clearsign        When enabled, fossil will attempt to sign all commits
**                     with gpg.  When disabled (the default), commits will
**                     be unsigned.
**
**    diff-command     External command to run when performing a diff.
**                     If undefined, the internal text diff will be used.
................................................................................
**    editor           Text editor command used for check-in comments.
**
**    gdiff-command    External command to run when performing a graphical
**                     diff. If undefined, text diff will be used.
**
**    http-port        The TCP/IP port number to use by the "server"
**                     and "ui" commands.  Default: 8080
**
**    ignore-glob      The VALUE is a comma-separated list of GLOB patterns
**                     specifying files that the "extra" command will ignore.
**                     Example:  *.o,*.obj,*.exe
**
**    localauth        If enabled, require that HTTP connections from
**                     127.0.0.1 be authenticated by password.  If
**                     false, all HTTP requests from localhost have
**                     unrestricted access to the repository.
**
**    mtime-changes    Use file modification times (mtimes) to detect when
................................................................................
**                     web browser when given a URL as an argument.
**                     Defaults to "start" on windows, "open" on Mac,
**                     and "firefox" on Unix.
*/
void setting_cmd(void){
  static const char *azName[] = {
    "autosync",
    "binary-glob",
    "clearsign",
    "diff-command",
    "dont-push",
    "editor",
    "gdiff-command",
    "ignore-glob",
    "http-port",
    "localauth",
    "mtime-changes",
    "pgp-command",
    "proxy",
    "web-browser",
  };

Changes to src/diffcmd.c.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  }
  blob_append(pBlob, zIn, -1);
}

/*
** This function implements a cross-platform "system()" interface.
*/
int portable_system(char *zOrigCmd){
  int rc;
#ifdef __MINGW32__
  /* On windows, we have to put double-quotes around the entire command.
  ** Who knows why - this is just the way windows works.
  */
  char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
  rc = system(zNewCmd);







|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  }
  blob_append(pBlob, zIn, -1);
}

/*
** This function implements a cross-platform "system()" interface.
*/
int portable_system(const char *zOrigCmd){
  int rc;
#ifdef __MINGW32__
  /* On windows, we have to put double-quotes around the entire command.
  ** Who knows why - this is just the way windows works.
  */
  char *zNewCmd = mprintf("\"%s\"", zOrigCmd);
  rc = system(zNewCmd);

Changes to src/file.c.

161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
      chmod(zFilename, buf.st_mode | 0111);
    }
  }else{
    if( (buf.st_mode & 0111)!=0 ){
      chmod(zFilename, buf.st_mode & ~0111);
    }
  }
#endif
}

/*
** Create the directory named in the argument, if it does not already
** exist.  If forceFlag is 1, delete any prior non-directory object 
** with the same name.
**







|







161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
      chmod(zFilename, buf.st_mode | 0111);
    }
  }else{
    if( (buf.st_mode & 0111)!=0 ){
      chmod(zFilename, buf.st_mode & ~0111);
    }
  }
#endif /* __MINGW32__ */
}

/*
** Create the directory named in the argument, if it does not already
** exist.  If forceFlag is 1, delete any prior non-directory object 
** with the same name.
**

Changes to src/finfo.c.

98
99
100
101
102
103
104

105
106
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
141




142
143


144
145
146
147
148
149


150
151





152

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174









175

176
177
** Show the complete change history for a single file. 
*/
void finfo_page(void){
  Stmt q;
  const char *zFilename;
  char zPrevDate[20];
  Blob title;


  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }
  style_header("File History");
  login_anonymous_available();

  zPrevDate[0] = 0;
  zFilename = PD("name","");
  db_prepare(&q,


    "SELECT substr(b.uuid,1,10), datetime(event.mtime,'localtime'),"
    "       coalesce(event.ecomment, event.comment),"
    "       coalesce(event.euser, event.user),"
    "       mlink.pid, mlink.fid, mlink.mid, mlink.fnid, ci.uuid"








    "  FROM mlink, blob b, event, blob ci"
    " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
    "   AND b.rid=mlink.fid"
    "   AND event.objid=mlink.mid"
    "   AND event.objid=ci.rid"
    " ORDER BY event.mtime DESC",

    zFilename
  );
  blob_zero(&title);
  blob_appendf(&title, "History of ");
  hyperlinked_path(zFilename, &title);
  @ <h2>%b(&title)</h2>
  blob_reset(&title);


  @ <table cellspacing=0 border=0 cellpadding=0>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zCom = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    int fpid = db_column_int(&q, 4);
    int frid = db_column_int(&q, 5);
    int mid = db_column_int(&q, 6);
    int fnid = db_column_int(&q, 7);
    const char *zCkin = db_column_text(&q,8);




    char zShort[20];
    char zShortCkin[20];


    if( memcmp(zDate, zPrevDate, 10) ){
      sprintf(zPrevDate, "%.10s", zDate);
      @ <tr><td colspan=3>
      @   <div class="divider">%s(zPrevDate)</div>
      @ </td></tr>
    }


    @ <tr><td valign="top">%s(&zDate[11])</td>
    @ <td width="20"></td>





    @ <td valign="top" align="left">

    sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid);
    sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin);
    if( g.okHistory ){
      @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a>
    }else{
      @ [%s(zShort)]
    }
    @ part of check-in
    hyperlink_to_uuid(zShortCkin);
    @ %h(zCom) (By: 
    hyperlink_to_user(zUser, zDate, " on");
    hyperlink_to_date(zDate, ")");
    if( g.okHistory ){
      if( fpid ){
        @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&amp;v2=%d(frid)">[diff]</a>
      }
      @ <a href="%s(g.zBaseURL)/annotate?mid=%d(mid)&amp;fnid=%d(fnid)">
      @ [annotate]</a>
      @ </td>
    }
  }
  db_finalize(&q);









  @ </table>

  style_footer();
}







>









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






>







>
>











>
>
>
>


>
>


|
|


>
>
|
<
>
>
>
>
>
|
>









|
|
|










>
>
>
>
>
>
>
>
>

>


98
99
100
101
102
103
104
105
106
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
** Show the complete change history for a single file. 
*/
void finfo_page(void){
  Stmt q;
  const char *zFilename;
  char zPrevDate[20];
  Blob title;
  GraphContext *pGraph;

  login_check_credentials();
  if( !g.okRead ){ login_needed(); return; }
  style_header("File History");
  login_anonymous_available();

  zPrevDate[0] = 0;
  zFilename = PD("name","");
  db_prepare(&q,
    "SELECT"
    " substr(b.uuid,1,10),"
    " datetime(event.mtime,'localtime'),"
    " coalesce(event.ecomment, event.comment),"
    " coalesce(event.euser, event.user),"

    " mlink.pid,"
    " mlink.fid,"
    " mlink.mid,"
    " mlink.fnid,"
    " ci.uuid,"
    " event.bgcolor,"
    " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
                                " AND tagxref.rid=mlink.mid)"
    "  FROM mlink, blob b, event, blob ci"
    " WHERE mlink.fnid=(SELECT fnid FROM filename WHERE name=%Q)"
    "   AND b.rid=mlink.fid"
    "   AND event.objid=mlink.mid"
    "   AND event.objid=ci.rid"
    " ORDER BY event.mtime DESC",
    TAG_BRANCH,
    zFilename
  );
  blob_zero(&title);
  blob_appendf(&title, "History of ");
  hyperlinked_path(zFilename, &title);
  @ <h2>%b(&title)</h2>
  blob_reset(&title);
  pGraph = graph_init();
  @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
  @ <table cellspacing=0 border=0 cellpadding=0>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    const char *zDate = db_column_text(&q, 1);
    const char *zCom = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    int fpid = db_column_int(&q, 4);
    int frid = db_column_int(&q, 5);
    int mid = db_column_int(&q, 6);
    int fnid = db_column_int(&q, 7);
    const char *zCkin = db_column_text(&q,8);
    const char *zBgClr = db_column_text(&q, 9);
    const char *zBr = db_column_text(&q, 10);
    int gidx;
    char zTime[10];
    char zShort[20];
    char zShortCkin[20];
    if( zBr==0 ) zBr = "trunk";
    gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr);
    if( memcmp(zDate, zPrevDate, 10) ){
      sprintf(zPrevDate, "%.10s", zDate);
      @ <tr><td>
      @   <div class="divider"><nobr>%s(zPrevDate)</nobr></div>
      @ </td></tr>
    }
    memcpy(zTime, &zDate[11], 5);
    zTime[5] = 0;
    @ <tr><td valign="top" align="right">

    @ <a href="%s(g.zTop)/timeline?c=%t(zDate)">%s(zTime)</a></td>
    @ <td width="20" align="left" valign="top"><div id="m%d(gidx)"></div></td>
    if( zBgClr && zBgClr[0] ){
      @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
    }else{
      @ <td valign="top" align="left">
    }
    sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid);
    sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin);
    if( g.okHistory ){
      @ <a href="%s(g.zTop)/artifact/%s(zUuid)">[%s(zShort)]</a>
    }else{
      @ [%s(zShort)]
    }
    @ part of check-in
    hyperlink_to_uuid(zShortCkin);
    @ %h(zCom) (user: 
    hyperlink_to_user(zUser, zDate, "");
    @ branch: %h(zBr))
    if( g.okHistory ){
      if( fpid ){
        @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(fpid)&amp;v2=%d(frid)">[diff]</a>
      }
      @ <a href="%s(g.zBaseURL)/annotate?mid=%d(mid)&amp;fnid=%d(fnid)">
      @ [annotate]</a>
      @ </td>
    }
  }
  db_finalize(&q);
  if( pGraph ){
    graph_finish(pGraph, 1);
    if( pGraph->nErr ){
      graph_free(pGraph);
      pGraph = 0;
    }else{
      @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
    }
  }
  @ </table>
  timeline_output_graph_javascript(pGraph);
  style_footer();
}

Changes to src/graph.c.

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

49
50
51
52
53
54
55
..
61
62
63
64
65
66
67

68


69
70
71
72
73
74
75
..
97
98
99
100
101
102
103

104
105


























106
107
108
109
110
111
112
...
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
150



151
152
153
154
155
156
157
...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211

212
213
214
215
216
217
218
219
220
221
222
223










224
225



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
...
255
256
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
283
284
285
286
287
...
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
#define GR_MAX_RAIL   32

/* The graph appears vertically beside a timeline.  Each row in the
** timeline corresponds to a row in the graph.
*/
struct GraphRow {
  int rid;                    /* The rid for the check-in */
  int isLeaf;                 /* True if the check-in is an open leaf */
  int nParent;                /* Number of parents */
  int aParent[GR_MAX_PARENT]; /* Array of parents.  0 element is primary .*/
  char *zBranch;              /* Branch name */

  GraphRow *pNext;            /* Next row down in the list of all rows */
  GraphRow *pPrev;            /* Previous row */
  
  int idx;                    /* Row index.  First is 1.  0 used for "none" */

  int iRail;                  /* Which rail this check-in appears on. 0-based.*/
  int aiRaiser[GR_MAX_RAIL];  /* Raisers from this node to a higher row. */
  int bDescender;             /* Raiser from bottom of graph to here. */
  u32 mergeIn;                /* Merge in from other rails */
  int mergeOut;               /* Merge out to this rail */
  int mergeUpto;              /* Draw the merge rail up to this level */

................................................................................
struct GraphContext {
  int nErr;             /* Number of errors encountered */
  int mxRail;           /* Number of rails required to render the graph */
  GraphRow *pFirst;     /* First row in the list */
  GraphRow *pLast;      /* Last row in the list */
  int nBranch;          /* Number of distinct branches */
  char **azBranch;      /* Names of the branches */

  int railMap[GR_MAX_RAIL];  /* Rail order mapping */


};

#endif

/*
** Malloc for zeroed space.  Panic if unable to provide the
** requested space.
................................................................................
  while( p->pFirst ){
    pRow = p->pFirst;
    p->pFirst = pRow->pNext;
    free(pRow);
  }
  for(i=0; i<p->nBranch; i++) free(p->azBranch[i]);
  free(p->azBranch);

  free(p);
}



























/*
** Return the canonical pointer for a given branch name.
** Multiple calls to this routine with equivalent strings
** will return the same pointer.
*/
static char *persistBranchName(GraphContext *p, const char *zBranch){
................................................................................
  p->azBranch[p->nBranch-1] = mprintf("%s", zBranch);
  return p->azBranch[p->nBranch-1];
}

/*
** Add a new row t the graph context.  Rows are added from top to bottom.
*/
void graph_add_row(
  GraphContext *p,     /* The context to which the row is added */
  int rid,             /* RID for the check-in */
  int isLeaf,          /* True if the check-in is an leaf */
  int nParent,         /* Number of parents */
  int *aParent,        /* Array of parents */
  const char *zBranch  /* Branch for this check-in */
){
  GraphRow *pRow;

  if( p->nErr ) return;
  if( nParent>GR_MAX_PARENT ){ p->nErr++; return; }
  pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) );
  pRow->rid = rid;
  pRow->isLeaf = isLeaf;
  pRow->nParent = nParent;
  pRow->zBranch = persistBranchName(p, zBranch);
  memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent);
  if( p->pFirst==0 ){
    p->pFirst = pRow;
  }else{
    p->pLast->pNext = pRow;
  }
  p->pLast = pRow;



}

/*
** Return the index of a rail currently not in use for any row between
** top and bottom, inclusive.  
*/
static int findFreeRail(
................................................................................
}

/*
** Compute the complete graph
*/
void graph_finish(GraphContext *p, int omitDescenders){
  GraphRow *pRow, *pDesc;
  Bag allRids;
  Bag notLeaf;
  int i;
  int nRow;
  u32 mask;
  u32 inUse;

  if( p==0 || p->pFirst==0 || p->nErr ) return;

  /* Initialize all rows */
  bag_init(&allRids);
  bag_init(&notLeaf);
  nRow = 0;
  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    if( pRow->pNext ) pRow->pNext->pPrev = pRow;
    pRow->idx = ++nRow;
    pRow->iRail = -1;
    pRow->mergeOut = -1;
    bag_insert(&allRids, pRow->rid);

  }
  p->mxRail = -1;

  /* Purge merge-parents that are out-of-graph
  */
  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    for(i=1; i<pRow->nParent; i++){
      if( !bag_find(&allRids, pRow->aParent[i]) ){
        pRow->aParent[i] = pRow->aParent[--pRow->nParent];
        i--;
      }
    }










    if( pRow->nParent>0 && bag_find(&allRids, pRow->aParent[0]) ){
      bag_insert(&notLeaf, pRow->aParent[0]);



    }
  }

  /* Identify rows where the primary parent is off screen.  Assign
  ** each to a rail and draw descenders to the bottom of the screen.
  */
  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    if( pRow->nParent==0 || !bag_find(&allRids,pRow->aParent[0]) ){
      if( omitDescenders ){
        pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0);
      }else{
        pRow->iRail = ++p->mxRail;
      }
      mask = 1<<(pRow->iRail);
      if( omitDescenders ){
................................................................................
  */
  inUse = (1<<(p->mxRail+1))-1;
  for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
    int parentRid;
    if( pRow->iRail>=0 ) continue;
    assert( pRow->nParent>0 );
    parentRid = pRow->aParent[0];
    assert( bag_find(&allRids, parentRid) );
    for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){}
    if( pDesc==0 ){
      /* Time skew */
      pRow->iRail = ++p->mxRail;
      pRow->railInUse = 1<<pRow->iRail;
      continue;
    }
................................................................................
    if( pDesc->aiRaiser[pDesc->iRail]==0 && pDesc->zBranch==pRow->zBranch ){
      pRow->iRail = pDesc->iRail;
    }else{
      pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0);
    }
    pDesc->aiRaiser[pRow->iRail] = pRow->idx;
    mask = 1<<pRow->iRail;
    if( bag_find(&notLeaf, pRow->rid) ){
      inUse |= mask;
    }else{
      inUse &= ~mask;
    }
    for(pDesc = pRow; ; pDesc=pDesc->pNext){
      assert( pDesc!=0 );
      pDesc->railInUse |= mask;
      if( pDesc->rid==parentRid ) break;
    }
  }
................................................................................
        }
      }
      pRow->mergeIn |= 1<<pDesc->mergeOut;
    }
  }

  /*
  ** Sort the rail numbers
  */
#if 0
  p->mxRail = -1;
  mask = 0;
  for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
    if( (mask & (1<<pRow->iRail))==0 ){
      p->railMap[pRow->iRail] = ++p->mxRail;
      mask |= 1<<pRow->iRail;
    }
    if( pRow->mergeOut>=0 && (mask & (1<<pRow->mergeOut))==0 ){
      p->railMap[pRow->mergeOut] = ++p->mxRail;
      mask |= 1<<pRow->mergeOut;
    }
  }
#else
  for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i;
  p->mxRail = 0;
  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
    if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
  }
#endif
}







<








>







 







>

>
>







 







>


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







 







|


<






|
|


<









>
>
>







 







<
<

<






|
|
<


<


<
>







|




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







|







 







<







 







|
|

|







 







|

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






<

33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
..
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
...
100
101
102
103
104
105
106
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
141
142
...
150
151
152
153
154
155
156
157
158
159

160
161
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
...
217
218
219
220
221
222
223


224

225
226
227
228
229
230
231
232

233
234

235
236

237
238
239
240
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
269
270
271
272
273
274
275
276
277
278
279
...
294
295
296
297
298
299
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
...
345
346
347
348
349
350
351
352
353














354
355
356
357
358
359

360
#define GR_MAX_RAIL   32

/* The graph appears vertically beside a timeline.  Each row in the
** timeline corresponds to a row in the graph.
*/
struct GraphRow {
  int rid;                    /* The rid for the check-in */

  int nParent;                /* Number of parents */
  int aParent[GR_MAX_PARENT]; /* Array of parents.  0 element is primary .*/
  char *zBranch;              /* Branch name */

  GraphRow *pNext;            /* Next row down in the list of all rows */
  GraphRow *pPrev;            /* Previous row */
  
  int idx;                    /* Row index.  First is 1.  0 used for "none" */
  int isLeaf;                 /* True if no direct child nodes */
  int iRail;                  /* Which rail this check-in appears on. 0-based.*/
  int aiRaiser[GR_MAX_RAIL];  /* Raisers from this node to a higher row. */
  int bDescender;             /* Raiser from bottom of graph to here. */
  u32 mergeIn;                /* Merge in from other rails */
  int mergeOut;               /* Merge out to this rail */
  int mergeUpto;              /* Draw the merge rail up to this level */

................................................................................
struct GraphContext {
  int nErr;             /* Number of errors encountered */
  int mxRail;           /* Number of rails required to render the graph */
  GraphRow *pFirst;     /* First row in the list */
  GraphRow *pLast;      /* Last row in the list */
  int nBranch;          /* Number of distinct branches */
  char **azBranch;      /* Names of the branches */
  int nRow;                  /* Number of rows */
  int railMap[GR_MAX_RAIL];  /* Rail order mapping */
  int nHash;                 /* Number of slots in apHash[] */
  GraphRow **apHash;         /* Hash table of rows */
};

#endif

/*
** Malloc for zeroed space.  Panic if unable to provide the
** requested space.
................................................................................
  while( p->pFirst ){
    pRow = p->pFirst;
    p->pFirst = pRow->pNext;
    free(pRow);
  }
  for(i=0; i<p->nBranch; i++) free(p->azBranch[i]);
  free(p->azBranch);
  free(p->apHash);
  free(p);
}

/*
** Insert a row into the hash table.  If there is already another
** row with the same rid, the other row is replaced.
*/
static void hashInsert(GraphContext *p, GraphRow *pRow){
  int h;
  h = pRow->rid % p->nHash;
  while( p->apHash[h] && p->apHash[h]->rid!=pRow->rid ){
    h++;
    if( h>=p->nHash ) h = 0;
  }
  p->apHash[h] = pRow;
}

/*
** Look up the row with rid.
*/
static GraphRow *hashFind(GraphContext *p, int rid){
  int h = rid % p->nHash;
  while( p->apHash[h] && p->apHash[h]->rid!=rid ){
    h++;
    if( h>=p->nHash ) h = 0;
  }
  return p->apHash[h];
}

/*
** Return the canonical pointer for a given branch name.
** Multiple calls to this routine with equivalent strings
** will return the same pointer.
*/
static char *persistBranchName(GraphContext *p, const char *zBranch){
................................................................................
  p->azBranch[p->nBranch-1] = mprintf("%s", zBranch);
  return p->azBranch[p->nBranch-1];
}

/*
** Add a new row t the graph context.  Rows are added from top to bottom.
*/
int graph_add_row(
  GraphContext *p,     /* The context to which the row is added */
  int rid,             /* RID for the check-in */

  int nParent,         /* Number of parents */
  int *aParent,        /* Array of parents */
  const char *zBranch  /* Branch for this check-in */
){
  GraphRow *pRow;

  if( p->nErr ) return 0;
  if( nParent>GR_MAX_PARENT ){ p->nErr++; return 0; }
  pRow = (GraphRow*)safeMalloc( sizeof(GraphRow) );
  pRow->rid = rid;

  pRow->nParent = nParent;
  pRow->zBranch = persistBranchName(p, zBranch);
  memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent);
  if( p->pFirst==0 ){
    p->pFirst = pRow;
  }else{
    p->pLast->pNext = pRow;
  }
  p->pLast = pRow;
  p->nRow++;
  pRow->idx = p->nRow;
  return pRow->idx;
}

/*
** Return the index of a rail currently not in use for any row between
** top and bottom, inclusive.  
*/
static int findFreeRail(
................................................................................
}

/*
** Compute the complete graph
*/
void graph_finish(GraphContext *p, int omitDescenders){
  GraphRow *pRow, *pDesc;


  int i;

  u32 mask;
  u32 inUse;

  if( p==0 || p->pFirst==0 || p->nErr ) return;

  /* Initialize all rows */
  p->nHash = p->nRow*2 + 1;
  p->apHash = safeMalloc( sizeof(p->apHash[0])*p->nHash );

  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    if( pRow->pNext ) pRow->pNext->pPrev = pRow;

    pRow->iRail = -1;
    pRow->mergeOut = -1;

    hashInsert(p, pRow);
  }
  p->mxRail = -1;

  /* Purge merge-parents that are out-of-graph
  */
  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    for(i=1; i<pRow->nParent; i++){
      if( hashFind(p, pRow->aParent[i])==0 ){
        pRow->aParent[i] = pRow->aParent[--pRow->nParent];
        i--;
      }
    }
  }

  /* Figure out which nodes have no direct children (children on
  ** the same rail).  Mark such nodes is isLeaf.
  */
  memset(p->apHash, 0, sizeof(p->apHash[0])*p->nHash);
  for(pRow=p->pLast; pRow; pRow=pRow->pPrev) pRow->isLeaf = 1;
  for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
    GraphRow *pParent;
    hashInsert(p, pRow);
    if( pRow->nParent>0
     && (pParent = hashFind(p, pRow->aParent[0]))!=0
     && pRow->zBranch==pParent->zBranch
    ){
      pParent->isLeaf = 0;
    }
  }

  /* Identify rows where the primary parent is off screen.  Assign
  ** each to a rail and draw descenders to the bottom of the screen.
  */
  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
      if( omitDescenders ){
        pRow->iRail = findFreeRail(p, pRow->idx, pRow->idx, 0, 0);
      }else{
        pRow->iRail = ++p->mxRail;
      }
      mask = 1<<(pRow->iRail);
      if( omitDescenders ){
................................................................................
  */
  inUse = (1<<(p->mxRail+1))-1;
  for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
    int parentRid;
    if( pRow->iRail>=0 ) continue;
    assert( pRow->nParent>0 );
    parentRid = pRow->aParent[0];

    for(pDesc=pRow->pNext; pDesc && pDesc->rid!=parentRid; pDesc=pDesc->pNext){}
    if( pDesc==0 ){
      /* Time skew */
      pRow->iRail = ++p->mxRail;
      pRow->railInUse = 1<<pRow->iRail;
      continue;
    }
................................................................................
    if( pDesc->aiRaiser[pDesc->iRail]==0 && pDesc->zBranch==pRow->zBranch ){
      pRow->iRail = pDesc->iRail;
    }else{
      pRow->iRail = findFreeRail(p, 0, pDesc->idx, inUse, 0);
    }
    pDesc->aiRaiser[pRow->iRail] = pRow->idx;
    mask = 1<<pRow->iRail;
    if( pRow->isLeaf ){
      inUse &= ~mask;
    }else{
      inUse |= mask;
    }
    for(pDesc = pRow; ; pDesc=pDesc->pNext){
      assert( pDesc!=0 );
      pDesc->railInUse |= mask;
      if( pDesc->rid==parentRid ) break;
    }
  }
................................................................................
        }
      }
      pRow->mergeIn |= 1<<pDesc->mergeOut;
    }
  }

  /*
  ** Find the maximum rail number.
  */














  for(i=0; i<GR_MAX_RAIL; i++) p->railMap[i] = i;
  p->mxRail = 0;
  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
    if( pRow->iRail>p->mxRail ) p->mxRail = pRow->iRail;
    if( pRow->mergeOut>p->mxRail ) p->mxRail = pRow->mergeOut;
  }

}

Changes to src/info.c.

730
731
732
733
734
735
736










































737
738
739
740
741
742
743
      if( pDownloadName && blob_size(pDownloadName)==0 ){
        blob_append(pDownloadName, zUuid, -1);
      }
      cnt++;
    }
    db_finalize(&q);
  }










































  if( cnt==0 ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    @ Control artifact.
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_append(pDownloadName, zUuid, -1);
    }
  }else if( linkToView && g.okHistory ){







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







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
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
      if( pDownloadName && blob_size(pDownloadName)==0 ){
        blob_append(pDownloadName, zUuid, -1);
      }
      cnt++;
    }
    db_finalize(&q);
  }
  db_prepare(&q, 
    "SELECT target, filename, datetime(mtime), user, src"
    "  FROM attachment"
    " WHERE src=(SELECT uuid FROM blob WHERE rid=%d)"
    " ORDER BY mtime DESC",
    rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTarget = db_column_text(&q, 0);
    const char *zFilename = db_column_text(&q, 1);
    const char *zDate = db_column_text(&q, 2);
    const char *zUser = db_column_text(&q, 3);
    const char *zSrc = db_column_text(&q, 4);
    if( cnt>0 ){
      @ Also attachment "%h(zFilename)" to
    }else{
      @ Attachment "%h(zFilename)" to
    }
    if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
      char zShort[20];
      memcpy(zShort, zTarget, 10);
      if( g.okHistory && g.okRdTkt ){
        @ ticket [<a href="%s(g.zTop)/tktview?name=%s(zShort)">%s(zShort)</a>]
      }else{
        @ ticket [%s(zShort)]
      }
    }else{
      if( g.okHistory && g.okRdWiki ){
        @ wiki page [<a href="%s(g.zTop)/wiki?name=%t(zTarget)">%h(zTarget)</a>]
      }else{
        @ wiki page [%h(zTarget)]
      }
    }
    @ added by
    hyperlink_to_user(zUser,zDate," on");
    hyperlink_to_date(zDate,".");
    cnt++;
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_append(pDownloadName, zSrc, -1);
    }
  }
  db_finalize(&q);
  if( cnt==0 ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    @ Control artifact.
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_append(pDownloadName, zUuid, -1);
    }
  }else if( linkToView && g.okHistory ){

Changes to src/login.c.

474
475
476
477
478
479
480

481
482
483
484
485
486
487
488
...
496
497
498
499
500
501
502

503
504
505
506
507
508
509
...
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.okSetup = 1;  /* Fall thru into Admin */
      case 'a':   g.okAdmin = g.okRdTkt = g.okWrTkt = g.okZip =
                              g.okRdWiki = g.okWrWiki = g.okNewWiki =
                              g.okApndWiki = g.okHistory = g.okClone = 
                              g.okNewTkt = g.okPassword = g.okRdAddr =

                              g.okTktFmt = 1;  /* Fall thru into Read/Write */
      case 'i':   g.okRead = g.okWrite = 1;                     break;
      case 'o':   g.okRead = 1;                                 break;
      case 'z':   g.okZip = 1;                                  break;

      case 'd':   g.okDelete = 1;                               break;
      case 'h':   g.okHistory = 1;                              break;
      case 'g':   g.okClone = 1;                                break;
................................................................................
      case 'e':   g.okRdAddr = 1;                               break;
      case 'r':   g.okRdTkt = 1;                                break;
      case 'n':   g.okNewTkt = 1;                               break;
      case 'w':   g.okWrTkt = g.okRdTkt = g.okNewTkt = 
                  g.okApndTkt = 1;                              break;
      case 'c':   g.okApndTkt = 1;                              break;
      case 't':   g.okTktFmt = 1;                               break;


      /* The "u" privileges is a little different.  It recursively 
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( zUser==0 ){
          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser);
................................................................................
int login_has_capability(const char *zCap, int nCap){
  int i;
  int rc = 1;
  if( nCap<0 ) nCap = strlen(zCap);
  for(i=0; i<nCap && rc && zCap[i]; i++){
    switch( zCap[i] ){
      case 'a':  rc = g.okAdmin;     break;
      /* case 'b': */
      case 'c':  rc = g.okApndTkt;   break;
      case 'd':  rc = g.okDelete;    break;
      case 'e':  rc = g.okRdAddr;    break;
      case 'f':  rc = g.okNewWiki;   break;
      case 'g':  rc = g.okClone;     break;
      case 'h':  rc = g.okHistory;   break;
      case 'i':  rc = g.okWrite;     break;







>
|







 







>







 







|







474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
...
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
...
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.okSetup = 1;  /* Fall thru into Admin */
      case 'a':   g.okAdmin = g.okRdTkt = g.okWrTkt = g.okZip =
                              g.okRdWiki = g.okWrWiki = g.okNewWiki =
                              g.okApndWiki = g.okHistory = g.okClone = 
                              g.okNewTkt = g.okPassword = g.okRdAddr =
                              g.okTktFmt = g.okAttach = 1;
                              /* Fall thru into Read/Write */
      case 'i':   g.okRead = g.okWrite = 1;                     break;
      case 'o':   g.okRead = 1;                                 break;
      case 'z':   g.okZip = 1;                                  break;

      case 'd':   g.okDelete = 1;                               break;
      case 'h':   g.okHistory = 1;                              break;
      case 'g':   g.okClone = 1;                                break;
................................................................................
      case 'e':   g.okRdAddr = 1;                               break;
      case 'r':   g.okRdTkt = 1;                                break;
      case 'n':   g.okNewTkt = 1;                               break;
      case 'w':   g.okWrTkt = g.okRdTkt = g.okNewTkt = 
                  g.okApndTkt = 1;                              break;
      case 'c':   g.okApndTkt = 1;                              break;
      case 't':   g.okTktFmt = 1;                               break;
      case 'b':   g.okAttach = 1;                               break;

      /* The "u" privileges is a little different.  It recursively 
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( zUser==0 ){
          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser);
................................................................................
int login_has_capability(const char *zCap, int nCap){
  int i;
  int rc = 1;
  if( nCap<0 ) nCap = strlen(zCap);
  for(i=0; i<nCap && rc && zCap[i]; i++){
    switch( zCap[i] ){
      case 'a':  rc = g.okAdmin;     break;
      case 'b':  rc = g.okAttach;    break;
      case 'c':  rc = g.okApndTkt;   break;
      case 'd':  rc = g.okDelete;    break;
      case 'e':  rc = g.okRdAddr;    break;
      case 'f':  rc = g.okNewWiki;   break;
      case 'g':  rc = g.okClone;     break;
      case 'h':  rc = g.okHistory;   break;
      case 'i':  rc = g.okWrite;     break;

Changes to src/main.c.

128
129
130
131
132
133
134

135
136
137
138
139
140
141
  int okNewWiki;          /* f: create new wiki via web */
  int okApndWiki;         /* m: append to wiki via web */
  int okWrWiki;           /* k: edit wiki via web */
  int okRdTkt;            /* r: view tickets via web */
  int okNewTkt;           /* n: create new tickets */
  int okApndTkt;          /* c: append to tickets via the web */
  int okWrTkt;            /* w: make changes to tickets via web */

  int okTktFmt;           /* t: create new ticket report formats */
  int okRdAddr;           /* e: read email addresses or other private data */
  int okZip;              /* z: download zipped artifact via /zip URL */

  /* For defense against Cross-site Request Forgery attacks */
  char zCsrfToken[12];    /* Value of the anti-CSRF token */
  int okCsrf;             /* Anti-CSRF token is present and valid */







>







128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
  int okNewWiki;          /* f: create new wiki via web */
  int okApndWiki;         /* m: append to wiki via web */
  int okWrWiki;           /* k: edit wiki via web */
  int okRdTkt;            /* r: view tickets via web */
  int okNewTkt;           /* n: create new tickets */
  int okApndTkt;          /* c: append to tickets via the web */
  int okWrTkt;            /* w: make changes to tickets via web */
  int okAttach;           /* b: add attachments */
  int okTktFmt;           /* t: create new ticket report formats */
  int okRdAddr;           /* e: read email addresses or other private data */
  int okZip;              /* z: download zipped artifact via /zip URL */

  /* For defense against Cross-site Request Forgery attacks */
  char zCsrfToken[12];    /* Value of the anti-CSRF token */
  int okCsrf;             /* Anti-CSRF token is present and valid */

Changes to src/main.mk.

11
12
13
14
15
16
17

18
19
20
21
22
23
24
..
81
82
83
84
85
86
87

88
89
90
91
92
93
94
...
151
152
153
154
155
156
157

158
159
160
161
162
163
164
...
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
...
285
286
287
288
289
290
291







292
293
294
295
296
297
298

XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)


SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/allrepo.c \

  $(SRCDIR)/bag.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
................................................................................
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/zip.c

TRANS_SRC = \
  add_.c \
  allrepo_.c \

  bag_.c \
  blob_.c \
  branch_.c \
  browse_.c \
  captcha_.c \
  cgi_.c \
  checkin_.c \
................................................................................
  winhttp_.c \
  xfer_.c \
  zip_.c

OBJ = \
 $(OBJDIR)/add.o \
 $(OBJDIR)/allrepo.o \

 $(OBJDIR)/bag.o \
 $(OBJDIR)/blob.o \
 $(OBJDIR)/branch.o \
 $(OBJDIR)/browse.o \
 $(OBJDIR)/captcha.o \
 $(OBJDIR)/cgi.o \
 $(OBJDIR)/checkin.o \
................................................................................
#
$(SRCDIR)/../manifest:	
	# noop

clean:	
	rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h
	rm -f translate makeheaders mkindex page_index.h headers
	rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h

page_index.h: $(TRANS_SRC) mkindex
	./mkindex $(TRANS_SRC) >$@
headers:	page_index.h makeheaders VERSION.h
	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
	touch headers
headers: Makefile
Makefile:
add_.c:	$(SRCDIR)/add.c translate
	./translate $(SRCDIR)/add.c >add_.c

$(OBJDIR)/add.o:	add_.c add.h  $(SRCDIR)/config.h
................................................................................
allrepo_.c:	$(SRCDIR)/allrepo.c translate
	./translate $(SRCDIR)/allrepo.c >allrepo_.c

$(OBJDIR)/allrepo.o:	allrepo_.c allrepo.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c

allrepo.h:	headers







bag_.c:	$(SRCDIR)/bag.c translate
	./translate $(SRCDIR)/bag.c >bag_.c

$(OBJDIR)/bag.o:	bag_.c bag.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/bag.o -c bag_.c

bag.h:	headers







>







 







>







 







>







 







|




|







 







>
>
>
>
>
>
>







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
..
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
...
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
...
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
...
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308

XTCC = $(TCC) $(CFLAGS) -I. -I$(SRCDIR)


SRC = \
  $(SRCDIR)/add.c \
  $(SRCDIR)/allrepo.c \
  $(SRCDIR)/attach.c \
  $(SRCDIR)/bag.c \
  $(SRCDIR)/blob.c \
  $(SRCDIR)/branch.c \
  $(SRCDIR)/browse.c \
  $(SRCDIR)/captcha.c \
  $(SRCDIR)/cgi.c \
  $(SRCDIR)/checkin.c \
................................................................................
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/zip.c

TRANS_SRC = \
  add_.c \
  allrepo_.c \
  attach_.c \
  bag_.c \
  blob_.c \
  branch_.c \
  browse_.c \
  captcha_.c \
  cgi_.c \
  checkin_.c \
................................................................................
  winhttp_.c \
  xfer_.c \
  zip_.c

OBJ = \
 $(OBJDIR)/add.o \
 $(OBJDIR)/allrepo.o \
 $(OBJDIR)/attach.o \
 $(OBJDIR)/bag.o \
 $(OBJDIR)/blob.o \
 $(OBJDIR)/branch.o \
 $(OBJDIR)/browse.o \
 $(OBJDIR)/captcha.o \
 $(OBJDIR)/cgi.o \
 $(OBJDIR)/checkin.o \
................................................................................
#
$(SRCDIR)/../manifest:	
	# noop

clean:	
	rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h
	rm -f translate makeheaders mkindex page_index.h headers
	rm -f add.h allrepo.h attach.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h

page_index.h: $(TRANS_SRC) mkindex
	./mkindex $(TRANS_SRC) >$@
headers:	page_index.h makeheaders VERSION.h
	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
	touch headers
headers: Makefile
Makefile:
add_.c:	$(SRCDIR)/add.c translate
	./translate $(SRCDIR)/add.c >add_.c

$(OBJDIR)/add.o:	add_.c add.h  $(SRCDIR)/config.h
................................................................................
allrepo_.c:	$(SRCDIR)/allrepo.c translate
	./translate $(SRCDIR)/allrepo.c >allrepo_.c

$(OBJDIR)/allrepo.o:	allrepo_.c allrepo.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/allrepo.o -c allrepo_.c

allrepo.h:	headers
attach_.c:	$(SRCDIR)/attach.c translate
	./translate $(SRCDIR)/attach.c >attach_.c

$(OBJDIR)/attach.o:	attach_.c attach.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/attach.o -c attach_.c

attach.h:	headers
bag_.c:	$(SRCDIR)/bag.c translate
	./translate $(SRCDIR)/bag.c >bag_.c

$(OBJDIR)/bag.o:	bag_.c bag.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/bag.o -c bag_.c

bag.h:	headers

Changes to src/makemake.tcl.

5
6
7
8
9
10
11

12
13
14
15
16
17
18

# Basenames of all source files that get preprocessed using
# "translate" and "makeheaders"
#
set src {
  add
  allrepo

  bag
  blob
  branch
  browse
  captcha
  cgi
  checkin







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Basenames of all source files that get preprocessed using
# "translate" and "makeheaders"
#
set src {
  add
  allrepo
  attach
  bag
  blob
  branch
  browse
  captcha
  cgi
  checkin

Changes to src/manifest.c.

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
...
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
...
176
177
178
179
180
181
182
183
184
185
186


187
188
189
190
191
192
193
194
195

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
...
245
246
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
277
278
279
280
281
...
606
607
608
609
610
611
612
613
614
615
616

617
618
619
620
621
622
623
624
625
626
627
628
629

630
631
632
633
634
635
636
637
638
639
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
....
1030
1031
1032
1033
1034
1035
1036

1037
1038
1039
1040
1041
1042
1043
....
1063
1064
1065
1066
1067
1068
1069




1070
1071
1072
1073
1074
1075
1076
....
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
....
1142
1143
1144
1145
1146
1147
1148
1149


















































1150
1151
1152
1153
** Types of control files
*/
#define CFTYPE_MANIFEST   1
#define CFTYPE_CLUSTER    2
#define CFTYPE_CONTROL    3
#define CFTYPE_WIKI       4
#define CFTYPE_TICKET     5

/*
** Mode parameter values
*/
#define CFMODE_READ       1
#define CFMODE_APPEND     2
#define CFMODE_WRITE      3

/*
** A parsed manifest or cluster.
*/
struct Manifest {
  Blob content;         /* The original content blob */
  int type;             /* Type of file */
  int mode;             /* Access mode */
  char *zComment;       /* Decoded comment */
  double rDate;         /* Time in the "D" line */
  char *zUser;          /* Name of the user */
  char *zRepoCksum;     /* MD5 checksum of the baseline content */
  char *zWiki;          /* Text of the wiki page */
  char *zWikiTitle;     /* Name of the wiki page */
  char *zTicketUuid;    /* UUID for a ticket */



  int nFile;            /* Number of F lines */
  int nFileAlloc;       /* Slots allocated in aFile[] */
  struct { 
    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;
  int nParent;          /* Number of parents */
  int nParentAlloc;     /* Slots allocated in azParent[] */
  char **azParent;      /* UUIDs of parents */
  int nCChild;          /* Number of cluster children */
  int nCChildAlloc;     /* Number of closts allocated in azCChild[] */
  char **azCChild;      /* UUIDs of referenced objects in a cluster */
  int nTag;             /* Number of T lines */
  int nTagAlloc;        /* Slots allocated in aTag[] */
  struct { 
    char *zName;           /* Name of the tag */
    char *zUuid;           /* UUID that the tag is applied to */
    char *zValue;          /* Value if the tag is really a property */
  } *aTag;
  int nField;           /* Number of J lines */
  int nFieldAlloc;      /* Slots allocated in aField[] */
  struct { 
    char *zName;           /* Key or field name */
    char *zValue;          /* Value of the field */
  } *aField;
  int nAttach;          /* Number of A lines */
  int nAttachAlloc;     /* Slots allocated in aAttach[] */
  struct { 
    char *zUuid;           /* UUID of the attachment */
    char *zName;           /* Name of the attachment */
    char *zDesc;           /* Description of the attachment */
  } *aAttach;
};
#endif


/*
** 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);
  free(p->aAttach);
  memset(p, 0, sizeof(*p));
}

/*
** Parse a blob into a Manifest object.  The Manifest object
** takes over the input blob and will free it when the
** Manifest object is freed.  Zeros are inserted into the blob
................................................................................
      goto manifest_syntax_error;
    }
    cPrevType = z[0];
    seenHeader = 1;
    if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error;
    switch( z[0] ){
      /*
      **     A <uuid> <filename> <description>
      **
      ** Identifies an attachment to either a wiki page or a ticket.
      ** <uuid> is the artifact that is the attachment.


      */
      case 'A': {
        char *zName, *zUuid, *zDesc;
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a3)==0 ) goto manifest_syntax_error;
        zUuid = blob_terminate(&a1);
        zName = blob_terminate(&a2);

        zDesc = blob_terminate(&a3);
        if( blob_size(&a1)!=UUID_SIZE ) goto manifest_syntax_error;
        if( !validate16(zUuid, UUID_SIZE) ) goto manifest_syntax_error;
        defossilize(zName);
        if( !file_is_simple_pathname(zName) ){
          goto manifest_syntax_error;
        }
        defossilize(zDesc);
        if( p->nAttach>=p->nAttachAlloc ){
          p->nAttachAlloc = p->nAttachAlloc*2 + 10;
          p->aAttach = realloc(p->aAttach,
                               p->nAttachAlloc*sizeof(p->aAttach[0]) );
          if( p->aAttach==0 ) fossil_panic("out of memory");
        }
        i = p->nAttach++;
        p->aAttach[i].zUuid = zUuid;
        p->aAttach[i].zName = zName;
        p->aAttach[i].zDesc = zDesc;
        if( i>0 && strcmp(p->aAttach[i-1].zUuid, zUuid)>=0 ){
          goto manifest_syntax_error;
        }
        break;
      }

      /*
      **     C <comment>
      **
      ** Comment text is fossil-encoded.  There may be no more than
................................................................................
        char *zDate;
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( p->rDate!=0.0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
        zDate = blob_terminate(&a1);
        p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate);
        break;
      }

      /*
      **     E <mode>
      **
      ** Access mode.  <mode> can be one of "read", "append",
      ** or "write".
      */
      case 'E': {
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( p->mode!=0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
        if( blob_eq(&a1, "write") ){
          p->mode = CFMODE_WRITE;
        }else if( blob_eq(&a1, "append") ){
          p->mode = CFMODE_APPEND;
        }else if( blob_eq(&a1, "read") ){
          p->mode = CFMODE_READ;
        }else{
          goto manifest_syntax_error;
        }
        break;
      }

      /*
      **     F <filename> <uuid> ?<permissions>? ?<old-name>?
      **
      ** Identifies a file in a manifest.  Multiple F lines are
................................................................................
  if( !seenHeader ) goto manifest_syntax_error;

  if( p->nFile>0 || p->zRepoCksum!=0 ){
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->rDate==0.0 ) goto manifest_syntax_error;
    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->nAttach>0 ) goto manifest_syntax_error;
    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;

    p->type = CFTYPE_MANIFEST;
  }else if( p->nCChild>0 ){
    if( p->rDate>0.0 ) goto manifest_syntax_error;
    if( p->zComment!=0 ) goto manifest_syntax_error;
    if( p->zUser!=0 ) goto manifest_syntax_error;
    if( p->nTag>0 ) goto manifest_syntax_error;
    if( p->nParent>0 ) goto manifest_syntax_error;
    if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->nAttach>0 ) goto manifest_syntax_error;
    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;

    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_CLUSTER;
  }else if( p->nField>0 ){
    if( p->rDate==0.0 ) goto manifest_syntax_error;
    if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->nTag>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid==0 ) goto manifest_syntax_error;
    if( p->zUser==0 ) goto manifest_syntax_error;

    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_TICKET;
  }else if( p->zWiki!=0 ){
    if( p->rDate==0.0 ) goto manifest_syntax_error;
    if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->nTag>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid!=0 ) goto manifest_syntax_error;
    if( p->zWikiTitle==0 ) goto manifest_syntax_error;

    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_WIKI;
  }else if( p->nTag>0 ){
    if( p->rDate<=0.0 ) goto manifest_syntax_error;
    if( p->zRepoCksum!=0 ) goto manifest_syntax_error;
    if( p->nParent>0 ) goto manifest_syntax_error;
    if( p->nAttach>0 ) goto manifest_syntax_error;
    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zWiki ) goto manifest_syntax_error;


    if( p->zWikiTitle ) goto manifest_syntax_error;

    if( p->zTicketUuid ) goto manifest_syntax_error;

    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_CONTROL;
  }else{
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->rDate==0.0 ) goto manifest_syntax_error;

    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->nAttach>0 ) goto manifest_syntax_error;
    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;

    p->type = CFTYPE_MANIFEST;
  }
    
  md5sum_init();
  return 1;

manifest_syntax_error:
  /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
  md5sum_init();
  manifest_clear(p);
................................................................................
  if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){
    manifest_clear(&m);
    return 0;
  }
  db_begin_transaction();
  if( m.type==CFTYPE_MANIFEST ){
    if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){

      for(i=0; i<m.nParent; i++){
        int pid = uuid_to_rid(m.azParent[i], 1);
        db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
                      "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
        if( i==0 ){
          add_mlink(pid, 0, rid, &m);
          parentid = pid;
................................................................................
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
        TAG_DATE, rid, m.rDate,
        rid, m.zUser, m.zComment, 
        TAG_BGCOLOR, rid,
        TAG_USER, rid,
        TAG_COMMENT, rid
      );




    }
  }
  if( m.type==CFTYPE_CLUSTER ){
    tag_insert("cluster", 1, 0, rid, m.rDate, rid);
    for(i=0; i<m.nCChild; i++){
      int mid;
      mid = uuid_to_rid(m.azCChild[i], 1);
................................................................................
    }
  }
  if( m.type==CFTYPE_WIKI ){
    char *zTag = mprintf("wiki-%s", m.zWikiTitle);
    int tagid = tag_findid(zTag, 1);
    int prior;
    char *zComment;





    tag_insert(zTag, 1, 0, rid, m.rDate, rid);
    free(zTag);
    prior = db_int(0,
      "SELECT rid FROM tagxref"
      " WHERE tagid=%d AND mtime<%.17g"
      " ORDER BY mtime DESC",
      tagid, m.rDate
    );
    if( prior ){
      content_deltify(prior, rid, 0);
    }

    zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);



    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,euser,ecomment)"
      "VALUES('w',%.17g,%d,%Q,%Q,"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
................................................................................

    assert( manifest_crosslink_busy==1 );
    zTag = mprintf("tkt-%s", m.zTicketUuid);
    tag_insert(zTag, 1, 0, rid, m.rDate, rid);
    free(zTag);
    db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
                  m.zTicketUuid);
  }


















































  db_end_transaction(0);
  manifest_clear(&m);
  return 1;
}







<
<
<
<
|
<
<






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







|
|

|


|
|





|
|




|
<
<
<
<
<
<
<







 







<







 







|


|
>
>


|



|
|
|
>
|
<
<




|
|
|
|
|
|
|
|
|
|
|
|
<
<







 







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







 







<



>







<


<


>




<






>




<




>




|
|
|
|
|
>
>
|
>

>

|


|
>


<



>


<







 







>







 







>
>
>
>







 







>
>
>
>
>
|










>
|
>
>
>







 








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




35
36
37
38
39
40
41




42


43
44
45
46
47
48
49

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87







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

102
103
104
105
106
107
108
...
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187


188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203


204
205
206
207
208
209
210
...
232
233
234
235
236
237
238























239
240
241
242
243
244
245
...
570
571
572
573
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
601
602
603
604
605
606
607

608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635

636
637
638
639
640
641

642
643
644
645
646
647
648
...
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
....
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
....
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
....
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
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
** 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



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

  char *zComment;       /* Decoded comment.  The C card. */
  double rDate;         /* Date and time from D card.  0.0 if no D card. */
  char *zUser;          /* Name of the user from the U card. */
  char *zRepoCksum;     /* MD5 checksum of the baseline content.  R card. */
  char *zWiki;          /* Text of the wiki page.  W card. */
  char *zWikiTitle;     /* Name of the wiki page. L 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 { 
    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 */
  int nTagAlloc;        /* Slots allocated in aTag[] */
  struct { 
    char *zName;           /* Name of the tag */
    char *zUuid;           /* UUID that the tag is applied to */
    char *zValue;          /* Value if the tag is really a property */
  } *aTag;              /* One for each T card */
  int nField;           /* Number of J cards */
  int nFieldAlloc;      /* Slots allocated in aField[] */
  struct { 
    char *zName;           /* Key or field name */
    char *zValue;          /* Value of the field */
  } *aField;            /* One for each J card */







};
#endif


/*
** 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));
}

/*
** Parse a blob into a Manifest object.  The Manifest object
** takes over the input blob and will free it when the
** Manifest object is freed.  Zeros are inserted into the blob
................................................................................
      goto manifest_syntax_error;
    }
    cPrevType = z[0];
    seenHeader = 1;
    if( blob_token(&line, &token)!=1 ) goto manifest_syntax_error;
    switch( z[0] ){
      /*
      **     A <filename> <target> ?<source>?
      **
      ** Identifies an attachment to either a wiki page or a ticket.
      ** <source> is the artifact that is the attachment.  <source>
      ** is omitted to delete an attachment.  <target> is the name of
      ** a wiki page or ticket to which that attachment is connected.
      */
      case 'A': {
        char *zName, *zTarget, *zSrc;
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a2)==0 ) goto manifest_syntax_error;
        if( p->zAttachName!=0 ) goto manifest_syntax_error;
        zName = blob_terminate(&a1);
        zTarget = blob_terminate(&a2);
        blob_token(&line, &a3);
        zSrc = blob_terminate(&a3);


        defossilize(zName);
        if( !file_is_simple_pathname(zName) ){
          goto manifest_syntax_error;
        }
        defossilize(zTarget);
        if( (blob_size(&a2)!=UUID_SIZE || !validate16(zTarget, UUID_SIZE))
           && !wiki_name_is_wellformed((const unsigned char *)zTarget) ){
          goto manifest_syntax_error;
        }
        if( blob_size(&a3)>0
         && (blob_size(&a3)!=UUID_SIZE || !validate16(zSrc, UUID_SIZE)) ){
          goto manifest_syntax_error;
        }
        p->zAttachName = (char*)file_tail(zName);
        p->zAttachSrc = zSrc;
        p->zAttachTarget = zTarget;


        break;
      }

      /*
      **     C <comment>
      **
      ** Comment text is fossil-encoded.  There may be no more than
................................................................................
        char *zDate;
        md5sum_step_text(blob_buffer(&line), blob_size(&line));
        if( p->rDate!=0.0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a1)==0 ) goto manifest_syntax_error;
        if( blob_token(&line, &a2)!=0 ) goto manifest_syntax_error;
        zDate = blob_terminate(&a1);
        p->rDate = db_double(0.0, "SELECT julianday(%Q)", zDate);























        break;
      }

      /*
      **     F <filename> <uuid> ?<permissions>? ?<old-name>?
      **
      ** Identifies a file in a manifest.  Multiple F lines are
................................................................................
  if( !seenHeader ) goto manifest_syntax_error;

  if( p->nFile>0 || p->zRepoCksum!=0 ){
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->rDate==0.0 ) goto manifest_syntax_error;
    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;

    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->zAttachName ) goto manifest_syntax_error;
    p->type = CFTYPE_MANIFEST;
  }else if( p->nCChild>0 ){
    if( p->rDate>0.0 ) goto manifest_syntax_error;
    if( p->zComment!=0 ) goto manifest_syntax_error;
    if( p->zUser!=0 ) goto manifest_syntax_error;
    if( p->nTag>0 ) goto manifest_syntax_error;
    if( p->nParent>0 ) goto manifest_syntax_error;

    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;

    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->zAttachName ) goto manifest_syntax_error;
    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_CLUSTER;
  }else if( p->nField>0 ){
    if( p->rDate==0.0 ) goto manifest_syntax_error;

    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->nTag>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid==0 ) goto manifest_syntax_error;
    if( p->zUser==0 ) goto manifest_syntax_error;
    if( p->zAttachName ) goto manifest_syntax_error;
    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_TICKET;
  }else if( p->zWiki!=0 ){
    if( p->rDate==0.0 ) goto manifest_syntax_error;

    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->nTag>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid!=0 ) goto manifest_syntax_error;
    if( p->zWikiTitle==0 ) goto manifest_syntax_error;
    if( p->zAttachName ) goto manifest_syntax_error;
    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_WIKI;
  }else if( p->nTag>0 ){
    if( p->rDate<=0.0 ) goto manifest_syntax_error;
    if( p->nParent>0 ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->zAttachName ) goto manifest_syntax_error;
    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_CONTROL;
  }else if( p->zAttachName ){
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->rDate==0.0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( !seenZ ) goto manifest_syntax_error;
    p->type = CFTYPE_ATTACHMENT;
  }else{
    if( p->nCChild>0 ) goto manifest_syntax_error;
    if( p->rDate<=0.0 ) goto manifest_syntax_error;
    if( p->nParent>0 ) goto manifest_syntax_error;
    if( p->nField>0 ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;

    if( p->zWiki ) goto manifest_syntax_error;
    if( p->zWikiTitle ) goto manifest_syntax_error;
    if( p->zTicketUuid ) goto manifest_syntax_error;
    if( p->zAttachName ) goto manifest_syntax_error;
    p->type = CFTYPE_MANIFEST;
  }

  md5sum_init();
  return 1;

manifest_syntax_error:
  /*fprintf(stderr, "Manifest error on line %i\n", lineNo);fflush(stderr);*/
  md5sum_init();
  manifest_clear(p);
................................................................................
  if( g.xlinkClusterOnly && m.type!=CFTYPE_CLUSTER ){
    manifest_clear(&m);
    return 0;
  }
  db_begin_transaction();
  if( m.type==CFTYPE_MANIFEST ){
    if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
      char *zCom;
      for(i=0; i<m.nParent; i++){
        int pid = uuid_to_rid(m.azParent[i], 1);
        db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
                      "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, m.rDate);
        if( i==0 ){
          add_mlink(pid, 0, rid, &m);
          parentid = pid;
................................................................................
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
        TAG_DATE, rid, m.rDate,
        rid, m.zUser, m.zComment, 
        TAG_BGCOLOR, rid,
        TAG_USER, rid,
        TAG_COMMENT, rid
      );
      zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
                        " WHERE rowid=last_insert_rowid()");
      wiki_extract_links(zCom, rid, 0, m.rDate, 1, WIKI_INLINE);
      free(zCom);
    }
  }
  if( m.type==CFTYPE_CLUSTER ){
    tag_insert("cluster", 1, 0, rid, m.rDate, rid);
    for(i=0; i<m.nCChild; i++){
      int mid;
      mid = uuid_to_rid(m.azCChild[i], 1);
................................................................................
    }
  }
  if( m.type==CFTYPE_WIKI ){
    char *zTag = mprintf("wiki-%s", m.zWikiTitle);
    int tagid = tag_findid(zTag, 1);
    int prior;
    char *zComment;
    int nWiki;
    char zLength[40];
    while( isspace(m.zWiki[0]) ) m.zWiki++;
    nWiki = strlen(m.zWiki);
    sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
    tag_insert(zTag, 1, zLength, rid, m.rDate, rid);
    free(zTag);
    prior = db_int(0,
      "SELECT rid FROM tagxref"
      " WHERE tagid=%d AND mtime<%.17g"
      " ORDER BY mtime DESC",
      tagid, m.rDate
    );
    if( prior ){
      content_deltify(prior, rid, 0);
    }
    if( nWiki>0 ){
      zComment = mprintf("Changes to wiki page [%h]", m.zWikiTitle);
    }else{
      zComment = mprintf("Deleted wiki page [%h]", m.zWikiTitle);
    }
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,euser,ecomment)"
      "VALUES('w',%.17g,%d,%Q,%Q,"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
................................................................................

    assert( manifest_crosslink_busy==1 );
    zTag = mprintf("tkt-%s", m.zTicketUuid);
    tag_insert(zTag, 1, 0, rid, m.rDate, rid);
    free(zTag);
    db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
                  m.zTicketUuid);
  }
  if( m.type==CFTYPE_ATTACHMENT ){
    db_multi_exec(
       "INSERT INTO attachment(attachid, mtime, src, target,"
                                        "filename, comment, user)"
       "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
       rid, m.rDate, m.zAttachSrc, m.zAttachTarget, m.zAttachName,
       (m.zComment ? m.zComment : ""), m.zUser
    );
    db_multi_exec(
       "UPDATE attachment SET isLatest = (mtime=="
          "(SELECT max(mtime) FROM attachment"
          "  WHERE target=%Q AND filename=%Q))"
       " WHERE target=%Q AND filename=%Q",
       m.zAttachTarget, m.zAttachName,
       m.zAttachTarget, m.zAttachName
    );
    if( strlen(m.zAttachTarget)!=UUID_SIZE
     || !validate16(m.zAttachTarget, UUID_SIZE) 
    ){
      char *zComment;
      if( m.zAttachSrc && m.zAttachSrc[0] ){
        zComment = mprintf("Add attachment \"%h\" to wiki page [%h]",
             m.zAttachName, m.zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
             m.zAttachName, m.zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('w',%.17g,%d,%Q,%Q)",
        m.rDate, rid, m.zUser, zComment
      );
      free(zComment);
    }else{
      char *zComment;
      if( m.zAttachSrc && m.zAttachSrc[0] ){
        zComment = mprintf("Add attachment \"%h\" to ticket [%.10s]",
             m.zAttachName, m.zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
             m.zAttachName, m.zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('t',%.17g,%d,%Q,%Q)",
        m.rDate, rid, m.zUser, zComment
      );
      free(zComment);
    }
  }
  db_end_transaction(0);
  manifest_clear(&m);
  return 1;
}

Changes to src/merge.c.

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
58
59
60

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75





76
77
78
79
80
81
82
...
227
228
229
230
231
232
233
234
235
236

237
238
239
240
241
242

243
244
245
246
247
248
249
...
252
253
254
255
256
257
258




259

260
261
262
263
264
265
266
#include "merge.h"
#include <assert.h>


/*
** COMMAND: merge
**
** Usage: %fossil merge [--cherrypick] VERSION
**
** The argument is a version that should be merged into the current
** checkout.  All changes from VERSION back to the nearest common
** ancestor are merged.  Except, if the --cherrypick option is used
** only the changes associated with the single check-in VERSION are
** merged.


**
** Only file content is merged.  The result continues to use the
** file and directory names from the current check-out even if those
** names might have been changed in the branch being merged in.








*/
void merge_cmd(void){
  int vid;              /* Current version */
  int mid;              /* Version we are merging against */
  int pid;              /* The pivot version - most recent common ancestor */
  int detailFlag;       /* True if the --detail option is present */
  int pickFlag;         /* True if the --cherrypick option is present */


  Stmt q;

  detailFlag = find_option("detail",0,0)!=0;
  pickFlag = find_option("cherrypick",0,0)!=0;


  if( g.argc!=3 ){
    usage("VERSION");
  }
  db_must_be_within_tree();

  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("nothing is checked out");
  }
  mid = name_to_rid(g.argv[2]);
  if( mid==0 ){
    fossil_fatal("not a version: %s", g.argv[2]);
  }
  if( mid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", mid) ){
    fossil_fatal("not a version: %s", g.argv[2]);
  }
  if( pickFlag ){
    pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid);
    if( pid<=0 ){
      fossil_fatal("cannot find an ancestor for %s", g.argv[2]);





    }
  }else{
    pivot_set_primary(mid);
    pivot_set_secondary(vid);
    db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0");
    while( db_step(&q)==SQLITE_ROW ){
      pivot_set_secondary(db_column_int(&q,0));
................................................................................
  }
  db_finalize(&q);

  /*
  ** Do a three-way merge on files that have changes pid->mid and pid->vid
  */
  db_prepare(&q,
    "SELECT ridm, idv, ridp, ridv FROM fv"
    " WHERE idp>0 AND idv>0 AND idm>0"
    "   AND ridm!=ridp AND (ridv!=ridp OR chnged)"

  );
  while( db_step(&q)==SQLITE_ROW ){
    int ridm = db_column_int(&q, 0);
    int idv = db_column_int(&q, 1);
    int ridp = db_column_int(&q, 2);
    int ridv = db_column_int(&q, 3);

    int rc;
    char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv);
    char *zFullPath;
    Blob m, p, v, r;
    /* Do a 3-way merge of idp->idm into idp->idv.  The results go into idv. */
    if( detailFlag ){
      printf("MERGE %s  (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv);
................................................................................
    }
    undo_save(zName);
    zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
    content_get(ridp, &p);
    content_get(ridm, &m);
    blob_zero(&v);
    blob_read_from_file(&v, zFullPath);




    rc = blob_merge(&p, &m, &v, &r);

    if( rc>=0 ){
      blob_write_to_file(&r, zFullPath);
      if( rc>0 ){
        printf("***** %d merge conflicts in %s\n", rc, zName);
      }
    }else{
      printf("***** Cannot merge binary file %s\n", zName);







|



|
|
|
>
>


|

>
>
>
>
>
>
>
>







>
>




>
>




>











|



>
>
>
>
>







 







|

|
>






>







 







>
>
>
>
|
>







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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
...
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
...
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#include "merge.h"
#include <assert.h>


/*
** COMMAND: merge
**
** Usage: %fossil merge [--cherrypick] [--backout] VERSION
**
** The argument is a version that should be merged into the current
** checkout.  All changes from VERSION back to the nearest common
** ancestor are merged.  Except, if either of the --cherrypick or
** --backout options are used only the changes associated with the
** single check-in VERSION are merged.  The --backout option causes
** the changes associated with VERSION to be removed from the current
** checkout rather than added.
**
** Only file content is merged.  The result continues to use the
** file and directory names from the current checkout even if those
** names might have been changed in the branch being merged in.
**
** Other options:
**
**   --detail                Show additional details of the merge
**
**   --binary GLOBPATTERN    Treat files that match GLOBPATTERN as binary
**                           and do not try to merge parallel changes.  This
**                           option overrides the "binary-glob" setting.
*/
void merge_cmd(void){
  int vid;              /* Current version */
  int mid;              /* Version we are merging against */
  int pid;              /* The pivot version - most recent common ancestor */
  int detailFlag;       /* True if the --detail option is present */
  int pickFlag;         /* True if the --cherrypick option is present */
  int backoutFlag;      /* True if the --backout optioni is present */
  const char *zBinGlob; /* The value of --binary */
  Stmt q;

  detailFlag = find_option("detail",0,0)!=0;
  pickFlag = find_option("cherrypick",0,0)!=0;
  backoutFlag = find_option("backout",0,0)!=0;
  zBinGlob = find_option("binary",0,1);
  if( g.argc!=3 ){
    usage("VERSION");
  }
  db_must_be_within_tree();
  if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("nothing is checked out");
  }
  mid = name_to_rid(g.argv[2]);
  if( mid==0 ){
    fossil_fatal("not a version: %s", g.argv[2]);
  }
  if( mid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", mid) ){
    fossil_fatal("not a version: %s", g.argv[2]);
  }
  if( pickFlag || backoutFlag ){
    pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid);
    if( pid<=0 ){
      fossil_fatal("cannot find an ancestor for %s", g.argv[2]);
    }
    if( backoutFlag ){
      int t = pid;
      pid = mid;
      mid = t;
    }
  }else{
    pivot_set_primary(mid);
    pivot_set_secondary(vid);
    db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0");
    while( db_step(&q)==SQLITE_ROW ){
      pivot_set_secondary(db_column_int(&q,0));
................................................................................
  }
  db_finalize(&q);

  /*
  ** Do a three-way merge on files that have changes pid->mid and pid->vid
  */
  db_prepare(&q,
    "SELECT ridm, idv, ridp, ridv, %s FROM fv"
    " WHERE idp>0 AND idv>0 AND idm>0"
    "   AND ridm!=ridp AND (ridv!=ridp OR chnged)",
    glob_expr("fv.fn", zBinGlob)
  );
  while( db_step(&q)==SQLITE_ROW ){
    int ridm = db_column_int(&q, 0);
    int idv = db_column_int(&q, 1);
    int ridp = db_column_int(&q, 2);
    int ridv = db_column_int(&q, 3);
    int isBinary = db_column_int(&q, 4);
    int rc;
    char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idv);
    char *zFullPath;
    Blob m, p, v, r;
    /* Do a 3-way merge of idp->idm into idp->idv.  The results go into idv. */
    if( detailFlag ){
      printf("MERGE %s  (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv);
................................................................................
    }
    undo_save(zName);
    zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
    content_get(ridp, &p);
    content_get(ridm, &m);
    blob_zero(&v);
    blob_read_from_file(&v, zFullPath);
    if( isBinary ){
      rc = -1;
      blob_zero(&r);
    }else{
      rc = blob_merge(&p, &m, &v, &r);
    }
    if( rc>=0 ){
      blob_write_to_file(&r, zFullPath);
      if( rc>0 ){
        printf("***** %d merge conflicts in %s\n", rc, zName);
      }
    }else{
      printf("***** Cannot merge binary file %s\n", zName);

Changes to src/name.c.

109
110
111
112
113
114
115

















116
117
118
119
120
121









122
123
124
125
126
127
128
...
130
131
132
133
134
135
136






































137
138
139
140
141
142
143
...
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    db_finalize(&q);
    rc = 0;
  }else{
    rc = 0;
  }
  return rc;
}


















/*
** Convert a symbolic tag name into the UUID of a check-in that contains
** that tag.  If the tag appears on multiple check-ins, return the UUID
** of the most recent check-in with the tag.
**









** Memory to hold the returned string comes from malloc() and needs to
** be freed by the caller.
*/
char *tag_to_uuid(const char *zTag){
  char *zUuid = 
    db_text(0,
       "SELECT blob.uuid"
................................................................................
       " WHERE tag.tagname='sym-'||%Q "
       "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
       "   AND event.objid=tagxref.rid "
       "   AND blob.rid=event.objid "
       " ORDER BY event.mtime DESC ",
       zTag
    );






































  return zUuid;
}

/*
** Convert a date/time string into a UUID.
**
** Input forms accepted:
................................................................................
  }else if( memcmp(zDate, "local:", 6)==0 ){
    zDate += 6;
  }else if( memcmp(zDate, "utc:", 4)==0 ){
    zDate += 4;
    useUtc = 1;
  }
  n = strlen(zDate);
  if( n<10 || zDate[4]!='-' || zDate[7]!='-' ) return 0;
  if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){
    zCopy = mprintf("%s", zDate);
    zCopy[n-3] = 0;
    zDate = zCopy;
    n -= 3;
    useUtc = 1;
  }







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






>
>
>
>
>
>
>
>
>







 







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







 







|







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
150
151
152
153
154
...
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
...
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    db_finalize(&q);
    rc = 0;
  }else{
    rc = 0;
  }
  return rc;
}

/*
** Return TRUE if the string begins with an ISO8601 date: YYYY-MM-DD.
*/
static int is_date(const char *z){
  if( !isdigit(z[0]) ) return 0;
  if( !isdigit(z[1]) ) return 0;
  if( !isdigit(z[2]) ) return 0;
  if( !isdigit(z[3]) ) return 0;
  if( z[4]!='-') return 0;
  if( !isdigit(z[5]) ) return 0;
  if( !isdigit(z[6]) ) return 0;
  if( z[7]!='-') return 0;
  if( !isdigit(z[8]) ) return 0;
  if( !isdigit(z[9]) ) return 0;
  return 1;
}

/*
** Convert a symbolic tag name into the UUID of a check-in that contains
** that tag.  If the tag appears on multiple check-ins, return the UUID
** of the most recent check-in with the tag.
**
** If the input string is of the form:
**
**      tag:date
**
** Then return the UUID of the oldest check-in with that tag that is
** not older than 'date'.
**
** An input of "tip" returns the most recent check-in.
**
** Memory to hold the returned string comes from malloc() and needs to
** be freed by the caller.
*/
char *tag_to_uuid(const char *zTag){
  char *zUuid = 
    db_text(0,
       "SELECT blob.uuid"
................................................................................
       " WHERE tag.tagname='sym-'||%Q "
       "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
       "   AND event.objid=tagxref.rid "
       "   AND blob.rid=event.objid "
       " ORDER BY event.mtime DESC ",
       zTag
    );
  if( zUuid==0 ){
    int nTag = strlen(zTag);
    int i;
    for(i=0; i<nTag-10; i++){
      if( zTag[i]==':' && is_date(&zTag[i+1]) ){
        char *zDate = mprintf("%s", &zTag[i+1]);
        char *zTagBase = mprintf("%.*s", i, zTag);
        int nDate = strlen(zDate);
        int useUtc = 0;
        if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){
          nDate -= 3;
          zDate[nDate] = 0;
          useUtc = 1;
        }
        zUuid = db_text(0,
          "SELECT blob.uuid"
          "  FROM tag, tagxref, event, blob"
          " WHERE tag.tagname='sym-'||%Q "
          "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
          "   AND event.objid=tagxref.rid "
          "   AND blob.rid=event.objid "
          "   AND event.mtime<=julianday(%Q %s)"
          " ORDER BY event.mtime DESC ",
          zTagBase, zDate, (useUtc ? "" : ",'utc'")
        );
        break;
      }
    }
    if( zUuid==0 && strcmp(zTag, "tip")==0 ){
      zUuid = db_text(0,
        "SELECT blob.uuid"
        "  FROM event, blob"
        " WHERE event.type='ci'"
        "   AND blob.rid=event.objid"
        " ORDER BY event.mtime DESC"
      );
    }
  }
  return zUuid;
}

/*
** Convert a date/time string into a UUID.
**
** Input forms accepted:
................................................................................
  }else if( memcmp(zDate, "local:", 6)==0 ){
    zDate += 6;
  }else if( memcmp(zDate, "utc:", 4)==0 ){
    zDate += 4;
    useUtc = 1;
  }
  n = strlen(zDate);
  if( n<10 || !is_date(zDate) ) return 0;
  if( n>4 && sqlite3_strnicmp(&zDate[n-3], "utc", 3)==0 ){
    zCopy = mprintf("%s", zDate);
    zCopy[n-3] = 0;
    zDate = zCopy;
    n -= 3;
    useUtc = 1;
  }

Changes to src/schema.c.

307
308
309
310
311
312
313






























314
315
316
317
318
319
320
...
391
392
393
394
395
396
397

398
399
400
401
402
403
404
@   value TEXT,                     -- Value of the tag.  Might be NULL.
@   mtime TIMESTAMP,                -- Time of addition or removal
@   rid INTEGER REFERENCE blob,     -- Artifact tag is applied to
@   UNIQUE(rid, tagid)
@ );
@ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);
@






























@ -- Template for the TICKET table
@ --
@ -- NB: when changing the schema of the TICKET table here, also make the
@ -- same change in tktsetup.c.
@ --
@ CREATE TABLE ticket(
@   -- Do not change any column that begins with tkt_
................................................................................
@ -- 
@ --
@ CREATE TABLE vfile(
@   id INTEGER PRIMARY KEY,           -- ID of the checked out file
@   vid INTEGER REFERENCES blob,      -- The baseline this file is part of.
@   chnged INT DEFAULT 0,             -- 0:unchnged 1:edited 2:m-chng 3:m-add
@   deleted BOOLEAN DEFAULT 0,        -- True if deleted 

@   rid INTEGER,                      -- Originally from this repository record
@   mrid INTEGER,                     -- Based on this record due to a merge
@   mtime INTEGER,                    -- Modification time of file on disk
@   pathname TEXT,                    -- Full pathname relative to root
@   origname TEXT,                    -- Original pathname. NULL if unchanged
@   UNIQUE(pathname,vid)
@ );







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







 







>







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
339
340
341
342
343
344
345
346
347
348
349
350
...
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
@   value TEXT,                     -- Value of the tag.  Might be NULL.
@   mtime TIMESTAMP,                -- Time of addition or removal
@   rid INTEGER REFERENCE blob,     -- Artifact tag is applied to
@   UNIQUE(rid, tagid)
@ );
@ CREATE INDEX tagxref_i1 ON tagxref(tagid, mtime);
@
@ -- When a hyperlink occurs from one artifact to another (for example
@ -- when a check-in comment refers to a ticket) an entry is made in
@ -- the following table for that hyperlink.  This table is used to
@ -- facilitate the display of "back links".
@ --
@ CREATE TABLE backlink(
@   target TEXT,           -- Where the hyperlink points to
@   srctype INT,           -- 0: check-in  1: ticket  2: wiki
@   srcid INT,             -- rid for checkin or wiki.  tkt_id for ticket.
@   mtime TIMESTAMP,       -- time that the hyperlink was added
@   UNIQUE(target, srctype, srcid)
@ );
@ CREATE INDEX backlink_src ON backlink(srcid, srctype);
@
@ -- Each attachment is an entry in the following table.  Only
@ -- the most recent attachment (identified by the D card) is saved.
@ --
@ CREATE TABLE attachment(
@   attachid INTEGER PRIMARY KEY,   -- Local id for this attachment
@   isLatest BOOLEAN DEFAULT 0,     -- True if this is the one to use
@   mtime TIMESTAMP,                -- Time when attachment last changed
@   src TEXT,                       -- UUID of the attachment.  NULL to delete
@   target TEXT,                    -- Object attached to. Wikiname or Tkt UUID
@   filename TEXT,                  -- Filename for the attachment
@   comment TEXT,                   -- Comment associated with this attachment
@   user TEXT                       -- Name of user adding attachment
@ );
@ CREATE INDEX attachment_idx1 ON attachment(target, filename, mtime);
@ CREATE INDEX attachment_idx2 ON attachment(src);
@
@ -- Template for the TICKET table
@ --
@ -- NB: when changing the schema of the TICKET table here, also make the
@ -- same change in tktsetup.c.
@ --
@ CREATE TABLE ticket(
@   -- Do not change any column that begins with tkt_
................................................................................
@ -- 
@ --
@ CREATE TABLE vfile(
@   id INTEGER PRIMARY KEY,           -- ID of the checked out file
@   vid INTEGER REFERENCES blob,      -- The baseline this file is part of.
@   chnged INT DEFAULT 0,             -- 0:unchnged 1:edited 2:m-chng 3:m-add
@   deleted BOOLEAN DEFAULT 0,        -- True if deleted 
@   isexe BOOLEAN,                    -- True if file should be executable
@   rid INTEGER,                      -- Originally from this repository record
@   mrid INTEGER,                     -- Based on this record due to a merge
@   mtime INTEGER,                    -- Modification time of file on disk
@   pathname TEXT,                    -- Full pathname relative to root
@   origname TEXT,                    -- Original pathname. NULL if unchanged
@   UNIQUE(pathname,vid)
@ );

Changes to src/setup.c.

143
144
145
146
147
148
149


150
151
152
153
154
155
156
...
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
...
272
273
274
275
276
277
278

279
280
281
282
283
284
285
...
293
294
295
296
297
298
299

300
301
302
303
304
305
306
...
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363

364
365
366
367
368
369
370
...
463
464
465
466
467
468
469

470
471
472
473
474
475
476
477
478
479
480
481
...
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
...
579
580
581
582
583
584
585





586
587
588
589
590
591
592
  @ <td valign="top">
  @ <b>Notes:</b>
  @ <ol>
  @ <li><p>The permission flags are as follows:</p>
  @ <table>
     @ <tr><td valign="top"><b>a</b></td>
     @   <td><i>Admin:</i> Create and delete users</td></tr>


     @ <tr><td valign="top"><b>c</b></td>
     @   <td><i>Append-Tkt:</i> Append to tickets</td></tr>
     @ <tr><td valign="top"><b>d</b></td>
     @   <td><i>Delete:</i> Delete wiki and tickets</td></tr>
     @ <tr><td valign="top"><b>e</b></td>
     @   <td><i>Email:</i> View sensitive data such as EMail addresses</td></tr>
     @ <tr><td valign="top"><b>f</b></td>
................................................................................
/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap, *zPw;
  char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
  char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
  char *oat, *oau, *oav, *oaz;
  const char *inherit[128];
  int doWrite;
  int uid;
  int higherUser = 0;  /* True if user being edited is SETUP and the */
                       /* user doing the editing is ADMIN.  Disallow editing */

  /* Must have ADMIN privleges to access this page
................................................................................
  ** to the page that displays a list of users.
  */
  doWrite = cgi_all("login","info","pw") && !higherUser;
  if( doWrite ){
    char zCap[50];
    int i = 0;
    int aa = P("aa")!=0;

    int ad = P("ad")!=0;
    int ae = P("ae")!=0;
    int ai = P("ai")!=0;
    int aj = P("aj")!=0;
    int ak = P("ak")!=0;
    int an = P("an")!=0;
    int ao = P("ao")!=0;
................................................................................
    int ah = P("ah")!=0;
    int ag = P("ag")!=0;
    int at = P("at")!=0;
    int au = P("au")!=0;
    int av = P("av")!=0;
    int az = P("az")!=0;
    if( aa ){ zCap[i++] = 'a'; }

    if( ac ){ zCap[i++] = 'c'; }
    if( ad ){ zCap[i++] = 'd'; }
    if( ae ){ zCap[i++] = 'e'; }
    if( af ){ zCap[i++] = 'f'; }
    if( ah ){ zCap[i++] = 'h'; }
    if( ag ){ zCap[i++] = 'g'; }
    if( ai ){ zCap[i++] = 'i'; }
................................................................................

  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  zPw = "";
  oaa = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = "";
  if( uid ){
    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
    if( strchr(zCap, 'a') ) oaa = " checked";

    if( strchr(zCap, 'c') ) oac = " checked";
    if( strchr(zCap, 'd') ) oad = " checked";
    if( strchr(zCap, 'e') ) oae = " checked";
    if( strchr(zCap, 'f') ) oaf = " checked";
    if( strchr(zCap, 'g') ) oag = " checked";
    if( strchr(zCap, 'h') ) oah = " checked";
    if( strchr(zCap, 'i') ) oai = " checked";
................................................................................
  @    <input type="checkbox" name="au"%s(oau)/>%s(B('u'))Reader<br>
  @    <input type="checkbox" name="av"%s(oav)/>%s(B('v'))Developer<br>
  @    <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br>
  @    <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br>
  @    <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br>
  @    <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br>
  @    <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br>

  @    <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Tkt<br>
  @    <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Tkt<br>
  @    <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Tkt<br>
  @    <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Tkt<br>
  @    <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Tkt Report<br>
  @    <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip
  @   </td>
  @ </tr>
  @ <tr>
  @   <td align="right">Password:</td>
  if( zPw[0] ){
    /* Obscure the password for all users */
................................................................................
  @ The <b>Check-out</b> privilege allows remote users to "pull".
  @ The <b>Clone</b> privilege allows remote users to "clone".
  @ </li><p>
  @
  @ <li><p>
  @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and
  @ <b>Write Wiki</b> privileges control access to wiki pages.  The
  @ <b>Read Tkt</b>, <b>New Tkt</b>, <b>Append Tkt</b>, and
  @ <b>Write Tkt</b> privileges control access to trouble tickets.
  @ The <b>Tkt Report</b> privilege allows the user to create or edit
  @ ticket report formats.
  @ </p></li>
  @
  @ <li><p>
  @ Users with the <b>Password</b> privilege are allowed to change their
  @ own password.  Recommended ON for most users but OFF for special
  @ users "developer", "anonymous", and "nobody".
................................................................................
  @ <li><p>
  @ The <b>EMail</b> privilege allows the display of sensitive information
  @ such as the email address of users and contact information on tickets.
  @ Recommended OFF for "anonymous" and for "nobody" but ON for
  @ "developer".
  @ </p></li>
  @





  @ <li><p>
  @ Login is prohibited if the password is an empty string.
  @ </p></li>
  @ </ul>
  @
  @ <h2>Special Logins</h2>
  @ 







>
>







 







|







 







>







 







>







 







|







>







 







>
|
|
|
|
|







 







|
|
|







 







>
>
>
>
>







143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
...
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
...
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
...
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
...
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
...
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
...
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
...
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
  @ <td valign="top">
  @ <b>Notes:</b>
  @ <ol>
  @ <li><p>The permission flags are as follows:</p>
  @ <table>
     @ <tr><td valign="top"><b>a</b></td>
     @   <td><i>Admin:</i> Create and delete users</td></tr>
     @ <tr><td valign="top"><b>b</b></td>
     @   <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr>
     @ <tr><td valign="top"><b>c</b></td>
     @   <td><i>Append-Tkt:</i> Append to tickets</td></tr>
     @ <tr><td valign="top"><b>d</b></td>
     @   <td><i>Delete:</i> Delete wiki and tickets</td></tr>
     @ <tr><td valign="top"><b>e</b></td>
     @   <td><i>Email:</i> View sensitive data such as EMail addresses</td></tr>
     @ <tr><td valign="top"><b>f</b></td>
................................................................................
/*
** WEBPAGE: /setup_uedit
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap, *zPw;
  char *oaa, *oas, *oar, *oaw, *oan, *oai, *oaj, *oao, *oap;
  char *oak, *oad, *oac, *oaf, *oam, *oah, *oag, *oae;
  char *oat, *oau, *oav, *oab, *oaz;
  const char *inherit[128];
  int doWrite;
  int uid;
  int higherUser = 0;  /* True if user being edited is SETUP and the */
                       /* user doing the editing is ADMIN.  Disallow editing */

  /* Must have ADMIN privleges to access this page
................................................................................
  ** to the page that displays a list of users.
  */
  doWrite = cgi_all("login","info","pw") && !higherUser;
  if( doWrite ){
    char zCap[50];
    int i = 0;
    int aa = P("aa")!=0;
    int ab = P("ab")!=0;
    int ad = P("ad")!=0;
    int ae = P("ae")!=0;
    int ai = P("ai")!=0;
    int aj = P("aj")!=0;
    int ak = P("ak")!=0;
    int an = P("an")!=0;
    int ao = P("ao")!=0;
................................................................................
    int ah = P("ah")!=0;
    int ag = P("ag")!=0;
    int at = P("at")!=0;
    int au = P("au")!=0;
    int av = P("av")!=0;
    int az = P("az")!=0;
    if( aa ){ zCap[i++] = 'a'; }
    if( ab ){ zCap[i++] = 'b'; }
    if( ac ){ zCap[i++] = 'c'; }
    if( ad ){ zCap[i++] = 'd'; }
    if( ae ){ zCap[i++] = 'e'; }
    if( af ){ zCap[i++] = 'f'; }
    if( ah ){ zCap[i++] = 'h'; }
    if( ag ){ zCap[i++] = 'g'; }
    if( ai ){ zCap[i++] = 'i'; }
................................................................................

  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  zPw = "";
  oaa = oab = oac = oad = oae = oaf = oag = oah = oai = oaj = oak = oam =
        oan = oao = oap = oar = oas = oat = oau = oav = oaw = oaz = "";
  if( uid ){
    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
    if( strchr(zCap, 'a') ) oaa = " checked";
    if( strchr(zCap, 'b') ) oab = " checked";
    if( strchr(zCap, 'c') ) oac = " checked";
    if( strchr(zCap, 'd') ) oad = " checked";
    if( strchr(zCap, 'e') ) oae = " checked";
    if( strchr(zCap, 'f') ) oaf = " checked";
    if( strchr(zCap, 'g') ) oag = " checked";
    if( strchr(zCap, 'h') ) oah = " checked";
    if( strchr(zCap, 'i') ) oai = " checked";
................................................................................
  @    <input type="checkbox" name="au"%s(oau)/>%s(B('u'))Reader<br>
  @    <input type="checkbox" name="av"%s(oav)/>%s(B('v'))Developer<br>
  @    <input type="checkbox" name="ag"%s(oag)/>%s(B('g'))Clone<br>
  @    <input type="checkbox" name="aj"%s(oaj)/>%s(B('j'))Read Wiki<br>
  @    <input type="checkbox" name="af"%s(oaf)/>%s(B('f'))New Wiki<br>
  @    <input type="checkbox" name="am"%s(oam)/>%s(B('m'))Append Wiki<br>
  @    <input type="checkbox" name="ak"%s(oak)/>%s(B('k'))Write Wiki<br>
  @    <input type="checkbox" name="ab"%s(oab)/>%s(B('b'))Attachments<br>
  @    <input type="checkbox" name="ar"%s(oar)/>%s(B('r'))Read Ticket<br>
  @    <input type="checkbox" name="an"%s(oan)/>%s(B('n'))New Ticket<br>
  @    <input type="checkbox" name="ac"%s(oac)/>%s(B('c'))Append Ticket<br>
  @    <input type="checkbox" name="aw"%s(oaw)/>%s(B('w'))Write Ticket<br>
  @    <input type="checkbox" name="at"%s(oat)/>%s(B('t'))Ticket Report<br>
  @    <input type="checkbox" name="az"%s(oaz)/>%s(B('z'))Download Zip
  @   </td>
  @ </tr>
  @ <tr>
  @   <td align="right">Password:</td>
  if( zPw[0] ){
    /* Obscure the password for all users */
................................................................................
  @ The <b>Check-out</b> privilege allows remote users to "pull".
  @ The <b>Clone</b> privilege allows remote users to "clone".
  @ </li><p>
  @
  @ <li><p>
  @ The <b>Read Wiki</b>, <b>New Wiki</b>, <b>Append Wiki</b>, and
  @ <b>Write Wiki</b> privileges control access to wiki pages.  The
  @ <b>Read Ticket</b>, <b>New Ticket</b>, <b>Append Ticket</b>, and
  @ <b>Write Ticket</b> privileges control access to trouble tickets.
  @ The <b>Ticket Report</b> privilege allows the user to create or edit
  @ ticket report formats.
  @ </p></li>
  @
  @ <li><p>
  @ Users with the <b>Password</b> privilege are allowed to change their
  @ own password.  Recommended ON for most users but OFF for special
  @ users "developer", "anonymous", and "nobody".
................................................................................
  @ <li><p>
  @ The <b>EMail</b> privilege allows the display of sensitive information
  @ such as the email address of users and contact information on tickets.
  @ Recommended OFF for "anonymous" and for "nobody" but ON for
  @ "developer".
  @ </p></li>
  @
  @ <li><p>
  @ The <b>Attachment</b> privilege is needed in order to add attachments
  @ to tickets or wiki.  Write privilege on the ticket or wiki is also
  @ required.</p></li>
  @
  @ <li><p>
  @ Login is prohibited if the password is an empty string.
  @ </p></li>
  @ </ul>
  @
  @ <h2>Special Logins</h2>
  @ 

Changes to src/skins.c.

160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
...
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
...
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
...
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
412
413
414
415
416
417
418
419
420
421
422
...
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
...
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
...
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
@   padding: 0.2ex 2ex;
@ }');
@ REPLACE INTO config VALUES('header','<html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="logo">
@     <nobr>$<project_name></nobr>
@   </div>
................................................................................
@        puts "Logged in as $login"
@      } else {
@        puts "Not logged in"
@      }
@   </th1></nobr></div>
@ </div>
@ <div class="mainmenu"><th1>
@ html "<a href="$baseurl$index_page">Home</a> "
@ if {[anycap jor]} {
@   html "<a href="$baseurl/timeline">Timeline</a> "
@ }
@ if {[hascap oh]} {
@   html "<a href="$baseurl/dir">Files</a> "
@ }
@ if {[hascap o]} {
@   html "<a href="$baseurl/leaves">Leaves</a> "
@   html "<a href="$baseurl/brlist">Branches</a> "
@   html "<a href="$baseurl/taglist">Tags</a> "
@ }
@ if {[hascap r]} {
@   html "<a href="$baseurl/reportlist">Tickets</a> "
@ }
@ if {[hascap j]} {
@   html "<a href="$baseurl/wiki">Wiki</a> "
@ }
@ if {[hascap s]} {
@   html "<a href="$baseurl/setup">Admin</a> "
@ } elseif {[hascap a]} {
@   html "<a href="$baseurl/setup_ulist">Users</a> "
@ }
@ if {[info exists login]} {
@   html "<a href="$baseurl/login">Logout</a> "
@ } else {
@   html "<a href="$baseurl/login">Login</a> "
@ }
@ </th1></div>
@ ');
@ REPLACE INTO config VALUES('footer','<div class="footer">
@ Fossil version $manifest_version $manifest_date 
@ </div>
@ </body></html>
................................................................................
@ }
@ ');
@ REPLACE INTO config VALUES('header','<html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="title">$<title></div>
@   <div class="status">
@     <div class="logo"><nobr>$<project_name></nobr></div><br/>
................................................................................
@        puts "Logged in as $login"
@      } else {
@        puts "Not logged in"
@      }
@   </th1></nobr></div>
@ </div>
@ <div class="mainmenu"><th1>
@ html "<a href="$baseurl$index_page">Home</a> "
@ if {[anycap jor]} {
@   html "<a href="$baseurl/timeline">Timeline</a> "
@ }
@ if {[hascap oh]} {
@   html "<a href="$baseurl/dir">Files</a> "
@ }
@ if {[hascap o]} {
@   html "<a href="$baseurl/leaves">Leaves</a> "
@   html "<a href="$baseurl/brlist">Branches</a> "
@   html "<a href="$baseurl/taglist">Tags</a> "
@ }
@ if {[hascap r]} {
@   html "<a href="$baseurl/reportlist">Tickets</a> "
@ }
@ if {[hascap j]} {
@   html "<a href="$baseurl/wiki">Wiki</a> "
@ }
@ if {[hascap s]} {
@   html "<a href="$baseurl/setup">Admin</a> "
@ } elseif {[hascap a]} {
@   html "<a href="$baseurl/setup_ulist">Users</a> "
@ }
@ if {[info exists login]} {
@   html "<a href="$baseurl/login">Logout</a> "
@ } else {
@   html "<a href="$baseurl/login">Login</a> "
@ }
@ </th1></div>
@ ');
@ REPLACE INTO config VALUES('footer','<div class="footer">
@ Fossil version $manifest_version $manifest_date
@ </div>
@ </body></html>
................................................................................
@   text-decoration: none;
@ }
@ div.mainmenu a:hover {
@   color: #eee;
@   background-color: #333;
@ }
@ 
@ /* Container for the sub-menu and content so they don"t spread
@ ** out underneath the main menu */
@ #container {
@   padding-left: 9em;
@ }
@ 
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu {
................................................................................
@   padding: 0.2ex 2ex;
@ }');
@ REPLACE INTO config VALUES('header','<html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="logo">
@     <!-- <img src="$baseurl/logo" alt="logo"> -->
@     <br><nobr>$<project_name></nobr>
................................................................................
@        puts "Logged in as $login"
@      } else {
@        puts "Not logged in"
@      }
@   </th1></nobr></div>
@ </div>
@ <div class="mainmenu"><ul><th1>
@ html "<li><a href="$baseurl$index_page">Home</a></li>"
@ if {[anycap jor]} {
@   html "<li><a href="$baseurl/timeline">Timeline</a></li>"
@ }
@ if {[hascap oh]} {
@   html "<li><a href="$baseurl/dir">Files</a></li>"
@ }
@ if {[hascap o]} {
@   html "<li><a href="$baseurl/leaves">Leaves</a></li>"
@   html "<li><a href="$baseurl/brlist">Branches</a></li>"
@   html "<li><a href="$baseurl/taglist">Tags</a></li>"
@ }
@ if {[hascap r]} {
@   html "<li><a href="$baseurl/reportlist">Tickets</a></li>"
@ }
@ if {[hascap j]} {
@   html "<li><a href="$baseurl/wiki">Wiki</a></li>"
@ }
@ if {[hascap s]} {
@   html "<li><a href="$baseurl/setup">Admin</a></li>"
@ } elseif {[hascap a]} {
@   html "<li><a href="$baseurl/setup_ulist">Users</a></li>"
@ }
@ if {[info exists login]} {
@   html "<li><a href="$baseurl/login">Logout</a></li>"
@ } else {
@   html "<li><a href="$baseurl/login">Login</a></li>"
@ }
@ </th1></ul></div>
@ <div id="container">
@ ');
@ REPLACE INTO config VALUES('footer','</div>
@ <div class="footer">
@ Fossil version $manifest_version $manifest_date







|







 







|

|


|


|
|
|


|


|


|

|


|

|







 







|







 







|

|


|


|
|
|


|


|


|

|


|

|







 







|







 







|







 







|

|


|


|
|
|


|


|


|

|


|

|







160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
...
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
...
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
...
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
412
413
414
415
416
417
418
419
420
421
422
...
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
...
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
...
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
@   padding: 0.2ex 2ex;
@ }');
@ REPLACE INTO config VALUES('header','<html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css?blackwhite" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="logo">
@     <nobr>$<project_name></nobr>
@   </div>
................................................................................
@        puts "Logged in as $login"
@      } else {
@        puts "Not logged in"
@      }
@   </th1></nobr></div>
@ </div>
@ <div class="mainmenu"><th1>
@ html "<a href=''$baseurl$index_page''>Home</a> "
@ if {[anycap jor]} {
@   html "<a href=''$baseurl/timeline''>Timeline</a> "
@ }
@ if {[hascap oh]} {
@   html "<a href=''$baseurl/dir?ci=tip''>Files</a> "
@ }
@ if {[hascap o]} {
@   html "<a href=''$baseurl/leaves''>Leaves</a> "
@   html "<a href=''$baseurl/brlist''>Branches</a> "
@   html "<a href=''$baseurl/taglist''>Tags</a> "
@ }
@ if {[hascap r]} {
@   html "<a href=''$baseurl/reportlist''>Tickets</a> "
@ }
@ if {[hascap j]} {
@   html "<a href=''$baseurl/wiki''>Wiki</a> "
@ }
@ if {[hascap s]} {
@   html "<a href=''$baseurl/setup''>Admin</a> "
@ } elseif {[hascap a]} {
@   html "<a href=''$baseurl/setup_ulist''>Users</a> "
@ }
@ if {[info exists login]} {
@   html "<a href=''$baseurl/login''>Logout</a> "
@ } else {
@   html "<a href=''$baseurl/login''>Login</a> "
@ }
@ </th1></div>
@ ');
@ REPLACE INTO config VALUES('footer','<div class="footer">
@ Fossil version $manifest_version $manifest_date 
@ </div>
@ </body></html>
................................................................................
@ }
@ ');
@ REPLACE INTO config VALUES('header','<html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css?tan" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="title">$<title></div>
@   <div class="status">
@     <div class="logo"><nobr>$<project_name></nobr></div><br/>
................................................................................
@        puts "Logged in as $login"
@      } else {
@        puts "Not logged in"
@      }
@   </th1></nobr></div>
@ </div>
@ <div class="mainmenu"><th1>
@ html "<a href=''$baseurl$index_page''>Home</a> "
@ if {[anycap jor]} {
@   html "<a href=''$baseurl/timeline''>Timeline</a> "
@ }
@ if {[hascap oh]} {
@   html "<a href=''$baseurl/dir?ci=tip''>Files</a> "
@ }
@ if {[hascap o]} {
@   html "<a href=''$baseurl/leaves''>Leaves</a> "
@   html "<a href=''$baseurl/brlist''>Branches</a> "
@   html "<a href=''$baseurl/taglist''>Tags</a> "
@ }
@ if {[hascap r]} {
@   html "<a href=''$baseurl/reportlist''>Tickets</a> "
@ }
@ if {[hascap j]} {
@   html "<a href=''$baseurl/wiki''>Wiki</a> "
@ }
@ if {[hascap s]} {
@   html "<a href=''$baseurl/setup''>Admin</a> "
@ } elseif {[hascap a]} {
@   html "<a href=''$baseurl/setup_ulist''>Users</a> "
@ }
@ if {[info exists login]} {
@   html "<a href=''$baseurl/login''>Logout</a> "
@ } else {
@   html "<a href=''$baseurl/login''>Login</a> "
@ }
@ </th1></div>
@ ');
@ REPLACE INTO config VALUES('footer','<div class="footer">
@ Fossil version $manifest_version $manifest_date
@ </div>
@ </body></html>
................................................................................
@   text-decoration: none;
@ }
@ div.mainmenu a:hover {
@   color: #eee;
@   background-color: #333;
@ }
@ 
@ /* Container for the sub-menu and content so they don''t spread
@ ** out underneath the main menu */
@ #container {
@   padding-left: 9em;
@ }
@ 
@ /* The submenu bar that *sometimes* appears below the main menu */
@ div.submenu {
................................................................................
@   padding: 0.2ex 2ex;
@ }');
@ REPLACE INTO config VALUES('header','<html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css?black2" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="logo">
@     <!-- <img src="$baseurl/logo" alt="logo"> -->
@     <br><nobr>$<project_name></nobr>
................................................................................
@        puts "Logged in as $login"
@      } else {
@        puts "Not logged in"
@      }
@   </th1></nobr></div>
@ </div>
@ <div class="mainmenu"><ul><th1>
@ html "<li><a href=''$baseurl$index_page''>Home</a></li>"
@ if {[anycap jor]} {
@   html "<li><a href=''$baseurl/timeline''>Timeline</a></li>"
@ }
@ if {[hascap oh]} {
@   html "<li><a href=''$baseurl/dir?ci=tip''>Files</a></li>"
@ }
@ if {[hascap o]} {
@   html "<li><a href=''$baseurl/leaves''>Leaves</a></li>"
@   html "<li><a href=''$baseurl/brlist''>Branches</a></li>"
@   html "<li><a href=''$baseurl/taglist''>Tags</a></li>"
@ }
@ if {[hascap r]} {
@   html "<li><a href=''$baseurl/reportlist''>Tickets</a></li>"
@ }
@ if {[hascap j]} {
@   html "<li><a href=''$baseurl/wiki''>Wiki</a></li>"
@ }
@ if {[hascap s]} {
@   html "<li><a href=''$baseurl/setup''>Admin</a></li>"
@ } elseif {[hascap a]} {
@   html "<li><a href=''$baseurl/setup_ulist''>Users</a></li>"
@ }
@ if {[info exists login]} {
@   html "<li><a href=''$baseurl/login''>Logout</a></li>"
@ } else {
@   html "<li><a href=''$baseurl/login''>Login</a></li>"
@ }
@ </th1></ul></div>
@ <div id="container">
@ ');
@ REPLACE INTO config VALUES('footer','</div>
@ <div class="footer">
@ Fossil version $manifest_version $manifest_date

Changes to src/style.c.

186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
...
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
*/
const char zDefaultHeader[] = 
@ <html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="logo">
@     <img src="$baseurl/logo" alt="logo">
@     <br><nobr>$<project_name></nobr>
................................................................................
@ </div>
@ <div class="mainmenu"><th1>
@ html "<a href='$baseurl$index_page'>Home</a> "
@ if {[anycap jor]} {
@   html "<a href='$baseurl/timeline'>Timeline</a> "
@ }
@ if {[hascap oh]} {
@   html "<a href='$baseurl/dir'>Files</a> "
@ }
@ if {[hascap o]} {
@   html "<a href='$baseurl/leaves'>Leaves</a> "
@   html "<a href='$baseurl/brlist'>Branches</a> "
@   html "<a href='$baseurl/taglist'>Tags</a> "
@ }
@ if {[hascap r]} {







|







 







|







186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
...
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
*/
const char zDefaultHeader[] = 
@ <html>
@ <head>
@ <title>$<project_name>: $<title></title>
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
@       href="$baseurl/timeline.rss">
@ <link rel="stylesheet" href="$baseurl/style.css?default" type="text/css"
@       media="screen">
@ </head>
@ <body>
@ <div class="header">
@   <div class="logo">
@     <img src="$baseurl/logo" alt="logo">
@     <br><nobr>$<project_name></nobr>
................................................................................
@ </div>
@ <div class="mainmenu"><th1>
@ html "<a href='$baseurl$index_page'>Home</a> "
@ if {[anycap jor]} {
@   html "<a href='$baseurl/timeline'>Timeline</a> "
@ }
@ if {[hascap oh]} {
@   html "<a href='$baseurl/dir?ci=tip'>Files</a> "
@ }
@ if {[hascap o]} {
@   html "<a href='$baseurl/leaves'>Leaves</a> "
@   html "<a href='$baseurl/brlist'>Branches</a> "
@   html "<a href='$baseurl/taglist'>Tags</a> "
@ }
@ if {[hascap r]} {

Changes to src/tag.c.

198
199
200
201
202
203
204





205
206
207
208
209
210
211
...
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
    case TAG_USER: {
      zCol = "euser";
      break;
    }
  }
  if( zCol ){
    db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid);





  }
  if( tagid==TAG_DATE ){
    db_multi_exec("UPDATE event SET mtime=julianday(%Q) WHERE objid=%d",
                  zValue, rid);
  }
  if( tagtype==0 || tagtype==2 ){
    tag_propagate(rid, tagid, tagtype, rid, zValue, mtime);
................................................................................

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

  style_header("Tagged Check-ins");
  style_submenu_element("List", "List", "taglist");
  login_anonymous_available();
  @ <h2>Check-ins with non-propagating tags:</t2>
  db_prepare(&q,
    "%s AND blob.rid IN (SELECT rid FROM tagxref"
    "                     WHERE tagtype=1 AND srcid>0"
    "                       AND tagid IN (SELECT tagid FROM tag "
    "                                      WHERE tagname GLOB 'sym-*'))"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www()







>
>
>
>
>







 







|







198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
...
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
    case TAG_USER: {
      zCol = "euser";
      break;
    }
  }
  if( zCol ){
    db_multi_exec("UPDATE event SET %s=%Q WHERE objid=%d", zCol, zValue, rid);
    if( tagid==TAG_COMMENT ){
      char *zCopy = mprintf("%s", zValue);
      wiki_extract_links(zCopy, rid, 0, mtime, 1, WIKI_INLINE);
      free(zCopy);
    }
  }
  if( tagid==TAG_DATE ){
    db_multi_exec("UPDATE event SET mtime=julianday(%Q) WHERE objid=%d",
                  zValue, rid);
  }
  if( tagtype==0 || tagtype==2 ){
    tag_propagate(rid, tagid, tagtype, rid, zValue, mtime);
................................................................................

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

  style_header("Tagged Check-ins");
  style_submenu_element("List", "List", "taglist");
  login_anonymous_available();
  @ <h2>Check-ins with non-propagating tags:</h2>
  db_prepare(&q,
    "%s AND blob.rid IN (SELECT rid FROM tagxref"
    "                     WHERE tagtype=1 AND srcid>0"
    "                       AND tagid IN (SELECT tagid FROM tag "
    "                                      WHERE tagname GLOB 'sym-*'))"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www()

Changes to src/timeline.c.

198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
...
239
240
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
...
288
289
290
291
292
293
294
295
296
297
298
299
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
...
356
357
358
359
360
361
362








363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
...
889
890
891
892
893
894
895



896
897
898
899
900
901
902
    wikiFlags = WIKI_INLINE | WIKI_NOBLOCK;
  }
  if( tmFlags & TIMELINE_GRAPH ){
    pGraph = graph_init();
    @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
  }

  db_multi_exec(
     "CREATE TEMP TABLE IF NOT EXISTS seen(rid INTEGER PRIMARY KEY);"
     "DELETE FROM seen;"
  );
  @ <table cellspacing=0 border=0 cellpadding=0>
  blob_zero(&comment);
  while( db_step(pQuery)==SQLITE_ROW ){
    int rid = db_column_int(pQuery, 0);
    const char *zUuid = db_column_text(pQuery, 1);
    int nPChild = db_column_int(pQuery, 5);
    int nParent = db_column_int(pQuery, 6);
................................................................................
      @ event%s(suppressCnt>1?"s":"") omitted.</i></small></tr>
      suppressCnt = 0;
    }
    if( strcmp(zType,"div")==0 ){
      @ <tr><td colspan=3><hr></td></tr>
      continue;
    }
    db_multi_exec("INSERT OR IGNORE INTO seen VALUES(%d)", rid);
    if( memcmp(zDate, zPrevDate, 10) ){
      sprintf(zPrevDate, "%.10s", zDate);
      @ <tr><td>
      @   <div class="divider"><nobr>%s(zPrevDate)</nobr></div>
      @ </td></tr>
    }
    memcpy(zTime, &zDate[11], 5);
    zTime[5] = 0;
    @ <tr>
    @ <td valign="top" align="right">%s(zTime)</td>
    @ <td width="20" align="left" valign="top">



























    @ <div id="m%d(rid)"></div>

    if( zBgClr && zBgClr[0] ){
      @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
    }else{
      @ <td valign="top" align="left">
    }
    if( zType[0]=='c' ){
      const char *azTag[5];
................................................................................
      }
      if( nTag>0 ){
        int i;
        for(i=0; i<nTag; i++){
          @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
        }
      }
      if( pGraph ){
        int nParent = 0;
        int aParent[32];
        const char *zBr;
        static Stmt qparent;
        static Stmt qbranch;
        db_static_prepare(&qparent,
          "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
        );
        db_static_prepare(&qbranch,
          "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
          TAG_BRANCH
        );
        db_bind_int(&qparent, ":rid", rid);
        while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
          aParent[nParent++] = db_column_int(&qparent, 0);
        }
        db_reset(&qparent);
        db_bind_int(&qbranch, ":rid", rid);
        if( db_step(&qbranch)==SQLITE_ROW ){
          zBr = db_column_text(&qbranch, 0);
        }else{
          zBr = "trunk";
        }
        graph_add_row(pGraph, rid, isLeaf, nParent, aParent, zBr);
        db_reset(&qbranch);
      }
    }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
      hyperlink_to_uuid(zUuid);
    }
    db_column_blob(pQuery, commentColumn, &comment);
    if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
      Blob truncated;
      blob_zero(&truncated);
................................................................................
      graph_free(pGraph);
      pGraph = 0;
    }else{
      @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
    }
  }
  @ </table>








  if( pGraph && pGraph->nErr==0 ){
    GraphRow *pRow;
    int i;
    char cSep;
    @ <script type="text/JavaScript">
    cgi_printf("var rowinfo = [\n");
    for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
      cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
        pRow->rid,
        pRow->iRail,
        pRow->bDescender,
        pRow->mergeOut,
        pRow->mergeUpto,
        pRow->aiRaiser[pRow->iRail]
      );
      cSep = '[';
................................................................................
    tagid = 0;
  }
  if( zType[0]=='a' ){
    tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH;
  }else{
    tmFlags = TIMELINE_GRAPH;
  }
  if( P("ng")!=0 ){
    tmFlags &= ~TIMELINE_GRAPH;
  }

  style_header("Timeline");
  login_anonymous_available();
  timeline_temp_table();
  blob_zero(&sql);
................................................................................
    if( zAfter ){
      blob_appendf(&desc, " occurring on or after %h.<br>", zAfter);
    }else if( zBefore ){
      blob_appendf(&desc, " occurring on or before %h.<br>", zBefore);
    }else if( zCirca ){
      blob_appendf(&desc, " occurring around %h.<br>", zCirca);
    }



    if( g.okHistory ){
      if( zAfter || n==nEntry ){
        zDate = db_text(0, "SELECT min(timestamp) FROM timeline");
        timeline_submenu(&url, "Older", "b", zDate, "a");
        free(zDate);
      }
      if( zBefore || (zAfter && n==nEntry) ){







<
<
<
<







 







<











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







 







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







 







>
>
>
>
>
>
>
>








|







 







|







 







>
>
>







198
199
200
201
202
203
204




205
206
207
208
209
210
211
...
235
236
237
238
239
240
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
...
311
312
313
314
315
316
317



























318
319
320
321
322
323
324
...
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
...
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
...
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
    wikiFlags = WIKI_INLINE | WIKI_NOBLOCK;
  }
  if( tmFlags & TIMELINE_GRAPH ){
    pGraph = graph_init();
    @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
  }





  @ <table cellspacing=0 border=0 cellpadding=0>
  blob_zero(&comment);
  while( db_step(pQuery)==SQLITE_ROW ){
    int rid = db_column_int(pQuery, 0);
    const char *zUuid = db_column_text(pQuery, 1);
    int nPChild = db_column_int(pQuery, 5);
    int nParent = db_column_int(pQuery, 6);
................................................................................
      @ event%s(suppressCnt>1?"s":"") omitted.</i></small></tr>
      suppressCnt = 0;
    }
    if( strcmp(zType,"div")==0 ){
      @ <tr><td colspan=3><hr></td></tr>
      continue;
    }

    if( memcmp(zDate, zPrevDate, 10) ){
      sprintf(zPrevDate, "%.10s", zDate);
      @ <tr><td>
      @   <div class="divider"><nobr>%s(zPrevDate)</nobr></div>
      @ </td></tr>
    }
    memcpy(zTime, &zDate[11], 5);
    zTime[5] = 0;
    @ <tr>
    @ <td valign="top" align="right">%s(zTime)</td>
    @ <td width="20" align="left" valign="top">
    if( pGraph && zType[0]=='c' ){
      int nParent = 0;
      int aParent[32];
      const char *zBr;
      int gidx;
      static Stmt qparent;
      static Stmt qbranch;
      db_static_prepare(&qparent,
        "SELECT pid FROM plink WHERE cid=:rid ORDER BY isprim DESC"
      );
      db_static_prepare(&qbranch,
        "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
        TAG_BRANCH
      );
      db_bind_int(&qparent, ":rid", rid);
      while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
        aParent[nParent++] = db_column_int(&qparent, 0);
      }
      db_reset(&qparent);
      db_bind_int(&qbranch, ":rid", rid);
      if( db_step(&qbranch)==SQLITE_ROW ){
        zBr = db_column_text(&qbranch, 0);
      }else{
        zBr = "trunk";
      }
      gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr);
      db_reset(&qbranch);
      @ <div id="m%d(gidx)"></div>
    }
    if( zBgClr && zBgClr[0] ){
      @ <td valign="top" align="left" bgcolor="%h(zBgClr)">
    }else{
      @ <td valign="top" align="left">
    }
    if( zType[0]=='c' ){
      const char *azTag[5];
................................................................................
      }
      if( nTag>0 ){
        int i;
        for(i=0; i<nTag; i++){
          @ <b>%s(azTag[i])%s(i==nTag-1?"":",")</b>
        }
      }



























    }else if( (tmFlags & TIMELINE_ARTID)!=0 ){
      hyperlink_to_uuid(zUuid);
    }
    db_column_blob(pQuery, commentColumn, &comment);
    if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
      Blob truncated;
      blob_zero(&truncated);
................................................................................
      graph_free(pGraph);
      pGraph = 0;
    }else{
      @ <tr><td><td><div style="width:%d(pGraph->mxRail*20+30)px;"></div>
    }
  }
  @ </table>
  timeline_output_graph_javascript(pGraph);
}

/*
** Generate all of the necessary javascript to generate a timeline
** graph.
*/
void timeline_output_graph_javascript(GraphContext *pGraph){
  if( pGraph && pGraph->nErr==0 ){
    GraphRow *pRow;
    int i;
    char cSep;
    @ <script type="text/JavaScript">
    cgi_printf("var rowinfo = [\n");
    for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
      cgi_printf("{id:\"m%d\",r:%d,d:%d,mo:%d,mu:%d,u:%d,au:",
        pRow->idx,
        pRow->iRail,
        pRow->bDescender,
        pRow->mergeOut,
        pRow->mergeUpto,
        pRow->aiRaiser[pRow->iRail]
      );
      cSep = '[';
................................................................................
    tagid = 0;
  }
  if( zType[0]=='a' ){
    tmFlags = TIMELINE_BRIEF | TIMELINE_GRAPH;
  }else{
    tmFlags = TIMELINE_GRAPH;
  }
  if( P("ng")!=0 || zSearch!=0 ){
    tmFlags &= ~TIMELINE_GRAPH;
  }

  style_header("Timeline");
  login_anonymous_available();
  timeline_temp_table();
  blob_zero(&sql);
................................................................................
    if( zAfter ){
      blob_appendf(&desc, " occurring on or after %h.<br>", zAfter);
    }else if( zBefore ){
      blob_appendf(&desc, " occurring on or before %h.<br>", zBefore);
    }else if( zCirca ){
      blob_appendf(&desc, " occurring around %h.<br>", zCirca);
    }
    if( zSearch ){
      blob_appendf(&desc, " matching \"%h\"", zSearch);
    }
    if( g.okHistory ){
      if( zAfter || n==nEntry ){
        zDate = db_text(0, "SELECT min(timestamp) FROM timeline");
        timeline_submenu(&url, "Older", "b", zDate, "a");
        free(zDate);
      }
      if( zBefore || (zAfter && n==nEntry) ){

Changes to src/tkt.c.

163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
...
222
223
224
225
226
227
228



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
...
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
...
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
...
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623

624


625
626
627
628








629
630
631
632



633

634
635
636


637
638
639
640
641
642




643








644





645
646


647
648
649
650
651
652
653
...
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
696
697
698
699
700
701
702
703
704
705
706
707
708

709
710

711
712
713
714
715
716
717
  const char *z;

  for(i=0; (z = cgi_parameter_name(i))!=0; i++){
    Th_Store(z, P(z));
  }
}

/*
** Rebuild all tickets named in the _pending_ticket table.
**
** This routine is called just prior to commit after new
** out-of-sequence ticket changes have been added.
*/
static int ticket_rebuild_at_commit(void){
  Stmt q;
  db_multi_exec(
    "DELETE FROM ticket WHERE tkt_uuid IN _pending_ticket"
  );
  db_prepare(&q, "SELECT uuid FROM _pending_ticket");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    ticket_rebuild_entry(zUuid);
  }
  db_multi_exec(
    "DELETE FROM _pending_ticket"
  );
  return 0;
}

/*
** Update an entry of the TICKET table according to the information
** in the control file given in p.  Attempt to create the appropriate
** TICKET table entry if createFlag is true.  If createFlag is false,
** that means we already know the entry exists and so we can save the
** work of trying to create it.
**
** Return TRUE if a new TICKET entry was created and FALSE if an
** existing entry was revised.
*/
int ticket_insert(const Manifest *p, int createFlag, int checkTime){
  Blob sql;
  Stmt q;
  int i;
  const char *zSep;
  int rc = 0;

  getAllTicketFields();
................................................................................
      if( fieldId(zName)<0 ) continue;
      blob_appendf(&sql,", %s=coalesce(%s,'') || %Q",
                   zName, zName, p->aField[i].zValue);
    }else{
      if( fieldId(zName)<0 ) continue;
      blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
    }



  }
  blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
                     p->zTicketUuid);
  db_prepare(&q, "%s", blob_str(&sql));
  db_bind_double(&q, ":mtime", p->rDate);
  db_step(&q);
  db_finalize(&q);
  if( checkTime && db_changes()==0 ){
    static int isInit = 0;
    if( !isInit ){
      db_multi_exec("CREATE TEMP TABLE _pending_ticket(uuid TEXT UNIQUE)");
      db_commit_hook(ticket_rebuild_at_commit, 1);
      isInit = 1;
    }
    db_multi_exec("INSERT OR IGNORE INTO _pending_ticket "
                  "VALUES(%Q)", p->zTicketUuid);
  }
  blob_reset(&sql);
  return rc;
}

/*
** Rebuild an entire entry in the TICKET table
*/
................................................................................
     "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, 0);
    manifest_ticket_event(rid, &manifest, createFlag, tagid);
    manifest_clear(&manifest);
    createFlag = 0;
  }
  db_finalize(&q);
}

................................................................................
** WEBPAGE: tktview
** URL:  tktview?name=UUID
**
** View a ticket.
*/
void tktview_page(void){
  const char *zScript;



  login_check_credentials();
  if( !g.okRdTkt ){ login_needed(); return; }
  if( g.okWrTkt || g.okApndTkt ){
    style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
        g.zTop, PD("name",""));
  }
  if( g.okHistory ){
    const char *zUuid = PD("name","");
    style_submenu_element("History", "History Of This Ticket", 
        "%s/tkthistory/%T", g.zTop, zUuid);
    style_submenu_element("Timeline", "Timeline Of This Ticket", 
        "%s/tkttimeline/%T", g.zTop, zUuid);


  }
  if( g.okNewTkt ){
    style_submenu_element("New Ticket", "Create a new ticket",
        "%s/tktnew", g.zTop);
  }





  style_header("View Ticket");
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromDb();
  zScript = ticket_viewpage_code();
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
  Th_Render(zScript);
  if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);



































  style_footer();
}

/*
** TH command:   append_field FIELD STRING
**
** FIELD is the name of a database column to which we might want
................................................................................
    }
  }
  return 0;
}

/*
** WEBPAGE: tkttimeline
** URL: /tkttimeline?name=TICKETUUID
**
** Show the change history for a single ticket in timeline format.
*/
void tkttimeline_page(void){
  Stmt q;
  char *zTitle;
  char *zSQL;
  const char *zUuid;

  int tagid;



  login_check_credentials();
  if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; }
  zUuid = PD("name","");








  style_submenu_element("History", "History",
    "%s/tkthistory/%s", g.zTop, zUuid);
  style_submenu_element("Status", "Status",
    "%s/info/%s", g.zTop, zUuid);



  zTitle = mprintf("Timeline Of Ticket %h", zUuid);

  style_header(zTitle);
  free(zTitle);



  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
    @ No such ticket: %h(zUuid)
    style_footer();
    return;
  }




  zSQL = mprintf("%s AND event.objid IN "








                 "  (SELECT rid FROM tagxref WHERE tagid=%d) "





                 "ORDER BY mtime DESC",
                 timeline_query_for_www(), tagid);


  db_prepare(&q, zSQL);
  free(zSQL);
  www_print_timeline(&q, TIMELINE_ARTID, 0);
  db_finalize(&q);
  style_footer();
}

................................................................................

  login_check_credentials();
  if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; }
  zUuid = PD("name","");
  zTitle = mprintf("History Of Ticket %h", zUuid);
  style_submenu_element("Status", "Status",
    "%s/info/%s", g.zTop, zUuid);


  style_submenu_element("Timeline", "Timeline",
    "%s/tkttimeline?name=%s", g.zTop, zUuid);
  style_header(zTitle);
  free(zTitle);

  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
    @ No such ticket: %h(zUuid)
    style_footer();
    return;
  }
  db_prepare(&q,

    "SELECT objid, uuid FROM event, blob"
    " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
    "   AND blob.rid=event.objid"





    " ORDER BY mtime DESC",
    tagid
  );
  while( db_step(&q)==SQLITE_ROW ){
    Blob content;
    Manifest m;


    int rid = db_column_int(&q, 0);
    const char *zChngUuid = db_column_text(&q, 1);


















    content_get(rid, &content);
    if( manifest_parse(&m, &content) && m.type==CFTYPE_TICKET ){
      char *zDate = db_text(0, "SELECT datetime(%.12f)", m.rDate);
      char zUuid[12];
      memcpy(zUuid, zChngUuid, 10);
      zUuid[10] = 0;
      @
      @ Ticket change
      @ [<a href="%s(g.zTop)/artifact/%T(zChngUuid)">%s(zUuid)</a>]</a>
      @ (rid %d(rid)) by
      hyperlink_to_user(m.zUser,zDate," on");
      hyperlink_to_date(zDate, ":");
      free(zDate);
      ticket_output_change_artifact(&m);

    }
    manifest_clear(&m);

  }
  db_finalize(&q);
  style_footer();
}

/*
** Return TRUE if the given BLOB contains a newline character.







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










|







 







>
>
>







<
<
<
<
<
<
<
<
<
<







 







|







 







>
>
>







<




>
>





>
>
>
>
>








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







 







|








>

>
>




>
>
>
>
>
>
>
>




>
>
>
|
>



>
>






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







 







>
>












>
|


>
>
>
>
>
|
|




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







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
...
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216










217
218
219
220
221
222
223
...
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
...
294
295
296
297
298
299
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
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
...
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
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
696
697
698
699
700
701
702
703
704
...
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
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
790
791
792
793
  const char *z;

  for(i=0; (z = cgi_parameter_name(i))!=0; i++){
    Th_Store(z, P(z));
  }
}























/*
** Update an entry of the TICKET table according to the information
** in the control file given in p.  Attempt to create the appropriate
** TICKET table entry if createFlag is true.  If createFlag is false,
** that means we already know the entry exists and so we can save the
** work of trying to create it.
**
** Return TRUE if a new TICKET entry was created and FALSE if an
** existing entry was revised.
*/
int ticket_insert(const Manifest *p, int createFlag, int rid){
  Blob sql;
  Stmt q;
  int i;
  const char *zSep;
  int rc = 0;

  getAllTicketFields();
................................................................................
      if( fieldId(zName)<0 ) continue;
      blob_appendf(&sql,", %s=coalesce(%s,'') || %Q",
                   zName, zName, p->aField[i].zValue);
    }else{
      if( fieldId(zName)<0 ) continue;
      blob_appendf(&sql,", %s=%Q", zName, p->aField[i].zValue);
    }
    if( rid>0 ){
      wiki_extract_links(p->aField[i].zValue, rid, 1, p->rDate, i==0, 0);
    }
  }
  blob_appendf(&sql, " WHERE tkt_uuid='%s' AND tkt_mtime<:mtime",
                     p->zTicketUuid);
  db_prepare(&q, "%s", blob_str(&sql));
  db_bind_double(&q, ":mtime", p->rDate);
  db_step(&q);
  db_finalize(&q);










  blob_reset(&sql);
  return rc;
}

/*
** Rebuild an entire entry in the TICKET table
*/
................................................................................
     "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);
}

................................................................................
** WEBPAGE: tktview
** URL:  tktview?name=UUID
**
** View a ticket.
*/
void tktview_page(void){
  const char *zScript;
  char *zFullName;
  const char *zUuid = PD("name","");

  login_check_credentials();
  if( !g.okRdTkt ){ login_needed(); return; }
  if( g.okWrTkt || g.okApndTkt ){
    style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
        g.zTop, PD("name",""));
  }
  if( g.okHistory ){

    style_submenu_element("History", "History Of This Ticket", 
        "%s/tkthistory/%T", g.zTop, zUuid);
    style_submenu_element("Timeline", "Timeline Of This Ticket", 
        "%s/tkttimeline/%T", g.zTop, zUuid);
    style_submenu_element("Check-ins", "Check-ins Of This Ticket", 
        "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
  }
  if( g.okNewTkt ){
    style_submenu_element("New Ticket", "Create a new ticket",
        "%s/tktnew", g.zTop);
  }
  if( g.okApndTkt && g.okAttach ){
    style_submenu_element("Attach", "Add An Attachment",
        "%s/attachadd?tkt=%T&from=%s/tktview%%3fname=%t",
        g.zTop, zUuid, g.zTop, zUuid);
  }
  style_header("View Ticket");
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromDb();
  zScript = ticket_viewpage_code();
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
  Th_Render(zScript);
  if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);

  zFullName = db_text(0, 
       "SELECT tkt_uuid FROM ticket"
       " WHERE tkt_uuid GLOB '%q*'", zUuid);
  if( zFullName ){
    int cnt = 0;
    Stmt q;
    db_prepare(&q,
       "SELECT datetime(mtime,'localtime'), filename, user"
       "  FROM attachment"
       " WHERE isLatest AND src!='' AND target=%Q"
       " ORDER BY mtime DESC",
       zFullName);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zDate = db_column_text(&q, 0);
      const char *zFile = db_column_text(&q, 1);
      const char *zUser = db_column_text(&q, 2);
      if( cnt==0 ){
        @ <hr><h2>Attachments:</h2>
        @ <ul>
      }
      cnt++;
      @ <li><a href="%s(g.zTop)/attachview?tkt=%s(zFullName)&file=%t(zFile)">
      @ %h(zFile)</a> add by %h(zUser) on
      hyperlink_to_date(zDate, ".");
      if( g.okWrTkt && g.okAttach ){
        @ [<a href="%s(g.zTop)/attachdelete?tkt=%s(zFullName)&file=%t(zFile)&from=%s(g.zTop)/tktview%%3fname=%s(zFullName)">delete</a>]
      }
    }
    if( cnt ){
      @ </ul>
    }
    db_finalize(&q);
  }
 
  style_footer();
}

/*
** TH command:   append_field FIELD STRING
**
** FIELD is the name of a database column to which we might want
................................................................................
    }
  }
  return 0;
}

/*
** WEBPAGE: tkttimeline
** URL: /tkttimeline?name=TICKETUUID&y=TYPE
**
** Show the change history for a single ticket in timeline format.
*/
void tkttimeline_page(void){
  Stmt q;
  char *zTitle;
  char *zSQL;
  const char *zUuid;
  char *zFullUuid;
  int tagid;
  char zGlobPattern[50];
  const char *zType;

  login_check_credentials();
  if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; }
  zUuid = PD("name","");
  zType = PD("y","a");
  if( zType[0]!='c' ){
    style_submenu_element("Check-ins", "Check-ins",
       "%s/tkttimeline?name=%T&y=ci", g.zTop, zUuid);
  }else{
    style_submenu_element("Timeline", "Timeline",
       "%s/tkttimeline?name=%T", g.zTop, zUuid);
  }
  style_submenu_element("History", "History",
    "%s/tkthistory/%s", g.zTop, zUuid);
  style_submenu_element("Status", "Status",
    "%s/info/%s", g.zTop, zUuid);
  if( zType[0]=='c' ){
    zTitle = mprintf("Check-Ins Associated With Ticket %h", zUuid);
  }else{
    zTitle = mprintf("Timeline Of Ticket %h", zUuid);
  }
  style_header(zTitle);
  free(zTitle);

  sqlite3_snprintf(6, zGlobPattern, "%s", zUuid);
  canonical16(zGlobPattern, strlen(zGlobPattern));
  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
    @ No such ticket: %h(zUuid)
    style_footer();
    return;
  }
  zFullUuid = db_text(0, "SELECT substr(tagname, 5) FROM tag WHERE tagid=%d",
                         tagid);
  if( zType[0]=='c' ){
    zSQL = mprintf(
         "%s AND event.objid IN "
         "   (SELECT srcid FROM backlink WHERE target GLOB '%.4s*' "
                                         "AND '%s' GLOB (target||'*')) "
         "ORDER BY mtime DESC",
         timeline_query_for_www(), zFullUuid, zFullUuid
    );
  }else{
    zSQL = mprintf(
         "%s AND event.objid IN "
         "  (SELECT rid FROM tagxref WHERE tagid=%d"
         "   UNION SELECT srcid FROM backlink"
                  " WHERE target GLOB '%.4s*'"
                  "   AND '%s' GLOB (target||'*')"
         "   UNION SELECT attachid FROM attachment"
                  " WHERE target=%Q) "
         "ORDER BY mtime DESC",
         timeline_query_for_www(), tagid, zFullUuid, zFullUuid, zFullUuid
    );
  }
  db_prepare(&q, zSQL);
  free(zSQL);
  www_print_timeline(&q, TIMELINE_ARTID, 0);
  db_finalize(&q);
  style_footer();
}

................................................................................

  login_check_credentials();
  if( !g.okHistory || !g.okRdTkt ){ login_needed(); return; }
  zUuid = PD("name","");
  zTitle = mprintf("History Of Ticket %h", zUuid);
  style_submenu_element("Status", "Status",
    "%s/info/%s", g.zTop, zUuid);
  style_submenu_element("Check-ins", "Check-ins",
    "%s/tkttimeline?name=%s?y=ci", g.zTop, zUuid);
  style_submenu_element("Timeline", "Timeline",
    "%s/tkttimeline?name=%s", g.zTop, zUuid);
  style_header(zTitle);
  free(zTitle);

  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
    @ No such ticket: %h(zUuid)
    style_footer();
    return;
  }
  db_prepare(&q,
    "SELECT datetime(mtime,'localtime'), objid, uuid, NULL, NULL, NULL"
    "  FROM event, blob"
    " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
    "   AND blob.rid=event.objid"
    " UNION "
    "SELECT datetime(mtime,'localtime'), attachid, uuid, src, filename, user"
    "  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;
    if( zFile!=0 ){
      const char *zSrc = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 5);
      if( zSrc==0 || zSrc[0]==0 ){
        @ 
        @ <p>Delete attachment "%h(zFile)"
      }else{
        @ 
        @ <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, ":");

        ticket_output_change_artifact(&m);
        @ </p>
      }
      manifest_clear(&m);
    }
  }
  db_finalize(&q);
  style_footer();
}

/*
** Return TRUE if the given BLOB contains a newline character.

Changes to src/wiki.c.

125
126
127
128
129
130
131


132
133
134
135
136
137
138
...
150
151
152
153
154
155
156
157

158
159
160
161
162
163
164
...
174
175
176
177
178
179
180
181

182
183
184
185
186
187
188
189
190





191
192
193
194
195
196
197
198
199
200
201
202
203
































204
205
206
207
208
209
210
...
513
514
515
516
517
518
519

520

521
522
523
524
525
526
527
...
536
537
538
539
540
541
542
543


544
545
546
547
548
549
550
551
552
...
602
603
604
605
606
607
608


609
610
611
612
613


614
615
616





617
618



619
620
621
622
623


624



625
626
627
628
629
630
631
  char *zTag;
  int rid = 0;
  int isSandbox;
  Blob wiki;
  Manifest m;
  const char *zPageName;
  char *zBody = mprintf("%s","<i>Empty Page</i>");



  login_check_credentials();
  if( !g.okRdWiki ){ login_needed(); return; }
  zPageName = P("name");
  if( zPageName==0 ){
    style_header("Wiki");
    @ <ul>
................................................................................
    @      to experiment.</li>
    if( g.okNewWiki ){
      @ <li>  Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li>
    }
    @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
    @      available on this server.</li>
	@ <li> <form method="GET" action="%s(g.zBaseURL)/wfind">
	@     Search wiki titles: <input type="text" name="title"/> &nbsp; <input type="submit" />

	@ </li>
    @ </ul>
    style_footer();
    return;
  }
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
................................................................................
    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 ){

        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);
    }





    if( rid && g.okApndWiki ){
      style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T",
           g.zTop, zPageName);
    }
    if( g.okHistory ){
      style_submenu_element("History", "History", "%s/whistory?name=%T",
           g.zTop, zPageName);
    }
  }
  style_header(zPageName);
  blob_init(&wiki, zBody, -1);
  wiki_convert(&wiki, 0, 0);
  blob_reset(&wiki);
































  if( !isSandbox ){
    manifest_clear(&m);
  }
  style_footer();
}

/*
................................................................................
static const char *zWikiPageName;

/*
** Function called to output extra text at the end of each line in
** a wiki history listing.
*/
static void wiki_history_extra(int rid){

  @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a>

}

/*
** WEBPAGE: whistory
** URL: /whistory?name=PAGENAME
**
** Show the complete change history for a single wiki page.
................................................................................
  zPageName = PD("name","");
  zTitle = mprintf("History Of %s", zPageName);
  style_header(zTitle);
  free(zTitle);

  zSQL = mprintf("%s AND event.objid IN "
                 "  (SELECT rid FROM tagxref WHERE tagid="
                       "(SELECT tagid FROM tag WHERE tagname='wiki-%q'))"


                 "ORDER BY mtime DESC",
                 timeline_query_for_www(), zPageName);
  db_prepare(&q, zSQL);
  free(zSQL);
  zWikiPageName = zPageName;
  www_print_timeline(&q, TIMELINE_ARTID, wiki_history_extra);
  db_finalize(&q);
  style_footer();
}
................................................................................
  @ %h(blob_str(&d))
  @ </pre>
  style_footer();
}

/*
** WEBPAGE: wcontent


**
** List all available wiki pages with date created and last modified.
*/
void wcontent_page(void){
  Stmt q;


  login_check_credentials();
  if( !g.okRdWiki ){ login_needed(); return; }
  style_header("Available Wiki Pages");





  @ <ul>
  db_prepare(&q, 



    "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'"
    " ORDER BY lower(tagname)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);


    @ <li><a href="%s(g.zBaseURL)/wiki?name=%T(zName)">%h(zName)</a></li>



  }
  db_finalize(&q);
  @ </ul>
  style_footer();
}

/*







>
>







 







|
>







 







|
>
|








>
>
>
>
>













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







 







>
|
>







 







|
>
>

|







 







>
>





>
>



>
>
>
>
>


>
>
>
|




>
>
|
>
>
>







125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
...
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
...
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
241
242
243
244
245
246
247
248
249
250
251
...
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
...
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
...
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
  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; }
  zPageName = P("name");
  if( zPageName==0 ){
    style_header("Wiki");
    @ <ul>
................................................................................
    @      to experiment.</li>
    if( g.okNewWiki ){
      @ <li>  Create a <a href="%s(g.zBaseURL)/wikinew">new wiki page</a>.</li>
    }
    @ <li> <a href="%s(g.zBaseURL)/wcontent">List of All Wiki Pages</a>
    @      available on this server.</li>
	@ <li> <form method="GET" action="%s(g.zBaseURL)/wfind">
	@     Search wiki titles: <input type="text" name="title"/>
        @  &nbsp; <input type="submit" />
	@ </li>
    @ </ul>
    style_footer();
    return;
  }
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
................................................................................
    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( 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);
    }
    if( rid && g.okApndWiki && g.okAttach ){
      style_submenu_element("Attach", "Add An Attachment",
           "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
           g.zTop, zPageName, g.zTop, zPageName);
    }
    if( rid && g.okApndWiki ){
      style_submenu_element("Append", "Add A Comment", "%s/wikiappend?name=%T",
           g.zTop, zPageName);
    }
    if( g.okHistory ){
      style_submenu_element("History", "History", "%s/whistory?name=%T",
           g.zTop, zPageName);
    }
  }
  style_header(zPageName);
  blob_init(&wiki, zBody, -1);
  wiki_convert(&wiki, 0, 0);
  blob_reset(&wiki);

  db_prepare(&q,
     "SELECT datetime(mtime,'localtime'), filename, user"
     "  FROM attachment"
     " WHERE isLatest AND src!='' AND target=%Q"
     " ORDER BY mtime DESC",
     zPageName);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDate = db_column_text(&q, 0);
    const char *zFile = db_column_text(&q, 1);
    const char *zUser = db_column_text(&q, 2);
    if( cnt==0 ){
      @ <hr><h2>Attachments:</h2>
      @ <ul>
    }
    cnt++;
    if( g.okHistory ){
      @ <li><a href="%s(g.zTop)/attachview?page=%s(zPageName)&file=%t(zFile)">
    }else{
      @ <li>
    }
    @ %h(zFile)</a> add by %h(zUser) on
    hyperlink_to_date(zDate, ".");
    if( g.okWrWiki && g.okAttach ){
      @ [<a href="%s(g.zTop)/attachdelete?page=%s(zPageName)&file=%t(zFile)&from=%s(g.zTop)/wiki%%3fname=%s(zPageName)">delete</a>]
    }
  }
  if( cnt ){
    @ </ul>
  }
  db_finalize(&q);
 
  if( !isSandbox ){
    manifest_clear(&m);
  }
  style_footer();
}

/*
................................................................................
static const char *zWikiPageName;

/*
** Function called to output extra text at the end of each line in
** a wiki history listing.
*/
static void wiki_history_extra(int rid){
  if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d", rid) ){
    @ <a href="%s(g.zTop)/wdiff?name=%t(zWikiPageName)&a=%d(rid)">[diff]</a>
  }
}

/*
** WEBPAGE: whistory
** URL: /whistory?name=PAGENAME
**
** Show the complete change history for a single wiki page.
................................................................................
  zPageName = PD("name","");
  zTitle = mprintf("History Of %s", zPageName);
  style_header(zTitle);
  free(zTitle);

  zSQL = mprintf("%s AND event.objid IN "
                 "  (SELECT rid FROM tagxref WHERE tagid="
                       "(SELECT tagid FROM tag WHERE tagname='wiki-%q')"
                 "   UNION SELECT attachid FROM attachment"
                          " WHERE target=%Q)"
                 "ORDER BY mtime DESC",
                 timeline_query_for_www(), zPageName, zPageName);
  db_prepare(&q, zSQL);
  free(zSQL);
  zWikiPageName = zPageName;
  www_print_timeline(&q, TIMELINE_ARTID, wiki_history_extra);
  db_finalize(&q);
  style_footer();
}
................................................................................
  @ %h(blob_str(&d))
  @ </pre>
  style_footer();
}

/*
** WEBPAGE: wcontent
**
**     all=1         Show deleted pages
**
** List all available wiki pages with date created and last modified.
*/
void wcontent_page(void){
  Stmt q;
  int showAll = P("all")!=0;

  login_check_credentials();
  if( !g.okRdWiki ){ login_needed(); return; }
  style_header("Available Wiki Pages");
  if( showAll ){
    style_submenu_element("Active", "Only Active Pages", "%s/wcontent", g.zTop);
  }else{
    style_submenu_element("All", "All", "%s/wcontent?all=1", g.zTop);
  }
  @ <ul>
  db_prepare(&q, 
    "SELECT"
    "  substr(tagname, 6),"
    "  (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC)"
    "  FROM tag WHERE tagname GLOB 'wiki-*'"
    " ORDER BY lower(tagname)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    int size = db_column_int(&q, 1);
    if( size>0 ){
      @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)">%h(zName)</a></li>
    }else if( showAll ){
      @ <li><a href="%s(g.zTop)/wiki?name=%T(zName)"><s>%h(zName)</s></a></li>
    }
  }
  db_finalize(&q);
  @ </ul>
  style_footer();
}

/*

Changes to src/wikiformat.c.

367
368
369
370
371
372
373













374
375
376
377
378
379
380
...
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
...
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
....
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
....
1445
1446
1447
1448
1449
1450
1451





















































































































































































  struct sStack {
    short iCode;                 /* Markup code */
    short allowWiki;             /* ALLOW_WIKI if wiki allowed before tag */
    const char *zId;             /* ID attribute or NULL */
  } *aStack;
};















/*
** z points to a "<" character.  Check to see if this is the start of
** a valid markup.  If it is, return the total number of characters in
** the markup including the initial "<" and the terminating ">".  If
** it is not well-formed markup, return 0.
*/
................................................................................

/*
** Parse only Wiki links, return everything else as TOKEN_RAW.
**
** z points to the start of a token.  Return the number of
** characters in that token. Write the token type into *pTokenType.
*/

static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='[' && (n = linkLength(z))>0 ){
    *pTokenType = TOKEN_LINK;
    return n;
  }
  *pTokenType = TOKEN_RAW;
................................................................................
** output its end tag if it is not a </div> tag.
*/
static void popStack(Renderer *p){
  if( p->nStack ){
    int iCode;
    p->nStack--;
    iCode = p->aStack[p->nStack].iCode;
    if( iCode!=MARKUP_DIV ){
      blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
    }
  }
}

/*
** Push a new markup value onto the stack.  Enlarge the stack
................................................................................
    renderer.state |= INLINE_MARKUP_ONLY;
  }
  if( flags & WIKI_INLINE ){
    renderer.wantAutoParagraph = 0;
  }else{
    renderer.wantAutoParagraph = 1;
  }
  if( db_get_int("wiki-use-html", 0) ){
    renderer.state |= WIKI_USE_HTML;
  }
  if( pOut ){
    renderer.pOut = pOut;
  }else{
    renderer.pOut = cgi_output_blob();
  }
................................................................................
  iStart = i+6;
  for(i=iStart; z[i] && (z[i]!='<' || strncmp(&z[i],"</title>",8)!=0); i++){}
  if( z[i]!='<' ) return 0;
  blob_init(pTitle, &z[iStart], i-iStart);
  blob_init(pTail, &z[i+8], -1);
  return 1;
}




























































































































































































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







 







<







 







|







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
...
631
632
633
634
635
636
637

638
639
640
641
642
643
644
...
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
....
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
....
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
  struct sStack {
    short iCode;                 /* Markup code */
    short allowWiki;             /* ALLOW_WIKI if wiki allowed before tag */
    const char *zId;             /* ID attribute or NULL */
  } *aStack;
};

/*
** Return TRUE if HTML should be used as the sole markup language for wiki.
**
** On first invocation, this routine consults the "wiki-use-html" setting.
** It caches the result for subsequent invocations, under the assumption
** that the setting will not change.
*/
static int wikiUsesHtml(void){
  static int r = -1;
  if( r<0 ) r = db_get_boolean("wiki-use-html", 0);
  return r;
}


/*
** z points to a "<" character.  Check to see if this is the start of
** a valid markup.  If it is, return the total number of characters in
** the markup including the initial "<" and the terminating ">".  If
** it is not well-formed markup, return 0.
*/
................................................................................

/*
** Parse only Wiki links, return everything else as TOKEN_RAW.
**
** z points to the start of a token.  Return the number of
** characters in that token. Write the token type into *pTokenType.
*/

static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='[' && (n = linkLength(z))>0 ){
    *pTokenType = TOKEN_LINK;
    return n;
  }
  *pTokenType = TOKEN_RAW;
................................................................................
** output its end tag if it is not a </div> tag.
*/
static void popStack(Renderer *p){
  if( p->nStack ){
    int iCode;
    p->nStack--;
    iCode = p->aStack[p->nStack].iCode;
    if( iCode!=MARKUP_DIV && p->pOut ){
      blob_appendf(p->pOut, "</%s>", aMarkup[iCode].zName);
    }
  }
}

/*
** Push a new markup value onto the stack.  Enlarge the stack
................................................................................
    renderer.state |= INLINE_MARKUP_ONLY;
  }
  if( flags & WIKI_INLINE ){
    renderer.wantAutoParagraph = 0;
  }else{
    renderer.wantAutoParagraph = 1;
  }
  if( wikiUsesHtml() ){
    renderer.state |= WIKI_USE_HTML;
  }
  if( pOut ){
    renderer.pOut = pOut;
  }else{
    renderer.pOut = cgi_output_blob();
  }
................................................................................
  iStart = i+6;
  for(i=iStart; z[i] && (z[i]!='<' || strncmp(&z[i],"</title>",8)!=0); i++){}
  if( z[i]!='<' ) return 0;
  blob_init(pTitle, &z[iStart], i-iStart);
  blob_init(pTail, &z[i+8], -1);
  return 1;
}

/*
** Parse text looking for wiki hyperlinks in one of the formats:
**
**       [target]
**       [target|...]
**
** Where "target" can be either an artifact ID prefix or a wiki page
** name.  For each such hyperlink found, add an entry to the
** backlink table.
*/
void wiki_extract_links(
  char *z,           /* The wiki text from which to extract links */
  int srcid,         /* srcid field for new BACKLINK table entries */
  int srctype,       /* srctype field for new BACKLINK table entries */
  double mtime,      /* mtime field for new BACKLINK table entries */
  int replaceFlag,   /* True first delete prior BACKLINK entries */
  int flags          /* wiki parsing flags */
){
  Renderer renderer;
  int tokenType;
  ParsedMarkup markup;
  int n;
  int inlineOnly;
  int wikiUseHtml = 0;

  memset(&renderer, 0, sizeof(renderer));
  renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
  if( flags & WIKI_NOBLOCK ){
    renderer.state |= INLINE_MARKUP_ONLY;
  }
  if( wikiUsesHtml() ){
    renderer.state |= WIKI_USE_HTML;
    wikiUseHtml = 1;
  }
  inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0;
  if( replaceFlag ){
    db_multi_exec("DELETE FROM backlink WHERE srctype=%d AND srcid=%d",
                  srctype, srcid);
  }

  while( z[0] ){
    if( wikiUseHtml ){
      n = nextRawToken(z, &renderer, &tokenType);
    }else{
      n = nextWikiToken(z, &renderer, &tokenType);
    }
    switch( tokenType ){
      case TOKEN_LINK: {
        char *zTarget;
        int i, c;
        char zLink[42];

        zTarget = &z[1];
        for(i=0; zTarget[i] && zTarget[i]!='|' && zTarget[i]!=']'; i++){}
        while(i>1 && zTarget[i-1]==' '){ i--; }
        c = zTarget[i];
        zTarget[i] = 0;
        if( is_valid_uuid(zTarget) ){
          memcpy(zLink, zTarget, i+1);
          canonical16(zLink, i);
          db_multi_exec(
             "REPLACE INTO backlink(target,srctype,srcid,mtime)"
             "VALUES(%Q,%d,%d,%g)", zLink, srctype, srcid, mtime
          );
        }
        zTarget[i] = c;
        break;
      }
      case TOKEN_MARKUP: {
        const char *zId;
        int iDiv;
        parseMarkup(&markup, z);

        /* Markup of the form </div id=ID> where there is a matching
        ** ID somewhere on the stack.  Exit the verbatim if were are in
        ** it.  Pop the stack up to the matching <div>.  Discard the
        ** </div>
        */
        if( markup.iCode==MARKUP_DIV && markup.endTag &&
             (zId = markupId(&markup))!=0 &&
             (iDiv = findTagWithId(&renderer, MARKUP_DIV, zId))>=0
        ){
          if( renderer.inVerbatim ){
            renderer.inVerbatim = 0;
            renderer.state = renderer.preVerbState;
          }
          while( renderer.nStack>iDiv+1 ) popStack(&renderer);
          if( renderer.aStack[iDiv].allowWiki ){
            renderer.state |= ALLOW_WIKI;
          }else{
            renderer.state &= ~ALLOW_WIKI;
          }
          renderer.nStack--;
        }else

        /* If within <verbatim id=ID> ignore everything other than
        ** </verbatim id=ID> and the </dev id=ID2> above.
        */
        if( renderer.inVerbatim ){
          if( endVerbatim(&renderer, &markup) ){
            renderer.inVerbatim = 0;
            renderer.state = renderer.preVerbState;
          }else{
            n = 1;
          }
        }else

        /* Render invalid markup literally.  The markup appears in the
        ** final output as plain text.
        */
        if( markup.iCode==MARKUP_INVALID ){
          n = 1;
        }else

        /* If the markup is not font-change markup ignore it if the
        ** font-change-only flag is set.
        */
        if( (markup.iType&MUTYPE_FONT)==0 &&
                            (renderer.state & FONT_MARKUP_ONLY)!=0 ){
          /* Do nothing */
        }else

        if( markup.iCode==MARKUP_NOWIKI ){
          if( markup.endTag ){
            renderer.state |= ALLOW_WIKI;
          }else{
            renderer.state &= ~ALLOW_WIKI;
          }
        }else

        /* Ignore block markup for in-line rendering.
        */
        if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
          /* Do nothing */
        }else

        /* Generate end-tags */
        if( markup.endTag ){
          popStackToTag(&renderer, markup.iCode);
        }else

        /* Push <div> markup onto the stack together with the id=ID attribute.
        */
        if( markup.iCode==MARKUP_DIV ){
          pushStackWithId(&renderer, markup.iCode, markupId(&markup),
                          (renderer.state & ALLOW_WIKI)!=0);
        }else

        /* Enter <verbatim> processing.  With verbatim enabled, all other
        ** markup other than the corresponding end-tag with the same ID is
        ** ignored.
        */
        if( markup.iCode==MARKUP_VERBATIM ){
          int vAttrIdx, vAttrDidAppend=0;
          renderer.zVerbatimId = 0;
          renderer.inVerbatim = 1;
          renderer.preVerbState = renderer.state;
          renderer.state &= ~ALLOW_WIKI;
          for (vAttrIdx = 0; vAttrIdx < markup.nAttr; vAttrIdx++){
            if( markup.aAttr[vAttrIdx].iACode == ATTR_ID ){
              renderer.zVerbatimId = markup.aAttr[0].zValue;
            }else if( markup.aAttr[vAttrIdx].iACode == ATTR_TYPE ){
              vAttrDidAppend=1;
            }
          }
          renderer.wantAutoParagraph = 0;
        }

        /* Restore the input text to its original configuration
        */
        unparseMarkup(&markup);
        break;
      }
      default: {
        break;
      }
    }
    z += n;
  }
}

Changes to src/winhttp.c.

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

/*
** Start a listening socket and process incoming HTTP requests on
** that socket.
*/
void win32_http_server(
  int mnPort, int mxPort,   /* Range of allowed TCP port numbers */
  char *zBrowser,           /* Command to launch browser.  (Or NULL) */
  char *zStopper,           /* Stop server when this file is exists (Or NULL) */
  char *zNotFound           /* The --notfound option, or NULL */
){
  WSADATA wd;
  SOCKET s = INVALID_SOCKET;
  SOCKADDR_IN addr;
  int idCnt = 0;
  int iPort = mnPort;
  char *zNotFoundOption;







|
|
|







135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151

/*
** Start a listening socket and process incoming HTTP requests on
** that socket.
*/
void win32_http_server(
  int mnPort, int mxPort,   /* Range of allowed TCP port numbers */
  const char *zBrowser,     /* Command to launch browser.  (Or NULL) */
  const char *zStopper,     /* Stop server when this file is exists (Or NULL) */
  const char *zNotFound     /* The --notfound option, or NULL */
){
  WSADATA wd;
  SOCKET s = INVALID_SOCKET;
  SOCKADDR_IN addr;
  int idCnt = 0;
  int iPort = mnPort;
  char *zNotFoundOption;

Changes to src/xfer.c.

516
517
518
519
520
521
522

523
524
525
526
527
528
529
...
534
535
536
537
538
539
540

541
542
543
544
545
546
547
....
1062
1063
1064
1065
1066
1067
1068

1069
1070
1071
1072
1073
1074
1075
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  db_prepare(&q, 
    "SELECT uuid FROM unclustered JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"

  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
    cnt++;
  }
  db_finalize(&q);
  return cnt;
................................................................................
*/
static void send_all(Xfer *pXfer){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM blob "
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"

  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
  }
  db_finalize(&q);
}

................................................................................
            blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
            xfer.nFileSent, xfer.nDeltaSent);
    nCardSent = 0;
    nCardRcvd = 0;
    xfer.nFileSent = 0;
    xfer.nDeltaSent = 0;
    xfer.nGimmeSent = 0;

    fflush(stdout);
    http_exchange(&send, &recv, cloneFlag==0 || nCycle>0);
    blob_reset(&send);

    /* Begin constructing the next message (which might never be
    ** sent) by beginning with the pull or push cards
    */







>







 







>







 







>







516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
...
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
....
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  db_prepare(&q, 
    "SELECT uuid FROM unclustered JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
    cnt++;
  }
  db_finalize(&q);
  return cnt;
................................................................................
*/
static void send_all(Xfer *pXfer){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM blob "
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
  }
  db_finalize(&q);
}

................................................................................
            blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
            xfer.nFileSent, xfer.nDeltaSent);
    nCardSent = 0;
    nCardRcvd = 0;
    xfer.nFileSent = 0;
    xfer.nDeltaSent = 0;
    xfer.nGimmeSent = 0;
    xfer.nIGotSent = 0;
    fflush(stdout);
    http_exchange(&send, &recv, cloneFlag==0 || nCycle>0);
    blob_reset(&send);

    /* Begin constructing the next message (which might never be
    ** sent) by beginning with the pull or push cards
    */

Changes to src/zip.c.

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
    blob_reset(&file);
  }
  blob_reset(&filename);
  zip_close(pZip);
}

/*
** COMMAND: test-baseline-zip
**


** Generate a ZIP archive for a specified baseline.




*/
void baseline_zip_cmd(void){
  int rid;
  Blob zip;



  if( g.argc!=4 ){
    usage("UUID ZIPFILE");

  }
  db_must_be_within_tree();
  rid = name_to_rid(g.argv[2]);











  zip_of_baseline(rid, &zip, g.argv[2]);
  blob_write_to_file(&zip, g.argv[3]);
}

/*
** WEBPAGE: zip
** URL: /zip/RID.zip
**







|

>
>
|
>
>
>
>




>
>
>

<
>

<

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







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
412
413
414
415
416
417
418
419
420
421
    blob_reset(&file);
  }
  blob_reset(&filename);
  zip_close(pZip);
}

/*
** COMMAND: zip
**
** Usage: %fossil zip VERSION OUTPUTFILE [--name DIRECTORYNAME]
**
** Generate a ZIP archive for a specified version.  If the --name option is
** used, it argument becomes the name of the top-level directory in the
** resulting ZIP archive.  If --name is omitted, the top-level directory
** named is derived from the project name, the check-in date and time, and
** the artifact ID of the check-in.
*/
void baseline_zip_cmd(void){
  int rid;
  Blob zip;
  const char *zName;
  zName = find_option("name", 0, 1);
  db_find_and_open_repository(1);
  if( g.argc!=4 ){

    usage("VERSION OUTPUTFILE");
  }

  rid = name_to_rid(g.argv[2]);
  if( zName==0 ){
    zName = db_text("default-name",
       "SELECT replace(%Q,' ','_') "
          " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  zip_of_baseline(rid, &zip, zName);
  blob_write_to_file(&zip, g.argv[3]);
}

/*
** WEBPAGE: zip
** URL: /zip/RID.zip
**

Changes to www/branching.wiki.

Added www/build-icons/linux.gif.

cannot compute difference between binary files

Added www/build-icons/linux64.gif.

cannot compute difference between binary files

Added www/build-icons/mac.gif.

cannot compute difference between binary files

Added www/build-icons/src.gif.

cannot compute difference between binary files

Added www/build-icons/win32.gif.

cannot compute difference between binary files

Changes to www/build.wiki.

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
..
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<a href="http://www.fossil-scm.org/fossil/leaves">Leaves</a> link at
the top of the page.</p></li>

<li><p>Select a version of of fossil you want to download.  Click on its
link.  Note that you must successfully log in as "anonymous" in step 1
above in order to see the link to the detailed version information.</p></li>

<li><p>On the version information page, click on the "[details]" hyperlink
that appears right after the check-in comment at the top of the page.</p>

<li><p>Finally, click on the
"Zip Archive" link.  This link will build a ZIP archive of the 
complete source code and download it to your browser.
</ol>

<h2>2.0 Compiling</h2>

................................................................................
<li value="6">
<p>Create a directory to hold the source code.  Then unzip the
ZIP archive you downloaded into that directory.  You should be
in the top-level folder of that directory</p></li>

<li><p><b>(Optional:)</b>
Edit the Makefile to set it up like you want.  You probably do not
need to do anything.  Do not be intimidated:  There are only 5
variables in the makefile that can be changed.  The whole Makefile
is only a few dozen lines long and most of those lines are comments.</p>

<li><p>Type "<b>make</b>"
</ol>

<h2>3.0 Installing</h2>







<
<
<







 







|







32
33
34
35
36
37
38



39
40
41
42
43
44
45
..
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<a href="http://www.fossil-scm.org/fossil/leaves">Leaves</a> link at
the top of the page.</p></li>

<li><p>Select a version of of fossil you want to download.  Click on its
link.  Note that you must successfully log in as "anonymous" in step 1
above in order to see the link to the detailed version information.</p></li>




<li><p>Finally, click on the
"Zip Archive" link.  This link will build a ZIP archive of the 
complete source code and download it to your browser.
</ol>

<h2>2.0 Compiling</h2>

................................................................................
<li value="6">
<p>Create a directory to hold the source code.  Then unzip the
ZIP archive you downloaded into that directory.  You should be
in the top-level folder of that directory</p></li>

<li><p><b>(Optional:)</b>
Edit the Makefile to set it up like you want.  You probably do not
need to do anything.  Do not be intimidated:  There are less than 10
variables in the makefile that can be changed.  The whole Makefile
is only a few dozen lines long and most of those lines are comments.</p>

<li><p>Type "<b>make</b>"
</ol>

<h2>3.0 Installing</h2>

Changes to www/embeddeddoc.wiki.

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
...
108
109
110
111
112
113
114
115
116
117

118















119
120
121
122
123
124
125
126
127
128
129
130
131
pull the documentation file from the local source tree on disk, not
from the any check-in.  The "<b>ckout</b>" keyword normally
only works when you start your server using the "<b>fossil server</b>"
or "<b>fossil ui</b>"
command line and is intended to show what the documentation you are currently
editing looks like before you check it in.

Finally, the <i>&lt;filename&gt;</i> element of the URL is the full
pathname of the documentation file starting from the root of the source
tree.

The mimetype (and thus the rendering) of documentation files is 
determined by the file suffix.  Fossil currently understands 192
different file suffixes, including all the popular ones such as
".css", ".gif", ".htm", ".html", ".jpg", ".jpeg", ".png", and ".txt".

................................................................................
recently.  If you wanted to see an historical version of this document,
you could substitute the name of a check-in for "<b>/tip/</b>".
For example, to see the version of this document associated with
check-in [9be1b00392], simply replace the "<b>/tip/</b>" with
"<b>/9be1b00392/</b>".  You can also substitute the symbolic name
for a particular version or branch.  For example, you might
replace "<b>/tip/</b>" with "<b>/trunk/</b>" to get the latest
version of this document in the "trunk" branch.  (As of this writing,
the self-hosting fossil repository only has a single branch "trunk" and
so "trunk" and "tip" amount to the same thing, but they would be different

in a project with multiple branches.)
















The file that encodes this document is stored in the fossil source tree under
the name "<b>www/embeddeddoc.wiki</b>" and so that name forms the
last part of the URL for this document.

As I sit writing this documentation file, I am testing my work by
running the "<b>fossil server</b>" command line and viewing
<b>http://localhost:8080/doc/ckout/www/embeddeddoc.wiki</b> in
Firefox.  I am doing this even though I have not yet checked in
the "<b>www/embeddeddoc.wiki</b>" file for the first time.  Using
the special "<b>ckout</b>" version identifier on the "<b>/doc</b>" page
it is easy to make multiple changes to multiple files and see how they all
look together before committing anything to the repository.







|
|







 







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













56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
...
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
141
142
143
144
145
146
pull the documentation file from the local source tree on disk, not
from the any check-in.  The "<b>ckout</b>" keyword normally
only works when you start your server using the "<b>fossil server</b>"
or "<b>fossil ui</b>"
command line and is intended to show what the documentation you are currently
editing looks like before you check it in.

Finally, the <i>&lt;filename&gt;</i> element of the URL is the
pathname of the documentation file relative to the root of the source
tree.

The mimetype (and thus the rendering) of documentation files is 
determined by the file suffix.  Fossil currently understands 192
different file suffixes, including all the popular ones such as
".css", ".gif", ".htm", ".html", ".jpg", ".jpeg", ".png", and ".txt".

................................................................................
recently.  If you wanted to see an historical version of this document,
you could substitute the name of a check-in for "<b>/tip/</b>".
For example, to see the version of this document associated with
check-in [9be1b00392], simply replace the "<b>/tip/</b>" with
"<b>/9be1b00392/</b>".  You can also substitute the symbolic name
for a particular version or branch.  For example, you might
replace "<b>/tip/</b>" with "<b>/trunk/</b>" to get the latest
version of this document in the "trunk" branch.  The symbolic name
can also be a date and time string in any of the following formats:</p>


<ul>
<li> <i>YYYY-MM-DD</i>
<li> <i>YYYY-MM-DD</i><b>T</b><i>HH:MM</i>
<li> <i>YYYY-MM-DD</i><b>T</b><i>HH:MM:SS</i>
</ul>

When the symbolic name is a date and time, fossil shows the version 
of the document that was most recently checked in as of the date
and time specified.  So, for example, to see what the fossil website
looked like at the beginning of 2010, enter:

<blockquote>
<a href="http://www.fossil-scm.org/index.html/doc/2010-01-01/www/index.wiki">
http://www.fossil-scm.org/index.html/doc/<b>2010-01-01</b>/www/index.wiki
</a>
</blockquote>

The file that encodes this document is stored in the fossil source tree under
the name "<b>www/embeddeddoc.wiki</b>" and so that name forms the
last part of the URL for this document.

As I sit writing this documentation file, I am testing my work by
running the "<b>fossil server</b>" command line and viewing
<b>http://localhost:8080/doc/ckout/www/embeddeddoc.wiki</b> in
Firefox.  I am doing this even though I have not yet checked in
the "<b>www/embeddeddoc.wiki</b>" file for the first time.  Using
the special "<b>ckout</b>" version identifier on the "<b>/doc</b>" page
it is easy to make multiple changes to multiple files and see how they all
look together before committing anything to the repository.

Changes to www/fileformat.wiki.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
44
45
46
47

48
49
50
51

52

53
54
55
56
57
58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
141
142
143
144
145
146
147
148
149
...
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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
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
269
270
271
272
273
274

275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293

294
295
296
297
298
299
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
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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402

















































403






































































































































<title>Fossil File Formats</title>
<h1 align="center">
Fossil File Formats
</h1>

<p>The global state of a fossil repository is kept simple so that it can
endure in useful form for decades or centuries. 
A fossil repository is intended to be readable,
searchable, and extensible by people not yet born.</p>

<p>
The global state of a fossil repository is an unordered
set of <i>artifacts</i>.
An artifact might be a source code file, the text of a wiki page,
part of a trouble ticket, or one of several special control artifacts
used to show the relationships between other artifacts within the
project.  Each artifact is normally represented on disk as a separate
file.  Artifacts can be text or binary.
</p>

<p>
In addition to the global state, 
each fossil repository also contains local state.
The local state consists of web-page formatting
preferences, authorized users, ticket display and reporting formats,
and so forth.  The global state is shared in common among all
repositories for the same project, whereas the local state is often
different in separate repositories.
The local state is not versioned and is not synchronized
with the global state.
The local state is not composed of artifacts and is not intended to be enduring.
This document is concerned with global state only.  Local state is only
mentioned here in order to distinguish it from global state.
</p>

<p>
Each artifact in the repository is named by its SHA1 hash.
No prefixes or meta information is added to a artifact before
its hash is computed.  The name of a artifact in the repository
is exactly the same SHA1 hash that is computed by sha1sum 
on the file as it exists in your source tree.</p>

<p>
Some artifacts have a particular format which gives them special
meaning to fossil.  Fossil recognizes:</p>

<ul>

<li> Manifests </li>
<li> Clusters </li>
<li> Control Artifacts </li>
<li> Wiki Pages </li>

<li> Ticket Changes </li>

</ul>

<p>These five artifact types are described in the sequel.</p>

<p>In the current implementation (as of 2009-01-25) the artifacts that
make up a fossil repository are stored in in as delta- and zlib-compressed
blobs in an <a href="http://www.sqlite.org/">SQLite</a> database.  This
is an implementation detail and might change in a future release.  For
the purpose of this article "file format" means the format of the artifacts,
not how the artifacts are stored on disk.  It is the artifact format that
is intended to be enduring.  The specifics of how artifacts are stored on
disk, though stable, is not intended to live as long as the
artifact format.</p>


<h2>1.0 The Manifest</h2>

<p>A manifest defines a check-in or version of the project
source tree.  The manifest contains a list of artifacts for
each file in the project and the corresponding filenames, as
well as information such as parent check-ins, the name of the
programmer who created the check-in, the date and time when
the check-in was created, and any check-in comments associated
with the check-in.</p>

<p>
Any artifact in the repository that follows the syntactic rules
of a manifest is a manifest.  Note that a manifest can
be both a real manifest and also a content file, though this
is rare.
</p>

<p>
A manifest is a text file.  Newline characters
(ASCII 0x0a) separate the file into "cards".
Each card begins with a single
character "card type".  Zero or more arguments may follow
the card type.  All arguments are separated from each other
and from the card-type character by a single space
character.  There is no surplus white space between arguments
and no leading or trailing whitespace except for the newline 
character that acts as the card separator.
</p>

<p>
All cards of the manifest occur in strict sorted lexicographical order.
No card may be duplicated.
The entire manifest may be PGP clear-signed, but otherwise it
may contain no additional text or data beyond what is described here.
</p>

<p>
Allowed cards in the manifest are as follows:
</p>

<blockquote>
<b>C</b> <i>checkin-comment</i><br>
<b>D</b> <i>time-and-date-stamp</i><br>
<b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br>
<b>P</b> <i>SHA1-hash</i>+<br>
<b>R</b> <i>repository-checksum</i><br>
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name  <b>*</b> ?value?</i><br>
<b>U</b> <i>user-login</i><br>
<b>Z</b> <i>manifest-checksum</i>
</blockquote>

<p>
A manifest must have exactly one C-card.  The sole argument to
the C-card is a check-in comment that describes the check-in that
the manifest defines.  The check-in comment is text.  The following
escape sequences are applied to the text:
A space (ASCII 0x20) is represented as "\s" (ASCII 0x5C, 0x73).  A
newline (ASCII 0x0a) is "\n" (ASCII 0x6C, x6E).  A backslash 
(ASCII 0x5C) is represented as two backslashes "\\".  Apart from
space and newline, no other whitespace characters are allowed in
the check-in comment.  Nor are any unprintable characters allowed
in the comment.
</p>

<p>
A manifest must have exactly one D-card.  The sole argument to
the D-card is a date-time stamp in the ISO8601 format.  The
date and time should be in coordinated universal time (UTC).
The format is:
</p>

<blockquote>
<i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i>
</blockquote>

<p>
A manifest has zero or more F-cards.  Each F-card defines a file
(other than the manifest itself) which is part of the check-in that
the manifest defines.  There are two, three, or four arguments.
The first argument
is the pathname of the file in the check-in relative to the root
of the project file hierarchy.  No ".." or "." directories are allowed
within the filename.  Space characters are escaped as in C-card
................................................................................
permissions associated with the file.  The only special code currently
defined is "x" which means that the file is executable.  All files are
always readable and writable.  This can be expressed by "w" permission
if desired but is optional.
The optional 4th argument is the name of the same file as it existed in
the parent check-in.  If the name of the file is unchanged from its
parent, then the 4th argument is omitted.
</p>

<p>
A manifest has zero or one P-cards.  Most manifests have one P-card.
The P-card has a varying number of arguments that
defines other manifests from which the current manifest
is derived.  Each argument is an 40-character lowercase 
hexadecimal SHA1 of the predecessor manifest.  All arguments
to the P-card must be unique to that line.
The first predecessor is the direct ancestor of the manifest.
Other arguments define manifests with which the first was
merged to yield the current manifest.  Most manifests have
a P-card with a single argument.  The first manifest in the
project has no ancestors and thus has no P-card.
</p>

<p>
A manifest may optionally have a single R-card.  The R-card has
a single argument which is the MD5 checksum of all files in 
the check-in except the manifest itself.  The checksum is expressed
as 32-characters of lowercase hexadecimal.   The checksum is
computed as follows:  For each file in the check-in (except for
the manifest itself) in strict sorted lexicographical order, 
take the pathname of the file relative to the root of the
repository, append a single space (ASCII 0x20), the
size of the file in ASCII decimal, a single newline
character (ASCII 0x0A), and the complete text of the file.
Compute the MD5 checksum of the the result.
</p>

<p>
A manifest might contain one or more T-cards used to set tags or
properties on the check-in.  The format of the T-card is the same as
described in <i>Control Artifacts</i> section below, except that the
second argument is the single characcter "<b>*</b>" instead of an
artifact ID.  The <b>*</b> in place of the artifact ID indicates that
the tag or property applies to the current artifact.  It is not
possible to encode the current artifact ID as part of an artifact,
since the act of inserting the artifact ID would change the artifact ID,
hence a <b>*</b> is used to represent "self".  T-cards are typically
added to manifests in order to set the <b>branch</b> property and a
symbolic name when the check-in is intended to start a new branch.
</p>

<p>
Each manifest has a single U-card.  The argument to the U-card is
the login of the user who created the manifest.  The login name
is encoded using the same character escapes as is used for the
check-in comment argument to the C-card.
</p>

<p>
A manifest has an option Z-card as its last line.  The argument
to the Z-card is a 32-character lowercase hexadecimal MD5 hash
of all prior lines of the manifest up to and including the newline 
character that immediately precedes the "Z".  The Z-card is just
a sanity check to prove that the manifest is well-formed and
consistent.
</p>

<p>A sample manifest from Fossil itself can be seen
[/artifact/28987096ac | here].


<h2>2.0 Clusters</h2>

<p>
A cluster is a artifact that declares the existence of other artifacts.
Clusters are used during repository synchronization to help 
reduce network traffic.  As such, clusters are an optimization and
may be removed from a repository without loss or damage to the
underlying project code.
</p>

<p>
Clusters follow a syntax that is very similar to manifests.
A Cluster is a line-oriented text file.  Newline characters
(ASCII 0x0a) separate the artifact into cards.  Each card begins with a single
character "card type".  Zero or more arguments may follow
the card type.  All arguments are separated from each other
and from the card-type character by a single space
character.  There is no surplus white space between arguments
................................................................................
and no leading or trailing whitespace except for the newline 
character that acts as the card separator.
All cards of a cluster occur in strict sorted lexicographical order.
No card may be duplicated.
The cluster may not contain additional text or data beyond 
what is described here.
Unlike manifests, clusters are never PGP signed.
</p>

<p>
Allowed cards in the cluster are as follows:
</p>

<blockquote>
<b>M</b> <i>artifact-id</i><br />
<b>Z</b> <i>checksum</i>
</blockquote>

<p>
A cluster contains one or more "M" cards followed by a single "Z"
line.  Each M card has a single argument which is the artifact ID of 
another artifact in the repository.  The Z card work exactly like
the Z card of a manifest.  The argument to the Z card is the
lower-case hexadecimal representation of the MD5 checksum of all
prior cards in the cluster.  Note that the Z card is required
on a cluster.
</p>

<p>An example cluster from Fossil can be seen
[/artifact/d03dbdd73a2a8 | here].</p>



<h2>3.0 Control Artifacts</h2>

<p>
Control artifacts are used to assign properties to other artifacts
within the repository.  The basic format of a control artifact is
the same as a manifest or cluster.  A control artifact is a text
files divided into cards by newline characters.  Each card has a
single-character card type followed by arguments.  Spaces separate
the card type and the arguments.  No surplus whitespace is allowed.
All cards must occur in strict lexigraphical order.
</p>

<p>
Allowed cards in a control artifact are as follows:
</p>

<blockquote>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name  artifact-id  ?value?</i><br />

<b>Z</b> <i>checksum</i><br />
</blockquote>

<p>
A control artifact must have one D card and one Z card and
one or more T cards.  No other cards or other text is
allowed in a control artifact.  Control artifacts might be PGP
clearsigned.</p>

<p>The D card and the Z card of a control artifact are the same
as in a manifest.</p>

<p>The T card represents a "tag" or property that is applied to
some other artifact.  The T card has two or three values.  The
second argument is the 40 character lowercase artifact ID of the artifact
to which the tag is to be applied. The
first value is the tag name.  The first character of the tag
is either "+", "-", or "*".  A "+" means the tag should be added
to the artifact.  The "-" means the tag should be removed.
The "*" character means the tag should be added to the artifact
and all direct descendants (but not branches) of the artifact down
to but not including the first descendant that contains a 
more recent "-" tag with the same name.
The optional third argument is the value of the tag.  A tag
without a value is a boolean.</p>

<p>When two or more tags with the same name are applied to the
same artifact, the tag with the latest (most recent) date is
used.</p>

<p>Some tags have special meaning.  The "comment" tag when applied
to a check-in will override the check-in comment of that check-in
for display purposes.</p>










<a name="wikichng"></a>
<h2>4.0 Wiki Pages</h2>

<p>A wiki page is an artifact with a format similar to manifests,
clusters, and control artifacts.  The artifact is divided into
cards by newline characters.  The format of each card is as in
manifests, clusters, and control artifacts.  Wiki artifacts accept
the following card types:</p>

<blockquote>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>L</b> <i>wiki-title</i><br />
<b>P</b> <i>parent-artifact-id</i>+<br />
<b>U</b> <i>user-name</i><br />
<b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br />
<b>Z</b> <i>checksum</i>
</blockquote>

<p>The D card is the date and time when the wiki page was edited.
The P card specifies the parent wiki pages, if any.  The L card
gives the name of the wiki page.  The U card specifies the login
of the user who made this edit to the wiki page.  The Z card is
the usual checksum over the either artifact.</p>

<p>The W card is used to specify the text of the wiki page.  The
argument to the W card is an integer which is the number of bytes
of text in the wiki page.  That text follows the newline character
that terminates the W card.  The wiki text is always followed by one
extra newline.</p>




<a name="tktchng"></a>
<h2>5.0 Ticket Changes</h2>

<p>A ticket-change artifact represents a change to a trouble ticket.
The following cards are allowed on a ticket change artifact:</p>

<blockquote>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>J</b> ?<b>+</b>?<i>name</i> ?<i>value</i>?<br />
<b>K</b> <i>ticket-id</i><br />
<b>U</b> <i>user-name</i><br />
<b>Z</b> <i>checksum</i>
</blockquote>

<p>
The D card is the usual date and time stamp and represents the point
in time when the change was entered.  The U card is the login of the
programmer who entered this change.  The Z card is the checksum over
the entire artifact.</p>

<p>
Every ticket has a unique ID.  The ticket to which this change is applied
is specified by the K card.  A ticket exists if it contains one or
more changes.  The first "change" to a ticket is what brings the
ticket into existence.</p>

<p>
J cards specify changes to the "value" of "fields" in the ticket.
If the <i>value</i> parameter of the J card is omitted, then the
field is set to an empty string.
Each fossil server has a ticket configuration which specifies the fields its
understands.  The ticket configuration is part of the local state for
the repository and thus can vary from one repository to another.
Hence a J card might specify a <i>field</i> that do not exist in the 
local ticket configuration.  If a J card specifies a <i>field</i> that
is not in the local configuration, then that J card
is simply ignored.</p>

<p>
The first argument of the J card is the field name.  The second
value is the field value.  If the field name begins with "+" then
the value is appended to the prior value.  Otherwise, the value
on the J card replaces any previous value of the field.
The field name and value are both encoded using the character
escapes defined for the C card of a manifest.

















































</p>











































































































































|


|

<







<

<












<

<






<

|


>
|
<
|
<
>
|
>


|

|







|

>


|





|

<




<

<









<

<




<

<

<












<










<

<




<





<







 







<

<











<

<











<

<











<

<




<

<






<

|


>


<





<

<







 







<

<

<






<







<

|
|

<
>


<







<

<

<




>



<



|

|
|

|











|

|

|

|

|
>
>
>
>
>
>
>
>
>




|



|










|



|

|



|
>
>
>




|
|









<



|

<



|

<









|

<






>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10

11
12
13
14
15
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

44

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72

73
74
75
76

77

78
79
80
81
82
83
84
85
86

87

88
89
90
91

92

93

94
95
96
97
98
99
100
101
102
103
104
105

106
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
...
138
139
140
141
142
143
144

145

146
147
148
149
150
151
152
153
154
155
156

157

158
159
160
161
162
163
164
165
166
167
168

169

170
171
172
173
174
175
176
177
178
179
180

181

182
183
184
185

186

187
188
189
190
191
192

193
194
195
196
197
198
199

200
201
202
203
204

205

206
207
208
209
210
211
212
...
213
214
215
216
217
218
219

220

221

222
223
224
225
226
227

228
229
230
231
232
233
234

235
236
237
238

239
240
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
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
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
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
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
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
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
<title>Fossil File Formats</title>
<h1 align="center">
Fossil File Formats
</h1>

The global state of a fossil repository is kept simple so that it can
endure in useful form for decades or centuries. 
A fossil repository is intended to be readable,
searchable, and extensible by people not yet born.


The global state of a fossil repository is an unordered
set of <i>artifacts</i>.
An artifact might be a source code file, the text of a wiki page,
part of a trouble ticket, or one of several special control artifacts
used to show the relationships between other artifacts within the
project.  Each artifact is normally represented on disk as a separate
file.  Artifacts can be text or binary.



In addition to the global state, 
each fossil repository also contains local state.
The local state consists of web-page formatting
preferences, authorized users, ticket display and reporting formats,
and so forth.  The global state is shared in common among all
repositories for the same project, whereas the local state is often
different in separate repositories.
The local state is not versioned and is not synchronized
with the global state.
The local state is not composed of artifacts and is not intended to be enduring.
This document is concerned with global state only.  Local state is only
mentioned here in order to distinguish it from global state.



Each artifact in the repository is named by its SHA1 hash.
No prefixes or meta information is added to a artifact before
its hash is computed.  The name of a artifact in the repository
is exactly the same SHA1 hash that is computed by sha1sum 
on the file as it exists in your source tree.</p>


Some artifacts have a particular format which gives them special
meaning to fossil.  Fossil recognizes:

<ul>
<li> [#manifest | Manifests] </li>
<li> [#cluster | Clusters] </li>

<li> [#ctrl | Control Artifacts] </li>

<li> [#wikichng | Wiki Pages] </li>
<li> [#tktchng | Ticket Changes] </li>
<li> [#artifact | Artifacts] </li>
</ul>

These five artifact types are described in the sequel.

In the current implementation (as of 2009-01-25) the artifacts that
make up a fossil repository are stored in in as delta- and zlib-compressed
blobs in an <a href="http://www.sqlite.org/">SQLite</a> database.  This
is an implementation detail and might change in a future release.  For
the purpose of this article "file format" means the format of the artifacts,
not how the artifacts are stored on disk.  It is the artifact format that
is intended to be enduring.  The specifics of how artifacts are stored on
disk, though stable, is not intended to live as long as the
artifact format.

<a name="manifest"></a>
<h2>1.0 The Manifest</h2>

A manifest defines a check-in or version of the project
source tree.  The manifest contains a list of artifacts for
each file in the project and the corresponding filenames, as
well as information such as parent check-ins, the name of the
programmer who created the check-in, the date and time when
the check-in was created, and any check-in comments associated
with the check-in.


Any artifact in the repository that follows the syntactic rules
of a manifest is a manifest.  Note that a manifest can
be both a real manifest and also a content file, though this
is rare.



A manifest is a text file.  Newline characters
(ASCII 0x0a) separate the file into "cards".
Each card begins with a single
character "card type".  Zero or more arguments may follow
the card type.  All arguments are separated from each other
and from the card-type character by a single space
character.  There is no surplus white space between arguments
and no leading or trailing whitespace except for the newline 
character that acts as the card separator.



All cards of the manifest occur in strict sorted lexicographical order.
No card may be duplicated.
The entire manifest may be PGP clear-signed, but otherwise it
may contain no additional text or data beyond what is described here.



Allowed cards in the manifest are as follows:


<blockquote>
<b>C</b> <i>checkin-comment</i><br>
<b>D</b> <i>time-and-date-stamp</i><br>
<b>F</b> <i>filename</i> <i>SHA1-hash</i> <i>permissions</i> <i>old-name</i><br>
<b>P</b> <i>SHA1-hash</i>+<br>
<b>R</b> <i>repository-checksum</i><br>
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name  <b>*</b> ?value?</i><br>
<b>U</b> <i>user-login</i><br>
<b>Z</b> <i>manifest-checksum</i>
</blockquote>


A manifest must have exactly one C-card.  The sole argument to
the C-card is a check-in comment that describes the check-in that
the manifest defines.  The check-in comment is text.  The following
escape sequences are applied to the text:
A space (ASCII 0x20) is represented as "\s" (ASCII 0x5C, 0x73).  A
newline (ASCII 0x0a) is "\n" (ASCII 0x6C, x6E).  A backslash 
(ASCII 0x5C) is represented as two backslashes "\\".  Apart from
space and newline, no other whitespace characters are allowed in
the check-in comment.  Nor are any unprintable characters allowed
in the comment.



A manifest must have exactly one D-card.  The sole argument to
the D-card is a date-time stamp in the ISO8601 format.  The
date and time should be in coordinated universal time (UTC).
The format is:


<blockquote>
<i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i>
</blockquote>


A manifest has zero or more F-cards.  Each F-card defines a file
(other than the manifest itself) which is part of the check-in that
the manifest defines.  There are two, three, or four arguments.
The first argument
is the pathname of the file in the check-in relative to the root
of the project file hierarchy.  No ".." or "." directories are allowed
within the filename.  Space characters are escaped as in C-card
................................................................................
permissions associated with the file.  The only special code currently
defined is "x" which means that the file is executable.  All files are
always readable and writable.  This can be expressed by "w" permission
if desired but is optional.
The optional 4th argument is the name of the same file as it existed in
the parent check-in.  If the name of the file is unchanged from its
parent, then the 4th argument is omitted.



A manifest has zero or one P-cards.  Most manifests have one P-card.
The P-card has a varying number of arguments that
defines other manifests from which the current manifest
is derived.  Each argument is an 40-character lowercase 
hexadecimal SHA1 of the predecessor manifest.  All arguments
to the P-card must be unique to that line.
The first predecessor is the direct ancestor of the manifest.
Other arguments define manifests with which the first was
merged to yield the current manifest.  Most manifests have
a P-card with a single argument.  The first manifest in the
project has no ancestors and thus has no P-card.



A manifest may optionally have a single R-card.  The R-card has
a single argument which is the MD5 checksum of all files in 
the check-in except the manifest itself.  The checksum is expressed
as 32-characters of lowercase hexadecimal.   The checksum is
computed as follows:  For each file in the check-in (except for
the manifest itself) in strict sorted lexicographical order, 
take the pathname of the file relative to the root of the
repository, append a single space (ASCII 0x20), the
size of the file in ASCII decimal, a single newline
character (ASCII 0x0A), and the complete text of the file.
Compute the MD5 checksum of the the result.



A manifest might contain one or more T-cards used to set tags or
properties on the check-in.  The format of the T-card is the same as
described in <i>Control Artifacts</i> section below, except that the
second argument is the single characcter "<b>*</b>" instead of an
artifact ID.  The <b>*</b> in place of the artifact ID indicates that
the tag or property applies to the current artifact.  It is not
possible to encode the current artifact ID as part of an artifact,
since the act of inserting the artifact ID would change the artifact ID,
hence a <b>*</b> is used to represent "self".  T-cards are typically
added to manifests in order to set the <b>branch</b> property and a
symbolic name when the check-in is intended to start a new branch.



Each manifest has a single U-card.  The argument to the U-card is
the login of the user who created the manifest.  The login name
is encoded using the same character escapes as is used for the
check-in comment argument to the C-card.



A manifest has an option Z-card as its last line.  The argument
to the Z-card is a 32-character lowercase hexadecimal MD5 hash
of all prior lines of the manifest up to and including the newline 
character that immediately precedes the "Z".  The Z-card is just
a sanity check to prove that the manifest is well-formed and
consistent.


A sample manifest from Fossil itself can be seen
[/artifact/28987096ac | here].

<a name="cluster"></a>
<h2>2.0 Clusters</h2>


A cluster is a artifact that declares the existence of other artifacts.
Clusters are used during repository synchronization to help 
reduce network traffic.  As such, clusters are an optimization and
may be removed from a repository without loss or damage to the
underlying project code.



Clusters follow a syntax that is very similar to manifests.
A Cluster is a line-oriented text file.  Newline characters
(ASCII 0x0a) separate the artifact into cards.  Each card begins with a single
character "card type".  Zero or more arguments may follow
the card type.  All arguments are separated from each other
and from the card-type character by a single space
character.  There is no surplus white space between arguments
................................................................................
and no leading or trailing whitespace except for the newline 
character that acts as the card separator.
All cards of a cluster occur in strict sorted lexicographical order.
No card may be duplicated.
The cluster may not contain additional text or data beyond 
what is described here.
Unlike manifests, clusters are never PGP signed.



Allowed cards in the cluster are as follows:


<blockquote>
<b>M</b> <i>artifact-id</i><br />
<b>Z</b> <i>checksum</i>
</blockquote>


A cluster contains one or more "M" cards followed by a single "Z"
line.  Each M card has a single argument which is the artifact ID of 
another artifact in the repository.  The Z card work exactly like
the Z card of a manifest.  The argument to the Z card is the
lower-case hexadecimal representation of the MD5 checksum of all
prior cards in the cluster.  Note that the Z card is required
on a cluster.


An example cluster from Fossil can be seen
[/artifact/d03dbdd73a2a8 | here].


<a name="ctrl"></a>
<h2>3.0 Control Artifacts</h2>


Control artifacts are used to assign properties to other artifacts
within the repository.  The basic format of a control artifact is
the same as a manifest or cluster.  A control artifact is a text
files divided into cards by newline characters.  Each card has a
single-character card type followed by arguments.  Spaces separate
the card type and the arguments.  No surplus whitespace is allowed.
All cards must occur in strict lexigraphical order.



Allowed cards in a control artifact are as follows:


<blockquote>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name  artifact-id  ?value?</i><br />
<b>U</b> <i>user-name</i><br />
<b>Z</b> <i>checksum</i><br />
</blockquote>


A control artifact must have one D card and one Z card and
one or more T cards.  No other cards or other text is
allowed in a control artifact.  Control artifacts might be PGP
clearsigned.

The D card and the Z card of a control artifact are the same
as in a manifest.

The T card represents a "tag" or property that is applied to
some other artifact.  The T card has two or three values.  The
second argument is the 40 character lowercase artifact ID of the artifact
to which the tag is to be applied. The
first value is the tag name.  The first character of the tag
is either "+", "-", or "*".  A "+" means the tag should be added
to the artifact.  The "-" means the tag should be removed.
The "*" character means the tag should be added to the artifact
and all direct descendants (but not branches) of the artifact down
to but not including the first descendant that contains a 
more recent "-" tag with the same name.
The optional third argument is the value of the tag.  A tag
without a value is a boolean.

When two or more tags with the same name are applied to the
same artifact, the tag with the latest (most recent) date is
used.

Some tags have special meaning.  The "comment" tag when applied
to a check-in will override the check-in comment of that check-in
for display purposes.  The "user" tag overrides the name of the
check-in user.  The "date" tag overrides the check-in date.
The "branch" tag sets the name of the branch that at check-in
belongs to.  Symbolic tags begin with the "sym-" prefix.

The U card is the name of the user that created the control 
artifact.  The Z card is the usual artifact checksum.

An example control artifacts can be seen [/info/9d302ccda8 | here].


<a name="wikichng"></a>
<h2>4.0 Wiki Pages</h2>

A wiki page is an artifact with a format similar to manifests,
clusters, and control artifacts.  The artifact is divided into
cards by newline characters.  The format of each card is as in
manifests, clusters, and control artifacts.  Wiki artifacts accept
the following card types:

<blockquote>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>L</b> <i>wiki-title</i><br />
<b>P</b> <i>parent-artifact-id</i>+<br />
<b>U</b> <i>user-name</i><br />
<b>W</b> <i>size</i> <b>\n</b> <i>text</i> <b>\n</b><br />
<b>Z</b> <i>checksum</i>
</blockquote>

The D card is the date and time when the wiki page was edited.
The P card specifies the parent wiki pages, if any.  The L card
gives the name of the wiki page.  The U card specifies the login
of the user who made this edit to the wiki page.  The Z card is
the usual checksum over the either artifact.

The W card is used to specify the text of the wiki page.  The
argument to the W card is an integer which is the number of bytes
of text in the wiki page.  That text follows the newline character
that terminates the W card.  The wiki text is always followed by one
extra newline.

An example wiki artifact can be seen
[/artifact/7b2f5fd0e0 | here].

<a name="tktchng"></a>
<h2>5.0 Ticket Changes</h2>

A ticket-change artifact represents a change to a trouble ticket.
The following cards are allowed on a ticket change artifact:

<blockquote>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>J</b> ?<b>+</b>?<i>name</i> ?<i>value</i>?<br />
<b>K</b> <i>ticket-id</i><br />
<b>U</b> <i>user-name</i><br />
<b>Z</b> <i>checksum</i>
</blockquote>


The D card is the usual date and time stamp and represents the point
in time when the change was entered.  The U card is the login of the
programmer who entered this change.  The Z card is the checksum over
the entire artifact.


Every ticket has a unique ID.  The ticket to which this change is applied
is specified by the K card.  A ticket exists if it contains one or
more changes.  The first "change" to a ticket is what brings the
ticket into existence.


J cards specify changes to the "value" of "fields" in the ticket.
If the <i>value</i> parameter of the J card is omitted, then the
field is set to an empty string.
Each fossil server has a ticket configuration which specifies the fields its
understands.  The ticket configuration is part of the local state for
the repository and thus can vary from one repository to another.
Hence a J card might specify a <i>field</i> that do not exist in the 
local ticket configuration.  If a J card specifies a <i>field</i> that
is not in the local configuration, then that J card
is simply ignored.


The first argument of the J card is the field name.  The second
value is the field value.  If the field name begins with "+" then
the value is appended to the prior value.  Otherwise, the value
on the J card replaces any previous value of the field.
The field name and value are both encoded using the character
escapes defined for the C card of a manifest.

An example ticket-change artifact can be seen
[/artifact/91f1ec6af053 | here].

<a name="attachment"></a>
<h2>6.0 Attachments</h2>

An attachment artifact associates some other artifact that is the
attachment (the source artifact) with a ticket or wiki page to which
the attachment is connected (the target artifact).
The following cards are allowed on an attachment artifact:

<blockquote>
<b>A</b> <i>filename target</i> ?<i>source</i>?
<b>C</b> <i>comment</i><br>
<b>D</b> <i>time-and-date-stamp</i><br />
<b>U</b> <i>user-name</i><br />
<b>Z</b> <i>checksum</i>
</blockquote>

The A card specifies a filename for the attachment in its first argument.
The second argument to the A card is the name
of the wiki page or ticket to which the attachment is connected.  The
third argument is either missing or else it is the 40-character artifact 
ID of the attachment itself.  A missing third argument means that the
attachment should be deleted.

The C card is an optional comment describing what the attachment is about.
The C card is optional, but there can only be one.

A single D card is required to give the date and time when the attachment
was applied.

A single U card gives the name of the user to added the attachment.
If an attachment is added anonymously, then the U card may be omitted.

The Z card is the usual checksum over the rest of the attachment artifact.


<a name="summary"></a>
<h2>7.0 Card Summary</h2>

The following table summaries the various kinds of cards that
appear on Fossil artifacts:

<table border=1 width="100%">
<tr>
<th rowspan=2 valign=bottom>Card Format</th>
<th colspan=6>Used By</th>
</tr>
<tr>
<th>Manifest</th>
<th>Cluster</th>
<th>Control</th>
<th>Wiki</th>
<th>Ticket</th>
<th>Attachment</th>
</tr>
<tr>
<td><b>A</b> <i>filename target source</i></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>X</b></td>
</tr>
<tr>
<td><b>C</b> <i>coment-text</i></td>
<td align=center><b>X</b></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td align=center><b>X</b></td>
</tr>
<tr>
<td><b>D</b> <i>date-time-stamp</i></td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
</tr>
<tr>
<td><b>F</b> <i>filename uuid permissions oldname</i></td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>J</b> <i>name value</i></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>K</b> <i>ticket-uuid</i></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>L</b> <i>wiki-title</i></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>M</b> <i>uuid</i></td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>P</b> <i>uuid ...</i></td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>R</b> <i>md5sum</i></td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<tr>
<td><b>T</b> (<b>+</b>|<b>*</b>|<b>-</b>)<i>tagname uuid value</i></td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>U</b> <i>username</i></td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
</tr>
<tr>
<td><b>W</b> <i>size</i></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
<td align=center><b>X</b></td>
<td align=center>&nbsp;</td>
<td align=center>&nbsp;</td>
</tr>
<tr>
<td><b>Z</b> <i>md5sum</i></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
<td align=center><b>X</b></td>
</tr>
</table>

Changes to www/mkdownload.tcl.

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





58
59

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

76
77
78
79
80
Click on links below to download prebuilt binaries and source tarballs for 
recent versions of <a href="/fossil">Fossil</a>.
The historical source code is also available in the
<a href="/fossil/doc/tip/www/selfhost.wiki">self-hosting
Fossil repositories</a>.
</p>











<table cellpadding="5">
}

proc Product {pattern desc} {


  set flist [glob -nocomplain download/$pattern]
  foreach file [lsort -dict $flist] {
    set file [file tail $file]
    if {![regexp -- {-([0-9]+)\.} $file all version]} continue
    set mtime [file mtime download/$file]
    set date [clock format $mtime -format {%Y-%m-%d %H:%M:%S UTC} -gmt 1]




























    set size [file size download/$file]
    set units bytes
    if {$size>1024*1024} {
      set size [format %.2f [expr {$size/(1024.0*1024.0)}]]
      set units MiB
    } elseif {$size>1024} {
      set size [format %.2f [expr {$size/(1024.0)}]]
      set units KiB
    }
    puts "<tr><td width=\"10\"></td>"
    puts "<td valign=\"top\" align=\"right\">"
    puts "<a href=\"download/$file\">$file</a></td>"
    puts "<td width=\"5\"></td>"
    regsub -all VERSION $desc $version d2
    puts "<td valign=\"top\">[string trim $d2].<br>Size: $size $units.<br>"
    puts "Created: $date</td></tr>"





  }
}


Product fossil-linux-x86-*.zip {
  Prebuilt fossil binary version [VERSION] for Linux on x86
}
Product fossil-linux-amd64-*.zip {
  Prebuilt fossil binary version [VERSION] for Linux on amd64
}
Product fossil-macosx-x86-*.zip {
  Prebuilt fossil binary version [VERSION] for MacOSX on x86
}
Product fossil-w32-*.zip {
  Prebuilt fossil binary version [VERSION] for windows
}
Product fossil-src-*.tar.gz {
  Source code tarball for fossil version [VERSION]
}


puts {</table>
</body>
</html>
}







>
>
>
>
>
>
>
>
>
>
|


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





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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84







85
86
87
88
89
90
91
92
93















94
95
96
97
98
99
Click on links below to download prebuilt binaries and source tarballs for 
recent versions of <a href="/fossil">Fossil</a>.
The historical source code is also available in the
<a href="/fossil/doc/tip/www/selfhost.wiki">self-hosting
Fossil repositories</a>.
</p>

<p>
<u>Important Note:</u>
After upgrading to a newer version of fossil, it is always a good idea
to run:
<blockquote><pre>
<b><big><tt>fossil all rebuild</tt></big></b>
</pre></blockquote>
Running "rebuild" this way is not always necessary, but it never hurts.
</p>

<table cellpadding="10">
}


# Find all all unique timestamps.
#
foreach file [glob -nocomplain download/fossil-*.zip] {





  if {[regexp {(\d+).zip$} $file all datetime]
       && [string length $datetime]==14} {
    set adate($datetime) 1
  }
}

# Do all dates from newest to oldest
#
foreach datetime [lsort -decr [array names adate]] {
  set dt [string range $datetime 0 3]-[string range $datetime 4 5]-
  append dt "[string range $datetime 6 7] "
  append dt "[string range $datetime 8 9]:[string range $datetime 10 11]:"
  append dt "[string range $datetime 12 13]"
  set link [string map {{ } +} $dt]
  set hr http://www.fossil-scm.org/fossil/timeline?c=$link&y=ci
  puts "<tr><td colspan=5 align=center><hr>"
  puts "<b>Fossil snapshot as of <a href=\"$hr\">$dt</a><td width=30></b>"
  puts "</td></tr>"
  
  foreach {prefix suffix img desc} {
    fossil-linux-x86 zip linux.gif {Linux x86}
    fossil-linux-amd64 zip linux64.gif {Linux x86_64}
    fossil-macosx-x86 zip mac.gif {Mac 10.5 x86}
    fossil-w32 zip win32.gif {Windows}
    fossil-src tar.gz src.gif {Source Tarball}
  } {
    set filename download/$prefix-$datetime.$suffix
    if {[file exists $filename]} {
      set size [file size $filename]
      set units bytes
      if {$size>1024*1024} {
        set size [format %.2f [expr {$size/(1024.0*1024.0)}]]
        set units MiB
      } elseif {$size>1024} {
        set size [format %.2f [expr {$size/(1024.0)}]]
        set units KiB
      }







      puts "<td align=center valign=bottom><a href=\"$filename\">"
      puts "<img src=\"build-icons/$img\" border=0><br>$desc</a><br>"
      puts "$size $units</td>"
    } else {
      puts "<td>&nbsp;</td>"
    }
  }
  puts "</tr>"
}















puts "<tr><td colspan=5><hr></td></tr>"

puts {</table>
</body>
</html>
}

Changes to www/quickstart.wiki.

65
66
67
68
69
70
71
72
73
74
75
76
77


78
79

80
81
82
83
84
85
86
87
    local configuration.  This is easily accomplished using the webserver
    that is built into fossil.  Start the fossil webserver like this:</p>
    
    <blockquote>
    <b>fossil ui </b><i> repository-filename</i>
    </blockquote>

    <p>This starts a webserver listening on port 8080.  You can
    specify a different port using the <b>-port</b> option on the command-line.
    After the server is running, fossil will then attempt to launch your
    web browser and make it point to this web server.  If your system
    has an unusual configuration, fossil might not be able to figure out
    how to start your web browser.  In that case, start the web browser


    yourself and point it at http://localhost:8080/.  Click on the
    "Admin" link on the menu bar to start configuring your repository.</p>

    
    <p>By default, fossil does not require a login for HTTP connections
    coming in from the IP loopback address 127.0.0.1.  You can, and perhaps
    should, change this after you create a few users.</p>
    
    <p>When you are finished configuring, just press Control-C or use
    the <b>kill</b> command to shut down the mini-server.</p>








|
<
<
|

|
>
>
|
|
>
|







65
66
67
68
69
70
71
72


73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    local configuration.  This is easily accomplished using the webserver
    that is built into fossil.  Start the fossil webserver like this:</p>
    
    <blockquote>
    <b>fossil ui </b><i> repository-filename</i>
    </blockquote>

    <p>This starts a web server then automatically launches your


    web browser and makes it point to this web server.  If your system
    has an unusual configuration, fossil might not be able to figure out
    how to start your web browser.  In that case, first tell fossil
    where to find your web browser using a command like this:</p>

    <blockquote>
    <b>fossil setting web-browser </b><i>  path-to-web-browser</i>
    </blockquote>
   
    <p>By default, fossil does not require a login for HTTP connections
    coming in from the IP loopback address 127.0.0.1.  You can, and perhaps
    should, change this after you create a few users.</p>
    
    <p>When you are finished configuring, just press Control-C or use
    the <b>kill</b> command to shut down the mini-server.</p>

Changes to www/webui.wiki.

146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
  80 stream tcp nowait.1000 root /usr/local/bin/fossil \
  /usr/local/bin/fossil http /home/www/sample-project.fossil 
  </verbatim>

As always, you'll want to adjust the pathnames to whatever is appropriate
for your system.  The xinetd setup uses a different syntax but follows
the same idea.

Once you have your new repository running on the network server, delete
the original repository from your local machine, then clone the repository
off of the server:

  <b>fossil clone http://user:password@myserver.org/cgi-bin/my-project</b>

(As always, adjust the URL as appropriate for your installation.)
After copying a repository, it is important to reclone it onto new machines.
Each repository has a random "repository ID" and repositories will not
sync with another repository having the same ID (to avoid sync loops).
Cloning the repository will give you a new repository ID in your local
copy and allow you to sync with the server.







<
<
<
<
<
<
<
<
<
<
<
<
<
146
147
148
149
150
151
152













  80 stream tcp nowait.1000 root /usr/local/bin/fossil \
  /usr/local/bin/fossil http /home/www/sample-project.fossil 
  </verbatim>

As always, you'll want to adjust the pathnames to whatever is appropriate
for your system.  The xinetd setup uses a different syntax but follows
the same idea.













Changes to www/wikitheory.wiki.

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   *  Description and comments in [./bugtheory.wiki | bug reports].
   *  Check-in comments.
   *  [./embeddeddoc.wiki | Embedded documentation] files whose
      name ends in "wiki".

The [/wiki_rules | formatting rules] for fossil wiki
are designed to be simple and intuitive.  The idea is that wiki provides
paragaph breaks, numbered and bulletted lists, and hyperlinking for
simple documents together with a safe subset of HTML for more complex
formatting tasks.

Some commentators feel that the use of HTML is a mistake and that
fossil should use the markup language of the
<i>fill-in-your-favorite</i> wiki engine instead.  That approach
was considered but was rejected for the following reasons:







|







6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
   *  Description and comments in [./bugtheory.wiki | bug reports].
   *  Check-in comments.
   *  [./embeddeddoc.wiki | Embedded documentation] files whose
      name ends in "wiki".

The [/wiki_rules | formatting rules] for fossil wiki
are designed to be simple and intuitive.  The idea is that wiki provides
paragraph breaks, numbered and bulletted lists, and hyperlinking for
simple documents together with a safe subset of HTML for more complex
formatting tasks.

Some commentators feel that the use of HTML is a mistake and that
fossil should use the markup language of the
<i>fill-in-your-favorite</i> wiki engine instead.  That approach
was considered but was rejected for the following reasons: